Skip to content

Commit

Permalink
fix product duplication bug
Browse files Browse the repository at this point in the history
  • Loading branch information
kilbot committed Jan 21, 2024
1 parent a5fbc05 commit 0ccddcc
Show file tree
Hide file tree
Showing 11 changed files with 282 additions and 43 deletions.
148 changes: 132 additions & 16 deletions includes/API/Traits/Uuid_Handler.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@
use WC_Order_Item;
use WCPOS\WooCommercePOS\Logger;
use WP_User;
use WC_Product;
use WC_Product_Variation;
use WC_Abstract_Order;
use Automattic\WooCommerce\Utilities\OrderUtil;
use function get_user_meta;
use function update_user_meta;

Expand All @@ -33,21 +37,25 @@ function ( WC_Meta_Data $meta ) {
$uuids
);

// If there is no uuid, add one, i.e., new product
if ( empty( $uuid_values ) ) {
$object->update_meta_data( '_woocommerce_pos_uuid', $this->create_uuid() );
}

// Check if there's more than one uuid, if so, delete and regenerate
// Check if there's more than one uuid, if so, keep the first and delete the rest.
if ( \count( $uuid_values ) > 1 ) {
foreach ( $uuids as $uuid_meta ) {
$object->delete_meta_data( $uuid_meta->key );
// Keep the first UUID and remove the rest.
for ( $i = 1; $i < count( $uuid_values ); $i++ ) {
$object->delete_meta_data_by_mid( $uuids[ $i ]->id );
}
$uuid_values = array( reset( $uuid_values ) ); // Keep only the first UUID in the array.
}

// Check conditions for updating the UUID.
$should_update_uuid = empty( $uuid_values )
|| ( isset( $uuid_values[0] ) && ! Uuid::isValid( $uuid_values[0] ) )
|| ( isset( $uuid_values[0] ) && $this->uuid_postmeta_exists( $uuid_values[0], $object ) );

if ( $should_update_uuid ) {
$object->update_meta_data( '_woocommerce_pos_uuid', $this->create_uuid() );
}
}


/**
* @param WP_User $user
*
Expand All @@ -56,7 +64,21 @@ function ( WC_Meta_Data $meta ) {
private function maybe_add_user_uuid( WP_User $user ): void {
$uuids = get_user_meta( $user->ID, '_woocommerce_pos_uuid', false );

if ( empty( $uuids ) || empty( $uuids[0] ) ) {
// Check if there's more than one uuid, if so, keep the first and delete the rest.
if ( count( $uuids ) > 1 ) {
// Keep the first UUID and remove the rest.
for ( $i = 1; $i < count( $uuids ); $i++ ) {
delete_user_meta( $user->ID, '_woocommerce_pos_uuid', $uuids[ $i ] );
}
$uuids = array( $uuids[0] );
}

// Check conditions for updating the UUID.
$should_update_uuid = empty( $uuids )
|| ( isset( $uuids[0] ) && ! Uuid::isValid( $uuids[0] ) )
|| ( isset( $uuids[0] ) && $this->uuid_usermeta_exists( $uuids[0], $user->ID ) );

if ( $should_update_uuid ) {
update_user_meta( $user->ID, '_woocommerce_pos_uuid', $this->create_uuid() );
}
}
Expand Down Expand Up @@ -84,14 +106,30 @@ private function maybe_add_order_item_uuid( WC_Order_Item $item ): void {
*
* @return string
*/
private function get_term_uuid( object $item ): string {
$uuid = get_term_meta( $item->term_id, '_woocommerce_pos_uuid', true );
if ( ! $uuid ) {
$uuid = Uuid::uuid4()->toString();
add_term_meta( $item->term_id, '_woocommerce_pos_uuid', $uuid, true );
private function get_term_uuid( $term ): string {
$uuids = get_term_meta( $term->term_id, '_woocommerce_pos_uuid', false );

// Check if there's more than one uuid, if so, keep the first and delete the rest.
if ( count( $uuids ) > 1 ) {
// Keep the first UUID and remove the rest.
for ( $i = 1; $i < count( $uuids ); $i++ ) {
delete_term_meta( $term->term_id, '_woocommerce_pos_uuid', $uuids[ $i ] );
}
$uuids = array( $uuids[0] );
}

// Check conditions for updating the UUID.
$should_update_uuid = empty( $uuids )
|| ( isset( $uuids[0] ) && ! Uuid::isValid( $uuids[0] ) )
|| ( isset( $uuids[0] ) && $this->uuid_termmeta_exists( $uuids[0], $term->term_id ) );

if ( $should_update_uuid ) {
$uuid = $this->create_uuid();
add_term_meta( $term->term_id, '_woocommerce_pos_uuid', $uuid, true );
return $uuid;
}

return $uuid;
return $uuids[0];
}

/**
Expand All @@ -108,4 +146,82 @@ private function create_uuid(): string {
return 'fallback-uuid-' . time();
}
}

/**
* Check if the given UUID is unique.
*
* @param string $uuid The UUID to check.
* @param WC_Data $object The WooCommerce data object.
* @return bool True if unique, false otherwise.
*/
private function uuid_postmeta_exists( string $uuid, WC_Data $object ): bool {
global $wpdb;

if ( $object instanceof WC_Abstract_Order && OrderUtil::custom_orders_table_usage_is_enabled() ) {
// Check the orders meta table.
$result = $wpdb->get_var(
$wpdb->prepare(
"SELECT 1 FROM {$wpdb->prefix}wc_ordermeta WHERE meta_key = '_woocommerce_pos_uuid' AND meta_value = %s AND order_id != %d LIMIT 1",
$uuid,
$object->get_id()
)
);
} else {
// Check the postmeta table.
$result = $wpdb->get_var(
$wpdb->prepare(
"SELECT 1 FROM {$wpdb->postmeta} WHERE meta_key = '_woocommerce_pos_uuid' AND meta_value = %s AND post_id != %d LIMIT 1",
$uuid,
$object->get_id()
)
);
}

// Convert the result to a boolean.
return (bool) $result;
}

/**
* Check if the given UUID already exists for any user.
*
* @param string $uuid The UUID to check.
* @param int $exclude_id The user ID to exclude from the check.
* @return bool True if unique, false otherwise.
*/
private function uuid_usermeta_exists( string $uuid, int $exclude_id ): bool {
global $wpdb;

$result = $wpdb->get_var(
$wpdb->prepare(
"SELECT 1 FROM {$wpdb->usermeta} WHERE meta_key = '_woocommerce_pos_uuid' AND meta_value = %s AND user_id != %d LIMIT 1",
$uuid,
$exclude_id
)
);

// Convert the result to a boolean.
return (bool) $result;
}

/**
* Check if the given UUID already exists for any term.
*
* @param string $uuid The UUID to check.
* @param int $exclude_term_id The term ID to exclude from the check.
* @return bool True if unique, false otherwise.
*/
private function uuid_termmeta_exists( string $uuid, int $exclude_term_id ): bool {
global $wpdb;

$result = $wpdb->get_var(
$wpdb->prepare(
"SELECT 1 FROM {$wpdb->termmeta} WHERE meta_key = '_woocommerce_pos_uuid' AND meta_value = %s AND term_id != %d LIMIT 1",
$uuid,
$exclude_term_id
)
);

// Convert the result to a boolean.
return (bool) $result;
}
}
57 changes: 44 additions & 13 deletions includes/Admin/Products/List_Products.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ class List_Products {
private $options;



public function __construct() {
$this->barcode_field = woocommerce_pos_get_settings( 'general', 'barcode_field' );

Expand All @@ -43,10 +43,15 @@ public function __construct() {
add_action( 'woocommerce_product_options_sku', array( $this, 'woocommerce_product_options_sku' ) );
add_action( 'woocommerce_process_product_meta', array( $this, 'woocommerce_process_product_meta' ) );
// variations
add_action('woocommerce_product_after_variable_attributes', array(
$this,
'after_variable_attributes_barcode_field',
), 10, 3);
add_action(
'woocommerce_product_after_variable_attributes',
array(
$this,
'after_variable_attributes_barcode_field',
),
10,
3
);
add_action( 'woocommerce_save_product_variation', array( $this, 'save_product_variation_barcode_field' ) );
}

Expand All @@ -57,17 +62,30 @@ public function __construct() {
add_action( 'woocommerce_product_bulk_edit_save', array( $this, 'bulk_edit_save' ) );
add_action( 'quick_edit_custom_box', array( $this, 'quick_edit' ), 10, 2 );
add_action( 'manage_product_posts_custom_column', array( $this, 'custom_product_column' ), 10, 2 );
add_action('woocommerce_product_after_variable_attributes', array(
$this,
'after_variable_attributes_pos_only_products',
), 10, 3);
add_action('woocommerce_save_product_variation', array(
$this,
'save_product_variation_pos_only_products',
));
add_action(
'woocommerce_product_after_variable_attributes',
array(
$this,
'after_variable_attributes_pos_only_products',
),
10,
3
);
add_action(
'woocommerce_save_product_variation',
array(
$this,
'save_product_variation_pos_only_products',
)
);
}

add_filter( 'woocommerce_duplicate_product_exclude_meta', array( $this, 'exclude_uuid_meta_on_product_duplicate' ) );
}

/**
*
*/
public function woocommerce_product_options_sku(): void {
woocommerce_wp_text_input(
array(
Expand Down Expand Up @@ -228,4 +246,17 @@ public function custom_product_column( $column, $post_id ): void {
echo '<div class="hidden" id="woocommerce_pos_inline_' . $post_id . '" data-visibility="' . $selected . '"></div>';
}
}

/**
* Filter to allow us to exclude meta keys from product duplication..
*
* @param array $exclude_meta The keys to exclude from the duplicate.
* @param array $existing_meta_keys The meta keys that the product already has.
*
* @return array
*/
public function exclude_uuid_meta_on_product_duplicate( array $meta_keys ) {
$meta_keys[] = '_woocommerce_pos_uuid';
return $meta_keys;
}
}
2 changes: 1 addition & 1 deletion includes/Templates/Frontend.php
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ public function head(): void {
public function footer(): void {
$development = isset( $_ENV['DEVELOPMENT'] ) && $_ENV['DEVELOPMENT'];
$user = wp_get_current_user();
$github_url = 'https://cdn.jsdelivr.net/gh/wcpos/web-bundle@latest/';
$github_url = 'https://cdn.jsdelivr.net/gh/wcpos/web-bundle@1.4/';
$auth_service = Auth::instance();
$stores = array_map(
function ( $store ) {
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@wcpos/woocommerce-pos",
"version": "1.4.7",
"version": "1.4.8",
"description": "A simple front-end for taking WooCommerce orders at the Point of Sale.",
"main": "index.js",
"workspaces": {
Expand Down
5 changes: 4 additions & 1 deletion readme.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ Contributors: kilbot
Tags: cart, e-commerce, ecommerce, inventory, point-of-sale, pos, sales, sell, shop, shopify, store, vend, woocommerce, wordpress-ecommerce
Requires at least: 5.6 & WooCommerce 5.3
Tested up to: 6.4
Stable tag: 1.4.7
Stable tag: 1.4.8
License: GPL-3.0
License URI: http://www.gnu.org/licenses/gpl-3.0.html

Expand Down Expand Up @@ -63,6 +63,9 @@ There is more information on our website at [https://wcpos.com](https://wcpos.co

== Changelog ==

= 1.4.8 - 2024/01/21 =
* Fix: duplicating Products in WC Admin also duplicated POS UUID, which casued problems

= 1.4.7 - 2024/01/18 =
* Bump: web application to version 1.4.1
* Fix: scroll-to-top issue when scrolling data tables
Expand Down
5 changes: 2 additions & 3 deletions templates/pos.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
<title><?php esc_attr_e( 'Point of Sale', 'woocommerce-pos' ); ?> - <?php esc_html( bloginfo( 'name' ) ); ?></title>
<meta charset="utf-8"/>

<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1.00001, viewport-fit=cover">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
<meta name="theme-color" content="#000000">
<meta name="apple-mobile-web-app-capable" content="yes"/>

Expand Down Expand Up @@ -54,7 +54,7 @@
* Matches Expo build

* Extend the react-native-web reset:
* https://github.com/necolas/react-native-web/blob/master/packages/react-native-web/src/exports/StyleSheet/initialRules.js
* https://necolas.github.io/react-native-web/docs/setup/#root-element
*/
html,
body,
Expand Down Expand Up @@ -115,4 +115,3 @@
<?php do_action( 'woocommerce_pos_footer' ); ?>

</html>

6 changes: 0 additions & 6 deletions templates/receipt.php
Original file line number Diff line number Diff line change
Expand Up @@ -320,8 +320,6 @@

</section><!-- /.col2-set -->



<?php do_action( 'woocommerce_order_details_after_customer_details', $order ); ?>

</section>
Expand All @@ -334,10 +332,6 @@
</div>
<?php } ?>

<div class="footer">
<p><?php esc_html_e( 'Thank you for your purchase!', 'woocommerce' ); ?></p>
<p><?php bloginfo( 'name' ); ?> - <?php bloginfo( 'description' ); ?></p>
</div>
</div>

</body>
Expand Down
35 changes: 35 additions & 0 deletions tests/includes/API/Test_Customers_Controller.php
Original file line number Diff line number Diff line change
Expand Up @@ -524,4 +524,39 @@ public function test_customer_search_with_excludes(): void {
$this->assertEquals( 1, \count( $data ) );
$this->assertEquals( $customer1->get_id(), $data[0]['id'] );
}

/**
*
*/
public function test_customer_uuid_is_unique(): void {
$uuid = UUID::uuid4()->toString();
$customer1 = CustomerHelper::create_customer();
$customer1->update_meta_data( '_woocommerce_pos_uuid', $uuid );
$customer1->save_meta_data();
$customer2 = CustomerHelper::create_customer();
$customer2->update_meta_data( '_woocommerce_pos_uuid', $uuid );
$customer2->save_meta_data();

$request = $this->wp_rest_get_request( '/wcpos/v1/customers' );

$response = $this->server->dispatch( $request );
$data = $response->get_data();

$this->assertEquals( 200, $response->get_status() );
$this->assertEquals( 2, \count( $data ) );

// pluck uuids from meta_data
$uuids = array();
foreach ( $data as $customer ) {
foreach ( $customer['meta_data'] as $meta ) {
if ( '_woocommerce_pos_uuid' === $meta['key'] ) {
$uuids[] = $meta['value'];
}
}
}

$this->assertEquals( 2, \count( $uuids ) );
$this->assertContains( $uuid, $uuids );
$this->assertEquals( 2, \count( array_unique( $uuids ) ) );
}
}
Loading

0 comments on commit 0ccddcc

Please sign in to comment.