PluginProbe
WCPOS – Point of Sale (POS) plugin for WooCommerce / 1.10.20
WCPOS – Point of Sale (POS) plugin for WooCommerce v1.10.20
1.10.20 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 All 164 releases
woocommerce-pos / includes / Services / Service_Groups.php

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

216 lines 7.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Construct the POS service groups for the current request.
4 *
5 * @package WCPOS\WooCommercePOS
6 */
7
8 namespace WCPOS\WooCommercePOS\Services;
9
10 use WCPOS\WooCommercePOS\Emails;
11 use WCPOS\WooCommercePOS\Gateways;
12 use WCPOS\WooCommercePOS\i18n;
13 use WCPOS\WooCommercePOS\Orders;
14 use WCPOS\WooCommercePOS\Products;
15 use WCPOS\WooCommercePOS\Templates;
16
17 /**
18 * One module for "which POS services does this request need, and do they exist yet?".
19 *
20 * Three groups. `always` is what WooCommerce consults on a plain shopper page
21 * before any order exists; `order` is the order-event observers; `pos` is what
22 * only POS, admin, REST, cron and CLI requests use. `Init::init_common()` picks
23 * the groups for the lane {@see Request_Lane} reports, and the order group is
24 * additionally armed on the first order write of ANY request — so the lane is an
25 * optimisation, never a correctness gate.
26 *
27 * Each group is constructed at most once per request, and a group is marked
28 * constructed BEFORE its services are built: a service that writes an order
29 * while constructing must not re-enter its own group.
30 *
31 * `Templates` straddles the order group's boundary on purpose. It is built here
32 * with the order group, but its post type and taxonomy are also read from My
33 * Account on the storefront lane, where that group does not exist. Its own
34 * {@see Templates::ensure_registered()} hatch covers that read. The hatch is the
35 * right shape for a service needed in part on every lane — not a symptom to fix
36 * by moving `Templates` into the always group, which would put its registration
37 * back on every shop page.
38 */
39 final class Service_Groups {
40 public const ALWAYS = 'always';
41 public const ORDER = 'order';
42 public const POS = 'pos';
43
44 /**
45 * Groups constructed so far this request, in construction order.
46 *
47 * @var array<string, bool>
48 */
49 private static array $constructed = array();
50
51 /**
52 * Whether this request has installed the late order-write trigger.
53 *
54 * @var bool
55 */
56 private static bool $armed = false;
57
58 /**
59 * Construct a group's services exactly once per request.
60 *
61 * Groups are independent: ensuring one never implies another. An unknown
62 * group name is a no-op and is not recorded as constructed.
63 *
64 * @param string $group One of the class constants.
65 */
66 public static function ensure( string $group ): void {
67 if ( isset( self::$constructed[ $group ] ) ) {
68 return;
69 }
70 if ( ! \in_array( $group, array( self::ALWAYS, self::ORDER, self::POS ), true ) ) {
71 return;
72 }
73
74 // Marked before constructing, so a service that writes an order during
75 // its own construction re-enters this method and returns immediately.
76 self::$constructed[ $group ] = true;
77
78 switch ( $group ) {
79 case self::ALWAYS:
80 self::construct_always();
81 break;
82 case self::ORDER:
83 self::construct_order();
84 break;
85 case self::POS:
86 self::construct_pos();
87 break;
88 }
89 }
90
91 /**
92 * Hook the order group to the first order write of the request.
93 *
94 * Every WooCommerce order write — create, update, status transition,
95 * `payment_complete()`, refund — goes through `WC_Abstract_Order::save()`,
96 * which fires `woocommerce_before_order_object_save` before the data store
97 * writes and before `woocommerce_new_order` / `woocommerce_order_status_changed`
98 * / `woocommerce_payment_complete` fire. Priority 0 there means every
99 * observer exists before any order is written — on a webhook, a cron
100 * spawned from a page view, a third-party plugin creating an order on
101 * `template_redirect`, or a lane the classifier got wrong. Nothing in the
102 * order group listens to trash or delete, so those need no arming.
103 *
104 * Refunds need their own hook. `save()` derives that action from the object
105 * type, so a `WC_Order_Refund` fires `woocommerce_before_order_refund_object_save`
106 * instead. `wc_create_refund()` does save the parent order too, but only AFTER
107 * WooCommerce has decided whether to send the customer refunded-order email —
108 * and that decision reads `Emails::manage_customer_emails`, which belongs to
109 * this group. Arming the parent save alone left the filter unregistered at the
110 * moment it was consulted, so a merchant with POS customer emails switched off
111 * still had the shopper emailed on a storefront-lane refund (a gateway refund
112 * webhook on `?wc-api=` is that lane). The refund save runs before the email
113 * decision, so arming it is early enough.
114 */
115 public static function arm_order_group(): void {
116 self::$armed = true;
117 add_action( 'woocommerce_before_order_object_save', array( self::class, 'ensure_order_group' ), 0, 0 );
118 add_action( 'woocommerce_before_order_refund_object_save', array( self::class, 'ensure_order_group' ), 0, 0 );
119 }
120
121 /**
122 * Construct the order group. Public because it is the armed hook's callback.
123 */
124 public static function ensure_order_group(): void {
125 self::ensure( self::ORDER );
126 }
127
128 /**
129 * Whether the late order-write trigger is installed on this request.
130 */
131 public static function armed(): bool {
132 return self::$armed;
133 }
134
135 /**
136 * Which groups this request has constructed, in construction order.
137 *
138 * @internal Test seam.
139 *
140 * @return string[]
141 */
142 public static function constructed(): array {
143 return array_keys( self::$constructed );
144 }
145
146 /**
147 * Forget this request's construction state. Tests only; hooks are restored separately.
148 *
149 * @internal
150 */
151 public static function reset(): void {
152 self::$constructed = array();
153 self::$armed = false;
154 }
155
156 /**
157 * Services every lane needs, including a plain storefront page.
158 *
159 * These are the ones WooCommerce consults before any order exists:
160 * translations, the product visibility filters, the order statuses and the
161 * read-side order filters (My Account renders POS orders), the gateway
162 * registration (WooCommerce builds its gateway list on cart pages) and the
163 * reserved-stock filter (POS drafts must reduce online availability at
164 * add-to-cart time).
165 */
166 private static function construct_always(): void {
167 // init the Services.
168 Settings::instance();
169 Auth::instance();
170
171 new i18n();
172 new Gateways();
173 new Products();
174 new Orders();
175 Stock_Validator::instance();
176 Order_Write_Intent::register();
177 }
178
179 /**
180 * The order-event observers, plus the action that lets others join them.
181 */
182 private static function construct_order(): void {
183 Receipt_Snapshot_Store::instance();
184 new Emails();
185 new Templates();
186 new Print_Job_Service();
187 new Cloud_Print_Trigger_Service();
188 new Cloud_Print_Submit_Service();
189 new Cloud_Print_Relay_Service();
190
191 /**
192 * Fires once per request when the POS order-event services exist:
193 * eagerly on POS, admin, REST, cron and CLI requests (from this
194 * plugin's `init` callback at priority 10), and on a storefront
195 * request the moment the first order is about to be written.
196 *
197 * Because the eager firing happens at `init` priority 10, a listener
198 * added later than that (for example from another plugin's `init`
199 * callback at priority 20) must check `did_action()` first and
200 * construct immediately when the action has already fired.
201 *
202 * @since 1.10.8
203 */
204 do_action( 'woocommerce_pos_order_services_ready' );
205 }
206
207 /**
208 * Services only POS, admin, REST, cron and CLI requests use.
209 */
210 private static function construct_pos(): void {
211 Extensions::instance();
212 new Decimal_Quantities();
213 new Customer_Meta_Parity();
214 }
215 }
216