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

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

165 lines 4.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * WCPOS sync wire normalization.
4 *
5 * @package WCPOS\WooCommercePOS\Sync
6 */
7
8 namespace WCPOS\WooCommercePOS\Sync;
9
10 /**
11 * Normalizes structured meta values before sync documents are hashed or emitted.
12 */
13 final class Meta_Normalizer {
14 /**
15 * Register the shared pre-stamping normalization seams.
16 */
17 public static function register_hooks(): void {
18 add_filter( 'woocommerce_pos_sync_proxy_response', array( __CLASS__, 'normalize' ), 5 );
19 add_filter( 'woocommerce_pos_sync_serialized_product', array( __CLASS__, 'normalize' ), 5 );
20 add_filter( 'woocommerce_pos_sync_serialized_order', array( __CLASS__, 'normalize' ), 5 );
21 }
22
23 /**
24 * Unregister the shared pre-stamping normalization seams.
25 */
26 public static function unregister_hooks(): void {
27 remove_filter( 'woocommerce_pos_sync_proxy_response', array( __CLASS__, 'normalize' ), 5 );
28 remove_filter( 'woocommerce_pos_sync_serialized_product', array( __CLASS__, 'normalize' ), 5 );
29 remove_filter( 'woocommerce_pos_sync_serialized_order', array( __CLASS__, 'normalize' ), 5 );
30 }
31
32 /**
33 * Recursively normalize every meta_data array in a document or payload.
34 *
35 * @param mixed $payload Document or payload being prepared for the wire.
36 *
37 * @return mixed
38 */
39 public static function normalize( $payload ) {
40 if ( ! is_array( $payload ) ) {
41 return $payload;
42 }
43
44 foreach ( $payload as $key => $value ) {
45 if ( 'meta_data' === $key && is_array( $value ) ) {
46 $value = self::normalize_meta_data( $value );
47 }
48
49 $payload[ $key ] = is_array( $value ) ? self::normalize( $value ) : $value;
50 }
51
52 return $payload;
53 }
54
55 /**
56 * Shape-tolerant reader for structured meta that may be stored in either
57 * form: the historical JSON-encoded string, or (after a typed client push
58 * lands through wc/v3) a native PHP array. Server-side consumers of
59 * `_woocommerce_pos_data`-style meta must read through this — a bare
60 * `json_decode( $raw )` fatals on PHP 8 the moment the storage holds an
61 * array.
62 *
63 * @param mixed $raw Meta value as returned by get_meta().
64 *
65 * @return array|null Decoded associative array, or null when the value is
66 * neither a JSON object/array string nor an array.
67 */
68 public static function decode_to_array( $raw ): ?array {
69 if ( is_array( $raw ) ) {
70 return $raw;
71 }
72 if ( is_object( $raw ) ) {
73 $raw = wp_json_encode( $raw );
74 }
75 if ( ! is_string( $raw ) || '' === $raw ) {
76 return null;
77 }
78 $decoded = json_decode( $raw, true );
79 if ( JSON_ERROR_NONE !== json_last_error() || ! is_array( $decoded ) ) {
80 return null;
81 }
82
83 return $decoded;
84 }
85
86 /**
87 * Normalize structured meta values and derived display fields.
88 *
89 * @param array $meta_data Serialized REST meta entries.
90 *
91 * @return array
92 */
93 private static function normalize_meta_data( array $meta_data ): array {
94 foreach ( $meta_data as $index => $entry ) {
95 // Top-level entity meta reaches the filters as live WC_Meta_Data objects
96 // (they only become arrays at JSON-encode time); convert a copy to the
97 // exact shape it would serialize to, and only swap it in when normalization
98 // actually happens — untouched entries keep their original form so
99 // revision hashes of scalar-only records are unchanged.
100 $is_meta_object = $entry instanceof \WC_Meta_Data;
101 if ( $is_meta_object ) {
102 $entry = json_decode( wp_json_encode( $entry ), true );
103 }
104
105 if ( ! is_array( $entry ) ) {
106 continue;
107 }
108
109 $entry_changed = false;
110 if ( isset( $entry['value'] ) && is_string( $entry['value'] ) ) {
111 $raw = $entry['value'];
112 $trimmed = trim( $raw );
113 $opening = substr( $trimmed, 0, 1 );
114 if ( '{' === $opening || '[' === $opening ) {
115 $decoded = json_decode( $raw );
116 if ( JSON_ERROR_NONE === json_last_error() && ( is_array( $decoded ) || $decoded instanceof \stdClass ) ) {
117 $entry['value'] = self::preserve_json_object_shape( $decoded );
118 $entry_changed = true;
119 }
120 }
121 }
122 foreach ( array( 'display_key', 'display_value' ) as $display_field ) {
123 if ( array_key_exists( $display_field, $entry ) && ! is_string( $entry[ $display_field ] ) ) {
124 unset( $entry[ $display_field ] );
125 $entry_changed = true;
126 }
127 }
128
129 if ( ! $is_meta_object || $entry_changed ) {
130 $meta_data[ $index ] = $entry;
131 }
132 }
133
134 return $meta_data;
135 }
136
137 /**
138 * Convert JSON objects to arrays unless doing so would change their wire shape.
139 *
140 * @param mixed $value Decoded JSON value.
141 *
142 * @return mixed
143 */
144 private static function preserve_json_object_shape( $value ) {
145 if ( is_array( $value ) ) {
146 return array_map( array( __CLASS__, __FUNCTION__ ), $value );
147 }
148
149 if ( ! $value instanceof \stdClass ) {
150 return $value;
151 }
152
153 /**
154 * Decoded object properties.
155 *
156 * @var array<int|string, mixed> $properties
157 */
158 $properties = array_map( array( __CLASS__, __FUNCTION__ ), get_object_vars( $value ) );
159
160 return array() === $properties || array_values( $properties ) === $properties
161 ? (object) $properties
162 : $properties;
163 }
164 }
165