PluginProbe
Tiered Pricing Table for WooCommerce / 8.1.0
Tiered Pricing Table for WooCommerce v8.1.0
8.1.0 8.0.2 7.1.7 7.1.5 7.1.4 7.1.1 6.5.0 6.4.0 6.1.0 trunk 1.0 1.1 2.0 2.0.1 2.0.2 2.1 2.1.1 2.1.2 2.2.0 2.2.1 2.2.2 2.2.3 2.3.0 2.3.1 2.3.2 All 92 releases
tier-pricing-table / src / Addons / Tools / PricingTest / RuleTester.php

RuleTester.php in Tiered Pricing Table for WooCommerce 8.1.0, at src/Addons/Tools/PricingTest/RuleTester.php

193 lines 8.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php namespace TierPricingTable\Addons\Tools\PricingTest;
2
3 use TierPricingTable\Addons\GlobalTieredPricing\GlobalPricingRule;
4 use WC_Product;
5 use WP_User;
6
7 /**
8 * Explains why a global pricing rule matches, or does not match, a product and a customer.
9 * Pure: reads the rule, the product and the user and returns sentences; PricingTester runs the pricing.
10 */
11 class RuleTester {
12
13 /**
14 * Why a rule matches, or does not match, a product and a customer. Mirrors
15 * GlobalPricingRule::matchRequirements() step by step; the verdict itself comes from that method.
16 *
17 * @return array{matches: bool, reasons: string[]}
18 */
19 public function explain( GlobalPricingRule $rule, WP_User $user, WC_Product $product ): array {
20 $parent = $product->is_type( array( 'variation', 'subscription-variation' ) ) ? wc_get_product( $product->get_parent_id() ) : $product;
21 $parent = $parent ? $parent : $product;
22
23 $productIds = array( (int) $product->get_id(), (int) $parent->get_id() );
24 $categories = array_map( 'intval', (array) $parent->get_category_ids() );
25 $tags = array_map( 'intval', (array) $parent->get_tag_ids() );
26 $brands = wp_get_post_terms( $parent->get_id(), 'product_brand', array( 'fields' => 'ids' ) );
27 $brands = is_wp_error( $brands ) ? array() : array_map( 'intval', (array) $brands );
28 $roles = GlobalPricingRule::rolesOf( $user ); // a visitor has the guest pseudo role
29
30 $reasons = array();
31
32 // 1. product exclusions
33 if ( array_intersect( $productIds, $rule->getExcludedProducts() ) ) {
34 $reasons[] = __( 'The product is listed under the excluded products.', 'tier-pricing-table' );
35 }
36 foreach ( array(
37 array( $categories, $rule->getEffectiveExcludedCategories(), 'product_cat', __( 'The product is in an excluded category: %s.', 'tier-pricing-table' ) ),
38 array( $tags, $rule->getExcludedProductTags(), 'product_tag', __( 'The product has an excluded tag: %s.', 'tier-pricing-table' ) ),
39 array( $brands, $rule->getExcludedProductBrands(), 'product_brand', __( 'The product has an excluded brand: %s.', 'tier-pricing-table' ) ),
40 ) as list( $productTerms, $excluded, $taxonomy, $message ) ) {
41 $hit = array_intersect( $productTerms, array_map( 'intval', $excluded ) );
42 if ( $hit ) {
43 $reasons[] = sprintf( $message, $this->termNames( $hit, $taxonomy ) );
44 }
45 }
46
47 // 2. customer exclusions
48 if ( $user->ID && in_array( $user->ID, array_map( 'intval', $rule->getExcludedUsers() ), true ) ) {
49 $reasons[] = __( 'The customer is listed under the excluded customers.', 'tier-pricing-table' );
50 }
51 $excludedRoles = array_intersect( $roles, $rule->getExcludedUserRoles() );
52 if ( $excludedRoles ) {
53 $reasons[] = sprintf( __( 'The customer has an excluded role: %s.', 'tier-pricing-table' ), $this->roleNames( $excludedRoles ) );
54 }
55
56 if ( $reasons ) {
57 return array( 'matches' => false, 'reasons' => $reasons );
58 }
59
60 // 3. product limitations
61 $limited = false;
62 $matched = array();
63 $checks = array(
64 array( $productIds, $rule->getIncludedProducts(), null, __( 'The product is listed under the rule\'s products.', 'tier-pricing-table' ) ),
65 array( $categories, $rule->getEffectiveIncludedCategories(), 'product_cat', __( 'The product is in the rule\'s category: %s.', 'tier-pricing-table' ) ),
66 array( $tags, $rule->getIncludedProductTags(), 'product_tag', __( 'The product has the rule\'s tag: %s.', 'tier-pricing-table' ) ),
67 array( $brands, $rule->getIncludedProductBrands(), 'product_brand', __( 'The product has the rule\'s brand: %s.', 'tier-pricing-table' ) ),
68 );
69 foreach ( $checks as list( $productTerms, $included, $taxonomy, $message ) ) {
70 if ( empty( $included ) ) {
71 continue;
72 }
73 $limited = true;
74 $hit = array_intersect( $productTerms, array_map( 'intval', $included ) );
75 if ( $hit ) {
76 $matched[] = $taxonomy ? sprintf( $message, $this->termNames( $hit, $taxonomy ) ) : $message;
77 }
78 }
79
80 if ( $limited && ! $matched ) {
81 $scope = array();
82 if ( $rule->getIncludedProductCategories() ) {
83 $scope[] = sprintf(
84 $rule->isIncludeSubcategories()
85 /* translators: %s: category names */
86 ? __( 'categories %s and their subcategories', 'tier-pricing-table' )
87 /* translators: %s: category names */
88 : __( 'categories %s', 'tier-pricing-table' ),
89 $this->termNames( $rule->getIncludedProductCategories(), 'product_cat' )
90 );
91 }
92 if ( $rule->getIncludedProductTags() ) {
93 $scope[] = sprintf( __( 'tags %s', 'tier-pricing-table' ), $this->termNames( $rule->getIncludedProductTags(), 'product_tag' ) );
94 }
95 if ( $rule->getIncludedProductBrands() ) {
96 $scope[] = sprintf( __( 'brands %s', 'tier-pricing-table' ), $this->termNames( $rule->getIncludedProductBrands(), 'product_brand' ) );
97 }
98 if ( $rule->getIncludedProducts() ) {
99 $scope[] = sprintf( _n( '%d selected product', '%d selected products', count( $rule->getIncludedProducts() ), 'tier-pricing-table' ), count( $rule->getIncludedProducts() ) );
100 }
101 $own = $categories ? sprintf( __( 'The product is in %s.', 'tier-pricing-table' ), $this->termNames( $categories, 'product_cat' ) ) : __( 'The product has no category.', 'tier-pricing-table' );
102
103 return array(
104 'matches' => false,
105 'reasons' => array( sprintf( __( 'The rule is limited to %s, and the product is in none of them.', 'tier-pricing-table' ), implode( '; ', $scope ) ), $own ),
106 );
107 }
108
109 $reasons = $matched ? $matched : array( __( 'The rule applies to every product (no products, categories, tags or brands selected).', 'tier-pricing-table' ) );
110
111 // 4. and 5. customer limitations
112 $includedRoles = $rule->getIncludedUserRoles();
113 $includedUsers = array_map( 'intval', $rule->getIncludedUsers() );
114
115 if ( empty( $includedRoles ) && empty( $includedUsers ) ) {
116 $reasons[] = __( 'The rule applies to every customer (no roles or customers selected).', 'tier-pricing-table' );
117
118 return array( 'matches' => true, 'reasons' => $reasons );
119 }
120
121 if ( $user->ID && in_array( $user->ID, $includedUsers, true ) ) {
122 $reasons[] = __( 'The customer is listed under the rule\'s customers.', 'tier-pricing-table' );
123
124 return array( 'matches' => true, 'reasons' => $reasons );
125 }
126
127 $roleHit = array_intersect( $roles, $includedRoles );
128 if ( $roleHit ) {
129 $reasons[] = sprintf( __( 'The customer has the rule\'s role: %s.', 'tier-pricing-table' ), $this->roleNames( $roleHit ) );
130
131 return array( 'matches' => true, 'reasons' => $reasons );
132 }
133
134 $who = $user->ID
135 ? ( $user->roles ? sprintf( __( 'This customer has the roles %s.', 'tier-pricing-table' ), $this->roleNames( $user->roles ) ) : __( 'This customer has no role.', 'tier-pricing-table' ) )
136 : __( 'A visitor who is not logged in only matches rules that include "Guests (not logged in)" or have no customer limits.', 'tier-pricing-table' );
137 $limits = array();
138 if ( $includedRoles ) {
139 $limits[] = sprintf( __( 'roles %s', 'tier-pricing-table' ), $this->roleNames( $includedRoles ) );
140 }
141 if ( $includedUsers ) {
142 $limits[] = sprintf( _n( '%d selected customer', '%d selected customers', count( $includedUsers ), 'tier-pricing-table' ), count( $includedUsers ) );
143 }
144
145 return array(
146 'matches' => false,
147 'reasons' => array( sprintf( __( 'The rule is limited to %s.', 'tier-pricing-table' ), implode( ' ' . __( 'and', 'tier-pricing-table' ) . ' ', $limits ) ), $who ),
148 );
149 }
150
151 protected function termNames( array $ids, string $taxonomy ): string {
152 $names = array();
153 foreach ( $ids as $id ) {
154 $term = get_term( (int) $id, $taxonomy );
155 $names[] = $term && ! is_wp_error( $term ) ? $term->name : '#' . (int) $id;
156 }
157
158 return implode( ', ', $names );
159 }
160
161 public function roleNames( array $roles ): string {
162 $names = array();
163 $all = function_exists( 'wp_roles' ) ? wp_roles()->role_names : array();
164 foreach ( $roles as $role ) {
165 if ( GlobalPricingRule::GUEST_ROLE === $role ) {
166 $names[] = __( 'Guests (not logged in)', 'tier-pricing-table' );
167 continue;
168 }
169 $names[] = isset( $all[ $role ] ) ? translate_user_role( $all[ $role ] ) : $role;
170 }
171
172 return implode( ', ', $names );
173 }
174
175 /**
176 * Human label of the provider that produced the pricing.
177 */
178 public static function providerLabel( string $provider ): string {
179 switch ( $provider ) {
180 case 'role-based':
181 return __( 'a role price on the product', 'tier-pricing-table' );
182 case 'user-based':
183 return __( 'a customer price on the product', 'tier-pricing-table' );
184 case 'global-rules':
185 return __( 'a global pricing rule', 'tier-pricing-table' );
186 case 'product':
187 return __( 'the product\'s own pricing', 'tier-pricing-table' );
188 default:
189 return $provider ? $provider : __( 'the product\'s own pricing', 'tier-pricing-table' );
190 }
191 }
192 }
193