PluginProbe
ShopBuilder – WooCommerce Builder For Elementor / 2.6.4
ShopBuilder – WooCommerce Builder For Elementor v2.6.4
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 +1119 -98 2.0.32.6.4 View file →
@@ -11,14 +11,15 @@
11 11 if ( ! defined( 'ABSPATH' ) ) {
12 12 exit( 'This script cannot be accessed directly.' );
13 13 }
14 14
15 +use WC_Product;
16 +use WC_Product_Query;
15 17 use Elementor\Icons_Manager;
16 18 use RadiusTheme\SB\Models\ReSizer;
17 19 use RadiusTheme\SB\Models\DataModel;
18 20 use RadiusTheme\SB\Models\ModuleList;
19 21 use RadiusTheme\SB\Models\Settings;
20 -use WC_Product;
21 22
22 23 /**
23 24 * Fns class
24 25 */
@@ -24,8 +25,13 @@
24 25 */
25 26 class Fns {
26 27
27 28 /**
29 + * @var array
30 + */
31 + private static $cache = [];
32 +
33 + /**
28 34 * Verify nonce.
29 35 *
30 36 * @return bool
31 37 */
@@ -38,9 +44,55 @@
38 44
39 45 return false;
40 46 }
41 47
48 + public static function get_nonce() {
49 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
50 + return isset( $_REQUEST[ rtsb()->nonceId ] ) ? sanitize_text_field( $_REQUEST[ rtsb()->nonceId ] ) : null;
51 + }
52 +
42 53 /**
54 + * Set Cookie or Session
55 + *
56 + * @param $name
57 + * @param $value
58 + */
59 + public static function setSession( $name, $value ) {
60 + if ( ! headers_sent() && session_status() == PHP_SESSION_NONE ) {
61 + session_start();
62 + $_SESSION[ $name ] = $value;
63 + } else {
64 + $_SESSION[ $name ] = $value;
65 + }
66 + }
67 + /**
68 + * Get Cookie or Session
69 + *
70 + * @param $name
71 + *
72 + * @return bool
73 + */
74 + public static function getSession( $name ) {
75 + if ( ! headers_sent() && session_status() == PHP_SESSION_NONE ) {
76 + session_start();
77 + }
78 + return $_SESSION[ $name ] ?? null; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
79 + }
80 + /**
81 + * Remove Session Variable
82 + *
83 + * @param string $name The name of the session variable to remove.
84 + */
85 + public static function removeSession( $name ) {
86 + if ( ! headers_sent() && session_status() == PHP_SESSION_NONE ) {
87 + session_start();
88 + unset( $_SESSION[ $name ] );
89 + } else {
90 + unset( $_SESSION[ $name ] );
91 + }
92 + }
93 +
94 + /**
43 95 * @param $plugin_slug
44 96 *
45 97 * @return bool
46 98 */
@@ -61,9 +113,46 @@
61 113 }
62 114
63 115 return false;
64 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' );
65 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 +
66 155 /**
67 156 * @param $data
68 157 *
69 158 * @return mixed|string
@@ -68,14 +157,25 @@
68 157 *
69 158 * @return mixed|string
70 159 */
71 160 public static function stripslashes_value( $data ) {
72 - if ( is_string( $data ) && strpos( $data, '\\' ) !== false ) {
73 - return stripslashes( $data );
74 - } elseif ( is_array( $data ) ) {
161 + if ( is_array( $data ) ) {
75 162 foreach ( $data as $key => $value ) {
76 163 $data[ $key ] = self::stripslashes_value( $value );
77 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 + }
78 178 }
79 179
80 180 return $data;
81 181 }
@@ -114,14 +214,15 @@
114 214
115 215 /**
116 216 * @param string $template_name Template name.
117 217 * @param string $template_path Template path. (default: '').
118 - * @param string $plugin_path Plugin path. (default: ''). fallback file from plugin
218 + * @param string $plugin_path Plugin path. (default: ''). fallback file from plugin.
119 219 *
120 220 * @return mixed|void
121 221 */
122 222 public static function locate_template( $template_name, $template_path = '', $plugin_path = '' ) {
123 - $template_name = $template_name . '.php';
223 + $template_name = self::sanitize_file_name( $template_name ) . '.php';
224 +
124 225 if ( ! $template_path ) {
125 226 $template_path = rtsb()->get_template_path();
126 227 }
127 228 if ( ! $plugin_path ) {
@@ -164,8 +265,19 @@
164 265 return apply_filters( 'rtsb/core/locate_template', $located, $template_name );
165 266 }
166 267
167 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 + /**
168 280 * Template Content
169 281 *
170 282 * @param string $template_name Template name.
171 283 * @param array $args Arguments. (default: array).
@@ -175,10 +287,19 @@
175 287 *
176 288 * @return false|string|void
177 289 */
178 290 public static function load_template( $template_name, array $args = null, $return = false, $template_path = '', $plugin_path = '' ) {
179 - $located = self::locate_template( $template_name, $template_path, $plugin_path );
180 -
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 + }
181 302 if ( ! file_exists( $located ) ) {
182 303 // translators: %s template.
183 304 self::doing_it_wrong( __FUNCTION__, sprintf( __( '%s does not exist.', 'shopbuilder' ), '<code>' . $located . '</code>' ), '1.0' );
184 305
@@ -186,9 +307,9 @@
186 307 }
187 308
188 309 if ( ! empty( $args ) && is_array( $args ) ) {
189 310 $atts = $args;
190 - extract( $args ); // @codingStandardsIgnoreLine
311 + extract( $args ); // @codingStandardsIgnoreLine
191 312 }
192 313
193 314 // Allow 3rd party plugin filter template file from their plugin.
194 315 $located = apply_filters( 'rtsb/core/get_template', $located, $template_name, $args );
@@ -198,8 +319,9 @@
198 319 }
199 320
200 321 do_action( 'rtsb/core/before_template_part', $template_name, $located, $args );
201 322 include $located;
323 +
202 324 do_action( 'rtsb/core/after_template_part', $template_name, $located, $args );
203 325
204 326 if ( $return ) {
205 327 return ob_get_clean();
@@ -302,9 +424,9 @@
302 424 // Return the Builder Template id.
303 425
304 426 if ( get_post_type( get_the_ID() ) == BuilderFns::$post_type_tb ) {
305 427 $product_id = get_post_meta( get_the_ID(), BuilderFns::$product_template_meta, true );
306 - if ( $product_id ) {
428 + if ( $product_id && get_post_status( $product_id ) ) {
307 429 return $product_id;
308 430 }
309 431 }
310 432
@@ -309,15 +431,16 @@
309 431 }
310 432
311 433 global $wpdb;
312 434 $cache_key = 'rtsb_prepared_product_id';
313 - $_post_id = wp_cache_get( $cache_key );
314 -
435 + $_post_id = wp_cache_get( $cache_key, 'shopbuilder' );
315 436 if ( false === $_post_id || 'publish' !== get_post_status( $_post_id ) ) {
437 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
316 438 $_post_id = $wpdb->get_var(
317 - $wpdb->prepare( "SELECT MAX(ID) FROM {$wpdb->prefix}posts WHERE post_type = %s AND post_status = %s", 'product', 'publish' )
439 + $wpdb->prepare( "SELECT MAX(ID) FROM {$wpdb->prefix}posts WHERE post_type = %s AND post_status IN('publish', 'draft')", 'product' )
318 440 );
319 - 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 );
320 443 }
321 444
322 445 return $_post_id;
323 446 }
@@ -322,19 +445,27 @@
322 445 return $_post_id;
323 446 }
324 447
325 448 /**
326 - * Get the product function.
449 + * Get the product function. Only used in single page widgets.
327 450 *
328 451 * @return object
329 452 */
330 453 public static function get_product() {
331 - global $post, $product;
454 + global $product;
455 +
332 456 if ( is_singular( 'product' ) && $product instanceof WC_Product ) {
457 + // do_action( 'rtsb_before_product_template_render' );.
333 458 return $product;
334 459 }
335 -
336 - return wc_get_product( self::get_prepared_product_id() );
460 + $cache_key = 'prepared_product_for_preview';
461 + if ( isset( self::$cache[ $cache_key ] ) ) {
462 + return self::$cache[ $cache_key ];
463 + }
464 + $product = wc_get_product( self::get_prepared_product_id() );
465 + self::$cache[ $cache_key ] = $product;
466 + do_action( 'rtsb_before_product_template_render' );
467 + return $product;
337 468 }
338 469
339 470 /**
340 471 * Error function
@@ -345,10 +476,11 @@
345 476 *
346 477 * @return void
347 478 */
348 479 public static function doing_it_wrong( $function, $message, $version ) {
480 + // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_wp_debug_backtrace_summary
349 481 $message .= ' Backtrace: ' . wp_debug_backtrace_summary();
350 - _doing_it_wrong( $function, $message, $version );
482 + _doing_it_wrong( esc_html( $function ), wp_kses_post( $message ), esc_html( $version ) );
351 483 }
352 484
353 485 /**
354 486 * @return array
@@ -479,13 +611,14 @@
479 611 }
480 612
481 613 if ( strlen( $page_content ) > 0 ) {
482 614 // Search for an existing page with the specified page content (typically a shortcode).
483 - $shortcode = str_replace( [ '<!-- wp:shortcode -->', '<!-- /wp:shortcode -->' ], '', $page_content );
615 + $shortcode = str_replace( [ '<!-- wp:shortcode -->', '<!-- /wp:shortcode -->' ], '', $page_content );
616 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
484 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}%" ) );
485 618 } else {
486 619 // Search for an existing page with the specified page slug.
487 - $valid_page_found = $wpdb->get_var( $wpdb->prepare( "SELECT ID FROM $wpdb->posts WHERE post_type='page' AND post_status NOT IN ( 'pending', 'trash', 'future', 'auto-draft' ) AND post_name = %s LIMIT 1;", $slug ) );
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
488 621 }
489 622
490 623 $valid_page_found = apply_filters( 'rtsb/core/create_page_id', $valid_page_found, $slug, $page_content, $options );
491 624
@@ -507,12 +640,12 @@
507 640
508 641 // Search for a matching valid trashed page.
509 642 if ( strlen( $page_content ) > 0 ) {
510 643 // Search for an existing page with the specified page content (typically a shortcode).
511 - $trashed_page_found = $wpdb->get_var( $wpdb->prepare( "SELECT ID FROM $wpdb->posts WHERE post_type='page' AND post_status = 'trash' AND post_content LIKE %s LIMIT 1;", "%{$page_content}%" ) );
644 + $trashed_page_found = $wpdb->get_var( $wpdb->prepare( "SELECT ID FROM $wpdb->posts WHERE post_type='page' AND post_status = 'trash' AND post_content LIKE %s LIMIT 1;", "%{$page_content}%" ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
512 645 } else {
513 646 // Search for an existing page with the specified page slug.
514 - $trashed_page_found = $wpdb->get_var( $wpdb->prepare( "SELECT ID FROM $wpdb->posts WHERE post_type='page' AND post_status = 'trash' AND post_name = %s LIMIT 1;", $slug ) );
647 + $trashed_page_found = $wpdb->get_var( $wpdb->prepare( "SELECT ID FROM $wpdb->posts WHERE post_type='page' AND post_status = 'trash' AND post_name = %s LIMIT 1;", $slug ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
515 648 }
516 649
517 650 if ( $trashed_page_found ) {
518 651 $page_id = $trashed_page_found;
@@ -883,8 +1016,51 @@
883 1016 return $allowed_html;
884 1017 }
885 1018
886 1019 /**
1020 + * Safe get a validated HTML tag.
1021 + *
1022 + * @param string $tag HTML tag.
1023 + *
1024 + * @return string
1025 + */
1026 + public static function get_validated_html_tag( $tag ) {
1027 + $allowed_html_wrapper_tags = [
1028 + 'a',
1029 + 'article',
1030 + 'aside',
1031 + 'button',
1032 + 'div',
1033 + 'footer',
1034 + 'h1',
1035 + 'h2',
1036 + 'h3',
1037 + 'h4',
1038 + 'h5',
1039 + 'h6',
1040 + 'header',
1041 + 'main',
1042 + 'nav',
1043 + 'p',
1044 + 'section',
1045 + 'span',
1046 + ];
1047 +
1048 + return in_array( strtolower( $tag ), $allowed_html_wrapper_tags, true ) ? $tag : 'div';
1049 + }
1050 +
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 + /**
887 1063 * Insert Some array element
888 1064 *
889 1065 * @param [type] $key The elements will insert nearby this key.
890 1066 * @param [type] $main_array Original array.
@@ -979,10 +1155,19 @@
979 1155
980 1156 if ( empty( $taxonomy ) ) {
981 1157 return $terms;
982 1158 }
1159 + $cache_key = 'rtsb_get_all_terms_by_tax_name_' . $taxonomy;
1160 + if ( isset( self::$cache[ $cache_key ] ) ) {
1161 + return self::$cache[ $cache_key ];
1162 + }
983 1163
984 - $termList = get_terms( [ $taxonomy ], [ 'hide_empty' => 0 ] );
1164 + $termList = get_terms(
1165 + [
1166 + 'taxonomy' => $taxonomy,
1167 + 'hide_empty' => false,
1168 + ]
1169 + );
985 1170
986 1171 if ( is_array( $termList ) && ! empty( $termList ) && empty( $termList['errors'] ) ) {
987 1172 if ( $placeholder ) {
988 1173 $terms = [
@@ -993,11 +1178,28 @@
993 1178 foreach ( $termList as $term ) {
994 1179 $terms[ $term->term_id ] = esc_html( $term->name );
995 1180 }
996 1181 }
997 -
1182 + self::$cache[ $cache_key ] = $terms;
998 1183 return $terms;
999 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 + }
1000 1202
1001 1203 /**
1002 1204 * Get all terms by attributes
1003 1205 *
@@ -1011,9 +1213,9 @@
1011 1213
1012 1214 if ( $attributes ) {
1013 1215 foreach ( $attributes as $tax ) {
1014 1216 if ( taxonomy_exists( wc_attribute_taxonomy_name( $tax->attribute_name ) ) ) {
1015 - $termList[ $tax->attribute_name ] = get_terms( wc_attribute_taxonomy_name( $tax->attribute_name ), 'orderby=name&hide_empty=0' );
1217 + $termList[ $tax->attribute_name ] = get_terms( wc_attribute_taxonomy_name( $tax->attribute_name ) );
1016 1218 }
1017 1219 }
1018 1220 }
1019 1221
@@ -1105,8 +1307,12 @@
1105 1307 *
1106 1308 * @return int|array
1107 1309 */
1108 1310 public static function get_terms( $taxonomy, $first_term = false, $only_parents = false, $return_slug = false ) {
1311 + if ( ! is_admin() ) {
1312 + return [];
1313 + }
1314 +
1109 1315 // TODO:: Return Slug always true for all settings.
1110 1316 $term_list = [];
1111 1317 $args = [
1112 1318 'taxonomy' => $taxonomy,
@@ -1158,9 +1364,9 @@
1158 1364 }
1159 1365
1160 1366 $html .= '<div class="product-rating">';
1161 1367 $html .= wc_get_rating_html( $average_rating, $rating_count );
1162 - $html .= ! empty( $html ) ? '<span class="count">(' . $average_rating . ')</span>' : '';
1368 + $html .= ! empty( $html ) ? '<span class="rtsb-count">(' . $average_rating . ')</span>' : '';
1163 1369 $html .= '</div>';
1164 1370
1165 1371 self::print_html( $html, true );
1166 1372
@@ -1171,15 +1377,15 @@
1171 1377 return '';
1172 1378 }
1173 1379
1174 1380 $preset = ! empty( $args['preset'] ) ? $args['preset'] : 'preset1';
1175 - $average = $args['show_average_rating'] ? '<span class="count">(' . $average_rating . ')</span>' : '';
1381 + $average = $args['show_average_rating'] ? '<span class="rtsb-count">(' . $average_rating . ')</span>' : '';
1176 1382 $count = '';
1177 1383
1178 1384 if ( $args['show_rating_count'] ) {
1179 - $count .= '<div class="count">';
1385 + $count .= '<div class="rtsb-count">';
1180 1386 $count .= sprintf(
1181 - /* translators: %s is the number of reviews */
1387 + /* translators: %s is the number of reviews */
1182 1388 _n( '%s Review', '%s Reviews', $rating_count, 'shopbuilder' ),
1183 1389 $rating_count
1184 1390 );
1185 1391 $count .= '</div>';
@@ -1204,8 +1410,23 @@
1204 1410
1205 1411 self::print_html( $html, true );
1206 1412 }
1207 1413
1414 + public static function get_product_simple_rating_html() {
1415 + global $product;
1416 + $html = null;
1417 + $rating_count = $product->get_rating_count();
1418 + $average_rating = $product->get_average_rating();
1419 + $html .= '<div class="product-rating">';
1420 + $html .= wc_get_rating_html( $average_rating, $rating_count );
1421 + $html .= ! empty( $html ) ? '<span class="rtsb-count">(' . $average_rating . ')</span>' : '';
1422 + $html .= '</div>';
1423 +
1424 + self::print_html( $html, true );
1425 + }
1426 +
1427 +
1428 +
1208 1429 /**
1209 1430 * Text truncation.
1210 1431 *
1211 1432 * @param string $text_to_truncate Text.
@@ -1248,8 +1469,14 @@
1248 1469 *
1249 1470 * @return void
1250 1471 */
1251 1472 public static function get_badge_html( $text, $class = 'fill' ) {
1473 + if ( self::is_module_active( 'product_badges' ) && 'rtsb_yes' === $text ) {
1474 + do_action( 'rtsb/modules/product_badges/frontend/display' );
1475 +
1476 + return;
1477 + }
1478 +
1252 1479 if ( empty( $text ) ) {
1253 1480 return;
1254 1481 }
1255 1482 ob_start();
@@ -1256,10 +1483,9 @@
1256 1483 ?>
1257 1484
1258 1485 <ul class="rtsb-promotion-list">
1259 1486 <li class="rtsb-promotion-list-item">
1260 - <span
1261 - class="rtsb-tag-<?php echo ! empty( $class ) ? esc_attr( $class ) : ''; ?>"><?php echo esc_html( $text ); ?></span>
1487 + <span class="rtsb-tag-<?php echo ! empty( $class ) ? esc_attr( $class ) : ''; ?>"><?php echo esc_html( $text ); ?></span>
1262 1488 </li>
1263 1489 </ul>
1264 1490
1265 1491 <?php
@@ -1297,10 +1523,42 @@
1297 1523
1298 1524 <?php
1299 1525 self::print_html( ob_get_clean() );
1300 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 + ?>
1301 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 + );
1302 1552
1553 + self::print_html( $brand_list );
1554 + ?>
1555 + </ul>
1556 +
1557 + <?php
1558 + self::print_html( ob_get_clean() );
1559 + }
1560 +
1303 1561 /**
1304 1562 * Get Featured Image HTML.
1305 1563 *
1306 1564 * @param string $type Image type.
@@ -1310,12 +1568,13 @@
1310 1568 * @param array $custom_img_size Custom image size.
1311 1569 * @param bool $lazy Lazy load check.
1312 1570 * @param bool $hover Hover image.
1313 1571 * @param bool $gallery Gallery image.
1572 + * @param bool $custom_image_id Custom category image ID.
1314 1573 *
1315 1574 * @return string|null
1316 1575 */
1317 - public static function get_product_image_html( $type = 'product', $post_id = null, $f_img_size = 'medium', $default_img_id = null, $custom_img_size = [], $lazy = false, $hover = false, $gallery = false ) {
1576 + public static function get_product_image_html( $type = 'product', $post_id = null, $f_img_size = 'medium', $default_img_id = null, $custom_img_size = [], $lazy = false, $hover = false, $gallery = false, $custom_image_id = 0 ) {
1318 1577 $img_html = null;
1319 1578 $attachment_id = null;
1320 1579 $c_size = false;
1321 1580 $hover_class = '';
@@ -1339,9 +1598,9 @@
1339 1598 if ( 'product' === $type ) {
1340 1599 $a_id = get_post_thumbnail_id( $post_id );
1341 1600 $post_title = get_the_title( $post_id );
1342 1601 } elseif ( 'category' === $type ) {
1343 - $a_id = get_term_meta( $post_id, 'thumbnail_id', true );
1602 + $a_id = $custom_image_id ? $custom_image_id : get_term_meta( $post_id, 'thumbnail_id', true );
1344 1603 $post_title = get_term( $post_id )->name;
1345 1604 } else {
1346 1605 $a_id = $default_img_id;
1347 1606 }
@@ -1491,8 +1750,20 @@
1491 1750 *
1492 1751 * @return string
1493 1752 */
1494 1753 public static function icons_manager( $control, $class = '', $builder = 'elementor' ): string {
1754 + if ( empty( $control['value'] ) ) {
1755 + return '';
1756 + }
1757 + if ( is_array( $control['value'] ) ) {
1758 + $cache_key = 'icons_managet_' . str_replace( ' ', '', md5( serialize( $control['value'] ) ) );
1759 + } else {
1760 + $cache_key = 'icons_managet_' . str_replace( ' ', '', $control['value'] );
1761 + }
1762 + if ( isset( self::$cache[ $cache_key ] ) ) {
1763 + return self::$cache[ $cache_key ];
1764 + }
1765 +
1495 1766 $attributes = [
1496 1767 'aria-hidden' => 'true',
1497 1768 ];
1498 1769
@@ -1505,9 +1776,11 @@
1505 1776 if ( defined( 'ELEMENTOR_VERSION' ) && ! empty( $control ) && 'elementor' === $builder ) {
1506 1777 Icons_Manager::render_icon( $control, $attributes );
1507 1778 }
1508 1779
1509 - return ob_get_clean();
1780 + $icons = ob_get_clean();
1781 + self::$cache[ $cache_key ] = $icons;
1782 + return $icons;
1510 1783 }
1511 1784
1512 1785 /**
1513 1786 * Get product swatches.
@@ -1545,14 +1818,95 @@
1545 1818 if ( ! $product->is_on_sale() ) {
1546 1819 return '';
1547 1820 }
1548 1821
1549 - $badge_text = '';
1822 + $badge_text = '';
1823 + $percentage = self::calculate_sale_percentage( $product );
1824 +
1825 + if ( $percentage > 0 ) {
1826 + $badge_text = '-' . round( $percentage ) . '%';
1827 + }
1828 +
1829 + if ( 'text' === $type ) {
1830 + $badge_text = $text;
1831 + }
1832 +
1833 + return $badge_text;
1834 + }
1835 +
1836 + /**
1837 + * Get sale badge
1838 + *
1839 + * @param object $product Product Object.
1840 + * @param string $type Badge type.
1841 + * @param string $text Badge text.
1842 + * @param string $out_of_stock_text Out of stock text.
1843 + *
1844 + * @return string
1845 + */
1846 + public static function get_promo_badge( $product, $type, $text, $out_of_stock_text ) {
1847 + $disable_badges = self::get_option( 'general', 'guest_user', 'hide_badges', '' );
1848 + $badges_visibility = rtsb()->has_pro() && ! is_user_logged_in() && $disable_badges;
1849 +
1850 + if ( $badges_visibility ) {
1851 + return '';
1852 + }
1853 +
1854 + if ( is_null( $product ) ) {
1855 + global $product;
1856 + }
1857 +
1858 + if ( ! $product instanceof WC_Product ) {
1859 + return '';
1860 + }
1861 +
1862 + if ( ! $product->is_in_stock() ) {
1863 + return $out_of_stock_text;
1864 + }
1865 +
1866 + if ( ! $product->is_on_sale() ) {
1867 + return '';
1868 + }
1869 +
1870 + $badge_text = '';
1871 + $percentage = self::calculate_sale_percentage( $product );
1872 +
1873 + if ( $percentage > 0 ) {
1874 + $badge_text = '-' . round( $percentage ) . '%';
1875 + }
1876 +
1877 + if ( 'text' === $type ) {
1878 + $badge_text = $text;
1879 + }
1880 +
1881 + return $badge_text;
1882 + }
1883 +
1884 + /**
1885 + * Calculate Sale Percentage
1886 + *
1887 + * @param object $product Product object.
1888 + *
1889 + * @return float|int|string
1890 + */
1891 + public static function calculate_sale_percentage( $product = null ) {
1892 + if ( is_null( $product ) ) {
1893 + global $product;
1894 + }
1895 +
1896 + if ( ! $product instanceof WC_Product ) {
1897 + return '';
1898 + }
1899 +
1900 + if ( ! $product->is_on_sale() ) {
1901 + return '';
1902 + }
1903 +
1550 1904 $percentage = null;
1551 1905 $max_percentage = '';
1552 1906
1553 1907 if ( $product->is_type( 'simple' ) ) {
1554 - $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;
1555 1909 } elseif ( $product->is_type( 'variable' ) ) {
1556 1910 $max_percentage = 0;
1557 1911
1558 1912 foreach ( $product->get_children() as $child_id ) {
@@ -1572,16 +1926,12 @@
1572 1926 }
1573 1927 }
1574 1928
1575 1929 if ( $max_percentage > 0 ) {
1576 - $badge_text = '-' . round( $max_percentage ) . '%';
1930 + return round( $max_percentage );
1577 1931 }
1578 1932
1579 - if ( 'text' === $type ) {
1580 - $badge_text = $text;
1581 - }
1582 -
1583 - return $badge_text;
1933 + return 0;
1584 1934 }
1585 1935
1586 1936 /**
1587 1937 * Pagination
@@ -1592,10 +1942,10 @@
1592 1942 *
1593 1943 * @return string
1594 1944 */
1595 1945 public static function custom_pagination( $pages = '', $range = 4, $ajax = false ) {
1596 - $html = null;
1597 - $showitems = ( $range * 2 ) + 1;
1946 + $html = null;
1947 + $visible_range = apply_filters( 'rtsb/elements/pagination/visible_range', ( $range * 2 ) + 1 );
1598 1948
1599 1949 global $paged;
1600 1950
1601 1951 if ( is_front_page() ) {
@@ -1623,8 +1973,9 @@
1623 1973
1624 1974 if ( $ajax ) {
1625 1975 $ajaxClass = ' rtsb-pagination-ajax';
1626 1976 $dataAttr = "data-paged='1'";
1977 + $dataAttr .= ' data-visible-range="' . esc_attr( $visible_range ) . '"';
1627 1978 }
1628 1979
1629 1980 if ( 1 !== $pages ) {
1630 1981 $html .= '<div class="rtsb-pagination' . $ajaxClass . '" ' . $dataAttr . '>';
@@ -1629,29 +1980,29 @@
1629 1980 if ( 1 !== $pages ) {
1630 1981 $html .= '<div class="rtsb-pagination' . $ajaxClass . '" ' . $dataAttr . '>';
1631 1982 $html .= '<ul class="pagination-list">';
1632 1983
1633 - if ( $paged > 2 && $paged > $range + 1 && $showitems < $pages ) {
1984 + if ( $paged > 2 && $paged > $visible_range + 1 && $visible_range < $pages ) {
1634 1985 $html .= "<li><a data-paged='1' href='" . get_pagenum_link( 1 ) . "' aria-label='First'>&laquo;</a></li>";
1635 1986 }
1636 1987
1637 - if ( $paged > 1 && $showitems < $pages ) {
1988 + if ( $paged > 1 && $visible_range < $pages ) {
1638 1989 $p = $paged - 1;
1639 1990 $html .= "<li><a data-paged='{$p}' href='" . get_pagenum_link( $p ) . "' aria-label='Previous'>&lsaquo;</a></li>";
1640 1991 }
1641 1992
1642 1993 for ( $i = 1; $i <= $pages; $i++ ) {
1643 - if ( 1 != $pages && ( ! ( $i >= $paged + $range + 1 || $i <= $paged - $range - 1 ) || $pages <= $showitems ) ) {
1994 + if ( 1 != $pages && ( ! ( $i >= $paged + $visible_range + 1 || $i <= $paged - $visible_range - 1 ) || $pages <= $visible_range ) ) {
1644 1995 $html .= ( $paged == $i ) ? '<li class="active"><span>' . $i . '</span></li>' : "<li><a data-paged='{$i}' href='" . get_pagenum_link( $i ) . "'>" . $i . '</a></li>';
1645 1996 }
1646 1997 }
1647 1998
1648 - if ( $paged < $pages && $showitems < $pages ) {
1999 + if ( $paged < $pages && $visible_range < $pages ) {
1649 2000 $p = $paged + 1;
1650 2001 $html .= "<li><a data-paged='{$p}' href=\"" . get_pagenum_link( $paged + 1 ) . "\" aria-label='Next'>&rsaquo;</a></li>";
1651 2002 }
1652 2003
1653 - if ( $paged < $pages - 1 && $paged + $range - 1 < $pages && $showitems < $pages ) {
2004 + if ( $paged < $pages - 1 && $paged + $range - 1 < $pages && $visible_range < $pages ) {
1654 2005 $html .= "<li><a data-paged='{$pages}' href='" . get_pagenum_link( $pages ) . "' aria-label='Last'>&raquo;</a></li>";
1655 2006 }
1656 2007
1657 2008 $html .= '</ul>';
@@ -1910,9 +2261,9 @@
1910 2261 $link['social_action'] = 'Share';
1911 2262 break;
1912 2263 case 'reddit':
1913 2264 $link['link'] = esc_url( 'https://reddit.com/submit?url=' . $link['url'] . '&title=' . $link['title'] );
1914 - $link['icon'] = '<svg xmlns="http://www.w3.org/2000/svg" width="512" height="512" viewBox="0 0 512 512"><title>ionicons-v5_logos</title><path d="M324,256a36,36,0,1,0,36,36A36,36,0,0,0,324,256Z"/><circle cx="188" cy="292" r="36" transform="translate(-97.43 94.17) rotate(-22.5)"/><path d="M496,253.77c0-31.19-25.14-56.56-56-56.56a55.72,55.72,0,0,0-35.61,12.86c-35-23.77-80.78-38.32-129.65-41.27l22-79L363.15,103c1.9,26.48,24,47.49,50.65,47.49,28,0,50.78-23,50.78-51.21S441,48,413,48c-19.53,0-36.31,11.19-44.85,28.77l-90-17.89L247.05,168.4l-4.63.13c-50.63,2.21-98.34,16.93-134.77,41.53A55.38,55.38,0,0,0,72,197.21c-30.89,0-56,25.37-56,56.56a56.43,56.43,0,0,0,28.11,49.06,98.65,98.65,0,0,0-.89,13.34c.11,39.74,22.49,77,63,105C146.36,448.77,199.51,464,256,464s109.76-15.23,149.83-42.89c40.53-28,62.85-65.27,62.85-105.06a109.32,109.32,0,0,0-.84-13.3A56.32,56.32,0,0,0,496,253.77ZM414,75a24,24,0,1,1-24,24A24,24,0,0,1,414,75ZM42.72,253.77a29.6,29.6,0,0,1,29.42-29.71,29,29,0,0,1,13.62,3.43c-15.5,14.41-26.93,30.41-34.07,47.68A30.23,30.23,0,0,1,42.72,253.77ZM390.82,399c-35.74,24.59-83.6,38.14-134.77,38.14S157,423.61,121.29,399c-33-22.79-51.24-52.26-51.24-83A78.5,78.5,0,0,1,75,288.72c5.68-15.74,16.16-30.48,31.15-43.79a155.17,155.17,0,0,1,14.76-11.53l.3-.21,0,0,.24-.17c35.72-24.52,83.52-38,134.61-38s98.9,13.51,134.62,38l.23.17.34.25A156.57,156.57,0,0,1,406,244.92c15,13.32,25.48,28.05,31.16,43.81a85.44,85.44,0,0,1,4.31,17.67,77.29,77.29,0,0,1,.6,9.65C442.06,346.77,423.86,376.24,390.82,399Zm69.6-123.92c-7.13-17.28-18.56-33.29-34.07-47.72A29.09,29.09,0,0,1,440,224a29.59,29.59,0,0,1,29.41,29.71A30.07,30.07,0,0,1,460.42,275.1Z"/><path d="M323.23,362.22c-.25.25-25.56,26.07-67.15,26.27-42-.2-66.28-25.23-67.31-26.27h0a4.14,4.14,0,0,0-5.83,0l-13.7,13.47a4.15,4.15,0,0,0,0,5.89h0c3.4,3.4,34.7,34.23,86.78,34.45,51.94-.22,83.38-31.05,86.78-34.45h0a4.16,4.16,0,0,0,0-5.9l-13.71-13.47a4.13,4.13,0,0,0-5.81,0Z"/></svg>';
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>';
1915 2266 $link['attr_title'] = esc_html__( 'Share on Reddit', 'shopbuilder' );
1916 2267 $link['social_network'] = 'Reddit';
1917 2268 $link['social_action'] = 'Share';
1918 2269 break;
@@ -2009,9 +2360,8 @@
2009 2360 return true;
2010 2361 }
2011 2362 }
2012 2363
2013 -
2014 2364 /***
2015 2365 * Save Settings data
2016 2366 *
2017 2367 * @param $section_id
@@ -2023,10 +2373,9 @@
2023 2373 public static function set_options( $section_id = '', $block_id = '', $rawOptions = [] ) {
2024 2374 $section_id = ! empty( $section_id ) ? sanitize_text_field( wp_unslash( $section_id ) ) : '';
2025 2375 $block_id = ! empty( $block_id ) ? sanitize_text_field( wp_unslash( $block_id ) ) : '';
2026 2376 $rawOptions = ! empty( $rawOptions ) ? $rawOptions : [];
2027 -
2028 - $results = [
2377 + $results = [
2029 2378 'status' => true,
2030 2379 'message' => '',
2031 2380 ];
2032 2381 if ( ! $section_id || ! $block_id ) {
@@ -2042,9 +2391,9 @@
2042 2391
2043 2392 return $results;
2044 2393 }
2045 2394
2046 - $options = DataModel::source()->get_option( $section_id, [] );
2395 + $options = DataModel::source()->get_option( $section_id, [], false );
2047 2396 $changed = false;
2048 2397 $fields = [];
2049 2398 if ( isset( $sections[ $section_id ]['list'][ $block_id ]['fields'] ) && ! empty( $sections[ $section_id ]['list'][ $block_id ]['fields'] ) ) {
2050 2399 $fields = $sections[ $section_id ]['list'][ $block_id ]['fields'];
@@ -2065,12 +2414,40 @@
2065 2414 $field = $fields[ $raw_option_key ];
2066 2415 if ( $field['type'] === 'switch' ) {
2067 2416 $value = 'on' === $raw_value ? 'on' : '';
2068 2417 } elseif ( 'repeaters' === $field['type'] ) {
2069 - $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 + }
2070 2448 } else {
2071 2449 if ( ! empty( $field['multiple'] ) || in_array( $field['type'], [ 'checkbox', 'search_and_multi_select' ] ) ) {
2072 -
2073 2450 if ( isset( $raw_value ) && is_array( $raw_value ) ) {
2074 2451 if ( ! empty( $field['sanitize_fn'] ) && is_callable( $field['sanitize_fn'] ) ) {
2075 2452 $value = array_map( $field['sanitize_fn'], $raw_value );
2076 2453 } else {
@@ -2079,10 +2456,16 @@
2079 2456 } else {
2080 2457 $value = [];
2081 2458 }
2082 2459 } else {
2083 - if ( ! empty( $field['sanitize_fn'] ) && is_callable( $field['sanitize_fn'] ) ) {
2084 - $value = $field['sanitize_fn']( $raw_value );
2460 + if ( ! empty( $field['sanitize_fn'] ) ) {
2461 + if ( is_callable( $field['sanitize_fn'] ) ) {
2462 + $value = $field['sanitize_fn']( $raw_value );
2463 + } elseif ( 'pass_all' === $field['sanitize_fn'] ) {
2464 + $value = $raw_value;
2465 + } else {
2466 + $value = $field['sanitize_fn']( $raw_value );
2467 + }
2085 2468 } else {
2086 2469 $value = sanitize_text_field( $raw_value );
2087 2470 }
2088 2471 }
@@ -2128,36 +2511,72 @@
2128 2511 * }
2129 2512 * return;
2130 2513 * }
2131 2514 */
2132 -
2133 2515 public static function count_products_by_taxonomies( $terms, $taxonomy = 'product_cat' ) {
2134 2516 if ( empty( $terms ) || ! is_array( $terms ) ) {
2135 2517 return;
2136 2518 }
2137 2519
2138 - $category_ids = array_map(
2139 - function ( $name ) {
2140 - return sanitize_title( $name );
2141 - },
2142 - $terms
2143 - );
2520 + $args = [
2521 + 'limit' => -1,
2522 + 'return' => 'ids',
2523 + ];
2144 2524
2525 + if ( 'product_cat' === $taxonomy ) {
2526 + $args['product_category_id'] = $terms;
2527 + } elseif ( 'product_brand' === $taxonomy ) {
2528 + $args['product_brand_id'] = $terms;
2529 + } else {
2530 + $args['product_tag_id'] = $terms;
2531 + }
2532 +
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 + }
2540 +
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 +
2145 2557 $args = [
2146 - 'post_type' => 'product',
2147 - 'posts_per_page' => - 1,
2148 - 'tax_query' => [
2149 - [
2150 - 'taxonomy' => $taxonomy,
2151 - 'field' => 'term_id',
2152 - 'terms' => $category_ids,
2153 - ],
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 ),
2154 2563 ],
2155 2564 ];
2156 2565
2157 - $query = new \WP_Query( $args );
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 + }
2158 2574
2159 - return $query->found_posts;
2575 + $query = new WC_Product_Query( $args );
2576 + $products = $query->get_products();
2577 +
2578 + return count( $products );
2160 2579 }
2161 2580
2162 2581 /**
2163 2582 * Check if product filters widget has ajax.
@@ -2170,9 +2589,20 @@
2170 2589 if ( empty( $page ) ) {
2171 2590 return false;
2172 2591 }
2173 2592
2174 - $id = BuilderFns::is_builder_preview() ? get_the_ID() : BuilderFns::builder_page_id_by_type( $page );
2593 + if ( 'page' === get_post_type() ) {
2594 + return false;
2595 + }
2596 +
2597 + $id = BuilderFns::is_builder_preview() ? get_the_ID() : BuilderFns::builder_page_id_by_type( $page );
2598 + if ( ! $id ) {
2599 + return false;
2600 + }
2601 + $cache_key = 'product_filters_has_ajax_' . $id;
2602 + if ( isset( self::$cache[ $cache_key ] ) ) {
2603 + return self::$cache[ $cache_key ];
2604 + }
2175 2605 $elmap = ElementorDataMap::instance();
2176 2606 $ajax = [];
2177 2607
2178 2608 foreach ( $elmap->get_widget_data( 'rtsb-ajax-product-filters', [], $id ) as $data ) {
@@ -2178,8 +2608,9 @@
2178 2608 foreach ( $elmap->get_widget_data( 'rtsb-ajax-product-filters', [], $id ) as $data ) {
2179 2609 $ajax[] = isset( $data['settings']['ajax_mode'] ) ? false : true;
2180 2610 }
2181 2611
2612 + self::$cache[ $cache_key ] = ! empty( $ajax[0] );
2182 2613 return ! empty( $ajax[0] );
2183 2614 }
2184 2615
2185 2616 /**
@@ -2196,9 +2627,11 @@
2196 2627
2197 2628 $id = BuilderFns::is_builder_preview() ? get_the_ID() : BuilderFns::builder_page_id_by_type( $page );
2198 2629 $elmap = ElementorDataMap::instance();
2199 2630 $ajax = [];
2200 -
2631 + if ( ! $id ) {
2632 + return false;
2633 + }
2201 2634 foreach ( $elmap->get_widget_data( 'rtsb-ajax-product-filters', [], $id ) as $data ) {
2202 2635 $ajax[] = ! isset( $data['settings']['active_filter'] );
2203 2636
2204 2637 }
@@ -2213,24 +2646,94 @@
2213 2646 *
2214 2647 * @return array An array of product categories with 'value' and 'label' keys.
2215 2648 */
2216 2649 public static function products_category_query( $search_query = null ) {
2217 - global $wpdb;
2650 + if ( ! is_admin() ) {
2651 + return [];
2652 + }
2218 2653
2219 - $sql = "SELECT t.term_id, t.name
2654 + $cache_key = 'rtsb_product_categories_' . md5( serialize( $search_query ) );
2655 +
2656 + if ( isset( self::$cache[ $cache_key ] ) ) {
2657 + return self::$cache[ $cache_key ];
2658 + }
2659 + $results = wp_cache_get( $cache_key, 'shopbuilder' );
2660 + if ( ! $results ) {
2661 + global $wpdb;
2662 + $sql = "SELECT t.term_id, t.name
2220 2663 FROM {$wpdb->terms} t
2221 2664 JOIN {$wpdb->term_taxonomy} tt ON t.term_id = tt.term_id
2222 2665 WHERE tt.taxonomy = 'product_cat'";
2223 2666
2224 - if ( $search_query ) {
2225 - $sql .= ' AND t.name LIKE %s';
2226 - $prepared_sql = $wpdb->prepare( $sql, '%' . $wpdb->esc_like( $search_query ) . '%' ); // Escaping and wildcard
2227 - } else {
2228 - $prepared_sql = $sql; // No need for placeholders
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 + }
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 );
2229 2678 }
2230 2679
2231 - $results = $wpdb->get_results( $prepared_sql );
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;
2232 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' );
2716 + if ( ! $results ) {
2717 + global $wpdb;
2718 + $sql = "SELECT t.term_id, t.name
2719 + FROM {$wpdb->terms} t
2720 + JOIN {$wpdb->term_taxonomy} tt ON t.term_id = tt.term_id
2721 + WHERE tt.taxonomy = 'product_tag'";
2722 +
2723 + if ( $search_query ) {
2724 + $sql .= ' AND t.name LIKE %s';
2725 + $prepared_sql = $wpdb->prepare( $sql, '%' . $wpdb->esc_like( $search_query ) . '%' ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
2726 + } else {
2727 + $prepared_sql = $sql; // No need for placeholders.
2728 + }
2729 +
2730 + $results = $wpdb->get_results( $prepared_sql ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.NotPrepared
2731 + // Cache the results in the object cache for future use.
2732 + wp_cache_set( $cache_key, $results, 'shopbuilder' ); // Adjust the expiration time as needed.
2733 + Cache::set_data_cache_key( $cache_key );
2734 + }
2735 +
2233 2736 $cats = [];
2234 2737 if ( ! is_array( $results ) && ! count( $results ) ) {
2235 2738 return $cats;
2236 2739 }
@@ -2243,18 +2746,20 @@
2243 2746 'label' => $category_title,
2244 2747 ];
2245 2748 }
2246 2749
2750 + // Cache the results in a static variable for the next call.
2751 + self::$cache[ $cache_key ] = $cats;
2247 2752 return $cats;
2248 2753 }
2249 2754
2250 2755 /**
2251 - * @param $search_query
2756 + * @param string $search_query Search query.
2252 2757 *
2253 2758 * @return array
2254 2759 */
2255 2760 public static function get_registered_post_types( $search_query = null ) {
2256 - // Get all registered post types
2761 + // Get all registered post types.
2257 2762 $registered_post_types = get_post_types(
2258 2763 [
2259 2764 'public' => true,
2260 2765 '_builtin' => false,
@@ -2267,11 +2772,11 @@
2267 2772 unset( $registered_post_types['rtsb_builder'] );
2268 2773
2269 2774 $post_types = [];
2270 2775
2271 - // Check if a search query is provided
2776 + // Check if a search query is provided.
2272 2777 if ( ! empty( $search_query ) ) {
2273 - // Filter post types based on the search query
2778 + // Filter post types based on the search query.
2274 2779 $registered_post_types = array_filter(
2275 2780 $registered_post_types,
2276 2781 function ( $post_type ) use ( $search_query ) {
2277 2782 return stripos( $post_type->labels->singular_name, $search_query ) !== false;
@@ -2276,9 +2781,9 @@
2276 2781 function ( $post_type ) use ( $search_query ) {
2277 2782 return stripos( $post_type->labels->singular_name, $search_query ) !== false;
2278 2783 }
2279 2784 );
2280 - // Check if 'post' match the search query and include them if they do
2785 + // Check if 'post' match the search query and include them if they do.
2281 2786 if ( stripos( 'post', $search_query ) !== false ) {
2282 2787 $post_types[] = [
2283 2788 'value' => 'post',
2284 2789 'label' => 'Post',
@@ -2290,9 +2795,9 @@
2290 2795 'label' => 'Post',
2291 2796 ];
2292 2797 }
2293 2798
2294 - // Convert the filtered post types to an array
2799 + // Convert the filtered post types to an array.
2295 2800 foreach ( $registered_post_types as $post_type ) {
2296 2801 $post_types[] = [
2297 2802 'value' => $post_type->name,
2298 2803 'label' => $post_type->labels->singular_name,
@@ -2304,9 +2809,9 @@
2304 2809
2305 2810 /**
2306 2811 * Get Post Types.
2307 2812 *
2308 - * @param $search_query
2813 + * @param string $search_query Search query.
2309 2814 *
2310 2815 * @return array
2311 2816 */
2312 2817 public static function get_post_types( $search_query = null, $args = [] ) {
@@ -2317,8 +2822,9 @@
2317 2822 'post_type' => 'any',
2318 2823 'posts_per_page' => 20,
2319 2824 'orderby' => 'ID',
2320 2825 'order' => 'DESC',
2826 + // 'suppress_filters' => false, // WPML Support, Remove Others Languages.
2321 2827 ]
2322 2828 );
2323 2829
2324 2830 if ( 'archive' !== $args['post_type'] ) {
@@ -2329,11 +2835,12 @@
2329 2835 $query_results = get_posts( $args );
2330 2836 foreach ( $query_results as $post ) {
2331 2837 $posts[] = [
2332 2838 'value' => $post->ID,
2333 - 'label' => $post->post_title,
2839 + 'label' => $post->post_title . ' (ID#' . $post->ID . ')',
2334 2840 ];
2335 2841 }
2842 +
2336 2843 if ( $search_query ) {
2337 2844 if ( strpos( '-error 404', $search_query ) ) {
2338 2845 $posts[] = [
2339 2846 'value' => 'error',
@@ -2340,12 +2847,14 @@
2340 2847 'label' => __( '404 Error', 'shopbuilder' ),
2341 2848 ];
2342 2849 }
2343 2850 } else {
2344 - $posts[] = [
2345 - 'value' => 'error',
2346 - 'label' => __( '404 Error', 'shopbuilder' ),
2347 - ];
2851 + if ( 'page' === $args['post_type'] ) {
2852 + $posts[] = [
2853 + 'value' => 'error',
2854 + 'label' => __( '404 Error', 'shopbuilder' ),
2855 + ];
2856 + }
2348 2857 }
2349 2858 } else {
2350 2859 $posts = self::get_registered_post_types( $search_query );
2351 2860 }
@@ -2359,6 +2868,518 @@
2359 2868 * @return array
2360 2869 */
2361 2870 public static function get_elementor_breakpoints() {
2362 2871 return ( new \Elementor\Core\Breakpoints\Manager() )->get_breakpoints_config();
2872 + }
2873 +
2874 + /**
2875 + * Render view.
2876 + *
2877 + * @param string $viewName View name.
2878 + * @param array $args View args.
2879 + * @param boolean $return View return.
2880 + * @return string|void
2881 + */
2882 + public static function renderView( $viewName, $args = [], $return = false ) {
2883 + $viewName = str_replace( '.', '/', $viewName );
2884 +
2885 + if ( ! empty( $args ) && is_array( $args ) ) {
2886 + extract( $args );
2887 + }
2888 +
2889 + $view_file = rtsb()->plugin_path() . '/resources/' . $viewName . '.php';
2890 +
2891 + if ( ! file_exists( $view_file ) ) {
2892 + _doing_it_wrong( __FUNCTION__, sprintf( '<code>%s</code> does not exist.', esc_html( $view_file ) ), '1.7.0' );
2893 +
2894 + return;
2895 + }
2896 +
2897 + if ( $return ) {
2898 + ob_start();
2899 + include $view_file;
2900 +
2901 + return ob_get_clean();
2902 + } else {
2903 + include $view_file;
2904 + }
2905 + }
2906 +
2907 + /**
2908 + * Best selling product query.
2909 + *
2910 + * @param int $minimum_sale Minimum sale/
2911 + * @param int $limit Total limit.
2912 + *
2913 + * @return array|mixed
2914 + */
2915 + public static function best_selling_products_ids( $minimum_sale = 1, $limit = 10 ) {
2916 + $cache_key = 'rtsb_best_selling_products_' . $minimum_sale . '_' . $limit;
2917 + // Check if the data is in the cache.
2918 + if ( isset( self::$cache[ $cache_key ] ) ) {
2919 + return self::$cache[ $cache_key ];
2920 + }
2921 + $cached_data = get_transient( $cache_key );
2922 + if ( $cached_data ) {
2923 + self::$cache[ $cache_key ] = $cached_data;
2924 + return $cached_data;
2925 + }
2926 + global $wpdb;
2927 + $sql = $wpdb->prepare(
2928 + "
2929 + SELECT ID
2930 + FROM {$wpdb->posts} AS p
2931 + LEFT JOIN {$wpdb->postmeta} AS pm ON p.ID = pm.post_id
2932 + WHERE p.post_type = 'product'
2933 + AND p.post_status = 'publish'
2934 + AND pm.meta_key = 'total_sales'
2935 + AND pm.meta_value >= %d
2936 + ORDER BY pm.meta_value + 0 DESC
2937 + LIMIT %d
2938 + ",
2939 + $minimum_sale,
2940 + $limit
2941 + );
2942 + $best_selling = $wpdb->get_col( $sql ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared
2943 + // Cache the result for future use.
2944 + set_transient( $cache_key, $best_selling, DAY_IN_SECONDS );
2945 + self::$cache[ $cache_key ] = $best_selling;
2946 + return $best_selling;
2947 + }
2948 +
2949 + /**
2950 + * @param null|Object $product Product Object.
2951 + * @param int $days_threshold Date String.
2952 + *
2953 + * @return bool
2954 + */
2955 + public static function is_new_product( $product = null, $days_threshold = 30 ) {
2956 + if ( is_null( $product ) ) {
2957 + global $product;
2958 + }
2959 + if ( ! $product instanceof WC_Product ) {
2960 + return false;
2961 + }
2962 +
2963 + $product_id = $product->get_id();
2964 + $days_threshold = ! empty( $days_threshold ) ? $days_threshold : 30;
2965 + $cache_key = 'is_new_product_' . $product_id . '_' . $days_threshold;
2966 +
2967 + if ( isset( self::$cache[ $cache_key ] ) ) {
2968 + return self::$cache[ $cache_key ];
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' ) );
2975 +
2976 + // Calculate the difference in days.
2977 + $days_difference = abs( time() - $publish_timestamp ) / ( 60 * 60 * 24 );
2978 +
2979 + // Check if the product is considered new.
2980 + $is_new = $days_difference <= $days_threshold;
2981 +
2982 + // Cache the result for a day.
2983 + self::$cache[ $cache_key ] = $is_new;
2984 +
2985 + return $is_new;
2986 + }
2987 +
2988 + /**
2989 + * Checks if a feature is disabled for guest users based on the provided option.
2990 + *
2991 + * @param string $option The option to retrieve from the item.
2992 + * @param mixed $default The default value if the option is not found.
2993 + *
2994 + * @return bool
2995 + */
2996 + public static function is_guest_feature_disabled( $option, $default ) {
2997 + $disabled = self::get_option( 'general', 'guest_user', $option, $default );
2998 +
2999 + return ! is_user_logged_in() && $disabled;
3000 + }
3001 +
3002 + /**
3003 + * Checks if a feature is disabled for guest users based on the provided option.
3004 + *
3005 + * @param string $option The option to retrieve from the item.
3006 + * @param mixed $default The default value if the option is not found.
3007 + *
3008 + * @return bool
3009 + */
3010 + public static function woocommerce_output_all_notices() {
3011 + if ( function_exists( 'wc_print_notices' ) ) :
3012 + echo '<div class="rtsb-notice">';
3013 + woocommerce_output_all_notices();
3014 + echo '</div>';
3015 + endif;
3016 + }
3017 +
3018 + /**
3019 + * @param object $product Product.
3020 + * @return bool
3021 + */
3022 + public static function is_visible_qty_input( $product = null ) {
3023 + $is_visible_qty = true;
3024 + if ( $product instanceof \WC_Product ) {
3025 + if ( $product->is_sold_individually() ) {
3026 + $is_visible_qty = false;
3027 + } elseif ( $product->managing_stock() ) {
3028 + if ( in_array( $product->get_backorders(), [ 'notify' , 'yes' ], true ) ) {
3029 + $is_visible_qty = true;
3030 + } elseif ( $product->get_stock_quantity() < 2 ) {
3031 + $is_visible_qty = false;
3032 + }
3033 + }
3034 + }
3035 + return $is_visible_qty;
3036 + }
3037 +
3038 + /**
3039 + * @param int $object_id Object id.
3040 + * @param string $element_type element type.
3041 + * @param string|null $current_lang null for current language, default for default languages.
3042 + * @return false|mixed|null
3043 + */
3044 + public static function wpml_object_id( $object_id, $element_type, $current_lang = 'default' ) {
3045 + if ( ! defined( 'ICL_SITEPRESS_VERSION' ) ) {
3046 + return $object_id;
3047 + }
3048 + if ( ! $object_id || ! $element_type ) {
3049 + return $object_id;
3050 + }
3051 + if ( 'default' === $current_lang ) {
3052 + $lang = apply_filters( 'wpml_default_language', null );
3053 + } else {
3054 + $lang = null;
3055 + }
3056 + return apply_filters( 'wpml_object_id', $object_id, $element_type, false, $lang );
3057 + }
3058 +
3059 + /**
3060 + * @return string
3061 + */
3062 + public static function wpml_current_language() {
3063 + $current = apply_filters( 'wpml_current_language', null );
3064 + $default = apply_filters( 'wpml_default_language', null );
3065 + return $default !== $current ? $current : null;
3066 + }
3067 +
3068 + /**
3069 + * Custom icons.
3070 + *
3071 + * @return string[]
3072 + */
3073 + public static function get_custom_icon_names() {
3074 + return [
3075 + 'heart-empty',
3076 + 'heart',
3077 + 'eye',
3078 + 'exchange',
3079 + 'plus',
3080 + 'minus',
3081 + 'avatar',
3082 + 'pay',
3083 + 'share',
3084 + 'clock',
3085 + 'check-alt',
3086 + 'check',
3087 + 'delete',
3088 + 'marker',
3089 + 'list',
3090 + 'list-2',
3091 + 'power',
3092 + 'cart',
3093 + 'cart-2',
3094 + 'cart-3',
3095 + 'downloads',
3096 + 'zoom',
3097 + 'user-edit',
3098 + 'grid',
3099 + 'filter',
3100 + 'billing',
3101 + 'login',
3102 + 'payment',
3103 + 'search',
3104 + 'edit',
3105 + 'coupon',
3106 + 'arrows-cw',
3107 + 'trash-empty',
3108 + ];
3109 + }
3110 +
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 + /**
3173 + * Flushes rewrite rules.
3174 + *
3175 + * @return void
3176 + */
3177 + public static function maybe_flush_rewrite_rules() {
3178 + add_action( 'wp_loaded', [ __CLASS__, 'flush_rewrite_rules' ] );
3179 + add_action( 'shutdown', [ __CLASS__, 'flush_rewrite_rules_once_more' ] );
3180 + }
3181 +
3182 + /**
3183 + * Flushes rewrite rules if needed.
3184 + *
3185 + * @return void
3186 + */
3187 + public static function flush_rewrite_rules() {
3188 + if ( get_option( 'rtsb_permalinks_need_flush' ) === 'yes' ) {
3189 + flush_rewrite_rules();
3190 + }
3191 +
3192 + if ( get_option( 'rtsb_permalinks_flushed' ) === 'yes' ) {
3193 + flush_rewrite_rules();
3194 + delete_option( 'rtsb_permalinks_flushed' );
3195 + }
3196 + }
3197 +
3198 + /**
3199 + * Flushes rewrite rules if needed for second time.
3200 + *
3201 + * @return void
3202 + */
3203 + public static function flush_rewrite_rules_once_more() {
3204 + if ( get_option( 'rtsb_permalinks_need_flush' ) === 'yes' ) {
3205 + flush_rewrite_rules();
3206 + update_option( 'rtsb_permalinks_flushed', 'yes' );
3207 + delete_option( 'rtsb_permalinks_need_flush' );
3208 + }
3209 + }
3210 +
3211 + /**
3212 + * Social Share Platforms.
3213 + *
3214 + * @return mixed|null
3215 + */
3216 + public static function set_repeater_options( $section_id, $block_id, $option_key, $compare_key, $compare_value, $values ) {
3217 + // $options = self::get_options( $section_id, $block_id );
3218 + $modules = DataModel::source()->get_option( $section_id, [], false );
3219 + if ( empty( $modules[ $block_id ] ) ) {
3220 + return;
3221 + }
3222 + $options = $modules[ $block_id ];
3223 + if ( empty( $options[ $option_key ] ) ) {
3224 + return;
3225 + }
3226 + $repeater_options = json_decode( $options[ $option_key ], true );
3227 + // Check if options are not empty and are in array format.
3228 + if ( ! is_array( $repeater_options ) ) {
3229 + return;
3230 + }
3231 + // Use array_column to get an array of values from $compare_key.
3232 + $column_values = array_column( $repeater_options, $compare_key );
3233 + // Use array_search to find the index of the $compare_value.
3234 + $index = array_search( $compare_value, $column_values, true );
3235 + if ( false === $index ) {
3236 + return;
3237 + }
3238 + $repeater_options[ $index ] = wp_parse_args( $values, $repeater_options[ $index ] );
3239 + $options[ $option_key ] = wp_json_encode( $repeater_options );
3240 + $modules[ $block_id ] = $options;
3241 + DataModel::source()->set_option( $section_id, $modules );
3242 + }
3243 +
3244 +
3245 + /**
3246 + * @param array $ids products id.
3247 + * @return array
3248 + */
3249 + public static function get_available_products_by_ids( $ids = [] ) {
3250 + $ids = array_filter(
3251 + $ids,
3252 + function ( $id ) {
3253 + return 'product' === get_post_type( $id ) && 'publish' === get_post_status( $id );
3254 + }
3255 + );
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;
2363 3384 }
2364 3385 }