PluginProbe
WCPOS – Point of Sale (POS) plugin for WooCommerce / 1.10.22
WCPOS – Point of Sale (POS) plugin for WooCommerce v1.10.22
1.10.22 1.10.21 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 All 166 releases
woocommerce-pos / includes / Init.php

Init.php in WCPOS – Point of Sale (POS) plugin for WooCommerce 1.10.22, at includes/Init.php

633 lines 22.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Load required classes.
4 *
5 * @author Paul Kilmurray <[email protected]>
6 *
7 * @see http://wcpos.com
8 * @package WCPOS\WooCommercePOS
9 */
10
11 namespace WCPOS\WooCommercePOS;
12
13 use WCPOS\WooCommercePOS\Admin\Consent;
14 use WCPOS\WooCommercePOS\Admin\Menu;
15 use WCPOS\WooCommercePOS\Services\Auth as AuthService;
16 use WCPOS\WooCommercePOS\Services\Service_Groups;
17
18 /**
19 * Init class.
20 */
21 class Init {
22 /**
23 * Observer awaiting the constructor's non-hook seed step.
24 *
25 * @var Sync\Visibility_Observer|null
26 */
27 private $visibility_observer;
28
29 /**
30 * Install the ordered wiring declared by {@see hook_rows()}.
31 *
32 * Non-hook setup stays here, interleaved at its original registration boundaries.
33 */
34 public function __construct() {
35 require_once PLUGIN_PATH . 'includes/wcpos-functions.php';
36 require_once PLUGIN_PATH . 'includes/wcpos-store-functions.php';
37 wp_cache_add_global_groups( 'wc_pos_user_uuid_locks' );
38
39 $rows = $this->hook_rows( true );
40 Hook_Manifest::validate( $rows );
41 Hook_Manifest::install( wp_list_filter( $rows, array( 'phase' => 'pre-latch' ) ) );
42 $sync_latched = Sync\Api::SCHEMA_VERSION === get_option( Sync\Api::SCHEMA_OPTION, null );
43 foreach ( $rows as $row ) {
44 if ( 'pre-latch' === $row['phase'] || ( 'sync-latched' === $row['phase'] && ! $sync_latched ) ) {
45 continue;
46 }
47 if ( array( $this, 'init' ) === $row['callback'] ) {
48 ( new Sync\Config_Fingerprint() )->maybe_cleanup_legacy_options();
49 }
50 Hook_Manifest::install( array( $row ) );
51 if ( null !== $this->visibility_observer ) {
52 $this->visibility_observer->maybe_seed_hidden_tombstones();
53 $this->visibility_observer = null;
54 }
55 }
56 }
57
58 /**
59 * Declare bootstrap wiring in registration order, without installing it.
60 *
61 * Null hooks invoke registrars immediately; their internal priorities/arity stay
62 * in register_hooks(). Phases keep the schema read after pre-latch hooks;
63 * sync-latched rows are post-read hooks omitted when the latch is down.
64 * The guard registrar MUST precede the JWT row: both register at priority 20,
65 * after core cookie/application-password handlers. Reversing them attributes
66 * JWT identity to prior authentication and fails open on /wc/v3/orders.
67 *
68 * @param bool $sync_latched Whether the verified sync schema latch is set.
69 * @return array Ordered rows consumed by Hook_Manifest::install().
70 */
71 public function hook_rows( bool $sync_latched ): array {
72 $rows = array(
73 array(
74 'hook' => null,
75 'callback' => static function (): void {
76 new Consent();
77 },
78 'priority' => 10,
79 'args' => 0,
80 'reason' => 'Default 10; lifecycle hooks must exist during plugins_loaded, before activation/update actions.',
81 'phase' => 'pre-latch',
82 ),
83 array(
84 'hook' => 'woocommerce_pos_rest_api_controllers',
85 'callback' => array( Sync\Api::class, 'register_controllers' ),
86 'priority' => 10,
87 'args' => 1,
88 'reason' => 'Default; sole callback. Response registrars stay inside this filter to retain REST activation timing.',
89 'phase' => 'pre-latch',
90 ),
91 array(
92 'hook' => Sync\Integrity_Digest::REBUILD_HOOK,
93 'callback' => array( Sync\Integrity_Digest::class, 'run_scheduled_rebuild' ),
94 'priority' => 10,
95 'args' => 1,
96 'reason' => 'Default; sole callback. Unlatched so an already-scheduled rebuild still has a listener.',
97 'phase' => 'pre-latch',
98 ),
99 array(
100 'hook' => null,
101 'callback' => array( Sync\Meta_Normalizer::class, 'register_hooks' ),
102 'priority' => 10,
103 'args' => 0,
104 'reason' => 'Priority 5 before revision 9 and augmentation 10, so revisions match bare wc/v3 rereads; also serves orders.',
105 'phase' => 'sync-latched',
106 ),
107 array(
108 'hook' => 'woocommerce_pos_sync_serialized_order',
109 'callback' => array( Sync\Pos_Uuid::class, 'stamp_serialized_record' ),
110 'priority' => 10,
111 'args' => 3,
112 'reason' => 'After normalization at 5, in step with product stampers at 10.',
113 'phase' => 'sync-latched',
114 ),
115 array(
116 'hook' => null,
117 'callback' => array( Sync\Augmentation_Pipeline::class, 'install' ),
118 'priority' => 10,
119 'args' => 0,
120 'reason' => 'Revision 9 hashes normalized, unaugmented bytes; UUID/digest/projections at 10 preserve extension order, including order-pull digests.',
121 'phase' => 'sync-latched',
122 ),
123 array(
124 'hook' => null,
125 'callback' => array( Sync\Pos_Uuid::class, 'register_hooks' ),
126 'priority' => 10,
127 'args' => 0,
128 'reason' => 'Default 10; identity is unconditional: before-save UUIDs land in the same write and native restores re-prove ownership (ADR 0038).',
129 'phase' => 'post-latch',
130 ),
131 array(
132 'hook' => null,
133 'callback' => static function (): void {
134 ( new Sync\Sync_Journal() )->register_hooks();
135 },
136 'priority' => 10,
137 'args' => 0,
138 'reason' => 'Default 10; dirty order updates coalesce until shutdown at PHP_INT_MAX, after WooCommerce customer 10/session 20 saves.',
139 'phase' => 'sync-latched',
140 ),
141 array(
142 'hook' => null,
143 'callback' => function (): void {
144 $this->visibility_observer = new Sync\Visibility_Observer();
145 $this->visibility_observer->register_hooks();
146 },
147 'priority' => 10,
148 'args' => 0,
149 'reason' => 'Default 10 after journal; records servable-set transitions, using generic pre-delete_option; Init then seeds tombstones.',
150 'phase' => 'sync-latched',
151 ),
152 array(
153 'hook' => null,
154 'callback' => static function (): void {
155 ( new Sync\Sync_Journal_Purge() )->register_hooks();
156 },
157 'priority' => 10,
158 'args' => 0,
159 'reason' => 'Default 10; sole cron listener; the registrar also schedules the daily purge.',
160 'phase' => 'sync-latched',
161 ),
162 array(
163 'hook' => null,
164 'callback' => static function (): void {
165 ( new Sync\Integrity_Digest() )->register_hooks();
166 },
167 'priority' => 10,
168 'args' => 0,
169 'reason' => 'Default 10, shutdown PHP_INT_MAX; journal registers first on shared hooks (reason unknown); dirty digests coalesce until flush.',
170 'phase' => 'sync-latched',
171 ),
172 array(
173 'hook' => 'init',
174 'callback' => array( $this, 'init' ),
175 'priority' => 10,
176 'args' => 1,
177 'reason' => 'Default 10; free services must exist before Pro init at 20.',
178 'phase' => 'post-latch',
179 ),
180 array(
181 'hook' => 'rest_api_init',
182 'callback' => array( $this, 'init_rest_api' ),
183 'priority' => 20,
184 'args' => 1,
185 'reason' => 'Original reason unknown (8f2b9eac); Pro deliberately registers before free at 9.',
186 'phase' => 'post-latch',
187 ),
188 array(
189 'hook' => 'query_vars',
190 'callback' => array( $this, 'query_vars' ),
191 'priority' => 10,
192 'args' => 1,
193 'reason' => 'Default; appends one variable.',
194 'phase' => 'post-latch',
195 ),
196 array(
197 'hook' => 'pre_update_option_woocommerce_pos_pro_settings_license',
198 'callback' => array( self::class, 'remove_license_transient' ),
199 'priority' => 10,
200 'args' => 2,
201 'reason' => 'Default; the reentrancy guard, not priority, makes legacy Pro license cache invalidation safe (f33b8d655).',
202 'phase' => 'post-latch',
203 ),
204 array(
205 'hook' => null,
206 'callback' => array( Rest_Cors::class, 'register_hooks' ),
207 'priority' => 10,
208 'args' => 0,
209 'reason' => 'Unconditional for unmarked preflights/relay; serve at 20 after core CORS at 10 so WCPOS is the last writer.',
210 'phase' => 'post-latch',
211 ),
212 array(
213 'hook' => 'send_headers',
214 'callback' => array( $this, 'send_headers' ),
215 'priority' => 99,
216 'args' => 1,
217 'reason' => 'Unknown beyond running late for WPSEO integration (62da70551).',
218 'phase' => 'post-latch',
219 ),
220 array(
221 'hook' => 'send_headers',
222 'callback' => array( $this, 'remove_x_frame_options' ),
223 'priority' => 9999,
224 'args' => 1,
225 'reason' => 'Must remove X-Frame-Options AFTER security plugins set it (80ee545a5).',
226 'phase' => 'post-latch',
227 ),
228 array(
229 'hook' => null,
230 'callback' => static function (): void {
231 ( new Services\Core_Order_Audit_Guard() )->register_hooks();
232 },
233 'priority' => 10,
234 'args' => 0,
235 'reason' => 'Auth provenance at 20 MUST register before JWT at 20, after core cookie/password auth; rest_pre_dispatch reads it at 10.',
236 'phase' => 'post-latch',
237 ),
238 array(
239 'hook' => null,
240 'callback' => array( Sync\Coupon_Modified_Date::class, 'register_hooks' ),
241 'priority' => 10,
242 'args' => 0,
243 'reason' => 'Default 10; unconditional meta-only coupon edit timestamps for date-based replication; journal uses wall clock, not post_modified.',
244 'phase' => 'post-latch',
245 ),
246 array(
247 'hook' => 'determine_current_user',
248 'callback' => array( $this, 'determine_current_user_early' ),
249 'priority' => 20,
250 'args' => 1,
251 'reason' => 'At 20 AFTER the audit guard and core cookie/password handlers; register before init, regardless of the request marker.',
252 'phase' => 'post-latch',
253 ),
254 array(
255 'hook' => null,
256 'callback' => static function (): void {
257 ( new Services\Lifecycle_Events() )->register_hooks();
258 },
259 'priority' => 10,
260 'args' => 0,
261 'reason' => 'Default 10; append after auth pair; admin_init flushes pending events and consent-gates refresh scheduling, not the cron listener.',
262 'phase' => 'post-latch',
263 ),
264 array(
265 'hook' => null,
266 'callback' => static function (): void {
267 Services\Error_Reporter::instance()->register_hooks();
268 },
269 'priority' => 10,
270 'args' => 0,
271 'reason' => 'Append after lifecycle; REST priority 999 reports the final response status, gated by consent (#1811).',
272 'phase' => 'post-latch',
273 ),
274 array(
275 'hook' => null,
276 'callback' => static function (): void {
277 add_action( 'wp_logout', array( Services\Auth::class, 'revoke_web_session_on_logout' ), 10, 1 );
278 add_action( 'password_reset', array( Services\Auth::class, 'revoke_sessions_on_password_reset' ), 10, 1 );
279 add_action( 'profile_update', array( Services\Auth::class, 'revoke_sessions_on_password_change' ), 10, 2 );
280 },
281 'priority' => 10,
282 'args' => 0,
283 'reason' => 'Default 10; logout ends this browser\'s web POS session, a password reset or change ends every POS session. Auth is built only when one fires.',
284 'phase' => 'post-latch',
285 ),
286 );
287
288 return array_values(
289 array_filter(
290 $rows,
291 static function ( array $row ) use ( $sync_latched ): bool {
292 return $sync_latched || 'sync-latched' !== $row['phase'];
293 }
294 )
295 );
296 }
297
298 /**
299 * Clear cached data that depends on the Pro license.
300 *
301 * @param mixed $value The new option value.
302 * @param mixed $old_value The previous option value (false when unset).
303 *
304 * @return mixed
305 */
306 public static function remove_license_transient( $value, $old_value = false ) {
307 // Pro's updater can react to the update_plugins deletion by reading —
308 // and, when the stored instance id is blank, re-saving — the license
309 // option, which re-enters this filter. Without the guard that cycle is
310 // unbounded and OOMs the first license activation on a fresh install.
311 static $clearing = false;
312 if ( $clearing ) {
313 return $value;
314 }
315 $clearing = true;
316 delete_transient( 'woocommerce_pos_pro_license_status' );
317
318 // The update caches bind to the license key and activation state. A
319 // write that changes neither — e.g. Pro's read-side instance mint —
320 // must not wipe update_plugins: Pro reacts to that deletion by
321 // clearing its own update-data cache, which empties the payload of an
322 // update check that is in flight when the mint occurs.
323 $old = \is_array( $old_value ) ? $old_value : array();
324 $new = \is_array( $value ) ? $value : array();
325 if (
326 (string) ( $old['key'] ?? '' ) !== (string) ( $new['key'] ?? '' )
327 || ! empty( $old['activated'] ) !== ! empty( $new['activated'] )
328 ) {
329 delete_site_transient( 'update_plugins' );
330 }
331 $clearing = false;
332
333 return $value;
334 }
335
336 /**
337 * Early authentication check for JWT tokens.
338 *
339 * This runs BEFORE rest_api_init, so we can authenticate users before WP REST API
340 * permission callbacks run. This is especially important for authorization via
341 * query parameter (?authorization=Bearer...) which some servers require.
342 *
343 * Note: We don't check for X-WCPOS header here because:
344 * 1. The header check uses getallheaders() which may not work in all environments
345 * 2. JWT authentication should work regardless - the token itself is proof of WCPOS usage
346 * 3. Invalid tokens (non-WCPOS) will fail validation anyway
347 *
348 * @param false|int $user_id User ID if one has been determined, false otherwise.
349 *
350 * @return false|int User ID if authenticated, original value otherwise.
351 */
352 public function determine_current_user_early( $user_id ) {
353 // Skip if user already authenticated.
354 if ( ! empty( $user_id ) ) {
355 return $user_id;
356 }
357
358 $authenticated_user_id = AuthService::instance()->authenticate_request();
359 if ( false === $authenticated_user_id || is_wp_error( $authenticated_user_id ) ) {
360 return $user_id;
361 }
362
363 return $authenticated_user_id;
364 }
365
366 /**
367 * Load the required resources.
368 */
369 public function init(): void {
370 $this->init_common();
371 $this->init_frontend();
372 $this->init_admin();
373 $this->init_integrations();
374 }
375
376 /**
377 * Loads the POS API and duck punches the WC REST API.
378 */
379 public function init_rest_api(): void {
380 $is_wcpos_request = woocommerce_pos_request();
381
382 if ( $is_wcpos_request ) {
383 if ( ! wcpos_request( 'header' ) && ! wcpos_request( 'query_var' ) ) {
384 // Namespace-detected only: routes still register, but surface
385 // that a proxy/WAF is stripping the X-WCPOS marker.
386 $this->log_unmarked_wcpos_rest_request();
387 }
388 new API();
389 } else {
390 // Queue the registration at a later priority of the SAME
391 // rest_api_init pass this method runs on (priority 20), so
392 // register_rest_route() executes during the action as WP requires.
393 // When this method is called outside the action (tests), the
394 // add_action is simply inert.
395 add_action( 'rest_api_init', array( $this, 'register_public_relay_routes' ), 30 );
396 new WC_API();
397 }
398 }
399
400 /**
401 * Register the relay's public consent-callback route for unmarked requests.
402 *
403 * The WCPOS Cloud Print relay proves site consent by fetching
404 * print-jobs/relay-verification WITHOUT the WCPOS request marker, so this
405 * single public route must exist even when the full WCPOS API is not
406 * loaded. Everything else stays behind the marker.
407 */
408 public function register_public_relay_routes(): void {
409 register_rest_route(
410 SHORT_NAME . '/v1',
411 '/print-jobs/relay-verification',
412 array(
413 'methods' => 'GET',
414 'callback' => array( new API\V1\Print_Jobs_Controller(), 'relay_verification' ),
415 'permission_callback' => '__return_true',
416 )
417 );
418 }
419
420 /**
421 * Log requests for a WCPOS namespace that omitted the request marker.
422 *
423 * Namespace detection registers the routes anyway; this surfaces that a
424 * proxy/WAF is stripping the X-WCPOS marker so misconfigured hosts stay
425 * visible in the logs. Warnings are limited by API version to avoid
426 * allowing repeated unauthenticated requests to flood WooCommerce logs.
427 */
428 private function log_unmarked_wcpos_rest_request(): void {
429 global $wp;
430
431 $route = isset( $wp->query_vars['rest_route'] )
432 ? '/' . ltrim( sanitize_text_field( wp_unslash( (string) $wp->query_vars['rest_route'] ) ), '/' )
433 : '';
434
435 if ( 1 !== preg_match( '#^/wcpos/v([12])(?:/|$)#', $route, $matches ) ) {
436 return;
437 }
438
439 // The relay's consent callback is expected unmarked traffic (see
440 // register_public_relay_routes()), not a misconfigured client.
441 if ( '/wcpos/v1/print-jobs/relay-verification' === $route ) {
442 return;
443 }
444
445 $transient = 'wcpos_missing_request_marker_v' . $matches[1];
446 if ( false !== get_transient( $transient ) ) {
447 return;
448 }
449
450 set_transient( $transient, 1, 5 * MINUTE_IN_SECONDS );
451 Logger::warning( $route . ': request marker missing (routes still registered via namespace detection).' );
452 }
453
454 /**
455 * Adds 'wcpos' to the query variables allowed before processing.
456 *
457 * Allows (publicly allowed) query vars to be added, removed, or changed prior
458 * to executing the query. Needed to allow custom rewrite rules using your own arguments
459 * to work, or any other custom query variables you want to be publicly available.
460 *
461 * @param string[] $query_vars The array of allowed query variable names.
462 *
463 * @return string[] The array of allowed query variable names.
464 */
465 public function query_vars( array $query_vars ): array {
466 $query_vars[] = SHORT_NAME;
467
468 return $query_vars;
469 }
470
471 /**
472 * Allow HEAD checks for WP API Link URL and server uptime.
473 *
474 * This is the NON-REST lane and is not part of the REST wire contract
475 * ({@see Rest_Cors}): `send_headers` fires from `WP::main()`, which a REST
476 * request never reaches — core's `rest_api_loaded()` runs on
477 * `parse_request` and dies. What it serves is the app's site-discovery
478 * probe against an ordinary page: the app reads the `Link:
479 * <.../wp-json/>; rel="https://api.w.org/"` header cross-origin to find
480 * the REST root, which needs both headers below. Some servers turn HEAD
481 * into GET, hence the `?_method=head` query param rather than the method.
482 *
483 * This is live, not legacy. The client calls it on every Connect:
484 * `packages/core/src/screens/auth/hooks/use-url-discovery.ts` issues
485 * `http.head()` against the site root, and
486 * `packages/hooks/src/use-http-client/use-http-client.tsx` sets
487 * `params._method = 'HEAD'` on every HEAD request (both in the client
488 * monorepo). That same client code deliberately omits the `X-WCPOS`
489 * marker for HEAD, so this handler cannot be marker-gated and must stay
490 * unconditional. 521ccb9a added it; the `?wcpos=1` gate it originally
491 * carried is long gone.
492 *
493 * @return void
494 */
495 public function send_headers(): void {
496 // some server convert HEAD to GET method, so use this query param instead.
497 if ( isset( $_GET['_method'] ) && 'head' === strtolower( sanitize_text_field( wp_unslash( $_GET['_method'] ) ) ) ) {
498 header( 'Access-Control-Allow-Origin: *' );
499 header( 'Access-Control-Expose-Headers: Link' );
500 }
501 }
502
503 /**
504 * Some security plugins will set X-Frame-Options: SAMEORIGIN/DENY, which will prevent the POS desktop
505 * application from opening pages like the login in an iframe.
506 *
507 * For pages we need, we will remove the X-Frame-Options header.
508 *
509 * @param mixed $wp The WP object.
510 *
511 * @return void
512 */
513 public function remove_x_frame_options( $wp ): void {
514 if ( woocommerce_pos_request() || isset( $wp->query_vars['wcpos-login'] ) ) {
515 if ( ! headers_sent() && \function_exists( 'header_remove' ) ) {
516 header_remove( 'X-Frame-Options' );
517 }
518 }
519 }
520
521 /**
522 * Common initializations, by request lane.
523 *
524 * Every request gets the always group; everything else is constructed only
525 * on the lanes that use it, and the order group additionally on the first
526 * order write of ANY request, so the lane classifier is an optimisation
527 * rather than a correctness gate. {@see Service_Groups} holds the membership
528 * of each group and the reasons behind it.
529 *
530 * Measured 2026-09-03: a storefront page loaded ~80 plugin files and 22
531 * objects for hooks that never fire there (see
532 * .claude/research/2026-09-03-lazy-service-construction-spec.md).
533 */
534 private function init_common(): void {
535 Service_Groups::ensure( Service_Groups::ALWAYS );
536
537 if ( Services\Request_Lane::is_storefront() ) {
538 // Order-event services arrive on the first order write, if any.
539 Service_Groups::arm_order_group();
540 return;
541 }
542
543 Service_Groups::ensure( Service_Groups::ORDER );
544 Service_Groups::ensure( Service_Groups::POS );
545 }
546
547 /**
548 * Construct the order-event services exactly once per request.
549 *
550 * Idempotent and safe to call after `init`; each service handles its own
551 * late registration. Fires `woocommerce_pos_order_services_ready` once so
552 * Pro and extensions can construct their own order-event services at the
553 * same moment on every lane. Implementation now lives in Service_Groups.
554 */
555 public static function ensure_order_services(): void {
556 Service_Groups::ensure( Service_Groups::ORDER );
557 }
558
559 /**
560 * Forget which groups were constructed. Tests only.
561 *
562 * @internal
563 */
564 public static function reset_request_state(): void {
565 Service_Groups::reset();
566 Services\Request_Lane::reset();
567 }
568
569 /**
570 * Frontend specific initializations.
571 */
572 private function init_frontend(): void {
573 if ( is_admin() ) {
574 return;
575 }
576 // The public receipt shortcode and the My Account receipt action are
577 // storefront features; they construct the template services when used.
578 new Storefront_Receipts();
579 if ( ! Services\Request_Lane::is_storefront() ) {
580 // The POS routes (rewrite rules, checkout context, order-pay and
581 // coupon forms) only matter on requests the classifier saw as POS.
582 new Template_Router();
583 new Form_Handler();
584 }
585 }
586
587 /**
588 * Admin specific initializations.
589 */
590 private function init_admin(): void {
591 if ( is_admin() ) {
592 // Register AJAX handler before the branch so it's available during AJAX requests.
593 add_action( 'wp_ajax_wcpos_track_upgrade_click_ajax', array( Menu::class, 'handle_upgrade_click_ajax' ) );
594 add_action( 'admin_post_wcpos_track_upgrade_click', array( Menu::class, 'handle_upgrade_click_redirect' ) );
595
596 if ( wp_doing_ajax() ) {
597 new AJAX();
598 } else {
599 new Admin();
600 }
601 }
602 }
603
604 /**
605 * Integrations.
606 */
607 private function init_integrations(): void {
608 // WooCommerce Bookings - http://www.woothemes.com/products/woocommerce-bookings/
609 // if ( class_exists( 'WC-Bookings' ) ) {
610 // new Integrations\Bookings();
611 // }.
612
613 // Yoast SEO - https://wordpress.org/plugins/wordpress-seo/.
614 if ( class_exists( 'WPSEO_Options' ) ) {
615 new Integrations\WPSEO();
616 }
617
618 // wePOS alters the WooCommerce REST API, breaking the expected schema
619 // It's very bad form on their part, but we need to work around it.
620 // Its only hook is admin_init (a conflict notice), so admin lane only.
621 if ( is_admin() ) {
622 new Integrations\WePOS();
623 }
624
625 // WooCommerce Tax - https://wordpress.org/plugins/woocommerce-services/
626 // Its class exists whenever the plugin is active, but its callbacks are
627 // only hooked when automated taxes are on and the store country is
628 // supported, so the integration looks them up on the hooks at
629 // recalculation time instead of gating on the class here.
630 new Integrations\WooCommerce_Tax();
631 }
632 }
633