PluginProbe
Tiered Pricing Table for WooCommerce / 8.0.2
Tiered Pricing Table for WooCommerce v8.0.2
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 2.3.3 All 91 releases
← All changes | src/PriceManager.php +244 -132 2.2.3 → 8.0.2 View file →
@@ -1,185 +1,297 @@
1 1 <?php
2 2
3 3 namespace TierPricingTable;
4 4
5 -class PriceManager
6 -{
7 - /**
8 - * Return fixed price rules or empty array if not exist rules
9 - *
10 - * @param $product_id
11 - *
12 - * @return array
13 - */
14 - public static function getFixedPriceRules( $product_id )
15 - {
16 - return self::getPriceRules( $product_id, 'fixed' );
5 +use WC_Product;
6 +class PriceManager {
7 + protected static $pricingRules = array();
8 +
9 + public static function getFixedPriceRules( $productId, string $context = 'view' ) : array {
10 + return self::getPriceRules( $productId, 'fixed', $context );
17 11 }
18 -
19 - /**
20 - * Return percentage price rules or empty array if not exist rules
21 - *
22 - * @param $product_id
23 - *
24 - * @return array
25 - */
26 - public static function getPercentagePriceRules( $product_id )
27 - {
28 - return self::getPriceRules( $product_id, 'percentage' );
12 +
13 + public static function getPercentagePriceRules( $productId, string $context = 'view' ) : array {
14 + return self::getPriceRules( $productId, 'percentage', $context );
29 15 }
30 -
31 - /**
32 - * Get product pricing rules
33 - *
34 - * @param int $product_id
35 - * @param bool $type
36 - *
37 - * @return array
38 - */
39 - public static function getPriceRules( $product_id, $type = false )
40 - {
41 - $type = ( $type ? $type : self::getPricingType( $product_id ) );
42 -
43 - if ( $type === 'fixed' ) {
44 - $rules = get_post_meta( $product_id, '_fixed_price_rules', true );
16 +
17 + public static function getPriceRules( $productId, ?string $type = null, string $context = 'view' ) : array {
18 + $type = ( $type ? $type : self::getPricingType( $productId, 'fixed', $context ) );
19 + if ( 'fixed' === $type ) {
20 + $rules = (array) get_post_meta( $productId, '_fixed_price_rules', true );
45 21 } else {
46 - $rules = get_post_meta( $product_id, '_percentage_price_rules', true );
22 + $rules = (array) get_post_meta( $productId, '_percentage_price_rules', true );
47 23 }
48 -
49 - $rules = ( !empty($rules) ? $rules : [] );
24 + $rules = array_map( function ( $price ) {
25 + return floatval( $price );
26 + }, $rules );
27 + $rules = ( !empty( $rules ) ? array_filter( $rules ) : array() );
50 28 ksort( $rules );
51 - return apply_filters(
52 - 'tier_pricing_table/price/product_price_rules',
53 - $rules,
54 - $product_id,
55 - $type
56 - );
29 + if ( 'edit' !== $context ) {
30 + $rules = apply_filters(
31 + 'tiered_pricing_table/price/product_price_rules',
32 + $rules,
33 + $productId,
34 + $type
35 + );
36 + }
37 + return array_filter( $rules, function ( $quantity ) {
38 + return intval( $quantity ) > 1;
39 + }, ARRAY_FILTER_USE_KEY );
57 40 }
58 -
41 +
59 42 /**
60 43 * Get price by product quantity
61 44 *
62 - * @param int $quantity
63 - * @param int $product_id
45 + * @param int $quantity
46 + * @param int $productId
47 + * @param ?string $context
48 + * @param ?string $place
49 + * @param bool $withTaxes
50 + * @param ?PricingRule $pricingRule
51 + * @param bool $roundPrice
64 52 *
65 53 * @return bool|float|int
66 54 */
67 - public static function getPriceByRules( $quantity, $product_id )
68 - {
69 - $rules = self::getPriceRules( $product_id );
70 - $type = self::getPricingType( $product_id );
71 - if ( $type === 'fixed' ) {
72 - foreach ( array_reverse( $rules, true ) as $_amount => $price ) {
73 - if ( $_amount <= $quantity ) {
74 - $product_price = wc_get_price_to_display( wc_get_product( $product_id ), [
75 - 'price' => $price,
76 - 'qty' => 1,
77 - ] );
55 + public static function getPriceByRules(
56 + $quantity,
57 + $productId,
58 + $context = 'view',
59 + $place = 'shop',
60 + bool $withTaxes = true,
61 + ?PricingRule $pricingRule = null,
62 + ?bool $roundPrice = null
63 + ) {
64 + $pricingRule = ( $pricingRule ? $pricingRule : self::getPricingRule( $productId ) );
65 + $roundPrice = ( !is_null( $roundPrice ) ? $roundPrice : CalculationLogic::roundPrice() );
66 + foreach ( array_reverse( $pricingRule->getRules(), true ) as $_amount => $price ) {
67 + if ( $_amount <= $quantity ) {
68 + if ( $pricingRule->isPercentage() ) {
69 + $product = wc_get_product( $productId );
70 + if ( $product ) {
71 + $productPrice = self::getProductPriceWithPercentageDiscount( $product, $price, $context );
72 + }
73 + } else {
74 + $productPrice = $price;
78 75 }
76 + if ( 'view' === $context && $withTaxes ) {
77 + $product = wc_get_product( $productId );
78 + $productPrice = self::getPriceToDisplay( $productPrice, $product, $place );
79 + }
80 + break;
79 81 }
80 82 }
81 -
82 - if ( $type === 'percentage' ) {
83 - $product = wc_get_product( $product_id );
84 - foreach ( array_reverse( $rules, true ) as $_amount => $percentDiscount ) {
85 - if ( $_amount <= $quantity ) {
86 - $product_price = wc_get_price_to_display( $product, [
87 - 'price' => self::getPriceByPercentDiscount( $product->get_price(), $percentDiscount ),
88 - 'qty' => 1,
89 - ] );
90 - }
91 - }
83 + $productPrice = $productPrice ?? false;
84 + if ( $productPrice && apply_filters( 'tiered_pricing_table/price/round_price', $roundPrice ) ) {
85 + $roundPrecision = (int) apply_filters( 'tiered_pricing_table/price/round_precision', max( 2, wc_get_price_decimals() ) );
86 + $productPrice = round( $productPrice, $roundPrecision );
92 87 }
93 -
94 - $product_price = ( isset( $product_price ) ? $product_price : false );
95 - return apply_filters(
96 - 'tier_pricing_table/price/price_by_rules',
97 - $product_price,
98 - $quantity,
99 - $product_id
100 - );
88 + if ( 'edit' !== $context ) {
89 + return apply_filters(
90 + 'tiered_pricing_table/price/price_by_rules',
91 + $productPrice,
92 + $quantity,
93 + $productId,
94 + $context,
95 + $place,
96 + $pricingRule
97 + );
98 + }
99 + return $productPrice;
101 100 }
102 -
101 +
103 102 /**
103 + * Calculate displayed price depend on taxes
104 + *
105 + * @param float $price
106 + * @param WC_Product $product
107 + * @param ?string $displayContext
108 + *
109 + * @return ?float
110 + */
111 + public static function getPriceToDisplay( $price, WC_Product $product, ?string $displayContext = 'shop' ) : ?float {
112 + if ( wc_tax_enabled() ) {
113 + $price = wc_get_price_to_display( $product, array(
114 + 'price' => $price,
115 + 'qty' => 1,
116 + 'display_context' => $displayContext,
117 + ) );
118 + }
119 + return floatval( $price );
120 + }
121 +
122 + /**
104 123 * Calculate price using percentage discount
105 124 *
106 - * @param float|int $price
107 - * @param float|int $discount
125 + * @param float $price
126 + * @param float $discount
108 127 *
109 - * @return bool|float|int
128 + * @return bool|float
110 129 */
111 - public static function getPriceByPercentDiscount( $price, $discount )
112 - {
113 -
130 + public static function getPriceByPercentDiscount( $price, $discount ) {
114 131 if ( $price > 0 && $discount <= 100 ) {
115 132 $discount_amount = $price / 100 * $discount;
116 133 return $price - $discount_amount;
117 134 }
118 -
119 135 return false;
120 136 }
121 -
137 +
138 + public static function getProductPriceWithPercentageDiscount( WC_Product $product, $discount, $context = 'view' ) {
139 + $discount = floatval( $discount );
140 + $productPrice = ( CalculationLogic::calculateDiscountBasedOnRegularPrice() ? $product->get_regular_price( $context ) : $product->get_price( $context ) );
141 + return self::getPriceByPercentDiscount( $productPrice, $discount );
142 + }
143 +
144 + public static function getPricingType( $productId, string $default = 'fixed', string $context = 'view' ) : string {
145 + if ( !tpt_fs()->can_use_premium_code() ) {
146 + return 'fixed';
147 + }
148 + $type = get_post_meta( $productId, '_tiered_price_rules_type', true );
149 + $type = ( in_array( $type, array('fixed', 'percentage') ) ? $type : $default );
150 + if ( 'edit' !== $context ) {
151 + return apply_filters( 'tiered_pricing_table/price/type', $type, $productId );
152 + }
153 + return $type;
154 + }
155 +
122 156 /**
123 - * Get pricing type of product. Available: fixed or percentage
157 + * Update product pricing type
124 158 *
125 - * @param int $product_id
126 - *
127 - * @param string $default
128 - *
129 - * @return string
159 + * @param int $productId
160 + * @param string $type
130 161 */
131 - public static function getPricingType( $product_id, $default = 'fixed' )
132 - {
133 - return 'fixed';
162 + public static function updatePriceRulesType( $productId, string $type ) {
163 + if ( in_array( $type, array('percentage', 'fixed') ) ) {
164 + update_post_meta( $productId, '_tiered_price_rules_type', $type );
165 + }
134 166 }
135 -
167 +
136 168 /**
137 - * Update price rules
169 + * Get product minimum quantity
138 170 *
139 - * @param array $amounts
140 - * @param array $prices
141 - * @param int $post_id
171 + * @param int $productId
172 + * @param ?string $context
173 + *
174 + * @return int
142 175 */
143 - public static function updateFixedPriceRules( $amounts, $prices, $post_id )
144 - {
145 - $rules = [];
146 - foreach ( $amounts as $key => $amount ) {
147 - if ( !empty($amount) && !empty($prices[$key]) && !key_exists( $amount, $rules ) ) {
148 - $rules[$amount] = wc_format_decimal( $prices[$key] );
176 + public static function getProductQtyMin( $productId, ?string $context = 'view' ) : ?int {
177 + return null;
178 + }
179 +
180 + public static function isProductMixAndMatch( $productId ) {
181 + $mixAndMatch = get_post_meta( $productId, '_tiered_price_mix_and_match_minimum', true );
182 + if ( '' !== $mixAndMatch ) {
183 + return 'yes' === $mixAndMatch;
184 + }
185 + $product = wc_get_product( $productId );
186 + if ( $product && $product->is_type( 'variation' ) ) {
187 + $parentMixAndMatch = get_post_meta( $product->get_parent_id(), '_tiered_price_mix_and_match_minimum', true );
188 + if ( '' !== $parentMixAndMatch ) {
189 + return 'yes' === $parentMixAndMatch;
149 190 }
150 191 }
151 - update_post_meta( $post_id, '_fixed_price_rules', $rules );
192 + return CalculationLogic::isMixAndMatchMinimumEnabled();
152 193 }
153 -
194 +
154 195 /**
155 - * Update price rules
196 + * Update product minimum quantity
156 197 *
157 - * @param array $amounts
158 - * @param array $percents
159 - * @param int $post_id
198 + * @param int $productId
199 + * @param ?int $minimum
160 200 */
161 - public static function updatePercentagePriceRules( $amounts, $percents, $post_id )
162 - {
163 - $rules = [];
164 - foreach ( $amounts as $key => $amount ) {
165 - if ( !empty($amount) && !empty($percents[$key]) && !key_exists( $amount, $rules ) && $percents[$key] < 99 ) {
166 - $rules[$amount] = $percents[$key];
167 - }
201 + public static function updateProductMinimumQuantity( $productId, ?int $minimum ) {
202 + if ( !$minimum ) {
203 + delete_post_meta( $productId, '_tiered_price_minimum_qty' );
204 + } else {
205 + update_post_meta( $productId, '_tiered_price_minimum_qty', $minimum );
168 206 }
169 - update_post_meta( $post_id, '_percentage_price_rules', $rules );
170 207 }
171 -
208 +
209 + public static function calculateDiscount( $originalPrice, $currentPrice ) {
210 + if ( $currentPrice >= $originalPrice ) {
211 + return 0;
212 + }
213 + return 100 * ($originalPrice - $currentPrice) / $originalPrice;
214 + }
215 +
172 216 /**
173 - * Update product pricing type
217 + * Main function to get pricing information for a product.
174 218 *
175 - * @param int $post_id
176 - * @param string $type
219 + * @param int $productId
220 + * @param ?string $tieredPricingType - 'percentage' or 'fixed'. Leave null to use default.
221 + *
222 + * @return PricingRule
177 223 */
178 - public static function updatePriceRulesType( $post_id, $type )
179 - {
180 - if ( in_array( $type, [ 'percentage', 'fixed' ] ) ) {
181 - update_post_meta( $post_id, '_tiered_price_rules_type', $type );
224 + public static function getPricingRule( $productId, ?string $tieredPricingType = null ) : PricingRule {
225 + /****************************************
226 + *
227 + * Object cache
228 + *
229 + ****************************************/
230 + if ( array_key_exists( $productId, self::$pricingRules ) && !$tieredPricingType ) {
231 + return self::$pricingRules[$productId];
182 232 }
233 + /****************************************
234 + *
235 + * Initialize variables
236 + *
237 + ****************************************/
238 + $product = null;
239 + $pricingRule = new PricingRule($productId);
240 + $tieredPricingType = ( $tieredPricingType ? $tieredPricingType : self::getPricingType( $productId ) );
241 + /****************************************
242 + *
243 + * Set tiered pricing for the product
244 + *
245 + ****************************************/
246 + $tieredPricingRules = self::getPriceRules( $productId, $tieredPricingType );
247 + // If product does not have rules, check parent product
248 + if ( empty( $tieredPricingRules ) ) {
249 + $product = wc_get_product( $productId );
250 + if ( $product && TierPricingTablePlugin::isVariationProductSupported( $product ) ) {
251 + $tieredPricingType = self::getPricingType( $product->get_parent_id() );
252 + $tieredPricingRules = self::getPriceRules( $product->get_parent_id(), $tieredPricingType );
253 + if ( !empty( $tieredPricingRules ) ) {
254 + $pricingRule->logPricingModification( 'Using parent product pricing rules' );
255 + }
256 + }
257 + }
258 + $pricingRule->setRules( $tieredPricingRules );
259 + $pricingRule->setType( $tieredPricingType );
260 + /****************************************
261 + *
262 + * Set minimum quantity for the product
263 + *
264 + ****************************************/
265 + $minimum = self::getProductQtyMin( $productId );
266 + $isMixAndMatch = self::isProductMixAndMatch( $productId );
267 + // If product does not have minimum quantity, check parent product
268 + if ( !$minimum ) {
269 + if ( $product && TierPricingTablePlugin::isVariationProductSupported( $product ) ) {
270 + $parentIsMixAndMatch = $isMixAndMatch;
271 + if ( !$parentIsMixAndMatch ) {
272 + $minimum = self::getProductQtyMin( $product->get_parent_id() );
273 + if ( $minimum ) {
274 + $pricingRule->logPricingModification( 'Using parent product minimum quantity' );
275 + }
276 + }
277 + }
278 + }
279 + $pricingRule->setMinimum( $minimum );
280 + $pricingRule->setMixAndMatchMinQuantity( $isMixAndMatch );
281 + /*****************************************
282 + *
283 + * Services that modify pricing rule
284 + *
285 + * @hooked QuantityManager - 1: Added maximum and quantity step information.
286 + * @hooked CategoryTierAddon - 10: Filter with category-based rules
287 + * @hooked RoleBasedPricingAddon - 20: Filter with role-based rules
288 + * @hooked RoleBasedPricingAddon - 25: Filter with customer-based rules
289 + * @hooked GlobalPricingService - 30: Filter with global rules
290 + *
291 + *****************************************/
292 + $pricingRule = apply_filters( 'tiered_pricing_table/price/pricing_rule', $pricingRule, $productId );
293 + self::$pricingRules[$productId] = $pricingRule;
294 + return $pricingRule;
183 295 }
184 296
185 -}
297 +}