PluginProbe
WCPOS – Point of Sale (POS) plugin for WooCommerce / 1.10.19
WCPOS – Point of Sale (POS) plugin for WooCommerce v1.10.19
1.10.19 1.10.18 1.10.17 1.10.16 1.10.15 1.10.13 1.10.14 1.10.12 1.10.11 1.10.10 1.10.9 1.10.8 untagged-3d9b7ccddc54df87c672 1.10.7 1.10.6 1.10.5 1.10.3 1.10.4 1.10.2 1.10.1 1.10.0 1.9.17 1.9.15 1.9.16 1.9.14 All 163 releases
← All changes | includes/API/V2/Orders_Controller.php +54 -2 1.10.21.10.19 View file →
@@ -12,8 +12,9 @@
12 12 use WCPOS\WooCommercePOS\Sync\Order_Document;
13 13 use WCPOS\WooCommercePOS\Sync\Order_Pull_Planner;
14 14 use WCPOS\WooCommercePOS\Sync\Order_Query;
15 15 use WCPOS\WooCommercePOS\Sync\Order_Serializer;
16 +use WCPOS\WooCommercePOS\Sync\Pos_Uuid;
16 17 use WCPOS\WooCommercePOS\Sync\Sync_Journal;
17 18 use WP_REST_Controller;
18 19 use WP_REST_Request;
19 20 use WP_REST_Server;
@@ -109,9 +110,41 @@
109 110 $query = new Order_Query();
110 111 $serializer = new Order_Serializer();
111 112 $change_rows = $query->changes_after_checkpoint( $updated_at_gmt, $order_id, $sequence, $limit + 1 );
112 113 $has_more = count( $change_rows ) > $limit;
114 + // The limit+1 probe row is a paging sentinel the planner pops before serving —
115 + // it is not part of this page, so the filter must not see (or drop) its id.
116 + $page_rows = $has_more ? array_slice( $change_rows, 0, $limit ) : $change_rows;
117 + $ids = array_map( 'intval', array_column( $page_rows, 'order_id' ) );
113 118
119 + /**
120 + * Filters the order IDs eligible for the custom pull lane.
121 + *
122 + * The interim hook-parity seam for order-scoping plugins on a lane that
123 + * bypasses `woocommerce_rest_orders_prepare_object_query`. It retires with
124 + * the lane at the 1.11.0 protocol boundary (ADR 0035, #1748).
125 + *
126 + * The contract, precisely:
127 + * - NARROW ONLY. Return the subset of `$ids` to serve; ids added by the
128 + * filter are ignored by construction.
129 + * - BE DETERMINISTIC for a given client. The checkpoint advances PAST an
130 + * excluded row and its journal entry is never re-offered to that client,
131 + * so exclusion is permanent per-checkpoint: a filter whose answer
132 + * changes between pages corrupts what the till holds, and a scope that
133 + * later WIDENS only reaches clients after a full resync.
134 + * - Exclusion does not tombstone. A copy the till already holds stays
135 + * until a real delete tombstones it (deleted rows bypass this filter,
136 + * so tombstones for excluded orders still flow — which is the desired
137 + * "drop it" signal for a scoped-out order).
138 + *
139 + * @since 1.10.3
140 + *
141 + * @param int[] $ids Candidate order IDs in this pull page.
142 + * @param WP_REST_Request $request Pull request.
143 + */
144 + $allowed = array_map( 'intval', (array) apply_filters( 'woocommerce_pos_order_pull_ids', $ids, $request ) );
145 + $allowed = array_flip( $allowed ); // O(1) membership for 250-row pages.
146 +
114 147 $planner = new Order_Pull_Planner(
115 148 array(
116 149 'updatedAtGmt' => $updated_at_gmt,
117 150 'orderId' => $order_id,
@@ -122,10 +155,29 @@
122 155 );
123 156 $plan = $planner->plan(
124 157 $change_rows,
125 158 $has_more,
126 - function ( int $id ) use ( $serializer, $request ): array {
127 - return $serializer->serialize_order( $id, $request );
159 + function ( int $id ) use ( $serializer, $request, $allowed ): array {
160 + // Narrow inside serialization: removing change rows would leave a
161 + // fully filtered page unable to advance, so the client would loop forever.
162 + if ( ! isset( $allowed[ $id ] ) ) {
163 + return array();
164 + }
165 + $order = wc_get_order( $id );
166 + $had_uuid = $order && '' !== (string) $order->get_meta( Pos_Uuid::META_KEY );
167 + $payload = $serializer->serialize_order( $id, $request );
168 + if ( ! $had_uuid && array() !== $payload ) {
169 + /*
170 + * First serialization of an unstamped order MINTS its identity: the
171 + * uuid save advances the stored date_updated_gmt AFTER this payload
172 + * captured the pre-mint date. Hashing that payload would serve a
173 + * revision stale the moment it leaves — the client's next push
174 + * false-409s against a fresh re-read. Serialize again from the
175 + * settled order (a pure read now: the identity exists).
176 + */
177 + $payload = $serializer->serialize_order( $id, $request );
178 + }
179 + return $payload;
128 180 },
129 181 static function ( array $full_payload ): string {
130 182 return Order_Serializer::canonical_revision( $full_payload );
131 183 }