PluginProbe
WCPOS – Point of Sale (POS) plugin for WooCommerce / 1.10.21
WCPOS – Point of Sale (POS) plugin for WooCommerce v1.10.21
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
← All changes | includes/wcpos-functions.php +91 -2 1.10.10 → 1.10.21 View file →
@@ -229,24 +229,113 @@
229 229 return (bool) apply_filters( 'woocommerce_pos_is_pro_active', \defined( 'WCPOS\WooCommercePOSPro\VERSION' ) );
230 230 }
231 231 }
232 232
233 +if ( ! \function_exists( 'wcpos_get_site_identity_home' ) ) {
234 + /**
235 + * Get the site address as an identity: no scheme, no `www.`, no trailing
236 + * slash; the host lower-cased; the port and the case-sensitive path kept,
237 + * so `example.com/staging` is another site and `www.example.com` is not.
238 + *
239 + * @return string Normalised home address.
240 + */
241 + function wcpos_get_site_identity_home(): string { // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- uses wcpos_ prefix.
242 + $parts = wp_parse_url( '//' . preg_replace( '#^(?:https?:)?//#i', '', home_url() ) );
243 + $host = preg_replace( '#^www\.#', '', strtolower( (string) ( $parts['host'] ?? '' ) ) );
244 + $port = isset( $parts['port'] ) ? ':' . $parts['port'] : '';
245 +
246 + return untrailingslashit( $host . $port . ( $parts['path'] ?? '' ) );
247 + }
248 +}
249 +
250 +if ( ! \function_exists( 'wcpos_site_identity_home_row' ) ) {
251 + /**
252 + * Read the persisted home marker straight from the options table.
253 + *
254 + * The option cache answers get_option() for the rest of the request, and a
255 + * same-value update_option() leaves it untouched; this is the only read that can tell a
256 + * marker another request already advanced from one whose write failed.
257 + *
258 + * @return string|null The stored marker, or null when absent.
259 + */
260 + function wcpos_site_identity_home_row(): ?string { // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- uses wcpos_ prefix.
261 + global $wpdb;
262 + $row = $wpdb->get_var( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- bypasses the option cache on purpose, see docblock.
263 + $wpdb->prepare( "SELECT option_value FROM {$wpdb->options} WHERE option_name = %s LIMIT 1", 'woocommerce_pos_uuid_home' ) // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- core table name.
264 + );
265 +
266 + return \is_string( $row ) ? $row : null;
267 + }
268 +}
269 +
233 270 /*
234 - * Get the site UUID (Plugin State), generating and persisting it on first use.
271 + * Get the site UUID (Plugin State), tied to the site address.
235 272 *
236 273 * @return string Site UUID.
237 274 */
238 275 if ( ! \function_exists( 'wcpos_get_site_uuid' ) ) {
239 276 /**
240 - * Get the site UUID, generating and persisting it on first use.
277 + * Get the site UUID, generating it on first use or when the address changes.
241 278 *
242 279 * Single owner for the woocommerce_pos_uuid option — the
243 280 * generate-if-missing logic previously lived in three places (REST index,
244 281 * POS frontend, analytics) and could race.
282 + * The saved home prevents database clones sharing a live store's identity.
283 + * On upgrade, an absent home is recorded without changing the existing UUID.
245 284 *
246 285 * @return string Site UUID.
247 286 */
248 287 function wcpos_get_site_uuid(): string { // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- uses wcpos_ prefix.
288 + $home = wcpos_get_site_identity_home();
289 + $stored_home = get_option( 'woocommerce_pos_uuid_home' );
290 + if ( false === $stored_home ) {
291 + // Keep the first writer's home if another request wins the race.
292 + $stored_home = add_option( 'woocommerce_pos_uuid_home', $home )
293 + ? $home
294 + : get_option( 'woocommerce_pos_uuid_home' );
295 + }
296 +
297 + if ( $stored_home !== $home ) {
298 + // A moved address is a new identity. Two requests can both land
299 + // here; the last write wins and each returns what is stored, so the
300 + // REST response never advertises a uuid the database does not hold.
301 + // The home marker advances only behind a persisted uuid: a vetoed or
302 + // failed write would otherwise hand out an identity that the next
303 + // call, seeing a matching home, no longer returns.
304 + $previous = get_option( 'woocommerce_pos_uuid', '' );
305 + $minted = \Ramsey\Uuid\Uuid::uuid4()->toString();
306 + if ( update_option( 'woocommerce_pos_uuid', $minted ) ) {
307 + // update_option() also answers false when the row already holds
308 + // $home: a concurrent request finished the move between this one's
309 + // read and its write. That is a completed move, not a failed one,
310 + // and rolling back would leave the old uuid behind the new marker
311 + // for good, since every later call sees a matching home. This
312 + // request's option cache still holds the old marker (the same-value
313 + // UPDATE touched no row, so nothing refreshed it), so the check
314 + // reads the row itself and, when the move is confirmed, drops the
315 + // stale cache so a later call in this request does not mint again,
316 + // and drops the uuid this request cached from its own write so the
317 + // value returned below is the one the other request left stored.
318 + $marker_moved = update_option( 'woocommerce_pos_uuid_home', $home ) || wcpos_site_identity_home_row() === $home;
319 + if ( $marker_moved && get_option( 'woocommerce_pos_uuid_home' ) !== $home ) {
320 + wp_cache_delete( 'woocommerce_pos_uuid', 'options' );
321 + wp_cache_delete( 'woocommerce_pos_uuid_home', 'options' );
322 + wp_cache_delete( 'alloptions', 'options' );
323 + }
324 + if ( ! $marker_moved && \is_string( $previous ) && '' !== $previous ) {
325 + // The marker did not move: put the previous uuid back so the next
326 + // call retries the whole move, rather than minting again on top of
327 + // a rotation the marker never recorded.
328 + update_option( 'woocommerce_pos_uuid', $previous );
329 + }
330 + }
331 +
332 + $stored = get_option( 'woocommerce_pos_uuid', '' );
333 + if ( \is_string( $stored ) && '' !== $stored ) {
334 + return $stored;
335 + }
336 + }
337 +
249 338 $uuid = get_option( 'woocommerce_pos_uuid', '' );
250 339 if ( \is_string( $uuid ) && '' !== $uuid ) {
251 340 return $uuid;
252 341 }