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 / Sync / Variable_Prices.php

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

142 lines 5.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * WCPOS sync read surface.
4 *
5 * @package WCPOS\WooCommercePOS\Sync
6 */
7
8 namespace WCPOS\WooCommercePOS\Sync;
9
10 use WC_Product_Variable;
11 use WCPOS\WooCommercePOS\Services\Variable_Price_Range;
12
13 // phpcs:disable Squiz.Commenting, Generic.Commenting -- Ported lab documentation is preserved verbatim.
14
15 /**
16 * Recomputes and stamps fresh variable-product price ranges.
17 */
18 final class Variable_Prices {
19 public const META_KEY = '_woocommerce_pos_variable_prices';
20
21 /**
22 * THE variable-price augmentation, registered ONCE with {@see Augmentation_Pipeline}
23 * and projected onto both read lanes (the catalog-proxy `/products` batch and the
24 * per-object serialize path behind `/resolve/barcode`, the targeted read and the
25 * revision-hash drill-down).
26 *
27 * WC caches a variable product's min/max in a `wc_var_prices_*` transient that goes
28 * STALE when a child variation's price changes (gap-analysis §4.3), so the POS would
29 * otherwise show a wrong range. For each `type: 'variable'` product, recompute the
30 * price / regular_price / sale_price ranges by reading each visible child variation
31 * DIRECTLY — NOT via `get_variation_prices()`, which can hand back that same stale
32 * transient (codex review) — and inject `_woocommerce_pos_variable_prices` into the
33 * served meta_data. Non-variable products and non-array entries pass through untouched.
34 *
35 * The `type` gate comes FIRST so a page of simple products never triggers an object
36 * load; the batch lane hands over a null `$object` and relies on that gate plus the
37 * lazy load by id below.
38 *
39 * @param mixed $payload Serialized product record.
40 * @param null|mixed $object Product object, when the lane has one loaded.
41 * @param null|mixed $request Request context.
42 */
43 public static function augment_record( $payload, $object = null, $request = null ) {
44 if ( ! \is_array( $payload ) || 'variable' !== ( $payload['type'] ?? '' ) ) {
45 return $payload;
46 }
47 if ( ( ! $object || ! \is_object( $object ) ) && isset( $payload['id'] ) && \function_exists( 'wc_get_product' ) ) {
48 $object = wc_get_product( (int) $payload['id'] );
49 }
50
51 return self::inject_variable_price_range( $payload, $object, $request );
52 }
53
54 /**
55 * Shared per-product stamp: if `$product` is a `type: 'variable'` array backed by a
56 * variable product object, inject the freshly-recomputed
57 * `_woocommerce_pos_variable_prices` range. Otherwise pass through untouched.
58 *
59 * The range itself comes from {@see Variable_Price_Range}, THE canonical
60 * computation shared with the V1 products controller; this method only renders
61 * it onto the wire — raw child strings, and a sub-range omitted entirely when it
62 * has no values (e.g. nothing on sale) rather than emitted as empty strings.
63 *
64 * @param mixed $object Product object backing the payload.
65 * @param null|mixed $request Request context, read for the `dp` precision.
66 */
67 private static function inject_variable_price_range( array $product, $object, $request = null ): array {
68 if ( 'variable' !== ( $product['type'] ?? '' ) || ! $object instanceof WC_Product_Variable ) {
69 return $product;
70 }
71 $computed = Variable_Price_Range::for( $object, Variable_Price_Range::FORMAT_RAW );
72 $ranges = self::wire_ranges( $computed );
73
74 // A simple-to-variable conversion can leave old simple price fields on the
75 // parent. Clear them even when no visible child has a price. When prices do
76 // exist, serve the formatted current minimum without pairing independent
77 // regular and sale minima from different variations into a false sale.
78 foreach ( array( 'price', 'regular_price', 'sale_price' ) as $price_key ) {
79 if ( array_key_exists( $price_key, $product ) ) {
80 $product[ $price_key ] = '';
81 }
82 }
83 if ( null === $ranges ) {
84 return self::inject_meta_entry( $product, self::META_KEY, null );
85 }
86 if ( array_key_exists( 'price', $product ) ) {
87 $decimals = \function_exists( 'wc_get_price_decimals' ) ? wc_get_price_decimals() : 2;
88 if ( $request instanceof \WP_REST_Request && null !== $request->get_param( 'dp' ) ) {
89 $decimals = absint( $request->get_param( 'dp' ) );
90 }
91 $product['price'] = wc_format_decimal( $computed['minimum_price'], $decimals );
92 }
93
94 return self::inject_meta_entry( $product, self::META_KEY, $ranges );
95 }
96
97 /**
98 * Render the canonical range onto the sync wire: drop every empty sub-range, and
99 * report null when no visible variation carries any price at all.
100 *
101 * @param array $computed The Variable_Price_Range result.
102 */
103 private static function wire_ranges( array $computed ): ?array {
104 if ( ! $computed['has_prices'] ) {
105 return null;
106 }
107 $ranges = array();
108 foreach ( $computed['ranges'] as $field => $range ) {
109 if ( '' === $range['min'] && '' === $range['max'] ) {
110 continue;
111 }
112 $ranges[ $field ] = $range;
113 }
114
115 return empty( $ranges ) ? null : $ranges;
116 }
117
118 /**
119 * Replace (or add) a single meta_data entry by key, preserving the others (order-stable).
120 * A null value removes the matching entry without adding a replacement.
121 */
122 private static function inject_meta_entry( array $payload, string $key, $value ): array {
123 $others = array();
124 $meta = ( isset( $payload['meta_data'] ) && \is_array( $payload['meta_data'] ) ) ? $payload['meta_data'] : array();
125 foreach ( $meta as $entry ) {
126 $entry_key = Meta_Entry::key( $entry );
127 if ( $key !== $entry_key ) {
128 $others[] = $entry;
129 }
130 }
131 if ( null !== $value ) {
132 $others[] = array(
133 'key' => $key,
134 'value' => $value,
135 );
136 }
137 $payload['meta_data'] = array_values( $others );
138
139 return $payload;
140 }
141 }
142