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
tier-pricing-table / src / Addons / TieredPricingCart / TieredPricingCartService.php

TieredPricingCartService.php in Tiered Pricing Table for WooCommerce 8.0.2, at src/Addons/TieredPricingCart/TieredPricingCartService.php

302 lines 12.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace TierPricingTable\Addons\TieredPricingCart;
4
5 /*
6 * Class TieredPricingCartService
7 *
8 * Service modifies product's price in the cart based on quantity and tiered pricing rules
9 *
10 * @package TierPricingTable\Addons\TieredPricingCart
11 */
12 use TierPricingTable\CalculationLogic;
13 use TierPricingTable\Core\ServiceContainerTrait;
14 use TierPricingTable\PriceManager;
15 use WC_Product;
16 use WC_Cart;
17 class TieredPricingCartService {
18 use ServiceContainerTrait;
19 protected $originalPrices = [];
20
21 public function __construct() {
22 add_action(
23 'woocommerce_before_calculate_totals',
24 array($this, 'calculateTieredPricingInCart'),
25 99999,
26 3
27 );
28 add_action(
29 'woocommerce_before_mini_cart_contents',
30 array($this, 'triggerMiniCartUpdate'),
31 9999,
32 3
33 );
34 add_filter(
35 'woocommerce_cart_item_price',
36 array($this, 'calculateCartItemPrice'),
37 999,
38 2
39 );
40 add_filter(
41 'woocommerce_cart_item_subtotal',
42 array($this, 'modifyCartItemSubtotal'),
43 999,
44 3
45 );
46 }
47
48 /**
49 * The "Total savings" row: what the tiered prices took off the cart, before the order total in the
50 * cart totals and in the checkout order review.
51 */
52 public function renderTotalSavings() {
53 if ( !CartOptionsSubsection::showTotalSavings() ) {
54 return;
55 }
56 $savings = $this->getTotalSavings();
57 if ( $savings <= 0 ) {
58 return;
59 }
60 $label = CartOptionsSubsection::getTotalSavingsLabel();
61 ?>
62 <tr class="tpt-cart-total-savings">
63 <th><?php
64 echo esc_html( $label );
65 ?></th>
66 <td data-title="<?php
67 echo esc_attr( $label );
68 ?>"><?php
69 echo wp_kses_post( wc_price( $savings ) );
70 ?></td>
71 </tr>
72 <?php
73 }
74
75 /**
76 * The sum of every line's saving: the original price (regular or sale, per the cart option) against
77 * the tiered price, both as displayed (with or without tax) times the quantity.
78 */
79 public function getTotalSavings() : float {
80 $cart = wc()->cart;
81 if ( !$cart instanceof WC_Cart ) {
82 return 0;
83 }
84 $considerSalePriceAsDiscount = $this->getContainer()->getSettings()->get( 'consider_sale_price_as_discount_in_cart', 'no' ) === 'yes';
85 $total = 0;
86 foreach ( $cart->get_cart() as $cartItemKey => $cartItem ) {
87 $newPrice = $this->getCartItemPrice( $cartItem, $cartItemKey, $cart );
88 if ( false === $newPrice ) {
89 continue;
90 }
91 $product = wc_get_product( $cartItem['data']->get_id() );
92 $originalPrice = ( $considerSalePriceAsDiscount ? $product->get_regular_price() : $product->get_price() );
93 if ( '' === $originalPrice || null === $originalPrice ) {
94 continue;
95 }
96 $quantity = (int) $cartItem['quantity'];
97 if ( $product->is_taxable() ) {
98 $withTax = $cart->display_prices_including_tax();
99 $original = ( $withTax ? wc_get_price_including_tax( $product, array(
100 'qty' => $quantity,
101 'price' => $originalPrice,
102 ) ) : wc_get_price_excluding_tax( $product, array(
103 'qty' => $quantity,
104 'price' => $originalPrice,
105 ) ) );
106 $new = ( $withTax ? wc_get_price_including_tax( $product, array(
107 'qty' => $quantity,
108 'price' => $newPrice,
109 ) ) : wc_get_price_excluding_tax( $product, array(
110 'qty' => $quantity,
111 'price' => $newPrice,
112 ) ) );
113 } else {
114 $original = (float) $originalPrice * $quantity;
115 $new = (float) $newPrice * $quantity;
116 }
117 $total += max( 0, (float) $original - (float) $new );
118 }
119 return (float) apply_filters( 'tiered_pricing_table/cart/total_savings', round( $total, wc_get_price_decimals() ), $cart );
120 }
121
122 public function modifyCartItemSubtotal( $subtotal, $cartItem, $cartItemKey ) {
123 $newPrice = $this->getCartItemPrice( $cartItem, $cartItemKey );
124 if ( false === $newPrice ) {
125 return $subtotal;
126 }
127 $recalculateCartItemSubtotal = apply_filters(
128 'tiered_pricing_table/cart/recalculate_cart_item_subtotal',
129 true,
130 $cartItem,
131 $cartItemKey,
132 $subtotal
133 );
134 if ( !$recalculateCartItemSubtotal ) {
135 return $subtotal;
136 }
137 $considerSalePriceAsDiscount = $this->getContainer()->getSettings()->get( 'consider_sale_price_as_discount_in_cart', 'no' ) === 'yes';
138 $considerSalePriceAsDiscount = apply_filters(
139 'tiered_pricing_table/cart/subtotal/consider_sale_price_as_discount',
140 $considerSalePriceAsDiscount,
141 $cartItem,
142 $cartItemKey
143 );
144 // Reset product instance because prices is already modified in the "woocommerce_before_calculate_totals" hook.
145 // We will not be able to get the original price
146 $product = wc_get_product( $cartItem['data']->get_id() );
147 if ( $product->is_taxable() ) {
148 if ( wc()->cart->display_prices_including_tax() ) {
149 $originalCartItemPrice = wc_get_price_including_tax( $product, array(
150 'qty' => $cartItem['quantity'],
151 'price' => ( $considerSalePriceAsDiscount ? $product->get_regular_price() : $product->get_price() ),
152 ) );
153 $newCartItemPrice = wc_get_price_including_tax( $product, array(
154 'qty' => $cartItem['quantity'],
155 'price' => $newPrice,
156 ) );
157 $originalProductSubtotal = wc_price( $originalCartItemPrice );
158 $newProductSubtotal = wc_price( $newCartItemPrice );
159 if ( !wc_prices_include_tax() && wc()->cart->get_subtotal_tax() > 0 ) {
160 $newProductSubtotal .= ' <small class="tax_label">' . WC()->countries->inc_tax_or_vat() . '</small>';
161 }
162 } else {
163 $originalCartItemPrice = wc_get_price_excluding_tax( $product, array(
164 'qty' => $cartItem['quantity'],
165 'price' => ( $considerSalePriceAsDiscount ? $product->get_regular_price() : $product->get_price() ),
166 ) );
167 $newCartItemPrice = wc_get_price_excluding_tax( $product, array(
168 'qty' => $cartItem['quantity'],
169 'price' => $newPrice,
170 ) );
171 $originalProductSubtotal = wc_price( $originalCartItemPrice );
172 $newProductSubtotal = wc_price( $newCartItemPrice );
173 if ( wc_prices_include_tax() && wc()->cart->get_subtotal_tax() > 0 ) {
174 $newProductSubtotal .= ' <small class="tax_label">' . WC()->countries->ex_tax_or_vat() . '</small>';
175 }
176 }
177 } else {
178 $_originalItemPrice = ( $considerSalePriceAsDiscount ? $product->get_regular_price() : $product->get_price() );
179 $originalCartItemPrice = (float) $_originalItemPrice * (float) $cartItem['quantity'];
180 $newCartItemPrice = (float) $newPrice * (float) $cartItem['quantity'];
181 $originalProductSubtotal = wc_price( $originalCartItemPrice );
182 $newProductSubtotal = wc_price( $newCartItemPrice );
183 }
184 return $newProductSubtotal;
185 }
186
187 public function getCartItemPrice( $cartItem, $cartItemKey, $cart = null ) {
188 $cart = ( $cart instanceof WC_Cart ? $cart : wc()->cart );
189 if ( !$cart instanceof WC_Cart ) {
190 return false;
191 }
192 $calculateTieredPriceForItem = apply_filters(
193 'tiered_pricing_table/cart/need_price_recalculation',
194 true,
195 $cartItem,
196 $cart
197 );
198 if ( !$calculateTieredPriceForItem ) {
199 return false;
200 }
201 if ( empty( $cartItem['data'] ) || !$cartItem['data'] instanceof WC_Product ) {
202 return false;
203 }
204 $pricingRule = PriceManager::getPricingRule( $cartItem['data']->get_id() );
205 $totalQuantity = $this->getTotalProductCount( $cartItem );
206 $newPrice = $pricingRule->getTierPrice( $totalQuantity, false, 'cart' );
207 return apply_filters(
208 'tiered_pricing_table/cart/product_cart_price',
209 $newPrice,
210 $cartItem,
211 $cartItemKey,
212 $totalQuantity
213 );
214 }
215
216 /**
217 * Calculate price by quantity rules
218 *
219 * @param WC_Cart $cart
220 */
221 public function calculateTieredPricingInCart( WC_Cart $cart ) {
222 if ( !empty( $cart->get_cart_contents() ) ) {
223 foreach ( $cart->get_cart_contents() as $key => $cartItem ) {
224 $newPrice = $this->getCartItemPrice( $cartItem, $key, $cart );
225 if ( false !== $newPrice ) {
226 if ( !isset( $this->originalPrices[$key] ) ) {
227 // Store the raw price prop ("edit" context) on purpose. The "view" context runs the
228 // "woocommerce_product_get_price" filter, which both RegularPricingService and currency
229 // switchers hook into. Storing an already filtered value and restoring it with
230 // set_price() would apply those filters a second time.
231 $this->originalPrices[$key] = $cartItem['data']->get_price( 'edit' );
232 }
233 $cartItem['data']->set_price( $newPrice );
234 $cartItem['data']->add_meta_data( 'tiered_pricing_cart_price_calculated', 'yes' );
235 } else {
236 if ( isset( $this->originalPrices[$key] ) ) {
237 // Fully revert the modification: put the raw price prop back and drop the flag, so the
238 // "woocommerce_product_get_price" filters (role-based pricing, currency conversion)
239 // apply to the restored price exactly once, as they would have without this service.
240 $cartItem['data']->set_price( $this->originalPrices[$key] );
241 $cartItem['data']->delete_meta_data( 'tiered_pricing_cart_price_calculated' );
242 unset($this->originalPrices[$key]);
243 }
244 }
245 // Update tiered pricing data
246 $cart->cart_contents[$key]['tiered_pricing_data'] = array(
247 'total_item_quantity' => $this->getTotalProductCount( $cartItem ),
248 );
249 }
250 }
251 }
252
253 /**
254 * Calculate price in mini cart
255 *
256 * @param string $price
257 * @param array $cartItem
258 *
259 * @return string
260 */
261 public function calculateCartItemPrice( $price, $cartItem ) {
262 $calculateTieredPriceForItem = apply_filters( 'tiered_pricing_table/cart/need_price_recalculation/item', true, $cartItem );
263 if ( $cartItem['data'] instanceof WC_Product && $calculateTieredPriceForItem ) {
264 $product = wc_get_product( $cartItem['data']->get_id() );
265 $pricingRule = PriceManager::getPricingRule( $cartItem['data']->get_id() );
266 $newPrice = $pricingRule->getTierPrice( $this->getTotalProductCount( $cartItem ), true, 'cart' );
267 $newPrice = apply_filters( 'tiered_pricing_table/cart/product_cart_price/item', $newPrice, $cartItem );
268 if ( false !== $newPrice ) {
269 return wc_price( $newPrice );
270 }
271 }
272 return $price;
273 }
274
275 public function triggerMiniCartUpdate() {
276 $cart = wc()->cart;
277 $cart->calculate_totals();
278 }
279
280 /**
281 * Get total product count depends on user's settings
282 *
283 * @param ?array $cartItem
284 *
285 * @return int
286 */
287 public function getTotalProductCount( ?array $cartItem ) : int {
288 if ( CalculationLogic::considerProductVariationAsOneProduct() ) {
289 $count = 0;
290 foreach ( wc()->cart->cart_contents as $cart_content ) {
291 if ( $cart_content['product_id'] == $cartItem['product_id'] ) {
292 $count += $cart_content['quantity'];
293 }
294 }
295 } else {
296 $count = $cartItem['quantity'];
297 }
298 return (int) apply_filters( 'tiered_pricing_table/cart/total_product_count', $count, $cartItem );
299 }
300
301 }
302