PluginProbe
Product Labels, Quick View, Buy Now, Pre-Orders, Frequently Bought Together & More for WooCommerce – Merchant / trunk
Product Labels, Quick View, Buy Now, Pre-Orders, Frequently Bought Together & More for WooCommerce – Merchant vtrunk
2.3.1 2.3.0 2.2.8 2.2.7 trunk 1.10.0 1.10.1 1.10.2 1.10.3 1.10.4 1.10.5 1.11.0 1.11.1 1.11.2 1.6 1.7 1.8 1.8.1 1.8.2 1.8.3 1.9.0 1.9.1 1.9.10 1.9.11 1.9.12 All 59 releases
merchant / inc / helpers.php

helpers.php in Product Labels, Quick View, Buy Now, Pre-Orders, Frequently Bought Together & More for WooCommerce – Merchant trunk, at inc/helpers.php

1,245 lines 35.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * Helper functions.
5 *
6 * @package Merchant
7 */
8
9 if ( ! defined( 'ABSPATH' ) ) {
10 exit; // Exit if accessed directly
11 }
12
13 /**
14 * Allowed tags general.
15 * Should be used to escape complex outputs like entire features html.
16 *
17 * @param array $extra
18 * @param bool $include_post_tags Whether to include post kses allowed tags or not. Default true.
19 *
20 * @return array $allowed_tags
21 */
22 if ( ! function_exists( 'merchant_kses_allowed_tags' ) ) {
23 function merchant_kses_allowed_tags( $extra = array(), $include_post_tags = true ) {
24 // Default
25 $allowed_tags = array_merge(
26 array(
27 // Meta
28 'meta' => array(
29 'name' => true,
30 'content' => true,
31 ),
32
33 // SVG Support
34 'svg' => array(
35 'class' => true,
36 'xmlns' => true,
37 'width' => true,
38 'height' => true,
39 'viewbox' => true,
40 'aria-hidden' => true,
41 'role' => true,
42 'focusable' => true,
43 'fill' => true,
44 'stroke' => true,
45 'stroke-linecap' => true,
46 'stroke-linejoin' => true,
47 'stroke-width' => true,
48 ),
49 'g' => array(
50 'id' => true,
51 'class' => true,
52 'clip-path' => true,
53 'style' => true,
54 'transform' => true,
55 ),
56 'path' => array(
57 'fill' => true,
58 'fill-rule' => true,
59 'd' => true,
60 'transform' => true,
61 'stroke' => true,
62 'stroke-width' => true,
63 'stroke-linejoin' => true,
64 'clip-rule' => true,
65 'opacity' => true,
66 ),
67 'polyline' => array(
68 'points' => true,
69 'fill' => true,
70 'fill-rule' => true,
71 'd' => true,
72 'transform' => true,
73 'stroke' => true,
74 'stroke-width' => true,
75 'stroke-linejoin' => true,
76 ),
77 'polygon' => array(
78 'fill' => true,
79 'fill-rule' => true,
80 'points' => true,
81 'transform' => true,
82 'focusable' => true,
83 'stroke' => true,
84 'stroke-width' => true,
85 ),
86 'rect' => array(
87 'x' => true,
88 'y' => true,
89 'rx' => true,
90 'width' => true,
91 'height' => true,
92 'transform' => true,
93 'fill' => true,
94 'stroke' => true,
95 'stroke-width' => true,
96 ),
97 'circle' => array(
98 'cx' => true,
99 'cy' => true,
100 'r' => true,
101 'width' => true,
102 'height' => true,
103 'transform' => true,
104 'fill' => true,
105 'stroke' => true,
106 'stroke-width' => true,
107 'stroke-linecap' => true,
108 'stroke-linejoin' => true,
109 ),
110 'line' => array(
111 'x1' => true,
112 'y1' => true,
113 'x2' => true,
114 'y2' => true,
115 'width' => true,
116 'height' => true,
117 'transform' => true,
118 'fill' => true,
119 'stroke' => true,
120 'stroke-width' => true,
121 'stroke-linecap' => true,
122 'stroke-linejoin' => true,
123 ),
124 'clipPath' => array(
125 'id' => true,
126 'class' => true,
127 'style' => true,
128 ),
129 'defs' => array(
130 'id' => true,
131 ),
132 'progress' => array(
133 'id' => true,
134 'class' => true,
135 'value' => true,
136 'max' => true,
137 ),
138 ),
139 $include_post_tags ? wp_kses_allowed_html( 'post' ) : array()
140 );
141
142 // Include schema markup tags
143 if ( in_array( 'schema_markup', $extra, true ) || in_array( 'all', $extra, true ) ) {
144 $tags = array( 'meta', 'nav', 'ul', 'li', 'a', 'span' );
145
146 foreach ( $tags as $tag ) {
147 if ( isset( $allowed_tags[ $tag ] ) ) {
148 $allowed_tags[ $tag ]['itemprop'] = true;
149 $allowed_tags[ $tag ]['itemscope'] = true;
150 $allowed_tags[ $tag ]['itemtype'] = true;
151 } else {
152 $allowed_tags[ $tag ] = array(
153 'itemprop' => true,
154 'itemscope' => true,
155 'itemtype' => true,
156 );
157 }
158 }
159 }
160
161 // Include iframe tags
162 if ( in_array( 'iframe', $extra, true ) || in_array( 'all', $extra, true ) ) {
163 $allowed_tags['iframe'] = array(
164 'src' => true,
165 'height' => true,
166 'width' => true,
167 'frameborder' => true,
168 'allowfullscreen' => true,
169 );
170 }
171
172 if ( in_array( 'wc_email', $extra, true ) || in_array( 'all', $extra, true ) ) {
173 $allowed_tags['body'] = array(
174 'class' => true,
175 'style' => true,
176 'leftmargin' => true,
177 'marginwidth' => true,
178 'topmargin' => true,
179 'marginheight' => true,
180 'offset' => true,
181 );
182
183 $allowed_tags['html'] = array(
184 'lang' => true,
185 );
186
187 $allowed_tags['style'] = array(
188 'type' => true,
189 );
190 $allowed_tags['head'] = array();
191 $allowed_tags['meta']['http-equiv'] = true;
192 }
193
194 // Include nonce tags
195 if ( in_array( 'nonce', $extra, true ) || in_array( 'all', $extra, true ) ) {
196 $allowed_tags['input'] = array(
197 'type' => true,
198 'id' => true,
199 'name' => true,
200 'value' => true,
201 'data-name' => true,
202 );
203 }
204
205 // Include bdi tag
206 if ( in_array( 'bdi', $extra, true ) || in_array( 'all', $extra, true ) ) {
207 $allowed_tags['bdi'] = array(
208 'class' => true,
209 'id' => true,
210 'style' => true,
211 );
212 }
213
214 // Include select2
215 if ( in_array( 'select2', $extra, true ) || in_array( 'all', $extra, true ) ) {
216 $allowed_tags['select'] = array(
217 'name' => true,
218 'class' => true,
219 'id' => true,
220 'style' => true,
221 'data-name' => true,
222 'data-source' => true,
223 'multiple' => true,
224 );
225
226 $allowed_tags['option'] = array(
227 'value' => true,
228 'selected' => true,
229 );
230
231 $allowed_tags['optgroup'] = array(
232 'label' => true,
233 );
234 }
235
236 // Include dd, dt tags
237 if ( in_array( 'dd', $extra, true ) || in_array( 'all', $extra, true ) ) {
238 $allowed_tags['dd'] = array(
239 'class' => true,
240 'id' => true,
241 'style' => true,
242 );
243
244 $allowed_tags['dt'] = array(
245 'class' => true,
246 'id' => true,
247 'style' => true,
248 );
249 }
250
251 // Include forms tags.
252 if ( in_array( 'forms', $extra, true ) || in_array( 'all', $extra, true ) ) {
253 $tags = array( 'form', 'input', 'select', 'option', 'textarea', 'a' );
254
255 foreach ( $tags as $tag ) {
256 $allowed_tags[ $tag ] = array(
257 'id' => true,
258 'class' => true,
259 'style' => true,
260 'name' => true,
261 'href' => true,
262 'target' => true,
263 'value' => true,
264 'type' => true,
265 'placeholder' => true,
266 'data' => '*',
267 'data-source' => true,
268 'data-product_id' => true,
269 'data-product_variations' => true,
270 'data-attribute_name' => true,
271 'data-show_option_none' => true,
272 'data-name' => true,
273 'data-allowed-types' => true,
274 'step' => true,
275 'min' => true,
276 'max' => true,
277 'selected' => true,
278 'checked' => true,
279 'onchange' => true,
280 'autocomplete' => true,
281 'required' => true,
282 'action' => true,
283 'method' => true,
284 'enctype' => true,
285 'size' => true,
286 'role' => true,
287 'inputmode' => true,
288 'aria-label' => true,
289 'multiple' => true,
290 );
291
292 if ( $tag === 'a' ) {
293 $allowed_tags[ $tag ]['href'] = true;
294 $allowed_tags[ $tag ]['title'] = true;
295 $allowed_tags[ $tag ]['target'] = true;
296 }
297 }
298 }
299
300
301 // Include script tag
302 if ( in_array( 'script', $extra, true ) ) {
303 $allowed_tags['script'] = array(
304 'id' => true,
305 'src' => true,
306 'type' => true,
307 'async' => true,
308 'defer' => true,
309 );
310 }
311
312 if ( in_array( 'div', $extra, true ) ) {
313 $tags = array( 'form', 'input', 'select', 'option', 'textarea', 'a', 'div' );
314
315 foreach ( $tags as $tag ) {
316 $allowed_tags[ $tag ] = array(
317 'id' => true,
318 'class' => true,
319 'style' => true,
320 'name' => true,
321 'href' => true,
322 'target' => true,
323 'value' => true,
324 'type' => true,
325 'placeholder' => true,
326 'data' => '*',
327 'data-source' => true,
328 'data-product_id' => true,
329 'data-product_variations' => true,
330 'data-attribute_name' => true,
331 'data-show_option_none' => true,
332 'data-name' => true,
333 'data-allowed-types' => true,
334 'step' => true,
335 'min' => true,
336 'max' => true,
337 'selected' => true,
338 'checked' => true,
339 'onchange' => true,
340 'autocomplete' => true,
341 'required' => true,
342 'action' => true,
343 'method' => true,
344 'enctype' => true,
345 'size' => true,
346 'role' => true,
347 'inputmode' => true,
348 'aria-label' => true,
349 'multiple' => true,
350 'title' => true,
351 'data-id' => true,
352 'data-product-id' => true,
353 );
354
355 if ( $tag === 'a' ) {
356 $allowed_tags[ $tag ]['href'] = true;
357 $allowed_tags[ $tag ]['title'] = true;
358 $allowed_tags[ $tag ]['target'] = true;
359 }
360 }
361 }
362
363 /**
364 * Filters the allowed tags.
365 *
366 * @since 1.2.5
367 */
368 return apply_filters( 'merchant_kses_allowed_tags', $allowed_tags );
369 }
370 }
371
372 /**
373 * Allowed tags for scripts.
374 *
375 * @return array $allowed_tags
376 */
377 if ( ! function_exists( 'merchant_kses_allowed_tags_for_code_snippets' ) ) {
378 function merchant_kses_allowed_tags_for_code_snippets() {
379 return array(
380 'script' => array(
381 'type' => true,
382 ),
383 );
384 }
385 }
386
387 /**
388 * Sanitize SVG markup.
389 *
390 * Uses the existing SVG allowlist from merchant_kses_allowed_tags()
391 * to preserve SVG elements while stripping unsafe content.
392 *
393 * @param mixed $value The raw SVG markup.
394 *
395 * @return string The sanitized SVG markup.
396 *
397 * @since 2.3.0
398 */
399 if ( ! function_exists( 'merchant_sanitize_svg' ) ) {
400 function merchant_sanitize_svg( $value ) {
401 return wp_kses( (string) $value, merchant_kses_allowed_tags( array(), false ) );
402 }
403 }
404
405 /**
406 * Format a price for admin preview display.
407 *
408 * Uses wc_price() when WooCommerce is available, otherwise falls back to a plain
409 * currency symbol + number_format string.
410 *
411 * @param float $price The price value.
412 * @param string $currency Currency symbol fallback (used only when WC is unavailable).
413 *
414 * @return string Formatted price HTML.
415 *
416 * @since 2.2.5
417 */
418 if ( ! function_exists( 'merchant_preview_price' ) ) {
419 function merchant_preview_price( $price, $currency = '$' ) {
420 if ( function_exists( 'wc_price' ) ) {
421 return wp_kses( wc_price( $price ), merchant_kses_allowed_tags( array( 'bdi' ) ) );
422 }
423
424 return esc_html( $currency ) . esc_html( number_format( (float) $price, 2 ) );
425 }
426 }
427
428 /**
429 * Check if WooCommerce checkout page is being rendered by block.
430 * Since WooCommerce 8.3.0 the checkout page is rendered by block.
431 *
432 * @return bool
433 */
434 if ( ! function_exists( 'merchant_is_checkout_block_layout' ) ) {
435 function merchant_is_checkout_block_layout() {
436 $checkout_page = wc_get_page_id( 'checkout' );
437
438 if ( empty( $checkout_page ) ) {
439 return false;
440 }
441
442 if ( function_exists( 'has_blocks' ) && has_blocks( $checkout_page ) ) {
443 $post = get_post( $checkout_page );
444 $blocks = parse_blocks( $post->post_content );
445
446 foreach ( $blocks as $block ) {
447 if ( 'woocommerce/checkout' === $block['blockName'] ) {
448 return true;
449 }
450 }
451 }
452
453 return false;
454 }
455 }
456
457 /**
458 * Check if WooCommerce cart page is being rendered by block.
459 * Since WooCommerce 8.3.0 the cart page is rendered by block.
460 *
461 * @return bool
462 */
463 if ( ! function_exists( 'merchant_is_cart_block_layout' ) ) {
464 function merchant_is_cart_block_layout() {
465 $cart_page = wc_get_page_id( 'cart' );
466
467 if ( empty( $cart_page ) ) {
468 return false;
469 }
470
471 if ( function_exists( 'has_blocks' ) && has_blocks( $cart_page ) ) {
472 $post = get_post( $cart_page );
473 $blocks = parse_blocks( $post->post_content );
474
475 foreach ( $blocks as $block ) {
476 if ( 'woocommerce/cart' === $block['blockName'] ) {
477 return true;
478 }
479 }
480 }
481
482 return false;
483 }
484 }
485
486 /**
487 * Get the product categories.
488 */
489 if ( ! function_exists( 'merchant_get_product_categories' ) ) {
490 function merchant_get_product_categories() {
491 $args = array(
492 'taxonomy' => 'product_cat',
493 'hide_empty' => false,
494 );
495
496 $categories = get_terms( $args );
497
498 $categories_tree = array();
499 $indexed_categories = array();
500
501 if ( ! empty( $categories ) && ! is_wp_error( $categories ) ) {
502 foreach ( $categories as $category ) {
503 $indexed_categories[ $category->term_id ] = array(
504 'id' => $category->term_id,
505 'slug' => $category->slug,
506 'name' => $category->name,
507 'parent' => $category->parent,
508 'children' => array(),
509 );
510 }
511
512 foreach ( $indexed_categories as &$cat ) {
513 if ( (int) $cat['parent'] === 0 ) {
514 $categories_tree[] = &$cat;
515 } else {
516 $indexed_categories[ $cat['parent'] ]['children'][] = &$cat;
517 }
518 }
519 }
520
521 return $categories_tree;
522 }
523 }
524
525 /**
526 * Get every descendant category slug of a given category term.
527 *
528 * Uses a single `child_of` term query, so the whole branch is returned in one
529 * call regardless of how deeply the tree is nested.
530 *
531 * @param int $term_id Parent product category term ID.
532 *
533 * @return string[] Descendant category slugs. Empty when the term has no children.
534 */
535 if ( ! function_exists( 'merchant_get_category_descendant_slugs' ) ) {
536 function merchant_get_category_descendant_slugs( $term_id ) {
537 $descendants = get_terms(
538 array(
539 'taxonomy' => 'product_cat',
540 'child_of' => (int) $term_id,
541 'fields' => 'slugs',
542 'hide_empty' => false,
543 )
544 );
545
546 if ( is_wp_error( $descendants ) || ! is_array( $descendants ) ) {
547 return array();
548 }
549
550 return $descendants;
551 }
552 }
553
554 /**
555 * Expand category slugs to include all of their descendant categories.
556 *
557 * Products are only ever assigned to the categories picked for them, so a
558 * product sitting in "Hoodies" is not matched by a rule targeting its parent
559 * "Clothing". Expanding the parent into its whole branch makes that match work.
560 *
561 * Slugs that do not resolve to a category are passed through untouched, so an
562 * unknown slug never silently disappears from a rule.
563 *
564 * @param string[] $slugs Category slugs to expand.
565 *
566 * @return string[] The given slugs plus every descendant slug, de-duplicated.
567 */
568 if ( ! function_exists( 'merchant_expand_categories_with_children' ) ) {
569 function merchant_expand_categories_with_children( $slugs ) {
570 $slugs = array_filter( array_map( 'strval', (array) $slugs ), 'strlen' );
571
572 if ( empty( $slugs ) ) {
573 return array();
574 }
575
576 $expanded = $slugs;
577
578 foreach ( $slugs as $slug ) {
579 $term = get_term_by( 'slug', $slug, 'product_cat' );
580
581 if ( empty( $term ) || is_wp_error( $term ) ) {
582 continue;
583 }
584
585 $expanded = array_merge( $expanded, merchant_get_category_descendant_slugs( $term->term_id ) );
586 }
587
588 return array_values( array_unique( $expanded ) );
589 }
590 }
591
592 /**
593 * Expand category slugs only when a campaign opted into subcategories.
594 *
595 * Call this at the point a campaign's category slugs are read, so matching
596 * behaviour stays byte-for-byte identical while the toggle is off.
597 *
598 * @param string[] $slugs Category slugs from the campaign.
599 * @param array<string, mixed> $settings Campaign settings holding the toggle.
600 *
601 * @return string[] Expanded slugs when the toggle is on, otherwise the input.
602 */
603 if ( ! function_exists( 'merchant_maybe_expand_categories' ) ) {
604 function merchant_maybe_expand_categories( $slugs, $settings ) {
605 if ( empty( $settings['include_subcategories'] ) ) {
606 return (array) $slugs;
607 }
608
609 return merchant_expand_categories_with_children( $slugs );
610 }
611 }
612
613 /**
614 * Get the product tags.
615 */
616 if ( ! function_exists( 'merchant_get_product_tags' ) ) {
617 function merchant_get_product_tags() {
618 $product_tags = get_terms( array(
619 'taxonomy' => 'product_tag',
620 'hide_empty' => false,
621 ) );
622
623 $tag_names = array();
624
625 if ( ! empty( $product_tags ) && ! is_wp_error( $product_tags ) ) {
626 foreach ( $product_tags as $tag ) {
627 $tag_names[ $tag->slug ] = $tag->name;
628 }
629 }
630
631 return $tag_names;
632 }
633 }
634
635 /**
636 * Convert array to css.
637 */
638 if ( ! function_exists( 'merchant_array_to_css' ) ) {
639 function merchant_array_to_css( $css_array ) {
640 $css = '';
641 if ( empty( $css_array ) ) {
642 return $css;
643 }
644 foreach ( $css_array as $key => $value ) {
645 $css .= $key . ':' . $value . ';';
646 }
647
648 return $css;
649 }
650 }
651
652 /**
653 * Get the share link data structure.
654 *
655 * @return array
656 */
657 if ( ! function_exists( 'merchant_get_share_link_data' ) ) {
658 function merchant_get_share_link_data() {
659 return array(
660 'facebook' => array(
661 'url' => 'https://www.facebook.com/sharer.php?u={{url}}',
662 'title' => __( 'Facebook', 'merchant' ),
663 ),
664 'twitter' => array(
665 'url' => 'https://twitter.com/intent/tweet?url={{url}}&text={{title}}',
666 'title' => __( 'X', 'merchant' ),
667 ),
668 'linkedin' => array(
669 'url' => 'https://www.linkedin.com/sharing/share-offsite/?url={{url}}',
670 'title' => __( 'LinkedIn', 'merchant' ),
671 ),
672 'reddit' => array(
673 'url' => 'https://reddit.com/submit?url={{url}}&title={{title}}',
674 'title' => __( 'Reddit', 'merchant' ),
675 ),
676 'whatsapp' => array(
677 'url' => 'https://api.whatsapp.com/send/?text={{url}}',
678 'title' => __( 'WhatsApp', 'merchant' ),
679 ),
680 'pinterest' => array(
681 'url' => 'http://pinterest.com/pin/create/link/?url={{url}}',
682 'title' => __( 'Pinterest', 'merchant' ),
683 ),
684 'telegram' => array(
685 'url' => 'https://t.me/share/url?url={{url}}&text={{title}}',
686 'title' => __( 'Telegram', 'merchant' ),
687 ),
688 'weibo' => array(
689 'url' => 'http://service.weibo.com/share/share.php?url={{url}}&appkey=&title={{title}}&pic=&ralateUid=',
690 'title' => __( 'Weibo', 'merchant' ),
691 ),
692 'vk' => array(
693 'url' => 'http://vk.com/share.php?url={{url}}&title={{title}}&comment={text}',
694 'title' => __( 'VK', 'merchant' ),
695 ),
696 'ok' => array(
697 'url' => 'https://connect.ok.ru/dk?st.cmd=WidgetSharePreview&st.shareUrl={{url}}',
698 'title' => __( 'OK', 'merchant' ),
699 ),
700 'xing' => array(
701 'url' => 'https://www.xing.com/spi/shares/new?url={{url}}',
702 'title' => __( 'Xing', 'merchant' ),
703 ),
704 'mail' => array(
705 'url' => 'mailto:?subject={{title}}&body={{url}}',
706 'title' => __( 'Mail', 'merchant' ),
707 ),
708 );
709 }
710 }
711
712 /**
713 * Get the share link url.
714 *
715 * @param string $social_network
716 *
717 * @return string
718 */
719 if ( ! function_exists( 'merchant_get_share_link_url' ) ) {
720 function merchant_get_share_link_url( $social_network, $url_to_share, $title_to_share = '' ) {
721 $share_link_data = merchant_get_share_link_data();
722
723 if ( ! isset( $share_link_data[ $social_network ] ) ) {
724 return '';
725 }
726
727 $share_link_url = str_replace(
728 array( '{{url}}', '{{title}}' ),
729 array( $url_to_share, $title_to_share ),
730 $share_link_data[ $social_network ]['url']
731 );
732
733 return $share_link_url;
734 }
735 }
736
737 /**
738 * Get the share link title.
739 *
740 * @param string $social_network
741 *
742 * @return string
743 */
744 if ( ! function_exists( 'merchant_get_share_link_title' ) ) {
745 function merchant_get_share_link_title( $social_network ) {
746 $share_link_data = merchant_get_share_link_data();
747
748 return isset( $share_link_data[ $social_network ] ) ? $share_link_data[ $social_network ]['title'] : '';
749 }
750 }
751
752 if ( ! function_exists( 'merchant_timezone' ) ) {
753 /**
754 * Get the WP timezone.
755 *
756 * @return string
757 */
758 function merchant_timezone() {
759 /**
760 * Filter the storewide sale timezone.
761 *
762 * @param string $timezone
763 *
764 * @since 1.9.9
765 */
766 return apply_filters(
767 'merchant_storewide_sale_timezone',
768 wp_timezone_string()
769 );
770 }
771 }
772
773 if ( ! function_exists( 'merchant_get_current_timestamp' ) ) {
774 /**
775 * Get the current timestamp.
776 *
777 * @return int|string
778 */
779 function merchant_get_current_timestamp() {
780 $timezone = new DateTimeZone( merchant_timezone() );
781
782 // Get the timestamp
783 return ( new DateTime( 'now', $timezone ) )->getTimestamp();
784 }
785 }
786
787 if ( ! function_exists( 'merchant_convert_date_to_timestamp' ) ) {
788 /**
789 * Convert date to timestamp.
790 *
791 * @param string $date The date to convert
792 * @param string $format The format of the date
793 *
794 * @return int The timestamp
795 */
796 function merchant_convert_date_to_timestamp( $date, $format = 'm-d-Y h:i A' ) {
797 $timezone = new DateTimeZone( merchant_timezone() );
798 $date_object = DateTime::createFromFormat( $format, $date, $timezone ); // Create DateTime object with specified format and timezone
799 if ( false === $date_object ) {
800 return 0;
801 }
802
803 return $date_object->getTimestamp(); // Output the timestamp
804 }
805 }
806
807 if ( ! function_exists( 'merchant_date_string_to_timestamp' ) ) {
808 /**
809 * Convert a date string to a Unix timestamp based on the specified format and timezone.
810 *
811 * This function signature matches Merchant Pro for compatibility.
812 *
813 * @param string $date The date string to convert.
814 * @param string|null $timezone_offset The timezone offset or timezone string. Defaults to WordPress timezone.
815 * @param string $format The format of the date string. Defaults to 'm-d-Y h:i A'.
816 *
817 * @return int The timestamp, or 0 on failure.
818 */
819 function merchant_date_string_to_timestamp( $date, $timezone_offset = null, $format = 'm-d-Y h:i A' ) {
820 try {
821 // If it's already numeric, return it as-is
822 if ( is_numeric( $date ) ) {
823 return (int) $date;
824 }
825
826 // Use WordPress timezone if not specified
827 $timezone_offset = $timezone_offset ?? wp_timezone_string();
828
829 // Create DateTime object from the specified format and timezone
830 $date_object = DateTime::createFromFormat( $format, $date, new DateTimeZone( $timezone_offset ) );
831
832 return $date_object ? $date_object->getTimestamp() : 0;
833 } catch ( Exception $e ) {
834 return 0;
835 }
836 }
837 }
838
839 /**
840 * Parses a list of product IDs
841 * @return array
842 */
843 if ( ! function_exists( 'merchant_parse_product_ids' ) ) {
844 function merchant_parse_product_ids( $product_ids ) {
845 $parsed_ids = $product_ids ?? array();
846
847 // Convert to array if it's not already an array
848 $parsed_ids = ! is_array( $parsed_ids ) ? explode( ',', $parsed_ids ) : $parsed_ids;
849
850 // Convert all elements to integers
851 $parsed_ids = array_map( 'intval', $parsed_ids );
852
853 return $parsed_ids;
854 }
855 }
856
857 /**
858 * Check if the user condition is passed.
859 *
860 * @param $args
861 *
862 * @return bool
863 */
864 if ( ! function_exists( 'merchant_is_user_condition_passed' ) ) {
865 function merchant_is_user_condition_passed( $args = array() ) {
866 $args = is_array( $args ) ? $args : array();
867
868 $is_logged_in = is_user_logged_in();
869 $current_user = $is_logged_in ? wp_get_current_user() : null;
870 $customer_id = is_object( $current_user ) && isset( $current_user->ID ) ? (int) $current_user->ID : 0;
871 $user_role = is_object( $current_user ) && isset( $current_user->roles ) && ! empty( $current_user->roles ) ? $current_user->roles[0] : '';
872
873 $condition = $args['user_condition'] ?? 'all';
874
875 $is_exclusion_enabled = $args['user_exclusion_enabled'] ?? false;
876 $exclude_users_raw = $args['exclude_users'] ?? array();
877 $excluded_customers = array_map( 'intval', is_array( $exclude_users_raw ) ? $exclude_users_raw : array() );
878 $excluded_roles_raw = $args['exclude_roles'] ?? array();
879 $excluded_roles = is_array( $excluded_roles_raw ) ? $excluded_roles_raw : array();
880
881 switch ( $condition ) {
882 case 'all':
883 case '':
884 if ( $is_exclusion_enabled ) {
885 if ( in_array( $customer_id, $excluded_customers, true ) ) {
886 return false;
887 }
888
889 if ( $condition === 'all' && in_array( $user_role, $excluded_roles, true ) ) {
890 return false;
891 }
892 }
893
894 return true;
895
896 case 'logged-in':
897 return $is_logged_in;
898
899 case 'roles':
900 if ( $is_exclusion_enabled && in_array( $customer_id, $excluded_customers, true ) ) {
901 return false;
902 }
903
904 $allowed_roles_raw = $args['user_condition_roles'] ?? array();
905 $allowed_roles = is_array( $allowed_roles_raw ) ? $allowed_roles_raw : array();
906 return in_array( $user_role, $allowed_roles, true );
907
908 case 'customers':
909 case 'users':
910 $allowed_users_raw = $args['user_condition_users'] ?? array();
911 $allowed_customers = array_map( 'intval', is_array( $allowed_users_raw ) ? $allowed_users_raw : array() );
912 return $is_logged_in && in_array( $customer_id, $allowed_customers, true );
913
914 default:
915 return false;
916 }
917 }
918 }
919
920 /**
921 * Check if a product is excluded based on given arguments.
922 *
923 * @param $product_id
924 * @param $args
925 *
926 * @return bool
927 */
928 if ( ! function_exists( 'merchant_is_product_excluded' ) ) {
929 function merchant_is_product_excluded( $product_id, $args = array() ) {
930 if ( empty( $args['exclusion_enabled'] ) ) {
931 return false;
932 }
933
934 $display_rule = $args['rules_to_display'] ?? $args['display_rules'] ?? $args['rules_to_apply'] ?? $args['trigger_on'] ?? 'products';
935
936 $rules = array(
937 'all',
938 'all_products',
939 'categories',
940 'category',
941 'by_category',
942 'tags',
943 'by_tags',
944 'featured_products',
945 'products_on_sale',
946 'new_products',
947 'out_of_stock',
948 'pre-order',
949 );
950
951 $product = wc_get_product( $product_id );
952 $_product_id = $product && $product->is_type( 'variation' ) ? $product->get_parent_id() : $product_id;
953
954 // Exclude products
955 if ( in_array( $display_rule, $rules, true ) ) {
956 $excluded_product_ids = $args['excluded_products'] ?? array();
957 $excluded_product_ids = merchant_parse_product_ids( $excluded_product_ids );
958
959 if ( in_array( (int) $product_id, $excluded_product_ids, true ) || in_array( (int) $_product_id, $excluded_product_ids, true ) ) {
960 return true;
961 }
962 }
963
964 // Exclude categories
965 if ( in_array( $display_rule, array( 'all', 'all_products', 'categories', 'category', 'by_category' ), true ) ) {
966 $excluded_categories_slugs = merchant_maybe_expand_categories( $args['excluded_categories'] ?? array(), $args );
967
968 if ( ! empty( $excluded_categories_slugs ) && has_term( $excluded_categories_slugs, 'product_cat', $_product_id ) ) {
969 return true;
970 }
971 }
972
973 // Exclude tags
974 if ( in_array( $display_rule, array( 'all', 'all_products', 'tags', 'by_tags' ), true ) ) {
975 $excluded_tags_slugs = $args['excluded_tags'] ?? array();
976
977 if ( ! empty( $excluded_tags_slugs ) && has_term( $excluded_tags_slugs, 'product_tag', $_product_id ) ) {
978 return true;
979 }
980 }
981
982 // Exclude brands
983 if ( in_array( $display_rule, array( 'all', 'all_products', 'brands', 'by_brands' ), true ) ) {
984 $excluded_brands_slugs = $args['excluded_brands'] ?? array();
985
986 if ( ! empty( $excluded_brands_slugs ) && has_term( $excluded_brands_slugs, 'product_brand', $_product_id ) ) {
987 return true;
988 }
989 }
990
991 // Exclude categories with toggle
992 if ( ! empty( $args['exclude_categories_toggle'] ) && in_array( $display_rule, array( 'all', 'all_products', 'categories', 'category', 'by_category' ), true ) ) {
993 $excluded_categories_slugs = merchant_maybe_expand_categories( $args['excluded_categories'] ?? array(), $args );
994
995 if ( ! empty( $excluded_categories_slugs ) && has_term( $excluded_categories_slugs, 'product_cat', $_product_id ) ) {
996 return true;
997 }
998 }
999
1000 // Exclude tags with toggle
1001 if ( ! empty( $args['exclude_tags_toggle'] ) && in_array( $display_rule, array( 'all', 'all_products', 'tags', 'by_tags' ), true ) ) {
1002 $excluded_tags_slugs = $args['excluded_tags'] ?? array();
1003
1004 if ( ! empty( $excluded_tags_slugs ) && has_term( $excluded_tags_slugs, 'product_tag', $_product_id ) ) {
1005 return true;
1006 }
1007 }
1008
1009 // Exclude brands with toggle
1010 if ( ! empty( $args['exclude_brands_toggle'] ) && in_array( $display_rule, array( 'all', 'all_products', 'brands', 'by_brands' ), true ) ) {
1011 $excluded_brands_slugs = $args['excluded_brands'] ?? array();
1012
1013 if ( ! empty( $excluded_brands_slugs ) && has_term( $excluded_brands_slugs, 'product_brand', $_product_id ) ) {
1014 return true;
1015 }
1016 }
1017
1018 return false;
1019 }
1020 }
1021
1022 /**
1023 * Check if a product or any of its variations is excluded based on the given offer.
1024 *
1025 * This method determines if a product (simple or variable) or any of its variations
1026 * satisfies the merchant exclusion rules.
1027 *
1028 * @param int $product_id The ID of the product to check.
1029 * @param array $offer The offer data to evaluate against exclusion rules.
1030 *
1031 * @return bool True if the product or any of its variations is excluded, false otherwise.
1032 */
1033 if ( ! function_exists( 'merchant_is_product_or_variation_excluded' ) ) {
1034 function merchant_is_product_or_variation_excluded( $product_id, $offer = array() ) {
1035 $product = wc_get_product( $product_id );
1036
1037 if ( ! $product ) {
1038 return false;
1039 }
1040
1041 // Get IDs to check for exclusion
1042 $product_ids = $product->is_type( 'variable' ) ? $product->get_children() : array( $product_id );
1043
1044 foreach ( $product_ids as $id ) {
1045 if ( merchant_is_product_excluded( $id, $offer ) ) {
1046 return true; // Excluded if any ID matches
1047 }
1048 }
1049
1050 return false;
1051 }
1052 }
1053
1054 /**
1055 * Get the label of the first active payment gateway in WooCommerce.
1056 *
1057 * @return string|null The label of the first active payment gateway, or null if none are found.
1058 */
1059 if ( ! function_exists( 'merchant_get_first_active_payment_gateway_label' ) ) {
1060 function merchant_get_first_active_payment_gateway_label() {
1061 // Get the available payment gateways
1062 $available_gateways = WC()->payment_gateways->get_available_payment_gateways();
1063
1064 // Check if there are any active gateways
1065 if ( ! empty( $available_gateways ) ) {
1066 // Get the first active gateway
1067 return reset( $available_gateways )->get_title();
1068 }
1069
1070 // Return null if no active gateways are found
1071 return null;
1072 }
1073 }
1074
1075 /**
1076 * Get the review count of a product.
1077 *
1078 * @param int $product_id The ID of the product to get the review count for.
1079 *
1080 * @return int The review count of the product.
1081 */
1082 if ( ! function_exists( 'merchant_get_product_reviews_count' ) ) {
1083 function merchant_get_product_reviews_count( $product_id ) {
1084 $product = wc_get_product( $product_id );
1085
1086 return $product ? $product->get_review_count() : 0;
1087 }
1088 }
1089
1090 if ( ! function_exists( 'merchant_get_modules_campaign_data' ) ) {
1091 /**
1092 * Retrieves campaign data for a specific module.
1093 *
1094 * This function fetches campaign data based on the provided module ID and processes it
1095 * into a standardized format. The campaigns are indexed by their `flexible_id` (expected to be a UUID v4)
1096 * and include details such as ID, title, and status.
1097 *
1098 * @param string $module_id The ID of the module for which to retrieve campaign data.
1099 * Expected values include constants like `Merchant_Buy_X_Get_Y::MODULE_ID`,
1100 * `Merchant_Pre_Orders::MODULE_ID`, and `Merchant_Product_Labels::MODULE_ID`.
1101 *
1102 * @return array An associative array of campaigns, where each key is the `flexible_id` (UUID v4)
1103 * of the campaign, and the value is an array containing the campaign's `id`, `title`,
1104 * and `status`.
1105 * Example:
1106 * [
1107 * '550e8400-e29b-41d4-a716-446655440000' => [
1108 * 'id' => '550e8400-e29b-41d4-a716-446655440000',
1109 * 'title' => 'Campaign Title',
1110 * 'status' => 'active',
1111 * ],
1112 * 'f47ac10b-58cc-4372-a567-0e02b2c3d479' => [
1113 * 'id' => 'f47ac10b-58cc-4372-a567-0e02b2c3d479',
1114 * 'title' => 'Another Campaign',
1115 * 'status' => 'inactive',
1116 * ],
1117 * ]
1118 */
1119 function merchant_get_module_campaigns_data( $module_id ) {
1120 $campaigns = array();
1121
1122 // Determine the key to use based on the module ID
1123 $key = '';
1124 if ( Merchant_Buy_X_Get_Y::MODULE_ID === $module_id || Merchant_Pre_Orders::MODULE_ID === $module_id ) {
1125 $key = 'rules';
1126 } elseif ( Merchant_Product_Labels::MODULE_ID === $module_id ) {
1127 $key = 'labels';
1128 } else {
1129 $key = 'offers';
1130 }
1131
1132 // Fetch the module campaigns
1133 $module_campaigns = Merchant_Admin_Options::get( $module_id, $key, array() );
1134 if ( empty( $module_campaigns ) ) {
1135 return $campaigns;
1136 }
1137
1138 // Process each campaign
1139 foreach ( $module_campaigns as $campaign ) {
1140 if ( empty( $campaign['flexible_id'] ) ) {
1141 continue;
1142 }
1143
1144 $campaigns[ $campaign['flexible_id'] ] = array(
1145 'campaign_key' => $key,
1146 'campaign_id' => $campaign['flexible_id'],
1147 'campaign_title' => $campaign['offer-title'] ?? $campaign['label-title'] ?? '',
1148 'campaign_status' => ( isset( $campaign['campaign_status'] ) && $campaign['campaign_status'] === 'inactive' ) ? 'inactive' : 'active',
1149 );
1150 }
1151
1152 return $campaigns;
1153 }
1154 }
1155
1156 if ( ! function_exists( 'merchant_get_modules_data' ) ) {
1157 /**
1158 * Get the data of all the modules.
1159 *
1160 * @return array
1161 */
1162 function merchant_get_modules_data() {
1163 $modules = array();
1164 $registered_modules = Merchant_Admin_Modules::get_modules();
1165 foreach ( $registered_modules as $module_category => $module_data ) {
1166 foreach ( $module_data['modules'] as $module_id => $module ) {
1167 if ( ! empty( $module ) ) {
1168 $modules[ $module_id ] = array(
1169 'id' => $module_id,
1170 'active' => Merchant_Modules::is_module_active( $module_id ),
1171 'name' => $module['title'],
1172 'desc' => $module['desc'],
1173 'category' => $module_category,
1174 'pro' => $module['pro'],
1175 );
1176
1177 $campaigns = merchant_get_module_campaigns_data( $module_id );
1178
1179 if ( ! empty( $campaigns ) ) {
1180 $modules[ $module_id ]['campaigns'] = $campaigns;
1181 }
1182 }
1183 }
1184 }
1185
1186 return $modules;
1187 }
1188 }
1189
1190 if ( ! function_exists( 'merchant_get_active_modules' ) ) {
1191 /**
1192 * Get the active modules.
1193 *
1194 * @return array
1195 */
1196 function merchant_get_active_modules() {
1197 $modules = array_keys( merchant_get_modules_data() );
1198
1199 return array_values( array_filter( $modules, static function ( $module_id ) {
1200 return Merchant_Modules::is_module_active( $module_id );
1201 } ) );
1202 }
1203 }
1204
1205 if ( ! function_exists( 'merchant_get_campaign_data' ) ) {
1206 /**
1207 * Get the data of a specific campaign.
1208 *
1209 * @param string $campaign_id The ID of the campaign.
1210 * @param string $module_id The ID of the module.
1211 *
1212 * @return array
1213 */
1214 function merchant_get_campaign_data( $campaign_id, $module_id ) {
1215 if ( Merchant_Buy_X_Get_Y::MODULE_ID === $module_id || Merchant_Pre_Orders::MODULE_ID === $module_id ) {
1216 $key = 'rules';
1217 } elseif ( Merchant_Product_Labels::MODULE_ID === $module_id ) {
1218 $key = 'labels';
1219 } elseif ( 'buy-now' === $module_id ) {
1220 $key = 'campaigns';
1221 } else {
1222 $key = 'offers';
1223 }
1224
1225 $all_modules = merchant_get_modules_data();
1226 $module_campaigns = Merchant_Admin_Options::get( $module_id, $key, array() );
1227 if ( ! empty( $module_campaigns ) ) {
1228 foreach ( $module_campaigns as $campaign ) {
1229 if ( isset( $all_modules[ $module_id ], $campaign['flexible_id'] ) && $campaign_id === $campaign['flexible_id'] ) {
1230 return array(
1231 'campaign_id' => $campaign['flexible_id'],
1232 'campaign_title' => $campaign['offer-title'] ?? $campaign['label-title'] ?? '',
1233 'campaign_status' => $campaign['status'] ?? 'active',
1234 'module_id' => $module_id,
1235 'module_name' => $all_modules[ $module_id ]['name'],
1236 'is_pro' => $all_modules[ $module_id ]['pro'],
1237 'is_module_active' => $all_modules[ $module_id ]['active'],
1238 );
1239 }
1240 }
1241 }
1242
1243 return array();
1244 }
1245 }