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

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

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