| 1 |
<?php |
| 2 |
/** |
| 3 |
* Coupon post-date touch shared by the v1 and v2 write lanes. |
| 4 |
* |
| 5 |
* @package WCPOS\WooCommercePOS\Sync |
| 6 |
*/ |
| 7 |
|
| 8 |
namespace WCPOS\WooCommercePOS\Sync; |
| 9 |
|
| 10 |
/** |
| 11 |
* WC's coupon CPT data store only calls wp_update_post() when a POST field |
| 12 |
* (code, description, status, dates) actually changes. A meta-backed edit — |
| 13 |
* `amount`, `discount_type`, the usage limits — is written straight to postmeta, |
| 14 |
* so `post_modified(_gmt)` is left stale. |
| 15 |
* |
| 16 |
* That is not cosmetic. The client's catalogue replication is INCREMENTAL and |
| 17 |
* date-based: it polls `?modified_after=<cursor>&fields=id,date_modified_gmt` |
| 18 |
* (packages/query/src/{collection-replication-state,data-fetcher}.ts), and |
| 19 |
* `Catalog_Proxy_Controller` forwards that parameter untouched to wc/v3, where |
| 20 |
* WooCommerce filters on `post_modified_gmt`. A coupon whose amount changed but |
| 21 |
* whose post date did not move is therefore INVISIBLE to every other till until |
| 22 |
* something else happens to touch the post. |
| 23 |
* |
| 24 |
* v1 covered this by installing a `woocommerce_update_coupon` listener for the |
| 25 |
* duration of a v1 REST dispatch (see API/V1/Coupons_Controller). The v2 push |
| 26 |
* lane forwards to stock wc/v3 and installs no such listener, so the writer has |
| 27 |
* to apply the touch itself — hence this shared helper rather than a second |
| 28 |
* copy of the SQL. |
| 29 |
* |
| 30 |
* @see https://github.com/wcpos/woocommerce-pos-pro/issues/86 |
| 31 |
*/ |
| 32 |
class Coupon_Modified_Date { |
| 33 |
/** |
| 34 |
* Listen for every coupon save, whatever wrote it. |
| 35 |
* |
| 36 |
* Registered UNCONDITIONALLY from Init — deliberately not behind the schema |
| 37 |
* latch, because this touches `wp_posts` only and the client's replication is |
| 38 |
* date-based on BOTH lanes. A cashier does not care which surface a discount |
| 39 |
* was edited from: a merchant changing a coupon amount in wp-admin, over |
| 40 |
* WP-CLI, or from another plugin has changed the coupon, and every till must |
| 41 |
* see it on its next incremental poll. v1 only ever installed this for the |
| 42 |
* duration of its own REST dispatch, so an admin-side edit was invisible to |
| 43 |
* the POS — that was a gap, not a design. |
| 44 |
* |
| 45 |
* `touch()` writes with $wpdb->update() rather than wp_update_post(), so it |
| 46 |
* cannot re-enter `woocommerce_update_coupon` — no recursion guard needed. |
| 47 |
*/ |
| 48 |
public static function register_hooks(): void { |
| 49 |
add_action( 'woocommerce_update_coupon', array( __CLASS__, 'touch' ), 10, 1 ); |
| 50 |
} |
| 51 |
|
| 52 |
/** |
| 53 |
* Advance a coupon's post_modified(_gmt) beyond its previous value. |
| 54 |
* |
| 55 |
* Unconditional by design, exactly like the v1 listener: the caller only |
| 56 |
* reaches this after a write it already knows succeeded. The stored value |
| 57 |
* must advance by at least one second because incremental queries use a |
| 58 |
* strict `modified_after` comparison and WordPress timestamps have |
| 59 |
* one-second precision. |
| 60 |
* |
| 61 |
* @param int $coupon_id The coupon post ID. |
| 62 |
*/ |
| 63 |
public static function touch( int $coupon_id ): void { |
| 64 |
global $wpdb; |
| 65 |
|
| 66 |
$previous_gmt = (string) get_post_field( 'post_modified_gmt', $coupon_id ); |
| 67 |
$modified_gmt = gmdate( |
| 68 |
'Y-m-d H:i:s', |
| 69 |
max( time(), strtotime( $previous_gmt . ' UTC' ) + 1 ) |
| 70 |
); |
| 71 |
|
| 72 |
$wpdb->update( |
| 73 |
$wpdb->posts, |
| 74 |
array( |
| 75 |
'post_modified' => get_date_from_gmt( $modified_gmt ), |
| 76 |
'post_modified_gmt' => $modified_gmt, |
| 77 |
), |
| 78 |
array( 'ID' => $coupon_id ), |
| 79 |
array( '%s', '%s' ), |
| 80 |
array( '%d' ) |
| 81 |
); |
| 82 |
|
| 83 |
clean_post_cache( $coupon_id ); |
| 84 |
} |
| 85 |
} |
| 86 |
|