PluginProbe
WCPOS – Point of Sale (POS) plugin for WooCommerce / 1.10.2
WCPOS – Point of Sale (POS) plugin for WooCommerce v1.10.2
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 / Collections.php

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

331 lines 12.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * WCPOS sync store component.
4 *
5 * @package WCPOS\WooCommercePOS\Sync
6 */
7
8 namespace WCPOS\WooCommercePOS\Sync;
9
10 // phpcs:disable Squiz.Commenting, Generic.Commenting -- Ported lab documentation is preserved verbatim.
11
12 /**
13 * THE collection registry (wayfinder #420 spec / #421 implementation): one
14 * table for the per-collection identity + capabilities that nine sites
15 * currently re-encode in three diverging vocabularies (plural collection,
16 * singular object_type, proxy resource slug). Consolidation precedent:
17 * trait-endpoint-permissions.php — but a trait shares BEHAVIOUR; this shares
18 * DATA, so it is a final class of pure lookups (no WP calls — unit-testable
19 * with no bootstrap, like the backfill controller's dispatchers it absorbs).
20 *
21 * Row shape — capabilities are NULLABLE GROUPS, never booleans: a capability
22 * a collection lacks is an ABSENT group, so "does X support Y" is
23 * `isset($row['y'])` and a group's fields exist only where they mean
24 * something (no empty-string taxonomy on a post collection, no id_type on
25 * tax_rates). Groups:
26 *
27 * - object_type — the singular journal vocabulary (always present).
28 * - identity — the uuid id-space: id_type (post|user|term|order), the
29 * collection's OWN scalar resolver scope (post_type or
30 * taxonomy — what resolve_id_by_uuid gets on a push; NOT
31 * the backfill scan scope), the ownership detector and the
32 * bulk uuid reader (METHOD NAMES on Pos_Uuid — rows stay
33 * pure data; consumers resolve callables), and the
34 * load_entity strategy key. null ⇒ no uuid identity
35 * (tax_rates, ADR 0009).
36 * - proxy — the namespaced read route + wc/v3 route + resource slug
37 * (the slug vocabulary trap: tax_rates' slug is `taxes`).
38 * - write — push support (the write map's route/id_type projection).
39 * - journal — the singular object_type the journal emits; orders consume
40 * it through their payload-windowed pull lane.
41 * - digest — leg-3 existence digests, present ONLY on the id-space
42 * OWNER row (products carries product+variation
43 * object_types; a copy on variations would double-project).
44 * Carries the stored object_types AND the live-row predicate
45 * name on Digest_Index, so the reader, the proxy stamper and
46 * the authoritative-absence answer all read one id-space fact
47 * instead of each re-deciding it.
48 * - fingerprint — config-change detection membership + the barcode flag.
49 * - backfill — uuid backfill support: the meta-store kind (post, order,
50 * user, or term) and the SCAN
51 * scope (products+variations scan together — which is why
52 * scan_post_types lives here and not on identity).
53 *
54 * Fail-closed lookups: unknown is null, NEVER a default. The
55 * default→products collapse in class-changes-controller.php:662 (a missing
56 * case pulls a PRODUCT with another record's id) is the bug class
57 * by_object_type() exists to kill — its consumers skip-and-log on null.
58 */
59 final class Collections {
60
61 /** One row per collection, keyed by the canonical plural name. */
62 private const ROWS = array(
63 'products' => array(
64 'object_type' => 'product',
65 'identity' => array(
66 'id_type' => 'post',
67 'post_type' => 'product',
68 'detector' => 'uuid_owned_by_other',
69 'bulk_reader' => 'bulk_read_post_uuids',
70 'loader' => 'product',
71 ),
72 'proxy' => array(
73 'route' => '/products',
74 'wc_route' => '/wc/v3/products',
75 'slug' => 'products',
76 'behavior' => \WCPOS\WooCommercePOS\API\V2\Proxy\Products_Proxy_Behavior::class,
77 ),
78 'write' => array( 'route' => '/wc/v3/products' ),
79 'journal' => array( 'object_type' => 'product' ),
80 'digest' => array(
81 'id_space' => 'products',
82 'object_types' => array( 'product', 'variation' ),
83 'live_rows' => 'live_row_exists_sql',
84 ),
85 'fingerprint' => array( 'barcode' => true ),
86 'backfill' => array(
87 'kind' => 'post',
88 'scan_post_types' => array( 'product', 'product_variation' ),
89 ),
90 ),
91 'variations' => array(
92 'object_type' => 'variation',
93 'identity' => array(
94 'id_type' => 'post',
95 'post_type' => 'product_variation',
96 'detector' => 'uuid_owned_by_other',
97 // No bulk reader: variations are stamped through the
98 // serialized-product filter, not a proxy list page.
99 'bulk_reader' => null,
100 'loader' => 'product',
101 ),
102 'proxy' => null, // hydrated via the per-id /variations controller
103 // Variations use WooCommerce's nested REST resource. The write controller
104 // takes the parent from create payloads and the stored object thereafter.
105 'write' => array( 'route' => '/wc/v3/products' ),
106 'journal' => array( 'object_type' => 'variation' ),
107 'digest' => null, // folded into the products id-space (owner row carries it)
108 'fingerprint' => array( 'barcode' => true ),
109 'backfill' => array(
110 'kind' => 'post',
111 'scan_post_types' => array( 'product', 'product_variation' ),
112 ),
113 ),
114 'orders' => array(
115 'object_type' => 'order',
116 'identity' => array(
117 'id_type' => 'order',
118 'detector' => 'uuid_owned_by_other_order',
119 'bulk_reader' => null, // payload mode — uuid read from served meta_data
120 'loader' => 'order',
121 ),
122 'proxy' => array(
123 'route' => '/orders',
124 'wc_route' => '/wc/v3/orders',
125 'slug' => 'orders',
126 'behavior' => \WCPOS\WooCommercePOS\API\V2\Proxy\Orders_Proxy_Behavior::class,
127 ),
128 'write' => array( 'route' => '/wc/v3/orders' ),
129 'journal' => array( 'object_type' => 'order' ), // orders consume the journal via the payload-windowed pull lane, catalogue via the pointer stream
130 'digest' => array(
131 'id_space' => 'orders',
132 'object_types' => array( 'order' ),
133 'live_rows' => 'order_live_row_exists_sql',
134 ),
135 'fingerprint' => null,
136 'backfill' => array( 'kind' => 'order' ),
137 ),
138 'customers' => array(
139 'object_type' => 'customer',
140 'identity' => array(
141 'id_type' => 'user',
142 'detector' => 'uuid_owned_by_other_user',
143 'bulk_reader' => 'bulk_read_user_uuids',
144 'loader' => 'customer',
145 ),
146 'proxy' => array(
147 'route' => '/customers',
148 'wc_route' => '/wc/v3/customers',
149 'slug' => 'customers',
150 'behavior' => \WCPOS\WooCommercePOS\API\V2\Proxy\Customers_Proxy_Behavior::class,
151 ),
152 'write' => array( 'route' => '/wc/v3/customers' ),
153 'journal' => array( 'object_type' => 'customer' ),
154 'digest' => array(
155 'id_space' => 'customers',
156 'object_types' => array( 'customer' ),
157 'live_rows' => 'customer_live_row_exists_sql',
158 ),
159 'fingerprint' => null,
160 'backfill' => array( 'kind' => 'user' ),
161 ),
162 'categories' => array(
163 'object_type' => 'category',
164 'identity' => array(
165 'id_type' => 'term',
166 'taxonomy' => 'product_cat',
167 'detector' => 'uuid_owned_by_other_term',
168 'bulk_reader' => 'bulk_read_term_uuids',
169 'loader' => 'term',
170 ),
171 'proxy' => array(
172 'route' => '/products/categories',
173 'wc_route' => '/wc/v3/products/categories',
174 'slug' => 'categories',
175 'behavior' => \WCPOS\WooCommercePOS\API\V2\Proxy\Terms_Proxy_Behavior::class,
176 ),
177 'write' => array( 'route' => '/wc/v3/products/categories' ),
178 'journal' => array( 'object_type' => 'category' ),
179 'digest' => null,
180 'fingerprint' => null,
181 'backfill' => array(
182 'kind' => 'term',
183 'taxonomy' => 'product_cat',
184 ),
185 ),
186 'brands' => array(
187 'object_type' => 'brand',
188 'identity' => array(
189 'id_type' => 'term',
190 'taxonomy' => 'product_brand',
191 'detector' => 'uuid_owned_by_other_term',
192 'bulk_reader' => 'bulk_read_term_uuids',
193 'loader' => 'term',
194 ),
195 'proxy' => array(
196 'route' => '/products/brands',
197 'wc_route' => '/wc/v3/products/brands',
198 'slug' => 'brands',
199 'behavior' => \WCPOS\WooCommercePOS\API\V2\Proxy\Terms_Proxy_Behavior::class,
200 ),
201 'write' => array( 'route' => '/wc/v3/products/brands' ),
202 'journal' => array( 'object_type' => 'brand' ),
203 'digest' => null,
204 'fingerprint' => null,
205 'backfill' => array(
206 'kind' => 'term',
207 'taxonomy' => 'product_brand',
208 ),
209 ),
210 'tags' => array(
211 'object_type' => 'tag',
212 'identity' => array(
213 'id_type' => 'term',
214 'taxonomy' => 'product_tag',
215 'detector' => 'uuid_owned_by_other_term',
216 'bulk_reader' => 'bulk_read_term_uuids',
217 'loader' => 'term',
218 ),
219 'proxy' => array(
220 'route' => '/products/tags',
221 'wc_route' => '/wc/v3/products/tags',
222 'slug' => 'tags',
223 'behavior' => \WCPOS\WooCommercePOS\API\V2\Proxy\Terms_Proxy_Behavior::class,
224 ),
225 'write' => null, // read-only: no client push path exists
226 'journal' => array( 'object_type' => 'tag' ),
227 'digest' => null,
228 'fingerprint' => null,
229 'backfill' => array(
230 'kind' => 'term',
231 'taxonomy' => 'product_tag',
232 ),
233 ),
234 'coupons' => array(
235 'object_type' => 'coupon',
236 'identity' => array(
237 'id_type' => 'post',
238 'post_type' => 'shop_coupon',
239 'detector' => 'uuid_owned_by_other',
240 'bulk_reader' => 'bulk_read_post_uuids',
241 'loader' => 'coupon',
242 ),
243 'proxy' => array(
244 'route' => '/coupons',
245 'wc_route' => '/wc/v3/coupons',
246 'slug' => 'coupons',
247 'behavior' => \WCPOS\WooCommercePOS\API\V2\Proxy\Coupons_Proxy_Behavior::class,
248 ),
249 'write' => array( 'route' => '/wc/v3/coupons' ),
250 'journal' => array( 'object_type' => 'coupon' ),
251 'digest' => null,
252 'fingerprint' => null,
253 'backfill' => array(
254 'kind' => 'post',
255 'scan_post_types' => array( 'shop_coupon' ),
256 ),
257 ),
258 'tax_rates' => array(
259 'object_type' => 'tax_rate',
260 'identity' => null, // ADR 0009: keyed by WooCommerce id — no uuid identity, principled
261 'proxy' => array(
262 'route' => '/taxes',
263 'wc_route' => '/wc/v3/taxes',
264 'slug' => 'taxes',
265 'behavior' => \WCPOS\WooCommercePOS\API\V2\Proxy\Taxes_Proxy_Behavior::class,
266 ),
267 'write' => null, // principled read-only
268 'journal' => array( 'object_type' => 'tax_rate' ),
269 'digest' => null,
270 'fingerprint' => array( 'barcode' => false ),
271 'backfill' => null, // no meta store to stamp
272 ),
273 );
274
275 /** Row lookup by canonical plural name. Null for unknown — callers decide
276 * their unsupported behavior EXPLICITLY (no default-to-products, ever). */
277 public static function row( string $collection ): ?array {
278 return self::ROWS[ $collection ] ?? null;
279 }
280
281 /** Inverse lookup: singular change-log object_type → row (+ its plural name
282 * under '_collection'). `product` resolves EXPLICITLY — it is not a
283 * fall-through default anywhere in this class. */
284 public static function by_object_type( string $object_type ): ?array {
285 foreach ( self::ROWS as $collection => $row ) {
286 if ( $row['object_type'] === $object_type ) {
287 return array( '_collection' => $collection ) + $row;
288 }
289 }
290 return null;
291 }
292
293 /** Resolve a singular object type to its canonical collection name. */
294 public static function collection_for_object_type( string $object_type ): ?string {
295 $row = self::by_object_type( $object_type );
296
297 return null === $row ? null : $row['_collection'];
298 }
299
300 /** Inverse lookup: proxy resource slug → row (tax_rates' slug is `taxes`). */
301 public static function by_proxy_slug( string $slug ): ?array {
302 foreach ( self::ROWS as $collection => $row ) {
303 if ( isset( $row['proxy'] ) && $row['proxy']['slug'] === $slug ) {
304 return array( '_collection' => $collection ) + $row;
305 }
306 }
307 return null;
308 }
309
310 /**
311 * Capability projection: every row carrying a non-null `$capability`
312 * group, keyed by plural name. The write map, the proxy RESOURCES, the
313 * digest dispatch and plugin.php's stamper wiring are all projections of
314 * this — adding a collection means adding ONE row above.
315 */
316 public static function with( string $capability ): array {
317 $rows = array();
318 foreach ( self::ROWS as $collection => $row ) {
319 if ( isset( $row[ $capability ] ) ) {
320 $rows[ $collection ] = $row;
321 }
322 }
323 return $rows;
324 }
325
326 /** The full canonical name list (tests, docs, admin surfaces). */
327 public static function names(): array {
328 return array_keys( self::ROWS );
329 }
330 }
331