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

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

4,062 lines 116.7 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 /**
2484 * Elementor Widget Active.
2485 *
2486 * @param string $widget Elementor Widget.
2487 *
2488 * @return true|void
2489 */
2490 public static function is_elementor_widget_active( $widget ) {
2491 $element_list = self::get_widgets_list();
2492
2493 if ( ! empty( $element_list[ $widget ]['active'] ) ) {
2494 return true;
2495 }
2496 }
2497
2498 /***
2499 * Save Settings data
2500 *
2501 * @param string $section_id Section ID.
2502 * @param string $block_id Block ID.
2503 * @param array $rawOptions Raw Options.
2504 *
2505 * @return array
2506 */
2507 public static function set_options( $section_id = '', $block_id = '', $rawOptions = [] ) { // phpcs:ignore Generic.Metrics.NestingLevel.TooHigh
2508 $section_id = ! empty( $section_id ) ? sanitize_text_field( wp_unslash( $section_id ) ) : '';
2509 $block_id = ! empty( $block_id ) ? sanitize_text_field( wp_unslash( $block_id ) ) : '';
2510 $rawOptions = ! empty( $rawOptions ) ? $rawOptions : [];
2511 $results = [
2512 'status' => true,
2513 'message' => '',
2514 ];
2515 if ( ! $section_id || ! $block_id ) {
2516 $results['status'] = false;
2517 $results['message'] = esc_html__( 'Section , block or options may be empty', 'shopbuilder' );
2518
2519 return $results;
2520 }
2521 $sections = Settings::instance()->get_sections();
2522 if ( empty( $sections[ $section_id ] ) || empty( $sections[ $section_id ]['list'][ $block_id ] ) ) {
2523 $results['status'] = false;
2524 $results['message'] = esc_html__( 'No section or block found with given data', 'shopbuilder' );
2525
2526 return $results;
2527 }
2528
2529 $options = DataModel::source()->get_option( $section_id, [], false );
2530 $changed = false;
2531 $fields = [];
2532 if ( isset( $sections[ $section_id ]['list'][ $block_id ]['fields'] ) && ! empty( $sections[ $section_id ]['list'][ $block_id ]['fields'] ) ) {
2533 $fields = $sections[ $section_id ]['list'][ $block_id ]['fields'];
2534 }
2535 do_action( 'rtsb/before/save/options', $section_id, $block_id, $rawOptions );
2536 if ( empty( $fields ) ) {
2537 if ( isset( $rawOptions['active'] ) ) {
2538 $changed = true;
2539 $options[ $block_id ]['active'] = 'on' === $rawOptions['active'] ? 'on' : '';
2540 }
2541 } else {
2542 foreach ( $rawOptions as $raw_option_key => $raw_value ) {
2543 if ( 'active' === $raw_option_key ) {
2544 $changed = true;
2545 $options[ $block_id ]['active'] = $sections[ $section_id ]['list'][ $block_id ]['active'] = 'on' === $raw_value ? 'on' : ''; // phpcs:ignore Squiz.PHP.DisallowMultipleAssignments.Found
2546 } else {
2547 if ( isset( $fields[ $raw_option_key ] ) ) {
2548 $field = $fields[ $raw_option_key ];
2549 if ( 'switch' === $field['type'] ) {
2550 $value = 'on' === $raw_value ? 'on' : '';
2551 } elseif ( 'repeaters' === $field['type'] ) {
2552 $the_value = [];
2553 $rep_title = [];
2554 if ( ! empty( $raw_value ) && is_array( $raw_value ) ) {
2555 foreach ( $raw_value as $key => $value ) {
2556 if ( is_string( $value ) ) {
2557 $the_value_decoded = json_decode( stripslashes_deep( $value ) );
2558 } else {
2559 $the_value_decoded = $value;
2560 }
2561 $cr = $the_value_decoded->title ?? '';
2562 if ( in_array( $cr, $rep_title, true ) ) {
2563 continue;
2564 }
2565 $rep_title[] = $cr;
2566 $the_value[] = $the_value_decoded;
2567 }
2568 } elseif ( ! empty( $raw_value ) && is_string( $raw_value ) ) {
2569 $the_value = $raw_value;
2570 }
2571 $value = wp_json_encode( $the_value, JSON_UNESCAPED_UNICODE );
2572 } elseif ( in_array( $field['type'], [ 'product_addons_special_settings', 'checkout_fields' ] ) ) { // phpcs:ignore WordPress.PHP.StrictInArray.MissingTrueStrict
2573 $manual_field_value = [];
2574 if ( is_array( $raw_value ) ) {
2575 foreach ( $raw_value as $key => $value ) {
2576 $manual_field_value[] = json_decode( stripslashes( $value ), true );
2577 }
2578 $value = wp_json_encode( $manual_field_value, JSON_UNESCAPED_UNICODE );
2579 } elseif ( is_string( $raw_value ) ) {
2580 $value = $raw_value;
2581 }
2582 } else {
2583 if ( ! empty( $field['multiple'] ) || in_array( $field['type'], [ 'checkbox', 'search_and_multi_select' ] ) ) { // phpcs:ignore WordPress.PHP.StrictInArray.MissingTrueStrict
2584 if ( isset( $raw_value ) && is_array( $raw_value ) ) {
2585 if ( ! empty( $field['sanitize_fn'] ) && is_callable( $field['sanitize_fn'] ) ) {
2586 $value = array_map( $field['sanitize_fn'], $raw_value );
2587 } else {
2588 $value = array_map( 'sanitize_text_field', $raw_value );
2589 }
2590 } else {
2591 $value = [];
2592 }
2593 } else {
2594 if ( ! empty( $field['sanitize_fn'] ) ) {
2595 if ( is_callable( $field['sanitize_fn'] ) ) {
2596 $value = $field['sanitize_fn']( $raw_value );
2597 } elseif ( 'pass_all' === $field['sanitize_fn'] ) {
2598 $value = $raw_value;
2599 } else {
2600 $value = $field['sanitize_fn']( $raw_value );
2601 }
2602 } else {
2603 $value = sanitize_text_field( $raw_value );
2604 }
2605 }
2606 }
2607 $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
2608 $changed = true;
2609 }
2610 }
2611 }
2612 }
2613 if ( ! $changed ) {
2614 $results['status'] = false;
2615 $results['message'] = esc_html__( 'No changes found for update', 'shopbuilder' );
2616
2617 return $results;
2618 }
2619
2620 DataModel::source()->set_option( $section_id, $options );
2621
2622 $results['status'] = true;
2623 $results['message'] = esc_html__( 'Successfully Saved', 'shopbuilder' );
2624 $results['sections'] = $sections;
2625
2626 return $results;
2627 }
2628
2629 /**
2630 * Count products by taxonomies.
2631 *
2632 * @param array $terms Terms.
2633 * @param string $taxonomy Taxonomy.
2634 *
2635 * @return int|void
2636 */
2637 public static function count_products_by_taxonomies( $terms, $taxonomy = 'product_cat' ) {
2638 if ( empty( $terms ) || ! is_array( $terms ) ) {
2639 return;
2640 }
2641
2642 $args = [
2643 'limit' => -1,
2644 'return' => 'ids',
2645 ];
2646
2647 if ( 'product_cat' === $taxonomy ) {
2648 $args['product_category_id'] = $terms;
2649 } elseif ( 'product_brand' === $taxonomy ) {
2650 $args['product_brand_id'] = $terms;
2651 } else {
2652 $args['product_tag_id'] = $terms;
2653 }
2654
2655 $query = new WC_Product_Query( $args );
2656
2657 return ! empty( $query->get_products() ) ? count( $query->get_products() ) : 0;
2658 }
2659
2660 /**
2661 * Count products by attribute terms.
2662 *
2663 * @param array $term_ids Term IDs.
2664 * @param string $relation Relation.
2665 *
2666 * @return int
2667 */
2668 public static function count_products_by_attribute_terms( $term_ids, $relation = 'AND' ) {
2669 if ( empty( $term_ids ) || ! is_array( $term_ids ) ) {
2670 return 0;
2671 }
2672
2673 $terms_by_taxonomy = [];
2674
2675 foreach ( $term_ids as $term_id ) {
2676 $term = get_term( $term_id );
2677 if ( ! is_wp_error( $term ) && $term ) {
2678 if ( ! isset( $terms_by_taxonomy[ $term->taxonomy ] ) ) {
2679 $terms_by_taxonomy[ $term->taxonomy ] = [];
2680 }
2681 $terms_by_taxonomy[ $term->taxonomy ][] = $term_id;
2682 }
2683 }
2684
2685 if ( empty( $terms_by_taxonomy ) ) {
2686 return 0;
2687 }
2688
2689 $args = [
2690 'status' => 'publish',
2691 'limit' => -1,
2692 'return' => 'ids',
2693 'tax_query' => [ // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query
2694 'relation' => strtoupper( $relation ),
2695 ],
2696 ];
2697
2698 foreach ( $terms_by_taxonomy as $taxonomy => $terms ) {
2699 $args['tax_query'][] = [
2700 'taxonomy' => $taxonomy,
2701 'field' => 'term_id',
2702 'terms' => $terms,
2703 'operator' => 'IN',
2704 ];
2705 }
2706
2707 $query = new WC_Product_Query( $args );
2708 $products = $query->get_products();
2709
2710 return count( $products );
2711 }
2712
2713 /**
2714 * Check if product filters widget has ajax.
2715 *
2716 * @param string $page Page name.
2717 *
2718 * @return bool
2719 */
2720 public static function product_filters_has_ajax( $page ) {
2721 if ( empty( $page ) ) {
2722 return false;
2723 }
2724
2725 if ( 'page' === get_post_type() ) {
2726 return false;
2727 }
2728
2729 $id = BuilderFns::is_builder_preview() ? get_the_ID() : BuilderFns::builder_page_id_by_type( $page );
2730 if ( ! $id ) {
2731 return false;
2732 }
2733 $cache_key = 'product_filters_has_ajax_' . $id;
2734 if ( isset( self::$cache[ $cache_key ] ) ) {
2735 return self::$cache[ $cache_key ];
2736 }
2737 $elmap = ElementorDataMap::instance();
2738 $ajax = [];
2739
2740 foreach ( $elmap->get_widget_data( 'rtsb-ajax-product-filters', [], $id ) as $data ) {
2741 $ajax[] = isset( $data['settings']['ajax_mode'] ) ? false : true;
2742 }
2743
2744 self::$cache[ $cache_key ] = ! empty( $ajax[0] );
2745 return ! empty( $ajax[0] );
2746 }
2747
2748 /**
2749 * Check if product has applied filter.
2750 *
2751 * @param string $page Page name.
2752 *
2753 * @return bool
2754 */
2755 public static function product_has_applied_filters( $page ) {
2756 if ( empty( $page ) ) {
2757 return false;
2758 }
2759
2760 $id = BuilderFns::is_builder_preview() ? get_the_ID() : BuilderFns::builder_page_id_by_type( $page );
2761 $elmap = ElementorDataMap::instance();
2762 $ajax = [];
2763 if ( ! $id ) {
2764 return false;
2765 }
2766 foreach ( $elmap->get_widget_data( 'rtsb-ajax-product-filters', [], $id ) as $data ) {
2767 $ajax[] = ! isset( $data['settings']['active_filter'] );
2768
2769 }
2770
2771 return ! empty( $ajax[0] );
2772 }
2773
2774 /**
2775 * Get WooCommerce product categories.
2776 *
2777 * @param string|null $search_query The search string for category names.
2778 *
2779 * @return array An array of product categories with 'value' and 'label' keys.
2780 */
2781 public static function products_category_query( $search_query = null ) {
2782 if ( ! is_admin() ) {
2783 return [];
2784 }
2785
2786 $cache_key = 'rtsb_product_categories_' . md5( serialize( $search_query ) ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_serialize
2787
2788 if ( isset( self::$cache[ $cache_key ] ) ) {
2789 return self::$cache[ $cache_key ];
2790 }
2791 $results = wp_cache_get( $cache_key, 'shopbuilder' );
2792 if ( ! $results ) {
2793 global $wpdb;
2794 $sql = "SELECT t.term_id, t.name
2795 FROM {$wpdb->terms} t
2796 JOIN {$wpdb->term_taxonomy} tt ON t.term_id = tt.term_id
2797 WHERE tt.taxonomy = 'product_cat'";
2798
2799 if ( $search_query ) {
2800 $sql .= ' AND t.name LIKE %s';
2801 $prepared_sql = $wpdb->prepare( $sql, '%' . $wpdb->esc_like( $search_query ) . '%' ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
2802 } else {
2803 $prepared_sql = $sql; // No need for placeholders.
2804 }
2805
2806 $results = $wpdb->get_results( $prepared_sql ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.NotPrepared
2807 // Cache the results in the object cache for future use.
2808 wp_cache_set( $cache_key, $results, 'shopbuilder' ); // Adjust the expiration time as needed.
2809 Cache::set_data_cache_key( $cache_key );
2810 }
2811
2812 $cats = [];
2813 if ( ! is_array( $results ) && ! count( $results ) ) {
2814 return $cats;
2815 }
2816 foreach ( $results as $row ) {
2817 $category_id = $row->term_id;
2818 $category_title = $row->name;
2819
2820 $cats[] = [
2821 'value' => $category_id,
2822 'label' => $category_title,
2823 ];
2824 }
2825
2826 // Cache the results in a static variable for the next call.
2827 self::$cache[ $cache_key ] = $cats;
2828 return $cats;
2829 }
2830 /**
2831 * Get WooCommerce product categories.
2832 *
2833 * @param string|null $search_query The search string for category names.
2834 *
2835 * @return array An array of product categories with 'value' and 'label' keys.
2836 */
2837 public static function products_tags_query( $search_query = null ) {
2838 if ( ! is_admin() ) {
2839 return [];
2840 }
2841
2842 $cache_key = 'rtsb_product_tags_' . md5( serialize( $search_query ) ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_serialize
2843
2844 if ( isset( self::$cache[ $cache_key ] ) ) {
2845 return self::$cache[ $cache_key ];
2846 }
2847 $results = wp_cache_get( $cache_key, 'shopbuilder' );
2848 if ( ! $results ) {
2849 global $wpdb;
2850 $sql = "SELECT t.term_id, t.name
2851 FROM {$wpdb->terms} t
2852 JOIN {$wpdb->term_taxonomy} tt ON t.term_id = tt.term_id
2853 WHERE tt.taxonomy = 'product_tag'";
2854
2855 if ( $search_query ) {
2856 $sql .= ' AND t.name LIKE %s';
2857 $prepared_sql = $wpdb->prepare( $sql, '%' . $wpdb->esc_like( $search_query ) . '%' ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
2858 } else {
2859 $prepared_sql = $sql; // No need for placeholders.
2860 }
2861
2862 $results = $wpdb->get_results( $prepared_sql ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.NotPrepared
2863 // Cache the results in the object cache for future use.
2864 wp_cache_set( $cache_key, $results, 'shopbuilder' ); // Adjust the expiration time as needed.
2865 Cache::set_data_cache_key( $cache_key );
2866 }
2867
2868 $cats = [];
2869 if ( ! is_array( $results ) && ! count( $results ) ) {
2870 return $cats;
2871 }
2872 foreach ( $results as $row ) {
2873 $category_id = $row->term_id;
2874 $category_title = $row->name;
2875
2876 $cats[] = [
2877 'value' => $category_id,
2878 'label' => $category_title,
2879 ];
2880 }
2881
2882 // Cache the results in a static variable for the next call.
2883 self::$cache[ $cache_key ] = $cats;
2884 return $cats;
2885 }
2886
2887 /**
2888 * @param string $search_query Search query.
2889 *
2890 * @return array
2891 */
2892 public static function get_registered_post_types( $search_query = null ) {
2893 // Get all registered post types.
2894 $registered_post_types = get_post_types(
2895 [
2896 'public' => true,
2897 '_builtin' => false,
2898 ],
2899 'objects'
2900 );
2901
2902 unset( $registered_post_types['elementor_library'] );
2903 unset( $registered_post_types['e-landing-page'] );
2904 unset( $registered_post_types['rtsb_builder'] );
2905
2906 $post_types = [];
2907
2908 // Check if a search query is provided.
2909 if ( ! empty( $search_query ) ) {
2910 // Filter post types based on the search query.
2911 $registered_post_types = array_filter(
2912 $registered_post_types,
2913 function ( $post_type ) use ( $search_query ) {
2914 return stripos( $post_type->labels->singular_name, $search_query ) !== false;
2915 }
2916 );
2917 // Check if 'post' match the search query and include them if they do.
2918 if ( stripos( 'post', $search_query ) !== false ) {
2919 $post_types[] = [
2920 'value' => 'post',
2921 'label' => 'Post',
2922 ];
2923 }
2924 } else {
2925 $post_types[] = [
2926 'value' => 'post',
2927 'label' => 'Post',
2928 ];
2929 }
2930
2931 // Convert the filtered post types to an array.
2932 foreach ( $registered_post_types as $post_type ) {
2933 $post_types[] = [
2934 'value' => $post_type->name,
2935 'label' => $post_type->labels->singular_name,
2936 ];
2937 }
2938
2939 return $post_types;
2940 }
2941
2942 /**
2943 * Get Post Types.
2944 *
2945 * @param string $search_query Search query.
2946 * @param array $args Arguments.
2947 *
2948 * @return array
2949 */
2950 public static function get_post_types( $search_query = null, $args = [] ) {
2951
2952 $args = wp_parse_args(
2953 $args,
2954 [
2955 'post_type' => 'any',
2956 'posts_per_page' => 20,
2957 'orderby' => 'ID',
2958 'order' => 'DESC',
2959 ]
2960 );
2961
2962 if ( 'archive' !== $args['post_type'] ) {
2963 if ( $search_query ) {
2964 $args['s'] = $search_query;
2965 }
2966 $posts = [];
2967 $query_results = get_posts( $args );
2968 foreach ( $query_results as $post ) {
2969 $posts[] = [
2970 'value' => $post->ID,
2971 'label' => $post->post_title . ' (ID#' . $post->ID . ')',
2972 ];
2973 }
2974
2975 if ( $search_query ) {
2976 if ( strpos( '-error 404', $search_query ) ) {
2977 $posts[] = [
2978 'value' => 'error',
2979 'label' => __( '404 Error', 'shopbuilder' ),
2980 ];
2981 }
2982 } else {
2983 if ( 'page' === $args['post_type'] ) {
2984 $posts[] = [
2985 'value' => 'error',
2986 'label' => __( '404 Error', 'shopbuilder' ),
2987 ];
2988 }
2989 }
2990 } else {
2991 $posts = self::get_registered_post_types( $search_query );
2992 }
2993
2994 return $posts;
2995 }
2996
2997 /**
2998 * Get all Elementor breakpoints.
2999 *
3000 * @return array
3001 */
3002 public static function get_elementor_breakpoints() {
3003 return ( new \Elementor\Core\Breakpoints\Manager() )->get_breakpoints_config();
3004 }
3005
3006 /**
3007 * Render view.
3008 *
3009 * @param string $viewName View name.
3010 * @param array $args View args.
3011 * @param boolean $return View return.
3012 * @return string|void
3013 */
3014 public static function renderView( $viewName, $args = [], $return = false ) {
3015 $viewName = str_replace( '.', '/', $viewName );
3016
3017 if ( ! empty( $args ) && is_array( $args ) ) {
3018 extract( $args ); // phpcs:ignore WordPress.PHP.DontExtract.extract_extract
3019 }
3020
3021 $view_file = rtsb()->plugin_path() . '/resources/' . $viewName . '.php';
3022
3023 if ( ! file_exists( $view_file ) ) {
3024 _doing_it_wrong( __FUNCTION__, sprintf( '<code>%s</code> does not exist.', esc_html( $view_file ) ), '1.7.0' );
3025
3026 return;
3027 }
3028
3029 if ( $return ) {
3030 ob_start();
3031 include $view_file;
3032
3033 return ob_get_clean();
3034 } else {
3035 include $view_file;
3036 }
3037 }
3038
3039 /**
3040 * Best selling product query.
3041 *
3042 * @param int $minimum_sale Minimum sale.
3043 * @param int $limit Total limit.
3044 *
3045 * @return array|mixed
3046 */
3047 public static function best_selling_products_ids( $minimum_sale = 1, $limit = 10 ) {
3048 $cache_key = 'rtsb_best_selling_products_' . $minimum_sale . '_' . $limit;
3049 // Check if the data is in the cache.
3050 if ( isset( self::$cache[ $cache_key ] ) ) {
3051 return self::$cache[ $cache_key ];
3052 }
3053 $cached_data = get_transient( $cache_key );
3054 if ( $cached_data ) {
3055 self::$cache[ $cache_key ] = $cached_data;
3056 return $cached_data;
3057 }
3058 global $wpdb;
3059 $sql = $wpdb->prepare(
3060 "
3061 SELECT ID
3062 FROM {$wpdb->posts} AS p
3063 LEFT JOIN {$wpdb->postmeta} AS pm ON p.ID = pm.post_id
3064 WHERE p.post_type = 'product'
3065 AND p.post_status = 'publish'
3066 AND pm.meta_key = 'total_sales'
3067 AND pm.meta_value >= %d
3068 ORDER BY pm.meta_value + 0 DESC
3069 LIMIT %d
3070 ",
3071 $minimum_sale,
3072 $limit
3073 );
3074 $best_selling = $wpdb->get_col( $sql ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared
3075 // Cache the result for future use.
3076 set_transient( $cache_key, $best_selling, DAY_IN_SECONDS );
3077 self::$cache[ $cache_key ] = $best_selling;
3078 return $best_selling;
3079 }
3080
3081 /**
3082 * @param null|Object $product Product Object.
3083 * @param int $days_threshold Date String.
3084 *
3085 * @return bool
3086 */
3087 public static function is_new_product( $product = null, $days_threshold = 30 ) {
3088 if ( is_null( $product ) ) {
3089 global $product;
3090 }
3091 if ( ! $product instanceof WC_Product ) {
3092 return false;
3093 }
3094
3095 $product_id = $product->get_id();
3096 $days_threshold = ! empty( $days_threshold ) ? $days_threshold : 30;
3097 $cache_key = 'is_new_product_' . $product_id . '_' . $days_threshold;
3098
3099 if ( isset( self::$cache[ $cache_key ] ) ) {
3100 return self::$cache[ $cache_key ];
3101 }
3102 $date_created = $product->get_date_created();
3103 if ( ! $date_created ) {
3104 return false;
3105 }
3106 $publish_timestamp = strtotime( $date_created->date_i18n( 'Y-m-d H:i:s' ) );
3107
3108 // Calculate the difference in days.
3109 $days_difference = abs( time() - $publish_timestamp ) / ( 60 * 60 * 24 );
3110
3111 // Check if the product is considered new.
3112 $is_new = $days_difference <= $days_threshold;
3113
3114 // Cache the result for a day.
3115 self::$cache[ $cache_key ] = $is_new;
3116
3117 return $is_new;
3118 }
3119
3120 /**
3121 * Checks if a feature is disabled for guest users based on the provided option.
3122 *
3123 * @param string $option The option to retrieve from the item.
3124 * @param mixed $default The default value if the option is not found.
3125 *
3126 * @return bool
3127 */
3128 public static function is_guest_feature_disabled( $option, $default ) {
3129 $disabled = self::get_option( 'general', 'guest_user', $option, $default );
3130
3131 return ! is_user_logged_in() && $disabled;
3132 }
3133
3134 /**
3135 * Checks if a feature is disabled for guest users based on the provided option.
3136 *
3137 * @return void
3138 */
3139 public static function woocommerce_output_all_notices() {
3140 if ( function_exists( 'wc_print_notices' ) ) :
3141 echo '<div class="rtsb-notice">';
3142 woocommerce_output_all_notices();
3143 echo '</div>';
3144 endif;
3145 }
3146
3147 /**
3148 * @param object $product Product.
3149 * @return bool
3150 */
3151 public static function is_visible_qty_input( $product = null ) {
3152 $is_visible_qty = true;
3153 if ( $product instanceof \WC_Product ) {
3154 if ( $product->is_sold_individually() ) {
3155 $is_visible_qty = false;
3156 } elseif ( $product->managing_stock() ) {
3157 if ( in_array( $product->get_backorders(), [ 'notify' , 'yes' ], true ) ) {
3158 $is_visible_qty = true;
3159 } elseif ( $product->get_stock_quantity() < 2 ) {
3160 $is_visible_qty = false;
3161 }
3162 }
3163 }
3164 return $is_visible_qty;
3165 }
3166
3167 /**
3168 * @param int $object_id Object id.
3169 * @param string $element_type element type.
3170 * @param string|null $current_lang null for current language, default for default languages.
3171 * @return false|mixed|null
3172 */
3173 public static function wpml_object_id( $object_id, $element_type, $current_lang = 'default' ) {
3174 if ( ! defined( 'ICL_SITEPRESS_VERSION' ) ) {
3175 return $object_id;
3176 }
3177 if ( ! $object_id || ! $element_type ) {
3178 return $object_id;
3179 }
3180 if ( 'default' === $current_lang ) {
3181 $lang = apply_filters( 'wpml_default_language', null );
3182 } else {
3183 $lang = null;
3184 }
3185 return apply_filters( 'wpml_object_id', $object_id, $element_type, false, $lang );
3186 }
3187
3188 /**
3189 * @return string
3190 */
3191 public static function wpml_current_language() {
3192 $current = apply_filters( 'wpml_current_language', null );
3193 $default = apply_filters( 'wpml_default_language', null );
3194 return $default !== $current ? $current : null;
3195 }
3196
3197 /**
3198 * Custom icons.
3199 *
3200 * @return string[]
3201 */
3202 public static function get_custom_icon_names() {
3203 return [
3204 'heart-empty',
3205 'heart',
3206 'eye',
3207 'exchange',
3208 'plus',
3209 'minus',
3210 'avatar',
3211 'pay',
3212 'share',
3213 'clock',
3214 'check-alt',
3215 'check',
3216 'delete',
3217 'marker',
3218 'list',
3219 'list-2',
3220 'power',
3221 'cart',
3222 'cart-2',
3223 'cart-3',
3224 'downloads',
3225 'zoom',
3226 'user-edit',
3227 'grid',
3228 'filter',
3229 'billing',
3230 'login',
3231 'payment',
3232 'search',
3233 'edit',
3234 'coupon',
3235 'arrows-cw',
3236 'trash-empty',
3237 ];
3238 }
3239
3240 /**
3241 * Retrieve an array of custom icons with their HTML representation.
3242 *
3243 * @return array
3244 */
3245 public static function get_icons() {
3246 $icon = self::get_custom_icon_names();
3247 $icons_array = [];
3248 foreach ( $icon as $value ) {
3249 $icons_array[ $value ] = '<i class="rtsb-icon rtsb-icon-' . $value . '"></i> <span class="icon-name">' . ucfirst( str_replace( '-', ' ', $value ) ) . '</span>';
3250 }
3251 return $icons_array;
3252 }
3253
3254 /**
3255 * Generates HTML markup for displaying an endpoint icon.
3256 *
3257 * @param array $data The endpoint data containing icon information.
3258 * @param boolean $wrapper Whether to wrap the icon in a span element.
3259 *
3260 * @return string
3261 */
3262 public static function get_icon_html( $data, $wrapper = true ) {
3263 if ( empty( $data ) || 'none' === $data['icon_source'] ) {
3264 return '';
3265 }
3266 $endpoint = '';
3267 $html = $wrapper ? '<span class="icon ' . esc_attr( $endpoint ) . '_icon">' : '';
3268
3269 if ( 'select_icon' === $data['icon_source'] ) {
3270 $html .= '<i class="rtsb-icon rtsb-icon-' . esc_attr( $data['custom_icon'] ) . '"></i>';
3271 } else {
3272 $icon_html = '';
3273 $icon_url = $data['image_icon']['source'] ?? '';
3274 $icon_id = $data['image_icon']['id'] ?? 0;
3275 $extension = ! empty( $icon_url ) ? strtolower( substr( strrchr( $data['image_icon']['source'], '.' ), 1 ) ) : '';
3276
3277 if ( 'svg' === $extension ) {
3278 $content = file_get_contents( $icon_url ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
3279 $is_svg = strpos( $content, '<svg' );
3280
3281 if ( false !== $is_svg ) {
3282 $icon_html = substr( $content, $is_svg );
3283 }
3284 } else {
3285 $attr = [
3286 'class' => 'rtsb-icon-img',
3287 'alt' => esc_html( ucfirst( $endpoint ) ) . esc_html__( ' Icon', 'shopbuilder' ),
3288 ];
3289
3290 $icon_html = wp_get_attachment_image( absint( $icon_id ), 'full', true, $attr );
3291 }
3292
3293 $html .= $icon_html;
3294 }
3295
3296 $html .= $wrapper ? '</span>' : '';
3297
3298 return $html;
3299 }
3300 /**
3301 * Flushes rewrite rules.
3302 *
3303 * @return void
3304 */
3305 public static function maybe_flush_rewrite_rules() {
3306 add_action( 'wp_loaded', [ __CLASS__, 'flush_rewrite_rules' ] );
3307 add_action( 'shutdown', [ __CLASS__, 'flush_rewrite_rules_once_more' ] );
3308 }
3309
3310 /**
3311 * Flushes rewrite rules if needed.
3312 *
3313 * @return void
3314 */
3315 public static function flush_rewrite_rules() {
3316 if ( get_option( 'rtsb_permalinks_need_flush' ) === 'yes' ) {
3317 flush_rewrite_rules();
3318 }
3319
3320 if ( get_option( 'rtsb_permalinks_flushed' ) === 'yes' ) {
3321 flush_rewrite_rules();
3322 delete_option( 'rtsb_permalinks_flushed' );
3323 }
3324 }
3325
3326 /**
3327 * Flushes rewrite rules if needed for second time.
3328 *
3329 * @return void
3330 */
3331 public static function flush_rewrite_rules_once_more() {
3332 if ( get_option( 'rtsb_permalinks_need_flush' ) === 'yes' ) {
3333 flush_rewrite_rules();
3334 update_option( 'rtsb_permalinks_flushed', 'yes' );
3335 delete_option( 'rtsb_permalinks_need_flush' );
3336 }
3337 }
3338
3339 /**
3340 * Social Share Platforms.
3341 *
3342 * @param string $section_id Section ID.
3343 * @param string $block_id Block ID.
3344 * @param string $option_key Option key.
3345 * @param string $compare_key Compare key.
3346 * @param string $compare_value Compare value.
3347 * @param array $values Values.
3348 *
3349 * @return void
3350 */
3351 public static function set_repeater_options( $section_id, $block_id, $option_key, $compare_key, $compare_value, $values ) {
3352 $modules = DataModel::source()->get_option( $section_id, [], false );
3353 if ( empty( $modules[ $block_id ] ) ) {
3354 return;
3355 }
3356 $options = $modules[ $block_id ];
3357 if ( empty( $options[ $option_key ] ) ) {
3358 return;
3359 }
3360 $repeater_options = json_decode( $options[ $option_key ], true );
3361 // Check if options are not empty and are in array format.
3362 if ( ! is_array( $repeater_options ) ) {
3363 return;
3364 }
3365 // Use array_column to get an array of values from $compare_key.
3366 $column_values = array_column( $repeater_options, $compare_key );
3367 // Use array_search to find the index of the $compare_value.
3368 $index = array_search( $compare_value, $column_values, true );
3369 if ( false === $index ) {
3370 return;
3371 }
3372 $repeater_options[ $index ] = wp_parse_args( $values, $repeater_options[ $index ] );
3373 $options[ $option_key ] = wp_json_encode( $repeater_options );
3374 $modules[ $block_id ] = $options;
3375 DataModel::source()->set_option( $section_id, $modules );
3376 }
3377
3378
3379 /**
3380 * @param array $ids products id.
3381 * @return array
3382 */
3383 public static function get_available_products_by_ids( $ids = [] ) {
3384 $ids = array_filter(
3385 $ids,
3386 function ( $id ) {
3387 return 'product' === get_post_type( $id ) && 'publish' === get_post_status( $id );
3388 }
3389 );
3390 return $ids;
3391 }
3392
3393 /**
3394 * Generate and cache dynamic CSS styles.
3395 *
3396 * @param array $options CSS options to apply (e.g. padding, margin).
3397 * @param string $cache_key Cache key for the CSS.
3398 * @param array $css_properties An array of CSS properties with selectors and properties.
3399 *
3400 * @return void
3401 */
3402 public static function dynamic_styles( $options, $cache_key, $css_properties ) {
3403 $cached_css = wp_cache_get( $cache_key, 'shopbuilder' );
3404 $style_handle = self::optimized_handle( 'rtsb-frontend' );
3405
3406 if ( false !== $cached_css ) {
3407 wp_add_inline_style( $style_handle, $cached_css );
3408 return;
3409 }
3410
3411 $css_rules = [];
3412 $grouped_css_rules = [];
3413
3414 foreach ( $css_properties as $option => $details ) {
3415 if ( ! empty( $options[ $option ] ) ) {
3416 $selector = $details['selector'] ?? '';
3417 $property = $details['property'] ?? '';
3418 $unit = $details['unit'] ?? '';
3419 $value = $options[ $option ];
3420
3421 if ( empty( $grouped_css_rules[ $selector ] ) ) {
3422 $grouped_css_rules[ $selector ] = [];
3423 }
3424
3425 if ( is_array( $property ) ) {
3426 foreach ( $property as $prop ) {
3427 $grouped_css_rules[ $selector ][] = $prop . ': ' . $value . $unit . ' !important';
3428 }
3429 } else {
3430 $grouped_css_rules[ $selector ][] = $property . ': ' . $value . $unit . ' !important';
3431 }
3432 }
3433 }
3434
3435 foreach ( $grouped_css_rules as $selector => $properties ) {
3436 $css_rules[] = $selector . ' {' . implode( '; ', $properties ) . '}';
3437 }
3438
3439 $dynamic_css = implode( ' ', $css_rules );
3440
3441 wp_cache_set( $cache_key, $dynamic_css, 'shopbuilder', 12 * HOUR_IN_SECONDS );
3442 Cache::set_data_cache_key( $cache_key );
3443
3444 if ( ! empty( $dynamic_css ) ) {
3445 wp_add_inline_style( $style_handle, $dynamic_css );
3446 }
3447 }
3448
3449 /**
3450 * Pro version notice.
3451 *
3452 * @param string $ver Pro Version.
3453 * @param string $tab Tab.
3454 * @param string $name Name.
3455 * @param bool $enable_free_Link Enable free link.
3456 *
3457 * @return array[]
3458 */
3459 public static function pro_version_notice( $ver, $tab = 'billing_fields', $name = 'ShopBuilder', $enable_free_Link = true ) {
3460 return [
3461 'version_check' => [
3462 'id' => 'version_check',
3463 'type' => 'title',
3464 'label' => sprintf(
3465 /* translators: 1: required version, 2: link to the Plugins page */
3466 esc_html__(
3467 'To access all features and settings of this module, please ensure that "%1$s" is updated to version %2$s. %3$s',
3468 'shopbuilder'
3469 ),
3470 $name,
3471 sprintf(
3472 '<br /><u><b>%s</b></u>',
3473 esc_html( $ver ?? '1.5.0' ) . ' or higher'
3474 ),
3475 $enable_free_Link
3476 ? sprintf(
3477 '%s <a class="pro-update-required" href="%s" target="_blank" title="%s">%s</a>',
3478 esc_attr__( 'Update to the latest version', 'shopbuilder' ),
3479 esc_url( admin_url( 'plugins.php' ) ),
3480 esc_attr__( 'Go to the Plugin Page', 'shopbuilder' ),
3481 esc_html__( 'from the Plugins page', 'shopbuilder' )
3482 )
3483 : ''
3484 ),
3485 'tab' => $tab,
3486 'customClass' => 'checkout-notice',
3487 ],
3488 ];
3489 }
3490
3491 /**
3492 * Get product gallery ids.
3493 *
3494 * @param object $product Product object.
3495 *
3496 * @return mixed
3497 */
3498 public static function get_cached_gallery_ids( $product ) {
3499 $product_id = $product->get_id();
3500 $cache_key = 'rtsb_product_gallery_ids_' . $product_id;
3501
3502 if ( isset( self::$cache[ $cache_key ] ) ) {
3503 return self::$cache[ $cache_key ];
3504 }
3505
3506 $cached_result = wp_cache_get( $cache_key, 'shopbuilder' );
3507
3508 if ( false !== $cached_result ) {
3509 self::$cache[ $cache_key ] = $cached_result;
3510
3511 return $cached_result;
3512 }
3513
3514 $gallery_ids = $product->get_gallery_image_ids();
3515 self::$cache[ $cache_key ] = $gallery_ids;
3516
3517 wp_cache_set( $cache_key, $gallery_ids, 'shopbuilder', 12 * HOUR_IN_SECONDS );
3518 Cache::set_data_cache_key( $cache_key );
3519
3520 return $gallery_ids;
3521 }
3522
3523 /**
3524 * Check if optimization settings are enabled.
3525 *
3526 * @return bool
3527 */
3528 public static function is_optimization_enabled() {
3529 $has_pro = rtsb()->has_pro();
3530 $pro_ver = defined( 'RTSBPRO_VERSION' ) ? RTSBPRO_VERSION : 0;
3531
3532 if ( $has_pro && version_compare( $pro_ver, '2.0.0', '<' ) ) {
3533 return false;
3534 }
3535
3536 $generalList = GeneralList::instance()->get_data();
3537
3538 return 'on' === ( $generalList['optimization']['enable_optimization'] ?? '' );
3539 }
3540
3541 /**
3542 * Get the optimized handle.
3543 *
3544 * @param string $handle Base handle used for script/style IDs.
3545 *
3546 * @return string
3547 */
3548 public static function optimized_handle( $handle ) {
3549 $use_optimization = self::is_optimization_enabled();
3550
3551 if ( $use_optimization ) {
3552 $handle = self::is_contextual_loading() ? self::get_optimized_handle_by_context() : 'rtsb-bundled';
3553 }
3554
3555 return $handle;
3556 }
3557
3558 /**
3559 * Get the optimized handle by context.
3560 *
3561 * @return string
3562 */
3563 public static function get_optimized_handle_by_context() {
3564 $context = self::detect_context();
3565
3566 if ( 'account' === $context ) {
3567 return 'rtsb-bundled-global';
3568 }
3569
3570 return "rtsb-bundled-$context";
3571 }
3572
3573 /**
3574 * Check if Elementor page.
3575 *
3576 * @param int $post_id Post ID.
3577 *
3578 * @return bool
3579 */
3580 public static function is_elementor_page( $post_id = null ) {
3581 if ( ! defined( 'ELEMENTOR_VERSION' ) ) {
3582 return false;
3583 }
3584
3585 $post_id = $post_id ?: get_the_ID();
3586
3587 if ( ! $post_id ) {
3588 return true;
3589 }
3590
3591 return Plugin::$instance->documents->get( $post_id )->is_built_with_elementor();
3592 }
3593
3594 /**
3595 * Check if shop or archive.
3596 *
3597 * @return bool
3598 */
3599 public static function is_shop_or_archive() {
3600 return is_shop() || is_product_category() || is_product_tag() || is_post_type_archive( 'product' ) || BuilderFns::is_archive() || BuilderFns::is_shop() || is_tax( 'product_brand' );
3601 }
3602
3603 /**
3604 * Detect context.
3605 *
3606 * @return string
3607 */
3608 public static function detect_context() {
3609 switch ( true ) {
3610 case is_product() || BuilderFns::is_product():
3611 $context = 'product';
3612 break;
3613
3614 case is_cart() || BuilderFns::is_cart():
3615 $context = 'cart';
3616 break;
3617
3618 case is_checkout() || BuilderFns::is_checkout():
3619 $context = 'checkout';
3620 break;
3621
3622 case self::is_shop_or_archive():
3623 $context = 'shop';
3624 break;
3625
3626 default:
3627 $context = 'global';
3628 break;
3629 }
3630
3631 return apply_filters( 'rtsb/optimizer/context_detect', $context );
3632 }
3633
3634 /**
3635 * Enqueue optimized assets.
3636 *
3637 * @return void
3638 */
3639 public static function enqueue_optimized_assets() {
3640 $asset_registry = AssetRegistry::instance();
3641 $bundled_assets = $asset_registry->get_bundled_assets();
3642 $context = self::detect_context();
3643 $contextual_loading = self::is_contextual_loading();
3644 $theme_handle = self::find_theme_stylesheet_handle();
3645
3646 if ( $contextual_loading ) {
3647 // Enqueue JS.
3648 if ( ! empty( $bundled_assets['js'] ) ) {
3649 $js_context = isset( $bundled_assets['js'][ $context ] ) ? $context : 'global';
3650
3651 if ( $js_context ) {
3652 wp_enqueue_script( $bundled_assets['js'][ $js_context ]['handle'] );
3653 }
3654 }
3655
3656 // Enqueue CSS.
3657 if ( ! empty( $bundled_assets['css'] ) ) {
3658 $css_context = isset( $bundled_assets['css'][ $context ] ) ? $context : 'global';
3659
3660 if ( $css_context ) {
3661 $handle = $bundled_assets['css'][ $css_context ]['handle'];
3662
3663 wp_enqueue_style( $handle );
3664
3665 if ( ! empty( $theme_handle ) ) {
3666 self::set_theme_dependency( $theme_handle, $handle );
3667 }
3668 }
3669 }
3670 } else {
3671 // Enqueue JS.
3672 if ( ! empty( $bundled_assets['js'] ) ) {
3673 wp_enqueue_script( $bundled_assets['js']['handle'] );
3674 }
3675
3676 // Enqueue CSS.
3677 if ( ! empty( $bundled_assets['css'] ) ) {
3678 $handle = $bundled_assets['css']['handle'];
3679
3680 wp_enqueue_style( $handle );
3681
3682 if ( ! empty( $theme_handle ) ) {
3683 self::set_theme_dependency( $theme_handle, $handle );
3684 }
3685 }
3686 }
3687 }
3688
3689 /**
3690 * Find theme stylesheet handle.
3691 *
3692 * @return string|false
3693 */
3694 private static function find_theme_stylesheet_handle() {
3695 static $cached_handle = null;
3696
3697 if ( null !== $cached_handle ) {
3698 return $cached_handle;
3699 }
3700
3701 global $wp_styles;
3702
3703 if ( ! $wp_styles instanceof WP_Styles ) {
3704 return false;
3705 }
3706
3707 $stylesheet_uri = get_stylesheet_uri();
3708 $theme_directory_uri = get_stylesheet_directory_uri();
3709
3710 $hash = md5( $stylesheet_uri );
3711 $transient_key = 'rtsb_theme_css_handle_' . $hash;
3712
3713 $transient = get_transient( $transient_key );
3714
3715 if ( false !== $transient ) {
3716 $cached_handle = $transient;
3717
3718 return $transient;
3719 }
3720
3721 foreach ( $wp_styles->registered as $handle => $style ) {
3722 $src = $style->src;
3723
3724 if (
3725 ( $src === $stylesheet_uri || strpos( $src, $theme_directory_uri ) !== false ) &&
3726 strpos( $src, 'style.css' ) !== false
3727 ) {
3728 set_transient( $transient_key, $handle, DAY_IN_SECONDS );
3729 Cache::set_transient_cache_key( $transient_key );
3730 $cached_handle = $handle;
3731
3732 return $handle;
3733 }
3734 }
3735
3736 $filtered_handle = apply_filters( 'rtsb/optimizer/theme_stylesheet_handle', false );
3737
3738 if ( is_string( $filtered_handle ) && ! empty( $filtered_handle ) ) {
3739 set_transient( $transient_key, $filtered_handle, DAY_IN_SECONDS );
3740 Cache::set_transient_cache_key( $transient_key );
3741 $cached_handle = $filtered_handle;
3742
3743 return $filtered_handle;
3744 }
3745
3746 set_transient( $transient_key, false, DAY_IN_SECONDS );
3747 Cache::set_transient_cache_key( $transient_key );
3748 $cached_handle = false;
3749
3750 return false;
3751 }
3752
3753 /**
3754 * Set theme dependency.
3755 *
3756 * @param string $theme_handle Theme handle.
3757 * @param string $our_handle Our handle.
3758 *
3759 * @return void
3760 */
3761 private static function set_theme_dependency( $theme_handle, $our_handle ) {
3762 global $wp_styles;
3763
3764 if ( ! isset( $wp_styles->registered[ $theme_handle ] ) ) {
3765 return;
3766 }
3767
3768 $theme_style = $wp_styles->registered[ $theme_handle ];
3769
3770 // Add our handle as a dependency if it's not already there.
3771 if ( ! in_array( $our_handle, $theme_style->deps, true ) ) {
3772 $theme_style->deps[] = $our_handle;
3773 }
3774 }
3775
3776 /**
3777 * Check if Elementor scripts should be loaded.
3778 *
3779 * @return bool
3780 */
3781 public static function should_load_elementor_scripts() {
3782 $data = GeneralList::instance()->get_data()['optimization'] ?? [];
3783
3784 $enable_optimization = $data['enable_optimization'] ?? 'on';
3785 $load_elementor_scripts = $data['load_elementor_scripts'] ?? 'on';
3786
3787 if ( 'on' !== $enable_optimization ) {
3788 return true;
3789 }
3790
3791 return 'on' === $load_elementor_scripts;
3792 }
3793
3794 /**
3795 * Locate asset.
3796 *
3797 * @param string $relative_path Relative path.
3798 *
3799 * @return string|null
3800 */
3801 public static function locate_asset( $relative_path ) {
3802 $free_context = rtsb();
3803 $pro_context = function_exists( 'rtsbpro' ) && rtsb()->has_pro() ? rtsbpro() : null;
3804
3805 $paths = [];
3806
3807 if ( $pro_context ) {
3808 $paths[] = $pro_context->get_assets_path( $relative_path );
3809 }
3810
3811 $paths[] = $free_context->get_assets_path( $relative_path );
3812
3813 foreach ( $paths as $path ) {
3814 if ( file_exists( $path ) ) {
3815 return $path;
3816 }
3817 }
3818
3819 return null;
3820 }
3821
3822 /**
3823 * Enqueue module assets.
3824 *
3825 * @param string $handle Asset handle.
3826 * @param string $module_name Module name.
3827 * @param array $options Options array: ['type' => 'css|js|both', 'deps' => [], 'context' => null].
3828 *
3829 * @return string
3830 */
3831 public static function enqueue_module_assets( $handle, $module_name, $options = [] ) {
3832 $use_optimization = self::is_optimization_enabled();
3833 $handle = self::optimized_handle( $handle );
3834
3835 if ( $use_optimization ) {
3836 return $handle;
3837 }
3838
3839 $defaults = [
3840 'type' => 'both',
3841 'deps' => [ 'jquery', 'rtsb-public' ],
3842 'context' => null,
3843 'version' => RTSB_VERSION,
3844 ];
3845
3846 $config = array_merge( $defaults, $options );
3847 $rtl_suffix = is_rtl() ? '-rtl' : '';
3848 $rtl_dir = is_rtl() ? trailingslashit( 'rtl' ) : trailingslashit( 'css' );
3849 $context = $config['context'] ?: rtsb();
3850 $load_css = in_array( $config['type'], [ 'css', 'both' ], true );
3851 $load_js = in_array( $config['type'], [ 'js', 'both' ], true );
3852
3853 // Register CSS if enabled.
3854 if ( $load_css ) {
3855 wp_register_style(
3856 $handle,
3857 $context->get_assets_uri( $rtl_dir . 'modules/' . $module_name . $rtl_suffix . '.css' ),
3858 [],
3859 $config['version']
3860 );
3861 }
3862
3863 // Register JS if enabled.
3864 if ( $load_js ) {
3865 wp_register_script(
3866 $handle,
3867 $context->get_assets_uri( 'js/modules/' . $module_name . '.js' ),
3868 $config['deps'],
3869 $config['version'],
3870 true
3871 );
3872 }
3873
3874 if ( $load_css ) {
3875 wp_enqueue_style( $handle );
3876
3877 $theme_handle = self::find_theme_stylesheet_handle();
3878
3879 if ( ! empty( $theme_handle ) ) {
3880 self::set_theme_dependency( $theme_handle, $handle );
3881 }
3882 }
3883
3884 if ( $load_js ) {
3885 wp_enqueue_script( $handle );
3886 }
3887
3888 return $handle;
3889 }
3890
3891 /**
3892 * Check if contextual loading is enabled.
3893 *
3894 * @return bool
3895 */
3896 public static function is_contextual_loading() {
3897 $data = GeneralList::instance()->get_data()['optimization'] ?? [];
3898
3899 return ! empty( $data['context_asset_loading'] ) && 'on' === $data['context_asset_loading'];
3900 }
3901
3902 /**
3903 * Get Modules list with cache support.
3904 *
3905 * @return array
3906 */
3907 public static function get_modules_list() {
3908 static $cached_modules = null;
3909
3910 if ( null !== $cached_modules ) {
3911 return $cached_modules;
3912 }
3913
3914 $cache_key = 'rtsb_module_list';
3915 $cache_group = 'shopbuilder';
3916
3917 $cached = wp_cache_get( $cache_key, $cache_group );
3918
3919 if ( false !== $cached ) {
3920 $cached_modules = $cached;
3921
3922 return $cached;
3923 }
3924
3925 $modules = ModuleList::instance()->get_data();
3926
3927 wp_cache_set( $cache_key, $modules, $cache_group, 12 * HOUR_IN_SECONDS );
3928 Cache::set_data_cache_key( $cache_key );
3929
3930 $cached_modules = $modules;
3931
3932 return $modules;
3933 }
3934
3935 /**
3936 * Get Elementor widgets list with cache support.
3937 *
3938 * @return array
3939 */
3940 public static function get_widgets_list() {
3941 static $cached_widgets = null;
3942
3943 if ( null !== $cached_widgets ) {
3944 return $cached_widgets;
3945 }
3946
3947 $cache_key = 'rtsb_elementor_widget_list';
3948 $cache_group = 'shopbuilder';
3949
3950 $cached = wp_cache_get( $cache_key, $cache_group );
3951
3952 if ( false !== $cached ) {
3953 $cached_widgets = $cached;
3954
3955 return $cached;
3956 }
3957
3958 $widgets = ElementList::instance()->get_list();
3959
3960 wp_cache_set( $cache_key, $widgets, $cache_group, 12 * HOUR_IN_SECONDS );
3961 Cache::set_data_cache_key( $cache_key );
3962
3963 $cached_widgets = $widgets;
3964
3965 return $widgets;
3966 }
3967
3968 /**
3969 * Convert the given price to the active currency.
3970 *
3971 * @param float $price The original price.
3972 * @param Object||null $product Product.
3973 *
3974 * @return float
3975 */
3976 public static function get_currency_base_price( $price, $product = null ) {
3977 return apply_filters( 'rtsb/convert/currency/price', $price, $product );
3978 }
3979 /**
3980 * Generate a signed URL with a payload.
3981 *
3982 * @param array $payload Associative data to encode.
3983 * @param string $base_url Base URL to append ?key=... (e.g. wc_get_checkout_url()).
3984 * @param string $key Key name to use in the URL.
3985 * @return string Signed URL with key parameter.
3986 */
3987 public static function generate_signed_url( array $payload, string $base_url, $key = 'key' ) {
3988 if ( empty( $key ) ) {
3989 $key = 'key';
3990 }
3991 // Convert payload to JSON.
3992 $data = wp_json_encode( $payload );
3993 // Create signature.
3994 $signature = wp_hash( $data );
3995 // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
3996 $encode_key = base64_encode( $data . '::' . $signature );
3997 // Return full URL with key param.
3998 return add_query_arg( [ $key => rawurlencode( $encode_key ) ], $base_url );
3999 }
4000
4001 /**
4002 * Decode and verify a signed URL key.
4003 *
4004 * @param string $key Encoded key from URL.
4005 * @return array|false Decoded payload array on success, false on failure.
4006 */
4007 public static function decode_signed_key( string $key ) {
4008 if ( empty( $key ) ) {
4009 return false;
4010 }
4011 $key = rawurldecode( wp_unslash( $key ) );
4012 // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode
4013 $raw = base64_decode( sanitize_text_field( $key ) );
4014 if ( ! $raw ) {
4015 return false;
4016 }
4017 $parts = explode( '::', $raw, 2 );
4018 if ( count( $parts ) !== 2 ) {
4019 return false;
4020 }
4021 list( $data_json, $signature ) = $parts;
4022 // Validate signature.
4023 if ( ! hash_equals( wp_hash( $data_json ), $signature ) ) {
4024 return false;
4025 }
4026 $payload = json_decode( $data_json, true );
4027 return is_array( $payload ) ? $payload : false;
4028 }
4029
4030 /**
4031 * Get Loco Translate MO file path for a plugin.
4032 *
4033 * @param string $textdomain Plugin textdomain.
4034 * @return void|false
4035 */
4036 public static function load_loco_textdomain( $textdomain ) {
4037 if ( ! function_exists( 'loco_plugin_version' ) ) {
4038 return false;
4039 }
4040 $lang = WP_LANG_DIR;
4041 $path = $lang . '/plugins/' . $textdomain . '-' . get_locale() . '.mo';
4042 if ( ! file_exists( $path ) && defined( 'LOCO_LANG_DIR' ) ) {
4043 $lang = LOCO_LANG_DIR;
4044 $path = $lang . '/plugins/' . $textdomain . '-' . get_locale() . '.mo';
4045 }
4046 if ( ! file_exists( $path ) ) {
4047 if ( 'shopbuilder' === $textdomain ) {
4048 $plugin_root = dirname( RTSB_FILE );
4049 } elseif ( 'shopbuilder-pro' === $textdomain ) {
4050 $plugin_root = dirname( RTSBPRO_FILE );
4051 } else {
4052 return false;
4053 }
4054 $path = $plugin_root . '/languages/' . $textdomain . '-' . get_locale() . '.mo';
4055 }
4056 if ( ! file_exists( $path ) ) {
4057 return false;
4058 }
4059 load_textdomain( $textdomain, $path );
4060 }
4061 }
4062