PluginProbe
ShopBuilder – WooCommerce Builder For Elementor / 3.1.3
ShopBuilder – WooCommerce Builder For Elementor v3.1.3
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 3.1.3, at app/Helpers/Fns.php

3,934 lines 112.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Fns Helpers class
4 *
5 * @package RadiusTheme\SB
6 */
7
8 namespace RadiusTheme\SB\Helpers;
9
10 // Do not allow directly accessing this file.
11 if ( ! defined( 'ABSPATH' ) ) {
12 exit( 'This script cannot be accessed directly.' );
13 }
14
15 use WP_Styles;
16 use WC_Product;
17 use Elementor\Plugin;
18 use WC_Product_Query;
19 use Elementor\Icons_Manager;
20 use RadiusTheme\SB\Models\ReSizer;
21 use RadiusTheme\SB\Models\Settings;
22 use RadiusTheme\SB\Models\DataModel;
23 use RadiusTheme\SB\Models\ModuleList;
24 use RadiusTheme\SB\Models\GeneralList;
25 use RadiusTheme\SB\Models\ElementList;
26 use RadiusTheme\SB\Controllers\AssetRegistry;
27
28 /**
29 * Fns class
30 */
31 class Fns {
32
33 /**
34 * @var array
35 */
36 private static $cache = [];
37
38 /**
39 * Verify nonce.
40 *
41 * @return bool
42 */
43 public static function verify_nonce() {
44 $nonce = isset( $_REQUEST[ rtsb()->nonceId ] ) ? sanitize_text_field( wp_unslash( $_REQUEST[ rtsb()->nonceId ] ) ) : null;
45 $nonceText = rtsb()->nonceText;
46 if ( wp_verify_nonce( $nonce, $nonceText ) ) {
47 return true;
48 }
49
50 return false;
51 }
52
53 /**
54 * Get nonce.
55 *
56 * @return string|null
57 */
58 public static function enable_loader() {
59 return 'on' !== self::get_option( 'general', 'optimization', 'remove_pre_loader', '' );
60 }
61 /**
62 * Get nonce.
63 *
64 * @return string|null
65 */
66 public static function content_invisible() {
67 $wp_rocket_compatibility = 'on' !== self::get_option( 'general', 'optimization', 'wp_rocket_compatibility', '' );
68 if ( $wp_rocket_compatibility ) {
69 return true;
70 }
71 if ( defined( 'WP_ROCKET_VERSION' ) ) {
72 return false;
73 }
74 return true;
75 }
76 /**
77 * Get nonce.
78 *
79 * @return string|null
80 */
81 public static function get_nonce() {
82 // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
83 return isset( $_REQUEST[ rtsb()->nonceId ] ) ? sanitize_text_field( $_REQUEST[ rtsb()->nonceId ] ) : null;
84 }
85
86 /**
87 * Set Session
88 *
89 * @param string $name Name.
90 * @param mixed $value Value.
91 */
92 public static function setSession( $name, $value ) {
93 if ( ! headers_sent() && session_status() == PHP_SESSION_NONE ) {
94 session_start();
95 $_SESSION[ $name ] = $value;
96 } else {
97 $_SESSION[ $name ] = $value;
98 }
99 }
100
101 /**
102 * Get Cookie or Session
103 *
104 * @param string $name Name.
105 *
106 * @return bool
107 */
108 public static function getSession( $name ) {
109 if ( ! headers_sent() && session_status() == PHP_SESSION_NONE ) {
110 session_start();
111 }
112 return $_SESSION[ $name ] ?? null; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
113 }
114 /**
115 * Remove Session Variable
116 *
117 * @param string $name The name of the session variable to remove.
118 */
119 public static function removeSession( $name ) {
120 if ( ! headers_sent() && session_status() == PHP_SESSION_NONE ) {
121 session_start();
122 unset( $_SESSION[ $name ] );
123 } else {
124 unset( $_SESSION[ $name ] );
125 }
126 }
127
128 /**
129 * Check if a plugin is installed.
130 *
131 * @param string $plugin_slug Plugin slug.
132 *
133 * @return bool
134 */
135 public static function check_plugin_installed( $plugin_slug ): bool {
136 $installed_plugins = get_plugins();
137
138 return array_key_exists( $plugin_slug, $installed_plugins ) || in_array( $plugin_slug, $installed_plugins, true );
139 }
140
141 /**
142 * Check if a plugin is active.
143 *
144 * @param string $plugin_slug Plugin slug.
145 *
146 * @return bool
147 */
148 public static function check_plugin_active( $plugin_slug ): bool {
149 if ( is_plugin_active( $plugin_slug ) ) {
150 return true;
151 }
152
153 return false;
154 }
155 /**
156 * Get all user roles.
157 *
158 * @return array
159 */
160 public static function get_current_user_roles() {
161 $current_user = wp_get_current_user();
162 return $current_user->roles;
163 }
164 /**
165 * Get all user roles.
166 *
167 * @return array|false|mixed
168 */
169 public static function get_all_user_roles() {
170 $cache_key = 'rtsb_get_user_roles';
171 $roles = wp_cache_get( $cache_key, 'shopbuilder' );
172
173 if ( empty( $roles ) ) {
174 if ( ! function_exists( 'get_editable_roles' ) ) {
175 require_once ABSPATH . 'wp-admin/includes/user.php';
176 }
177
178 $user_roles = \get_editable_roles();
179 $roles = [];
180
181 foreach ( $user_roles as $key => $role ) {
182 if ( empty( $role['capabilities'] ) ) {
183 continue;
184 }
185 $roles[ $key ] = $role['name'];
186 }
187 }
188 wp_cache_set( $cache_key, $roles, 'shopbuilder' );
189 Cache::set_data_cache_key( $cache_key );
190 return $roles;
191 }
192
193 /**
194 * Stripslashes value.
195 *
196 * @param mixed $data Data.
197 *
198 * @return mixed|string
199 */
200 public static function stripslashes_value( $data ) {
201 if ( is_array( $data ) ) {
202 foreach ( $data as $key => $value ) {
203 $data[ $key ] = self::stripslashes_value( $value );
204 }
205 } elseif ( is_string( $data ) ) {
206 $trimmed = trim( $data );
207 // Try to decode JSON.
208 $json = json_decode( $trimmed, true );
209 if ( json_last_error() === JSON_ERROR_NONE && is_array( $json ) ) {
210 // Recursively stripslashes on decoded array.
211 $json = self::stripslashes_value( $json );
212 return wp_json_encode( $json, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE );
213 }
214 // Not valid JSON, just stripslashes if needed.
215 if ( strpos( $data, '\\' ) !== false ) {
216 return stripslashes( $data );
217 }
218 }
219
220 return $data;
221 }
222
223 /**
224 * Multiselect settings field value
225 *
226 * @param string $string String.
227 *
228 * @return array
229 */
230 public static function multiselect_settings_field_value( $string ) {
231
232 $values = [];
233 if ( empty( $string ) ) {
234 return $values;
235 }
236
237 $stripslashes_data = self::stripslashes_value( $string );
238 if ( is_array( $stripslashes_data ) && count( $stripslashes_data ) ) {
239 foreach ( $stripslashes_data as $item ) {
240 $item = is_array( $item ) ? $item : json_decode( $item, true );
241 $values[] = $item['value'];
242 }
243 }
244
245 return $values;
246 }
247
248 /**
249 * Check if is block theme
250 *
251 * @return bool
252 */
253 public static function check_is_block_theme(): bool {
254 global $wp_version;
255
256 return version_compare( $wp_version, '5.9', '>=' ) && function_exists( 'wp_is_block_theme' ) && wp_is_block_theme();
257 }
258
259 /**
260 * Locate template
261 *
262 * @param string $template_name Template name.
263 * @param string $template_path Template path. (default: '').
264 * @param string $plugin_path Plugin path. (default: ''). fallback file from plugin.
265 *
266 * @return mixed|void
267 */
268 public static function locate_template( $template_name, $template_path = '', $plugin_path = '' ) {
269 $template_name = self::sanitize_file_name( $template_name ) . '.php';
270
271 if ( ! $template_path ) {
272 $template_path = rtsb()->get_template_path();
273 }
274 if ( ! $plugin_path ) {
275 $plugin_path = RTSB_ABSPATH . 'templates/';
276 }
277
278 $template_rtsb_path = trailingslashit( $template_path ) . $template_name;
279 $template_path = '/' . $template_name;
280 $plugin_path = $plugin_path . $template_name;
281 $pro_plugin_path = rtsb()->has_pro() ? RTSBPRO_PATH . 'templates/' . $template_name : '';
282
283 $located = locate_template(
284 apply_filters(
285 'rtsb/core/locate_template_files',
286 [
287 $template_rtsb_path, // Search in <theme>/shopbuilder/.
288 $template_path, // Search in <theme>/.
289 ]
290 )
291 );
292
293 if ( ! $located ) {
294 if ( $pro_plugin_path && rtsb()->has_pro() && file_exists( $pro_plugin_path ) ) {
295 return apply_filters( 'rtsb/core/locate_template', $pro_plugin_path, $template_name );
296 } elseif ( file_exists( $plugin_path ) ) {
297 return apply_filters( 'rtsb/core/locate_template', $plugin_path, $template_name );
298 }
299 }
300
301 /**
302 * APPLY_FILTERS: rtsb/core/locate_template
303 *
304 * Filter the location of the templates.
305 *
306 * @param string $located Template found
307 * @param string $path Template path
308 *
309 * @return string
310 */
311 return apply_filters( 'rtsb/core/locate_template', $located, $template_name );
312 }
313
314 /**
315 * Remove any character that is not alphanumeric, /, _, or -.
316 *
317 * @param string $name Name to sanitize.
318 *
319 * @return array|string|string[]|null
320 */
321 private static function sanitize_file_name( $name ) {
322 return preg_replace( '/[^a-zA-Z0-9\/_\-]/', '', $name );
323 }
324
325 /**
326 * Template Content
327 *
328 * @param string $template_name Template name.
329 * @param array $args Arguments. (default: array).
330 * @param bool $return Whether to return or print the template.
331 * @param string $template_path Template path. (default: '').
332 * @param string $plugin_path Fallback path from where file will load if fail to load from template. (default: '').
333 *
334 * @return false|string|void
335 */
336 public static function load_template( $template_name, $args = null, $return = false, $template_path = '', $plugin_path = '' ) {
337 $cache_key = sanitize_key( implode( '-', [ 'template', $template_name, $template_path ] ) );
338 $located = (string) wp_cache_get( $cache_key, 'shopbuilder' );
339 if ( ! $located ) {
340 $located = self::locate_template( $template_name, $template_path, $plugin_path );
341 // Don't cache the absolute path so that it can be shared between web servers with different paths.
342 $cache_path = wc_tokenize_path( $located, wc_get_path_define_tokens() );
343 Cache::set_template_cache( $cache_key, $cache_path );
344 } else {
345 // Make sure that the absolute path to the template is resolved.
346 $located = wc_untokenize_path( $located, wc_get_path_define_tokens() );
347 }
348 if ( ! file_exists( $located ) ) {
349 // translators: %s template.
350 self::doing_it_wrong( __FUNCTION__, sprintf( __( '%s does not exist.', 'shopbuilder' ), '<code>' . $located . '</code>' ), '1.0' );
351
352 return;
353 }
354
355 if ( ! empty( $args ) && is_array( $args ) ) {
356 $atts = $args;
357 extract( $args ); // @codingStandardsIgnoreLine
358 }
359
360 // Allow 3rd party plugin filter template file from their plugin.
361 $located = apply_filters( 'rtsb/core/get_template', $located, $template_name, $args );
362
363 if ( $return ) {
364 ob_start();
365 }
366
367 do_action( 'rtsb/core/before_template_part', $template_name, $located, $args );
368 include $located;
369
370 do_action( 'rtsb/core/after_template_part', $template_name, $located, $args );
371
372 if ( $return ) {
373 return ob_get_clean();
374 }
375 }
376
377 /**
378 * Verify nonce.
379 *
380 * @return string
381 */
382 public static function is_woocommerce() {
383 return is_woocommerce() || is_cart() || is_checkout() || is_account_page();
384 }
385
386 /**
387 * Page builder
388 *
389 * @param int $post_id post id.
390 *
391 * @return string
392 */
393 public static function page_edit_with( $post_id ) {
394 if ( ! $post_id ) {
395 return '';
396 }
397
398 $edit_with = get_post_meta( $post_id, '_elementor_edit_mode', true );
399
400 if ( 'builder' === $edit_with ) {
401 $edit_by = 'elementor';
402 } else {
403 $edit_by = 'gutenberg';
404 }
405
406 return $edit_by;
407 }
408
409 /**
410 * Returns default expiration for wishlist cookie
411 *
412 * @return int Number of seconds the cookie should last.
413 */
414 public static function get_cookie_expiration() {
415 return intval( apply_filters( 'rtsb/cookie_expiration', 60 * 60 * 24 * 30 ) );
416 }
417
418 /**
419 * Create a cookie.
420 *
421 * @param string $name Cookie name.
422 * @param mixed $value Cookie value.
423 * @param int $time Cookie expiration time.
424 * @param bool $secure Whether cookie should be available to secured connection only.
425 * @param bool $httponly Whether cookie should be available to HTTP request only (no js handling).
426 *
427 * @return bool
428 * @since 1.0.0
429 */
430 public static function setcookie( $name, $value = [], $time = null, $secure = false, $httponly = false ) {
431
432 if ( ! apply_filters( 'rtsb/set_cookie', true ) || empty( $name ) ) {
433 return false;
434 }
435
436 $time = ! empty( $time ) ? $time : time() + self::get_cookie_expiration();
437
438 $value = wp_json_encode( stripslashes_deep( $value ) );
439 $expiration = apply_filters( 'rtsb/cookie_expiration_time', $time ); // Default 30 days.
440
441 $_COOKIE[ $name ] = $value;
442 wc_setcookie( $name, $value, $expiration, $secure, $httponly );
443
444 return true;
445 }
446
447 /**
448 * Retrieve the value of a cookie.
449 *
450 * @param string $name Cookie name.
451 *
452 * @return mixed
453 * @since 1.0.0
454 */
455 public static function getcookie( $name ) {
456 if ( isset( $_COOKIE[ $name ] ) ) {
457 return json_decode( sanitize_text_field( wp_unslash( $_COOKIE[ $name ] ) ), true );
458 }
459
460 return [];
461 }
462
463 /**
464 * Woocommerce Last product id return
465 */
466 public static function get_prepared_product_id() {
467 if ( is_singular( 'product' ) ) {
468 return get_the_ID();
469 }
470 // Return the Builder Template id.
471
472 if ( get_post_type( get_the_ID() ) == BuilderFns::$post_type_tb ) {
473 $product_id = get_post_meta( get_the_ID(), BuilderFns::$product_template_meta, true );
474 if ( $product_id && 'product' === get_post_type( $product_id ) && get_post_status( $product_id ) ) {
475 return $product_id;
476 }
477 }
478
479 global $wpdb;
480 $cache_key = 'rtsb_prepared_product_id';
481 $_post_id = wp_cache_get( $cache_key, 'shopbuilder' );
482 if ( false === $_post_id || 'publish' !== get_post_status( $_post_id ) ) {
483 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
484 $_post_id = $wpdb->get_var(
485 $wpdb->prepare( "SELECT MAX(ID) FROM {$wpdb->prefix}posts WHERE post_type = %s AND post_status IN('publish', 'draft')", 'product' )
486 );
487 wp_cache_set( $cache_key, $_post_id, 'shopbuilder', 12 * HOUR_IN_SECONDS );
488 Cache::set_data_cache_key( $cache_key );
489 }
490
491 return $_post_id;
492 }
493
494 /**
495 * Get the product function. Only used in single page widgets.
496 *
497 * @return object
498 */
499 public static function get_product() {
500 global $product;
501
502 if ( is_singular( 'product' ) && $product instanceof WC_Product ) {
503 return $product;
504 }
505 $cache_key = 'prepared_product_for_preview';
506 if ( isset( self::$cache[ $cache_key ] ) ) {
507 return self::$cache[ $cache_key ];
508 }
509 $product = wc_get_product( self::get_prepared_product_id() );
510 self::$cache[ $cache_key ] = $product;
511 do_action( 'rtsb_before_product_template_render' );
512 return $product;
513 }
514
515 /**
516 * Error function
517 *
518 * @param [type] $function function name.
519 * @param [type] $message message.
520 * @param [type] $version version.
521 *
522 * @return void
523 */
524 public static function doing_it_wrong( $function, $message, $version ) {
525 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_wp_debug_backtrace_summary
526 $message .= ' Backtrace: ' . wp_debug_backtrace_summary();
527 _doing_it_wrong( esc_html( $function ), wp_kses_post( $message ), esc_html( $version ) );
528 }
529
530 /**
531 * @return array
532 */
533 public static function get_pages() {
534 $pages = [];
535 $rawPages = get_pages();
536 if ( ! empty( $rawPages ) ) {
537 foreach ( $rawPages as $page ) {
538 $pages[ $page->ID ] = $page->post_title;
539 }
540 }
541
542 return $pages;
543 }
544
545 /**
546 * Get section items
547 *
548 * @param string $section_id Section ID.
549 *
550 * @return array
551 */
552 public static function get_section_items( $section_id ) {
553 if ( ! $section_id ) {
554 return [];
555 }
556
557 return DataModel::source()->get_option( $section_id, [] );
558 }
559
560 /**
561 * Get options items
562 *
563 * @param string $section_id Section ID.
564 * @param string $item_id Item ID.
565 *
566 * @return array
567 */
568 public static function get_options( $section_id, $item_id ) {
569 if ( ! $section_id || ! $item_id ) {
570 return [];
571 }
572 $sections = self::get_section_items( $section_id );
573
574 return isset( $sections[ $item_id ] ) ? $sections[ $item_id ] : [];
575 }
576
577 /**
578 * Get options value with default value if options doesn't exist.
579 *
580 * @param array $group Group.
581 * @param string $option_key Option key.
582 * @param string $default_value Default value.
583 *
584 * @return mixed|string
585 */
586 public static function get_options_by_default_val( $group, $option_key, $default_value = '' ) {
587
588 if ( ! $option_key || ! isset( $group[ $option_key ] ) ) {
589 return $default_value;
590 }
591
592 return $group[ $option_key ];
593 }
594
595 /**
596 * Get option.
597 *
598 * @param string $section_id Section ID.
599 * @param string $item_id Item ID.
600 * @param string $option_id Option ID.
601 * @param null $default EXCEPT multi_checkbox you can provide default value if given option does not set any value.
602 * @param null $type checkbox, multi_checkbox, number.
603 *
604 * @return bool|int|mixed|null
605 */
606 public static function get_option( $section_id, $item_id, $option_id, $default = null, $type = null ) {
607 $options = self::get_options( $section_id, $item_id );
608
609 if ( 'checkbox' === $type ) {
610 if ( isset( $options[ $option_id ] ) ) {
611 return 'on' === $options[ $option_id ];
612 }
613
614 return $default;
615 } elseif ( 'multi_checkbox' === $type ) {
616 return isset( $options[ $option_id ] ) && is_array( $options[ $option_id ] ) && in_array( $default, $options[ $option_id ] ); // phpcs:ignore WordPress.PHP.StrictInArray.MissingTrueStrict
617 } elseif ( 'number' === $type ) {
618 return isset( $options[ $option_id ] ) ? absint( $options[ $option_id ] ) : absint( $default );
619 }
620
621 return ! empty( $options[ $option_id ] ) ? $options[ $option_id ] : $default;
622 }
623
624
625 /**
626 * Create a page and store the ID in an option.
627 *
628 * @param mixed $slug Slug for the new page.
629 * @param array $options ['section_id', 'item_id', 'option_id']Option name to store the page's ID.
630 * @param string $page_title (default: '') Title for the new page.
631 * @param string $page_content (default: '') Content for the new page.
632 * @param int $post_parent (default: 0) Parent for the new page.
633 * @param string $post_status (default: publish) The post status of the new page.
634 *
635 * @return int page ID.
636 */
637 public static function create_page( $slug, $options = '', $page_title = '', $page_content = '', $post_parent = 0, $post_status = 'publish' ) {
638 global $wpdb;
639
640 $option_value = 0;
641 if ( ! empty( $options ) ) {
642 if ( is_array( $options ) ) {
643 $options = wp_parse_args(
644 $options,
645 [
646 'section_id' => '',
647 'item_id' => '',
648 'option_id' => '',
649 ]
650 );
651 $option_value = self::get_option( $options['section_id'], $options['item_id'], $options['option_id'], 0, 'number' );
652 } else {
653 $option_value = absint( get_option( $options ) );
654 }
655 }
656
657 if ( $option_value > 0 ) {
658 $page_object = get_post( $option_value );
659
660 if ( $page_object && 'page' === $page_object->post_type && ! in_array(
661 $page_object->post_status,
662 [
663 'pending',
664 'trash',
665 'future',
666 'auto-draft',
667 ],
668 true
669 ) ) {
670 // Valid page is already in place.
671 return $page_object->ID;
672 }
673 }
674
675 if ( strlen( $page_content ) > 0 ) {
676 // Search for an existing page with the specified page content (typically a shortcode).
677 $shortcode = str_replace( [ '<!-- wp:shortcode -->', '<!-- /wp:shortcode -->' ], '', $page_content );
678 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
679 $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}%" ) );
680 } else {
681 // Search for an existing page with the specified page slug.
682 $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
683 }
684
685 $valid_page_found = apply_filters( 'rtsb/core/create_page_id', $valid_page_found, $slug, $page_content, $options );
686
687 if ( $valid_page_found ) {
688 if ( is_array( $options ) ) {
689 if ( ! empty( $options['section_id'] ) && $options['item_id'] && $options['option_id'] ) {
690 $section_items = DataModel::source()->get_option( $options['section_id'], [] );
691 $section_items[ $options['item_id'] ][ $options['option_id'] ] = $valid_page_found;
692 DataModel::source()->set_option( $options['section_id'], $section_items );
693 }
694 } else {
695 if ( $options ) {
696 update_option( $options, $valid_page_found );
697 }
698 }
699
700 return $valid_page_found;
701 }
702
703 // Search for a matching valid trashed page.
704 if ( strlen( $page_content ) > 0 ) {
705 // Search for an existing page with the specified page content (typically a shortcode).
706 $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
707 } else {
708 // Search for an existing page with the specified page slug.
709 $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
710 }
711
712 if ( $trashed_page_found ) {
713 $page_id = $trashed_page_found;
714 $page_data = [
715 'ID' => $page_id,
716 'post_status' => $post_status,
717 ];
718 wp_update_post( $page_data );
719 } else {
720 $page_data = [
721 'post_status' => $post_status,
722 'post_type' => 'page',
723 'post_author' => 1,
724 'post_name' => $slug,
725 'post_title' => $page_title,
726 'post_content' => $page_content,
727 'post_parent' => $post_parent,
728 'comment_status' => 'closed',
729 ];
730 $page_id = wp_insert_post( $page_data );
731
732 do_action( 'rtsb/core/page_created', $page_id, $page_data );
733 }
734
735 if ( is_array( $options ) ) {
736 if ( ! empty( $options['section_id'] ) && $options['item_id'] && $options['option_id'] ) {
737 $section_items = DataModel::source()->get_option( $options['section_id'], [] );
738 $section_items[ $options['item_id'] ][ $options['option_id'] ] = $page_id;
739 DataModel::source()->set_option( $options['section_id'], $section_items );
740 }
741 } else {
742 if ( $options ) {
743 update_option( $options, $page_id );
744 }
745 }
746
747 return $page_id;
748 }
749
750 /**
751 * Get allowed HTML tags.
752 *
753 * @return array
754 */
755 public static function get_kses_array() {
756 return [
757 'a' => [
758 'class' => [],
759 'href' => [],
760 'rel' => [],
761 'title' => [],
762 ],
763 'abbr' => [
764 'title' => [],
765 ],
766 'b' => [],
767 'blockquote' => [
768 'cite' => [],
769 ],
770 'cite' => [
771 'title' => [],
772 ],
773 'code' => [],
774 'del' => [
775 'datetime' => [],
776 'title' => [],
777 ],
778 'dd' => [],
779 'div' => [
780 'class' => [],
781 'title' => [],
782 'style' => [],
783 ],
784 'dl' => [],
785 'dt' => [],
786 'em' => [],
787 'h1' => [
788 'class' => [],
789 ],
790 'h2' => [
791 'class' => [],
792 ],
793 'h3' => [
794 'class' => [],
795 ],
796 'h4' => [
797 'class' => [],
798 ],
799 'h5' => [
800 'class' => [],
801 ],
802 'h6' => [
803 'class' => [],
804 ],
805 'i' => [
806 'class' => [],
807 ],
808 'img' => [
809 'alt' => [],
810 'class' => [],
811 'height' => [],
812 'src' => [],
813 'width' => [],
814 ],
815 'li' => [
816 'class' => [],
817 ],
818 'ol' => [
819 'class' => [],
820 ],
821 'p' => [
822 'class' => [],
823 ],
824 'q' => [
825 'cite' => [],
826 'title' => [],
827 ],
828 'span' => [
829 'class' => [],
830 'title' => [],
831 'style' => [],
832 ],
833 'iframe' => [
834 'width' => [],
835 'height' => [],
836 'scrolling' => [],
837 'frameborder' => [],
838 'allow' => [],
839 'src' => [],
840 ],
841 'strike' => [],
842 'br' => [],
843 'strong' => [],
844 'data-wow-duration' => [],
845 'data-wow-delay' => [],
846 'data-wallpaper-options' => [],
847 'data-stellar-background-ratio' => [],
848 'ul' => [
849 'class' => [],
850 ],
851 ];
852 }
853
854
855 /**
856 * Escape output of wishlist icon
857 *
858 * @param string $data Data to escape.
859 *
860 * @return void
861 */
862 public static function print_icon( $data ) {
863 /**
864 * APPLY_FILTERS: rtsb/core/allowed_icon_html
865 *
866 * Filter the allowed HTML for the icons.
867 *
868 * @param array $allowed_icon_html Allowed HTML
869 *
870 * @return array
871 */
872 $allowed_icon_html = apply_filters(
873 'rtsb/core/allowed_icon_html',
874 [
875 'i' => [
876 'class' => true,
877 ],
878 'img' => [
879 'src' => true,
880 'alt' => true,
881 'width' => true,
882 'height' => true,
883 ],
884 'svg' => [
885 'class' => true,
886 'aria-hidden' => true,
887 'aria-labelledby' => true,
888 'role' => true,
889 'xmlns' => true,
890 'width' => true,
891 'height' => true,
892 'viewbox' => true,
893 'stroke' => true,
894 'fill' => true,
895 ],
896 'g' => [
897 'fill' => true,
898 ],
899 'title' => [
900 'title' => true,
901 ],
902 'path' => [
903 'd' => true,
904 'fill' => true,
905 'stroke' => true,
906 'stroke-width' => true,
907 'stroke-linecap' => true,
908 'stroke-linejoin' => true,
909 'fill-rule' => true,
910 'clip-rule' => true,
911 ],
912 ]
913 );
914
915 echo wp_kses( $data, $allowed_icon_html );
916 }
917
918 /**
919 * Prints HTMl.
920 *
921 * @param string $html HTML.
922 * @param bool $allHtml All HTML.
923 *
924 * @return void
925 */
926 public static function print_html( $html, $allHtml = false ) {
927 if ( ! $html ) {
928 return;
929 }
930 if ( $allHtml ) {
931 echo stripslashes_deep( $html ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
932 } else {
933 echo wp_kses_post( stripslashes_deep( $html ) );
934 }
935 }
936
937 /**
938 * Allowed HTML for wp_kses.
939 *
940 * @param string $level Tag level.
941 *
942 * @return mixed
943 */
944 public static function allowedHtml( $level = 'basic' ) {
945 $allowed_html = [];
946
947 switch ( $level ) {
948 case 'basic':
949 $allowed_html = [
950 'b' => [
951 'class' => [],
952 'id' => [],
953 ],
954 'i' => [
955 'class' => [],
956 'id' => [],
957 ],
958 'u' => [
959 'class' => [],
960 'id' => [],
961 ],
962 'br' => [
963 'class' => [],
964 'id' => [],
965 ],
966 'em' => [
967 'class' => [],
968 'id' => [],
969 ],
970 'span' => [
971 'class' => [],
972 'id' => [],
973 ],
974 'strong' => [
975 'class' => [],
976 'id' => [],
977 ],
978 'hr' => [
979 'class' => [],
980 'id' => [],
981 ],
982 'div' => [
983 'class' => [],
984 'id' => [],
985 ],
986 'a' => [
987 'href' => [],
988 'title' => [],
989 'class' => [],
990 'id' => [],
991 'target' => [],
992 ],
993 ];
994 break;
995
996 case 'advanced':
997 $allowed_html = [
998 'b' => [
999 'class' => [],
1000 'id' => [],
1001 ],
1002 'i' => [
1003 'class' => [],
1004 'id' => [],
1005 ],
1006 'u' => [
1007 'class' => [],
1008 'id' => [],
1009 ],
1010 'br' => [
1011 'class' => [],
1012 'id' => [],
1013 ],
1014 'em' => [
1015 'class' => [],
1016 'id' => [],
1017 ],
1018 'span' => [
1019 'class' => [],
1020 'id' => [],
1021 ],
1022 'strong' => [
1023 'class' => [],
1024 'id' => [],
1025 ],
1026 'hr' => [
1027 'class' => [],
1028 'id' => [],
1029 ],
1030 'a' => [
1031 'href' => [],
1032 'title' => [],
1033 'class' => [],
1034 'id' => [],
1035 'target' => [],
1036 ],
1037 'input' => [
1038 'type' => [],
1039 'name' => [],
1040 'class' => [],
1041 'value' => [],
1042 ],
1043 ];
1044 break;
1045
1046 case 'image':
1047 $allowed_html = [
1048 'img' => [
1049 'src' => [],
1050 'data-src' => [],
1051 'alt' => [],
1052 'height' => [],
1053 'width' => [],
1054 'class' => [],
1055 'id' => [],
1056 'style' => [],
1057 'srcset' => [],
1058 'loading' => [],
1059 'sizes' => [],
1060 ],
1061 'div' => [
1062 'class' => [],
1063 ],
1064 ];
1065 break;
1066
1067 case 'anchor':
1068 $allowed_html = [
1069 'a' => [
1070 'href' => [],
1071 'title' => [],
1072 'class' => [],
1073 'id' => [],
1074 'style' => [],
1075 ],
1076 ];
1077 break;
1078
1079 default:
1080 // code...
1081 break;
1082 }
1083
1084 return $allowed_html;
1085 }
1086
1087 /**
1088 * Safe get a validated HTML tag.
1089 *
1090 * @param string $tag HTML tag.
1091 *
1092 * @return string
1093 */
1094 public static function get_validated_html_tag( $tag ) {
1095 $allowed_html_wrapper_tags = [
1096 'a',
1097 'article',
1098 'aside',
1099 'button',
1100 'div',
1101 'footer',
1102 'h1',
1103 'h2',
1104 'h3',
1105 'h4',
1106 'h5',
1107 'h6',
1108 'header',
1109 'main',
1110 'nav',
1111 'p',
1112 'section',
1113 'span',
1114 ];
1115
1116 return in_array( strtolower( $tag ), $allowed_html_wrapper_tags, true ) ? $tag : 'div';
1117 }
1118
1119 /**
1120 * Safe print a validated HTML tag.
1121 *
1122 * @param string $tag HTML tag.
1123 *
1124 * @return void
1125 */
1126 public static function print_validated_html_tag( $tag ) {
1127 self::print_html( self::get_validated_html_tag( $tag ) );
1128 }
1129
1130 /**
1131 * Insert Some array element
1132 *
1133 * @param [type] $key The elements will insert nearby this key.
1134 * @param [type] $main_array Original array.
1135 * @param [type] $insert_array some element will insert in original array.
1136 * @param boolean $is_after array insert position base on the key.
1137 *
1138 * @return array
1139 */
1140 public static function insert_controls( $key, $main_array, $insert_array, $is_after = false ) {
1141 $index = array_search( $key, array_keys( $main_array ), true );
1142 if ( 'integer' === gettype( $index ) ) {
1143 if ( $is_after ) {
1144 $index++;
1145 }
1146 $main_array = array_merge(
1147 array_slice( $main_array, 0, $index ),
1148 $insert_array,
1149 array_slice( $main_array, $index )
1150 );
1151 }
1152
1153 return $main_array;
1154 }
1155
1156 /**
1157 * Image Sizes
1158 *
1159 * @return array
1160 */
1161 public static function get_image_sizes() {
1162 global $_wp_additional_image_sizes;
1163
1164 $sizes = [];
1165
1166 foreach ( get_intermediate_image_sizes() as $_size ) {
1167 if ( in_array( $_size, [ 'thumbnail', 'medium', 'large' ], true ) ) {
1168 $sizes[ $_size ]['width'] = get_option( "{$_size}_size_w" );
1169 $sizes[ $_size ]['height'] = get_option( "{$_size}_size_h" );
1170 $sizes[ $_size ]['crop'] = (bool) get_option( "{$_size}_crop" );
1171 } elseif ( isset( $_wp_additional_image_sizes[ $_size ] ) ) {
1172 $sizes[ $_size ] = [
1173 'width' => $_wp_additional_image_sizes[ $_size ]['width'],
1174 'height' => $_wp_additional_image_sizes[ $_size ]['height'],
1175 'crop' => $_wp_additional_image_sizes[ $_size ]['crop'],
1176 ];
1177 }
1178 }
1179
1180 $imgSize = [];
1181
1182 foreach ( $sizes as $key => $img ) {
1183 $imgSize[ $key ] = ucfirst( $key ) . " ({$img['width']}*{$img['height']})";
1184 }
1185
1186 $imgSize['full'] = esc_html__( 'Full size', 'shopbuilder' );
1187 $imgSize['rtsb_custom'] = esc_html__( 'Custom image size', 'shopbuilder' );
1188
1189 return $imgSize;
1190 }
1191
1192 /**
1193 * Free Layouts
1194 *
1195 * @param string $layout Layout.
1196 *
1197 * @return array
1198 */
1199 public static function free_layouts( $layout = '' ) {
1200 if ( ! empty( $layout ) && false !== strpos( $layout, 'rtsb-' ) ) {
1201 return [ $layout => esc_html__( 'Addon Layout', 'shopbuilder' ) ];
1202 }
1203
1204 $layouts = [
1205 'grid-layout1' => esc_html__( 'Grid Layout 1', 'shopbuilder' ),
1206 'grid-layout2' => esc_html__( 'Grid Layout 2', 'shopbuilder' ),
1207 'list-layout1' => esc_html__( 'List Layout 1', 'shopbuilder' ),
1208 'list-layout2' => esc_html__( 'List Layout 2', 'shopbuilder' ),
1209 'slider-layout1' => esc_html__( 'Slider Layout 1', 'shopbuilder' ),
1210 'slider-layout2' => esc_html__( 'Slider Layout 2', 'shopbuilder' ),
1211 'category-single-layout1' => esc_html__( 'Category Single Layout 1', 'shopbuilder' ),
1212 'category-layout1' => esc_html__( 'Category Layout 1', 'shopbuilder' ),
1213 'category-layout2' => esc_html__( 'Category Layout 2', 'shopbuilder' ),
1214 ];
1215
1216 return apply_filters( 'rtsb/elements/elementor/free_layouts', $layouts );
1217 }
1218
1219 /**
1220 * Get all terms by taxonomy
1221 *
1222 * @param string $taxonomy Taxonomy name.
1223 * @param bool $placeholder Placeholder..
1224 *
1225 * @return array
1226 */
1227 public static function get_all_terms( $taxonomy, $placeholder = false ) {
1228 $terms = [];
1229
1230 if ( empty( $taxonomy ) ) {
1231 return $terms;
1232 }
1233 $cache_key = 'rtsb_get_all_terms_by_tax_name_' . $taxonomy;
1234 if ( isset( self::$cache[ $cache_key ] ) ) {
1235 return self::$cache[ $cache_key ];
1236 }
1237
1238 $termList = get_terms(
1239 [
1240 'taxonomy' => $taxonomy,
1241 'hide_empty' => false,
1242 ]
1243 );
1244
1245 if ( is_array( $termList ) && ! empty( $termList ) && empty( $termList['errors'] ) ) {
1246 if ( $placeholder ) {
1247 $terms = [
1248 'all' => __( 'All Products', 'shopbuilder' ),
1249 ];
1250 }
1251
1252 foreach ( $termList as $term ) {
1253 $terms[ $term->term_id ] = esc_html( $term->name );
1254 }
1255 }
1256 self::$cache[ $cache_key ] = $terms;
1257 return $terms;
1258 }
1259 /**
1260 * Get all product attribute taxonomy by id
1261 *
1262 * @param array $term_ids Term id.
1263 *
1264 * @return array
1265 */
1266 public static function tax_filter_attr_taxonomy( $term_ids ) {
1267 $taxonomies = [];
1268 foreach ( $term_ids as $id ) {
1269 $term = get_term( $id );
1270 if ( ! is_wp_error( $term ) && $term ) {
1271 $taxonomies[] = $term->taxonomy;
1272 }
1273 }
1274 return array_unique( $taxonomies );
1275 }
1276
1277 /**
1278 * Get all terms by attributes
1279 *
1280 * @return array
1281 */
1282 public static function get_all_attributes() {
1283 $terms = [];
1284 $termList = [];
1285
1286 $attributes = wc_get_attribute_taxonomies();
1287
1288 if ( $attributes ) {
1289 foreach ( $attributes as $tax ) {
1290 if ( taxonomy_exists( wc_attribute_taxonomy_name( $tax->attribute_name ) ) ) {
1291 $termList[ $tax->attribute_name ] = get_terms( wc_attribute_taxonomy_name( $tax->attribute_name ) );
1292 }
1293 }
1294 }
1295
1296 if ( ! empty( $termList ) && empty( $termList['errors'] ) && is_array( $termList ) ) {
1297 foreach ( $termList as $name => $atts ) {
1298 foreach ( $atts as $term ) {
1299 $terms[ $term->term_id ] = esc_html( ucwords( str_replace( '-', ' ', $name ) ) . ' - ' . $term->name );
1300 }
1301 }
1302 }
1303
1304 return $terms;
1305 }
1306
1307 /**
1308 * Get all attributes name
1309 *
1310 * @return array
1311 */
1312 public static function get_all_attributes_name() {
1313 $attributes_name = [];
1314
1315 $attributes = wc_get_attribute_taxonomies();
1316
1317 if ( $attributes ) {
1318 foreach ( $attributes as $tax ) {
1319 if ( taxonomy_exists( wc_attribute_taxonomy_name( $tax->attribute_name ) ) ) {
1320 $attributes_name[ wc_attribute_taxonomy_name( $tax->attribute_name ) ] = ucwords( $tax->attribute_name );
1321 }
1322 }
1323 }
1324
1325 return $attributes_name;
1326 }
1327
1328 /**
1329 * Get User list
1330 *
1331 * @return array
1332 */
1333 public static function get_users() {
1334 $users = [];
1335 $u = get_users();
1336
1337 if ( ! empty( $u ) ) {
1338 foreach ( $u as $user ) {
1339 $users[ $user->ID ] = $user->display_name;
1340 }
1341 }
1342
1343 return $users;
1344 }
1345
1346 /**
1347 * Get Taxonomy List.
1348 *
1349 * @return array
1350 */
1351 public static function get_tax_list() {
1352 return apply_filters(
1353 'rtsb/elements/tax_list',
1354 [
1355 'product_cat' => esc_html__( 'Product Category', 'shopbuilder' ),
1356 ]
1357 );
1358 }
1359
1360 /**
1361 * Get Category Query List.
1362 *
1363 * @return array
1364 */
1365 public static function get_cat_list() {
1366 return [
1367 'all' => esc_html__( 'All Categories', 'shopbuilder' ),
1368 'specific_parent' => esc_html__( 'Sub-Categories by Parent', 'shopbuilder' ),
1369 'cat_ids' => esc_html__( 'Select by ID', 'shopbuilder' ),
1370 'selection' => esc_html__( 'Manual Selection', 'shopbuilder' ),
1371 ];
1372 }
1373
1374 /**
1375 * Get Term List.
1376 *
1377 * @param string $taxonomy Taxonomy.
1378 * @param bool $first_term First term.
1379 * @param bool $only_parents Only parent terms.
1380 * @param bool $return_slug Return with slug.
1381 *
1382 * @return int|array
1383 */
1384 public static function get_terms( $taxonomy, $first_term = false, $only_parents = false, $return_slug = false ) {
1385 if ( ! is_admin() ) {
1386 return [];
1387 }
1388
1389 $term_list = [];
1390 $args = [
1391 'taxonomy' => $taxonomy,
1392 'hide_empty' => false,
1393 ];
1394
1395 if ( $only_parents ) {
1396 $args['parent'] = 0;
1397 }
1398
1399 $terms = get_terms( $args );
1400
1401 if ( empty( $terms ) ) {
1402 return [ esc_html__( 'Nothing found', 'shopbuilder' ) ];
1403 }
1404
1405 foreach ( $terms as $term ) {
1406 if ( $return_slug ) {
1407 $term_list[ $term->slug ] = $term->name;
1408 } else {
1409 $term_list[ $term->term_id ] = $term->name;
1410 }
1411 }
1412
1413 if ( $first_term ) {
1414 return array_keys( $term_list )[0];
1415 } else {
1416 return $term_list;
1417 }
1418 }
1419
1420 /**
1421 * Get product rating.
1422 *
1423 * @param array $args Arguments.
1424 *
1425 * @return string|void
1426 */
1427 public static function get_product_rating_html( $args = [] ) {
1428 global $product;
1429
1430 $html = '';
1431 $rating_count = $product->get_rating_count();
1432 $average_rating = $product->get_average_rating();
1433
1434 if ( ! rtsb()->has_pro() || empty( $args ) ) {
1435 if ( ! $rating_count || empty( wc_get_rating_html( $average_rating, $rating_count ) ) ) {
1436 return $html;
1437 }
1438
1439 $html .= '<div class="product-rating">';
1440 $html .= wc_get_rating_html( $average_rating, $rating_count );
1441 $html .= ! empty( $html ) ? '<span class="rtsb-count">(' . $average_rating . ')</span>' : '';
1442 $html .= '</div>';
1443
1444 self::print_html( $html, true );
1445
1446 return;
1447 }
1448
1449 if ( empty( $rating_count ) && ! $args['show_empty_rating'] ) {
1450 return '';
1451 }
1452
1453 $preset = ! empty( $args['preset'] ) ? $args['preset'] : 'preset1';
1454 $average = $args['show_average_rating'] ? '<span class="rtsb-count">(' . $average_rating . ')</span>' : '';
1455 $count = '';
1456
1457 if ( $args['show_rating_count'] ) {
1458 $count .= '<div class="rtsb-count">';
1459 $count .= sprintf(
1460 /* translators: %s is the number of reviews */
1461 _n( '%s Review', '%s Reviews', $rating_count, 'shopbuilder' ),
1462 $rating_count
1463 );
1464 $count .= '</div>';
1465 }
1466
1467 if ( 'preset2' === $preset ) {
1468 $average = $args['show_average_rating'] ? '<div class="inner-wrapper"><span class="star-icon"></span><span class="average-rating">' . $average_rating . '</span></div>' : '';
1469 }
1470
1471 $html .= '<div class="product-rating ' . esc_attr( $preset ) . '">';
1472
1473 if ( 'preset1' === $preset ) {
1474 $html .= wc_get_rating_html( $average_rating, $rating_count );
1475 $html .= $average;
1476 $html .= $count;
1477 } elseif ( 'preset2' === $preset ) {
1478 $html .= $average;
1479 $html .= $count;
1480 }
1481
1482 $html .= '</div>';
1483
1484 self::print_html( $html, true );
1485 }
1486
1487 /**
1488 * Get product simple rating.
1489 *
1490 * @return void
1491 */
1492 public static function get_product_simple_rating_html() {
1493 global $product;
1494 $html = null;
1495 $rating_count = $product->get_rating_count();
1496 $average_rating = $product->get_average_rating();
1497 $html .= '<div class="product-rating">';
1498 $html .= wc_get_rating_html( $average_rating, $rating_count );
1499 $html .= ! empty( $html ) ? '<span class="rtsb-count">(' . $average_rating . ')</span>' : '';
1500 $html .= '</div>';
1501
1502 self::print_html( $html, true );
1503 }
1504
1505
1506
1507 /**
1508 * Text truncation.
1509 *
1510 * @param string $text_to_truncate Text.
1511 * @param int $limit Limit.
1512 * @param string $after After text.
1513 *
1514 * @return string
1515 */
1516 public static function text_truncation( $text_to_truncate, $limit, $after = '&#8230;' ) {
1517 if ( empty( $limit ) ) {
1518 return $text_to_truncate;
1519 }
1520
1521 $limit++;
1522
1523 $text = '';
1524
1525 if ( mb_strlen( $text_to_truncate ) > $limit ) {
1526 $subex = mb_substr( wp_strip_all_tags( $text_to_truncate ), 0, $limit );
1527 $exwords = explode( ' ', $subex );
1528 $excut = - ( mb_strlen( $exwords[ count( $exwords ) - 1 ] ) );
1529
1530 if ( $excut < 0 ) {
1531 $text .= mb_substr( $subex, 0, $excut ) . $after;
1532 } else {
1533 $text .= $subex . $after;
1534 }
1535 } else {
1536 $text .= $text_to_truncate;
1537 }
1538
1539 return $text;
1540 }
1541
1542 /**
1543 * Promo Badge HTML
1544 *
1545 * @param string $text Text.
1546 * @param string $class Class.
1547 *
1548 * @return void
1549 */
1550 public static function get_badge_html( $text, $class = 'fill' ) {
1551 if ( self::is_module_active( 'product_badges' ) && 'rtsb_yes' === $text ) {
1552 do_action( 'rtsb/modules/product_badges/frontend/display' );
1553
1554 return;
1555 }
1556
1557 if ( empty( $text ) ) {
1558 return;
1559 }
1560 ob_start();
1561 ?>
1562
1563 <ul class="rtsb-promotion-list">
1564 <li class="rtsb-promotion-list-item">
1565 <span class="rtsb-tag-<?php echo ! empty( $class ) ? esc_attr( $class ) : ''; ?>"><?php echo esc_html( $text ); ?></span>
1566 </li>
1567 </ul>
1568
1569 <?php
1570 self::print_html( ob_get_clean() );
1571 }
1572
1573 /**
1574 * Categories HTML
1575 *
1576 * @param int $id Product ID.
1577 * @param string $class Custom class.
1578 *
1579 * @return void|string
1580 */
1581 public static function get_categories_list( $id, $class = 'rtsb-category-outline' ) {
1582 if ( empty( $id ) ) {
1583 return '';
1584 }
1585
1586 ob_start();
1587 ?>
1588
1589 <ul class="rtsb-category-list <?php echo esc_attr( $class ); ?>">
1590 <?php
1591 self::print_html(
1592 wc_get_product_category_list(
1593 $id,
1594 '</li><li class="rtsb-category-list-item">',
1595 '<li class="rtsb-category-list-item">',
1596 '</li>'
1597 )
1598 );
1599 ?>
1600 </ul>
1601
1602 <?php
1603 self::print_html( ob_get_clean() );
1604 }
1605 /**
1606 * Brands HTML
1607 *
1608 * @param int $id Product ID.
1609 * @param string $class Custom class.
1610 *
1611 * @return void|string
1612 */
1613 public static function get_brands_list( $id, $class = 'rtsb-brand-outline' ) {
1614 $terms = get_the_terms( $id, 'product_brand' );
1615 if ( empty( $id ) || empty( $terms ) || is_wp_error( $terms ) ) {
1616 return '';
1617 }
1618 ob_start();
1619 ?>
1620
1621 <ul class="rtsb-brand-list <?php echo esc_attr( $class ); ?>">
1622 <?php
1623 $brand_list = get_the_term_list(
1624 $id,
1625 'product_brand',
1626 '<li class="rtsb-brand-list-item">',
1627 '</li><li class="rtsb-brand-list-item">',
1628 '</li>'
1629 );
1630
1631 self::print_html( $brand_list );
1632 ?>
1633 </ul>
1634
1635 <?php
1636 self::print_html( ob_get_clean() );
1637 }
1638
1639 /**
1640 * Get Featured Image HTML.
1641 *
1642 * @param string $type Image type.
1643 * @param int $post_id Post ID.
1644 * @param string $f_img_size Image size.
1645 * @param null $default_img_id Default image ID.
1646 * @param array $custom_img_size Custom image size.
1647 * @param bool $lazy Lazy load check.
1648 * @param bool $hover Hover image.
1649 * @param bool $gallery Gallery image.
1650 * @param bool $custom_image_id Custom category image ID.
1651 *
1652 * @return string|null
1653 */
1654 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 ) {
1655 $img_html = null;
1656 $attachment_id = null;
1657 $c_size = false;
1658 $hover_class = '';
1659 $post_title = '';
1660
1661 if ( 'rtsb_custom' === $f_img_size ) {
1662 $f_img_size = 'full';
1663 $c_size = true;
1664 }
1665
1666 if ( ! rtsb()->has_pro() ) {
1667 $gallery = false;
1668 }
1669
1670 if ( $hover ) {
1671 $a_id = $post_id;
1672 $hover_class = ' rtsb-img-hover';
1673 } elseif ( $gallery ) {
1674 $a_id = $post_id;
1675 } else {
1676 if ( 'product' === $type ) {
1677 $a_id = get_post_thumbnail_id( $post_id );
1678 $post_title = get_the_title( $post_id );
1679 } elseif ( 'category' === $type ) {
1680 $a_id = $custom_image_id ? $custom_image_id : get_term_meta( $post_id, 'thumbnail_id', true );
1681 $post_title = get_term( $post_id )->name;
1682 } else {
1683 $a_id = $default_img_id;
1684 }
1685 }
1686
1687 $img_alt = trim( wp_strip_all_tags( get_post_meta( $a_id, '_wp_attachment_image_alt', true ) ) );
1688 $alt_tag = ! empty( $img_alt ) ? $img_alt : wp_strip_all_tags( $post_title );
1689 $lazy_class = $lazy ? ' swiper-lazy' : '';
1690 $attr = [
1691 'class' => 'img-responsive rtsb-product-image' . $lazy_class . $hover_class,
1692 'alt' => $alt_tag,
1693 ];
1694
1695 if ( $a_id ) {
1696 $img_html = wp_get_attachment_image( $a_id, $f_img_size, false, $attr );
1697 $attachment_id = $a_id;
1698 }
1699
1700 if ( ! $img_html && $default_img_id ) {
1701 $img_html = wp_get_attachment_image( $default_img_id, $f_img_size, false, $attr );
1702 $attachment_id = $default_img_id;
1703 }
1704
1705 if ( $img_html && $c_size ) {
1706 preg_match( '@src="([^"]+)"@', $img_html, $match );
1707 $img_src = array_pop( $match );
1708 $w = ! empty( $custom_img_size['width'] ) ? absint( $custom_img_size['width'] ) : null;
1709 $h = ! empty( $custom_img_size['height'] ) ? absint( $custom_img_size['height'] ) : null;
1710 $c = ! empty( $custom_img_size['crop'] ) && 'soft' === $custom_img_size['crop'] ? false : true;
1711
1712 if ( $w && $h ) {
1713 $image = self::image_resize( $img_src, $w, $h, $c, false );
1714
1715 if ( ! empty( $image ) ) {
1716 [ $src, $width, $height ] = $image;
1717
1718 $hwstring = image_hwstring( $width, $height );
1719 $attachment = get_post( $attachment_id );
1720 $attr = apply_filters( 'wp_get_attachment_image_attributes', $attr, $attachment, $f_img_size );
1721
1722 if ( $lazy ) {
1723 $attr['data-src'] = $src;
1724 } else {
1725 $attr['src'] = $src;
1726 }
1727
1728 $attr = array_map( 'esc_attr', $attr );
1729 $img_html = rtrim( "<img $hwstring" );
1730
1731 foreach ( $attr as $name => $value ) {
1732 $img_html .= " $name=" . '"' . $value . '"';
1733 }
1734
1735 $img_html .= ' />';
1736 }
1737 }
1738 }
1739
1740 if ( ! $img_html ) {
1741 $hwstring = image_hwstring( 160, 160 );
1742 $attr = isset( $attr['src'] ) ? apply_filters( 'wp_get_attachment_image_attributes', $attr, false, $f_img_size ) : [];
1743 $attr['class'] = 'default-img';
1744 $attr['src'] = esc_url( rtsb()->get_assets_uri( 'images/demo.png' ) );
1745 $attr['alt'] = esc_html__( 'Default Image', 'shopbuilder' );
1746 $img_html = rtrim( "<img $hwstring" );
1747
1748 foreach ( $attr as $name => $value ) {
1749 $img_html .= " $name=" . '"' . $value . '"';
1750 }
1751
1752 $img_html .= ' />';
1753 }
1754
1755 if ( $lazy ) {
1756 $img_html = $img_html . '<div class="swiper-lazy-preloader swiper-lazy-preloader"></div>';
1757 }
1758
1759 return $img_html;
1760 }
1761
1762 /**
1763 * Call the Image resize model for resize function
1764 *
1765 * @param string $url URL.
1766 * @param int $width Width.
1767 * @param int $height Height.
1768 * @param string $crop Crop.
1769 * @param bool|true $single Single.
1770 * @param bool|false $upscale Upscale.
1771 *
1772 * @return array|bool|string
1773 */
1774 public static function image_resize( $url, $width = null, $height = null, $crop = null, $single = true, $upscale = false ) {
1775 $rtResize = new ReSizer();
1776
1777 return $rtResize->process( $url, $width, $height, $crop, $single, $upscale );
1778 }
1779
1780 /**
1781 * Get Product Image
1782 *
1783 * @param string $f_image Featured Image.
1784 * @param string $h_image Hover Image.
1785 *
1786 * @return void
1787 */
1788 public static function get_product_image( $f_image, $h_image = null ) {
1789 if ( empty( $f_image ) ) {
1790 return;
1791 }
1792
1793 echo wp_kses( $f_image, self::allowedHtml( 'image' ) );
1794
1795 if ( ! empty( $h_image ) ) {
1796 echo wp_kses( $h_image, self::allowedHtml( 'image' ) );
1797 }
1798 }
1799
1800 /**
1801 * Post Custom Field value.
1802 *
1803 * @param int $post_id Post id.
1804 * @param string $field_key Custom Field Key.
1805 * @param string $field_fallback FallBack text.
1806 *
1807 * @return string|void
1808 */
1809 public static function get_post_custom_field_value( $post_id, $field_key = '', $field_fallback = '' ) {
1810 if ( ! $post_id || ! $field_key ) {
1811 return;
1812 }
1813 $field_value = get_post_meta( $post_id, $field_key, true );
1814 if ( empty( $field_value ) ) {
1815 $field_value = ! empty( $field_fallback ) ? $field_fallback : $field_value;
1816 }
1817 $field_value = apply_filters( 'rtsb/get_post_custom_field_value/' . $field_key, $field_value, $field_key );
1818
1819 return sprintf( '<span class="rtsb-woo-custom-field">%s</span>', $field_value );
1820 }
1821
1822 /**
1823 * Elementor Icon
1824 *
1825 * @param array $control Elementor Control array.
1826 * @param string $class Custom class.
1827 * @param string $builder Builder name.
1828 *
1829 * @return string
1830 */
1831 public static function icons_manager( $control, $class = '', $builder = 'elementor' ): string {
1832 if ( empty( $control['value'] ) ) {
1833 return '';
1834 }
1835 if ( is_array( $control['value'] ) ) {
1836 $cache_key = 'icons_managet_' . str_replace( ' ', '', md5( serialize( $control['value'] ) ) ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_serialize
1837 } else {
1838 $cache_key = 'icons_managet_' . str_replace( ' ', '', $control['value'] );
1839 }
1840 if ( isset( self::$cache[ $cache_key ] ) ) {
1841 return self::$cache[ $cache_key ];
1842 }
1843
1844 $attributes = [
1845 'aria-hidden' => 'true',
1846 ];
1847
1848 if ( ! empty( $class ) ) {
1849 $attributes['class'] = esc_attr( $class );
1850 }
1851
1852 ob_start();
1853
1854 if ( defined( 'ELEMENTOR_VERSION' ) && ! empty( $control ) && 'elementor' === $builder ) {
1855 Icons_Manager::render_icon( $control, $attributes );
1856 }
1857
1858 $icons = ob_get_clean();
1859 self::$cache[ $cache_key ] = $icons;
1860 return $icons;
1861 }
1862
1863 /**
1864 * Get product swatches.
1865 *
1866 * @param string $type Swatch type.
1867 *
1868 * @return void
1869 */
1870 public static function get_product_swatches( $type ) {
1871 ?>
1872 <div class="rtsb-swatches <?php echo esc_attr( $type ); ?>-layout">
1873 <?php
1874 if ( class_exists( 'Rtwpvsp' ) ) {
1875 do_action( 'rtwpvs_show_archive_variation' );
1876 } else {
1877 do_action( 'rtsb/vs/showcase/variation' );
1878 }
1879 ?>
1880 </div>
1881 <?php
1882 }
1883
1884 /**
1885 * Get sale badge
1886 *
1887 * @param string $type Badge type.
1888 * @param string $text Badge text.
1889 * @param string $out_of_stock_text Out of stock text.
1890 *
1891 * @return string
1892 */
1893 public static function get_sale_badge( $type, $text, $out_of_stock_text ) {
1894 global $product;
1895
1896 if ( ! $product->is_in_stock() ) {
1897 return $out_of_stock_text;
1898 }
1899
1900 if ( ! $product->is_on_sale() ) {
1901 return '';
1902 }
1903
1904 $badge_text = '';
1905 $percentage = self::calculate_sale_percentage( $product );
1906
1907 if ( $percentage > 0 ) {
1908 $badge_text = '-' . round( $percentage ) . '%';
1909 }
1910
1911 if ( 'text' === $type ) {
1912 $badge_text = $text;
1913 }
1914
1915 return $badge_text;
1916 }
1917
1918 /**
1919 * Get sale badge
1920 *
1921 * @param object $product Product Object.
1922 * @param string $type Badge type.
1923 * @param string $text Badge text.
1924 * @param string $out_of_stock_text Out of stock text.
1925 *
1926 * @return string
1927 */
1928 public static function get_promo_badge( $product, $type, $text, $out_of_stock_text ) {
1929 $disable_badges = self::get_option( 'general', 'guest_user', 'hide_badges', '' );
1930 $badges_visibility = rtsb()->has_pro() && ! is_user_logged_in() && $disable_badges;
1931
1932 if ( $badges_visibility ) {
1933 return '';
1934 }
1935
1936 if ( is_null( $product ) ) {
1937 global $product;
1938 }
1939
1940 if ( ! $product instanceof WC_Product ) {
1941 return '';
1942 }
1943
1944 if ( ! $product->is_in_stock() ) {
1945 return $out_of_stock_text;
1946 }
1947
1948 if ( ! $product->is_on_sale() ) {
1949 return '';
1950 }
1951
1952 $badge_text = '';
1953 $percentage = self::calculate_sale_percentage( $product );
1954
1955 if ( $percentage > 0 ) {
1956 $badge_text = '-' . round( $percentage ) . '%';
1957 }
1958
1959 if ( 'text' === $type ) {
1960 $badge_text = $text;
1961 }
1962
1963 return $badge_text;
1964 }
1965
1966 /**
1967 * Calculate Sale Percentage
1968 *
1969 * @param object $product Product object.
1970 *
1971 * @return float|int|string
1972 */
1973 public static function calculate_sale_percentage( $product = null ) {
1974 if ( is_null( $product ) ) {
1975 global $product;
1976 }
1977 if ( ! $product instanceof WC_Product ) {
1978 return '';
1979 }
1980 if ( ! $product->is_on_sale() ) {
1981 return '';
1982 }
1983 $max_percentage = 0;
1984 if ( $product->is_type( 'simple' ) ) {
1985 $regular_price = (float) $product->get_regular_price();
1986 $sale_price = (float) $product->get_sale_price();
1987 if ( $regular_price > 0 && $sale_price > 0 ) {
1988 $max_percentage = ( ( $regular_price - $sale_price ) / $regular_price ) * 100;
1989 }
1990 } elseif ( $product->is_type( 'variable' ) ) {
1991 $prices = $product->get_variation_prices( true );
1992 if ( ! empty( $prices['regular_price'] ) && ! empty( $prices['sale_price'] ) ) {
1993 foreach ( $prices['regular_price'] as $key => $regular_price ) {
1994 $sale_price = $prices['sale_price'][ $key ];
1995
1996 if ( $regular_price > 0 && $sale_price > 0 && $regular_price > $sale_price ) {
1997 $percentage = ( ( $regular_price - $sale_price ) / $regular_price ) * 100;
1998 $max_percentage = max( $max_percentage, $percentage );
1999 }
2000 }
2001 }
2002 }
2003 return $max_percentage > 0 ? round( $max_percentage ) : 0;
2004 }
2005
2006 /**
2007 * Pagination
2008 *
2009 * @param string $pages Pages.
2010 * @param integer $range Range.
2011 * @param boolean $ajax Ajax.
2012 *
2013 * @return string
2014 */
2015 public static function custom_pagination( $pages = '', $range = 4, $ajax = false ) {
2016 $html = null;
2017 $visible_range = apply_filters( 'rtsb/elements/pagination/visible_range', ( $range * 2 ) + 1 );
2018
2019 global $paged;
2020
2021 if ( is_front_page() ) {
2022 $paged = ( get_query_var( 'page' ) ) ? get_query_var( 'page' ) : 1; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
2023 } else {
2024 $paged = ( get_query_var( 'paged' ) ) ? get_query_var( 'paged' ) : 1; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
2025 }
2026
2027 if ( empty( $paged ) ) {
2028 $paged = 1; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
2029 }
2030
2031 if ( '' === $pages ) {
2032 global $wp_query;
2033
2034 $pages = $wp_query->max_num_pages;
2035
2036 if ( ! $pages ) {
2037 $pages = 1;
2038 }
2039 }
2040
2041 $ajaxClass = null;
2042 $dataAttr = null;
2043
2044 if ( $ajax ) {
2045 $ajaxClass = ' rtsb-pagination-ajax';
2046 $dataAttr = "data-paged='1'";
2047 $dataAttr .= ' data-visible-range="' . esc_attr( $visible_range ) . '"';
2048 }
2049
2050 if ( 1 !== $pages ) {
2051 $html .= '<div class="rtsb-pagination' . $ajaxClass . '" ' . $dataAttr . '>';
2052 $html .= '<ul class="pagination-list">';
2053
2054 if ( $paged > 2 && $paged > $visible_range + 1 && $visible_range < $pages ) {
2055 $html .= "<li><a data-paged='1' href='" . get_pagenum_link( 1 ) . "' aria-label='First'>&laquo;</a></li>";
2056 }
2057
2058 if ( $paged > 1 && $visible_range < $pages ) {
2059 $p = $paged - 1;
2060 $html .= "<li><a data-paged='{$p}' href='" . get_pagenum_link( $p ) . "' aria-label='Previous'>&lsaquo;</a></li>";
2061 }
2062
2063 for ( $i = 1; $i <= $pages; $i++ ) {
2064 if ( 1 != $pages && ( ! ( $i >= $paged + $visible_range + 1 || $i <= $paged - $visible_range - 1 ) || $pages <= $visible_range ) ) {
2065 $html .= ( $paged == $i ) ? '<li class="active"><span>' . $i . '</span></li>' : "<li><a data-paged='{$i}' href='" . get_pagenum_link( $i ) . "'>" . $i . '</a></li>';
2066 }
2067 }
2068
2069 if ( $paged < $pages && $visible_range < $pages ) {
2070 $p = $paged + 1;
2071 $html .= "<li><a data-paged='{$p}' href=\"" . get_pagenum_link( $paged + 1 ) . "\" aria-label='Next'>&rsaquo;</a></li>";
2072 }
2073
2074 if ( $paged < $pages - 1 && $paged + $range - 1 < $pages && $visible_range < $pages ) {
2075 $html .= "<li><a data-paged='{$pages}' href='" . get_pagenum_link( $pages ) . "' aria-label='Last'>&raquo;</a></li>";
2076 }
2077
2078 $html .= '</ul>';
2079 $html .= '</div>';
2080 }
2081
2082 return $html;
2083 }
2084
2085 /**
2086 * Action Buttons HTMl
2087 *
2088 * @param array $items Items.
2089 * @param array $ajax_cart Ajax cart HTML.
2090 * @param string $preset Button preset.
2091 * @param array $position Position.
2092 * @param string $placement Placement.
2093 *
2094 * @return void|string
2095 */
2096 public static function get_formatted_action_buttons( $items, $ajax_cart = '', $preset = 'preset1', $position = 'above', $placement = 'top' ) {
2097 if ( empty( $items ) ) {
2098 return;
2099 }
2100
2101 $html = '';
2102 $class = '';
2103 $args = [ $items, $ajax_cart, $preset, $position, $placement ];
2104
2105 if ( ( 'top' === $placement && 'after' === $position ) || ( 'bottom' === $placement && 'above' === $position ) ) {
2106 return apply_filters( 'rtsb/elements/elementor/formatted_action_buttons', $html, $args );
2107 }
2108
2109 if ( 'preset2' === $preset ) {
2110 $class = 'rtsb-action-buttons-vertical vertical-delay-effect ' . $preset;
2111 } elseif ( 'preset4' === $preset ) {
2112 $class = 'rtsb-action-buttons-vertical rtsb-action-buttons-vertical-left vertical-delay-effect ' . $preset;
2113 } elseif ( 'preset1' === $preset || 'preset3' === $preset ) {
2114 $class = 'rtsb-action-buttons-cart-box-width-auto horizontal-floating-btn ' . $preset;
2115 }
2116
2117 if ( 'after' === $position && 'preset1' === $preset ) {
2118 $class .= ' after-content';
2119 }
2120
2121 if ( 'preset3' === $preset ) {
2122 $html .= '<div class="rtsb-action-buttons top-part ' . esc_attr( $preset ) . '">';
2123 $html .= '<ul class="rtsb-action-button-list">';
2124
2125 ob_start();
2126 /**
2127 * Additional formatted action buttons hook.
2128 */
2129 do_action( 'rtsb/elements/elementor/additional_action_buttons', $items );
2130 $html .= ob_get_clean();
2131
2132 $html .= self::get_action_button_by_type( $items, 'wishlist' );
2133 $html .= self::get_action_button_by_type( $items, 'compare' );
2134 $html .= self::get_action_button_by_type( $items, 'quick_view' );
2135 $html .= '</ul>';
2136 $html .= '</div>';
2137 $html .= '<div class="rtsb-action-buttons bottom-part ' . esc_attr( $preset ) . '">';
2138 $html .= '<ul class="rtsb-action-button-list">';
2139 $html .= self::get_action_button_by_type( $items, 'add_to_cart', $ajax_cart );
2140 $html .= '</ul>';
2141 $html .= '</div>';
2142 } elseif ( 'preset1' === $preset || 'preset2' === $preset || 'preset4' === $preset ) {
2143 $html .= '<div class="rtsb-action-buttons ' . esc_attr( $class ) . '">';
2144 $html .= '<ul class="rtsb-action-button-list">';
2145 $html .= self::get_action_button_by_type( $items, 'add_to_cart', $ajax_cart );
2146
2147 ob_start();
2148 /**
2149 * Additional formatted action buttons hook.
2150 */
2151 do_action( 'rtsb/elements/elementor/additional_action_buttons', $items );
2152 $html .= ob_get_clean();
2153
2154 $html .= self::get_action_button_by_type( $items, 'wishlist' );
2155 $html .= self::get_action_button_by_type( $items, 'compare' );
2156 $html .= self::get_action_button_by_type( $items, 'quick_view' );
2157 $html .= '</ul>';
2158 $html .= '</div>';
2159 }
2160
2161 return apply_filters( 'rtsb/elements/elementor/formatted_action_buttons', $html, $args );
2162 }
2163
2164 /**
2165 * Get Action Button HTML
2166 *
2167 * @param array $items Items.
2168 * @param string $type Button type.
2169 * @param string $cart_html Ajax cart HTML.
2170 * @param string $wrapper Wrapper tag.
2171 *
2172 * @return void|string
2173 */
2174 public static function get_action_button_by_type( $items, $type, $cart_html = '', $wrapper = 'li' ) {
2175 if ( ! in_array( $type, $items, true ) ) {
2176 return;
2177 }
2178
2179 $html = '';
2180 $class = 'rtsb-action-button-item';
2181
2182 if ( 'add_to_cart' === $type ) {
2183 $class .= ' rtsb-cart' . ( empty( $cart_html ) ? esc_attr( ' no-cart-button' ) : '' );
2184 } else {
2185 $class .= ' rtsb-' . esc_attr( str_replace( '_', '-', $type ) );
2186 }
2187
2188 if ( ( 'add_to_cart' === $type ) && ( ! empty( $cart_html ) ) ) {
2189 $html .= $cart_html;
2190 } else {
2191 $html .= shortcode_exists( 'rtsb_' . $type . '_button' ) ? do_shortcode( '[rtsb_' . $type . '_button]' ) : null;
2192 }
2193
2194 if ( ! empty( $html ) ) {
2195 $html = '<' . esc_attr( $wrapper ) . ' class="' . esc_attr( $class ) . '">' . $html . '</' . esc_attr( $wrapper ) . '>';
2196 }
2197
2198 return apply_filters( 'rtsb/elements/elementor/get_action_button_by_type', $html );
2199 }
2200
2201 /**
2202 * Social Share Platforms.
2203 *
2204 * @return mixed|null
2205 */
2206 public static function social_share_platforms_list() {
2207 return apply_filters(
2208 'rtsb/settings/social_share/platforms',
2209 [
2210 [
2211 'value' => 'facebook',
2212 'label' => esc_html__( 'Facebook', 'shopbuilder' ),
2213 ],
2214 [
2215 'value' => 'twitter',
2216 'label' => esc_html__( 'Twitter', 'shopbuilder' ),
2217 ],
2218 [
2219 'value' => 'linkedin',
2220 'label' => esc_html__( 'Linkedin', 'shopbuilder' ),
2221 ],
2222 [
2223 'value' => 'pinterest',
2224 'label' => esc_html__( 'Pinterest', 'shopbuilder' ),
2225 ],
2226 [
2227 'value' => 'skype',
2228 'label' => esc_html__( 'Skype', 'shopbuilder' ),
2229 ],
2230 [
2231 'value' => 'whatsapp',
2232 'label' => esc_html__( 'Whatsapp', 'shopbuilder' ),
2233 ],
2234 [
2235 'value' => 'reddit',
2236 'label' => esc_html__( 'Reddit', 'shopbuilder' ),
2237 ],
2238 [
2239 'value' => 'telegram',
2240 'label' => esc_html__( 'Telegram', 'shopbuilder' ),
2241 ],
2242 ]
2243 );
2244 }
2245
2246 /**
2247 * Get Social Share link HTML.
2248 *
2249 * @param int $id Post ID.
2250 * @param array $types Preset type.
2251 * @param string $preset Style type.
2252 * @param boolean $show_icon Show icon.
2253 * @param boolean $show_text Show text.
2254 *
2255 * @return string
2256 */
2257 public static function get_social_share_html( int $id, array $types, string $preset = 'default', $show_icon = true, $show_text = true ) {
2258 $attr = [ 'postid' => $id ];
2259 $output = '';
2260
2261 if ( empty( $types ) ) {
2262 return $output;
2263 }
2264
2265 foreach ( $types as $type ) {
2266 $link = [];
2267 $link['type'] = $type['share_items'];
2268 $link['class'] = '';
2269 $link['img'] = apply_filters( 'rtsb/elements/share/default_img', '', $id, $link );
2270
2271 if ( 'site' === $id ) {
2272 $link['url'] = home_url();
2273 $link['title'] = wp_strip_all_tags( get_bloginfo( 'name' ) );
2274 } elseif ( 0 === strpos( $id, 'http' ) ) {
2275 $link['url'] = $id;
2276 $link['title'] = '';
2277 } else {
2278 $link['url'] = get_permalink( $id );
2279 $link['title'] = wp_strip_all_tags( get_the_title( $id ) );
2280
2281 if ( has_post_thumbnail( $id ) ) {
2282 $link['img'] = wp_get_attachment_image_url( get_post_thumbnail_id( $id ), 'full' );
2283 }
2284
2285 $link['img'] = apply_filters( 'rtsb/elements/share/single_img', $link['img'], $id, $link );
2286 }
2287
2288 $link['url'] = apply_filters( 'rtsb/elements/share/url', $link['url'], $link );
2289
2290 switch ( $type['share_items'] ) {
2291 case 'facebook':
2292 $link['link'] = esc_url( 'https://www.facebook.com/sharer/sharer.php?u=' . $link['url'] . '&display=popup&ref=plugin&src=share_button' );
2293 $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>';
2294 $link['attr_title'] = esc_html__( 'Share on Facebook', 'shopbuilder' );
2295 $link['social_network'] = 'Facebook';
2296 $link['social_action'] = 'Share';
2297 break;
2298 case 'twitter':
2299 $link['link'] = esc_url( 'https://x.com/intent/tweet?text=' . htmlspecialchars( rawurlencode( html_entity_decode( $link['title'], ENT_COMPAT, 'UTF-8' ) ), ENT_COMPAT, 'UTF-8' ) . '&url=' . $link['url'] );
2300 $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>';
2301 $link['attr_title'] = esc_html__( 'Share on Twitter', 'shopbuilder' );
2302 $link['social_network'] = 'Twitter';
2303 $link['social_action'] = 'Tweet';
2304 break;
2305 case 'pinterest':
2306 $link['link'] = esc_url( 'https://pinterest.com/pin/create/button/?url=' . $link['url'] . '&media=' . $link['img'] . '&description=' . $link['title'] );
2307 $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>';
2308 $link['attr_title'] = esc_html__( 'Share on Pinterest', 'shopbuilder' );
2309 $link['social_network'] = 'Pinterest';
2310 $link['social_action'] = 'Pin';
2311 break;
2312 case 'linkedin':
2313 $link['link'] = esc_url( 'https://www.linkedin.com/shareArticle?url=' . $link['url'] . '&title=' . $link['title'] );
2314 $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>';
2315 $link['attr_title'] = esc_html__( 'Share on LinkedIn', 'shopbuilder' );
2316 $link['social_network'] = 'LinkedIn';
2317 $link['social_action'] = 'Share';
2318 break;
2319 case 'skype':
2320 $link['link'] = esc_url( 'https://web.skype.com/share?url=' . $link['url'] );
2321 $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>';
2322 $link['attr_title'] = esc_html__( 'Share on Skype', 'shopbuilder' );
2323 $link['social_network'] = 'Skype';
2324 $link['social_action'] = 'Skype';
2325 break;
2326 case 'whatsapp':
2327 $link['link'] = esc_url( 'https://wa.me/?text=' . $link['title'] . ' ' . $link['url'] );
2328 $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>';
2329 $link['attr_title'] = esc_html__( 'Share on Whatsapp', 'shopbuilder' );
2330 $link['social_network'] = 'Whatsapp';
2331 $link['social_action'] = 'Share';
2332 break;
2333 case 'reddit':
2334 $link['link'] = esc_url( 'https://reddit.com/submit?url=' . $link['url'] . '&title=' . $link['title'] );
2335 $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>';
2336 $link['attr_title'] = esc_html__( 'Share on Reddit', 'shopbuilder' );
2337 $link['social_network'] = 'Reddit';
2338 $link['social_action'] = 'Share';
2339 break;
2340 case 'telegram':
2341 $link['link'] = esc_url( 'https://telegram.me/share/url?text=' . $link['title'] . '&url=' . $link['url'] );
2342 $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>';
2343 $link['attr_title'] = esc_html__( 'Share on Telegram', 'shopbuilder' );
2344 $link['social_network'] = 'Telegram';
2345 $link['social_action'] = 'Share';
2346 break;
2347 }
2348
2349 $link['label'] = $type['share_text'];
2350 $link['target'] = '_blank';
2351 $link['rel'] = 'nofollow noopener noreferrer';
2352
2353 $data = '';
2354 $link = apply_filters( 'rtsb/elements/share/share_link', $link, $id, $preset );
2355 $icon = apply_filters( 'rtsb/elements/share/share_icon', $link['icon'], $preset );
2356 $target = ! empty( $link['target'] ) ? ' target="' . esc_attr( $link['target'] ) . '" ' : '';
2357 $rel = ! empty( $link['rel'] ) ? ' rel="' . esc_attr( $link['rel'] ) . '" ' : '';
2358 $attr_title = ! empty( $link['attr_title'] ) ? ' title="' . esc_attr( $link['attr_title'] ) . '" ' : '';
2359 $elements = [];
2360
2361 // Add classes.
2362 $css_classes = [
2363 'rtsb-share-btn',
2364 sanitize_html_class( $link['type'] ),
2365 ];
2366 $css_classes = array_merge( $css_classes, explode( ' ', $link['class'] ) );
2367 $css_classes = array_map( 'sanitize_html_class', $css_classes );
2368 $css_classes = implode( ' ', array_filter( $css_classes ) );
2369
2370 unset( $attr['pin-do'], $attr['action'] );
2371
2372 if ( 'pinterest' === $type['share_items'] ) {
2373 $attr['pin-do'] = 'none';
2374 }
2375
2376 if ( 'whatsapp' === $type['share_items'] ) {
2377 $attr['action'] = 'share/whatsapp/share';
2378 }
2379
2380 $attr = apply_filters( 'rtsb/elements/share/link_data', $attr, $link, $id );
2381
2382 if ( ! empty( $attr ) ) {
2383 foreach ( $attr as $key => $val ) {
2384 $data .= ' data-' . sanitize_html_class( $key ) . '="' . esc_attr( $val ) . '"';
2385 }
2386 }
2387
2388 $additional_attr = apply_filters( 'rtsb/elements/share/additional_attr', [], $link, $id, $preset );
2389
2390 if ( ! empty( $additional_attr ) ) {
2391 $attr_output = join( ' ', $additional_attr );
2392
2393 if ( ! empty( $data ) ) {
2394 $attr_output = ' ' . $attr_output;
2395 }
2396
2397 $data .= $attr_output;
2398 }
2399
2400 $elements['wrapper_start'] = sprintf(
2401 '<li class="rtsb-share-item"><a href="%s"%s%s%s class="%s"%s>',
2402 ! empty( $link['link'] ) ? esc_attr( $link['link'] ) : '',
2403 $attr_title,
2404 $target,
2405 $rel,
2406 $css_classes,
2407 $data
2408 );
2409 $elements['wrapper_end'] = '</a></li>';
2410
2411 $elements['icon'] = $show_icon ? '<span class="rtsb-share-icon">' . ( ! empty( $icon ) ? $icon : null ) . '</span>' : null;
2412 $elements['label'] = $show_text && ! empty( $link['label'] ) ? '<span class="rtsb-share-label">' . $link['label'] . '</span>' : null;
2413 $elements['icon_label'] = '<span class="rtsb-share-icon-label">' . $elements['icon'] . $elements['label'] . '</span>';
2414 $elements = apply_filters( 'rtsb/elements/share/output_elements', $elements, $link, $id );
2415
2416 $output .= $elements['wrapper_start'] . $elements['icon_label'] . $elements['wrapper_end'];
2417 }
2418
2419 return apply_filters( 'rtsb/elements/share/list_output', $output );
2420 }
2421
2422 /**
2423 * Social Share Platforms.
2424 *
2425 * @param string $module Module.
2426 *
2427 * @return true|void
2428 */
2429 public static function is_module_active( $module ) {
2430 $modulelist = self::get_modules_list();
2431
2432 if ( ! empty( $modulelist[ $module ]['active'] ) ) {
2433 return true;
2434 }
2435 }
2436
2437 /**
2438 * Elementor Widget Active.
2439 *
2440 * @param string $widget Elementor Widget.
2441 *
2442 * @return true|void
2443 */
2444 public static function is_elementor_widget_active( $widget ) {
2445 $element_list = self::get_widgets_list();
2446
2447 if ( ! empty( $element_list[ $widget ]['active'] ) ) {
2448 return true;
2449 }
2450 }
2451
2452 /***
2453 * Save Settings data
2454 *
2455 * @param string $section_id Section ID.
2456 * @param string $block_id Block ID.
2457 * @param array $rawOptions Raw Options.
2458 *
2459 * @return array
2460 */
2461 public static function set_options( $section_id = '', $block_id = '', $rawOptions = [] ) { // phpcs:ignore Generic.Metrics.NestingLevel.TooHigh
2462 $section_id = ! empty( $section_id ) ? sanitize_text_field( wp_unslash( $section_id ) ) : '';
2463 $block_id = ! empty( $block_id ) ? sanitize_text_field( wp_unslash( $block_id ) ) : '';
2464 $rawOptions = ! empty( $rawOptions ) ? $rawOptions : [];
2465 $results = [
2466 'status' => true,
2467 'message' => '',
2468 ];
2469 if ( ! $section_id || ! $block_id ) {
2470 $results['status'] = false;
2471 $results['message'] = esc_html__( 'Section , block or options may be empty', 'shopbuilder' );
2472
2473 return $results;
2474 }
2475 $sections = Settings::instance()->get_sections();
2476 if ( empty( $sections[ $section_id ] ) || empty( $sections[ $section_id ]['list'][ $block_id ] ) ) {
2477 $results['status'] = false;
2478 $results['message'] = esc_html__( 'No section or block found with given data', 'shopbuilder' );
2479
2480 return $results;
2481 }
2482
2483 $options = DataModel::source()->get_option( $section_id, [], false );
2484 $changed = false;
2485 $fields = [];
2486 if ( isset( $sections[ $section_id ]['list'][ $block_id ]['fields'] ) && ! empty( $sections[ $section_id ]['list'][ $block_id ]['fields'] ) ) {
2487 $fields = $sections[ $section_id ]['list'][ $block_id ]['fields'];
2488 }
2489
2490 if ( empty( $fields ) ) {
2491 if ( isset( $rawOptions['active'] ) ) {
2492 $changed = true;
2493 $options[ $block_id ]['active'] = 'on' === $rawOptions['active'] ? 'on' : '';
2494 }
2495 } else {
2496 foreach ( $rawOptions as $raw_option_key => $raw_value ) {
2497 if ( 'active' === $raw_option_key ) {
2498 $changed = true;
2499 $options[ $block_id ]['active'] = $sections[ $section_id ]['list'][ $block_id ]['active'] = 'on' === $raw_value ? 'on' : ''; // phpcs:ignore Squiz.PHP.DisallowMultipleAssignments.Found
2500 } else {
2501 if ( isset( $fields[ $raw_option_key ] ) ) {
2502 $field = $fields[ $raw_option_key ];
2503 if ( 'switch' === $field['type'] ) {
2504 $value = 'on' === $raw_value ? 'on' : '';
2505 } elseif ( 'repeaters' === $field['type'] ) {
2506 $the_value = [];
2507 $rep_title = [];
2508 if ( ! empty( $raw_value ) && is_array( $raw_value ) ) {
2509 foreach ( $raw_value as $key => $value ) {
2510 if ( is_string( $value ) ) {
2511 $the_value_decoded = json_decode( stripslashes_deep( $value ) );
2512 } else {
2513 $the_value_decoded = $value;
2514 }
2515 $cr = $the_value_decoded->title ?? '';
2516 if ( in_array( $cr, $rep_title, true ) ) {
2517 continue;
2518 }
2519 $rep_title[] = $cr;
2520 $the_value[] = $the_value_decoded;
2521 }
2522 } elseif ( ! empty( $raw_value ) && is_string( $raw_value ) ) {
2523 $the_value = $raw_value;
2524 }
2525 $value = wp_json_encode( $the_value, JSON_UNESCAPED_UNICODE );
2526 } elseif ( in_array( $field['type'], [ 'product_addons_special_settings', 'checkout_fields' ] ) ) { // phpcs:ignore WordPress.PHP.StrictInArray.MissingTrueStrict
2527 $manual_field_value = [];
2528 if ( is_array( $raw_value ) ) {
2529 foreach ( $raw_value as $key => $value ) {
2530 $manual_field_value[] = json_decode( stripslashes( $value ), true );
2531 }
2532 $value = wp_json_encode( $manual_field_value, JSON_UNESCAPED_UNICODE );
2533 } elseif ( is_string( $raw_value ) ) {
2534 $value = $raw_value;
2535 }
2536 } else {
2537 if ( ! empty( $field['multiple'] ) || in_array( $field['type'], [ 'checkbox', 'search_and_multi_select' ] ) ) { // phpcs:ignore WordPress.PHP.StrictInArray.MissingTrueStrict
2538 if ( isset( $raw_value ) && is_array( $raw_value ) ) {
2539 if ( ! empty( $field['sanitize_fn'] ) && is_callable( $field['sanitize_fn'] ) ) {
2540 $value = array_map( $field['sanitize_fn'], $raw_value );
2541 } else {
2542 $value = array_map( 'sanitize_text_field', $raw_value );
2543 }
2544 } else {
2545 $value = [];
2546 }
2547 } else {
2548 if ( ! empty( $field['sanitize_fn'] ) ) {
2549 if ( is_callable( $field['sanitize_fn'] ) ) {
2550 $value = $field['sanitize_fn']( $raw_value );
2551 } elseif ( 'pass_all' === $field['sanitize_fn'] ) {
2552 $value = $raw_value;
2553 } else {
2554 $value = $field['sanitize_fn']( $raw_value );
2555 }
2556 } else {
2557 $value = sanitize_text_field( $raw_value );
2558 }
2559 }
2560 }
2561 $options[ $block_id ][ $raw_option_key ] = $sections[ $section_id ]['list'][ $block_id ]['fields'][ $raw_option_key ]['value'] = $value ?? null; // phpcs:ignore Squiz.PHP.DisallowMultipleAssignments.Found
2562 $changed = true;
2563 }
2564 }
2565 }
2566 }
2567 if ( ! $changed ) {
2568 $results['status'] = false;
2569 $results['message'] = esc_html__( 'No changes found for update', 'shopbuilder' );
2570
2571 return $results;
2572 }
2573
2574 DataModel::source()->set_option( $section_id, $options );
2575
2576 $results['status'] = true;
2577 $results['message'] = esc_html__( 'Successfully Saved', 'shopbuilder' );
2578 $results['sections'] = $sections;
2579
2580 return $results;
2581 }
2582
2583 /**
2584 * Count products by taxonomies.
2585 *
2586 * @param array $terms Terms.
2587 * @param string $taxonomy Taxonomy.
2588 *
2589 * @return int|void
2590 */
2591 public static function count_products_by_taxonomies( $terms, $taxonomy = 'product_cat' ) {
2592 if ( empty( $terms ) || ! is_array( $terms ) ) {
2593 return;
2594 }
2595
2596 $args = [
2597 'limit' => -1,
2598 'return' => 'ids',
2599 ];
2600
2601 if ( 'product_cat' === $taxonomy ) {
2602 $args['product_category_id'] = $terms;
2603 } elseif ( 'product_brand' === $taxonomy ) {
2604 $args['product_brand_id'] = $terms;
2605 } else {
2606 $args['product_tag_id'] = $terms;
2607 }
2608
2609 $query = new WC_Product_Query( $args );
2610
2611 return ! empty( $query->get_products() ) ? count( $query->get_products() ) : 0;
2612 }
2613
2614 /**
2615 * Count products by attribute terms.
2616 *
2617 * @param array $term_ids Term IDs.
2618 * @param string $relation Relation.
2619 *
2620 * @return int
2621 */
2622 public static function count_products_by_attribute_terms( $term_ids, $relation = 'AND' ) {
2623 if ( empty( $term_ids ) || ! is_array( $term_ids ) ) {
2624 return 0;
2625 }
2626
2627 $terms_by_taxonomy = [];
2628
2629 foreach ( $term_ids as $term_id ) {
2630 $term = get_term( $term_id );
2631 if ( ! is_wp_error( $term ) && $term ) {
2632 if ( ! isset( $terms_by_taxonomy[ $term->taxonomy ] ) ) {
2633 $terms_by_taxonomy[ $term->taxonomy ] = [];
2634 }
2635 $terms_by_taxonomy[ $term->taxonomy ][] = $term_id;
2636 }
2637 }
2638
2639 if ( empty( $terms_by_taxonomy ) ) {
2640 return 0;
2641 }
2642
2643 $args = [
2644 'status' => 'publish',
2645 'limit' => -1,
2646 'return' => 'ids',
2647 'tax_query' => [ // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query
2648 'relation' => strtoupper( $relation ),
2649 ],
2650 ];
2651
2652 foreach ( $terms_by_taxonomy as $taxonomy => $terms ) {
2653 $args['tax_query'][] = [
2654 'taxonomy' => $taxonomy,
2655 'field' => 'term_id',
2656 'terms' => $terms,
2657 'operator' => 'IN',
2658 ];
2659 }
2660
2661 $query = new WC_Product_Query( $args );
2662 $products = $query->get_products();
2663
2664 return count( $products );
2665 }
2666
2667 /**
2668 * Check if product filters widget has ajax.
2669 *
2670 * @param string $page Page name.
2671 *
2672 * @return bool
2673 */
2674 public static function product_filters_has_ajax( $page ) {
2675 if ( empty( $page ) ) {
2676 return false;
2677 }
2678
2679 if ( 'page' === get_post_type() ) {
2680 return false;
2681 }
2682
2683 $id = BuilderFns::is_builder_preview() ? get_the_ID() : BuilderFns::builder_page_id_by_type( $page );
2684 if ( ! $id ) {
2685 return false;
2686 }
2687 $cache_key = 'product_filters_has_ajax_' . $id;
2688 if ( isset( self::$cache[ $cache_key ] ) ) {
2689 return self::$cache[ $cache_key ];
2690 }
2691 $elmap = ElementorDataMap::instance();
2692 $ajax = [];
2693
2694 foreach ( $elmap->get_widget_data( 'rtsb-ajax-product-filters', [], $id ) as $data ) {
2695 $ajax[] = isset( $data['settings']['ajax_mode'] ) ? false : true;
2696 }
2697
2698 self::$cache[ $cache_key ] = ! empty( $ajax[0] );
2699 return ! empty( $ajax[0] );
2700 }
2701
2702 /**
2703 * Check if product has applied filter.
2704 *
2705 * @param string $page Page name.
2706 *
2707 * @return bool
2708 */
2709 public static function product_has_applied_filters( $page ) {
2710 if ( empty( $page ) ) {
2711 return false;
2712 }
2713
2714 $id = BuilderFns::is_builder_preview() ? get_the_ID() : BuilderFns::builder_page_id_by_type( $page );
2715 $elmap = ElementorDataMap::instance();
2716 $ajax = [];
2717 if ( ! $id ) {
2718 return false;
2719 }
2720 foreach ( $elmap->get_widget_data( 'rtsb-ajax-product-filters', [], $id ) as $data ) {
2721 $ajax[] = ! isset( $data['settings']['active_filter'] );
2722
2723 }
2724
2725 return ! empty( $ajax[0] );
2726 }
2727
2728 /**
2729 * Get WooCommerce product categories.
2730 *
2731 * @param string|null $search_query The search string for category names.
2732 *
2733 * @return array An array of product categories with 'value' and 'label' keys.
2734 */
2735 public static function products_category_query( $search_query = null ) {
2736 if ( ! is_admin() ) {
2737 return [];
2738 }
2739
2740 $cache_key = 'rtsb_product_categories_' . md5( serialize( $search_query ) ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_serialize
2741
2742 if ( isset( self::$cache[ $cache_key ] ) ) {
2743 return self::$cache[ $cache_key ];
2744 }
2745 $results = wp_cache_get( $cache_key, 'shopbuilder' );
2746 if ( ! $results ) {
2747 global $wpdb;
2748 $sql = "SELECT t.term_id, t.name
2749 FROM {$wpdb->terms} t
2750 JOIN {$wpdb->term_taxonomy} tt ON t.term_id = tt.term_id
2751 WHERE tt.taxonomy = 'product_cat'";
2752
2753 if ( $search_query ) {
2754 $sql .= ' AND t.name LIKE %s';
2755 $prepared_sql = $wpdb->prepare( $sql, '%' . $wpdb->esc_like( $search_query ) . '%' ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
2756 } else {
2757 $prepared_sql = $sql; // No need for placeholders.
2758 }
2759
2760 $results = $wpdb->get_results( $prepared_sql ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.NotPrepared
2761 // Cache the results in the object cache for future use.
2762 wp_cache_set( $cache_key, $results, 'shopbuilder' ); // Adjust the expiration time as needed.
2763 Cache::set_data_cache_key( $cache_key );
2764 }
2765
2766 $cats = [];
2767 if ( ! is_array( $results ) && ! count( $results ) ) {
2768 return $cats;
2769 }
2770 foreach ( $results as $row ) {
2771 $category_id = $row->term_id;
2772 $category_title = $row->name;
2773
2774 $cats[] = [
2775 'value' => $category_id,
2776 'label' => $category_title,
2777 ];
2778 }
2779
2780 // Cache the results in a static variable for the next call.
2781 self::$cache[ $cache_key ] = $cats;
2782 return $cats;
2783 }
2784 /**
2785 * Get WooCommerce product categories.
2786 *
2787 * @param string|null $search_query The search string for category names.
2788 *
2789 * @return array An array of product categories with 'value' and 'label' keys.
2790 */
2791 public static function products_tags_query( $search_query = null ) {
2792 if ( ! is_admin() ) {
2793 return [];
2794 }
2795
2796 $cache_key = 'rtsb_product_tags_' . md5( serialize( $search_query ) ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_serialize
2797
2798 if ( isset( self::$cache[ $cache_key ] ) ) {
2799 return self::$cache[ $cache_key ];
2800 }
2801 $results = wp_cache_get( $cache_key, 'shopbuilder' );
2802 if ( ! $results ) {
2803 global $wpdb;
2804 $sql = "SELECT t.term_id, t.name
2805 FROM {$wpdb->terms} t
2806 JOIN {$wpdb->term_taxonomy} tt ON t.term_id = tt.term_id
2807 WHERE tt.taxonomy = 'product_tag'";
2808
2809 if ( $search_query ) {
2810 $sql .= ' AND t.name LIKE %s';
2811 $prepared_sql = $wpdb->prepare( $sql, '%' . $wpdb->esc_like( $search_query ) . '%' ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
2812 } else {
2813 $prepared_sql = $sql; // No need for placeholders.
2814 }
2815
2816 $results = $wpdb->get_results( $prepared_sql ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.NotPrepared
2817 // Cache the results in the object cache for future use.
2818 wp_cache_set( $cache_key, $results, 'shopbuilder' ); // Adjust the expiration time as needed.
2819 Cache::set_data_cache_key( $cache_key );
2820 }
2821
2822 $cats = [];
2823 if ( ! is_array( $results ) && ! count( $results ) ) {
2824 return $cats;
2825 }
2826 foreach ( $results as $row ) {
2827 $category_id = $row->term_id;
2828 $category_title = $row->name;
2829
2830 $cats[] = [
2831 'value' => $category_id,
2832 'label' => $category_title,
2833 ];
2834 }
2835
2836 // Cache the results in a static variable for the next call.
2837 self::$cache[ $cache_key ] = $cats;
2838 return $cats;
2839 }
2840
2841 /**
2842 * @param string $search_query Search query.
2843 *
2844 * @return array
2845 */
2846 public static function get_registered_post_types( $search_query = null ) {
2847 // Get all registered post types.
2848 $registered_post_types = get_post_types(
2849 [
2850 'public' => true,
2851 '_builtin' => false,
2852 ],
2853 'objects'
2854 );
2855
2856 unset( $registered_post_types['elementor_library'] );
2857 unset( $registered_post_types['e-landing-page'] );
2858 unset( $registered_post_types['rtsb_builder'] );
2859
2860 $post_types = [];
2861
2862 // Check if a search query is provided.
2863 if ( ! empty( $search_query ) ) {
2864 // Filter post types based on the search query.
2865 $registered_post_types = array_filter(
2866 $registered_post_types,
2867 function ( $post_type ) use ( $search_query ) {
2868 return stripos( $post_type->labels->singular_name, $search_query ) !== false;
2869 }
2870 );
2871 // Check if 'post' match the search query and include them if they do.
2872 if ( stripos( 'post', $search_query ) !== false ) {
2873 $post_types[] = [
2874 'value' => 'post',
2875 'label' => 'Post',
2876 ];
2877 }
2878 } else {
2879 $post_types[] = [
2880 'value' => 'post',
2881 'label' => 'Post',
2882 ];
2883 }
2884
2885 // Convert the filtered post types to an array.
2886 foreach ( $registered_post_types as $post_type ) {
2887 $post_types[] = [
2888 'value' => $post_type->name,
2889 'label' => $post_type->labels->singular_name,
2890 ];
2891 }
2892
2893 return $post_types;
2894 }
2895
2896 /**
2897 * Get Post Types.
2898 *
2899 * @param string $search_query Search query.
2900 * @param array $args Arguments.
2901 *
2902 * @return array
2903 */
2904 public static function get_post_types( $search_query = null, $args = [] ) {
2905
2906 $args = wp_parse_args(
2907 $args,
2908 [
2909 'post_type' => 'any',
2910 'posts_per_page' => 20,
2911 'orderby' => 'ID',
2912 'order' => 'DESC',
2913 ]
2914 );
2915
2916 if ( 'archive' !== $args['post_type'] ) {
2917 if ( $search_query ) {
2918 $args['s'] = $search_query;
2919 }
2920 $posts = [];
2921 $query_results = get_posts( $args );
2922 foreach ( $query_results as $post ) {
2923 $posts[] = [
2924 'value' => $post->ID,
2925 'label' => $post->post_title . ' (ID#' . $post->ID . ')',
2926 ];
2927 }
2928
2929 if ( $search_query ) {
2930 if ( strpos( '-error 404', $search_query ) ) {
2931 $posts[] = [
2932 'value' => 'error',
2933 'label' => __( '404 Error', 'shopbuilder' ),
2934 ];
2935 }
2936 } else {
2937 if ( 'page' === $args['post_type'] ) {
2938 $posts[] = [
2939 'value' => 'error',
2940 'label' => __( '404 Error', 'shopbuilder' ),
2941 ];
2942 }
2943 }
2944 } else {
2945 $posts = self::get_registered_post_types( $search_query );
2946 }
2947
2948 return $posts;
2949 }
2950
2951 /**
2952 * Get all Elementor breakpoints.
2953 *
2954 * @return array
2955 */
2956 public static function get_elementor_breakpoints() {
2957 return ( new \Elementor\Core\Breakpoints\Manager() )->get_breakpoints_config();
2958 }
2959
2960 /**
2961 * Render view.
2962 *
2963 * @param string $viewName View name.
2964 * @param array $args View args.
2965 * @param boolean $return View return.
2966 * @return string|void
2967 */
2968 public static function renderView( $viewName, $args = [], $return = false ) {
2969 $viewName = str_replace( '.', '/', $viewName );
2970
2971 if ( ! empty( $args ) && is_array( $args ) ) {
2972 extract( $args ); // phpcs:ignore WordPress.PHP.DontExtract.extract_extract
2973 }
2974
2975 $view_file = rtsb()->plugin_path() . '/resources/' . $viewName . '.php';
2976
2977 if ( ! file_exists( $view_file ) ) {
2978 _doing_it_wrong( __FUNCTION__, sprintf( '<code>%s</code> does not exist.', esc_html( $view_file ) ), '1.7.0' );
2979
2980 return;
2981 }
2982
2983 if ( $return ) {
2984 ob_start();
2985 include $view_file;
2986
2987 return ob_get_clean();
2988 } else {
2989 include $view_file;
2990 }
2991 }
2992
2993 /**
2994 * Best selling product query.
2995 *
2996 * @param int $minimum_sale Minimum sale.
2997 * @param int $limit Total limit.
2998 *
2999 * @return array|mixed
3000 */
3001 public static function best_selling_products_ids( $minimum_sale = 1, $limit = 10 ) {
3002 $cache_key = 'rtsb_best_selling_products_' . $minimum_sale . '_' . $limit;
3003 // Check if the data is in the cache.
3004 if ( isset( self::$cache[ $cache_key ] ) ) {
3005 return self::$cache[ $cache_key ];
3006 }
3007 $cached_data = get_transient( $cache_key );
3008 if ( $cached_data ) {
3009 self::$cache[ $cache_key ] = $cached_data;
3010 return $cached_data;
3011 }
3012 global $wpdb;
3013 $sql = $wpdb->prepare(
3014 "
3015 SELECT ID
3016 FROM {$wpdb->posts} AS p
3017 LEFT JOIN {$wpdb->postmeta} AS pm ON p.ID = pm.post_id
3018 WHERE p.post_type = 'product'
3019 AND p.post_status = 'publish'
3020 AND pm.meta_key = 'total_sales'
3021 AND pm.meta_value >= %d
3022 ORDER BY pm.meta_value + 0 DESC
3023 LIMIT %d
3024 ",
3025 $minimum_sale,
3026 $limit
3027 );
3028 $best_selling = $wpdb->get_col( $sql ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared
3029 // Cache the result for future use.
3030 set_transient( $cache_key, $best_selling, DAY_IN_SECONDS );
3031 self::$cache[ $cache_key ] = $best_selling;
3032 return $best_selling;
3033 }
3034
3035 /**
3036 * @param null|Object $product Product Object.
3037 * @param int $days_threshold Date String.
3038 *
3039 * @return bool
3040 */
3041 public static function is_new_product( $product = null, $days_threshold = 30 ) {
3042 if ( is_null( $product ) ) {
3043 global $product;
3044 }
3045 if ( ! $product instanceof WC_Product ) {
3046 return false;
3047 }
3048
3049 $product_id = $product->get_id();
3050 $days_threshold = ! empty( $days_threshold ) ? $days_threshold : 30;
3051 $cache_key = 'is_new_product_' . $product_id . '_' . $days_threshold;
3052
3053 if ( isset( self::$cache[ $cache_key ] ) ) {
3054 return self::$cache[ $cache_key ];
3055 }
3056 $date_created = $product->get_date_created();
3057 if ( ! $date_created ) {
3058 return false;
3059 }
3060 $publish_timestamp = strtotime( $date_created->date_i18n( 'Y-m-d H:i:s' ) );
3061
3062 // Calculate the difference in days.
3063 $days_difference = abs( time() - $publish_timestamp ) / ( 60 * 60 * 24 );
3064
3065 // Check if the product is considered new.
3066 $is_new = $days_difference <= $days_threshold;
3067
3068 // Cache the result for a day.
3069 self::$cache[ $cache_key ] = $is_new;
3070
3071 return $is_new;
3072 }
3073
3074 /**
3075 * Checks if a feature is disabled for guest users based on the provided option.
3076 *
3077 * @param string $option The option to retrieve from the item.
3078 * @param mixed $default The default value if the option is not found.
3079 *
3080 * @return bool
3081 */
3082 public static function is_guest_feature_disabled( $option, $default ) {
3083 $disabled = self::get_option( 'general', 'guest_user', $option, $default );
3084
3085 return ! is_user_logged_in() && $disabled;
3086 }
3087
3088 /**
3089 * Checks if a feature is disabled for guest users based on the provided option.
3090 *
3091 * @return void
3092 */
3093 public static function woocommerce_output_all_notices() {
3094 if ( function_exists( 'wc_print_notices' ) ) :
3095 echo '<div class="rtsb-notice">';
3096 woocommerce_output_all_notices();
3097 echo '</div>';
3098 endif;
3099 }
3100
3101 /**
3102 * @param object $product Product.
3103 * @return bool
3104 */
3105 public static function is_visible_qty_input( $product = null ) {
3106 $is_visible_qty = true;
3107 if ( $product instanceof \WC_Product ) {
3108 if ( $product->is_sold_individually() ) {
3109 $is_visible_qty = false;
3110 } elseif ( $product->managing_stock() ) {
3111 if ( in_array( $product->get_backorders(), [ 'notify' , 'yes' ], true ) ) {
3112 $is_visible_qty = true;
3113 } elseif ( $product->get_stock_quantity() < 2 ) {
3114 $is_visible_qty = false;
3115 }
3116 }
3117 }
3118 return $is_visible_qty;
3119 }
3120
3121 /**
3122 * @param int $object_id Object id.
3123 * @param string $element_type element type.
3124 * @param string|null $current_lang null for current language, default for default languages.
3125 * @return false|mixed|null
3126 */
3127 public static function wpml_object_id( $object_id, $element_type, $current_lang = 'default' ) {
3128 if ( ! defined( 'ICL_SITEPRESS_VERSION' ) ) {
3129 return $object_id;
3130 }
3131 if ( ! $object_id || ! $element_type ) {
3132 return $object_id;
3133 }
3134 if ( 'default' === $current_lang ) {
3135 $lang = apply_filters( 'wpml_default_language', null );
3136 } else {
3137 $lang = null;
3138 }
3139 return apply_filters( 'wpml_object_id', $object_id, $element_type, false, $lang );
3140 }
3141
3142 /**
3143 * @return string
3144 */
3145 public static function wpml_current_language() {
3146 $current = apply_filters( 'wpml_current_language', null );
3147 $default = apply_filters( 'wpml_default_language', null );
3148 return $default !== $current ? $current : null;
3149 }
3150
3151 /**
3152 * Custom icons.
3153 *
3154 * @return string[]
3155 */
3156 public static function get_custom_icon_names() {
3157 return [
3158 'heart-empty',
3159 'heart',
3160 'eye',
3161 'exchange',
3162 'plus',
3163 'minus',
3164 'avatar',
3165 'pay',
3166 'share',
3167 'clock',
3168 'check-alt',
3169 'check',
3170 'delete',
3171 'marker',
3172 'list',
3173 'list-2',
3174 'power',
3175 'cart',
3176 'cart-2',
3177 'cart-3',
3178 'downloads',
3179 'zoom',
3180 'user-edit',
3181 'grid',
3182 'filter',
3183 'billing',
3184 'login',
3185 'payment',
3186 'search',
3187 'edit',
3188 'coupon',
3189 'arrows-cw',
3190 'trash-empty',
3191 ];
3192 }
3193
3194 /**
3195 * Retrieve an array of custom icons with their HTML representation.
3196 *
3197 * @return array
3198 */
3199 public static function get_icons() {
3200 $icon = self::get_custom_icon_names();
3201 $icons_array = [];
3202 foreach ( $icon as $value ) {
3203 $icons_array[ $value ] = '<i class="rtsb-icon rtsb-icon-' . $value . '"></i> <span class="icon-name">' . ucfirst( str_replace( '-', ' ', $value ) ) . '</span>';
3204 }
3205 return $icons_array;
3206 }
3207
3208 /**
3209 * Generates HTML markup for displaying an endpoint icon.
3210 *
3211 * @param array $data The endpoint data containing icon information.
3212 * @param boolean $wrapper Whether to wrap the icon in a span element.
3213 *
3214 * @return string
3215 */
3216 public static function get_icon_html( $data, $wrapper = true ) {
3217 if ( empty( $data ) || 'none' === $data['icon_source'] ) {
3218 return '';
3219 }
3220 $endpoint = '';
3221 $html = $wrapper ? '<span class="icon ' . esc_attr( $endpoint ) . '_icon">' : '';
3222
3223 if ( 'select_icon' === $data['icon_source'] ) {
3224 $html .= '<i class="rtsb-icon rtsb-icon-' . esc_attr( $data['custom_icon'] ) . '"></i>';
3225 } else {
3226 $icon_html = '';
3227 $icon_url = $data['image_icon']['source'] ?? '';
3228 $icon_id = $data['image_icon']['id'] ?? 0;
3229 $extension = ! empty( $icon_url ) ? strtolower( substr( strrchr( $data['image_icon']['source'], '.' ), 1 ) ) : '';
3230
3231 if ( 'svg' === $extension ) {
3232 $content = file_get_contents( $icon_url ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
3233 $is_svg = strpos( $content, '<svg' );
3234
3235 if ( false !== $is_svg ) {
3236 $icon_html = substr( $content, $is_svg );
3237 }
3238 } else {
3239 $attr = [
3240 'class' => 'rtsb-icon-img',
3241 'alt' => esc_html( ucfirst( $endpoint ) ) . esc_html__( ' Icon', 'shopbuilder' ),
3242 ];
3243
3244 $icon_html = wp_get_attachment_image( absint( $icon_id ), 'full', true, $attr );
3245 }
3246
3247 $html .= $icon_html;
3248 }
3249
3250 $html .= $wrapper ? '</span>' : '';
3251
3252 return $html;
3253 }
3254 /**
3255 * Flushes rewrite rules.
3256 *
3257 * @return void
3258 */
3259 public static function maybe_flush_rewrite_rules() {
3260 add_action( 'wp_loaded', [ __CLASS__, 'flush_rewrite_rules' ] );
3261 add_action( 'shutdown', [ __CLASS__, 'flush_rewrite_rules_once_more' ] );
3262 }
3263
3264 /**
3265 * Flushes rewrite rules if needed.
3266 *
3267 * @return void
3268 */
3269 public static function flush_rewrite_rules() {
3270 if ( get_option( 'rtsb_permalinks_need_flush' ) === 'yes' ) {
3271 flush_rewrite_rules();
3272 }
3273
3274 if ( get_option( 'rtsb_permalinks_flushed' ) === 'yes' ) {
3275 flush_rewrite_rules();
3276 delete_option( 'rtsb_permalinks_flushed' );
3277 }
3278 }
3279
3280 /**
3281 * Flushes rewrite rules if needed for second time.
3282 *
3283 * @return void
3284 */
3285 public static function flush_rewrite_rules_once_more() {
3286 if ( get_option( 'rtsb_permalinks_need_flush' ) === 'yes' ) {
3287 flush_rewrite_rules();
3288 update_option( 'rtsb_permalinks_flushed', 'yes' );
3289 delete_option( 'rtsb_permalinks_need_flush' );
3290 }
3291 }
3292
3293 /**
3294 * Social Share Platforms.
3295 *
3296 * @param string $section_id Section ID.
3297 * @param string $block_id Block ID.
3298 * @param string $option_key Option key.
3299 * @param string $compare_key Compare key.
3300 * @param string $compare_value Compare value.
3301 * @param array $values Values.
3302 *
3303 * @return void
3304 */
3305 public static function set_repeater_options( $section_id, $block_id, $option_key, $compare_key, $compare_value, $values ) {
3306 $modules = DataModel::source()->get_option( $section_id, [], false );
3307 if ( empty( $modules[ $block_id ] ) ) {
3308 return;
3309 }
3310 $options = $modules[ $block_id ];
3311 if ( empty( $options[ $option_key ] ) ) {
3312 return;
3313 }
3314 $repeater_options = json_decode( $options[ $option_key ], true );
3315 // Check if options are not empty and are in array format.
3316 if ( ! is_array( $repeater_options ) ) {
3317 return;
3318 }
3319 // Use array_column to get an array of values from $compare_key.
3320 $column_values = array_column( $repeater_options, $compare_key );
3321 // Use array_search to find the index of the $compare_value.
3322 $index = array_search( $compare_value, $column_values, true );
3323 if ( false === $index ) {
3324 return;
3325 }
3326 $repeater_options[ $index ] = wp_parse_args( $values, $repeater_options[ $index ] );
3327 $options[ $option_key ] = wp_json_encode( $repeater_options );
3328 $modules[ $block_id ] = $options;
3329 DataModel::source()->set_option( $section_id, $modules );
3330 }
3331
3332
3333 /**
3334 * @param array $ids products id.
3335 * @return array
3336 */
3337 public static function get_available_products_by_ids( $ids = [] ) {
3338 $ids = array_filter(
3339 $ids,
3340 function ( $id ) {
3341 return 'product' === get_post_type( $id ) && 'publish' === get_post_status( $id );
3342 }
3343 );
3344 return $ids;
3345 }
3346
3347 /**
3348 * Generate and cache dynamic CSS styles.
3349 *
3350 * @param array $options CSS options to apply (e.g. padding, margin).
3351 * @param string $cache_key Cache key for the CSS.
3352 * @param array $css_properties An array of CSS properties with selectors and properties.
3353 *
3354 * @return void
3355 */
3356 public static function dynamic_styles( $options, $cache_key, $css_properties ) {
3357 $cached_css = wp_cache_get( $cache_key, 'shopbuilder' );
3358 $style_handle = self::optimized_handle( 'rtsb-frontend' );
3359
3360 if ( false !== $cached_css ) {
3361 wp_add_inline_style( $style_handle, $cached_css );
3362 return;
3363 }
3364
3365 $css_rules = [];
3366 $grouped_css_rules = [];
3367
3368 foreach ( $css_properties as $option => $details ) {
3369 if ( ! empty( $options[ $option ] ) ) {
3370 $selector = $details['selector'] ?? '';
3371 $property = $details['property'] ?? '';
3372 $unit = $details['unit'] ?? '';
3373 $value = $options[ $option ];
3374
3375 if ( empty( $grouped_css_rules[ $selector ] ) ) {
3376 $grouped_css_rules[ $selector ] = [];
3377 }
3378
3379 if ( is_array( $property ) ) {
3380 foreach ( $property as $prop ) {
3381 $grouped_css_rules[ $selector ][] = $prop . ': ' . $value . $unit . ' !important';
3382 }
3383 } else {
3384 $grouped_css_rules[ $selector ][] = $property . ': ' . $value . $unit . ' !important';
3385 }
3386 }
3387 }
3388
3389 foreach ( $grouped_css_rules as $selector => $properties ) {
3390 $css_rules[] = $selector . ' {' . implode( '; ', $properties ) . '}';
3391 }
3392
3393 $dynamic_css = implode( ' ', $css_rules );
3394
3395 wp_cache_set( $cache_key, $dynamic_css, 'shopbuilder', 12 * HOUR_IN_SECONDS );
3396 Cache::set_data_cache_key( $cache_key );
3397
3398 if ( ! empty( $dynamic_css ) ) {
3399 wp_add_inline_style( $style_handle, $dynamic_css );
3400 }
3401 }
3402
3403 /**
3404 * Pro version notice.
3405 *
3406 * @param string $ver Pro Version.
3407 * @param string $tab Tab.
3408 * @param string $name Name.
3409 * @param bool $enable_free_Link Enable free link.
3410 *
3411 * @return array[]
3412 */
3413 public static function pro_version_notice( $ver, $tab = 'billing_fields', $name = 'ShopBuilder', $enable_free_Link = true ) {
3414 return [
3415 'version_check' => [
3416 'id' => 'version_check',
3417 'type' => 'title',
3418 'label' => sprintf(
3419 /* translators: 1: required version, 2: link to the Plugins page */
3420 esc_html__(
3421 'To access all features and settings of this module, please ensure that "%1$s" is updated to version %2$s. %3$s',
3422 'shopbuilder'
3423 ),
3424 $name,
3425 sprintf(
3426 '<br /><u><b>%s</b></u>',
3427 esc_html( $ver ?? '1.5.0' ) . ' or higher'
3428 ),
3429 $enable_free_Link
3430 ? sprintf(
3431 '%s <a class="pro-update-required" href="%s" target="_blank" title="%s">%s</a>',
3432 esc_attr__( 'Update to the latest version', 'shopbuilder' ),
3433 esc_url( admin_url( 'plugins.php' ) ),
3434 esc_attr__( 'Go to the Plugin Page', 'shopbuilder' ),
3435 esc_html__( 'from the Plugins page', 'shopbuilder' )
3436 )
3437 : ''
3438 ),
3439 'tab' => $tab,
3440 'customClass' => 'checkout-notice',
3441 ],
3442 ];
3443 }
3444
3445 /**
3446 * Get product gallery ids.
3447 *
3448 * @param object $product Product object.
3449 *
3450 * @return mixed
3451 */
3452 public static function get_cached_gallery_ids( $product ) {
3453 $product_id = $product->get_id();
3454 $cache_key = 'rtsb_product_gallery_ids_' . $product_id;
3455
3456 if ( isset( self::$cache[ $cache_key ] ) ) {
3457 return self::$cache[ $cache_key ];
3458 }
3459
3460 $cached_result = wp_cache_get( $cache_key, 'shopbuilder' );
3461
3462 if ( false !== $cached_result ) {
3463 self::$cache[ $cache_key ] = $cached_result;
3464
3465 return $cached_result;
3466 }
3467
3468 $gallery_ids = $product->get_gallery_image_ids();
3469 self::$cache[ $cache_key ] = $gallery_ids;
3470
3471 wp_cache_set( $cache_key, $gallery_ids, 'shopbuilder', 12 * HOUR_IN_SECONDS );
3472 Cache::set_data_cache_key( $cache_key );
3473
3474 return $gallery_ids;
3475 }
3476
3477 /**
3478 * Check if optimization settings are enabled.
3479 *
3480 * @return bool
3481 */
3482 public static function is_optimization_enabled() {
3483 $has_pro = rtsb()->has_pro();
3484 $pro_ver = defined( 'RTSBPRO_VERSION' ) ? RTSBPRO_VERSION : 0;
3485
3486 if ( $has_pro && version_compare( $pro_ver, '2.0.0', '<' ) ) {
3487 return false;
3488 }
3489
3490 $generalList = GeneralList::instance()->get_data();
3491
3492 return 'on' === ( $generalList['optimization']['enable_optimization'] ?? '' );
3493 }
3494
3495 /**
3496 * Get the optimized handle.
3497 *
3498 * @param string $handle Base handle used for script/style IDs.
3499 *
3500 * @return string
3501 */
3502 public static function optimized_handle( $handle ) {
3503 $use_optimization = self::is_optimization_enabled();
3504
3505 if ( $use_optimization ) {
3506 $handle = self::is_contextual_loading() ? self::get_optimized_handle_by_context() : 'rtsb-bundled';
3507 }
3508
3509 return $handle;
3510 }
3511
3512 /**
3513 * Get the optimized handle by context.
3514 *
3515 * @return string
3516 */
3517 public static function get_optimized_handle_by_context() {
3518 $context = self::detect_context();
3519
3520 if ( 'account' === $context ) {
3521 return 'rtsb-bundled-global';
3522 }
3523
3524 return "rtsb-bundled-$context";
3525 }
3526
3527 /**
3528 * Check if Elementor page.
3529 *
3530 * @param int $post_id Post ID.
3531 *
3532 * @return bool
3533 */
3534 public static function is_elementor_page( $post_id = null ) {
3535 if ( ! defined( 'ELEMENTOR_VERSION' ) ) {
3536 return false;
3537 }
3538
3539 $post_id = $post_id ?: get_the_ID();
3540
3541 if ( ! $post_id ) {
3542 return true;
3543 }
3544
3545 return Plugin::$instance->documents->get( $post_id )->is_built_with_elementor();
3546 }
3547
3548 /**
3549 * Check if shop or archive.
3550 *
3551 * @return bool
3552 */
3553 public static function is_shop_or_archive() {
3554 return is_shop() || is_product_category() || is_product_tag() || is_post_type_archive( 'product' ) || BuilderFns::is_archive() || BuilderFns::is_shop() || is_tax( 'product_brand' );
3555 }
3556
3557 /**
3558 * Detect context.
3559 *
3560 * @return string
3561 */
3562 public static function detect_context() {
3563 switch ( true ) {
3564 case is_product() || BuilderFns::is_product():
3565 $context = 'product';
3566 break;
3567
3568 case is_cart() || BuilderFns::is_cart():
3569 $context = 'cart';
3570 break;
3571
3572 case is_checkout() || BuilderFns::is_checkout():
3573 $context = 'checkout';
3574 break;
3575
3576 case self::is_shop_or_archive():
3577 $context = 'shop';
3578 break;
3579
3580 default:
3581 $context = 'global';
3582 break;
3583 }
3584
3585 return apply_filters( 'rtsb/optimizer/context_detect', $context );
3586 }
3587
3588 /**
3589 * Enqueue optimized assets.
3590 *
3591 * @return void
3592 */
3593 public static function enqueue_optimized_assets() {
3594 $asset_registry = AssetRegistry::instance();
3595 $bundled_assets = $asset_registry->get_bundled_assets();
3596 $context = self::detect_context();
3597 $contextual_loading = self::is_contextual_loading();
3598 $theme_handle = self::find_theme_stylesheet_handle();
3599
3600 if ( $contextual_loading ) {
3601 // Enqueue JS.
3602 if ( ! empty( $bundled_assets['js'] ) ) {
3603 $js_context = isset( $bundled_assets['js'][ $context ] ) ? $context : 'global';
3604
3605 if ( $js_context ) {
3606 wp_enqueue_script( $bundled_assets['js'][ $js_context ]['handle'] );
3607 }
3608 }
3609
3610 // Enqueue CSS.
3611 if ( ! empty( $bundled_assets['css'] ) ) {
3612 $css_context = isset( $bundled_assets['css'][ $context ] ) ? $context : 'global';
3613
3614 if ( $css_context ) {
3615 $handle = $bundled_assets['css'][ $css_context ]['handle'];
3616
3617 wp_enqueue_style( $handle );
3618
3619 if ( ! empty( $theme_handle ) ) {
3620 self::set_theme_dependency( $theme_handle, $handle );
3621 }
3622 }
3623 }
3624 } else {
3625 // Enqueue JS.
3626 if ( ! empty( $bundled_assets['js'] ) ) {
3627 wp_enqueue_script( $bundled_assets['js']['handle'] );
3628 }
3629
3630 // Enqueue CSS.
3631 if ( ! empty( $bundled_assets['css'] ) ) {
3632 $handle = $bundled_assets['css']['handle'];
3633
3634 wp_enqueue_style( $handle );
3635
3636 if ( ! empty( $theme_handle ) ) {
3637 self::set_theme_dependency( $theme_handle, $handle );
3638 }
3639 }
3640 }
3641 }
3642
3643 /**
3644 * Find theme stylesheet handle.
3645 *
3646 * @return string|false
3647 */
3648 private static function find_theme_stylesheet_handle() {
3649 static $cached_handle = null;
3650
3651 if ( null !== $cached_handle ) {
3652 return $cached_handle;
3653 }
3654
3655 global $wp_styles;
3656
3657 if ( ! $wp_styles instanceof WP_Styles ) {
3658 return false;
3659 }
3660
3661 $stylesheet_uri = get_stylesheet_uri();
3662 $theme_directory_uri = get_stylesheet_directory_uri();
3663
3664 $hash = md5( $stylesheet_uri );
3665 $transient_key = 'rtsb_theme_css_handle_' . $hash;
3666
3667 $transient = get_transient( $transient_key );
3668
3669 if ( false !== $transient ) {
3670 $cached_handle = $transient;
3671
3672 return $transient;
3673 }
3674
3675 foreach ( $wp_styles->registered as $handle => $style ) {
3676 $src = $style->src;
3677
3678 if (
3679 ( $src === $stylesheet_uri || strpos( $src, $theme_directory_uri ) !== false ) &&
3680 strpos( $src, 'style.css' ) !== false
3681 ) {
3682 set_transient( $transient_key, $handle, DAY_IN_SECONDS );
3683 Cache::set_transient_cache_key( $transient_key );
3684 $cached_handle = $handle;
3685
3686 return $handle;
3687 }
3688 }
3689
3690 $filtered_handle = apply_filters( 'rtsb/optimizer/theme_stylesheet_handle', false );
3691
3692 if ( is_string( $filtered_handle ) && ! empty( $filtered_handle ) ) {
3693 set_transient( $transient_key, $filtered_handle, DAY_IN_SECONDS );
3694 Cache::set_transient_cache_key( $transient_key );
3695 $cached_handle = $filtered_handle;
3696
3697 return $filtered_handle;
3698 }
3699
3700 set_transient( $transient_key, false, DAY_IN_SECONDS );
3701 Cache::set_transient_cache_key( $transient_key );
3702 $cached_handle = false;
3703
3704 return false;
3705 }
3706
3707 /**
3708 * Set theme dependency.
3709 *
3710 * @param string $theme_handle Theme handle.
3711 * @param string $our_handle Our handle.
3712 *
3713 * @return void
3714 */
3715 private static function set_theme_dependency( $theme_handle, $our_handle ) {
3716 global $wp_styles;
3717
3718 if ( ! isset( $wp_styles->registered[ $theme_handle ] ) ) {
3719 return;
3720 }
3721
3722 $theme_style = $wp_styles->registered[ $theme_handle ];
3723
3724 // Add our handle as a dependency if it's not already there.
3725 if ( ! in_array( $our_handle, $theme_style->deps, true ) ) {
3726 $theme_style->deps[] = $our_handle;
3727 }
3728 }
3729
3730 /**
3731 * Check if Elementor scripts should be loaded.
3732 *
3733 * @return bool
3734 */
3735 public static function should_load_elementor_scripts() {
3736 $data = GeneralList::instance()->get_data()['optimization'] ?? [];
3737
3738 $enable_optimization = $data['enable_optimization'] ?? 'on';
3739 $load_elementor_scripts = $data['load_elementor_scripts'] ?? 'on';
3740
3741 if ( 'on' !== $enable_optimization ) {
3742 return true;
3743 }
3744
3745 return 'on' === $load_elementor_scripts;
3746 }
3747
3748 /**
3749 * Locate asset.
3750 *
3751 * @param string $relative_path Relative path.
3752 *
3753 * @return string|null
3754 */
3755 public static function locate_asset( $relative_path ) {
3756 $free_context = rtsb();
3757 $pro_context = function_exists( 'rtsbpro' ) && rtsb()->has_pro() ? rtsbpro() : null;
3758
3759 $paths = [];
3760
3761 if ( $pro_context ) {
3762 $paths[] = $pro_context->get_assets_path( $relative_path );
3763 }
3764
3765 $paths[] = $free_context->get_assets_path( $relative_path );
3766
3767 foreach ( $paths as $path ) {
3768 if ( file_exists( $path ) ) {
3769 return $path;
3770 }
3771 }
3772
3773 return null;
3774 }
3775
3776 /**
3777 * Enqueue module assets.
3778 *
3779 * @param string $handle Asset handle.
3780 * @param string $module_name Module name.
3781 * @param array $options Options array: ['type' => 'css|js|both', 'deps' => [], 'context' => null].
3782 *
3783 * @return string
3784 */
3785 public static function enqueue_module_assets( $handle, $module_name, $options = [] ) {
3786 $use_optimization = self::is_optimization_enabled();
3787 $handle = self::optimized_handle( $handle );
3788
3789 if ( $use_optimization ) {
3790 return $handle;
3791 }
3792
3793 $defaults = [
3794 'type' => 'both',
3795 'deps' => [ 'jquery', 'rtsb-public' ],
3796 'context' => null,
3797 'version' => RTSB_VERSION,
3798 ];
3799
3800 $config = array_merge( $defaults, $options );
3801 $rtl_suffix = is_rtl() ? '-rtl' : '';
3802 $rtl_dir = is_rtl() ? trailingslashit( 'rtl' ) : trailingslashit( 'css' );
3803 $context = $config['context'] ?: rtsb();
3804 $load_css = in_array( $config['type'], [ 'css', 'both' ], true );
3805 $load_js = in_array( $config['type'], [ 'js', 'both' ], true );
3806
3807 // Register CSS if enabled.
3808 if ( $load_css ) {
3809 wp_register_style(
3810 $handle,
3811 $context->get_assets_uri( $rtl_dir . 'modules/' . $module_name . $rtl_suffix . '.css' ),
3812 [],
3813 $config['version']
3814 );
3815 }
3816
3817 // Register JS if enabled.
3818 if ( $load_js ) {
3819 wp_register_script(
3820 $handle,
3821 $context->get_assets_uri( 'js/modules/' . $module_name . '.js' ),
3822 $config['deps'],
3823 $config['version'],
3824 true
3825 );
3826 }
3827
3828 if ( $load_css ) {
3829 wp_enqueue_style( $handle );
3830
3831 $theme_handle = self::find_theme_stylesheet_handle();
3832
3833 if ( ! empty( $theme_handle ) ) {
3834 self::set_theme_dependency( $theme_handle, $handle );
3835 }
3836 }
3837
3838 if ( $load_js ) {
3839 wp_enqueue_script( $handle );
3840 }
3841
3842 return $handle;
3843 }
3844
3845 /**
3846 * Check if contextual loading is enabled.
3847 *
3848 * @return bool
3849 */
3850 public static function is_contextual_loading() {
3851 $data = GeneralList::instance()->get_data()['optimization'] ?? [];
3852
3853 return ! empty( $data['context_asset_loading'] ) && 'on' === $data['context_asset_loading'];
3854 }
3855
3856 /**
3857 * Get Modules list with cache support.
3858 *
3859 * @return array
3860 */
3861 public static function get_modules_list() {
3862 static $cached_modules = null;
3863
3864 if ( null !== $cached_modules ) {
3865 return $cached_modules;
3866 }
3867
3868 $cache_key = 'rtsb_module_list';
3869 $cache_group = 'shopbuilder';
3870
3871 $cached = wp_cache_get( $cache_key, $cache_group );
3872
3873 if ( false !== $cached ) {
3874 $cached_modules = $cached;
3875
3876 return $cached;
3877 }
3878
3879 $modules = ModuleList::instance()->get_data();
3880
3881 wp_cache_set( $cache_key, $modules, $cache_group, 12 * HOUR_IN_SECONDS );
3882 Cache::set_data_cache_key( $cache_key );
3883
3884 $cached_modules = $modules;
3885
3886 return $modules;
3887 }
3888
3889 /**
3890 * Get Elementor widgets list with cache support.
3891 *
3892 * @return array
3893 */
3894 public static function get_widgets_list() {
3895 static $cached_widgets = null;
3896
3897 if ( null !== $cached_widgets ) {
3898 return $cached_widgets;
3899 }
3900
3901 $cache_key = 'rtsb_elementor_widget_list';
3902 $cache_group = 'shopbuilder';
3903
3904 $cached = wp_cache_get( $cache_key, $cache_group );
3905
3906 if ( false !== $cached ) {
3907 $cached_widgets = $cached;
3908
3909 return $cached;
3910 }
3911
3912 $widgets = ElementList::instance()->get_list();
3913
3914 wp_cache_set( $cache_key, $widgets, $cache_group, 12 * HOUR_IN_SECONDS );
3915 Cache::set_data_cache_key( $cache_key );
3916
3917 $cached_widgets = $widgets;
3918
3919 return $widgets;
3920 }
3921
3922 /**
3923 * Convert the given price to the active currency.
3924 *
3925 * @param float $price The original price.
3926 * @param Object||null $product Product.
3927 *
3928 * @return float
3929 */
3930 public static function get_currency_base_price( $price, $product = null ) {
3931 return apply_filters( 'rtsb/convert/currency/price', $price, $product );
3932 }
3933 }
3934