PluginProbe
WCPOS – Point of Sale (POS) plugin for WooCommerce / 1.10.0
WCPOS – Point of Sale (POS) plugin for WooCommerce v1.10.0
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
woocommerce-pos / includes / Sync / Augmentation_Pipeline.php

Augmentation_Pipeline.php in WCPOS – Point of Sale (POS) plugin for WooCommerce 1.10.0, at includes/Sync/Augmentation_Pipeline.php

259 lines 9.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * WCPOS sync augmentation pipeline.
4 *
5 * @package WCPOS\WooCommercePOS\Sync
6 */
7
8 namespace WCPOS\WooCommercePOS\Sync;
9
10 /**
11 * THE augmentation pipeline behind both sync read lanes.
12 *
13 * A served record reaches the client down one of two lanes:
14 *
15 * - the BATCH lane — the catalog proxy hands a whole wc/v3 LIST to the public
16 * `woocommerce_pos_sync_proxy_response` filter;
17 * - the PER-OBJECT lane — {@see Product_Serializer} hands ONE serialized product
18 * to the public `woocommerce_pos_sync_serialized_product` filter.
19 *
20 * Every augmentation used to be wired to BOTH filters by hand, so each stamper
21 * shipped as a pair of near-identical twins (`stamp_proxy_*` / `stamp_serialized_*`)
22 * and a fix to one silently missed the other. The pipeline is the single place an
23 * augmentation is declared; the two public filter names survive as thin
24 * PROJECTIONS of it, so third-party code that hooks either name keeps working at
25 * the priorities it always used.
26 *
27 * Two kinds of entry:
28 *
29 * - RECORD augmenters — `callable( array $payload, mixed $object, mixed $request ): array`.
30 * Declared ONCE and projected onto both lanes; on the batch lane the pipeline
31 * walks the list and hands each record over with a null `$object`, which is
32 * exactly the per-object lane's contract (an augmenter that needs the WC object
33 * resolves it lazily by id, so a page of simple products never pays for a load
34 * it does not use).
35 * - LANE augmenters — pinned to one lane because they must see the WHOLE batch.
36 * The uuid, revision and digest stampers bulk-read their meta store in ONE query
37 * per page; folding them into the record loop would be N queries per page.
38 *
39 * Priorities are preserved verbatim from the hand-wiring: the pipeline adds ONE
40 * projection callback per (lane, priority) group, so a record augmenter declared
41 * at 10 still runs at 10 relative to any external hooker.
42 *
43 * `Meta_Normalizer` is deliberately NOT registered here: it also normalizes the
44 * ORDER lane, so it keeps its own three-filter registrar and stays at priority 5,
45 * ahead of everything this pipeline installs.
46 */
47 final class Augmentation_Pipeline {
48 /**
49 * The batch lane's public filter name.
50 */
51 public const PROXY_FILTER = 'woocommerce_pos_sync_proxy_response';
52
53 /**
54 * The per-object product lane's public filter name.
55 */
56 public const SERIALIZED_FILTER = 'woocommerce_pos_sync_serialized_product';
57
58 /**
59 * Lane id for the catalog-proxy batch response.
60 */
61 public const LANE_PROXY = 'proxy';
62
63 /**
64 * Lane id for the per-object serialized product payload.
65 */
66 public const LANE_SERIALIZED = 'serialized';
67
68 /**
69 * Registered record augmenters.
70 *
71 * @var array<int, array{callback: callable, priority: int, lanes: string[], resource: string}>
72 */
73 private static $record_augmenters = array();
74
75 /**
76 * Projection callbacks added to the public filters, kept for uninstall.
77 *
78 * @var array<int, array{0: string, 1: callable, 2: int}>
79 */
80 private static $projections = array();
81
82 /**
83 * Declare a per-record augmenter.
84 *
85 * @param callable $callback callable( array $payload, mixed $object, mixed $request ): array.
86 * @param int $priority Filter priority, matching the lane's public filter.
87 * @param string[] $lanes Lanes this augmenter serves. Defaults to both.
88 * @param string $resource Batch-lane resource slug this augmenter serves.
89 */
90 public static function add_record_augmenter( callable $callback, int $priority = 10, array $lanes = array( self::LANE_PROXY, self::LANE_SERIALIZED ), string $resource = 'products' ): void {
91 self::$record_augmenters[] = array(
92 'callback' => $callback,
93 'priority' => $priority,
94 'lanes' => $lanes,
95 'resource' => $resource,
96 );
97 }
98
99 /**
100 * Wire the default WCPOS augmentations onto both public filters.
101 *
102 * Called once from {@see \WCPOS\WooCommercePOS\Init} when the sync feature is
103 * enabled and the schema latch is healthy.
104 */
105 public static function install(): void {
106 self::reset();
107
108 // Batch-lane-only augmenters. Each bulk-reads its meta store once per page,
109 // so it MUST see the whole list; each keeps its own registrar because the
110 // registrars are also the seams the tests and the ops endpoints drive.
111 // Revision runs at 9 — BEFORE uuid/digest augmentation — so the stamped
112 // bytes equal what the write path recomputes from a bare wc/v3 re-read.
113 // The digest registrar also owns the ORDER pull lane's stamper, so every
114 // served-record augmentation is wired from here and nowhere else.
115 Revision::register_proxy_stamps();
116 Proxy_Uuid_Stamper::register_proxy_stampers();
117 Integrity_Digest::register_proxy_digest_stampers();
118
119 // Per-object-lane-only identity stamp: the batch lane's identity twin is
120 // Proxy_Uuid_Stamper above, which bulk-reads instead of loading per record.
121 self::add_record_augmenter(
122 array( Pos_Uuid::class, 'stamp_serialized_record' ),
123 10,
124 array( self::LANE_SERIALIZED )
125 );
126
127 // Declared ONCE, projected onto both lanes.
128 self::add_record_augmenter( array( Variable_Prices::class, 'augment_record' ), 10 );
129 self::add_record_augmenter( array( Product_Images::class, 'augment_record' ), 10 );
130
131 self::wire();
132 }
133
134 /**
135 * Project the current registry onto the two public filters.
136 *
137 * Adds ONE projection callback per (lane, priority) group, replacing any
138 * projections a previous call installed. Call this after declaring extra
139 * augmenters with {@see add_record_augmenter}.
140 */
141 public static function wire(): void {
142 self::unwire();
143 foreach ( self::grouped( self::LANE_PROXY ) as $priority => $entries ) {
144 $callback = static function ( $data, $resource = '', $request = null ) use ( $entries ) {
145 return self::apply_to_list( $data, $resource, $request, $entries );
146 };
147 add_filter( self::PROXY_FILTER, $callback, $priority, 3 );
148 self::$projections[] = array( self::PROXY_FILTER, $callback, $priority );
149 }
150 foreach ( self::grouped( self::LANE_SERIALIZED ) as $priority => $entries ) {
151 $callback = static function ( $payload, $object = null, $request = null ) use ( $entries ) {
152 return self::apply_to_record( $payload, $object, $request, $entries );
153 };
154 add_filter( self::SERIALIZED_FILTER, $callback, $priority, 3 );
155 self::$projections[] = array( self::SERIALIZED_FILTER, $callback, $priority );
156 }
157 }
158
159 /**
160 * Remove every projection this pipeline installed and forget the registry.
161 *
162 * The batch-lane registrars own their own unregister seams, so callers that
163 * need a full teardown call those too.
164 */
165 public static function reset(): void {
166 self::unwire();
167 self::$record_augmenters = array();
168 }
169
170 /**
171 * Apply the per-object lane's record augmenters to ONE serialized payload.
172 *
173 * @param mixed $payload Serialized product payload.
174 * @param mixed $object Product or variation backing the payload.
175 * @param mixed $request Serialization context.
176 * @param array $entries Record augmenters to apply.
177 *
178 * @return mixed
179 */
180 private static function apply_to_record( $payload, $object, $request, array $entries ) {
181 if ( ! \is_array( $payload ) ) {
182 return $payload;
183 }
184 foreach ( $entries as $entry ) {
185 $result = ( $entry['callback'] )( $payload, $object, $request );
186 $payload = \is_array( $result ) ? $result : $payload;
187 }
188
189 return $payload;
190 }
191
192 /**
193 * Apply the batch lane's record augmenters across a proxied list.
194 *
195 * Entries are applied OUTERMOST — the whole list passes through one augmenter
196 * before the next — preserving the order the hand-wired twins ran in.
197 *
198 * @param mixed $data Proxied response list.
199 * @param mixed $resource Proxy resource slug.
200 * @param mixed $request Request context.
201 * @param array $entries Record augmenters to apply.
202 *
203 * @return mixed
204 */
205 private static function apply_to_list( $data, $resource, $request, array $entries ) {
206 if ( ! \is_array( $data ) ) {
207 return $data;
208 }
209 foreach ( $entries as $entry ) {
210 if ( $entry['resource'] !== $resource ) {
211 continue;
212 }
213 foreach ( $data as $index => $record ) {
214 if ( ! \is_array( $record ) ) {
215 continue;
216 }
217 // The batch lane has no loaded object: augmenters resolve it lazily
218 // by id, exactly as they do on the per-object lane.
219 $result = ( $entry['callback'] )( $record, null, $request );
220 if ( \is_array( $result ) ) {
221 $data[ $index ] = $result;
222 }
223 }
224 }
225
226 return $data;
227 }
228
229 /**
230 * Detach every projection callback this pipeline added, keeping the registry.
231 */
232 private static function unwire(): void {
233 foreach ( self::$projections as $projection ) {
234 remove_filter( $projection[0], $projection[1], $projection[2] );
235 }
236 self::$projections = array();
237 }
238
239 /**
240 * Group one lane's record augmenters by priority, ascending, registration-stable.
241 *
242 * @param string $lane Lane id.
243 *
244 * @return array<int, array<int, array{callback: callable, priority: int, lanes: string[], resource: string}>>
245 */
246 private static function grouped( string $lane ): array {
247 $groups = array();
248 foreach ( self::$record_augmenters as $entry ) {
249 if ( ! \in_array( $lane, $entry['lanes'], true ) ) {
250 continue;
251 }
252 $groups[ $entry['priority'] ][] = $entry;
253 }
254 ksort( $groups );
255
256 return $groups;
257 }
258 }
259