PluginProbe
WCPOS – Point of Sale (POS) plugin for WooCommerce / 1.9.16
WCPOS – Point of Sale (POS) plugin for WooCommerce v1.9.16
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 / API / Traits / Uuid_Handler.php

Uuid_Handler.php in WCPOS – Point of Sale (POS) plugin for WooCommerce 1.9.16, at includes/API/Traits/Uuid_Handler.php

339 lines 9.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Uuid_Handler.
4 *
5 * @package WCPOS\WooCommercePOS
6 */
7
8 namespace WCPOS\WooCommercePOS\API\Traits;
9
10 use Exception;
11 use Ramsey\Uuid\Uuid;
12 use WC_Data;
13 use WC_Meta_Data;
14 use WC_Order_Item;
15 use WCPOS\WooCommercePOS\Logger;
16 use WP_User;
17 use WC_Product;
18 use WC_Product_Variation;
19 use WC_Abstract_Order;
20 use Automattic\WooCommerce\Utilities\OrderUtil;
21 use function get_user_meta;
22 use function update_user_meta;
23 use function delete_user_meta;
24 use function delete_term_meta;
25 use function add_term_meta;
26 use function wp_cache_add;
27 use function wp_cache_delete;
28
29 /**
30 * Trait Uuid_Handler.
31 *
32 * Ensures each WooCommerce record (products, orders, customers etc)
33 * has a consistent unique UUID stored in the database.
34 */
35 trait Uuid_Handler {
36
37 /**
38 * Acquire a lock using the persistent object cache.
39 *
40 * @param string $lock_key Unique key for the lock.
41 * @param int $timeout Timeout in seconds.
42 * @return bool True if lock acquired, false otherwise.
43 */
44 private function acquire_lock( string $lock_key, int $timeout = 10 ): bool {
45 $attempts = 0;
46 $sleep_time = 100000; // 100ms in microseconds.
47 // Try every 100ms until timeout.
48 while ( $attempts < $timeout * 10 ) {
49 // wp_cache_add() returns true if the key did not exist.
50 if ( wp_cache_add( $lock_key, true, 'wc_pos_locks', $timeout ) ) {
51 return true;
52 }
53 usleep( $sleep_time );
54 $attempts++;
55 }
56 return false;
57 }
58
59 /**
60 * Release a lock.
61 *
62 * @param string $lock_key Unique key for the lock.
63 * @return void
64 */
65 private function release_lock( string $lock_key ): void {
66 wp_cache_delete( $lock_key, 'wc_pos_locks' );
67 }
68
69 /**
70 * Make sure the WC Data Object has a UUID.
71 *
72 * @param WC_Data $object The WooCommerce data object.
73 * @return void
74 */
75 private function maybe_add_post_uuid( WC_Data $object ): void {
76 // Use object_type to ensure uniqueness across different record types.
77 $lock_key = 'wc_pos_uuid_' . $object->get_type() . '_' . $object->get_id();
78 if ( ! $this->acquire_lock( $lock_key, 10 ) ) {
79 Logger::log( "Unable to acquire lock for post UUID update for {$object->get_type()} id " . $object->get_id() );
80 return;
81 }
82 try {
83 $meta_data = $object->get_meta_data();
84 $uuids = array_filter(
85 $meta_data,
86 function ( WC_Meta_Data $meta ) {
87 return '_woocommerce_pos_uuid' === $meta->key;
88 }
89 );
90
91 $uuid_values = array_map(
92 function ( WC_Meta_Data $meta ) {
93 return $meta->value;
94 },
95 $uuids
96 );
97
98 // Re-index to ensure sequential keys.
99 $uuid_values = array_values( $uuid_values );
100
101 // If more than one UUID exists, keep the first and delete the rest.
102 if ( count( $uuid_values ) > 1 ) {
103 $first_uuid_key = key( $uuids );
104 foreach ( $uuids as $key => $uuid_meta ) {
105 if ( $key === $first_uuid_key ) {
106 continue;
107 }
108 $object->delete_meta_data_by_mid( $uuid_meta->id );
109 }
110 $uuid_values = array( reset( $uuids )->value );
111 }
112
113 $should_update_uuid = empty( $uuid_values )
114 || ( isset( $uuid_values[0] ) && ! Uuid::isValid( $uuid_values[0] ) )
115 || ( isset( $uuid_values[0] ) && $this->uuid_postmeta_exists( $uuid_values[0], $object ) );
116
117 if ( $should_update_uuid ) {
118 $object->update_meta_data( '_woocommerce_pos_uuid', $this->create_uuid() );
119 $object->save_meta_data();
120 }
121 } finally {
122 $this->release_lock( $lock_key );
123 }
124 }
125
126 /**
127 * Ensure the WP_User has a valid UUID.
128 *
129 * @param WP_User $user The WordPress user object.
130 * @return void
131 */
132 private function maybe_add_user_uuid( WP_User $user ): void {
133 $lock_key = 'wc_pos_uuid_user_' . $user->ID;
134 if ( ! $this->acquire_lock( $lock_key, 10 ) ) {
135 Logger::log( 'Unable to acquire lock for user UUID update for user id ' . $user->ID );
136 return;
137 }
138 try {
139 $uuids = get_user_meta( $user->ID, '_woocommerce_pos_uuid', false );
140
141 // If more than one UUID exists, keep the first and remove the rest.
142 $uuids_count = count( $uuids );
143 if ( $uuids_count > 1 ) {
144 for ( $i = 1; $i < $uuids_count; $i++ ) {
145 delete_user_meta( $user->ID, '_woocommerce_pos_uuid', $uuids[ $i ] );
146 }
147 $uuids = array( $uuids[0] );
148 }
149
150 $should_update_uuid = empty( $uuids )
151 || ( isset( $uuids[0] ) && ! Uuid::isValid( $uuids[0] ) )
152 || ( isset( $uuids[0] ) && $this->uuid_usermeta_exists( $uuids[0], $user->ID ) );
153
154 if ( $should_update_uuid ) {
155 update_user_meta( $user->ID, '_woocommerce_pos_uuid', $this->create_uuid() );
156 }
157 } finally {
158 $this->release_lock( $lock_key );
159 }
160 }
161
162 /**
163 * Ensure the WC_Order_Item has a valid UUID.
164 *
165 * @param WC_Order_Item $item The order item object.
166 * @return void
167 */
168 private function maybe_add_order_item_uuid( WC_Order_Item $item ): void {
169 $lock_key = 'wc_pos_uuid_order_item_' . $item->get_id();
170 if ( ! $this->acquire_lock( $lock_key, 10 ) ) {
171 Logger::log( 'Unable to acquire lock for order item UUID update for order item id ' . $item->get_id() );
172 return;
173 }
174 try {
175 $uuid = $item->get_meta( '_woocommerce_pos_uuid' );
176 if ( ! $uuid ) {
177 $uuid = Uuid::uuid4()->toString();
178 $item->update_meta_data( '_woocommerce_pos_uuid', $uuid );
179 $item->save_meta_data();
180 }
181 } finally {
182 $this->release_lock( $lock_key );
183 }
184 }
185
186 /**
187 * Ensure the term has a valid UUID and return it.
188 *
189 * @param object $term The term object.
190 * @return string
191 */
192 private function get_term_uuid( $term ): string {
193 $lock_key = 'wc_pos_uuid_term_' . $term->term_id;
194 if ( ! $this->acquire_lock( $lock_key, 10 ) ) {
195 Logger::log( 'Unable to acquire lock for term UUID update for term id ' . $term->term_id );
196 $uuids = get_term_meta( $term->term_id, '_woocommerce_pos_uuid', false );
197 return ( ! empty( $uuids ) && Uuid::isValid( $uuids[0] ) ) ? $uuids[0] : $this->create_uuid();
198 }
199 try {
200 $uuids = get_term_meta( $term->term_id, '_woocommerce_pos_uuid', false );
201
202 $uuids_count = count( $uuids );
203 if ( $uuids_count > 1 ) {
204 for ( $i = 1; $i < $uuids_count; $i++ ) {
205 delete_term_meta( $term->term_id, '_woocommerce_pos_uuid', $uuids[ $i ] );
206 }
207 $uuids = array( $uuids[0] );
208 }
209
210 $should_update_uuid = empty( $uuids )
211 || ( isset( $uuids[0] ) && ! Uuid::isValid( $uuids[0] ) )
212 || ( isset( $uuids[0] ) && $this->uuid_termmeta_exists( $uuids[0], $term->term_id ) );
213
214 if ( $should_update_uuid ) {
215 $uuid = $this->create_uuid();
216 add_term_meta( $term->term_id, '_woocommerce_pos_uuid', $uuid, true );
217 return $uuid;
218 }
219
220 return $uuids[0];
221 } finally {
222 $this->release_lock( $lock_key );
223 }
224 }
225
226 /**
227 * Generate a new UUID.
228 *
229 * @return string
230 */
231 private function create_uuid(): string {
232 try {
233 return Uuid::uuid4()->toString();
234 } catch ( Exception $e ) {
235 Logger::log( 'UUID generation failed: ' . $e->getMessage() );
236 return 'fallback-uuid-' . time();
237 }
238 }
239
240 /**
241 * Check if the given UUID is unique.
242 *
243 * @param string $uuid The UUID to check.
244 * @param WC_Data $object The WooCommerce data object.
245 * @return bool True if unique, false otherwise.
246 */
247 private function uuid_postmeta_exists( string $uuid, WC_Data $object ): bool {
248 global $wpdb;
249
250 if ( $object instanceof WC_Abstract_Order && class_exists( OrderUtil::class ) && OrderUtil::custom_orders_table_usage_is_enabled() ) {
251 $result = $wpdb->get_var(
252 $wpdb->prepare(
253 "SELECT 1 FROM {$wpdb->prefix}wc_orders_meta WHERE meta_key = '_woocommerce_pos_uuid' AND meta_value = %s AND order_id != %d LIMIT 1",
254 $uuid,
255 $object->get_id()
256 )
257 );
258 } else {
259 $result = $wpdb->get_var(
260 $wpdb->prepare(
261 "SELECT 1 FROM {$wpdb->postmeta} WHERE meta_key = '_woocommerce_pos_uuid' AND meta_value = %s AND post_id != %d LIMIT 1",
262 $uuid,
263 $object->get_id()
264 )
265 );
266 }
267
268 return (bool) $result;
269 }
270
271 /**
272 * Retrieve order IDs by UUID.
273 *
274 * @param string $uuid The UUID to search for.
275 * @return array
276 */
277 private function get_order_ids_by_uuid( string $uuid ) {
278 global $wpdb;
279
280 if ( class_exists( OrderUtil::class ) && OrderUtil::custom_orders_table_usage_is_enabled() ) {
281 $result = $wpdb->get_col(
282 $wpdb->prepare(
283 "SELECT order_id FROM {$wpdb->prefix}wc_orders_meta WHERE meta_key = '_woocommerce_pos_uuid' AND meta_value = %s",
284 $uuid
285 )
286 );
287 } else {
288 $result = $wpdb->get_col(
289 $wpdb->prepare(
290 "SELECT post_id FROM {$wpdb->postmeta} WHERE meta_key = '_woocommerce_pos_uuid' AND meta_value = %s",
291 $uuid
292 )
293 );
294 }
295
296 return $result;
297 }
298
299 /**
300 * Check if the given UUID already exists for any user.
301 *
302 * @param string $uuid The UUID to check.
303 * @param int $exclude_id The user ID to exclude.
304 * @return bool True if unique, false otherwise.
305 */
306 private function uuid_usermeta_exists( string $uuid, int $exclude_id ): bool {
307 global $wpdb;
308 $result = $wpdb->get_var(
309 $wpdb->prepare(
310 "SELECT 1 FROM {$wpdb->usermeta} WHERE meta_key = '_woocommerce_pos_uuid' AND meta_value = %s AND user_id != %d LIMIT 1",
311 $uuid,
312 $exclude_id
313 )
314 );
315
316 return (bool) $result;
317 }
318
319 /**
320 * Check if the given UUID already exists for any term.
321 *
322 * @param string $uuid The UUID to check.
323 * @param int $exclude_term_id The term ID to exclude.
324 * @return bool True if unique, false otherwise.
325 */
326 private function uuid_termmeta_exists( string $uuid, int $exclude_term_id ): bool {
327 global $wpdb;
328 $result = $wpdb->get_var(
329 $wpdb->prepare(
330 "SELECT 1 FROM {$wpdb->termmeta} WHERE meta_key = '_woocommerce_pos_uuid' AND meta_value = %s AND term_id != %d LIMIT 1",
331 $uuid,
332 $exclude_term_id
333 )
334 );
335
336 return (bool) $result;
337 }
338 }
339