PluginProbe
ShopBuilder – WooCommerce Builder For Elementor / 2.4.2
ShopBuilder – WooCommerce Builder For Elementor v2.4.2
3.4.2 3.4.1 3.4.0 2.0.1 2.0.2 2.0.3 2.1.0 2.1.1 2.1.10 2.1.11 2.1.12 2.1.13 2.1.14 2.1.15 2.1.2 2.1.3 2.1.4 2.1.5 2.1.6 2.1.7 2.1.8 2.1.9 2.2.0 2.2.1 2.2.2 All 63 releases
shopbuilder / app / Helpers / Fns.php

Fns.php in ShopBuilder – WooCommerce Builder For Elementor 2.4.2, at app/Helpers/Fns.php

3,106 lines 91.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Fns Helpers class
4 *
5 * @package RadiusTheme\SB
6 */
7
8 namespace RadiusTheme\SB\Helpers;
9
10 // Do not allow directly accessing this file.
11 if ( ! defined( 'ABSPATH' ) ) {
12 exit( 'This script cannot be accessed directly.' );
13 }
14
15 use WC_Product;
16 use WC_Product_Query;
17 use Elementor\Icons_Manager;
18 use RadiusTheme\SB\Models\ReSizer;
19 use RadiusTheme\SB\Models\DataModel;
20 use RadiusTheme\SB\Models\ModuleList;
21 use RadiusTheme\SB\Models\Settings;
22
23 /**
24 * Fns class
25 */
26 class Fns {
27
28 /**
29 * @var array
30 */
31 private static $cache = [];
32
33 /**
34 * Verify nonce.
35 *
36 * @return bool
37 */
38 public static function verify_nonce() {
39 $nonce = isset( $_REQUEST[ rtsb()->nonceId ] ) ? sanitize_text_field( wp_unslash( $_REQUEST[ rtsb()->nonceId ] ) ) : null;
40 $nonceText = rtsb()->nonceText;
41 if ( wp_verify_nonce( $nonce, $nonceText ) ) {
42 return true;
43 }
44
45 return false;
46 }
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
53
54 /**
55 * Set Cookie or Session
56 *
57 * @param $name
58 * @param $value
59 */
60 public static function setSession( $name, $value ) {
61 if ( ! headers_sent() && session_status() == PHP_SESSION_NONE ) {
62 session_start();
63 $_SESSION[ $name ] = $value;
64 } else {
65 $_SESSION[ $name ] = $value;
66 }
67 }
68 /**
69 * Get Cookie or Session
70 *
71 * @param $name
72 *
73 * @return bool
74 */
75 public static function getSession( $name ) {
76 if ( ! headers_sent() && session_status() == PHP_SESSION_NONE ) {
77 session_start();
78 }
79 return $_SESSION[ $name ] ?? null; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
80 }
81 /**
82 * Remove Session Variable
83 *
84 * @param string $name The name of the session variable to remove.
85 */
86 public static function removeSession( $name ) {
87 if ( ! headers_sent() && session_status() == PHP_SESSION_NONE ) {
88 session_start();
89 unset( $_SESSION[ $name ] );
90 } else {
91 unset( $_SESSION[ $name ] );
92 }
93 }
94
95 /**
96 * @param $plugin_slug
97 *
98 * @return bool
99 */
100 public static function check_plugin_installed( $plugin_slug ): bool {
101 $installed_plugins = get_plugins();
102
103 return array_key_exists( $plugin_slug, $installed_plugins ) || in_array( $plugin_slug, $installed_plugins, true );
104 }
105
106 /**
107 * @param $plugin_slug
108 *
109 * @return bool
110 */
111 public static function check_plugin_active( $plugin_slug ): bool {
112 if ( is_plugin_active( $plugin_slug ) ) {
113 return true;
114 }
115
116 return false;
117 }
118
119 /**
120 * Get all user roles.
121 *
122 * @return array|false|mixed
123 */
124 public static function get_all_user_roles() {
125 $cache_key = 'rtsb_get_user_roles';
126 $roles = wp_cache_get( $cache_key, 'shopbuilder' );
127
128 if ( empty( $roles ) ) {
129 if ( ! function_exists( 'get_editable_roles' ) ) {
130 require_once ABSPATH . 'wp-admin/includes/user.php';
131 }
132
133 $user_roles = \get_editable_roles();
134 $roles = [];
135
136 foreach ( $user_roles as $key => $role ) {
137 if ( empty( $role['capabilities'] ) ) {
138 continue;
139 }
140 $roles[ $key ] = $role['name'];
141 }
142 }
143 wp_cache_set( $cache_key, $roles, 'shopbuilder' );
144 Cache::set_data_cache_key( $cache_key );
145 return $roles;
146 }
147
148 /**
149 * @param $data
150 *
151 * @return mixed|string
152 */
153 public static function stripslashes_value( $data ) {
154 if ( is_string( $data ) && strpos( $data, '\\' ) !== false ) {
155 return stripslashes( $data );
156 } elseif ( is_array( $data ) ) {
157 foreach ( $data as $key => $value ) {
158 $data[ $key ] = self::stripslashes_value( $value );
159 }
160 }
161
162 return $data;
163 }
164
165 /**
166 * @param $string
167 *
168 * @return array
169 */
170 public static function multiselect_settings_field_value( $string ) {
171
172 $values = [];
173 if ( empty( $string ) ) {
174 return $values;
175 }
176
177 $stripslashes_data = self::stripslashes_value( $string );
178 if ( is_array( $stripslashes_data ) && count( $stripslashes_data ) ) {
179 foreach ( $stripslashes_data as $item ) {
180 $item = is_array( $item ) ? $item : json_decode( $item, true );
181 $values[] = $item['value'];
182 }
183 }
184
185 return $values;
186 }
187
188 /**
189 * @return bool
190 */
191 public static function check_is_block_theme(): bool {
192 global $wp_version;
193
194 return version_compare( $wp_version, '5.9', '>=' ) && function_exists( 'wp_is_block_theme' ) && wp_is_block_theme();
195 }
196
197 /**
198 * @param string $template_name Template name.
199 * @param string $template_path Template path. (default: '').
200 * @param string $plugin_path Plugin path. (default: ''). fallback file from plugin.
201 *
202 * @return mixed|void
203 */
204 public static function locate_template( $template_name, $template_path = '', $plugin_path = '' ) {
205 $template_name = self::sanitize_file_name( $template_name ) . '.php';
206
207 if ( ! $template_path ) {
208 $template_path = rtsb()->get_template_path();
209 }
210 if ( ! $plugin_path ) {
211 $plugin_path = RTSB_ABSPATH . 'templates/';
212 }
213
214 $template_rtsb_path = trailingslashit( $template_path ) . $template_name;
215 $template_path = '/' . $template_name;
216 $plugin_path = $plugin_path . $template_name;
217 $pro_plugin_path = rtsb()->has_pro() ? RTSBPRO_PATH . 'templates/' . $template_name : '';
218
219 $located = locate_template(
220 apply_filters(
221 'rtsb/core/locate_template_files',
222 [
223 $template_rtsb_path, // Search in <theme>/shopbuilder/.
224 $template_path, // Search in <theme>/.
225 ]
226 )
227 );
228
229 if ( ! $located ) {
230 if ( file_exists( $plugin_path ) ) {
231 return apply_filters( 'rtsb/core/locate_template', $plugin_path, $template_name );
232 } elseif ( $pro_plugin_path && rtsb()->has_pro() && file_exists( $pro_plugin_path ) ) {
233 return apply_filters( 'rtsb/core/locate_template', $pro_plugin_path, $template_name );
234 }
235 }
236
237 /**
238 * APPLY_FILTERS: rtsb/core/locate_template
239 *
240 * Filter the location of the templates.
241 *
242 * @param string $located Template found
243 * @param string $path Template path
244 *
245 * @return string
246 */
247 return apply_filters( 'rtsb/core/locate_template', $located, $template_name );
248 }
249
250 /**
251 * Remove any character that is not alphanumeric, /, _, or -.
252 *
253 * @param string $name Name to sanitize.
254 *
255 * @return array|string|string[]|null
256 */
257 private static function sanitize_file_name( $name ) {
258 return preg_replace( '/[^a-zA-Z0-9\/_\-]/', '', $name );
259 }
260
261 /**
262 * Template Content
263 *
264 * @param string $template_name Template name.
265 * @param array $args Arguments. (default: array).
266 * @param bool $return Whether to return or print the template.
267 * @param string $template_path Template path. (default: '').
268 * @param string $plugin_path Fallback path from where file will load if fail to load from template. (default: '').
269 *
270 * @return false|string|void
271 */
272 public static function load_template( $template_name, array $args = null, $return = false, $template_path = '', $plugin_path = '' ) {
273 $cache_key = sanitize_key( implode( '-', [ 'template', $template_name, $template_path ] ) );
274 $located = (string) wp_cache_get( $cache_key, 'shopbuilder' );
275 if ( ! $located ) {
276 $located = self::locate_template( $template_name, $template_path, $plugin_path );
277 // Don't cache the absolute path so that it can be shared between web servers with different paths.
278 $cache_path = wc_tokenize_path( $located, wc_get_path_define_tokens() );
279 Cache::set_template_cache( $cache_key, $cache_path );
280 } else {
281 // Make sure that the absolute path to the template is resolved.
282 $located = wc_untokenize_path( $located, wc_get_path_define_tokens() );
283 }
284 if ( ! file_exists( $located ) ) {
285 // translators: %s template.
286 self::doing_it_wrong( __FUNCTION__, sprintf( __( '%s does not exist.', 'shopbuilder' ), '<code>' . $located . '</code>' ), '1.0' );
287
288 return;
289 }
290
291 if ( ! empty( $args ) && is_array( $args ) ) {
292 $atts = $args;
293 extract( $args ); // @codingStandardsIgnoreLine
294 }
295
296 // Allow 3rd party plugin filter template file from their plugin.
297 $located = apply_filters( 'rtsb/core/get_template', $located, $template_name, $args );
298
299 if ( $return ) {
300 ob_start();
301 }
302
303 do_action( 'rtsb/core/before_template_part', $template_name, $located, $args );
304 include $located;
305
306 do_action( 'rtsb/core/after_template_part', $template_name, $located, $args );
307
308 if ( $return ) {
309 return ob_get_clean();
310 }
311 }
312
313 /**
314 * Verify nonce.
315 *
316 * @return string
317 */
318 public static function is_woocommerce() {
319 return is_woocommerce() || is_cart() || is_checkout() || is_account_page();
320 }
321
322 /**
323 * Page builder
324 *
325 * @param int $post_id post id.
326 *
327 * @return string
328 */
329 public static function page_edit_with( $post_id ) {
330 if ( ! $post_id ) {
331 return '';
332 }
333
334 $edit_with = get_post_meta( $post_id, '_elementor_edit_mode', true );
335
336 if ( 'builder' === $edit_with ) {
337 $edit_by = 'elementor';
338 } else {
339 $edit_by = 'gutenberg';
340 }
341
342 return $edit_by;
343 }
344
345 /**
346 * Returns default expiration for wishlist cookie
347 *
348 * @return int Number of seconds the cookie should last.
349 */
350 public static function get_cookie_expiration() {
351 return intval( apply_filters( 'rtsb/cookie_expiration', 60 * 60 * 24 * 30 ) );
352 }
353
354 /**
355 * Create a cookie.
356 *
357 * @param string $name Cookie name.
358 * @param mixed $value Cookie value.
359 * @param int $time Cookie expiration time.
360 * @param bool $secure Whether cookie should be available to secured connection only.
361 * @param bool $httponly Whether cookie should be available to HTTP request only (no js handling).
362 *
363 * @return bool
364 * @since 1.0.0
365 */
366 public static function setcookie( $name, $value = [], $time = null, $secure = false, $httponly = false ) {
367
368 if ( ! apply_filters( 'rtsb/set_cookie', true ) || empty( $name ) ) {
369 return false;
370 }
371
372 $time = ! empty( $time ) ? $time : time() + self::get_cookie_expiration();
373
374 $value = wp_json_encode( stripslashes_deep( $value ) );
375 $expiration = apply_filters( 'rtsb/cookie_expiration_time', $time ); // Default 30 days.
376
377 $_COOKIE[ $name ] = $value;
378 wc_setcookie( $name, $value, $expiration, $secure, $httponly );
379
380 return true;
381 }
382
383 /**
384 * Retrieve the value of a cookie.
385 *
386 * @param string $name Cookie name.
387 *
388 * @return mixed
389 * @since 1.0.0
390 */
391 public static function getcookie( $name ) {
392 if ( isset( $_COOKIE[ $name ] ) ) {
393 return json_decode( sanitize_text_field( wp_unslash( $_COOKIE[ $name ] ) ), true );
394 }
395
396 return [];
397 }
398
399 /**
400 * Woocommerce Last product id return
401 */
402 public static function get_prepared_product_id() {
403 if ( is_singular( 'product' ) ) {
404 return get_the_ID();
405 }
406 // Return the Builder Template id.
407
408 if ( get_post_type( get_the_ID() ) == BuilderFns::$post_type_tb ) {
409 $product_id = get_post_meta( get_the_ID(), BuilderFns::$product_template_meta, true );
410 if ( $product_id && get_post_status( $product_id ) ) {
411 return $product_id;
412 }
413 }
414
415 global $wpdb;
416 $cache_key = 'rtsb_prepared_product_id';
417 $_post_id = wp_cache_get( $cache_key, 'shopbuilder' );
418 if ( false === $_post_id || 'publish' !== get_post_status( $_post_id ) ) {
419 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
420 $_post_id = $wpdb->get_var(
421 $wpdb->prepare( "SELECT MAX(ID) FROM {$wpdb->prefix}posts WHERE post_type = %s AND post_status IN('publish', 'draft')", 'product' )
422 );
423 wp_cache_set( $cache_key, $_post_id, 'shopbuilder', 12 * HOUR_IN_SECONDS );
424 Cache::set_data_cache_key( $cache_key );
425 }
426
427 return $_post_id;
428 }
429
430 /**
431 * Get the product function. Only used in single page widgets.
432 *
433 * @return object
434 */
435 public static function get_product() {
436 global $product;
437
438 if ( is_singular( 'product' ) && $product instanceof WC_Product ) {
439 // do_action( 'rtsb_before_product_template_render' );.
440 return $product;
441 }
442 $cache_key = 'prepared_product_for_preview';
443 if ( isset( self::$cache[ $cache_key ] ) ) {
444 return self::$cache[ $cache_key ];
445 }
446 $product = wc_get_product( self::get_prepared_product_id() );
447 self::$cache[ $cache_key ] = $product;
448 do_action( 'rtsb_before_product_template_render' );
449 return $product;
450 }
451
452 /**
453 * Error function
454 *
455 * @param [type] $function function name.
456 * @param [type] $message message.
457 * @param [type] $version version.
458 *
459 * @return void
460 */
461 public static function doing_it_wrong( $function, $message, $version ) {
462 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_wp_debug_backtrace_summary
463 $message .= ' Backtrace: ' . wp_debug_backtrace_summary();
464 _doing_it_wrong( esc_html( $function ), wp_kses_post( $message ), esc_html( $version ) );
465 }
466
467 /**
468 * @return array
469 */
470 public static function get_pages() {
471 $pages = [];
472 $rawPages = get_pages();
473 if ( ! empty( $rawPages ) ) {
474 foreach ( $rawPages as $page ) {
475 $pages[ $page->ID ] = $page->post_title;
476 }
477 }
478
479 return $pages;
480 }
481
482 public static function get_section_items( $section_id ) {
483 if ( ! $section_id ) {
484 return [];
485 }
486
487 return DataModel::source()->get_option( $section_id, [] );
488 }
489
490 public static function get_options( $section_id, $item_id ) {
491 if ( ! $section_id || ! $item_id ) {
492 return [];
493 }
494 $sections = self::get_section_items( $section_id );
495
496 return isset( $sections[ $item_id ] ) ? $sections[ $item_id ] : [];
497 }
498
499 /**
500 * Get options value with default value if options doesn't exist.
501 *
502 * @param $group
503 * @param $option_key
504 * @param $default_value
505 *
506 * @return mixed|string
507 */
508 public static function get_options_by_default_val( $group, $option_key, $default_value = '' ) {
509
510 if ( ! $option_key || ! isset( $group[ $option_key ] ) ) {
511 return $default_value;
512 }
513
514 return $group[ $option_key ];
515 }
516
517 /**
518 * @param string $section_id
519 * @param string $item_id
520 * @param string $option_id
521 * @param null $default EXCEPT multi_checkbox you can provide default value if given option does not set any value
522 * @param null $type checkbox, multi_checkbox, number
523 *
524 * @return bool|int|mixed|null
525 */
526 public static function get_option( $section_id, $item_id, $option_id, $default = null, $type = null ) {
527 $options = self::get_options( $section_id, $item_id );
528
529 if ( $type === 'checkbox' ) {
530 if ( isset( $options[ $option_id ] ) ) {
531 return $options[ $option_id ] === 'on';
532 }
533
534 return $default;
535 } elseif ( $type === 'multi_checkbox' ) {
536 return isset( $options[ $option_id ] ) && is_array( $options[ $option_id ] ) && in_array( $default, $options[ $option_id ] );
537 } elseif ( $type === 'number' ) {
538 return isset( $options[ $option_id ] ) ? absint( $options[ $option_id ] ) : absint( $default );
539 }
540
541 return isset( $options[ $option_id ] ) && ! empty( $options[ $option_id ] ) ? $options[ $option_id ] : $default;
542 }
543
544
545 /**
546 * Create a page and store the ID in an option.
547 *
548 * @param mixed $slug Slug for the new page.
549 * @param array $options ['section_id', 'item_id', 'option_id']Option name to store the page's ID.
550 * @param string $page_title (default: '') Title for the new page.
551 * @param string $page_content (default: '') Content for the new page.
552 * @param int $post_parent (default: 0) Parent for the new page.
553 * @param string $post_status (default: publish) The post status of the new page.
554 *
555 * @return int page ID.
556 */
557 public static function create_page( $slug, $options = '', $page_title = '', $page_content = '', $post_parent = 0, $post_status = 'publish' ) {
558 global $wpdb;
559
560 $option_value = 0;
561 if ( ! empty( $options ) ) {
562 if ( is_array( $options ) ) {
563 $options = wp_parse_args(
564 $options,
565 [
566 'section_id' => '',
567 'item_id' => '',
568 'option_id' => '',
569 ]
570 );
571 $option_value = self::get_option( $options['section_id'], $options['item_id'], $options['option_id'], 0, 'number' );
572 } else {
573 $option_value = absint( get_option( $options ) );
574 }
575 }
576
577 if ( $option_value > 0 ) {
578 $page_object = get_post( $option_value );
579
580 if ( $page_object && 'page' === $page_object->post_type && ! in_array(
581 $page_object->post_status,
582 [
583 'pending',
584 'trash',
585 'future',
586 'auto-draft',
587 ],
588 true
589 ) ) {
590 // Valid page is already in place.
591 return $page_object->ID;
592 }
593 }
594
595 if ( strlen( $page_content ) > 0 ) {
596 // Search for an existing page with the specified page content (typically a shortcode).
597 $shortcode = str_replace( [ '<!-- wp:shortcode -->', '<!-- /wp:shortcode -->' ], '', $page_content );
598 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
599 $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}%" ) );
600 } else {
601 // Search for an existing page with the specified page slug.
602 $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
603 }
604
605 $valid_page_found = apply_filters( 'rtsb/core/create_page_id', $valid_page_found, $slug, $page_content, $options );
606
607 if ( $valid_page_found ) {
608 if ( is_array( $options ) ) {
609 if ( ! empty( $options['section_id'] ) && $options['item_id'] && $options['option_id'] ) {
610 $section_items = DataModel::source()->get_option( $options['section_id'], [] );
611 $section_items[ $options['item_id'] ][ $options['option_id'] ] = $valid_page_found;
612 DataModel::source()->set_option( $options['section_id'], $section_items );
613 }
614 } else {
615 if ( $options ) {
616 update_option( $options, $valid_page_found );
617 }
618 }
619
620 return $valid_page_found;
621 }
622
623 // Search for a matching valid trashed page.
624 if ( strlen( $page_content ) > 0 ) {
625 // Search for an existing page with the specified page content (typically a shortcode).
626 $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
627 } else {
628 // Search for an existing page with the specified page slug.
629 $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
630 }
631
632 if ( $trashed_page_found ) {
633 $page_id = $trashed_page_found;
634 $page_data = [
635 'ID' => $page_id,
636 'post_status' => $post_status,
637 ];
638 wp_update_post( $page_data );
639 } else {
640 $page_data = [
641 'post_status' => $post_status,
642 'post_type' => 'page',
643 'post_author' => 1,
644 'post_name' => $slug,
645 'post_title' => $page_title,
646 'post_content' => $page_content,
647 'post_parent' => $post_parent,
648 'comment_status' => 'closed',
649 ];
650 $page_id = wp_insert_post( $page_data );
651
652 do_action( 'rtsb/core/page_created', $page_id, $page_data );
653 }
654
655 if ( is_array( $options ) ) {
656 if ( ! empty( $options['section_id'] ) && $options['item_id'] && $options['option_id'] ) {
657 $section_items = DataModel::source()->get_option( $options['section_id'], [] );
658 $section_items[ $options['item_id'] ][ $options['option_id'] ] = $page_id;
659 DataModel::source()->set_option( $options['section_id'], $section_items );
660 }
661 } else {
662 if ( $options ) {
663 update_option( $options, $page_id );
664 }
665 }
666
667 return $page_id;
668 }
669
670 public static function get_kses_array() {
671 return [
672 'a' => [
673 'class' => [],
674 'href' => [],
675 'rel' => [],
676 'title' => [],
677 ],
678 'abbr' => [
679 'title' => [],
680 ],
681 'b' => [],
682 'blockquote' => [
683 'cite' => [],
684 ],
685 'cite' => [
686 'title' => [],
687 ],
688 'code' => [],
689 'del' => [
690 'datetime' => [],
691 'title' => [],
692 ],
693 'dd' => [],
694 'div' => [
695 'class' => [],
696 'title' => [],
697 'style' => [],
698 ],
699 'dl' => [],
700 'dt' => [],
701 'em' => [],
702 'h1' => [
703 'class' => [],
704 ],
705 'h2' => [
706 'class' => [],
707 ],
708 'h3' => [
709 'class' => [],
710 ],
711 'h4' => [
712 'class' => [],
713 ],
714 'h5' => [
715 'class' => [],
716 ],
717 'h6' => [
718 'class' => [],
719 ],
720 'i' => [
721 'class' => [],
722 ],
723 'img' => [
724 'alt' => [],
725 'class' => [],
726 'height' => [],
727 'src' => [],
728 'width' => [],
729 ],
730 'li' => [
731 'class' => [],
732 ],
733 'ol' => [
734 'class' => [],
735 ],
736 'p' => [
737 'class' => [],
738 ],
739 'q' => [
740 'cite' => [],
741 'title' => [],
742 ],
743 'span' => [
744 'class' => [],
745 'title' => [],
746 'style' => [],
747 ],
748 'iframe' => [
749 'width' => [],
750 'height' => [],
751 'scrolling' => [],
752 'frameborder' => [],
753 'allow' => [],
754 'src' => [],
755 ],
756 'strike' => [],
757 'br' => [],
758 'strong' => [],
759 'data-wow-duration' => [],
760 'data-wow-delay' => [],
761 'data-wallpaper-options' => [],
762 'data-stellar-background-ratio' => [],
763 'ul' => [
764 'class' => [],
765 ],
766 ];
767 }
768
769
770 /*
771 * Escape output of wishlist icon
772 *
773 * @param string $data Data to escape.
774 * @return string Escaped data
775 */
776 public static function print_icon( $data ) {
777 /**
778 * APPLY_FILTERS: rtsb/core/allowed_icon_html
779 *
780 * Filter the allowed HTML for the icons.
781 *
782 * @param array $allowed_icon_html Allowed HTML
783 *
784 * @return array
785 */
786 $allowed_icon_html = apply_filters(
787 'rtsb/core/allowed_icon_html',
788 [
789 'i' => [
790 'class' => true,
791 ],
792 'img' => [
793 'src' => true,
794 'alt' => true,
795 'width' => true,
796 'height' => true,
797 ],
798 'svg' => [
799 'class' => true,
800 'aria-hidden' => true,
801 'aria-labelledby' => true,
802 'role' => true,
803 'xmlns' => true,
804 'width' => true,
805 'height' => true,
806 'viewbox' => true,
807 'stroke' => true,
808 'fill' => true,
809 ],
810 'g' => [
811 'fill' => true,
812 ],
813 'title' => [
814 'title' => true,
815 ],
816 'path' => [
817 'd' => true,
818 'fill' => true,
819 'stroke' => true,
820 'stroke-width' => true,
821 'stroke-linecap' => true,
822 'stroke-linejoin' => true,
823 'fill-rule' => true,
824 'clip-rule' => true,
825 ],
826 ]
827 );
828
829 echo wp_kses( $data, $allowed_icon_html );
830 }
831
832 /**
833 * Prints HTMl.
834 *
835 * @param string $html HTML.
836 * @param bool $allHtml All HTML.
837 *
838 * @return void
839 */
840 public static function print_html( $html, $allHtml = false ) {
841 if ( ! $html ) {
842 return;
843 }
844 if ( $allHtml ) {
845 echo stripslashes_deep( $html ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
846 } else {
847 echo wp_kses_post( stripslashes_deep( $html ) );
848 }
849 }
850
851 /**
852 * Allowed HTML for wp_kses.
853 *
854 * @param string $level Tag level.
855 *
856 * @return mixed
857 */
858 public static function allowedHtml( $level = 'basic' ) {
859 $allowed_html = [];
860 // TODO:: Need Optimize.
861 switch ( $level ) {
862 case 'basic':
863 $allowed_html = [
864 'b' => [
865 'class' => [],
866 'id' => [],
867 ],
868 'i' => [
869 'class' => [],
870 'id' => [],
871 ],
872 'u' => [
873 'class' => [],
874 'id' => [],
875 ],
876 'br' => [
877 'class' => [],
878 'id' => [],
879 ],
880 'em' => [
881 'class' => [],
882 'id' => [],
883 ],
884 'span' => [
885 'class' => [],
886 'id' => [],
887 ],
888 'strong' => [
889 'class' => [],
890 'id' => [],
891 ],
892 'hr' => [
893 'class' => [],
894 'id' => [],
895 ],
896 'div' => [
897 'class' => [],
898 'id' => [],
899 ],
900 'a' => [
901 'href' => [],
902 'title' => [],
903 'class' => [],
904 'id' => [],
905 'target' => [],
906 ],
907 ];
908 break;
909
910 case 'advanced':
911 $allowed_html = [
912 'b' => [
913 'class' => [],
914 'id' => [],
915 ],
916 'i' => [
917 'class' => [],
918 'id' => [],
919 ],
920 'u' => [
921 'class' => [],
922 'id' => [],
923 ],
924 'br' => [
925 'class' => [],
926 'id' => [],
927 ],
928 'em' => [
929 'class' => [],
930 'id' => [],
931 ],
932 'span' => [
933 'class' => [],
934 'id' => [],
935 ],
936 'strong' => [
937 'class' => [],
938 'id' => [],
939 ],
940 'hr' => [
941 'class' => [],
942 'id' => [],
943 ],
944 'a' => [
945 'href' => [],
946 'title' => [],
947 'class' => [],
948 'id' => [],
949 'target' => [],
950 ],
951 'input' => [
952 'type' => [],
953 'name' => [],
954 'class' => [],
955 'value' => [],
956 ],
957 ];
958 break;
959
960 case 'image':
961 $allowed_html = [
962 'img' => [
963 'src' => [],
964 'data-src' => [],
965 'alt' => [],
966 'height' => [],
967 'width' => [],
968 'class' => [],
969 'id' => [],
970 'style' => [],
971 'srcset' => [],
972 'loading' => [],
973 'sizes' => [],
974 ],
975 'div' => [
976 'class' => [],
977 ],
978 ];
979 break;
980
981 case 'anchor':
982 $allowed_html = [
983 'a' => [
984 'href' => [],
985 'title' => [],
986 'class' => [],
987 'id' => [],
988 'style' => [],
989 ],
990 ];
991 break;
992
993 default:
994 // code...
995 break;
996 }
997
998 return $allowed_html;
999 }
1000
1001 /**
1002 * Safe get a validated HTML tag.
1003 *
1004 * @param string $tag HTML tag.
1005 *
1006 * @return string
1007 */
1008 public static function get_validated_html_tag( $tag ) {
1009 $allowed_html_wrapper_tags = [
1010 'a',
1011 'article',
1012 'aside',
1013 'button',
1014 'div',
1015 'footer',
1016 'h1',
1017 'h2',
1018 'h3',
1019 'h4',
1020 'h5',
1021 'h6',
1022 'header',
1023 'main',
1024 'nav',
1025 'p',
1026 'section',
1027 'span',
1028 ];
1029
1030 return in_array( strtolower( $tag ), $allowed_html_wrapper_tags, true ) ? $tag : 'div';
1031 }
1032
1033 /**
1034 * Safe print a validated HTML tag.
1035 *
1036 * @param string $tag HTML tag.
1037 *
1038 * @return void
1039 */
1040 public static function print_validated_html_tag( $tag ) {
1041 self::print_html( self::get_validated_html_tag( $tag ) );
1042 }
1043
1044 /**
1045 * Insert Some array element
1046 *
1047 * @param [type] $key The elements will insert nearby this key.
1048 * @param [type] $main_array Original array.
1049 * @param [type] $insert_array some element will insert in original array.
1050 * @param boolean $is_after array insert position base on the key.
1051 *
1052 * @return array
1053 */
1054 public static function insert_controls( $key, $main_array, $insert_array, $is_after = false ) {
1055 $index = array_search( $key, array_keys( $main_array ), true );
1056 if ( 'integer' === gettype( $index ) ) {
1057 if ( $is_after ) {
1058 $index++;
1059 }
1060 $main_array = array_merge(
1061 array_slice( $main_array, 0, $index ),
1062 $insert_array,
1063 array_slice( $main_array, $index )
1064 );
1065 }
1066
1067 return $main_array;
1068 }
1069
1070 /**
1071 * Image Sizes
1072 *
1073 * @return array
1074 */
1075 public static function get_image_sizes() {
1076 global $_wp_additional_image_sizes;
1077
1078 $sizes = [];
1079
1080 foreach ( get_intermediate_image_sizes() as $_size ) {
1081 if ( in_array( $_size, [ 'thumbnail', 'medium', 'large' ], true ) ) {
1082 $sizes[ $_size ]['width'] = get_option( "{$_size}_size_w" );
1083 $sizes[ $_size ]['height'] = get_option( "{$_size}_size_h" );
1084 $sizes[ $_size ]['crop'] = (bool) get_option( "{$_size}_crop" );
1085 } elseif ( isset( $_wp_additional_image_sizes[ $_size ] ) ) {
1086 $sizes[ $_size ] = [
1087 'width' => $_wp_additional_image_sizes[ $_size ]['width'],
1088 'height' => $_wp_additional_image_sizes[ $_size ]['height'],
1089 'crop' => $_wp_additional_image_sizes[ $_size ]['crop'],
1090 ];
1091 }
1092 }
1093
1094 $imgSize = [];
1095
1096 foreach ( $sizes as $key => $img ) {
1097 $imgSize[ $key ] = ucfirst( $key ) . " ({$img['width']}*{$img['height']})";
1098 }
1099
1100 $imgSize['full'] = esc_html__( 'Full size', 'shopbuilder' );
1101 $imgSize['rtsb_custom'] = esc_html__( 'Custom image size', 'shopbuilder' );
1102
1103 return $imgSize;
1104 }
1105
1106 /**
1107 * Free Layouts
1108 *
1109 * @return array
1110 */
1111 public static function free_layouts() {
1112 $layouts = [
1113 'grid-layout1' => esc_html__( 'Grid Layout 1', 'shopbuilder' ),
1114 'grid-layout2' => esc_html__( 'Grid Layout 2', 'shopbuilder' ),
1115 'list-layout1' => esc_html__( 'List Layout 1', 'shopbuilder' ),
1116 'list-layout2' => esc_html__( 'List Layout 2', 'shopbuilder' ),
1117 'slider-layout1' => esc_html__( 'Slider Layout 1', 'shopbuilder' ),
1118 'slider-layout2' => esc_html__( 'Slider Layout 2', 'shopbuilder' ),
1119 'category-single-layout1' => esc_html__( 'Category Single Layout 1', 'shopbuilder' ),
1120 'category-layout1' => esc_html__( 'Category Layout 1', 'shopbuilder' ),
1121 'category-layout2' => esc_html__( 'Category Layout 2', 'shopbuilder' ),
1122 ];
1123
1124 return apply_filters( 'rtsb/elements/elementor/free_layouts', $layouts );
1125 }
1126
1127 /**
1128 * Get all terms by taxonomy
1129 *
1130 * @param string $taxonomy Taxonomy name.
1131 * @param bool $placeholder Placeholder..
1132 *
1133 * @return array
1134 */
1135 public static function get_all_terms( $taxonomy, $placeholder = false ) {
1136 $terms = [];
1137
1138 if ( empty( $taxonomy ) ) {
1139 return $terms;
1140 }
1141 $cache_key = 'rtsb_get_all_terms_by_tax_name_' . $taxonomy;
1142 if ( isset( self::$cache[ $cache_key ] ) ) {
1143 return self::$cache[ $cache_key ];
1144 }
1145
1146 $termList = get_terms(
1147 [
1148 'taxonomy' => $taxonomy,
1149 'hide_empty' => false,
1150 ]
1151 );
1152
1153 if ( is_array( $termList ) && ! empty( $termList ) && empty( $termList['errors'] ) ) {
1154 if ( $placeholder ) {
1155 $terms = [
1156 'all' => __( 'All Products', 'shopbuilder' ),
1157 ];
1158 }
1159
1160 foreach ( $termList as $term ) {
1161 $terms[ $term->term_id ] = esc_html( $term->name );
1162 }
1163 }
1164 self::$cache[ $cache_key ] = $terms;
1165 return $terms;
1166 }
1167
1168 /**
1169 * Get all terms by attributes
1170 *
1171 * @return array
1172 */
1173 public static function get_all_attributes() {
1174 $terms = [];
1175 $termList = [];
1176
1177 $attributes = wc_get_attribute_taxonomies();
1178
1179 if ( $attributes ) {
1180 foreach ( $attributes as $tax ) {
1181 if ( taxonomy_exists( wc_attribute_taxonomy_name( $tax->attribute_name ) ) ) {
1182 $termList[ $tax->attribute_name ] = get_terms( wc_attribute_taxonomy_name( $tax->attribute_name ) );
1183 }
1184 }
1185 }
1186
1187 if ( ! empty( $termList ) && empty( $termList['errors'] ) && is_array( $termList ) ) {
1188 foreach ( $termList as $name => $atts ) {
1189 foreach ( $atts as $term ) {
1190 $terms[ $term->term_id ] = esc_html( ucwords( str_replace( '-', ' ', $name ) ) . ' - ' . $term->name );
1191 }
1192 }
1193 }
1194
1195 return $terms;
1196 }
1197
1198 /**
1199 * Get all attributes name
1200 *
1201 * @return array
1202 */
1203 public static function get_all_attributes_name() {
1204 $attributes_name = [];
1205
1206 $attributes = wc_get_attribute_taxonomies();
1207
1208 if ( $attributes ) {
1209 foreach ( $attributes as $tax ) {
1210 if ( taxonomy_exists( wc_attribute_taxonomy_name( $tax->attribute_name ) ) ) {
1211 $attributes_name[ wc_attribute_taxonomy_name( $tax->attribute_name ) ] = ucwords( $tax->attribute_name );
1212 }
1213 }
1214 }
1215
1216 return $attributes_name;
1217 }
1218
1219 /**
1220 * Get User list
1221 *
1222 * @return array
1223 */
1224 public static function get_users() {
1225 $users = [];
1226 $u = get_users();
1227
1228 if ( ! empty( $u ) ) {
1229 foreach ( $u as $user ) {
1230 $users[ $user->ID ] = $user->display_name;
1231 }
1232 }
1233
1234 return $users;
1235 }
1236
1237 /**
1238 * Get Taxonomy List.
1239 *
1240 * @return array
1241 */
1242 public static function get_tax_list() {
1243 return apply_filters(
1244 'rtsb/elements/tax_list',
1245 [
1246 'product_cat' => esc_html__( 'Product Category', 'shopbuilder' ),
1247 ]
1248 );
1249 }
1250
1251 /**
1252 * Get Category Query List.
1253 *
1254 * @return array
1255 */
1256 public static function get_cat_list() {
1257 return [
1258 'all' => esc_html__( 'All Categories', 'shopbuilder' ),
1259 'specific_parent' => esc_html__( 'Sub-Categories by Parent', 'shopbuilder' ),
1260 'cat_ids' => esc_html__( 'Select by ID', 'shopbuilder' ),
1261 'selection' => esc_html__( 'Manual Selection', 'shopbuilder' ),
1262 ];
1263 }
1264
1265 /**
1266 * Get Term List.
1267 *
1268 * @param string $taxonomy Taxonomy.
1269 * @param bool $first_term First term.
1270 * @param bool $only_parents Only parent terms.
1271 * @param bool $return_slug Return with slug.
1272 *
1273 * @return int|array
1274 */
1275 public static function get_terms( $taxonomy, $first_term = false, $only_parents = false, $return_slug = false ) {
1276 if ( ! is_admin() ) {
1277 return [];
1278 }
1279
1280 // TODO:: Return Slug always true for all settings.
1281 $term_list = [];
1282 $args = [
1283 'taxonomy' => $taxonomy,
1284 'hide_empty' => false,
1285 ];
1286
1287 if ( $only_parents ) {
1288 $args['parent'] = 0;
1289 }
1290
1291 $terms = get_terms( $args );
1292
1293 if ( empty( $terms ) ) {
1294 return [ esc_html__( 'Nothing found', 'shopbuilder' ) ];
1295 }
1296
1297 foreach ( $terms as $term ) {
1298 if ( $return_slug ) {
1299 $term_list[ $term->slug ] = $term->name;
1300 } else {
1301 $term_list[ $term->term_id ] = $term->name;
1302 }
1303 }
1304
1305 if ( $first_term ) {
1306 return array_keys( $term_list )[0];
1307 } else {
1308 return $term_list;
1309 }
1310 }
1311
1312 /**
1313 * Get product rating.
1314 *
1315 * @param array $args Arguments.
1316 *
1317 * @return string|void
1318 */
1319 public static function get_product_rating_html( $args = [] ) {
1320 global $product;
1321
1322 $html = '';
1323 $rating_count = $product->get_rating_count();
1324 $average_rating = $product->get_average_rating();
1325
1326 if ( ! rtsb()->has_pro() || empty( $args ) ) {
1327 if ( ! $rating_count || empty( wc_get_rating_html( $average_rating, $rating_count ) ) ) {
1328 return $html;
1329 }
1330
1331 $html .= '<div class="product-rating">';
1332 $html .= wc_get_rating_html( $average_rating, $rating_count );
1333 $html .= ! empty( $html ) ? '<span class="rtsb-count">(' . $average_rating . ')</span>' : '';
1334 $html .= '</div>';
1335
1336 self::print_html( $html, true );
1337
1338 return;
1339 }
1340
1341 if ( empty( $rating_count ) && ! $args['show_empty_rating'] ) {
1342 return '';
1343 }
1344
1345 $preset = ! empty( $args['preset'] ) ? $args['preset'] : 'preset1';
1346 $average = $args['show_average_rating'] ? '<span class="rtsb-count">(' . $average_rating . ')</span>' : '';
1347 $count = '';
1348
1349 if ( $args['show_rating_count'] ) {
1350 $count .= '<div class="rtsb-count">';
1351 $count .= sprintf(
1352 /* translators: %s is the number of reviews */
1353 _n( '%s Review', '%s Reviews', $rating_count, 'shopbuilder' ),
1354 $rating_count
1355 );
1356 $count .= '</div>';
1357 }
1358
1359 if ( 'preset2' === $preset ) {
1360 $average = $args['show_average_rating'] ? '<div class="inner-wrapper"><span class="star-icon"></span><span class="average-rating">' . $average_rating . '</span></div>' : '';
1361 }
1362
1363 $html .= '<div class="product-rating ' . esc_attr( $preset ) . '">';
1364
1365 if ( 'preset1' === $preset ) {
1366 $html .= wc_get_rating_html( $average_rating, $rating_count );
1367 $html .= $average;
1368 $html .= $count;
1369 } elseif ( 'preset2' === $preset ) {
1370 $html .= $average;
1371 $html .= $count;
1372 }
1373
1374 $html .= '</div>';
1375
1376 self::print_html( $html, true );
1377 }
1378
1379 public static function get_product_simple_rating_html() {
1380 global $product;
1381 $html = null;
1382 $rating_count = $product->get_rating_count();
1383 $average_rating = $product->get_average_rating();
1384 $html .= '<div class="product-rating">';
1385 $html .= wc_get_rating_html( $average_rating, $rating_count );
1386 $html .= ! empty( $html ) ? '<span class="rtsb-count">(' . $average_rating . ')</span>' : '';
1387 $html .= '</div>';
1388
1389 self::print_html( $html, true );
1390 }
1391
1392
1393
1394 /**
1395 * Text truncation.
1396 *
1397 * @param string $text_to_truncate Text.
1398 * @param int $limit Limit.
1399 * @param string $after After text.
1400 *
1401 * @return string
1402 */
1403 public static function text_truncation( $text_to_truncate, $limit, $after = '&#8230;' ) {
1404 if ( empty( $limit ) ) {
1405 return $text_to_truncate;
1406 }
1407
1408 $limit++;
1409
1410 $text = '';
1411
1412 if ( mb_strlen( $text_to_truncate ) > $limit ) {
1413 $subex = mb_substr( wp_strip_all_tags( $text_to_truncate ), 0, $limit );
1414 $exwords = explode( ' ', $subex );
1415 $excut = - ( mb_strlen( $exwords[ count( $exwords ) - 1 ] ) );
1416
1417 if ( $excut < 0 ) {
1418 $text .= mb_substr( $subex, 0, $excut ) . $after;
1419 } else {
1420 $text .= $subex . $after;
1421 }
1422 } else {
1423 $text .= $text_to_truncate;
1424 }
1425
1426 return $text;
1427 }
1428
1429 /**
1430 * Promo Badge HTML
1431 *
1432 * @param string $text Text.
1433 * @param string $class Class.
1434 *
1435 * @return void
1436 */
1437 public static function get_badge_html( $text, $class = 'fill' ) {
1438 if ( self::is_module_active( 'product_badges' ) && 'rtsb_yes' === $text ) {
1439 do_action( 'rtsb/modules/product_badges/frontend/display' );
1440
1441 return;
1442 }
1443
1444 if ( empty( $text ) ) {
1445 return;
1446 }
1447 ob_start();
1448 ?>
1449
1450 <ul class="rtsb-promotion-list">
1451 <li class="rtsb-promotion-list-item">
1452 <span class="rtsb-tag-<?php echo ! empty( $class ) ? esc_attr( $class ) : ''; ?>"><?php echo esc_html( $text ); ?></span>
1453 </li>
1454 </ul>
1455
1456 <?php
1457 self::print_html( ob_get_clean() );
1458 }
1459
1460 /**
1461 * Categories HTML
1462 *
1463 * @param int $id Product ID.
1464 * @param string $class Custom class.
1465 *
1466 * @return void|string
1467 */
1468 public static function get_categories_list( $id, $class = 'rtsb-category-outline' ) {
1469 if ( empty( $id ) ) {
1470 return '';
1471 }
1472
1473 ob_start();
1474 ?>
1475
1476 <ul class="rtsb-category-list <?php echo esc_attr( $class ); ?>">
1477 <?php
1478 self::print_html(
1479 wc_get_product_category_list(
1480 $id,
1481 '</li><li class="rtsb-category-list-item">',
1482 '<li class="rtsb-category-list-item">',
1483 '</li>'
1484 )
1485 );
1486 ?>
1487 </ul>
1488
1489 <?php
1490 self::print_html( ob_get_clean() );
1491 }
1492
1493 /**
1494 * Get Featured Image HTML.
1495 *
1496 * @param string $type Image type.
1497 * @param int $post_id Post ID.
1498 * @param string $f_img_size Image size.
1499 * @param null $default_img_id Default image ID.
1500 * @param array $custom_img_size Custom image size.
1501 * @param bool $lazy Lazy load check.
1502 * @param bool $hover Hover image.
1503 * @param bool $gallery Gallery image.
1504 * @param bool $custom_image_id Custom category image ID.
1505 *
1506 * @return string|null
1507 */
1508 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 ) {
1509 $img_html = null;
1510 $attachment_id = null;
1511 $c_size = false;
1512 $hover_class = '';
1513 $post_title = '';
1514
1515 if ( 'rtsb_custom' === $f_img_size ) {
1516 $f_img_size = 'full';
1517 $c_size = true;
1518 }
1519
1520 if ( ! rtsb()->has_pro() ) {
1521 $gallery = false;
1522 }
1523
1524 if ( $hover ) {
1525 $a_id = $post_id;
1526 $hover_class = ' rtsb-img-hover';
1527 } elseif ( $gallery ) {
1528 $a_id = $post_id;
1529 } else {
1530 if ( 'product' === $type ) {
1531 $a_id = get_post_thumbnail_id( $post_id );
1532 $post_title = get_the_title( $post_id );
1533 } elseif ( 'category' === $type ) {
1534 $a_id = $custom_image_id ? $custom_image_id : get_term_meta( $post_id, 'thumbnail_id', true );
1535 $post_title = get_term( $post_id )->name;
1536 } else {
1537 $a_id = $default_img_id;
1538 }
1539 }
1540
1541 $img_alt = trim( wp_strip_all_tags( get_post_meta( $a_id, '_wp_attachment_image_alt', true ) ) );
1542 $alt_tag = ! empty( $img_alt ) ? $img_alt : wp_strip_all_tags( $post_title );
1543 $lazy_class = $lazy ? ' swiper-lazy' : '';
1544 $attr = [
1545 'class' => 'img-responsive rtsb-product-image' . $lazy_class . $hover_class,
1546 'alt' => $alt_tag,
1547 ];
1548
1549 if ( $a_id ) {
1550 $img_html = wp_get_attachment_image( $a_id, $f_img_size, false, $attr );
1551 $attachment_id = $a_id;
1552 }
1553
1554 if ( ! $img_html && $default_img_id ) {
1555 $img_html = wp_get_attachment_image( $default_img_id, $f_img_size, false, $attr );
1556 $attachment_id = $default_img_id;
1557 }
1558
1559 if ( $img_html && $c_size ) {
1560 preg_match( '@src="([^"]+)"@', $img_html, $match );
1561 $img_src = array_pop( $match );
1562 $w = ! empty( $custom_img_size['width'] ) ? absint( $custom_img_size['width'] ) : null;
1563 $h = ! empty( $custom_img_size['height'] ) ? absint( $custom_img_size['height'] ) : null;
1564 $c = ! empty( $custom_img_size['crop'] ) && 'soft' === $custom_img_size['crop'] ? false : true;
1565
1566 if ( $w && $h ) {
1567 $image = self::image_resize( $img_src, $w, $h, $c, false );
1568
1569 if ( ! empty( $image ) ) {
1570 [ $src, $width, $height ] = $image;
1571
1572 $hwstring = image_hwstring( $width, $height );
1573 $attachment = get_post( $attachment_id );
1574 $attr = apply_filters( 'wp_get_attachment_image_attributes', $attr, $attachment, $f_img_size );
1575
1576 if ( $lazy ) {
1577 $attr['data-src'] = $src;
1578 } else {
1579 $attr['src'] = $src;
1580 }
1581
1582 $attr = array_map( 'esc_attr', $attr );
1583 $img_html = rtrim( "<img $hwstring" );
1584
1585 foreach ( $attr as $name => $value ) {
1586 $img_html .= " $name=" . '"' . $value . '"';
1587 }
1588
1589 $img_html .= ' />';
1590 }
1591 }
1592 }
1593
1594 if ( ! $img_html ) {
1595 $hwstring = image_hwstring( 160, 160 );
1596 $attr = isset( $attr['src'] ) ? apply_filters( 'wp_get_attachment_image_attributes', $attr, false, $f_img_size ) : [];
1597 $attr['class'] = 'default-img';
1598 $attr['src'] = esc_url( rtsb()->get_assets_uri( 'images/demo.png' ) );
1599 $attr['alt'] = esc_html__( 'Default Image', 'shopbuilder' );
1600 $img_html = rtrim( "<img $hwstring" );
1601
1602 foreach ( $attr as $name => $value ) {
1603 $img_html .= " $name=" . '"' . $value . '"';
1604 }
1605
1606 $img_html .= ' />';
1607 }
1608
1609 if ( $lazy ) {
1610 $img_html = $img_html . '<div class="swiper-lazy-preloader swiper-lazy-preloader"></div>';
1611 }
1612
1613 return $img_html;
1614 }
1615
1616 /**
1617 * Call the Image resize model for resize function
1618 *
1619 * @param string $url URL.
1620 * @param int $width Width.
1621 * @param int $height Height.
1622 * @param string $crop Crop.
1623 * @param bool|true $single Single.
1624 * @param bool|false $upscale Upscale.
1625 *
1626 * @return array|bool|string
1627 */
1628 public static function image_resize( $url, $width = null, $height = null, $crop = null, $single = true, $upscale = false ) {
1629 $rtResize = new ReSizer();
1630
1631 return $rtResize->process( $url, $width, $height, $crop, $single, $upscale );
1632 }
1633
1634 /**
1635 * Get Product Image
1636 *
1637 * @param string $f_image Featured Image.
1638 * @param string $h_image Hover Image.
1639 *
1640 * @return void
1641 */
1642 public static function get_product_image( $f_image, $h_image = null ) {
1643 if ( empty( $f_image ) ) {
1644 return;
1645 }
1646
1647 echo wp_kses( $f_image, self::allowedHtml( 'image' ) );
1648
1649 if ( ! empty( $h_image ) ) {
1650 echo wp_kses( $h_image, self::allowedHtml( 'image' ) );
1651 }
1652 }
1653
1654 /**
1655 * Post Custom Field value.
1656 *
1657 * @param int $post_id Post id.
1658 * @param string $field_key Custom Field Key.
1659 * @param string $field_fallback FallBack text.
1660 *
1661 * @return string|void
1662 */
1663 public static function get_post_custom_field_value( $post_id, $field_key = '', $field_fallback = '' ) {
1664 if ( ! $post_id || ! $field_key ) {
1665 return;
1666 }
1667 $field_value = get_post_meta( $post_id, $field_key, true );
1668 if ( empty( $field_value ) ) {
1669 $field_value = ! empty( $field_fallback ) ? $field_fallback : $field_value;
1670 }
1671 $field_value = apply_filters( 'rtsb/get_post_custom_field_value/' . $field_key, $field_value, $field_key );
1672
1673 return sprintf( '<span class="rtsb-woo-custom-field">%s</span>', $field_value );
1674 }
1675
1676 /**
1677 * Elementor Icon
1678 *
1679 * @param array $control Elementor Control array.
1680 * @param string $class Custom class.
1681 * @param string $builder Builder name.
1682 *
1683 * @return string
1684 */
1685 public static function icons_manager( $control, $class = '', $builder = 'elementor' ): string {
1686 if ( empty( $control['value'] ) ) {
1687 return '';
1688 }
1689 if ( is_array( $control['value'] ) ) {
1690 $cache_key = 'icons_managet_' . str_replace( ' ', '', md5( serialize( $control['value'] ) ) );
1691 } else {
1692 $cache_key = 'icons_managet_' . str_replace( ' ', '', $control['value'] );
1693 }
1694 if ( isset( self::$cache[ $cache_key ] ) ) {
1695 return self::$cache[ $cache_key ];
1696 }
1697
1698 $attributes = [
1699 'aria-hidden' => 'true',
1700 ];
1701
1702 if ( ! empty( $class ) ) {
1703 $attributes['class'] = esc_attr( $class );
1704 }
1705
1706 ob_start();
1707
1708 if ( defined( 'ELEMENTOR_VERSION' ) && ! empty( $control ) && 'elementor' === $builder ) {
1709 Icons_Manager::render_icon( $control, $attributes );
1710 }
1711
1712 $icons = ob_get_clean();
1713 self::$cache[ $cache_key ] = $icons;
1714 return $icons;
1715 }
1716
1717 /**
1718 * Get product swatches.
1719 *
1720 * @param string $type Swatch type.
1721 *
1722 * @return void
1723 */
1724 public static function get_product_swatches( $type ) {
1725 ?>
1726 <div class="rtsb-swatches <?php echo esc_attr( $type ); ?>-layout">
1727 <?php
1728 do_action( 'rtwpvs_show_archive_variation' );
1729 ?>
1730 </div>
1731 <?php
1732 }
1733
1734 /**
1735 * Get sale badge
1736 *
1737 * @param string $type Badge type.
1738 * @param string $text Badge text.
1739 * @param string $out_of_stock_text Out of stock text.
1740 *
1741 * @return string
1742 */
1743 public static function get_sale_badge( $type, $text, $out_of_stock_text ) {
1744 global $product;
1745
1746 if ( ! $product->is_in_stock() ) {
1747 return $out_of_stock_text;
1748 }
1749
1750 if ( ! $product->is_on_sale() ) {
1751 return '';
1752 }
1753
1754 $badge_text = '';
1755 $percentage = self::calculate_sale_percentage( $product );
1756
1757 if ( $percentage > 0 ) {
1758 $badge_text = '-' . round( $percentage ) . '%';
1759 }
1760
1761 if ( 'text' === $type ) {
1762 $badge_text = $text;
1763 }
1764
1765 return $badge_text;
1766 }
1767
1768 /**
1769 * Get sale badge
1770 *
1771 * @param object $product Product Object.
1772 * @param string $type Badge type.
1773 * @param string $text Badge text.
1774 * @param string $out_of_stock_text Out of stock text.
1775 *
1776 * @return string
1777 */
1778 public static function get_promo_badge( $product, $type, $text, $out_of_stock_text ) {
1779 $disable_badges = self::get_option( 'general', 'guest_user', 'hide_badges', '' );
1780 $badges_visibility = rtsb()->has_pro() && ! is_user_logged_in() && $disable_badges;
1781
1782 if ( $badges_visibility ) {
1783 return '';
1784 }
1785
1786 if ( is_null( $product ) ) {
1787 global $product;
1788 }
1789
1790 if ( ! $product instanceof WC_Product ) {
1791 return '';
1792 }
1793
1794 if ( ! $product->is_in_stock() ) {
1795 return $out_of_stock_text;
1796 }
1797
1798 if ( ! $product->is_on_sale() ) {
1799 return '';
1800 }
1801
1802 $badge_text = '';
1803 $percentage = self::calculate_sale_percentage( $product );
1804
1805 if ( $percentage > 0 ) {
1806 $badge_text = '-' . round( $percentage ) . '%';
1807 }
1808
1809 if ( 'text' === $type ) {
1810 $badge_text = $text;
1811 }
1812
1813 return $badge_text;
1814 }
1815
1816 /**
1817 * Calculate Sale Percentage
1818 *
1819 * @param object $product Product object.
1820 *
1821 * @return float|int|string
1822 */
1823 public static function calculate_sale_percentage( $product = null ) {
1824 if ( is_null( $product ) ) {
1825 global $product;
1826 }
1827
1828 if ( ! $product instanceof WC_Product ) {
1829 return '';
1830 }
1831
1832 if ( ! $product->is_on_sale() ) {
1833 return '';
1834 }
1835
1836 $percentage = null;
1837 $max_percentage = '';
1838
1839 if ( $product->is_type( 'simple' ) ) {
1840 $max_percentage = ( ( $product->get_regular_price() - $product->get_sale_price() ) / $product->get_regular_price() ) * 100;
1841 } elseif ( $product->is_type( 'variable' ) ) {
1842 $max_percentage = 0;
1843
1844 foreach ( $product->get_children() as $child_id ) {
1845
1846 $variation = wc_get_product( $child_id );
1847
1848 $price = $variation->get_regular_price();
1849 $sale = $variation->get_sale_price();
1850
1851 if ( 0 !== $price && ! empty( $sale ) ) {
1852 $percentage = ( $price - $sale ) / $price * 100;
1853 }
1854
1855 if ( $percentage > $max_percentage ) {
1856 $max_percentage = $percentage;
1857 }
1858 }
1859 }
1860
1861 if ( $max_percentage > 0 ) {
1862 return round( $max_percentage );
1863 }
1864
1865 return 0;
1866 }
1867
1868 /**
1869 * Pagination
1870 *
1871 * @param string $pages Pages.
1872 * @param integer $range Range.
1873 * @param boolean $ajax Ajax.
1874 *
1875 * @return string
1876 */
1877 public static function custom_pagination( $pages = '', $range = 4, $ajax = false ) {
1878 $html = null;
1879 $visible_range = apply_filters( 'rtsb/elements/pagination/visible_range', ( $range * 2 ) + 1 );
1880
1881 global $paged;
1882
1883 if ( is_front_page() ) {
1884 $paged = ( get_query_var( 'page' ) ) ? get_query_var( 'page' ) : 1;
1885 } else {
1886 $paged = ( get_query_var( 'paged' ) ) ? get_query_var( 'paged' ) : 1;
1887 }
1888
1889 if ( empty( $paged ) ) {
1890 $paged = 1;
1891 }
1892
1893 if ( '' === $pages ) {
1894 global $wp_query;
1895
1896 $pages = $wp_query->max_num_pages;
1897
1898 if ( ! $pages ) {
1899 $pages = 1;
1900 }
1901 }
1902
1903 $ajaxClass = null;
1904 $dataAttr = null;
1905
1906 if ( $ajax ) {
1907 $ajaxClass = ' rtsb-pagination-ajax';
1908 $dataAttr = "data-paged='1'";
1909 $dataAttr .= ' data-visible-range="' . esc_attr( $visible_range ) . '"';
1910 }
1911
1912 if ( 1 !== $pages ) {
1913 $html .= '<div class="rtsb-pagination' . $ajaxClass . '" ' . $dataAttr . '>';
1914 $html .= '<ul class="pagination-list">';
1915
1916 if ( $paged > 2 && $paged > $visible_range + 1 && $visible_range < $pages ) {
1917 $html .= "<li><a data-paged='1' href='" . get_pagenum_link( 1 ) . "' aria-label='First'>&laquo;</a></li>";
1918 }
1919
1920 if ( $paged > 1 && $visible_range < $pages ) {
1921 $p = $paged - 1;
1922 $html .= "<li><a data-paged='{$p}' href='" . get_pagenum_link( $p ) . "' aria-label='Previous'>&lsaquo;</a></li>";
1923 }
1924
1925 for ( $i = 1; $i <= $pages; $i++ ) {
1926 if ( 1 != $pages && ( ! ( $i >= $paged + $visible_range + 1 || $i <= $paged - $visible_range - 1 ) || $pages <= $visible_range ) ) {
1927 $html .= ( $paged == $i ) ? '<li class="active"><span>' . $i . '</span></li>' : "<li><a data-paged='{$i}' href='" . get_pagenum_link( $i ) . "'>" . $i . '</a></li>';
1928 }
1929 }
1930
1931 if ( $paged < $pages && $visible_range < $pages ) {
1932 $p = $paged + 1;
1933 $html .= "<li><a data-paged='{$p}' href=\"" . get_pagenum_link( $paged + 1 ) . "\" aria-label='Next'>&rsaquo;</a></li>";
1934 }
1935
1936 if ( $paged < $pages - 1 && $paged + $range - 1 < $pages && $visible_range < $pages ) {
1937 $html .= "<li><a data-paged='{$pages}' href='" . get_pagenum_link( $pages ) . "' aria-label='Last'>&raquo;</a></li>";
1938 }
1939
1940 $html .= '</ul>';
1941 $html .= '</div>';
1942 }
1943
1944 return $html;
1945 }
1946
1947 /**
1948 * Action Buttons HTMl
1949 *
1950 * @param array $items Items.
1951 * @param array $ajax_cart Ajax cart HTML.
1952 * @param string $preset Button preset.
1953 * @param array $position Position.
1954 * @param string $placement Placement.
1955 *
1956 * @return void|string
1957 */
1958 public static function get_formatted_action_buttons( $items, $ajax_cart = '', $preset = 'preset1', $position = 'above', $placement = 'top' ) {
1959 if ( empty( $items ) ) {
1960 return;
1961 }
1962
1963 $html = '';
1964 $class = '';
1965 $args = [ $items, $ajax_cart, $preset, $position, $placement ];
1966
1967 if ( ( 'top' === $placement && 'after' === $position ) || ( 'bottom' === $placement && 'above' === $position ) ) {
1968 return apply_filters( 'rtsb/elements/elementor/formatted_action_buttons', $html, $args );
1969 }
1970
1971 if ( 'preset2' === $preset ) {
1972 $class = 'rtsb-action-buttons-vertical vertical-delay-effect ' . $preset;
1973 } elseif ( 'preset4' === $preset ) {
1974 $class = 'rtsb-action-buttons-vertical rtsb-action-buttons-vertical-left vertical-delay-effect ' . $preset;
1975 } elseif ( 'preset1' === $preset || 'preset3' === $preset ) {
1976 $class = 'rtsb-action-buttons-cart-box-width-auto horizontal-floating-btn ' . $preset;
1977 }
1978
1979 if ( 'after' === $position && 'preset1' === $preset ) {
1980 $class .= ' after-content';
1981 }
1982
1983 if ( 'preset3' === $preset ) {
1984 $html .= '<div class="rtsb-action-buttons top-part ' . esc_attr( $preset ) . '">';
1985 $html .= '<ul class="rtsb-action-button-list">';
1986
1987 ob_start();
1988 /**
1989 * Additional formatted action buttons hook.
1990 */
1991 do_action( 'rtsb/elements/elementor/additional_action_buttons', $items );
1992 $html .= ob_get_clean();
1993
1994 $html .= self::get_action_button_by_type( $items, 'wishlist' );
1995 $html .= self::get_action_button_by_type( $items, 'compare' );
1996 $html .= self::get_action_button_by_type( $items, 'quick_view' );
1997 $html .= '</ul>';
1998 $html .= '</div>';
1999 $html .= '<div class="rtsb-action-buttons bottom-part ' . esc_attr( $preset ) . '">';
2000 $html .= '<ul class="rtsb-action-button-list">';
2001 $html .= self::get_action_button_by_type( $items, 'add_to_cart', $ajax_cart );
2002 $html .= '</ul>';
2003 $html .= '</div>';
2004 } elseif ( 'preset1' === $preset || 'preset2' === $preset || 'preset4' === $preset ) {
2005 $html .= '<div class="rtsb-action-buttons ' . esc_attr( $class ) . '">';
2006 $html .= '<ul class="rtsb-action-button-list">';
2007 $html .= self::get_action_button_by_type( $items, 'add_to_cart', $ajax_cart );
2008
2009 ob_start();
2010 /**
2011 * Additional formatted action buttons hook.
2012 */
2013 do_action( 'rtsb/elements/elementor/additional_action_buttons', $items );
2014 $html .= ob_get_clean();
2015
2016 $html .= self::get_action_button_by_type( $items, 'wishlist' );
2017 $html .= self::get_action_button_by_type( $items, 'compare' );
2018 $html .= self::get_action_button_by_type( $items, 'quick_view' );
2019 $html .= '</ul>';
2020 $html .= '</div>';
2021 }
2022
2023 return apply_filters( 'rtsb/elements/elementor/formatted_action_buttons', $html, $args );
2024 }
2025
2026 /**
2027 * Get Action Button HTML
2028 *
2029 * @param array $items Items.
2030 * @param string $type Button type.
2031 * @param string $cart_html Ajax cart HTML.
2032 * @param string $wrapper Wrapper tag.
2033 *
2034 * @return void|string
2035 */
2036 public static function get_action_button_by_type( $items, $type, $cart_html = '', $wrapper = 'li' ) {
2037 if ( ! in_array( $type, $items, true ) ) {
2038 return;
2039 }
2040
2041 $html = '';
2042 $class = 'rtsb-action-button-item';
2043
2044 if ( 'add_to_cart' === $type ) {
2045 $class .= ' rtsb-cart' . ( empty( $cart_html ) ? esc_attr( ' no-cart-button' ) : '' );
2046 } else {
2047 $class .= ' rtsb-' . esc_attr( str_replace( '_', '-', $type ) );
2048 }
2049
2050 $html .= '<' . esc_attr( $wrapper ) . ' class="' . esc_attr( $class ) . '">';
2051
2052 if ( ( 'add_to_cart' === $type ) && ( ! empty( $cart_html ) ) ) {
2053 $html .= $cart_html;
2054 } else {
2055 $html .= shortcode_exists( 'rtsb_' . $type . '_button' ) ? do_shortcode( '[rtsb_' . $type . '_button]' ) : null;
2056 }
2057
2058 $html .= '</' . esc_attr( $wrapper ) . '>';
2059
2060 return apply_filters( 'rtsb/elements/elementor/get_action_button_by_type', $html );
2061 }
2062
2063 /**
2064 * Social Share Platforms.
2065 *
2066 * @return mixed|null
2067 */
2068 public static function social_share_platforms_list() {
2069 return apply_filters(
2070 'rtsb/settings/social_share/platforms',
2071 [
2072 [
2073 'value' => 'facebook',
2074 'label' => esc_html__( 'Facebook', 'shopbuilder' ),
2075 ],
2076 [
2077 'value' => 'twitter',
2078 'label' => esc_html__( 'Twitter', 'shopbuilder' ),
2079 ],
2080 [
2081 'value' => 'linkedin',
2082 'label' => esc_html__( 'Linkedin', 'shopbuilder' ),
2083 ],
2084 [
2085 'value' => 'pinterest',
2086 'label' => esc_html__( 'Pinterest', 'shopbuilder' ),
2087 ],
2088 [
2089 'value' => 'skype',
2090 'label' => esc_html__( 'Skype', 'shopbuilder' ),
2091 ],
2092 [
2093 'value' => 'whatsapp',
2094 'label' => esc_html__( 'Whatsapp', 'shopbuilder' ),
2095 ],
2096 [
2097 'value' => 'reddit',
2098 'label' => esc_html__( 'Reddit', 'shopbuilder' ),
2099 ],
2100 [
2101 'value' => 'telegram',
2102 'label' => esc_html__( 'Telegram', 'shopbuilder' ),
2103 ],
2104 ]
2105 );
2106 }
2107
2108 /**
2109 * Get Social Share link HTML.
2110 *
2111 * @param int $id Post ID.
2112 * @param array $types Preset type.
2113 * @param string $preset Style type.
2114 * @param boolean $show_icon Show icon.
2115 * @param boolean $show_text Show text.
2116 *
2117 * @return string
2118 */
2119 public static function get_social_share_html( int $id, array $types, string $preset = 'default', $show_icon = true, $show_text = true ) {
2120 $attr = [ 'postid' => $id ];
2121 $output = '';
2122
2123 if ( empty( $types ) ) {
2124 return $output;
2125 }
2126
2127 foreach ( $types as $type ) {
2128 $link = [];
2129 $link['type'] = $type['share_items'];
2130 $link['class'] = '';
2131 $link['img'] = apply_filters( 'rtsb/elements/share/default_img', '', $id, $link );
2132
2133 if ( 'site' === $id ) {
2134 $link['url'] = home_url();
2135 $link['title'] = wp_strip_all_tags( get_bloginfo( 'name' ) );
2136 } elseif ( 0 === strpos( $id, 'http' ) ) {
2137 $link['url'] = $id;
2138 $link['title'] = '';
2139 } else {
2140 $link['url'] = get_permalink( $id );
2141 $link['title'] = wp_strip_all_tags( get_the_title( $id ) );
2142
2143 if ( has_post_thumbnail( $id ) ) {
2144 $link['img'] = wp_get_attachment_image_url( get_post_thumbnail_id( $id ), 'full' );
2145 }
2146
2147 $link['img'] = apply_filters( 'rtsb/elements/share/single_img', $link['img'], $id, $link );
2148 }
2149
2150 $link['url'] = apply_filters( 'rtsb/elements/share/url', $link['url'], $link );
2151
2152 switch ( $type['share_items'] ) {
2153 case 'facebook':
2154 $link['link'] = esc_url( 'https://www.facebook.com/sharer/sharer.php?u=' . $link['url'] . '&display=popup&ref=plugin&src=share_button' );
2155 $link['icon'] = '<svg xmlns="http://www.w3.org/2000/svg" width="18.8125" height="32" viewBox="0 0 602 1024"><path d="M548 6.857v150.857h-89.714q-49.143 0-66.286 20.571t-17.143 61.714v108h167.429l-22.286 169.143h-145.143v433.714h-174.857v-433.714h-145.714v-169.143h145.714v-124.571q0-106.286 59.429-164.857t158.286-58.571q84 0 130.286 6.857z"></path></svg>';
2156 $link['attr_title'] = esc_html__( 'Share on Facebook', 'shopbuilder' );
2157 $link['social_network'] = 'Facebook';
2158 $link['social_action'] = 'Share';
2159 break;
2160 case 'twitter':
2161 $link['link'] = esc_url( 'https://twitter.com/intent/tweet?text=' . htmlspecialchars( rawurlencode( html_entity_decode( $link['title'], ENT_COMPAT, 'UTF-8' ) ), ENT_COMPAT, 'UTF-8' ) . '&url=' . $link['url'] );
2162 $link['icon'] = '<svg width="24" height="24" viewBox="0 0 1200 1227" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M714.163 519.284L1160.89 0H1055.03L667.137 450.887L357.328 0H0L468.492 681.821L0 1226.37H105.866L515.491 750.218L842.672 1226.37H1200L714.137 519.284H714.163ZM569.165 687.828L521.697 619.934L144.011 79.6944H306.615L611.412 515.685L658.88 583.579L1055.08 1150.3H892.476L569.165 687.854V687.828Z" /></svg>';
2163 $link['attr_title'] = esc_html__( 'Share on Twitter', 'shopbuilder' );
2164 $link['social_network'] = 'Twitter';
2165 $link['social_action'] = 'Tweet';
2166 break;
2167 case 'pinterest':
2168 $link['link'] = esc_url( 'https://pinterest.com/pin/create/button/?url=' . $link['url'] . '&media=' . $link['img'] . '&description=' . $link['title'] );
2169 $link['icon'] = '<svg xmlns="http://www.w3.org/2000/svg" width="22.84375" height="32" viewBox="0 0 731 1024"><path d="M0 341.143q0-61.714 21.429-116.286t59.143-95.143 86.857-70.286 105.714-44.571 115.429-14.857q90.286 0 168 38t126.286 110.571 48.571 164q0 54.857-10.857 107.429t-34.286 101.143-57.143 85.429-82.857 58.857-108 22q-38.857 0-77.143-18.286t-54.857-50.286q-5.714 22.286-16 64.286t-13.429 54.286-11.714 40.571-14.857 40.571-18.286 35.714-26.286 44.286-35.429 49.429l-8 2.857-5.143-5.714q-8.571-89.714-8.571-107.429 0-52.571 12.286-118t38-164.286 29.714-116q-18.286-37.143-18.286-96.571 0-47.429 29.714-89.143t75.429-41.714q34.857 0 54.286 23.143t19.429 58.571q0 37.714-25.143 109.143t-25.143 106.857q0 36 25.714 59.714t62.286 23.714q31.429 0 58.286-14.286t44.857-38.857 32-54.286 21.714-63.143 11.429-63.429 3.714-56.857q0-98.857-62.571-154t-163.143-55.143q-114.286 0-190.857 74t-76.571 187.714q0 25.143 7.143 48.571t15.429 37.143 15.429 26 7.143 17.429q0 16-8.571 41.714t-21.143 25.714q-1.143 0-9.714-1.714-29.143-8.571-51.714-32t-34.857-54-18.571-61.714-6.286-60.857z"></path></svg>';
2170 $link['attr_title'] = esc_html__( 'Share on Pinterest', 'shopbuilder' );
2171 $link['social_network'] = 'Pinterest';
2172 $link['social_action'] = 'Pin';
2173 break;
2174 case 'linkedin':
2175 $link['link'] = esc_url( 'https://www.linkedin.com/shareArticle?url=' . $link['url'] . '&title=' . $link['title'] );
2176 $link['icon'] = '<svg xmlns="http://www.w3.org/2000/svg" width="27.4375" height="32" viewBox="0 0 878 1024"><path d="M199.429 357.143v566.286h-188.571v-566.286h188.571zM211.429 182.286q0.571 41.714-28.857 69.714t-77.429 28h-1.143q-46.857 0-75.429-28t-28.571-69.714q0-42.286 29.429-70t76.857-27.714 76 27.714 29.143 70zM877.714 598.857v324.571h-188v-302.857q0-60-23.143-94t-72.286-34q-36 0-60.286 19.714t-36.286 48.857q-6.286 17.143-6.286 46.286v316h-188q1.143-228 1.143-369.714t-0.571-169.143l-0.571-27.429h188v82.286h-1.143q11.429-18.286 23.429-32t32.286-29.714 49.714-24.857 65.429-8.857q97.714 0 157.143 64.857t59.429 190z"></path></svg>';
2177 $link['attr_title'] = esc_html__( 'Share on LinkedIn', 'shopbuilder' );
2178 $link['social_network'] = 'LinkedIn';
2179 $link['social_action'] = 'Share';
2180 break;
2181 case 'skype':
2182 $link['link'] = esc_url( 'https://web.skype.com/share?url=' . $link['url'] );
2183 $link['icon'] = '<svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="24px" height="24px" viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve" class="eapps-social-share-buttons-item-icon"> <path d="M23.016,13.971c0.111-0.638,0.173-1.293,0.173-1.963 c0-6.213-5.014-11.249-11.199-11.249c-0.704,0-1.393,0.068-2.061,0.193C8.939,0.348,7.779,0,6.536,0C2.926,0,0,2.939,0,6.565 c0,1.264,0.357,2.443,0.973,3.445c-0.116,0.649-0.18,1.316-0.18,1.999c0,6.212,5.014,11.25,11.198,11.25 c0.719,0,1.419-0.071,2.099-0.201C15.075,23.656,16.229,24,17.465,24C21.074,24,24,21.061,24,17.435 C24,16.163,23.639,14.976,23.016,13.971z M12.386,19.88c-3.19,0-6.395-1.453-6.378-3.953c0.005-0.754,0.565-1.446,1.312-1.446 c1.877,0,1.86,2.803,4.85,2.803c2.098,0,2.814-1.15,2.814-1.95c0-2.894-9.068-1.12-9.068-6.563c0-2.945,2.409-4.977,6.196-4.753 c3.61,0.213,5.727,1.808,5.932,3.299c0.102,0.973-0.543,1.731-1.662,1.731c-1.633,0-1.8-2.188-4.613-2.188 c-1.269,0-2.341,0.53-2.341,1.679c0,2.402,9.014,1.008,9.014,6.295C18.441,17.882,16.012,19.88,12.386,19.88z"></path> </svg>';
2184 $link['attr_title'] = esc_html__( 'Share on Skype', 'shopbuilder' );
2185 $link['social_network'] = 'Skype';
2186 $link['social_action'] = 'Skype';
2187 break;
2188 case 'whatsapp':
2189 $link['link'] = esc_url( 'https://wa.me/?text=' . $link['title'] . ' ' . $link['url'] );
2190 $link['icon'] = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-whatsapp" viewBox="0 0 16 16"> <path d="M13.601 2.326A7.854 7.854 0 0 0 7.994 0C3.627 0 .068 3.558.064 7.926c0 1.399.366 2.76 1.057 3.965L0 16l4.204-1.102a7.933 7.933 0 0 0 3.79.965h.004c4.368 0 7.926-3.558 7.93-7.93A7.898 7.898 0 0 0 13.6 2.326zM7.994 14.521a6.573 6.573 0 0 1-3.356-.92l-.24-.144-2.494.654.666-2.433-.156-.251a6.56 6.56 0 0 1-1.007-3.505c0-3.626 2.957-6.584 6.591-6.584a6.56 6.56 0 0 1 4.66 1.931 6.557 6.557 0 0 1 1.928 4.66c-.004 3.639-2.961 6.592-6.592 6.592zm3.615-4.934c-.197-.099-1.17-.578-1.353-.646-.182-.065-.315-.099-.445.099-.133.197-.513.646-.627.775-.114.133-.232.148-.43.05-.197-.1-.836-.308-1.592-.985-.59-.525-.985-1.175-1.103-1.372-.114-.198-.011-.304.088-.403.087-.088.197-.232.296-.346.1-.114.133-.198.198-.33.065-.134.034-.248-.015-.347-.05-.099-.445-1.076-.612-1.47-.16-.389-.323-.335-.445-.34-.114-.007-.247-.007-.38-.007a.729.729 0 0 0-.529.247c-.182.198-.691.677-.691 1.654 0 .977.71 1.916.81 2.049.098.133 1.394 2.132 3.383 2.992.47.205.84.326 1.129.418.475.152.904.129 1.246.08.38-.058 1.171-.48 1.338-.943.164-.464.164-.86.114-.943-.049-.084-.182-.133-.38-.232z"/> </svg>';
2191 $link['attr_title'] = esc_html__( 'Share on Whatsapp', 'shopbuilder' );
2192 $link['social_network'] = 'Whatsapp';
2193 $link['social_action'] = 'Share';
2194 break;
2195 case 'reddit':
2196 $link['link'] = esc_url( 'https://reddit.com/submit?url=' . $link['url'] . '&title=' . $link['title'] );
2197 $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>';
2198 $link['attr_title'] = esc_html__( 'Share on Reddit', 'shopbuilder' );
2199 $link['social_network'] = 'Reddit';
2200 $link['social_action'] = 'Share';
2201 break;
2202 case 'telegram':
2203 $link['link'] = esc_url( 'https://telegram.me/share/url?text=' . $link['title'] . '&url=' . $link['url'] );
2204 $link['icon'] = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-telegram" viewBox="0 0 16 16"> <path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zM8.287 5.906c-.778.324-2.334.994-4.666 2.01-.378.15-.577.298-.595.442-.03.243.275.339.69.47l.175.055c.408.133.958.288 1.243.294.26.006.549-.1.868-.32 2.179-1.471 3.304-2.214 3.374-2.23.05-.012.12-.026.166.016.047.041.042.12.037.141-.03.129-1.227 1.241-1.846 1.817-.193.18-.33.307-.358.336a8.154 8.154 0 0 1-.188.186c-.38.366-.664.64.015 1.088.327.216.589.393.85.571.284.194.568.387.936.629.093.06.183.125.27.187.331.236.63.448.997.414.214-.02.435-.22.547-.82.265-1.417.786-4.486.906-5.751a1.426 1.426 0 0 0-.013-.315.337.337 0 0 0-.114-.217.526.526 0 0 0-.31-.093c-.3.005-.763.166-2.984 1.09z"/> </svg>';
2205 $link['attr_title'] = esc_html__( 'Share on Telegram', 'shopbuilder' );
2206 $link['social_network'] = 'Telegram';
2207 $link['social_action'] = 'Share';
2208 break;
2209 }
2210
2211 $link['label'] = $type['share_text'];
2212 $link['target'] = '_blank';
2213 $link['rel'] = 'nofollow noopener noreferrer';
2214
2215 $data = '';
2216 $link = apply_filters( 'rtsb/elements/share/share_link', $link, $id, $preset );
2217 $icon = apply_filters( 'rtsb/elements/share/share_icon', $link['icon'], $preset );
2218 $target = ! empty( $link['target'] ) ? ' target="' . esc_attr( $link['target'] ) . '" ' : '';
2219 $rel = ! empty( $link['rel'] ) ? ' rel="' . esc_attr( $link['rel'] ) . '" ' : '';
2220 $attr_title = ! empty( $link['attr_title'] ) ? ' title="' . esc_attr( $link['attr_title'] ) . '" ' : '';
2221 $elements = [];
2222
2223 // Add classes.
2224 $css_classes = [
2225 'rtsb-share-btn',
2226 sanitize_html_class( $link['type'] ),
2227 ];
2228 $css_classes = array_merge( $css_classes, explode( ' ', $link['class'] ) );
2229 $css_classes = array_map( 'sanitize_html_class', $css_classes );
2230 $css_classes = implode( ' ', array_filter( $css_classes ) );
2231
2232 unset( $attr['pin-do'], $attr['action'] );
2233
2234 if ( 'pinterest' === $type['share_items'] ) {
2235 $attr['pin-do'] = 'none';
2236 }
2237
2238 if ( 'whatsapp' === $type['share_items'] ) {
2239 $attr['action'] = 'share/whatsapp/share';
2240 }
2241
2242 $attr = apply_filters( 'rtsb/elements/share/link_data', $attr, $link, $id );
2243
2244 if ( ! empty( $attr ) ) {
2245 foreach ( $attr as $key => $val ) {
2246 $data .= ' data-' . sanitize_html_class( $key ) . '="' . esc_attr( $val ) . '"';
2247 }
2248 }
2249
2250 $additional_attr = apply_filters( 'rtsb/elements/share/additional_attr', [], $link, $id, $preset );
2251
2252 if ( ! empty( $additional_attr ) ) {
2253 $attr_output = join( ' ', $additional_attr );
2254
2255 if ( ! empty( $data ) ) {
2256 $attr_output = ' ' . $attr_output;
2257 }
2258
2259 $data .= $attr_output;
2260 }
2261
2262 $elements['wrapper_start'] = sprintf(
2263 '<li class="rtsb-share-item"><a href="%s"%s%s%s class="%s"%s>',
2264 ! empty( $link['link'] ) ? esc_attr( $link['link'] ) : '',
2265 $attr_title,
2266 $target,
2267 $rel,
2268 $css_classes,
2269 $data
2270 );
2271 $elements['wrapper_end'] = '</a></li>';
2272
2273 $elements['icon'] = $show_icon ? '<span class="rtsb-share-icon">' . ( ! empty( $icon ) ? $icon : null ) . '</span>' : null;
2274 $elements['label'] = $show_text && ! empty( $link['label'] ) ? '<span class="rtsb-share-label">' . $link['label'] . '</span>' : null;
2275 $elements['icon_label'] = '<span class="rtsb-share-icon-label">' . $elements['icon'] . $elements['label'] . '</span>';
2276 $elements = apply_filters( 'rtsb/elements/share/output_elements', $elements, $link, $id );
2277
2278 $output .= $elements['wrapper_start'] . $elements['icon_label'] . $elements['wrapper_end'];
2279 }
2280
2281 return apply_filters( 'rtsb/elements/share/list_output', $output );
2282 }
2283
2284 /**
2285 * Social Share Platforms.
2286 *
2287 * @return mixed|null
2288 */
2289 public static function is_module_active( $module ) {
2290 $modulelist = ModuleList::instance()->get_data();
2291 if ( ! empty( $modulelist[ $module ]['active'] ) ) {
2292 return true;
2293 }
2294 }
2295
2296 /***
2297 * Save Settings data
2298 *
2299 * @param $section_id
2300 * @param $block_id
2301 * @param $rawOptions
2302 *
2303 * @return array
2304 */
2305 public static function set_options( $section_id = '', $block_id = '', $rawOptions = [] ) {
2306 $section_id = ! empty( $section_id ) ? sanitize_text_field( wp_unslash( $section_id ) ) : '';
2307 $block_id = ! empty( $block_id ) ? sanitize_text_field( wp_unslash( $block_id ) ) : '';
2308 $rawOptions = ! empty( $rawOptions ) ? $rawOptions : [];
2309 $results = [
2310 'status' => true,
2311 'message' => '',
2312 ];
2313 if ( ! $section_id || ! $block_id ) {
2314 $results['status'] = false;
2315 $results['message'] = esc_html__( 'Section , block or options may be empty', 'shopbuilder' );
2316
2317 return $results;
2318 }
2319 $sections = Settings::instance()->get_sections();
2320 if ( empty( $sections[ $section_id ] ) || empty( $sections[ $section_id ]['list'][ $block_id ] ) ) {
2321 $results['status'] = false;
2322 $results['message'] = esc_html__( 'No section or block found with given data', 'shopbuilder' );
2323
2324 return $results;
2325 }
2326
2327 $options = DataModel::source()->get_option( $section_id, [], false );
2328 $changed = false;
2329 $fields = [];
2330 if ( isset( $sections[ $section_id ]['list'][ $block_id ]['fields'] ) && ! empty( $sections[ $section_id ]['list'][ $block_id ]['fields'] ) ) {
2331 $fields = $sections[ $section_id ]['list'][ $block_id ]['fields'];
2332 }
2333
2334 if ( empty( $fields ) ) {
2335 if ( isset( $rawOptions['active'] ) ) {
2336 $changed = true;
2337 $options[ $block_id ]['active'] = 'on' === $rawOptions['active'] ? 'on' : '';
2338 }
2339 } else {
2340 foreach ( $rawOptions as $raw_option_key => $raw_value ) {
2341 if ( $raw_option_key === 'active' ) {
2342 $changed = true;
2343 $options[ $block_id ]['active'] = $sections[ $section_id ]['list'][ $block_id ]['active'] = 'on' === $raw_value ? 'on' : '';
2344 } else {
2345 if ( isset( $fields[ $raw_option_key ] ) ) {
2346 $field = $fields[ $raw_option_key ];
2347 if ( $field['type'] === 'switch' ) {
2348 $value = 'on' === $raw_value ? 'on' : '';
2349 } elseif ( 'repeaters' === $field['type'] ) {
2350 $the_value = [];
2351 $rep_title = [];
2352 if ( ! empty( $raw_value ) && is_array( $raw_value ) ) {
2353 foreach ( $raw_value as $key => $value ) {
2354 if ( is_string( $value ) ) {
2355 $the_value_decoded = json_decode( stripslashes_deep( $value ) );
2356 } else {
2357 $the_value_decoded = $value;
2358 }
2359 $cr = $the_value_decoded->title ?? '';
2360 if ( in_array( $cr, $rep_title, true ) ) {
2361 continue;
2362 }
2363 $rep_title[] = $cr;
2364 $the_value[] = $the_value_decoded;
2365 }
2366 } elseif ( ! empty( $raw_value ) && is_string( $raw_value ) ) {
2367 $the_value = $raw_value;
2368 }
2369 $value = wp_json_encode( $the_value );
2370 } elseif ( in_array( $field['type'], [ 'product_addons_special_settings', 'checkout_fields' ] ) ) {
2371 $manual_field_value = [];
2372 if ( is_array( $raw_value ) ) {
2373 foreach ( $raw_value as $key => $value ) {
2374 $manual_field_value[] = json_decode( stripslashes( $value ), true );
2375 }
2376 $value = wp_json_encode( $manual_field_value );
2377 } elseif ( is_string( $raw_value ) ) {
2378 $value = $raw_value;
2379 }
2380 } else {
2381 if ( ! empty( $field['multiple'] ) || in_array( $field['type'], [ 'checkbox', 'search_and_multi_select' ] ) ) {
2382 if ( isset( $raw_value ) && is_array( $raw_value ) ) {
2383 if ( ! empty( $field['sanitize_fn'] ) && is_callable( $field['sanitize_fn'] ) ) {
2384 $value = array_map( $field['sanitize_fn'], $raw_value );
2385 } else {
2386 $value = array_map( 'sanitize_text_field', $raw_value );
2387 }
2388 } else {
2389 $value = [];
2390 }
2391 } else {
2392 if ( ! empty( $field['sanitize_fn'] ) ) {
2393 if ( is_callable( $field['sanitize_fn'] ) ) {
2394 $value = $field['sanitize_fn']( $raw_value );
2395 } elseif ( 'pass_all' === $field['sanitize_fn'] ) {
2396 $value = $raw_value;
2397 } else {
2398 $value = $field['sanitize_fn']( $raw_value );
2399 }
2400 } else {
2401 $value = sanitize_text_field( $raw_value );
2402 }
2403 }
2404 }
2405 $options[ $block_id ][ $raw_option_key ] = $sections[ $section_id ]['list'][ $block_id ]['fields'][ $raw_option_key ]['value'] = $value ?? null;
2406 $changed = true;
2407 }
2408 }
2409 }
2410 }
2411 if ( ! $changed ) {
2412 $results['status'] = false;
2413 $results['message'] = esc_html__( 'No changes found for update', 'shopbuilder' );
2414
2415 return $results;
2416 }
2417
2418 DataModel::source()->set_option( $section_id, $options );
2419
2420 $results['status'] = true;
2421 $results['message'] = esc_html__( 'Successfully Saved', 'shopbuilder' );
2422 $results['sections'] = $sections;
2423
2424 return $results;
2425 }
2426
2427 /***
2428 * @param $meta_key
2429 * @param $meta_value
2430 *
2431 * @return mixed|void
2432 *
2433 *
2434 * public static function get_post_id_by_meta_key_and_value( $meta_key, $meta_value ) {
2435 * if( ! $meta_key || ! $meta_value ){
2436 * return;
2437 * }
2438 * global $wpdb;
2439 * $prepare_guery = $wpdb->prepare( "SELECT post_id FROM $wpdb->postmeta WHERE meta_key = '%s' and meta_value = '%d'", $meta_key, $meta_value );
2440 * $the_products = $wpdb->get_col( $prepare_guery );
2441 * if( count( $the_products ) > 0 ){
2442 * return $the_products[0];
2443 * }
2444 * return;
2445 * }
2446 */
2447 public static function count_products_by_taxonomies( $terms, $taxonomy = 'product_cat' ) {
2448 if ( empty( $terms ) || ! is_array( $terms ) ) {
2449 return;
2450 }
2451
2452 $args = [
2453 'limit' => -1,
2454 'return' => 'ids',
2455 ];
2456
2457 if ( 'product_cat' === $taxonomy ) {
2458 $args['product_category_id'] = $terms;
2459 } else {
2460 $args['product_tag_id'] = $terms;
2461 }
2462
2463 $query = new WC_Product_Query( $args );
2464 return ! empty( $query->get_products() ) ? count( $query->get_products() ) : 0;
2465 }
2466
2467 /**
2468 * Check if product filters widget has ajax.
2469 *
2470 * @param string $page Page name.
2471 *
2472 * @return bool
2473 */
2474 public static function product_filters_has_ajax( $page ) {
2475 if ( empty( $page ) ) {
2476 return false;
2477 }
2478
2479 if ( 'page' === get_post_type() ) {
2480 return false;
2481 }
2482
2483 $id = BuilderFns::is_builder_preview() ? get_the_ID() : BuilderFns::builder_page_id_by_type( $page );
2484 if ( ! $id ) {
2485 return false;
2486 }
2487 $cache_key = 'product_filters_has_ajax_' . $id;
2488 if ( isset( self::$cache[ $cache_key ] ) ) {
2489 return self::$cache[ $cache_key ];
2490 }
2491 $elmap = ElementorDataMap::instance();
2492 $ajax = [];
2493
2494 foreach ( $elmap->get_widget_data( 'rtsb-ajax-product-filters', [], $id ) as $data ) {
2495 $ajax[] = isset( $data['settings']['ajax_mode'] ) ? false : true;
2496 }
2497
2498 self::$cache[ $cache_key ] = ! empty( $ajax[0] );
2499 return ! empty( $ajax[0] );
2500 }
2501
2502 /**
2503 * Check if product has applied filter.
2504 *
2505 * @param string $page Page name.
2506 *
2507 * @return bool
2508 */
2509 public static function product_has_applied_filters( $page ) {
2510 if ( empty( $page ) ) {
2511 return false;
2512 }
2513
2514 $id = BuilderFns::is_builder_preview() ? get_the_ID() : BuilderFns::builder_page_id_by_type( $page );
2515 $elmap = ElementorDataMap::instance();
2516 $ajax = [];
2517 if ( ! $id ) {
2518 return false;
2519 }
2520 foreach ( $elmap->get_widget_data( 'rtsb-ajax-product-filters', [], $id ) as $data ) {
2521 $ajax[] = ! isset( $data['settings']['active_filter'] );
2522
2523 }
2524
2525 return ! empty( $ajax[0] );
2526 }
2527
2528 /**
2529 * Get WooCommerce product categories.
2530 *
2531 * @param string|null $search_query The search string for category names.
2532 *
2533 * @return array An array of product categories with 'value' and 'label' keys.
2534 */
2535 public static function products_category_query( $search_query = null ) {
2536 if ( ! is_admin() ) {
2537 return [];
2538 }
2539
2540 $cache_key = 'rtsb_product_categories_' . md5( serialize( $search_query ) );
2541
2542 if ( isset( self::$cache[ $cache_key ] ) ) {
2543 return self::$cache[ $cache_key ];
2544 }
2545 $results = wp_cache_get( $cache_key, 'shopbuilder' );
2546 if ( ! $results ) {
2547 global $wpdb;
2548 $sql = "SELECT t.term_id, t.name
2549 FROM {$wpdb->terms} t
2550 JOIN {$wpdb->term_taxonomy} tt ON t.term_id = tt.term_id
2551 WHERE tt.taxonomy = 'product_cat'";
2552
2553 if ( $search_query ) {
2554 $sql .= ' AND t.name LIKE %s';
2555 $prepared_sql = $wpdb->prepare( $sql, '%' . $wpdb->esc_like( $search_query ) . '%' ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
2556 } else {
2557 $prepared_sql = $sql; // No need for placeholders.
2558 }
2559
2560 $results = $wpdb->get_results( $prepared_sql ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.NotPrepared
2561 // Cache the results in the object cache for future use.
2562 wp_cache_set( $cache_key, $results, 'shopbuilder' ); // Adjust the expiration time as needed.
2563 Cache::set_data_cache_key( $cache_key );
2564 }
2565
2566 $cats = [];
2567 if ( ! is_array( $results ) && ! count( $results ) ) {
2568 return $cats;
2569 }
2570 foreach ( $results as $row ) {
2571 $category_id = $row->term_id;
2572 $category_title = $row->name;
2573
2574 $cats[] = [
2575 'value' => $category_id,
2576 'label' => $category_title,
2577 ];
2578 }
2579
2580 // Cache the results in a static variable for the next call.
2581 self::$cache[ $cache_key ] = $cats;
2582 return $cats;
2583 }
2584
2585 /**
2586 * @param string $search_query Search query.
2587 *
2588 * @return array
2589 */
2590 public static function get_registered_post_types( $search_query = null ) {
2591 // Get all registered post types.
2592 $registered_post_types = get_post_types(
2593 [
2594 'public' => true,
2595 '_builtin' => false,
2596 ],
2597 'objects'
2598 );
2599
2600 unset( $registered_post_types['elementor_library'] );
2601 unset( $registered_post_types['e-landing-page'] );
2602 unset( $registered_post_types['rtsb_builder'] );
2603
2604 $post_types = [];
2605
2606 // Check if a search query is provided.
2607 if ( ! empty( $search_query ) ) {
2608 // Filter post types based on the search query.
2609 $registered_post_types = array_filter(
2610 $registered_post_types,
2611 function ( $post_type ) use ( $search_query ) {
2612 return stripos( $post_type->labels->singular_name, $search_query ) !== false;
2613 }
2614 );
2615 // Check if 'post' match the search query and include them if they do.
2616 if ( stripos( 'post', $search_query ) !== false ) {
2617 $post_types[] = [
2618 'value' => 'post',
2619 'label' => 'Post',
2620 ];
2621 }
2622 } else {
2623 $post_types[] = [
2624 'value' => 'post',
2625 'label' => 'Post',
2626 ];
2627 }
2628
2629 // Convert the filtered post types to an array.
2630 foreach ( $registered_post_types as $post_type ) {
2631 $post_types[] = [
2632 'value' => $post_type->name,
2633 'label' => $post_type->labels->singular_name,
2634 ];
2635 }
2636
2637 return $post_types;
2638 }
2639
2640 /**
2641 * Get Post Types.
2642 *
2643 * @param string $search_query Search query.
2644 *
2645 * @return array
2646 */
2647 public static function get_post_types( $search_query = null, $args = [] ) {
2648
2649 $args = wp_parse_args(
2650 $args,
2651 [
2652 'post_type' => 'any',
2653 'posts_per_page' => 20,
2654 'orderby' => 'ID',
2655 'order' => 'DESC',
2656 // 'suppress_filters' => false, // WPML Support, Remove Others Languages.
2657 ]
2658 );
2659
2660 if ( 'archive' !== $args['post_type'] ) {
2661 if ( $search_query ) {
2662 $args['s'] = $search_query;
2663 }
2664 $posts = [];
2665 $query_results = get_posts( $args );
2666 foreach ( $query_results as $post ) {
2667 $posts[] = [
2668 'value' => $post->ID,
2669 'label' => $post->post_title . ' ( ID ' . $post->ID . ')',
2670 ];
2671 }
2672
2673 if ( $search_query ) {
2674 if ( strpos( '-error 404', $search_query ) ) {
2675 $posts[] = [
2676 'value' => 'error',
2677 'label' => __( '404 Error', 'shopbuilder' ),
2678 ];
2679 }
2680 } else {
2681 if ( 'page' === $args['post_type'] ) {
2682 $posts[] = [
2683 'value' => 'error',
2684 'label' => __( '404 Error', 'shopbuilder' ),
2685 ];
2686 }
2687 }
2688 } else {
2689 $posts = self::get_registered_post_types( $search_query );
2690 }
2691
2692 return $posts;
2693 }
2694
2695 /**
2696 * Get all Elementor breakpoints.
2697 *
2698 * @return array
2699 */
2700 public static function get_elementor_breakpoints() {
2701 return ( new \Elementor\Core\Breakpoints\Manager() )->get_breakpoints_config();
2702 }
2703
2704 /**
2705 * Render view.
2706 *
2707 * @param string $viewName View name.
2708 * @param array $args View args.
2709 * @param boolean $return View return.
2710 * @return string|void
2711 */
2712 public static function renderView( $viewName, $args = [], $return = false ) {
2713 $viewName = str_replace( '.', '/', $viewName );
2714
2715 if ( ! empty( $args ) && is_array( $args ) ) {
2716 extract( $args );
2717 }
2718
2719 $view_file = rtsb()->plugin_path() . '/resources/' . $viewName . '.php';
2720
2721 if ( ! file_exists( $view_file ) ) {
2722 _doing_it_wrong( __FUNCTION__, sprintf( '<code>%s</code> does not exist.', esc_html( $view_file ) ), '1.7.0' );
2723
2724 return;
2725 }
2726
2727 if ( $return ) {
2728 ob_start();
2729 include $view_file;
2730
2731 return ob_get_clean();
2732 } else {
2733 include $view_file;
2734 }
2735 }
2736
2737 /**
2738 * Best selling product query.
2739 *
2740 * @param int $minimum_sale Minimum sale/
2741 * @param int $limit Total limit.
2742 *
2743 * @return array|mixed
2744 */
2745 public static function best_selling_products_ids( $minimum_sale = 1, $limit = 10 ) {
2746 $cache_key = 'rtsb_best_selling_products_' . $minimum_sale . '_' . $limit;
2747 // Check if the data is in the cache.
2748 if ( isset( self::$cache[ $cache_key ] ) ) {
2749 return self::$cache[ $cache_key ];
2750 }
2751 $cached_data = get_transient( $cache_key );
2752 if ( $cached_data ) {
2753 self::$cache[ $cache_key ] = $cached_data;
2754 return $cached_data;
2755 }
2756 global $wpdb;
2757 $sql = $wpdb->prepare(
2758 "
2759 SELECT ID
2760 FROM {$wpdb->posts} AS p
2761 LEFT JOIN {$wpdb->postmeta} AS pm ON p.ID = pm.post_id
2762 WHERE p.post_type = 'product'
2763 AND p.post_status = 'publish'
2764 AND pm.meta_key = 'total_sales'
2765 AND pm.meta_value >= %d
2766 ORDER BY pm.meta_value + 0 DESC
2767 LIMIT %d
2768 ",
2769 $minimum_sale,
2770 $limit
2771 );
2772 $best_selling = $wpdb->get_col( $sql ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared
2773 // Cache the result for future use.
2774 set_transient( $cache_key, $best_selling, DAY_IN_SECONDS );
2775 self::$cache[ $cache_key ] = $best_selling;
2776 return $best_selling;
2777 }
2778
2779 /**
2780 * @param null|Object $product Product Object.
2781 * @param int $days_threshold Date String.
2782 *
2783 * @return bool
2784 */
2785 public static function is_new_product( $product = null, $days_threshold = 30 ) {
2786 if ( is_null( $product ) ) {
2787 global $product;
2788 }
2789 if ( ! $product instanceof WC_Product ) {
2790 return false;
2791 }
2792
2793 $product_id = $product->get_id();
2794 $days_threshold = ! empty( $days_threshold ) ? $days_threshold : 30;
2795 $cache_key = 'is_new_product_' . $product_id . '_' . $days_threshold;
2796
2797 if ( isset( self::$cache[ $cache_key ] ) ) {
2798 return self::$cache[ $cache_key ];
2799 }
2800
2801 // Get the product publish date.
2802 $publish_timestamp = strtotime( $product->get_date_created()->date_i18n( 'Y-m-d H:i:s' ) );
2803
2804 // Calculate the difference in days.
2805 $days_difference = abs( time() - $publish_timestamp ) / ( 60 * 60 * 24 );
2806
2807 // Check if the product is considered new.
2808 $is_new = $days_difference <= $days_threshold;
2809
2810 // Cache the result for a day.
2811 self::$cache[ $cache_key ] = $is_new;
2812
2813 return $is_new;
2814 }
2815
2816 /**
2817 * Checks if a feature is disabled for guest users based on the provided option.
2818 *
2819 * @param string $option The option to retrieve from the item.
2820 * @param mixed $default The default value if the option is not found.
2821 *
2822 * @return bool
2823 */
2824 public static function is_guest_feature_disabled( $option, $default ) {
2825 $disabled = self::get_option( 'general', 'guest_user', $option, $default );
2826
2827 return ! is_user_logged_in() && $disabled;
2828 }
2829
2830 /**
2831 * Checks if a feature is disabled for guest users based on the provided option.
2832 *
2833 * @param string $option The option to retrieve from the item.
2834 * @param mixed $default The default value if the option is not found.
2835 *
2836 * @return bool
2837 */
2838 public static function woocommerce_output_all_notices() {
2839 if ( function_exists( 'wc_print_notices' ) ) :
2840 echo '<div class="rtsb-notice">';
2841 woocommerce_output_all_notices();
2842 echo '</div>';
2843 endif;
2844 }
2845
2846 /**
2847 * @param object $product Product.
2848 * @return bool
2849 */
2850 public static function is_visible_qty_input( $product = null ) {
2851 $is_visible_qty = true;
2852 if ( $product instanceof \WC_Product ) {
2853 if ( $product->is_sold_individually() ) {
2854 $is_visible_qty = false;
2855 } elseif ( $product->managing_stock() ) {
2856 if ( in_array( $product->get_backorders(), [ 'notify' , 'yes' ], true ) ) {
2857 $is_visible_qty = true;
2858 } elseif ( $product->get_stock_quantity() < 2 ) {
2859 $is_visible_qty = false;
2860 }
2861 }
2862 }
2863 return $is_visible_qty;
2864 }
2865
2866 /**
2867 * @param int $object_id Object id.
2868 * @param string $element_type element type.
2869 * @param string|null $current_lang null for current language, default for default languages.
2870 * @return false|mixed|null
2871 */
2872 public static function wpml_object_id( $object_id, $element_type, $current_lang = 'default' ) {
2873 if ( ! defined( 'ICL_SITEPRESS_VERSION' ) ) {
2874 return $object_id;
2875 }
2876 if ( ! $object_id || ! $element_type ) {
2877 return $object_id;
2878 }
2879 if ( 'default' === $current_lang ) {
2880 $lang = apply_filters( 'wpml_default_language', null );
2881 } else {
2882 $lang = null;
2883 }
2884 return apply_filters( 'wpml_object_id', $object_id, $element_type, false, $lang );
2885 }
2886
2887 /**
2888 * @return string
2889 */
2890 public static function wpml_current_language() {
2891 $current = apply_filters( 'wpml_current_language', null );
2892 $default = apply_filters( 'wpml_default_language', null );
2893 return $default !== $current ? $current : null;
2894 }
2895
2896 /**
2897 * Custom icons.
2898 *
2899 * @return string[]
2900 */
2901 public static function get_custom_icon_names() {
2902 return [
2903 'heart-empty',
2904 'heart',
2905 'eye',
2906 'exchange',
2907 'plus',
2908 'minus',
2909 'avatar',
2910 'pay',
2911 'share',
2912 'clock',
2913 'check-alt',
2914 'check',
2915 'delete',
2916 'marker',
2917 'list',
2918 'list-2',
2919 'power',
2920 'cart',
2921 'cart-2',
2922 'cart-3',
2923 'downloads',
2924 'zoom',
2925 'user-edit',
2926 'grid',
2927 'filter',
2928 'billing',
2929 'login',
2930 'payment',
2931 'search',
2932 'edit',
2933 'coupon',
2934 'arrows-cw',
2935 'trash-empty',
2936 ];
2937 }
2938
2939 /**
2940 * Flushes rewrite rules.
2941 *
2942 * @return void
2943 */
2944 public static function maybe_flush_rewrite_rules() {
2945 add_action( 'wp_loaded', [ __CLASS__, 'flush_rewrite_rules' ] );
2946 add_action( 'shutdown', [ __CLASS__, 'flush_rewrite_rules_once_more' ] );
2947 }
2948
2949 /**
2950 * Flushes rewrite rules if needed.
2951 *
2952 * @return void
2953 */
2954 public static function flush_rewrite_rules() {
2955 if ( get_option( 'rtsb_permalinks_need_flush' ) === 'yes' ) {
2956 flush_rewrite_rules();
2957 }
2958
2959 if ( get_option( 'rtsb_permalinks_flushed' ) === 'yes' ) {
2960 flush_rewrite_rules();
2961 delete_option( 'rtsb_permalinks_flushed' );
2962 }
2963 }
2964
2965 /**
2966 * Flushes rewrite rules if needed for second time.
2967 *
2968 * @return void
2969 */
2970 public static function flush_rewrite_rules_once_more() {
2971 if ( get_option( 'rtsb_permalinks_need_flush' ) === 'yes' ) {
2972 flush_rewrite_rules();
2973 update_option( 'rtsb_permalinks_flushed', 'yes' );
2974 delete_option( 'rtsb_permalinks_need_flush' );
2975 }
2976 }
2977
2978 /**
2979 * Social Share Platforms.
2980 *
2981 * @return mixed|null
2982 */
2983 public static function set_repeater_options( $section_id, $block_id, $option_key, $compare_key, $compare_value, $values ) {
2984 // $options = self::get_options( $section_id, $block_id );
2985 $modules = DataModel::source()->get_option( $section_id, [], false );
2986 if ( empty( $modules[ $block_id ] ) ) {
2987 return;
2988 }
2989 $options = $modules[ $block_id ];
2990 if ( empty( $options[ $option_key ] ) ) {
2991 return;
2992 }
2993 $repeater_options = json_decode( $options[ $option_key ], true );
2994 // Check if options are not empty and are in array format.
2995 if ( ! is_array( $repeater_options ) ) {
2996 return;
2997 }
2998 // Use array_column to get an array of values from $compare_key.
2999 $column_values = array_column( $repeater_options, $compare_key );
3000 // Use array_search to find the index of the $compare_value.
3001 $index = array_search( $compare_value, $column_values, true );
3002 if ( false === $index ) {
3003 return;
3004 }
3005 $repeater_options[ $index ] = wp_parse_args( $values, $repeater_options[ $index ] );
3006 $options[ $option_key ] = wp_json_encode( $repeater_options );
3007 $modules[ $block_id ] = $options;
3008 DataModel::source()->set_option( $section_id, $modules );
3009 }
3010
3011
3012 /**
3013 * @param array $ids products id.
3014 * @return array
3015 */
3016 public static function get_available_products_by_ids( $ids = [] ) {
3017 $ids = array_filter(
3018 $ids,
3019 function ( $id ) {
3020 return 'product' === get_post_type( $id ) && 'publish' === get_post_status( $id );
3021 }
3022 );
3023 return $ids;
3024 }
3025
3026 /**
3027 * Generate and cache dynamic CSS styles.
3028 *
3029 * @param array $options CSS options to apply (e.g. padding, margin).
3030 * @param string $cache_key Cache key for the CSS.
3031 * @param array $css_properties An array of CSS properties with selectors and properties.
3032 * @param string $style_handle The handle for the inline styles.
3033 *
3034 * @return void
3035 */
3036 public static function dynamic_styles( $options, $cache_key, $css_properties, $style_handle = 'rtsb-frontend' ) {
3037 $cached_css = wp_cache_get( $cache_key, 'shopbuilder' );
3038
3039 if ( false !== $cached_css ) {
3040 wp_add_inline_style( $style_handle, $cached_css );
3041 return;
3042 }
3043
3044 $css_rules = [];
3045 $grouped_css_rules = [];
3046
3047 foreach ( $css_properties as $option => $details ) {
3048 if ( ! empty( $options[ $option ] ) ) {
3049 $selector = $details['selector'] ?? '';
3050 $property = $details['property'] ?? '';
3051 $unit = $details['unit'] ?? '';
3052 $value = $options[ $option ];
3053
3054 if ( empty( $grouped_css_rules[ $selector ] ) ) {
3055 $grouped_css_rules[ $selector ] = [];
3056 }
3057
3058 if ( is_array( $property ) ) {
3059 foreach ( $property as $prop ) {
3060 $grouped_css_rules[ $selector ][] = $prop . ': ' . $value . $unit . ' !important';
3061 }
3062 } else {
3063 $grouped_css_rules[ $selector ][] = $property . ': ' . $value . $unit . ' !important';
3064 }
3065 }
3066 }
3067
3068 foreach ( $grouped_css_rules as $selector => $properties ) {
3069 $css_rules[] = $selector . ' {' . implode( '; ', $properties ) . '}';
3070 }
3071
3072 $dynamic_css = implode( ' ', $css_rules );
3073
3074 wp_cache_set( $cache_key, $dynamic_css, 'shopbuilder', 12 * HOUR_IN_SECONDS );
3075 Cache::set_data_cache_key( $cache_key );
3076
3077 if ( ! empty( $dynamic_css ) ) {
3078 wp_add_inline_style( $style_handle, $dynamic_css );
3079 }
3080 }
3081
3082 /**
3083 * Pro version notice.
3084 *
3085 * @param string $ver Pro Version.
3086 *
3087 * @return array[]
3088 */
3089 public static function pro_version_notice( $ver ) {
3090 return [
3091 'version_check' => [
3092 'id' => 'version_check',
3093 'type' => 'title',
3094 'label' => sprintf(
3095 /* translators: 1: required version, 2: link to the Plugins page */
3096 esc_html__( 'To access all features and settings of this module, please ensure that \'ShopBuilder Pro\' is updated to version %1$s. Update to the latest version %2$s.', 'shopbuilder' ),
3097 '<br /><u><b>' . esc_html( $ver ?? '1.5.0' ) . ' or higher</b></u>',
3098 '<a class="pro-update-required" href="' . esc_url( admin_url( 'plugins.php' ) ) . '" target="_blank" title="Go to the Plugin Page">from the Plugins page</a>'
3099 ),
3100 'tab' => 'billing_fields',
3101 'customClass' => 'checkout-notice',
3102 ],
3103 ];
3104 }
3105 }
3106