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 +493 -44 2.1.122.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
@@ -57,16 +56,14 @@
57 56 * @param $name
58 57 * @param $value
59 58 */
60 59 public static function setSession( $name, $value ) {
61 - if ( ! headers_sent() && session_status() == PHP_SESSION_NONE ) {
62 - session_start();
60 + if ( ! headers_sent() && session_status() == PHP_SESSION_NONE ) {
61 + session_start();
63 62 $_SESSION[ $name ] = $value;
64 - //session_write_close();
65 63 } else {
66 64 $_SESSION[ $name ] = $value;
67 65 }
68 -
69 66 }
70 67 /**
71 68 * Get Cookie or Session
72 69 *
@@ -77,9 +74,9 @@
77 74 public static function getSession( $name ) {
78 75 if ( ! headers_sent() && session_status() == PHP_SESSION_NONE ) {
79 76 session_start();
80 77 }
81 - return $_SESSION[ $name ] ?? null;
78 + return $_SESSION[ $name ] ?? null; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
82 79 }
83 80 /**
84 81 * Remove Session Variable
85 82 *
@@ -88,9 +85,8 @@
88 85 public static function removeSession( $name ) {
89 86 if ( ! headers_sent() && session_status() == PHP_SESSION_NONE ) {
90 87 session_start();
91 88 unset( $_SESSION[ $name ] );
92 - //session_write_close();
93 89 } else {
94 90 unset( $_SESSION[ $name ] );
95 91 }
96 92 }
@@ -117,9 +113,46 @@
117 113 }
118 114
119 115 return false;
120 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' );
121 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 +
122 155 /**
123 156 * @param $data
124 157 *
125 158 * @return mixed|string
@@ -124,14 +157,25 @@
124 157 *
125 158 * @return mixed|string
126 159 */
127 160 public static function stripslashes_value( $data ) {
128 - if ( is_string( $data ) && strpos( $data, '\\' ) !== false ) {
129 - return stripslashes( $data );
130 - } elseif ( is_array( $data ) ) {
161 + if ( is_array( $data ) ) {
131 162 foreach ( $data as $key => $value ) {
132 163 $data[ $key ] = self::stripslashes_value( $value );
133 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 + }
134 178 }
135 179
136 180 return $data;
137 181 }
@@ -170,14 +214,15 @@
170 214
171 215 /**
172 216 * @param string $template_name Template name.
173 217 * @param string $template_path Template path. (default: '').
174 - * @param string $plugin_path Plugin path. (default: ''). fallback file from plugin
218 + * @param string $plugin_path Plugin path. (default: ''). fallback file from plugin.
175 219 *
176 220 * @return mixed|void
177 221 */
178 222 public static function locate_template( $template_name, $template_path = '', $plugin_path = '' ) {
179 - $template_name = $template_name . '.php';
223 + $template_name = self::sanitize_file_name( $template_name ) . '.php';
224 +
180 225 if ( ! $template_path ) {
181 226 $template_path = rtsb()->get_template_path();
182 227 }
183 228 if ( ! $plugin_path ) {
@@ -220,8 +265,19 @@
220 265 return apply_filters( 'rtsb/core/locate_template', $located, $template_name );
221 266 }
222 267
223 268 /**
269 + * Remove any character that is not alphanumeric, /, _, or -.
270 + *
271 + * @param string $name Name to sanitize.
272 + *
273 + * @return array|string|string[]|null
274 + */
275 + private static function sanitize_file_name( $name ) {
276 + return preg_replace( '/[^a-zA-Z0-9\/_\-]/', '', $name );
277 + }
278 +
279 + /**
224 280 * Template Content
225 281 *
226 282 * @param string $template_name Template name.
227 283 * @param array $args Arguments. (default: array).
@@ -231,10 +287,19 @@
231 287 *
232 288 * @return false|string|void
233 289 */
234 290 public static function load_template( $template_name, array $args = null, $return = false, $template_path = '', $plugin_path = '' ) {
235 - $located = self::locate_template( $template_name, $template_path, $plugin_path );
236 -
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 + }
237 302 if ( ! file_exists( $located ) ) {
238 303 // translators: %s template.
239 304 self::doing_it_wrong( __FUNCTION__, sprintf( __( '%s does not exist.', 'shopbuilder' ), '<code>' . $located . '</code>' ), '1.0' );
240 305
@@ -242,9 +307,9 @@
242 307 }
243 308
244 309 if ( ! empty( $args ) && is_array( $args ) ) {
245 310 $atts = $args;
246 - extract( $args ); // @codingStandardsIgnoreLine
311 + extract( $args ); // @codingStandardsIgnoreLine
247 312 }
248 313
249 314 // Allow 3rd party plugin filter template file from their plugin.
250 315 $located = apply_filters( 'rtsb/core/get_template', $located, $template_name, $args );
@@ -359,9 +424,9 @@
359 424 // Return the Builder Template id.
360 425
361 426 if ( get_post_type( get_the_ID() ) == BuilderFns::$post_type_tb ) {
362 427 $product_id = get_post_meta( get_the_ID(), BuilderFns::$product_template_meta, true );
363 - if ( $product_id ) {
428 + if ( $product_id && get_post_status( $product_id ) ) {
364 429 return $product_id;
365 430 }
366 431 }
367 432
@@ -366,16 +431,16 @@
366 431 }
367 432
368 433 global $wpdb;
369 434 $cache_key = 'rtsb_prepared_product_id';
370 - $_post_id = wp_cache_get( $cache_key );
371 -
435 + $_post_id = wp_cache_get( $cache_key, 'shopbuilder' );
372 436 if ( false === $_post_id || 'publish' !== get_post_status( $_post_id ) ) {
373 - // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
437 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
374 438 $_post_id = $wpdb->get_var(
375 439 $wpdb->prepare( "SELECT MAX(ID) FROM {$wpdb->prefix}posts WHERE post_type = %s AND post_status IN('publish', 'draft')", 'product' )
376 440 );
377 - 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 );
378 443 }
379 444
380 445 return $_post_id;
381 446 }
@@ -411,8 +476,9 @@
411 476 *
412 477 * @return void
413 478 */
414 479 public static function doing_it_wrong( $function, $message, $version ) {
480 + // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_wp_debug_backtrace_summary
415 481 $message .= ' Backtrace: ' . wp_debug_backtrace_summary();
416 482 _doing_it_wrong( esc_html( $function ), wp_kses_post( $message ), esc_html( $version ) );
417 483 }
418 484
@@ -546,9 +612,9 @@
546 612
547 613 if ( strlen( $page_content ) > 0 ) {
548 614 // Search for an existing page with the specified page content (typically a shortcode).
549 615 $shortcode = str_replace( [ '<!-- wp:shortcode -->', '<!-- /wp:shortcode -->' ], '', $page_content );
550 - // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
616 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
551 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}%" ) );
552 618 } else {
553 619 // Search for an existing page with the specified page slug.
554 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
@@ -950,13 +1016,15 @@
950 1016 return $allowed_html;
951 1017 }
952 1018
953 1019 /**
954 - * Safe print a validated HTML tag.
1020 + * Safe get a validated HTML tag.
955 1021 *
956 - * @param string $tag
1022 + * @param string $tag HTML tag.
1023 + *
1024 + * @return string
957 1025 */
958 - public static function print_validated_html_tag( $tag ) {
1026 + public static function get_validated_html_tag( $tag ) {
959 1027 $allowed_html_wrapper_tags = [
960 1028 'a',
961 1029 'article',
962 1030 'aside',
@@ -976,12 +1044,23 @@
976 1044 'section',
977 1045 'span',
978 1046 ];
979 1047
980 - 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';
981 1049 }
982 1050
983 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 + /**
984 1063 * Insert Some array element
985 1064 *
986 1065 * @param [type] $key The elements will insert nearby this key.
987 1066 * @param [type] $main_array Original array.
@@ -1102,8 +1181,25 @@
1102 1181 }
1103 1182 self::$cache[ $cache_key ] = $terms;
1104 1183 return $terms;
1105 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 + }
1106 1202
1107 1203 /**
1108 1204 * Get all terms by attributes
1109 1205 *
@@ -1287,9 +1383,9 @@
1287 1383
1288 1384 if ( $args['show_rating_count'] ) {
1289 1385 $count .= '<div class="rtsb-count">';
1290 1386 $count .= sprintf(
1291 - /* translators: %s is the number of reviews */
1387 + /* translators: %s is the number of reviews */
1292 1388 _n( '%s Review', '%s Reviews', $rating_count, 'shopbuilder' ),
1293 1389 $rating_count
1294 1390 );
1295 1391 $count .= '</div>';
@@ -1427,9 +1523,42 @@
1427 1523
1428 1524 <?php
1429 1525 self::print_html( ob_get_clean() );
1430 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 + ?>
1431 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 +
1432 1561 /**
1433 1562 * Get Featured Image HTML.
1434 1563 *
1435 1564 * @param string $type Image type.
@@ -1775,9 +1904,9 @@
1775 1904 $percentage = null;
1776 1905 $max_percentage = '';
1777 1906
1778 1907 if ( $product->is_type( 'simple' ) ) {
1779 - $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;
1780 1909 } elseif ( $product->is_type( 'variable' ) ) {
1781 1910 $max_percentage = 0;
1782 1911
1783 1912 foreach ( $product->get_children() as $child_id ) {
@@ -2132,9 +2261,9 @@
2132 2261 $link['social_action'] = 'Share';
2133 2262 break;
2134 2263 case 'reddit':
2135 2264 $link['link'] = esc_url( 'https://reddit.com/submit?url=' . $link['url'] . '&title=' . $link['title'] );
2136 - $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>';
2137 2266 $link['attr_title'] = esc_html__( 'Share on Reddit', 'shopbuilder' );
2138 2267 $link['social_network'] = 'Reddit';
2139 2268 $link['social_action'] = 'Share';
2140 2269 break;
@@ -2285,12 +2414,40 @@
2285 2414 $field = $fields[ $raw_option_key ];
2286 2415 if ( $field['type'] === 'switch' ) {
2287 2416 $value = 'on' === $raw_value ? 'on' : '';
2288 2417 } elseif ( 'repeaters' === $field['type'] ) {
2289 - $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 + }
2290 2448 } else {
2291 2449 if ( ! empty( $field['multiple'] ) || in_array( $field['type'], [ 'checkbox', 'search_and_multi_select' ] ) ) {
2292 -
2293 2450 if ( isset( $raw_value ) && is_array( $raw_value ) ) {
2294 2451 if ( ! empty( $field['sanitize_fn'] ) && is_callable( $field['sanitize_fn'] ) ) {
2295 2452 $value = array_map( $field['sanitize_fn'], $raw_value );
2296 2453 } else {
@@ -2366,15 +2523,60 @@
2366 2523 ];
2367 2524
2368 2525 if ( 'product_cat' === $taxonomy ) {
2369 2526 $args['product_category_id'] = $terms;
2527 + } elseif ( 'product_brand' === $taxonomy ) {
2528 + $args['product_brand_id'] = $terms;
2370 2529 } else {
2371 2530 $args['product_tag_id'] = $terms;
2372 2531 }
2373 2532
2374 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 + }
2375 2540
2376 - 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 );
2377 2579 }
2378 2580
2379 2581 /**
2380 2582 * Check if product filters widget has ajax.
@@ -2453,17 +2655,71 @@
2453 2655
2454 2656 if ( isset( self::$cache[ $cache_key ] ) ) {
2455 2657 return self::$cache[ $cache_key ];
2456 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'";
2457 2666
2458 - $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 + }
2459 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' );
2460 2716 if ( ! $results ) {
2461 2717 global $wpdb;
2462 2718 $sql = "SELECT t.term_id, t.name
2463 2719 FROM {$wpdb->terms} t
2464 2720 JOIN {$wpdb->term_taxonomy} tt ON t.term_id = tt.term_id
2465 - WHERE tt.taxonomy = 'product_cat'";
2721 + WHERE tt.taxonomy = 'product_tag'";
2466 2722
2467 2723 if ( $search_query ) {
2468 2724 $sql .= ' AND t.name LIKE %s';
2469 2725 $prepared_sql = $wpdb->prepare( $sql, '%' . $wpdb->esc_like( $search_query ) . '%' ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
@@ -2472,9 +2728,10 @@
2472 2728 }
2473 2729
2474 2730 $results = $wpdb->get_results( $prepared_sql ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.NotPrepared
2475 2731 // Cache the results in the object cache for future use.
2476 - 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 );
2477 2734 }
2478 2735
2479 2736 $cats = [];
2480 2737 if ( ! is_array( $results ) && ! count( $results ) ) {
@@ -2578,11 +2835,12 @@
2578 2835 $query_results = get_posts( $args );
2579 2836 foreach ( $query_results as $post ) {
2580 2837 $posts[] = [
2581 2838 'value' => $post->ID,
2582 - 'label' => $post->post_title . ' (' . $post->ID . ')',
2839 + 'label' => $post->post_title . ' (ID#' . $post->ID . ')',
2583 2840 ];
2584 2841 }
2842 +
2585 2843 if ( $search_query ) {
2586 2844 if ( strpos( '-error 404', $search_query ) ) {
2587 2845 $posts[] = [
2588 2846 'value' => 'error',
@@ -2589,12 +2847,14 @@
2589 2847 'label' => __( '404 Error', 'shopbuilder' ),
2590 2848 ];
2591 2849 }
2592 2850 } else {
2593 - $posts[] = [
2594 - 'value' => 'error',
2595 - 'label' => __( '404 Error', 'shopbuilder' ),
2596 - ];
2851 + if ( 'page' === $args['post_type'] ) {
2852 + $posts[] = [
2853 + 'value' => 'error',
2854 + 'label' => __( '404 Error', 'shopbuilder' ),
2855 + ];
2856 + }
2597 2857 }
2598 2858 } else {
2599 2859 $posts = self::get_registered_post_types( $search_query );
2600 2860 }
@@ -2706,12 +2966,14 @@
2706 2966
2707 2967 if ( isset( self::$cache[ $cache_key ] ) ) {
2708 2968 return self::$cache[ $cache_key ];
2709 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' ) );
2710 2975
2711 - // Get the product publish date.
2712 - $publish_timestamp = strtotime( $product->get_date_created()->date_i18n( 'Y-m-d H:i:s' ) );
2713 -
2714 2976 // Calculate the difference in days.
2715 2977 $days_difference = abs( time() - $publish_timestamp ) / ( 60 * 60 * 24 );
2716 2978
2717 2979 // Check if the product is considered new.
@@ -2846,8 +3108,69 @@
2846 3108 ];
2847 3109 }
2848 3110
2849 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 + /**
2850 3173 * Flushes rewrite rules.
2851 3174 *
2852 3175 * @return void
2853 3176 */
@@ -2913,9 +3236,10 @@
2913 3236 return;
2914 3237 }
2915 3238 $repeater_options[ $index ] = wp_parse_args( $values, $repeater_options[ $index ] );
2916 3239 $options[ $option_key ] = wp_json_encode( $repeater_options );
2917 - self::set_options( $section_id, $block_id, $options );
3240 + $modules[ $block_id ] = $options;
3241 + DataModel::source()->set_option( $section_id, $modules );
2918 3242 }
2919 3243
2920 3244
2921 3245 /**
@@ -2931,6 +3255,131 @@
2931 3255 );
2932 3256 return $ids;
2933 3257 }
2934 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' );
2935 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;
3384 + }
2936 3385 }