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

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

113 lines 4.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Create identity proof shared by fresh writes and poisoned retries.
4 *
5 * @package WCPOS\WooCommercePOS\Sync
6 */
7
8 namespace WCPOS\WooCommercePOS\Sync;
9
10 use WCPOS\WooCommercePOS\Interfaces\Collection_Writer_Interface;
11 use WP_Error;
12
13 // phpcs:disable Squiz.Commenting, Generic.Commenting -- Lifecycle docblocks are intentionally concise.
14
15 /**
16 * A created record must own its client UUID before the mutation is finalized,
17 * or the till keys on the wrong record. A retry re-enters the same proof against
18 * a poison checkpoint. ADR 0038 governs WHEN identity is re-proved; this module
19 * only moves WHERE the sequence lives.
20 *
21 * after_create: fresh lane, after the checkpoint attempt and before the proof.
22 * after_identity: fresh lane, after the proof and before finalization.
23 * after_recovery: retry lane, after the proof and before finalization.
24 * after_update: update lane only, called by the controller, not this module.
25 */
26 final class Create_Identity {
27 /** @var mixed Duck-typed mutation store; tests inject an in-memory implementation. */
28 private $store;
29
30 /** Use the same store as the write controller. */
31 public function __construct( $store ) {
32 $this->store = $store;
33 }
34
35 /** @return int|WP_Error Created id after proof and finalization, or the protocol error. */
36 public function stamp( array $meta, array $m, int $new_id, int $response_status, Collection_Writer_Interface $writer ) {
37 $checkpointed = $this->store->mark_poison( $m['mutationId'], $new_id, $response_status );
38 $writer->after_create( $new_id, $m['payload'] );
39 $identity_error = $this->prove( $meta, $new_id, $m['recordId'] );
40 // Attempt identity even when the checkpoint failed: the known record must retain its UUID.
41 if ( ! $checkpointed ) {
42 $this->store->mark_indeterminate( $m['mutationId'], $new_id, $response_status );
43 return $this->finalize_error();
44 }
45 if ( $identity_error ) {
46 return $identity_error;
47 }
48 $writer->after_identity( $new_id, $m['payload'] );
49 if ( ! $this->store->finalize_poison( $m['mutationId'], $new_id ) ) {
50 return $this->finalize_error();
51 }
52 return $new_id;
53 }
54
55 /** @return array{id: int, status: int}|WP_Error Recovered id and recorded status, or the protocol error. */
56 public function recover( array $meta, array $m, array $hit, Collection_Writer_Interface $writer ) {
57 $remote_id = (int) ( $hit['remote_id'] ?? 0 );
58 $record_uuid = (string) ( $hit['record_uuid'] ?? '' );
59 if ( $record_uuid !== $m['recordId'] ) {
60 return $this->conflict_error();
61 }
62 if ( 'create' !== ( $hit['operation'] ?? '' ) || $remote_id <= 0 ) {
63 return $this->persistence_error();
64 }
65 $resolved = $this->store->resolve_id_by_uuid( $meta['id_type'], $record_uuid, $meta );
66 if ( is_wp_error( $resolved ) ) {
67 return $resolved;
68 }
69 if ( $resolved > 0 && $resolved !== $remote_id ) {
70 return $this->persistence_error();
71 }
72 $identity_error = $this->prove( $meta, $remote_id, $record_uuid );
73 if ( $identity_error ) {
74 return $identity_error;
75 }
76 $writer->after_recovery( $remote_id, $m['payload'] );
77 if ( ! $this->store->finalize_poison( $m['mutationId'], $remote_id ) ) {
78 return $this->finalize_error();
79 }
80 return array(
81 'id' => $remote_id,
82 'status' => isset( $hit['response_status'] ) ? (int) $hit['response_status'] : 201,
83 );
84 }
85
86 /** Persist the UUID and prove it resolves back to this exact record. */
87 private function prove( array $meta, int $id, string $record_uuid ): ?WP_Error {
88 if ( ! $this->store->persist_uuid( $meta['id_type'], $id, $record_uuid ) ) {
89 return $this->persistence_error();
90 }
91 $resolved = $this->store->resolve_id_by_uuid( $meta['id_type'], $record_uuid, $meta );
92 if ( is_wp_error( $resolved ) ) {
93 return $resolved;
94 }
95 return $resolved !== $id ? $this->persistence_error() : null;
96 }
97
98 /** Same code and wording for either lane's failed identity proof. */
99 private function persistence_error(): WP_Error {
100 return new WP_Error( 'woo_rxdb_sync_identity_persistence_failed', 'Unable to persist created record identity.', array( 'status' => 500 ) );
101 }
102
103 /** A retry cannot change the checkpoint's client identity. */
104 private function conflict_error(): WP_Error {
105 return new WP_Error( 'woo_rxdb_sync_identity_conflict', 'recordId disagrees with the stored mutation identity.', array( 'status' => 422 ) );
106 }
107
108 /** Keep the controller's update/delete finalization error wording unchanged. */
109 private function finalize_error(): WP_Error {
110 return new WP_Error( 'woo_rxdb_sync_finalize_failed', 'Woo write succeeded but mutation finalization failed; retry the same mutationId.', array( 'status' => 500 ) );
111 }
112 }
113