PluginProbe
ShopBuilder – WooCommerce Builder For Elementor / 2.6.5
ShopBuilder – WooCommerce Builder For Elementor v2.6.5
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 +479 -39 2.1.13 → 2.6.5 View file →
@@ -45,13 +45,12 @@
45 45 return false;
46 46 }
47 47
48 48 public static function get_nonce() {
49 - // phpcs:ignore WordPress.Security.NonceVerification.Recommended
49 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
50 50 return isset( $_REQUEST[ rtsb()->nonceId ] ) ? sanitize_text_field( $_REQUEST[ rtsb()->nonceId ] ) : null;
51 51 }
52 52
53 -
54 53 /**
55 54 * Set Cookie or Session
56 55 *
57 56 * @param $name
@@ -60,9 +59,8 @@
60 59 public static function setSession( $name, $value ) {
61 60 if ( ! headers_sent() && session_status() == PHP_SESSION_NONE ) {
62 61 session_start();
63 62 $_SESSION[ $name ] = $value;
64 - // session_write_close();
65 63 } else {
66 64 $_SESSION[ $name ] = $value;
67 65 }
68 66 }
@@ -76,9 +74,9 @@
76 74 public static function getSession( $name ) {
77 75 if ( ! headers_sent() && session_status() == PHP_SESSION_NONE ) {
78 76 session_start();
79 77 }
80 - return $_SESSION[ $name ] ?? null;
78 + return $_SESSION[ $name ] ?? null; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
81 79 }
82 80 /**
83 81 * Remove Session Variable
84 82 *
@@ -87,9 +85,8 @@
87 85 public static function removeSession( $name ) {
88 86 if ( ! headers_sent() && session_status() == PHP_SESSION_NONE ) {
89 87 session_start();
90 88 unset( $_SESSION[ $name ] );
91 - // session_write_close();
92 89 } else {
93 90 unset( $_SESSION[ $name ] );
94 91 }
95 92 }
@@ -116,9 +113,46 @@
116 113 }
117 114
118 115 return false;
119 116 }
117 + /**
118 + * Get all user roles.
119 + *
120 + * @return array|false|mixed
121 + */
122 + public static function get_current_user_roles() {
123 + $current_user = wp_get_current_user();
124 + return $current_user->roles;
125 + }
126 + /**
127 + * Get all user roles.
128 + *
129 + * @return array|false|mixed
130 + */
131 + public static function get_all_user_roles() {
132 + $cache_key = 'rtsb_get_user_roles';
133 + $roles = wp_cache_get( $cache_key, 'shopbuilder' );
120 134
135 + if ( empty( $roles ) ) {
136 + if ( ! function_exists( 'get_editable_roles' ) ) {
137 + require_once ABSPATH . 'wp-admin/includes/user.php';
138 + }
139 +
140 + $user_roles = \get_editable_roles();
141 + $roles = [];
142 +
143 + foreach ( $user_roles as $key => $role ) {
144 + if ( empty( $role['capabilities'] ) ) {
145 + continue;
146 + }
147 + $roles[ $key ] = $role['name'];
148 + }
149 + }
150 + wp_cache_set( $cache_key, $roles, 'shopbuilder' );
151 + Cache::set_data_cache_key( $cache_key );
152 + return $roles;
153 + }
154 +
121 155 /**
122 156 * @param $data
123 157 *
124 158 * @return mixed|string
@@ -123,14 +157,25 @@
123 157 *
124 158 * @return mixed|string
125 159 */
126 160 public static function stripslashes_value( $data ) {
127 - if ( is_string( $data ) && strpos( $data, '\\' ) !== false ) {
128 - return stripslashes( $data );
129 - } elseif ( is_array( $data ) ) {
161 + if ( is_array( $data ) ) {
130 162 foreach ( $data as $key => $value ) {
131 163 $data[ $key ] = self::stripslashes_value( $value );
132 164 }
165 + } elseif ( is_string( $data ) ) {
166 + $trimmed = trim( $data );
167 + // Try to decode JSON.
168 + $json = json_decode( $trimmed, true );
169 + if ( json_last_error() === JSON_ERROR_NONE && is_array( $json ) ) {
170 + // Recursively stripslashes on decoded array.
171 + $json = self::stripslashes_value( $json );
172 + return wp_json_encode( $json, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE );
173 + }
174 + // Not valid JSON, just stripslashes if needed.
175 + if ( strpos( $data, '\\' ) !== false ) {
176 + return stripslashes( $data );
177 + }
133 178 }
134 179
135 180 return $data;
136 181 }
@@ -242,10 +287,19 @@
242 287 *
243 288 * @return false|string|void
244 289 */
245 290 public static function load_template( $template_name, array $args = null, $return = false, $template_path = '', $plugin_path = '' ) {
246 - $located = self::locate_template( $template_name, $template_path, $plugin_path );
247 -
291 + $cache_key = sanitize_key( implode( '-', [ 'template', $template_name, $template_path ] ) );
292 + $located = (string) wp_cache_get( $cache_key, 'shopbuilder' );
293 + if ( ! $located ) {
294 + $located = self::locate_template( $template_name, $template_path, $plugin_path );
295 + // Don't cache the absolute path so that it can be shared between web servers with different paths.
296 + $cache_path = wc_tokenize_path( $located, wc_get_path_define_tokens() );
297 + Cache::set_template_cache( $cache_key, $cache_path );
298 + } else {
299 + // Make sure that the absolute path to the template is resolved.
300 + $located = wc_untokenize_path( $located, wc_get_path_define_tokens() );
301 + }
248 302 if ( ! file_exists( $located ) ) {
249 303 // translators: %s template.
250 304 self::doing_it_wrong( __FUNCTION__, sprintf( __( '%s does not exist.', 'shopbuilder' ), '<code>' . $located . '</code>' ), '1.0' );
251 305
@@ -253,9 +307,9 @@
253 307 }
254 308
255 309 if ( ! empty( $args ) && is_array( $args ) ) {
256 310 $atts = $args;
257 - extract( $args ); // @codingStandardsIgnoreLine
311 + extract( $args ); // @codingStandardsIgnoreLine
258 312 }
259 313
260 314 // Allow 3rd party plugin filter template file from their plugin.
261 315 $located = apply_filters( 'rtsb/core/get_template', $located, $template_name, $args );
@@ -370,9 +424,9 @@
370 424 // Return the Builder Template id.
371 425
372 426 if ( get_post_type( get_the_ID() ) == BuilderFns::$post_type_tb ) {
373 427 $product_id = get_post_meta( get_the_ID(), BuilderFns::$product_template_meta, true );
374 - if ( $product_id ) {
428 + if ( $product_id && get_post_status( $product_id ) ) {
375 429 return $product_id;
376 430 }
377 431 }
378 432
@@ -377,16 +431,16 @@
377 431 }
378 432
379 433 global $wpdb;
380 434 $cache_key = 'rtsb_prepared_product_id';
381 - $_post_id = wp_cache_get( $cache_key );
382 -
435 + $_post_id = wp_cache_get( $cache_key, 'shopbuilder' );
383 436 if ( false === $_post_id || 'publish' !== get_post_status( $_post_id ) ) {
384 - // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
437 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
385 438 $_post_id = $wpdb->get_var(
386 439 $wpdb->prepare( "SELECT MAX(ID) FROM {$wpdb->prefix}posts WHERE post_type = %s AND post_status IN('publish', 'draft')", 'product' )
387 440 );
388 - wp_cache_set( $cache_key, $_post_id, '', 12 * HOUR_IN_SECONDS );
441 + wp_cache_set( $cache_key, $_post_id, 'shopbuilder', 12 * HOUR_IN_SECONDS );
442 + Cache::set_data_cache_key( $cache_key );
389 443 }
390 444
391 445 return $_post_id;
392 446 }
@@ -422,8 +476,9 @@
422 476 *
423 477 * @return void
424 478 */
425 479 public static function doing_it_wrong( $function, $message, $version ) {
480 + // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_wp_debug_backtrace_summary
426 481 $message .= ' Backtrace: ' . wp_debug_backtrace_summary();
427 482 _doing_it_wrong( esc_html( $function ), wp_kses_post( $message ), esc_html( $version ) );
428 483 }
429 484
@@ -557,9 +612,9 @@
557 612
558 613 if ( strlen( $page_content ) > 0 ) {
559 614 // Search for an existing page with the specified page content (typically a shortcode).
560 615 $shortcode = str_replace( [ '<!-- wp:shortcode -->', '<!-- /wp:shortcode -->' ], '', $page_content );
561 - // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
616 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
562 617 $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}%" ) );
563 618 } else {
564 619 // Search for an existing page with the specified page slug.
565 620 $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
@@ -961,13 +1016,15 @@
961 1016 return $allowed_html;
962 1017 }
963 1018
964 1019 /**
965 - * Safe print a validated HTML tag.
1020 + * Safe get a validated HTML tag.
966 1021 *
967 - * @param string $tag
1022 + * @param string $tag HTML tag.
1023 + *
1024 + * @return string
968 1025 */
969 - public static function print_validated_html_tag( $tag ) {
1026 + public static function get_validated_html_tag( $tag ) {
970 1027 $allowed_html_wrapper_tags = [
971 1028 'a',
972 1029 'article',
973 1030 'aside',
@@ -987,12 +1044,23 @@
987 1044 'section',
988 1045 'span',
989 1046 ];
990 1047
991 - self::print_html( in_array( strtolower( $tag ), $allowed_html_wrapper_tags, true ) ? $tag : 'div' );
1048 + return in_array( strtolower( $tag ), $allowed_html_wrapper_tags, true ) ? $tag : 'div';
992 1049 }
993 1050
994 1051 /**
1052 + * Safe print a validated HTML tag.
1053 + *
1054 + * @param string $tag HTML tag.
1055 + *
1056 + * @return void
1057 + */
1058 + public static function print_validated_html_tag( $tag ) {
1059 + self::print_html( self::get_validated_html_tag( $tag ) );
1060 + }
1061 +
1062 + /**
995 1063 * Insert Some array element
996 1064 *
997 1065 * @param [type] $key The elements will insert nearby this key.
998 1066 * @param [type] $main_array Original array.
@@ -1113,8 +1181,25 @@
1113 1181 }
1114 1182 self::$cache[ $cache_key ] = $terms;
1115 1183 return $terms;
1116 1184 }
1185 + /**
1186 + * Get all product attribute taxonomy by id
1187 + *
1188 + * @param array $term_ids Term id.
1189 + *
1190 + * @return array
1191 + */
1192 + public static function tax_filter_attr_taxonomy( $term_ids ) {
1193 + $taxonomies = [];
1194 + foreach ( $term_ids as $id ) {
1195 + $term = get_term( $id );
1196 + if ( ! is_wp_error( $term ) && $term ) {
1197 + $taxonomies[] = $term->taxonomy;
1198 + }
1199 + }
1200 + return array_unique( $taxonomies );
1201 + }
1117 1202
1118 1203 /**
1119 1204 * Get all terms by attributes
1120 1205 *
@@ -1298,9 +1383,9 @@
1298 1383
1299 1384 if ( $args['show_rating_count'] ) {
1300 1385 $count .= '<div class="rtsb-count">';
1301 1386 $count .= sprintf(
1302 - /* translators: %s is the number of reviews */
1387 + /* translators: %s is the number of reviews */
1303 1388 _n( '%s Review', '%s Reviews', $rating_count, 'shopbuilder' ),
1304 1389 $rating_count
1305 1390 );
1306 1391 $count .= '</div>';
@@ -1438,9 +1523,42 @@
1438 1523
1439 1524 <?php
1440 1525 self::print_html( ob_get_clean() );
1441 1526 }
1527 + /**
1528 + * Brands HTML
1529 + *
1530 + * @param int $id Product ID.
1531 + * @param string $class Custom class.
1532 + *
1533 + * @return void|string
1534 + */
1535 + public static function get_brands_list( $id, $class = 'rtsb-brand-outline' ) {
1536 + $terms = get_the_terms( $id, 'product_brand' );
1537 + if ( empty( $id ) || empty( $terms ) || is_wp_error( $terms ) ) {
1538 + return '';
1539 + }
1540 + ob_start();
1541 + ?>
1442 1542
1543 + <ul class="rtsb-brand-list <?php echo esc_attr( $class ); ?>">
1544 + <?php
1545 + $brand_list = get_the_term_list(
1546 + $id,
1547 + 'product_brand',
1548 + '<li class="rtsb-brand-list-item">',
1549 + '</li><li class="rtsb-brand-list-item">',
1550 + '</li>'
1551 + );
1552 +
1553 + self::print_html( $brand_list );
1554 + ?>
1555 + </ul>
1556 +
1557 + <?php
1558 + self::print_html( ob_get_clean() );
1559 + }
1560 +
1443 1561 /**
1444 1562 * Get Featured Image HTML.
1445 1563 *
1446 1564 * @param string $type Image type.
@@ -1786,9 +1904,9 @@
1786 1904 $percentage = null;
1787 1905 $max_percentage = '';
1788 1906
1789 1907 if ( $product->is_type( 'simple' ) ) {
1790 - $max_percentage = ( ( $product->get_regular_price() - $product->get_sale_price() ) / $product->get_regular_price() ) * 100;
1908 + $max_percentage = $product->get_sale_price() ? ( ( $product->get_regular_price() - $product->get_sale_price() ) / $product->get_regular_price() ) * 100 : 0;
1791 1909 } elseif ( $product->is_type( 'variable' ) ) {
1792 1910 $max_percentage = 0;
1793 1911
1794 1912 foreach ( $product->get_children() as $child_id ) {
@@ -2143,9 +2261,9 @@
2143 2261 $link['social_action'] = 'Share';
2144 2262 break;
2145 2263 case 'reddit':
2146 2264 $link['link'] = esc_url( 'https://reddit.com/submit?url=' . $link['url'] . '&title=' . $link['title'] );
2147 - $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>';
2265 + $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>';
2148 2266 $link['attr_title'] = esc_html__( 'Share on Reddit', 'shopbuilder' );
2149 2267 $link['social_network'] = 'Reddit';
2150 2268 $link['social_action'] = 'Share';
2151 2269 break;
@@ -2296,12 +2414,40 @@
2296 2414 $field = $fields[ $raw_option_key ];
2297 2415 if ( $field['type'] === 'switch' ) {
2298 2416 $value = 'on' === $raw_value ? 'on' : '';
2299 2417 } elseif ( 'repeaters' === $field['type'] ) {
2300 - $value = stripslashes_deep( $raw_value );
2418 + $the_value = [];
2419 + $rep_title = [];
2420 + if ( ! empty( $raw_value ) && is_array( $raw_value ) ) {
2421 + foreach ( $raw_value as $key => $value ) {
2422 + if ( is_string( $value ) ) {
2423 + $the_value_decoded = json_decode( stripslashes_deep( $value ) );
2424 + } else {
2425 + $the_value_decoded = $value;
2426 + }
2427 + $cr = $the_value_decoded->title ?? '';
2428 + if ( in_array( $cr, $rep_title, true ) ) {
2429 + continue;
2430 + }
2431 + $rep_title[] = $cr;
2432 + $the_value[] = $the_value_decoded;
2433 + }
2434 + } elseif ( ! empty( $raw_value ) && is_string( $raw_value ) ) {
2435 + $the_value = $raw_value;
2436 + }
2437 + $value = wp_json_encode( $the_value );
2438 + } elseif ( in_array( $field['type'], [ 'product_addons_special_settings', 'checkout_fields' ] ) ) {
2439 + $manual_field_value = [];
2440 + if ( is_array( $raw_value ) ) {
2441 + foreach ( $raw_value as $key => $value ) {
2442 + $manual_field_value[] = json_decode( stripslashes( $value ), true );
2443 + }
2444 + $value = wp_json_encode( $manual_field_value );
2445 + } elseif ( is_string( $raw_value ) ) {
2446 + $value = $raw_value;
2447 + }
2301 2448 } else {
2302 2449 if ( ! empty( $field['multiple'] ) || in_array( $field['type'], [ 'checkbox', 'search_and_multi_select' ] ) ) {
2303 -
2304 2450 if ( isset( $raw_value ) && is_array( $raw_value ) ) {
2305 2451 if ( ! empty( $field['sanitize_fn'] ) && is_callable( $field['sanitize_fn'] ) ) {
2306 2452 $value = array_map( $field['sanitize_fn'], $raw_value );
2307 2453 } else {
@@ -2377,15 +2523,60 @@
2377 2523 ];
2378 2524
2379 2525 if ( 'product_cat' === $taxonomy ) {
2380 2526 $args['product_category_id'] = $terms;
2527 + } elseif ( 'product_brand' === $taxonomy ) {
2528 + $args['product_brand_id'] = $terms;
2381 2529 } else {
2382 2530 $args['product_tag_id'] = $terms;
2383 2531 }
2384 2532
2385 2533 $query = new WC_Product_Query( $args );
2534 + return ! empty( $query->get_products() ) ? count( $query->get_products() ) : 0;
2535 + }
2536 + public static function count_products_by_attribute_terms( $term_ids, $relation = 'AND' ) {
2537 + if ( empty( $term_ids ) || ! is_array( $term_ids ) ) {
2538 + return 0;
2539 + }
2386 2540
2387 - return ! empty( $query->get_products() ) ? count( $query->get_products() ) : 0;
2541 + $terms_by_taxonomy = [];
2542 +
2543 + foreach ( $term_ids as $term_id ) {
2544 + $term = get_term( $term_id );
2545 + if ( ! is_wp_error( $term ) && $term ) {
2546 + if ( ! isset( $terms_by_taxonomy[ $term->taxonomy ] ) ) {
2547 + $terms_by_taxonomy[ $term->taxonomy ] = [];
2548 + }
2549 + $terms_by_taxonomy[ $term->taxonomy ][] = $term_id;
2550 + }
2551 + }
2552 +
2553 + if ( empty( $terms_by_taxonomy ) ) {
2554 + return 0;
2555 + }
2556 +
2557 + $args = [
2558 + 'status' => 'publish',
2559 + 'limit' => -1,
2560 + 'return' => 'ids',
2561 + 'tax_query' => [ // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query
2562 + 'relation' => strtoupper( $relation ),
2563 + ],
2564 + ];
2565 +
2566 + foreach ( $terms_by_taxonomy as $taxonomy => $terms ) {
2567 + $args['tax_query'][] = [
2568 + 'taxonomy' => $taxonomy,
2569 + 'field' => 'term_id',
2570 + 'terms' => $terms,
2571 + 'operator' => 'IN',
2572 + ];
2573 + }
2574 +
2575 + $query = new WC_Product_Query( $args );
2576 + $products = $query->get_products();
2577 +
2578 + return count( $products );
2388 2579 }
2389 2580
2390 2581 /**
2391 2582 * Check if product filters widget has ajax.
@@ -2464,17 +2655,71 @@
2464 2655
2465 2656 if ( isset( self::$cache[ $cache_key ] ) ) {
2466 2657 return self::$cache[ $cache_key ];
2467 2658 }
2659 + $results = wp_cache_get( $cache_key, 'shopbuilder' );
2660 + if ( ! $results ) {
2661 + global $wpdb;
2662 + $sql = "SELECT t.term_id, t.name
2663 + FROM {$wpdb->terms} t
2664 + JOIN {$wpdb->term_taxonomy} tt ON t.term_id = tt.term_id
2665 + WHERE tt.taxonomy = 'product_cat'";
2468 2666
2469 - $results = wp_cache_get( $cache_key );
2667 + if ( $search_query ) {
2668 + $sql .= ' AND t.name LIKE %s';
2669 + $prepared_sql = $wpdb->prepare( $sql, '%' . $wpdb->esc_like( $search_query ) . '%' ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
2670 + } else {
2671 + $prepared_sql = $sql; // No need for placeholders.
2672 + }
2470 2673
2674 + $results = $wpdb->get_results( $prepared_sql ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.NotPrepared
2675 + // Cache the results in the object cache for future use.
2676 + wp_cache_set( $cache_key, $results, 'shopbuilder' ); // Adjust the expiration time as needed.
2677 + Cache::set_data_cache_key( $cache_key );
2678 + }
2679 +
2680 + $cats = [];
2681 + if ( ! is_array( $results ) && ! count( $results ) ) {
2682 + return $cats;
2683 + }
2684 + foreach ( $results as $row ) {
2685 + $category_id = $row->term_id;
2686 + $category_title = $row->name;
2687 +
2688 + $cats[] = [
2689 + 'value' => $category_id,
2690 + 'label' => $category_title,
2691 + ];
2692 + }
2693 +
2694 + // Cache the results in a static variable for the next call.
2695 + self::$cache[ $cache_key ] = $cats;
2696 + return $cats;
2697 + }
2698 + /**
2699 + * Get WooCommerce product categories.
2700 + *
2701 + * @param string|null $search_query The search string for category names.
2702 + *
2703 + * @return array An array of product categories with 'value' and 'label' keys.
2704 + */
2705 + public static function products_tags_query( $search_query = null ) {
2706 + if ( ! is_admin() ) {
2707 + return [];
2708 + }
2709 +
2710 + $cache_key = 'rtsb_product_tags_' . md5( serialize( $search_query ) );
2711 +
2712 + if ( isset( self::$cache[ $cache_key ] ) ) {
2713 + return self::$cache[ $cache_key ];
2714 + }
2715 + $results = wp_cache_get( $cache_key, 'shopbuilder' );
2471 2716 if ( ! $results ) {
2472 2717 global $wpdb;
2473 2718 $sql = "SELECT t.term_id, t.name
2474 2719 FROM {$wpdb->terms} t
2475 2720 JOIN {$wpdb->term_taxonomy} tt ON t.term_id = tt.term_id
2476 - WHERE tt.taxonomy = 'product_cat'";
2721 + WHERE tt.taxonomy = 'product_tag'";
2477 2722
2478 2723 if ( $search_query ) {
2479 2724 $sql .= ' AND t.name LIKE %s';
2480 2725 $prepared_sql = $wpdb->prepare( $sql, '%' . $wpdb->esc_like( $search_query ) . '%' ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
@@ -2483,9 +2728,10 @@
2483 2728 }
2484 2729
2485 2730 $results = $wpdb->get_results( $prepared_sql ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.NotPrepared
2486 2731 // Cache the results in the object cache for future use.
2487 - wp_cache_set( $cache_key, $results ); // Adjust the expiration time as needed.
2732 + wp_cache_set( $cache_key, $results, 'shopbuilder' ); // Adjust the expiration time as needed.
2733 + Cache::set_data_cache_key( $cache_key );
2488 2734 }
2489 2735
2490 2736 $cats = [];
2491 2737 if ( ! is_array( $results ) && ! count( $results ) ) {
@@ -2589,11 +2835,12 @@
2589 2835 $query_results = get_posts( $args );
2590 2836 foreach ( $query_results as $post ) {
2591 2837 $posts[] = [
2592 2838 'value' => $post->ID,
2593 - 'label' => $post->post_title . ' (' . $post->ID . ')',
2839 + 'label' => $post->post_title . ' (ID#' . $post->ID . ')',
2594 2840 ];
2595 2841 }
2842 +
2596 2843 if ( $search_query ) {
2597 2844 if ( strpos( '-error 404', $search_query ) ) {
2598 2845 $posts[] = [
2599 2846 'value' => 'error',
@@ -2600,12 +2847,14 @@
2600 2847 'label' => __( '404 Error', 'shopbuilder' ),
2601 2848 ];
2602 2849 }
2603 2850 } else {
2604 - $posts[] = [
2605 - 'value' => 'error',
2606 - 'label' => __( '404 Error', 'shopbuilder' ),
2607 - ];
2851 + if ( 'page' === $args['post_type'] ) {
2852 + $posts[] = [
2853 + 'value' => 'error',
2854 + 'label' => __( '404 Error', 'shopbuilder' ),
2855 + ];
2856 + }
2608 2857 }
2609 2858 } else {
2610 2859 $posts = self::get_registered_post_types( $search_query );
2611 2860 }
@@ -2717,12 +2966,14 @@
2717 2966
2718 2967 if ( isset( self::$cache[ $cache_key ] ) ) {
2719 2968 return self::$cache[ $cache_key ];
2720 2969 }
2970 + $date_created = $product->get_date_created();
2971 + if ( ! $date_created ) {
2972 + return false;
2973 + }
2974 + $publish_timestamp = strtotime( $date_created->date_i18n( 'Y-m-d H:i:s' ) );
2721 2975
2722 - // Get the product publish date.
2723 - $publish_timestamp = strtotime( $product->get_date_created()->date_i18n( 'Y-m-d H:i:s' ) );
2724 -
2725 2976 // Calculate the difference in days.
2726 2977 $days_difference = abs( time() - $publish_timestamp ) / ( 60 * 60 * 24 );
2727 2978
2728 2979 // Check if the product is considered new.
@@ -2857,8 +3108,69 @@
2857 3108 ];
2858 3109 }
2859 3110
2860 3111 /**
3112 + * Retrieve an array of custom icons with their HTML representation.
3113 + *
3114 + * @return array
3115 + */
3116 + public static function get_icons() {
3117 + $icon = self::get_custom_icon_names();
3118 + $icons_array = [];
3119 + foreach ( $icon as $value ) {
3120 + $icons_array[ $value ] = '<i class="rtsb-icon rtsb-icon-' . $value . '"></i> <span class="icon-name">' . ucfirst( str_replace( '-', ' ', $value ) ) . '</span>';
3121 + }
3122 + return $icons_array;
3123 + }
3124 +
3125 + /**
3126 + * Generates HTML markup for displaying an endpoint icon.
3127 + *
3128 + * @param array $data The endpoint data containing icon information.
3129 + * @param string $endpoint The endpoint identifier.
3130 + * @param boolean $wrapper Whether to wrap the icon in a span element.
3131 + *
3132 + * @return string
3133 + */
3134 + public static function get_icon_html( $data, $wrapper = true ) {
3135 + if ( empty( $data ) || 'none' === $data['icon_source'] ) {
3136 + return '';
3137 + }
3138 + $endpoint = '';
3139 + $html = $wrapper ? '<span class="icon ' . esc_attr( $endpoint ) . '_icon">' : '';
3140 +
3141 + if ( 'select_icon' === $data['icon_source'] ) {
3142 + $html .= '<i class="rtsb-icon rtsb-icon-' . esc_attr( $data['custom_icon'] ) . '"></i>';
3143 + } else {
3144 + $icon_html = '';
3145 + $icon_url = $data['image_icon']['source'] ?? '';
3146 + $icon_id = $data['image_icon']['id'] ?? 0;
3147 + $extension = ! empty( $icon_url ) ? strtolower( substr( strrchr( $data['image_icon']['source'], '.' ), 1 ) ) : '';
3148 +
3149 + if ( 'svg' === $extension ) {
3150 + $content = file_get_contents( $icon_url ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
3151 + $is_svg = strpos( $content, '<svg' );
3152 +
3153 + if ( false !== $is_svg ) {
3154 + $icon_html = substr( $content, $is_svg );
3155 + }
3156 + } else {
3157 + $attr = [
3158 + 'class' => 'rtsb-icon-img',
3159 + 'alt' => esc_html( ucfirst( $endpoint ) ) . esc_html__( ' Icon', 'shopbuilder' ),
3160 + ];
3161 +
3162 + $icon_html = wp_get_attachment_image( absint( $icon_id ), 'full', true, $attr );
3163 + }
3164 +
3165 + $html .= $icon_html;
3166 + }
3167 +
3168 + $html .= $wrapper ? '</span>' : '';
3169 +
3170 + return $html;
3171 + }
3172 + /**
2861 3173 * Flushes rewrite rules.
2862 3174 *
2863 3175 * @return void
2864 3176 */
@@ -2924,9 +3236,10 @@
2924 3236 return;
2925 3237 }
2926 3238 $repeater_options[ $index ] = wp_parse_args( $values, $repeater_options[ $index ] );
2927 3239 $options[ $option_key ] = wp_json_encode( $repeater_options );
2928 - self::set_options( $section_id, $block_id, $options );
3240 + $modules[ $block_id ] = $options;
3241 + DataModel::source()->set_option( $section_id, $modules );
2929 3242 }
2930 3243
2931 3244
2932 3245 /**
@@ -2940,6 +3253,133 @@
2940 3253 return 'product' === get_post_type( $id ) && 'publish' === get_post_status( $id );
2941 3254 }
2942 3255 );
2943 3256 return $ids;
3257 + }
3258 +
3259 + /**
3260 + * Generate and cache dynamic CSS styles.
3261 + *
3262 + * @param array $options CSS options to apply (e.g. padding, margin).
3263 + * @param string $cache_key Cache key for the CSS.
3264 + * @param array $css_properties An array of CSS properties with selectors and properties.
3265 + * @param string $style_handle The handle for the inline styles.
3266 + *
3267 + * @return void
3268 + */
3269 + public static function dynamic_styles( $options, $cache_key, $css_properties, $style_handle = 'rtsb-frontend' ) {
3270 + $cached_css = wp_cache_get( $cache_key, 'shopbuilder' );
3271 +
3272 + if ( false !== $cached_css ) {
3273 + wp_add_inline_style( $style_handle, $cached_css );
3274 + return;
3275 + }
3276 +
3277 + $css_rules = [];
3278 + $grouped_css_rules = [];
3279 +
3280 + foreach ( $css_properties as $option => $details ) {
3281 + if ( ! empty( $options[ $option ] ) ) {
3282 + $selector = $details['selector'] ?? '';
3283 + $property = $details['property'] ?? '';
3284 + $unit = $details['unit'] ?? '';
3285 + $value = $options[ $option ];
3286 +
3287 + if ( empty( $grouped_css_rules[ $selector ] ) ) {
3288 + $grouped_css_rules[ $selector ] = [];
3289 + }
3290 +
3291 + if ( is_array( $property ) ) {
3292 + foreach ( $property as $prop ) {
3293 + $grouped_css_rules[ $selector ][] = $prop . ': ' . $value . $unit . ' !important';
3294 + }
3295 + } else {
3296 + $grouped_css_rules[ $selector ][] = $property . ': ' . $value . $unit . ' !important';
3297 + }
3298 + }
3299 + }
3300 +
3301 + foreach ( $grouped_css_rules as $selector => $properties ) {
3302 + $css_rules[] = $selector . ' {' . implode( '; ', $properties ) . '}';
3303 + }
3304 +
3305 + $dynamic_css = implode( ' ', $css_rules );
3306 +
3307 + wp_cache_set( $cache_key, $dynamic_css, 'shopbuilder', 12 * HOUR_IN_SECONDS );
3308 + Cache::set_data_cache_key( $cache_key );
3309 +
3310 + if ( ! empty( $dynamic_css ) ) {
3311 + wp_add_inline_style( $style_handle, $dynamic_css );
3312 + }
3313 + }
3314 +
3315 + /**
3316 + * Pro version notice.
3317 + *
3318 + * @param string $ver Pro Version.
3319 + *
3320 + * @return array[]
3321 + */
3322 + public static function pro_version_notice( $ver, $tab = 'billing_fields', $name = 'ShopBuilder', $enable_free_Link = true ) {
3323 + return [
3324 + 'version_check' => [
3325 + 'id' => 'version_check',
3326 + 'type' => 'title',
3327 + 'label' => sprintf(
3328 + /* translators: 1: required version, 2: link to the Plugins page */
3329 + esc_html__(
3330 + 'To access all features and settings of this module, please ensure that "%1$s" is updated to version %2$s. %3$s',
3331 + 'shopbuilder'
3332 + ),
3333 + $name,
3334 + sprintf(
3335 + '<br /><u><b>%s</b></u>',
3336 + esc_html( $ver ?? '1.5.0' ) . ' or higher'
3337 + ),
3338 + $enable_free_Link
3339 + ? sprintf(
3340 + '%s <a class="pro-update-required" href="%s" target="_blank" title="%s">%s</a>',
3341 + esc_attr__( 'Update to the latest version', 'shopbuilder' ),
3342 + esc_url( admin_url( 'plugins.php' ) ),
3343 + esc_attr__( 'Go to the Plugin Page', 'shopbuilder' ),
3344 + esc_html__( 'from the Plugins page', 'shopbuilder' )
3345 + )
3346 + : ''
3347 + ),
3348 + 'tab' => $tab,
3349 + 'customClass' => 'checkout-notice',
3350 + ],
3351 + ];
3352 + }
3353 +
3354 + /**
3355 + * Get product gallery ids.
3356 + *
3357 + * @param object $product Product object.
3358 + *
3359 + * @return mixed
3360 + */
3361 + public static function get_cached_gallery_ids( $product ) {
3362 + $product_id = $product->get_id();
3363 + $cache_key = 'rtsb_product_gallery_ids_' . $product_id;
3364 +
3365 + if ( isset( self::$cache[ $cache_key ] ) ) {
3366 + return self::$cache[ $cache_key ];
3367 + }
3368 +
3369 + $cached_result = wp_cache_get( $cache_key, 'shopbuilder' );
3370 +
3371 + if ( false !== $cached_result ) {
3372 + self::$cache[ $cache_key ] = $cached_result;
3373 +
3374 + return $cached_result;
3375 + }
3376 +
3377 + $gallery_ids = $product->get_gallery_image_ids();
3378 + self::$cache[ $cache_key ] = $gallery_ids;
3379 +
3380 + wp_cache_set( $cache_key, $gallery_ids, 'shopbuilder', 12 * HOUR_IN_SECONDS );
3381 + Cache::set_data_cache_key( $cache_key );
3382 +
3383 + return $gallery_ids;
2944 3384 }
2945 3385 }