PluginProbe
Discount Rules for WooCommerce – Disco | Dynamic Pricing, Conditions, Bulk, Bundle, BOGO / 1.4.21
Discount Rules for WooCommerce – Disco | Dynamic Pricing, Conditions, Bulk, Bundle, BOGO v1.4.21
1.4.21 1.4.20 1.4.19 1.4.18 1.4.17 1.4.16 1.4.15 1.4.14 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 All 184 releases
← All changes | functions/order.php +191 -1 1.4.7 → 1.4.21 View file →
@@ -56,9 +56,9 @@
56 56 */
57 57 $existing = $order->get_meta( 'disco_campaign', false );
58 58
59 59 if ( ! empty( $existing ) ) {
60 - WC()->session->__unset( 'disco_campaign' );
60 + disco_finalise_campaign_usage( $order );
61 61
62 62 return;
63 63 }
64 64
@@ -70,8 +70,13 @@
70 70 $order->save();
71 71
72 72 WC()->session->__unset( 'disco_campaign' );
73 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 +
74 79 // Clear price cache so user limits are re-evaluated
75 80 if ( !function_exists( 'disco_clear_price_cache' ) ) {
76 81 return;
77 82 }
@@ -80,8 +85,193 @@
80 85 }
81 86
82 87 add_action( 'woocommerce_thankyou', 'disco_add_order_meta', PHP_INT_MAX );
83 88 add_action( 'woocommerce_payment_complete', 'disco_add_order_meta', PHP_INT_MAX );
89 +}
90 +
91 +if ( ! function_exists( 'disco_finalise_campaign_usage' ) ) {
92 +
93 + /**
94 + * Settle the caches and session for an order that already carries its meta.
95 + *
96 + * Normally the meta was written by disco_add_campaign_order_meta() as the
97 + * order was created. The usage caches have to be dropped here rather than
98 + * there: at creation the order is not persisted yet, so a flush would
99 + * immediately repopulate the old count from the database.
100 + *
101 + * @param \WC_Order $order Order object.
102 + * @return void
103 + */
104 + function disco_finalise_campaign_usage( $order ) {
105 + foreach ( disco_campaign_ids_on_order( $order ) as $campaign_id ) {
106 + \Disco\App\Features\UserLimit::flush_cache( $campaign_id );
107 + }
108 +
109 + if ( WC()->session ) {
110 + WC()->session->__unset( 'disco_campaign' );
111 + }
112 +
113 + if ( ! function_exists( 'disco_clear_price_cache' ) ) {
114 + return;
115 + }
116 +
117 + disco_clear_price_cache();
118 + }
119 +
120 +}
121 +
122 +if ( ! function_exists( 'disco_campaign_ids_on_order' ) ) {
123 +
124 + /**
125 + * Campaign IDs already recorded on an order.
126 + *
127 + * @param \WC_Order $order Order object.
128 + * @return array<int, int>
129 + */
130 + function disco_campaign_ids_on_order( $order ): array {
131 + if ( ! $order instanceof WC_Order ) {
132 + return array();
133 + }
134 +
135 + $meta = $order->get_meta( 'disco_campaign', false );
136 +
137 + if ( empty( $meta ) ) {
138 + return array();
139 + }
140 +
141 + return array_map( 'intval', wp_list_pluck( $meta, 'value' ) );
142 + }
143 +
144 +}
145 +
146 +if ( ! function_exists( 'disco_add_campaign_order_meta' ) ) {
147 +
148 + /**
149 + * Record the applied campaigns while the order is being created.
150 + *
151 + * The campaign IDs are staged in the WC session as the cart is priced, and
152 + * were previously read back on woocommerce_thankyou / payment_complete. Those
153 + * run in a later request, which is not always the same session: an off site
154 + * gateway can return the customer on a fresh one, and a payment webhook has
155 + * no customer session at all. The handover then found nothing and returned
156 + * silently, leaving a discounted order with no campaign recorded, so its
157 + * usage limit never counted it and Analytics never saw it.
158 + *
159 + * Reading the session here instead keeps both halves in the request that
160 + * priced the cart. The later hooks stay in place as a fallback for anything
161 + * that reaches them without meta, and to drop the usage caches once the order
162 + * actually exists.
163 + *
164 + * @param \WC_Order $order The order being created.
165 + * @return void
166 + */
167 + function disco_add_campaign_order_meta( $order ) {
168 + if ( ! $order instanceof WC_Order || ! WC()->session ) {
169 + return;
170 + }
171 +
172 + $campaigns = WC()->session->get( 'disco_campaign' );
173 +
174 + if ( empty( $campaigns ) || ! is_array( $campaigns ) ) {
175 + return;
176 + }
177 +
178 + $existing = disco_campaign_ids_on_order( $order );
179 +
180 + foreach ( $campaigns as $campaign_id ) {
181 + $campaign_id = (int) $campaign_id;
182 +
183 + if ( in_array( $campaign_id, $existing, true ) ) {
184 + continue;
185 + }
186 +
187 + $order->add_meta_data( 'disco_campaign', $campaign_id, false );
188 +
189 + $existing[] = $campaign_id;
190 + }
191 + }
192 +
193 + // Classic (shortcode) checkout: order is saved by WC after this action.
194 + add_action( 'woocommerce_checkout_create_order', 'disco_add_campaign_order_meta', 10 );
195 +
196 + // Block / Store API checkout: WC_Checkout::create_order() does not run, so
197 + // the action above never fires.
198 + add_action( 'woocommerce_store_api_checkout_update_order_meta', 'disco_add_campaign_order_meta', 10 );
199 +}
200 +
201 +if ( ! function_exists( 'disco_flush_campaign_usage_cache_on_status_change' ) ) {
202 +
203 + /**
204 + * Invalidate cached campaign usage counts when an order changes status.
205 + *
206 + * The usage count only counts orders in processing / on-hold / completed, so
207 + * any status transition can change it. Flushing here keeps the cached count
208 + * correct without querying on every cart or fragment-refresh request.
209 + *
210 + * @param int $order_id Order ID.
211 + * @return void
212 + */
213 + function disco_flush_campaign_usage_cache_on_status_change( $order_id ) {
214 + $order = wc_get_order( $order_id );
215 +
216 + if ( ! $order instanceof WC_Order ) {
217 + return;
218 + }
219 +
220 + $meta = $order->get_meta( 'disco_campaign', false );
221 +
222 + if ( empty( $meta ) || ! is_array( $meta ) ) {
223 + return;
224 + }
225 +
226 + foreach ( wp_list_pluck( $meta, 'value' ) as $campaign_id ) {
227 + \Disco\App\Features\UserLimit::flush_cache( (int) $campaign_id );
228 + }
229 + }
230 +
231 + add_action( 'woocommerce_order_status_changed', 'disco_flush_campaign_usage_cache_on_status_change', 10, 1 );
232 + add_action( 'woocommerce_trash_order', 'disco_flush_campaign_usage_cache_on_status_change', 10, 1 );
233 + add_action( 'woocommerce_untrash_order', 'disco_flush_campaign_usage_cache_on_status_change', 10, 1 );
234 + add_action( 'woocommerce_before_delete_order', 'disco_flush_campaign_usage_cache_on_status_change', 10, 1 );
235 + add_action( 'woocommerce_delete_order', 'disco_flush_campaign_usage_cache_on_status_change', 10, 1 );
236 +}
237 +
238 +if ( ! function_exists( 'disco_flush_campaign_usage_cache_on_meta_write' ) ) {
239 +
240 + /**
241 + * Invalidate cached campaign usage counts when disco_campaign meta is written
242 + * outside of this plugin.
243 + *
244 + * Imports, migrations and third-party code write order meta directly rather
245 + * than going through disco_add_order_meta(), so without this the cached count
246 + * would stay stale until its TTL expired.
247 + *
248 + * @param int|array $meta_id Meta ID (array on delete).
249 + * @param int $object_id Order ID.
250 + * @param string $meta_key Meta key.
251 + * @param mixed $meta_value Meta value.
252 + * @return void
253 + */
254 + // $meta_id and $object_id are unused but required: the meta hooks pass their
255 + // arguments positionally, so $meta_key and $meta_value cannot be reached
256 + // without declaring them.
257 + function disco_flush_campaign_usage_cache_on_meta_write( $meta_id, $object_id, $meta_key, $meta_value ) { //phpcs:ignore
258 + if ( 'disco_campaign' !== $meta_key ) {
259 + return;
260 + }
261 +
262 + \Disco\App\Features\UserLimit::flush_cache( (int) $meta_value );
263 + }
264 +
265 + // Legacy (post table) orders.
266 + add_action( 'added_post_meta', 'disco_flush_campaign_usage_cache_on_meta_write', 10, 4 );
267 + add_action( 'updated_post_meta', 'disco_flush_campaign_usage_cache_on_meta_write', 10, 4 );
268 + add_action( 'deleted_post_meta', 'disco_flush_campaign_usage_cache_on_meta_write', 10, 4 );
269 +
270 + // HPOS orders.
271 + add_action( 'added_wc_order_meta', 'disco_flush_campaign_usage_cache_on_meta_write', 10, 4 );
272 + add_action( 'updated_wc_order_meta', 'disco_flush_campaign_usage_cache_on_meta_write', 10, 4 );
273 + add_action( 'deleted_wc_order_meta', 'disco_flush_campaign_usage_cache_on_meta_write', 10, 4 );
84 274 }
85 275
86 276 if ( ! function_exists( 'disco_reset_campaign_session' ) ) {
87 277