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

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

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