PluginProbe
ShopBuilder – WooCommerce Builder For Elementor / 2.6.0
ShopBuilder – WooCommerce Builder For Elementor v2.6.0
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.6.0, at app/Helpers/Fns.php

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