PluginProbe
WCPOS – Point of Sale (POS) plugin for WooCommerce / 1.10.16
WCPOS – Point of Sale (POS) plugin for WooCommerce v1.10.16
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 1.9.13 All 162 releases
woocommerce-pos / includes / Services / Barcode_Field.php

Barcode_Field.php in WCPOS – Point of Sale (POS) plugin for WooCommerce 1.10.16, at includes/Services/Barcode_Field.php

163 lines 5.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * The POS barcode field.
4 *
5 * @package WCPOS\WooCommercePOS\Services
6 */
7
8 namespace WCPOS\WooCommercePOS\Services;
9
10 /**
11 * THE barcode-field decision.
12 *
13 * A merchant picks ONE product meta key to be the POS barcode. Two of the
14 * possible values are not plain postmeta at all — `_sku` and `_global_unique_id`
15 * are WooCommerce product PROPERTIES with their own accessors — so every place
16 * that touches a barcode has to branch three ways: SKU, GTIN, or arbitrary meta.
17 * That branch used to be re-implemented at ~13 call sites across the V1 REST
18 * controllers, the V2 sync surface and the config fingerprint, with the write
19 * block duplicated byte-for-byte between the products and variations
20 * controllers. The cost showed up as shotgun surgery: flipping the default once
21 * meant editing the same `elseif` in three files.
22 *
23 * This class is the single owner of the decision. Everything else asks it a
24 * question — which meta key, what is this product's barcode, write this value,
25 * which keys does search cover, which key does `orderby=barcode` sort on — and
26 * never re-derives the answer.
27 *
28 * Invariants deliberately preserved from the ported call sites:
29 *
30 * - BLANK COERCION. A blank (or whitespace-only) `barcode_field` setting resolves
31 * to DEFAULT_FIELD. The setting's REST validator accepts any string, so `''`
32 * can be persisted; before this class the two accessors disagreed about what
33 * that meant — the V1 read path took the raw `''` (and wrote postmeta under an
34 * EMPTY meta key) while the sync fingerprint advertised GTIN. One coerced
35 * answer removes the divergence.
36 * - TWO WOOCOMMERCE VERSION GUARDS. `get_global_unique_id()`/`set_global_unique_id()`
37 * only exist from WC 9.1, so both the read and the write path fall back to the
38 * raw `_global_unique_id` postmeta — the same key WooCommerce itself uses — on
39 * older versions.
40 * - THE SAVE-PATH SPLIT. The GTIN and SKU paths call `save()` while the custom
41 * meta path calls `save_meta_data()`. This is NOT an oversight to be unified:
42 * the full save exists so product update hooks fire and the sync lane observes
43 * the change.
44 *
45 * Not owned here: how a barcode meta key is NAMED ON THE WIRE for the sync
46 * client (`sku` / `global_unique_id` / `meta_data:<key>`, gated on WC 9.2 REST
47 * support) — that is a payload-shape concern and stays in
48 * {@see \WCPOS\WooCommercePOS\Sync\Config_Fingerprint}, which reads its key from
49 * here.
50 */
51 final class Barcode_Field {
52 /**
53 * The meta key used when the `barcode_field` setting is unset or blank.
54 */
55 public const DEFAULT_FIELD = '_global_unique_id';
56
57 /**
58 * Fields the settings picker must always offer. Both are WooCommerce product
59 * PROPERTIES with their own accessors, not guaranteed postmeta rows, so
60 * meta-key discovery cannot be relied on to surface them.
61 *
62 * @var string[]
63 */
64 public const CORE_FIELDS = array( '_sku', self::DEFAULT_FIELD );
65
66 /**
67 * The active barcode meta key.
68 *
69 * Read from production settings and coerced to a non-empty string: a blank
70 * option falls back to the default rather than producing an empty meta key.
71 *
72 * @return string
73 */
74 public static function meta_key(): string {
75 $value = Settings::instance()->barcode_field();
76
77 return '' === trim( $value ) ? self::DEFAULT_FIELD : $value;
78 }
79
80 /**
81 * Read a product's barcode.
82 *
83 * A custom meta key can hold anything another plugin put there, including an
84 * array. Only scalars become a barcode — casting an array would emit an
85 * "Array to string conversion" warning and serve the literal string "Array".
86 *
87 * @param \WC_Data $product The product or variation object.
88 *
89 * @return string
90 */
91 public static function read( $product ): string {
92 $barcode_field = self::meta_key();
93
94 if ( '_sku' === $barcode_field ) {
95 return (string) $product->get_sku();
96 }
97 // get_global_unique_id() requires WC 9.1+, fall back to raw meta on older versions.
98 if ( self::DEFAULT_FIELD === $barcode_field && method_exists( $product, 'get_global_unique_id' ) ) {
99 return (string) $product->get_global_unique_id();
100 }
101
102 $value = $product->get_meta( $barcode_field );
103
104 return is_scalar( $value ) ? (string) $value : '';
105 }
106
107 /**
108 * Write a barcode onto a product and persist it.
109 *
110 * The SKU and GTIN paths take a FULL save (not `save_meta_data()`) so product
111 * update hooks fire for sync.
112 *
113 * @param \WC_Data $product The product or variation object.
114 * @param mixed $value The barcode value.
115 */
116 public static function write( $product, $value ): void {
117 $barcode_field = self::meta_key();
118
119 if ( '_sku' === $barcode_field ) {
120 $product->set_sku( $value );
121 $product->save();
122 } elseif ( self::DEFAULT_FIELD === $barcode_field ) {
123 if ( method_exists( $product, 'set_global_unique_id' ) ) {
124 $product->set_global_unique_id( $value );
125 } else {
126 // WC < 9.1 has no GTIN setter — write the same postmeta key it uses.
127 $product->update_meta_data( $barcode_field, $value );
128 }
129 // Full save (not save_meta_data) so product update hooks fire for sync.
130 $product->save();
131 } else {
132 $product->update_meta_data( $barcode_field, $value );
133 $product->save_meta_data();
134 }
135 }
136
137 /**
138 * The postmeta keys a product/variation search covers: always `_sku`, plus the
139 * active barcode field when it is something else.
140 *
141 * @return array<int, string>
142 */
143 public static function search_keys(): array {
144 $keys = array( '_sku' );
145 $barcode_field = self::meta_key();
146
147 if ( '_sku' !== $barcode_field ) {
148 $keys[] = $barcode_field;
149 }
150
151 return $keys;
152 }
153
154 /**
155 * The postmeta key `orderby=barcode` sorts on.
156 *
157 * @return string
158 */
159 public static function orderby_key(): string {
160 return self::meta_key();
161 }
162 }
163