PluginProbe
Discount Rules for WooCommerce – Disco | Dynamic Pricing, Conditions, Bulk, Bundle, BOGO / trunk
Discount Rules for WooCommerce – Disco | Dynamic Pricing, Conditions, Bulk, Bundle, BOGO vtrunk
1.4.13 1.4.12 1.4.11 1.4.10 1.4.9 1.4.8 1.4.7 1.4.6 1.4.5 1.4.4 1.4.3 1.4.2 1.4.1 1.4.0 1.3.54 1.3.53 1.3.52 1.3.51 1.3.50 1.3.49 1.3.48 1.3.47 1.3.46 1.3.45 1.3.44 All 176 releases
disco / functions / order.php

order.php in Discount Rules for WooCommerce – Disco | Dynamic Pricing, Conditions, Bulk, Bundle, BOGO trunk, at functions/order.php

239 lines 7.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Disco
4 *
5 * @package Disco
6 * @author Ohidul Islam <wahid0003@gmail.com>
7 * @link http://domain.tld
8 * @license GPL 2.0+
9 * @copyright 2022 WebAppick
10 */
11
12 // Ensure the file is not accessed directly.
13 if ( ! defined( 'ABSPATH' ) ) {
14 exit;
15 }
16
17 if ( ! function_exists( 'disco_add_order_meta' ) ) {
18
19 /**
20 * Get a campaign id from WC session and set into post meta after place order.
21 * Unset WC session after update post-meta.
22 *
23 * @param int $order_id Order ID.
24 * @return void
25 */
26 function disco_add_order_meta( $order_id ) {
27 // Validate session exists
28 if ( ! WC()->session ) {
29 return;
30 }
31
32 $campaigns = WC()->session->get( 'disco_campaign' );
33
34 if ( empty( $campaigns ) || ! is_array( $campaigns ) ) {
35 return;
36 }
37
38 $order = wc_get_order( $order_id );
39
40 // Validate order exists
41 if ( ! $order instanceof WC_Order ) {
42 return;
43 }
44
45 /**
46 * Add each campaign ID as separate meta entry.
47 * Using unique=false allows multiple campaign IDs per order.
48 */
49 /**
50 * Guard against double execution.
51 *
52 * This callback fires on both woocommerce_thankyou and
53 * woocommerce_payment_complete. Depending on the gateway and page
54 * reloads either can run first, so skip if campaign meta is already
55 * stored to avoid duplicate disco_campaign entries.
56 */
57 $existing = $order->get_meta( 'disco_campaign', false );
58
59 if ( ! empty( $existing ) ) {
60 WC()->session->__unset( 'disco_campaign' );
61
62 return;
63 }
64
65 foreach ( $campaigns as $campaign_id ) {
66 $order->add_meta_data( 'disco_campaign', (int) $campaign_id, false );
67 }
68
69 // Save once after all meta is added (more efficient)
70 $order->save();
71
72 WC()->session->__unset( 'disco_campaign' );
73
74 // Drop the cached usage counts for the campaigns this order just used.
75 foreach ( $campaigns as $campaign_id ) {
76 \Disco\App\Features\UserLimit::flush_cache( (int) $campaign_id );
77 }
78
79 // Clear price cache so user limits are re-evaluated
80 if ( !function_exists( 'disco_clear_price_cache' ) ) {
81 return;
82 }
83
84 disco_clear_price_cache();
85 }
86
87 add_action( 'woocommerce_thankyou', 'disco_add_order_meta', PHP_INT_MAX );
88 add_action( 'woocommerce_payment_complete', 'disco_add_order_meta', PHP_INT_MAX );
89 }
90
91 if ( ! function_exists( 'disco_flush_campaign_usage_cache_on_status_change' ) ) {
92
93 /**
94 * Invalidate cached campaign usage counts when an order changes status.
95 *
96 * The usage count only counts orders in processing / on-hold / completed, so
97 * any status transition can change it. Flushing here keeps the cached count
98 * correct without querying on every cart or fragment-refresh request.
99 *
100 * @param int $order_id Order ID.
101 * @return void
102 */
103 function disco_flush_campaign_usage_cache_on_status_change( $order_id ) {
104 $order = wc_get_order( $order_id );
105
106 if ( ! $order instanceof WC_Order ) {
107 return;
108 }
109
110 $meta = $order->get_meta( 'disco_campaign', false );
111
112 if ( empty( $meta ) || ! is_array( $meta ) ) {
113 return;
114 }
115
116 foreach ( wp_list_pluck( $meta, 'value' ) as $campaign_id ) {
117 \Disco\App\Features\UserLimit::flush_cache( (int) $campaign_id );
118 }
119 }
120
121 add_action( 'woocommerce_order_status_changed', 'disco_flush_campaign_usage_cache_on_status_change', 10, 1 );
122 add_action( 'woocommerce_trash_order', 'disco_flush_campaign_usage_cache_on_status_change', 10, 1 );
123 add_action( 'woocommerce_untrash_order', 'disco_flush_campaign_usage_cache_on_status_change', 10, 1 );
124 add_action( 'woocommerce_before_delete_order', 'disco_flush_campaign_usage_cache_on_status_change', 10, 1 );
125 add_action( 'woocommerce_delete_order', 'disco_flush_campaign_usage_cache_on_status_change', 10, 1 );
126 }
127
128 if ( ! function_exists( 'disco_flush_campaign_usage_cache_on_meta_write' ) ) {
129
130 /**
131 * Invalidate cached campaign usage counts when disco_campaign meta is written
132 * outside of this plugin.
133 *
134 * Imports, migrations and third-party code write order meta directly rather
135 * than going through disco_add_order_meta(), so without this the cached count
136 * would stay stale until its TTL expired.
137 *
138 * @param int|array $meta_id Meta ID (array on delete).
139 * @param int $object_id Order ID.
140 * @param string $meta_key Meta key.
141 * @param mixed $meta_value Meta value.
142 * @return void
143 */
144 // $meta_id and $object_id are unused but required: the meta hooks pass their
145 // arguments positionally, so $meta_key and $meta_value cannot be reached
146 // without declaring them.
147 function disco_flush_campaign_usage_cache_on_meta_write( $meta_id, $object_id, $meta_key, $meta_value ) { //phpcs:ignore
148 if ( 'disco_campaign' !== $meta_key ) {
149 return;
150 }
151
152 \Disco\App\Features\UserLimit::flush_cache( (int) $meta_value );
153 }
154
155 // Legacy (post table) orders.
156 add_action( 'added_post_meta', 'disco_flush_campaign_usage_cache_on_meta_write', 10, 4 );
157 add_action( 'updated_post_meta', 'disco_flush_campaign_usage_cache_on_meta_write', 10, 4 );
158 add_action( 'deleted_post_meta', 'disco_flush_campaign_usage_cache_on_meta_write', 10, 4 );
159
160 // HPOS orders.
161 add_action( 'added_wc_order_meta', 'disco_flush_campaign_usage_cache_on_meta_write', 10, 4 );
162 add_action( 'updated_wc_order_meta', 'disco_flush_campaign_usage_cache_on_meta_write', 10, 4 );
163 add_action( 'deleted_wc_order_meta', 'disco_flush_campaign_usage_cache_on_meta_write', 10, 4 );
164 }
165
166 if ( ! function_exists( 'disco_reset_campaign_session' ) ) {
167
168 /**
169 * Clear the disco_campaign session before cart totals are recalculated.
170 *
171 * This prevents stale campaign IDs (from previously discounted products
172 * that were later removed from the cart) from being saved to order meta.
173 * The session is rebuilt fresh each time checkout prices are recalculated.
174 *
175 * @return void
176 */
177 function disco_reset_campaign_session() {
178 if ( ! is_checkout() ) {
179 return;
180 }
181
182 if ( ! WC()->session ) {
183 return;
184 }
185
186 WC()->session->__unset( 'disco_campaign' );
187 }
188
189 add_action( 'woocommerce_before_calculate_totals', 'disco_reset_campaign_session', 0 );
190 }
191
192 if ( ! function_exists( 'disco_add_free_shipping_order_meta' ) ) {
193
194 /**
195 * Persist free-shipping campaign IDs to the order meta during checkout.
196 *
197 * Free shipping is applied through the woocommerce_package_rates filter,
198 * whose results WooCommerce caches per package. Because of that cache, the
199 * filter is not guaranteed to run on the final order-placement recalculation,
200 * so the campaign ID cannot be reliably staged in the WC session like the
201 * product and cart intents are. Instead we evaluate the Shipping intents
202 * fresh here, where the order object and cart are both available, and write
203 * the campaign IDs straight to the order.
204 *
205 * @param \WC_Order $order The order being created.
206 * @return void
207 */
208 function disco_add_free_shipping_order_meta( $order ) {
209 if ( ! $order instanceof WC_Order ) {
210 return;
211 }
212
213 $campaign_ids = ( new \Disco\App\Disco )->get_applied_free_shipping_campaign_ids();
214
215 if ( empty( $campaign_ids ) ) {
216 return;
217 }
218
219 // Avoid duplicating IDs already staged from product/cart intents.
220 $existing = array_map( 'intval', $order->get_meta( 'disco_campaign', false ) ? wp_list_pluck( $order->get_meta( 'disco_campaign', false ), 'value' ) : array() );
221
222 foreach ( $campaign_ids as $campaign_id ) {
223 if ( in_array( (int) $campaign_id, $existing, true ) ) {
224 continue;
225 }
226
227 $order->add_meta_data( 'disco_campaign', (int) $campaign_id, false );
228 }
229 }
230
231 // Classic (shortcode) checkout: order is saved by WC after this action.
232 add_action( 'woocommerce_checkout_create_order', 'disco_add_free_shipping_order_meta', 20 );
233
234 // Block / Store API checkout: WC_Checkout::create_order() does not run, so
235 // the action above never fires. This Store API hook also passes the WC_Order
236 // (as its first arg) before the order is persisted.
237 add_action( 'woocommerce_store_api_checkout_update_order_meta', 'disco_add_free_shipping_order_meta', 20 );
238 }
239