PluginProbe
WCPOS – Point of Sale (POS) plugin for WooCommerce / trunk
WCPOS – Point of Sale (POS) plugin for WooCommerce vtrunk
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 1.9.13 1.9.12 1.9.11 1.9.10 All 159 releases
woocommerce-pos / includes / Sync / Revision.php

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

114 lines 4.2 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 canonical record revision (one function, G1) — a stable content hash of a
14 * record's wc/v3 serialization, used for optimistic-concurrency on writes and as
15 * the `baseRevision` a client sends back on an update. Distinct from the
16 * change-signal's internal change-DETECTION hash (class-change-log): this is the
17 * record's content revision exposed to the client.
18 *
19 * Stable by construction so it changes ONLY when the record's representation does:
20 * - volatile/derived REST fields are stripped — `related_ids` comes back in random
21 * order every GET, `_links` is request-derived — shared with the change-log via
22 * the `woocommerce_pos_sync_revision_excluded_fields` filter;
23 * - keys are sorted recursively, so wc/v3 (or a filter) re-ordering properties is
24 * not seen as a change.
25 *
26 * IMPORTANT: always compute over the BARE wc/v3 serialization — never a payload that
27 * has had `_woocommerce_pos_uuid` re-injected — because the conflict check re-reads
28 * the record via wc/v3 (which omits that protected meta), so the two must hash the
29 * same bytes.
30 */
31 class Revision {
32
33 /**
34 * #423 step 1b: stamp each proxied record's CANONICAL revision as a
35 * top-level `_rxdb_revision` (transport metadata, like `_rxdb_digest`) so
36 * the client lane builders stop synthesizing date/id stand-ins that can
37 * never match a push-side recompute. Registered at priority 9 — BEFORE
38 * the uuid/digest stampers (priority 10) augment the payload — so the
39 * stamped bytes equal what the write path's revision_for computes from a
40 * bare wc/v3 re-read. Orders additionally identity-strip (HPOS exposes
41 * the protected uuid meta on reads).
42 */
43 public static function register_proxy_stamps(): void {
44 add_filter( 'woocommerce_pos_sync_proxy_response', array( __CLASS__, 'stamp_proxy_revisions' ), 9, 3 );
45 }
46
47 /**
48 * Remove the canonical revision stamper.
49 */
50 public static function unregister_proxy_stamps(): void {
51 remove_filter( 'woocommerce_pos_sync_proxy_response', array( __CLASS__, 'stamp_proxy_revisions' ), 9 );
52 }
53
54 public static function stamp_proxy_revisions( $data, $resource = '', $request = null ) {
55 if ( ! is_array( $data ) ) {
56 return $data;
57 }
58 $row = Collections::by_proxy_slug( (string) $resource );
59 if ( null === $row ) {
60 return $data;
61 }
62 $is_order = 'order' === $row['object_type'];
63 foreach ( $data as $index => $record ) {
64 if ( ! is_array( $record ) ) {
65 continue;
66 }
67 $data[ $index ]['_rxdb_revision'] = $is_order
68 ? Order_Serializer::canonical_revision( $record )
69 : self::compute( $record );
70 }
71 return $data;
72 }
73
74 public static function compute( array $data ): string {
75 return 'sha256:' . hash( 'sha256', (string) wp_json_encode( self::canonicalize( $data ) ) );
76 }
77
78 public static function canonicalize( array $data ): array {
79 $excluded = apply_filters( 'woocommerce_pos_sync_revision_excluded_fields', array( 'related_ids', '_links', 'links' ) );
80 foreach ( (array) $excluded as $field ) {
81 unset( $data[ $field ] );
82 }
83 return self::sort_keys_recursive( $data );
84 }
85
86 private static function sort_keys_recursive( array $data ): array {
87 ksort( $data );
88 foreach ( $data as $key => $value ) {
89 if ( is_array( $value ) ) {
90 $data[ $key ] = self::sort_keys_recursive( $value );
91 // Taxonomy collections are sets; other lists retain semantic order.
92 if ( ! in_array( $key, array( 'categories', 'tags', 'brands' ), true ) || count( $value ) < 2 || array_keys( $value ) !== range( 0, count( $value ) - 1 ) ) {
93 continue;
94 }
95 foreach ( $value as $term ) {
96 $id = is_array( $term ) ? ( $term['id'] ?? null ) : ( is_object( $term ) ? ( $term->id ?? null ) : null );
97 if ( ! is_int( $id ) ) {
98 continue 2;
99 }
100 }
101 usort(
102 $data[ $key ],
103 static function ( $left, $right ): int {
104 $left_id = is_array( $left ) ? $left['id'] : $left->id;
105 $right_id = is_array( $right ) ? $right['id'] : $right->id;
106 return $left_id <=> $right_id;
107 }
108 );
109 }
110 }
111 return $data;
112 }
113 }
114