PluginProbe
Product Labels, Quick View, Buy Now, Pre-Orders, Frequently Bought Together & More for WooCommerce – Merchant / 2.3.2
Product Labels, Quick View, Buy Now, Pre-Orders, Frequently Bought Together & More for WooCommerce – Merchant v2.3.2
2.3.2 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 All 60 releases
← All changes | inc/helpers.php +118 -6 2.2.72.3.2 View file →
@@ -384,8 +384,26 @@
384 384 }
385 385 }
386 386
387 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 +/**
388 406 * Format a price for admin preview display.
389 407 *
390 408 * Uses wc_price() when WooCommerce is available, otherwise falls back to a plain
391 409 * currency symbol + number_format string.
@@ -504,8 +522,96 @@
504 522 }
505 523 }
506 524
507 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 +/**
508 614 * Get the product tags.
509 615 */
510 616 if ( ! function_exists( 'merchant_get_product_tags' ) ) {
511 617 function merchant_get_product_tags() {
@@ -766,10 +872,12 @@
766 872
767 873 $condition = $args['user_condition'] ?? 'all';
768 874
769 875 $is_exclusion_enabled = $args['user_exclusion_enabled'] ?? false;
770 - $excluded_customers = array_map( 'intval', $args['exclude_users'] ?? array() );
771 - $excluded_roles = $args['exclude_roles'] ?? array();
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();
772 880
773 881 switch ( $condition ) {
774 882 case 'all':
775 883 case '':
@@ -792,14 +900,16 @@
792 900 if ( $is_exclusion_enabled && in_array( $customer_id, $excluded_customers, true ) ) {
793 901 return false;
794 902 }
795 903
796 - $allowed_roles = $args['user_condition_roles'] ?? array();
904 + $allowed_roles_raw = $args['user_condition_roles'] ?? array();
905 + $allowed_roles = is_array( $allowed_roles_raw ) ? $allowed_roles_raw : array();
797 906 return in_array( $user_role, $allowed_roles, true );
798 907
799 908 case 'customers':
800 909 case 'users':
801 - $allowed_customers = array_map( 'intval', $args['user_condition_users'] ?? array() );
910 + $allowed_users_raw = $args['user_condition_users'] ?? array();
911 + $allowed_customers = array_map( 'intval', is_array( $allowed_users_raw ) ? $allowed_users_raw : array() );
802 912 return $is_logged_in && in_array( $customer_id, $allowed_customers, true );
803 913
804 914 default:
805 915 return false;
@@ -852,9 +962,9 @@
852 962 }
853 963
854 964 // Exclude categories
855 965 if ( in_array( $display_rule, array( 'all', 'all_products', 'categories', 'category', 'by_category' ), true ) ) {
856 - $excluded_categories_slugs = $args['excluded_categories'] ?? array();
966 + $excluded_categories_slugs = merchant_maybe_expand_categories( $args['excluded_categories'] ?? array(), $args );
857 967
858 968 if ( ! empty( $excluded_categories_slugs ) && has_term( $excluded_categories_slugs, 'product_cat', $_product_id ) ) {
859 969 return true;
860 970 }
@@ -879,9 +989,9 @@
879 989 }
880 990
881 991 // Exclude categories with toggle
882 992 if ( ! empty( $args['exclude_categories_toggle'] ) && in_array( $display_rule, array( 'all', 'all_products', 'categories', 'category', 'by_category' ), true ) ) {
883 - $excluded_categories_slugs = $args['excluded_categories'] ?? array();
993 + $excluded_categories_slugs = merchant_maybe_expand_categories( $args['excluded_categories'] ?? array(), $args );
884 994
885 995 if ( ! empty( $excluded_categories_slugs ) && has_term( $excluded_categories_slugs, 'product_cat', $_product_id ) ) {
886 996 return true;
887 997 }
@@ -1105,8 +1215,10 @@
1105 1215 if ( Merchant_Buy_X_Get_Y::MODULE_ID === $module_id || Merchant_Pre_Orders::MODULE_ID === $module_id ) {
1106 1216 $key = 'rules';
1107 1217 } elseif ( Merchant_Product_Labels::MODULE_ID === $module_id ) {
1108 1218 $key = 'labels';
1219 + } elseif ( 'buy-now' === $module_id ) {
1220 + $key = 'campaigns';
1109 1221 } else {
1110 1222 $key = 'offers';
1111 1223 }
1112 1224