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 +278 -303 2.3.3 → 8.0.2 View file →
@@ -1,322 +1,297 @@
1 -<?php namespace TierPricingTable;
1 +<?php
2 2
3 +namespace TierPricingTable;
4 +
3 5 use WC_Product;
4 -
5 6 class PriceManager {
7 + protected static $pricingRules = array();
6 8
7 - /**
8 - * Return fixed price rules or empty array if not exist rules
9 - *
10 - * @param $product_id
11 - * @param string $context
12 - *
13 - * @return array
14 - */
15 - public static function getFixedPriceRules( $product_id, $context = 'view' ) {
16 - return self::getPriceRules( $product_id, 'fixed', $context );
17 - }
9 + public static function getFixedPriceRules( $productId, string $context = 'view' ) : array {
10 + return self::getPriceRules( $productId, 'fixed', $context );
11 + }
18 12
19 - /**
20 - * Return percentage price rules or empty array if not exist rules
21 - *
22 - * @param $product_id
23 - * @param string $context
24 - *
25 - * @return array
26 - */
27 - public static function getPercentagePriceRules( $product_id, $context = 'view' ) {
28 - return self::getPriceRules( $product_id, 'percentage', $context );
29 - }
13 + public static function getPercentagePriceRules( $productId, string $context = 'view' ) : array {
14 + return self::getPriceRules( $productId, 'percentage', $context );
15 + }
30 16
31 - /**
32 - * Get product pricing rules
33 - *
34 - * @param int $product_id
35 - * @param bool $type
36 - * @param string $context
37 - *
38 - * @return array
39 - */
40 - public static function getPriceRules( $product_id, $type = false, $context = 'view' ) {
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 );
21 + } else {
22 + $rules = (array) get_post_meta( $productId, '_percentage_price_rules', true );
23 + }
24 + $rules = array_map( function ( $price ) {
25 + return floatval( $price );
26 + }, $rules );
27 + $rules = ( !empty( $rules ) ? array_filter( $rules ) : array() );
28 + ksort( $rules );
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 );
40 + }
41 41
42 - $type = $type ? $type : self::getPricingType( $product_id, 'fixed', $context );
42 + /**
43 + * Get price by product quantity
44 + *
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
52 + *
53 + * @return bool|float|int
54 + */
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;
75 + }
76 + if ( 'view' === $context && $withTaxes ) {
77 + $product = wc_get_product( $productId );
78 + $productPrice = self::getPriceToDisplay( $productPrice, $product, $place );
79 + }
80 + break;
81 + }
82 + }
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 );
87 + }
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;
100 + }
43 101
44 - if ( 'fixed' === $type ) {
45 - $rules = get_post_meta( $product_id, '_fixed_price_rules', true );
46 - } else {
47 - $rules = get_post_meta( $product_id, '_percentage_price_rules', true );
48 - }
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 + }
49 121
50 - // If no rules for variation check for product level rules.
51 - if ( 'view' === $context && self::variationHasNoOwnRules( $product_id, $rules ) ) {
122 + /**
123 + * Calculate price using percentage discount
124 + *
125 + * @param float $price
126 + * @param float $discount
127 + *
128 + * @return bool|float
129 + */
130 + public static function getPriceByPercentDiscount( $price, $discount ) {
131 + if ( $price > 0 && $discount <= 100 ) {
132 + $discount_amount = $price / 100 * $discount;
133 + return $price - $discount_amount;
134 + }
135 + return false;
136 + }
52 137
53 - $product = wc_get_product( $product_id );
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 + }
54 143
55 - $product_id = $product->get_parent_id();
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 + }
56 155
57 - $type = self::getPricingType( $product_id );
156 + /**
157 + * Update product pricing type
158 + *
159 + * @param int $productId
160 + * @param string $type
161 + */
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 + }
166 + }
58 167
59 - if ( 'fixed' === $type ) {
60 - $rules = get_post_meta( $product_id, '_fixed_price_rules', true );
61 - } else {
62 - $rules = get_post_meta( $product_id, '_percentage_price_rules', true );
63 - }
64 - }
168 + /**
169 + * Get product minimum quantity
170 + *
171 + * @param int $productId
172 + * @param ?string $context
173 + *
174 + * @return int
175 + */
176 + public static function getProductQtyMin( $productId, ?string $context = 'view' ) : ?int {
177 + return null;
178 + }
65 179
66 - $rules = ! empty( $rules ) ? $rules : array();
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;
190 + }
191 + }
192 + return CalculationLogic::isMixAndMatchMinimumEnabled();
193 + }
67 194
68 - ksort( $rules );
195 + /**
196 + * Update product minimum quantity
197 + *
198 + * @param int $productId
199 + * @param ?int $minimum
200 + */
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 );
206 + }
207 + }
69 208
70 - return apply_filters( 'tier_pricing_table/price/product_price_rules', $rules, $product_id, $type );
71 - }
209 + public static function calculateDiscount( $originalPrice, $currentPrice ) {
210 + if ( $currentPrice >= $originalPrice ) {
211 + return 0;
212 + }
213 + return 100 * ($originalPrice - $currentPrice) / $originalPrice;
214 + }
72 215
73 - /**
74 - * Get price by product quantity
75 - *
76 - * @param int $quantity
77 - * @param int $product_id
78 - * @param string $context
79 - * @param string $place
80 - *
81 - * @return bool|float|int
82 - */
83 - public static function getPriceByRules( $quantity, $product_id, $context = 'view', $place = 'shop' ) {
216 + /**
217 + * Main function to get pricing information for a product.
218 + *
219 + * @param int $productId
220 + * @param ?string $tieredPricingType - 'percentage' or 'fixed'. Leave null to use default.
221 + *
222 + * @return PricingRule
223 + */
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];
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;
295 + }
84 296
85 - $rules = self::getPriceRules( $product_id );
86 -
87 - $type = self::getPricingType( $product_id );
88 -
89 - if ( 'fixed' === $type ) {
90 - foreach ( array_reverse( $rules, true ) as $_amount => $price ) {
91 - if ( $_amount <= $quantity ) {
92 -
93 - $product_price = $price;
94 -
95 - if ( 'view' === $context ) {
96 - $product = wc_get_product( $product_id );
97 -
98 - $product_price = self::getPriceWithTaxes( $product_price, $product, $place );
99 - }
100 -
101 - break;
102 - }
103 - }
104 - }
105 -
106 - if ( 'percentage' === $type ) {
107 - $product = wc_get_product( $product_id );
108 -
109 - foreach ( array_reverse( $rules, true ) as $_amount => $percentDiscount ) {
110 - if ( $_amount <= $quantity ) {
111 -
112 - $product_price = self::getPriceByPercentDiscount( $product->get_price(), $percentDiscount );
113 -
114 - if ( 'view' === $context ) {
115 -
116 - $product = wc_get_product( $product_id );
117 -
118 - $product_price = self::getPriceWithTaxes( $product_price, $product, $place );
119 -
120 - }
121 -
122 - break;
123 - }
124 - }
125 - }
126 -
127 - $product_price = isset( $product_price ) ? $product_price : false;
128 -
129 - return apply_filters( 'tier_pricing_table/price/price_by_rules', $product_price, $quantity, $product_id );
130 - }
131 -
132 - /**
133 - * Calculate displayed price depend on taxes
134 - *
135 - * @param float $price
136 - * @param WC_Product $product
137 - * @param string $place
138 - *
139 - * @return float
140 - */
141 - public static function getPriceWithTaxes( $price, $product, $place = 'shop' ) {
142 -
143 - if ( wc_tax_enabled() ) {
144 -
145 - if ( 'cart' === $place ) {
146 - $price = 'incl' === get_option( 'woocommerce_tax_display_cart' ) ?
147 -
148 - wc_get_price_including_tax( $product, array(
149 - 'qty' => 1,
150 - 'price' => $price
151 - )
152 - ) :
153 -
154 - wc_get_price_excluding_tax( $product, array(
155 - 'qty' => 1,
156 - 'price' => $price
157 - )
158 - );
159 - } else {
160 - $price = wc_get_price_to_display( $product, array(
161 - 'price' => $price,
162 - 'qty' => 1,
163 - ) );
164 - }
165 - }
166 -
167 - return $price;
168 - }
169 -
170 -
171 - /**
172 - * Calculate price using percentage discount
173 - *
174 - * @param float|int $price
175 - * @param float|int $discount
176 - *
177 - * @return bool|float|int
178 - */
179 - public static function getPriceByPercentDiscount( $price, $discount ) {
180 - if ( $price > 0 && $discount <= 100 ) {
181 - $discount_amount = ( $price / 100 ) * $discount;
182 -
183 - return $price - $discount_amount;
184 - }
185 -
186 - return false;
187 - }
188 -
189 - /**
190 - * Get pricing type of product. Available: fixed or percentage
191 - *
192 - * @param int $product_id
193 - * @param string $default
194 - * @param string $context
195 - *
196 - * @return string
197 - */
198 - public static function getPricingType( $product_id, $default = 'fixed', $context = 'view' ) {
199 -
200 - $type = get_post_meta( $product_id, '_tiered_price_rules_type', true );
201 -
202 - if ( 'view' === $context && self::variationHasNoOwnRules( $product_id ) ) {
203 - $product = wc_get_product( $product_id );
204 -
205 - $type = get_post_meta( $product->get_parent_id(), '_tiered_price_rules_type', true );
206 - }
207 -
208 - $type = in_array( $type, array( 'fixed', 'percentage' ) ) ? $type : $default;
209 -
210 - return apply_filters( 'tier_pricing_table/price/type', $type, $product_id );
211 - }
212 -
213 - /**
214 - * Update price rules
215 - *
216 - * @param array $amounts
217 - * @param array $prices
218 - * @param int $product_id
219 - */
220 - public static function updateFixedPriceRules( $amounts, $prices, $product_id ) {
221 - $rules = array();
222 -
223 - foreach ( $amounts as $key => $amount ) {
224 - if ( ! empty( $amount ) && ! empty( $prices[ $key ] ) && ! key_exists( $amount, $rules ) ) {
225 - $rules[ $amount ] = wc_format_decimal( $prices[ $key ] );
226 - }
227 - }
228 -
229 - update_post_meta( $product_id, '_fixed_price_rules', $rules );
230 - }
231 -
232 - /**
233 - * Update price rules
234 - *
235 - * @param array $amounts
236 - * @param array $percents
237 - * @param int $product_id
238 - */
239 - public static function updatePercentagePriceRules( $amounts, $percents, $product_id ) {
240 - $rules = array();
241 -
242 - foreach ( $amounts as $key => $amount ) {
243 - if ( ! empty( $amount ) && ! empty( $percents[ $key ] ) && ! key_exists( $amount,
244 - $rules ) && $percents[ $key ] < 99 ) {
245 - $rules[ $amount ] = $percents[ $key ];
246 - }
247 - }
248 -
249 - update_post_meta( $product_id, '_percentage_price_rules', $rules );
250 - }
251 -
252 - /**
253 - * Update product pricing type
254 - *
255 - * @param int $product_id
256 - * @param string $type
257 - */
258 - public static function updatePriceRulesType( $product_id, $type ) {
259 - if ( in_array( $type, array( 'percentage', 'fixed' ) ) ) {
260 - update_post_meta( $product_id, '_tiered_price_rules_type', $type );
261 - }
262 - }
263 -
264 - /**
265 - * Get minimum product qty for table
266 - *
267 - * @param int $product_id
268 - * @param string $context
269 - *
270 - * @return int
271 - */
272 - public static function getProductQtyMin( $product_id, $context = 'view' ) {
273 -
274 - if ( 'view' === $context && self::variationHasNoOwnRules( $product_id ) ) {
275 - $product = wc_get_product( $product_id );
276 - $product_id = $product->get_parent_id();
277 - }
278 -
279 - $min = get_post_meta( $product_id, '_tiered_price_minimum_qty', true );
280 -
281 - $min = $min ? intval( $min ) : 1;
282 -
283 - return apply_filters( 'tier_pricing_table/price/minimum', $min, $product_id );
284 - }
285 -
286 - /**
287 - * Update product min
288 - *
289 - * @param int $product_id
290 - * @param int $min
291 - */
292 - public static function updateProductQtyMin( $product_id, $min ) {
293 -
294 - $min = intval( $min );
295 -
296 - if ( $min > 0 ) {
297 - update_post_meta( $product_id, '_tiered_price_minimum_qty', $min );
298 - }
299 - }
300 -
301 - /**
302 - * Check if variation has no own rules
303 - *
304 - * @param int $product_id
305 - * @param bool $rules
306 - *
307 - * @return bool
308 - */
309 - protected static function variationHasNoOwnRules( $product_id, $rules = false ) {
310 -
311 - $rules = $rules ? $rules : self::getPriceRules( $product_id, false, 'edit' );
312 -
313 - if ( empty( $rules ) ) {
314 -
315 - $product = wc_get_product( $product_id );
316 -
317 - return $product->is_type( 'variation' );
318 - }
319 -
320 - return false;
321 - }
322 297 }