From 3b8eb75c93d34e2e4863d6829e20773d6a46a0a8 Mon Sep 17 00:00:00 2001 From: Claudiu Lodromanean Date: Wed, 8 Mar 2017 11:51:38 -0800 Subject: [PATCH 01/59] Better handling of nested arrays in apply_changes --- includes/abstracts/abstract-wc-data.php | 9 +++- tests/framework/class-wc-mock-wc-data.php | 16 +++++++ tests/unit-tests/crud/data.php | 54 +++++++++++++++++++++++ tests/unit-tests/order/crud.php | 28 +++++++++++- 4 files changed, 104 insertions(+), 3 deletions(-) diff --git a/includes/abstracts/abstract-wc-data.php b/includes/abstracts/abstract-wc-data.php index 52b1139d05c..8de8d51c54e 100644 --- a/includes/abstracts/abstract-wc-data.php +++ b/includes/abstracts/abstract-wc-data.php @@ -550,7 +550,14 @@ abstract class WC_Data { * @since 2.7.0 */ public function apply_changes() { - $this->data = array_merge( $this->data, $this->changes ); + foreach ( $this->changes as $key => $change ) { + if ( is_array( $change ) ) { + $this->data[ $key ] = array_key_exists( $key, $this->data ) ? array_merge( $this->data[ $key ], $change ) : $change; + } else { + $this->data[ $key ] = $change; + } + } + $this->changes = array(); } diff --git a/tests/framework/class-wc-mock-wc-data.php b/tests/framework/class-wc-mock-wc-data.php index 0e55c615cc4..7dd26e05bb6 100644 --- a/tests/framework/class-wc-mock-wc-data.php +++ b/tests/framework/class-wc-mock-wc-data.php @@ -193,6 +193,22 @@ class WC_Mock_WC_Data extends WC_Data { ); } + /** + * Set the data to any arbitrary data. + * @param array $data + */ + public function set_data( $data ) { + $this->data = $data; + } + + /** + * Set the changes to any arbitrary changes. + * @param array $changes + */ + public function set_changes( $changes ) { + $this->changes = $changes; + } + /** * Simple save. */ diff --git a/tests/unit-tests/crud/data.php b/tests/unit-tests/crud/data.php index 4e4e52cf4fd..164b60f7417 100644 --- a/tests/unit-tests/crud/data.php +++ b/tests/unit-tests/crud/data.php @@ -273,4 +273,58 @@ class WC_Tests_CRUD_Data extends WC_Unit_Test_Case { $object->update_meta_data( 'test_field_0', 'another field 2' ); $this->assertEquals( 'val1', $object->get_meta( 'test_field_2' ) ); } + + function test_apply_changes() { + $data = array( + 'prop1' => 'value1', + 'prop2' => 'value2', + ); + + $changes = array( + 'prop1' => 'new_value1', + 'prop3' => 'value3' + ); + + $object = new WC_Mock_WC_Data; + $object->set_data( $data ); + $object->set_changes( $changes ); + $object->apply_changes(); + + $new_data = $object->get_data(); + $new_changes = $object->get_changes(); + + $this->assertEquals( 'new_value1', $new_data['prop1'] ); + $this->assertEquals( 'value2', $new_data['prop2'] ); + $this->assertEquals( 'value3', $new_data['prop3'] ); + $this->assertEmpty( $new_changes ); + } + + function test_apply_changes_nested() { + $data = array( + 'prop1' => 'value1', + 'prop2' => array( + 'subprop1' => 1, + 'subprop2' => 2, + ), + ); + + $changes = array( + 'prop2' => array( + 'subprop1' => 1000, + 'subprop3' => 3, + ), + ); + + $object = new WC_Mock_WC_Data; + $object->set_data( $data ); + $object->set_changes( $changes ); + $object->apply_changes(); + + $new_data = $object->get_data(); + + $this->assertEquals( 'value1', $new_data['prop1'] ); + $this->assertEquals( 1000, $new_data['prop2']['subprop1'] ); + $this->assertEquals( 2, $new_data['prop2']['subprop2'] ); + $this->assertEquals( 3, $new_data['prop2']['subprop3'] ); + } } diff --git a/tests/unit-tests/order/crud.php b/tests/unit-tests/order/crud.php index d82376aed71..2a46889f373 100644 --- a/tests/unit-tests/order/crud.php +++ b/tests/unit-tests/order/crud.php @@ -814,7 +814,7 @@ class WC_Tests_CRUD_Orders extends WC_Unit_Test_Case { */ function test_get_billing_state() { $object = new WC_Order(); - $set_to = 'Boulder'; + $set_to = 'Oregon'; $object->set_billing_state( $set_to ); $this->assertEquals( $set_to, $object->get_billing_state() ); } @@ -864,6 +864,18 @@ class WC_Tests_CRUD_Orders extends WC_Unit_Test_Case { $this->assertEquals( $set_to, $object->get_billing_phone() ); } + function test_set_billing_after_save() { + $object = new WC_Order(); + $phone = '123456678'; + $object->set_billing_phone( $phone ); + $object->save(); + $state = 'Oregon'; + $object->set_billing_state( $state ); + + $this->assertEquals( $phone, $object->get_billing_phone() ); + $this->assertEquals( $state, $object->get_billing_state() ); + } + /** * Test: get_shipping_first_name */ @@ -929,7 +941,7 @@ class WC_Tests_CRUD_Orders extends WC_Unit_Test_Case { */ function test_get_shipping_state() { $object = new WC_Order(); - $set_to = 'Boulder'; + $set_to = 'Oregon'; $object->set_shipping_state( $set_to ); $this->assertEquals( $set_to, $object->get_shipping_state() ); } @@ -954,6 +966,18 @@ class WC_Tests_CRUD_Orders extends WC_Unit_Test_Case { $this->assertEquals( $set_to, $object->get_shipping_country() ); } + function test_set_shipping_after_save() { + $object = new WC_Order(); + $country = 'US'; + $object->set_shipping_country( $country ); + $object->save(); + $state = 'Oregon'; + $object->set_shipping_state( $state ); + + $this->assertEquals( $country, $object->get_shipping_country() ); + $this->assertEquals( $state, $object->get_shipping_state() ); + } + /** * Test: get_payment_method */ From b645fc55130d47c6aea82ce0eedc9d5680ee63f5 Mon Sep 17 00:00:00 2001 From: Claudiu Lodromanean Date: Wed, 8 Mar 2017 12:02:14 -0800 Subject: [PATCH 02/59] Docblocks --- tests/unit-tests/crud/data.php | 6 ++++++ tests/unit-tests/order/crud.php | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/tests/unit-tests/crud/data.php b/tests/unit-tests/crud/data.php index 164b60f7417..ff978270980 100644 --- a/tests/unit-tests/crud/data.php +++ b/tests/unit-tests/crud/data.php @@ -274,6 +274,9 @@ class WC_Tests_CRUD_Data extends WC_Unit_Test_Case { $this->assertEquals( 'val1', $object->get_meta( 'test_field_2' ) ); } + /** + * Test applying changes + */ function test_apply_changes() { $data = array( 'prop1' => 'value1', @@ -299,6 +302,9 @@ class WC_Tests_CRUD_Data extends WC_Unit_Test_Case { $this->assertEmpty( $new_changes ); } + /** + * Test applying changes with a nested array + */ function test_apply_changes_nested() { $data = array( 'prop1' => 'value1', diff --git a/tests/unit-tests/order/crud.php b/tests/unit-tests/order/crud.php index 2a46889f373..0d413b23a1c 100644 --- a/tests/unit-tests/order/crud.php +++ b/tests/unit-tests/order/crud.php @@ -864,6 +864,9 @@ class WC_Tests_CRUD_Orders extends WC_Unit_Test_Case { $this->assertEquals( $set_to, $object->get_billing_phone() ); } + /** + * Test: Setting/getting billing settings after an order is saved + */ function test_set_billing_after_save() { $object = new WC_Order(); $phone = '123456678'; @@ -966,6 +969,9 @@ class WC_Tests_CRUD_Orders extends WC_Unit_Test_Case { $this->assertEquals( $set_to, $object->get_shipping_country() ); } + /** + * Test: Setting/getting shipping settings after an order is saved + */ function test_set_shipping_after_save() { $object = new WC_Order(); $country = 'US'; From e36ee46deaa62e69cd11b5eb691b2a7b179f7566 Mon Sep 17 00:00:00 2001 From: Claudiu Lodromanean Date: Wed, 8 Mar 2017 15:27:37 -0800 Subject: [PATCH 03/59] Make increase_usage_count work correctly on concurrent checkoutt --- includes/class-wc-coupon.php | 8 ++-- .../class-wc-coupon-data-store-cpt.php | 46 ++++++++++++++++++- includes/wc-order-functions.php | 4 +- tests/unit-tests/coupon/data-store.php | 10 ++-- 4 files changed, 55 insertions(+), 13 deletions(-) diff --git a/includes/class-wc-coupon.php b/includes/class-wc-coupon.php index d4ecd7a9ca7..591d8671006 100644 --- a/includes/class-wc-coupon.php +++ b/includes/class-wc-coupon.php @@ -709,8 +709,8 @@ class WC_Coupon extends WC_Legacy_Coupon { */ public function increase_usage_count( $used_by = '' ) { if ( $this->get_id() && $this->data_store ) { - $this->set_prop( 'usage_count', ( $this->get_usage_count( 'edit' ) + 1 ) ); - $this->data_store->increase_usage_count( $this, $used_by ); + $new_count = $this->data_store->increase_usage_count( $this, $used_by ); + $this->set_prop( 'usage_count', $new_count ); } } @@ -721,8 +721,8 @@ class WC_Coupon extends WC_Legacy_Coupon { */ public function decrease_usage_count( $used_by = '' ) { if ( $this->get_id() && $this->get_usage_count() > 0 && $this->data_store ) { - $this->set_prop( 'usage_count', ( $this->get_usage_count( 'edit' ) - 1 ) ); - $this->data_store->decrease_usage_count( $this, $used_by ); + $new_count = $this->data_store->decrease_usage_count( $this, $used_by ); + $this->set_prop( 'usage_count', $new_count ); } } diff --git a/includes/data-stores/class-wc-coupon-data-store-cpt.php b/includes/data-stores/class-wc-coupon-data-store-cpt.php index a40ea529141..08354e1d3a9 100644 --- a/includes/data-stores/class-wc-coupon-data-store-cpt.php +++ b/includes/data-stores/class-wc-coupon-data-store-cpt.php @@ -228,13 +228,15 @@ class WC_Coupon_Data_Store_CPT extends WC_Data_Store_WP implements WC_Coupon_Dat * @since 2.7.0 * @param WC_Coupon * @param string $used_by Either user ID or billing email + * @return int New usage count */ public function increase_usage_count( &$coupon, $used_by = '' ) { - update_post_meta( $coupon->get_id(), 'usage_count', $coupon->get_usage_count( 'edit' ) ); + $new_count = $this->increase_usage_count_meta( $coupon ); if ( $used_by ) { add_post_meta( $coupon->get_id(), '_used_by', strtolower( $used_by ) ); $coupon->set_used_by( (array) get_post_meta( $coupon->get_id(), '_used_by' ) ); } + return $new_count; } /** @@ -243,10 +245,11 @@ class WC_Coupon_Data_Store_CPT extends WC_Data_Store_WP implements WC_Coupon_Dat * @since 2.7.0 * @param WC_Coupon * @param string $used_by Either user ID or billing email + * @return int New usage count */ public function decrease_usage_count( &$coupon, $used_by = '' ) { global $wpdb; - update_post_meta( $coupon->get_id(), 'usage_count', $coupon->get_usage_count() ); + $new_count = $this->decrease_usage_count_meta( $coupon ); if ( $used_by ) { /** * We're doing this the long way because `delete_post_meta( $id, $key, $value )` deletes. @@ -258,6 +261,45 @@ class WC_Coupon_Data_Store_CPT extends WC_Data_Store_WP implements WC_Coupon_Dat $coupon->set_used_by( (array) get_post_meta( $coupon->get_id(), '_used_by' ) ); } } + return $new_count; + } + + /** + * Increase coupon usage count post meta. + * + * @since 2.7.0 + * @param WC_Coupon + * @return int New usage count + */ + private function increase_usage_count_meta( &$coupon ) { + global $wpdb; + $id = $coupon->get_id(); + $new_count = $coupon->get_usage_count( 'edit' ) + 1; + $updated = $wpdb->query( $wpdb->prepare( "UPDATE $wpdb->postmeta SET meta_value = meta_value + 1 WHERE meta_key = 'usage_count' AND post_id = %d;", $id ) ); + if ( ! $updated ) { + add_post_meta( $id, 'usage_count', $new_count, true ); + } + + return $new_count; + } + + /** + * Decrease coupon usage count post meta. + * + * @since 2.7.0 + * @param WC_Coupon + * @return int New usage count + */ + private function decrease_usage_count_meta( &$coupon ) { + global $wpdb; + $id = $coupon->get_id(); + $new_count = $coupon->get_usage_count( 'edit' ) - 1; + $updated = $wpdb->query( $wpdb->prepare( "UPDATE $wpdb->postmeta SET meta_value = meta_value - 1 WHERE meta_key = 'usage_count' AND post_id = %d;", $id ) ); + if ( ! $updated ) { + add_post_meta( $id, 'usage_count', $new_count, true ); + } + + return $new_count; } /** diff --git a/includes/wc-order-functions.php b/includes/wc-order-functions.php index c4108471ded..8b834bf5dc3 100644 --- a/includes/wc-order-functions.php +++ b/includes/wc-order-functions.php @@ -816,10 +816,10 @@ function wc_update_coupon_usage_counts( $order_id ) { switch ( $action ) { case 'reduce' : - $coupon->dcr_usage_count( $used_by ); + $coupon->decrease_usage_count( $used_by ); break; case 'increase' : - $coupon->inc_usage_count( $used_by ); + $coupon->increase_usage_count( $used_by ); break; } } diff --git a/tests/unit-tests/coupon/data-store.php b/tests/unit-tests/coupon/data-store.php index 3184114ffaf..05513015368 100644 --- a/tests/unit-tests/coupon/data-store.php +++ b/tests/unit-tests/coupon/data-store.php @@ -110,19 +110,19 @@ class WC_Tests_Coupon_Data_Store extends WC_Unit_Test_Case { $this->assertEquals( 0, $coupon->get_usage_count() ); $this->assertEmpty( $coupon->get_used_by() ); - $coupon->inc_usage_count( 'woo@woo.local' ); + $coupon->increase_usage_count( 'woo@woo.local' ); $this->assertEquals( 1, $coupon->get_usage_count() ); $this->assertEquals( array( 'woo@woo.local' ), $coupon->get_used_by() ); - $coupon->inc_usage_count( $user_id ); - $coupon->inc_usage_count( $user_id ); + $coupon->increase_usage_count( $user_id ); + $coupon->increase_usage_count( $user_id ); $data_store = WC_Data_Store::load( 'coupon' ); $this->assertEquals( 2, $data_store->get_usage_by_user_id( $coupon, $user_id ) ); - $coupon->dcr_usage_count( 'woo@woo.local' ); - $coupon->dcr_usage_count( $user_id ); + $coupon->decrease_usage_count( 'woo@woo.local' ); + $coupon->decrease_usage_count( $user_id ); $this->assertEquals( 1, $coupon->get_usage_count() ); $this->assertEquals( array( 1 ), $coupon->get_used_by() ); } From 9427eb048d3e6055ef1a58fb093a9908015cbd00 Mon Sep 17 00:00:00 2001 From: Mike Jolley Date: Thu, 9 Mar 2017 13:45:33 +0000 Subject: [PATCH 04/59] Set default status in getter, not during contruct. Prevents loops like in Prospress/woocommerce-subscriptions#1902 --- includes/abstracts/abstract-wc-order.php | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/includes/abstracts/abstract-wc-order.php b/includes/abstracts/abstract-wc-order.php index 72a655b22b7..2c5c06498df 100644 --- a/includes/abstracts/abstract-wc-order.php +++ b/includes/abstracts/abstract-wc-order.php @@ -103,11 +103,6 @@ abstract class WC_Abstract_Order extends WC_Abstract_Legacy_Order { $this->set_object_read( true ); } - // Set default status if none were read. - if ( ! $this->get_status() ) { - $this->set_status( apply_filters( 'woocommerce_default_order_status', 'pending' ) ); - } - $this->data_store = WC_Data_Store::load( $this->data_store_name ); if ( $this->get_id() > 0 ) { @@ -283,7 +278,13 @@ abstract class WC_Abstract_Order extends WC_Abstract_Legacy_Order { * @return string */ public function get_status( $context = 'view' ) { - return $this->get_prop( 'status', $context ); + $status = $this->get_prop( 'status', $context ); + + if ( empty( $status ) && 'view' === $context ) { + // In view context, return the default status if no status has been set. + $status = apply_filters( 'woocommerce_default_order_status', 'pending' ); + } + return $status; } /** From 28a8d05285a57b1669efa49a34924e637b536f33 Mon Sep 17 00:00:00 2001 From: Mike Jolley Date: Thu, 9 Mar 2017 14:40:19 +0000 Subject: [PATCH 05/59] Handle date paid in getter and during save to avoid filters during construct --- includes/class-wc-order.php | 15 ++++++++++----- .../abstract-wc-order-data-store-cpt.php | 18 ++++-------------- .../class-wc-order-data-store-cpt.php | 7 +++++++ 3 files changed, 21 insertions(+), 19 deletions(-) diff --git a/includes/class-wc-order.php b/includes/class-wc-order.php index 9db3e21ff98..d0eccdb14fc 100644 --- a/includes/class-wc-order.php +++ b/includes/class-wc-order.php @@ -253,11 +253,10 @@ class WC_Order extends WC_Abstract_Order { * `payment_complete` method. * * @since 2.7.0 - * @param $date_paid What to set date paid to. Defaults to current time. */ - public function maybe_set_date_paid( $date_paid = '' ) { - if ( ! $this->get_date_paid( 'edit' ) && $this->has_status( array( 'processing', 'completed' ) ) ) { - $this->set_date_paid( $date_paid ? $date_paid : current_time( 'timestamp' ) ); + public function maybe_set_date_paid() { + if ( ! $this->get_date_paid( 'edit' ) && $this->has_status( apply_filters( 'woocommerce_payment_complete_order_status', $this->needs_processing() ? 'processing' : 'completed', $this->get_id() ) ) ) { + $this->set_date_paid( current_time( 'timestamp' ) ); } } @@ -729,7 +728,13 @@ class WC_Order extends WC_Abstract_Order { * @return int */ public function get_date_paid( $context = 'view' ) { - return $this->get_prop( 'date_paid', $context ); + $date_paid = $this->get_prop( 'date_paid', $context ); + + if ( 'view' === $context && ! $date_paid && version_compare( $this->get_version( 'edit' ), '2.7', '<' ) && $this->has_status( apply_filters( 'woocommerce_payment_complete_order_status', $this->needs_processing() ? 'processing' : 'completed', $this->get_id() ) ) ) { + // In view context, return a date if missing. + $date_paid = $this->get_date_created( 'edit' ) + } + return $date_paid; } /** diff --git a/includes/data-stores/abstract-wc-order-data-store-cpt.php b/includes/data-stores/abstract-wc-order-data-store-cpt.php index 6bd586b80fc..e5b54274497 100644 --- a/includes/data-stores/abstract-wc-order-data-store-cpt.php +++ b/includes/data-stores/abstract-wc-order-data-store-cpt.php @@ -100,21 +100,10 @@ abstract class Abstract_WC_Order_Data_Store_CPT extends WC_Data_Store_WP impleme /** * In older versions, discounts may have been stored differently. * Update them now so if the object is saved, the correct values are - * stored. - * @todo When/if meta is flattened, handle this in the migration script. + * stored. @todo When meta is flattened, handle this during migration. */ - if ( ! $order->get_version( 'edit' ) || version_compare( $order->get_version( 'edit' ), '2.3.7', '<' ) ) { - if ( $order->get_prices_include_tax( 'edit' ) ) { - $order->set_discount_total( (double) get_post_meta( $order->get_id(), '_cart_discount', true ) - (double) get_post_meta( $order->get_id(), '_cart_discount_tax', true ) ); - } - } - - /** - * In older versions, paid date may not have been set. - * @todo When/if meta is flattened, handle this in the migration script. - */ - if ( ! $order->get_version( 'edit' ) || version_compare( $order->get_version( 'edit' ), '2.7', '<' ) ) { - $order->maybe_set_date_paid( $order->get_date_created( 'edit' ) ); + if ( version_compare( $order->get_version( 'edit' ), '2.3.7', '<' ) && $order->get_prices_include_tax( 'edit' ) ) { + $order->set_discount_total( (double) get_post_meta( $order->get_id(), '_cart_discount', true ) - (double) get_post_meta( $order->get_id(), '_cart_discount_tax', true ) ); } } @@ -248,6 +237,7 @@ abstract class Abstract_WC_Order_Data_Store_CPT extends WC_Data_Store_WP impleme ); $props_to_update = $this->get_props_to_update( $order, $meta_key_to_props ); + foreach ( $props_to_update as $meta_key => $prop ) { $value = $order->{"get_$prop"}( 'edit' ); diff --git a/includes/data-stores/class-wc-order-data-store-cpt.php b/includes/data-stores/class-wc-order-data-store-cpt.php index 764826b6793..226faccb33e 100644 --- a/includes/data-stores/class-wc-order-data-store-cpt.php +++ b/includes/data-stores/class-wc-order-data-store-cpt.php @@ -131,7 +131,14 @@ class WC_Order_Data_Store_CPT extends Abstract_WC_Order_Data_Store_CPT implement * @param WC_Order $order */ public function update( &$order ) { + // Before updating, ensure date paid is set if missing. + if ( ! $order->get_date_paid( 'edit' ) && version_compare( $order->get_version( 'edit' ), '2.7', '<' ) && $order->has_status( apply_filters( 'woocommerce_payment_complete_order_status', $order->needs_processing() ? 'processing' : 'completed', $order->get_id() ) ) ) { + $order->set_date_paid( $order->get_date_created( 'edit' ) ); + } + + // Update the order. parent::update( $order ); + do_action( 'woocommerce_update_order', $order->get_id() ); } From ac1c2f02b785c1719d69550b611eaf557b7b5019 Mon Sep 17 00:00:00 2001 From: Mike Jolley Date: Thu, 9 Mar 2017 14:53:52 +0000 Subject: [PATCH 06/59] syntax --- includes/class-wc-order.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/includes/class-wc-order.php b/includes/class-wc-order.php index d0eccdb14fc..f17827b66be 100644 --- a/includes/class-wc-order.php +++ b/includes/class-wc-order.php @@ -732,7 +732,7 @@ class WC_Order extends WC_Abstract_Order { if ( 'view' === $context && ! $date_paid && version_compare( $this->get_version( 'edit' ), '2.7', '<' ) && $this->has_status( apply_filters( 'woocommerce_payment_complete_order_status', $this->needs_processing() ? 'processing' : 'completed', $this->get_id() ) ) ) { // In view context, return a date if missing. - $date_paid = $this->get_date_created( 'edit' ) + $date_paid = $this->get_date_created( 'edit' ); } return $date_paid; } From 3aea9a0b2f12e54dca5ac930635cb8ad78158116 Mon Sep 17 00:00:00 2001 From: Claudiu Lodromanean Date: Thu, 9 Mar 2017 08:51:47 -0800 Subject: [PATCH 07/59] DRY out the meta bumping functions --- .../class-wc-coupon-data-store-cpt.php | 32 +++++-------------- 1 file changed, 8 insertions(+), 24 deletions(-) diff --git a/includes/data-stores/class-wc-coupon-data-store-cpt.php b/includes/data-stores/class-wc-coupon-data-store-cpt.php index 08354e1d3a9..b45350f8499 100644 --- a/includes/data-stores/class-wc-coupon-data-store-cpt.php +++ b/includes/data-stores/class-wc-coupon-data-store-cpt.php @@ -231,7 +231,7 @@ class WC_Coupon_Data_Store_CPT extends WC_Data_Store_WP implements WC_Coupon_Dat * @return int New usage count */ public function increase_usage_count( &$coupon, $used_by = '' ) { - $new_count = $this->increase_usage_count_meta( $coupon ); + $new_count = $this->vary_usage_count_meta( $coupon, 'increase' ); if ( $used_by ) { add_post_meta( $coupon->get_id(), '_used_by', strtolower( $used_by ) ); $coupon->set_used_by( (array) get_post_meta( $coupon->get_id(), '_used_by' ) ); @@ -249,7 +249,7 @@ class WC_Coupon_Data_Store_CPT extends WC_Data_Store_WP implements WC_Coupon_Dat */ public function decrease_usage_count( &$coupon, $used_by = '' ) { global $wpdb; - $new_count = $this->decrease_usage_count_meta( $coupon ); + $new_count = $this->vary_usage_count_meta( $coupon, 'decrease' ); if ( $used_by ) { /** * We're doing this the long way because `delete_post_meta( $id, $key, $value )` deletes. @@ -265,36 +265,20 @@ class WC_Coupon_Data_Store_CPT extends WC_Data_Store_WP implements WC_Coupon_Dat } /** - * Increase coupon usage count post meta. + * Increase or decrease the usage count for a coupon by 1. * * @since 2.7.0 * @param WC_Coupon + * @param string $operation 'increase' or 'decrease' * @return int New usage count */ - private function increase_usage_count_meta( &$coupon ) { + private function vary_usage_count_meta( &$coupon, $operation = 'increase' ) { global $wpdb; $id = $coupon->get_id(); - $new_count = $coupon->get_usage_count( 'edit' ) + 1; - $updated = $wpdb->query( $wpdb->prepare( "UPDATE $wpdb->postmeta SET meta_value = meta_value + 1 WHERE meta_key = 'usage_count' AND post_id = %d;", $id ) ); - if ( ! $updated ) { - add_post_meta( $id, 'usage_count', $new_count, true ); - } + $operator = ( 'increase' === $operation ) ? '+' : '-'; + $new_count = ( 'increase' === $operation ) ? ( $coupon->get_usage_count( 'edit' ) + 1 ) : ( $coupon->get_usage_count( 'edit' ) - 1 ); - return $new_count; - } - - /** - * Decrease coupon usage count post meta. - * - * @since 2.7.0 - * @param WC_Coupon - * @return int New usage count - */ - private function decrease_usage_count_meta( &$coupon ) { - global $wpdb; - $id = $coupon->get_id(); - $new_count = $coupon->get_usage_count( 'edit' ) - 1; - $updated = $wpdb->query( $wpdb->prepare( "UPDATE $wpdb->postmeta SET meta_value = meta_value - 1 WHERE meta_key = 'usage_count' AND post_id = %d;", $id ) ); + $updated = $wpdb->query( $wpdb->prepare( "UPDATE $wpdb->postmeta SET meta_value = meta_value {$operator} 1 WHERE meta_key = 'usage_count' AND post_id = %d;", $id ) ); if ( ! $updated ) { add_post_meta( $id, 'usage_count', $new_count, true ); } From 8a18702c278ae0e3e72491d5d92c7bbd5a90eac7 Mon Sep 17 00:00:00 2001 From: Claudiu Lodromanean Date: Thu, 9 Mar 2017 09:06:05 -0800 Subject: [PATCH 08/59] Just use array_replace_recursive in apply_changes --- includes/abstracts/abstract-wc-data.php | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/includes/abstracts/abstract-wc-data.php b/includes/abstracts/abstract-wc-data.php index 8de8d51c54e..63568afe08d 100644 --- a/includes/abstracts/abstract-wc-data.php +++ b/includes/abstracts/abstract-wc-data.php @@ -550,14 +550,7 @@ abstract class WC_Data { * @since 2.7.0 */ public function apply_changes() { - foreach ( $this->changes as $key => $change ) { - if ( is_array( $change ) ) { - $this->data[ $key ] = array_key_exists( $key, $this->data ) ? array_merge( $this->data[ $key ], $change ) : $change; - } else { - $this->data[ $key ] = $change; - } - } - + $this->data = array_replace_recursive( $this->data, $this->changes ); $this->changes = array(); } From cd92351dd7bca272deec9af345eadf4d8f6904dc Mon Sep 17 00:00:00 2001 From: Justin Shreve Date: Thu, 9 Mar 2017 10:34:45 -0800 Subject: [PATCH 09/59] Fix direct property access for coupon amount. In 2.6, you could access the amount via $coupon->coupon_amount. Or legacy code incorrectly handles $coupon->amount instead. https://github.com/woocommerce/woocommerce/blob/77785833409869b33428fd0ce2c131ee3018df45/includes/class-wc-coupon.php#L102 This PR handles both since the RCs and betas allowed `->amount` and I don't want to break anything that may be accessing it that way.. To Test: * `phpunit --filter=test_coupon_backwards_compat_props_use_correct_getters` --- includes/legacy/class-wc-legacy-coupon.php | 2 ++ tests/unit-tests/coupon/data.php | 2 ++ 2 files changed, 4 insertions(+) diff --git a/includes/legacy/class-wc-legacy-coupon.php b/includes/legacy/class-wc-legacy-coupon.php index cd41f609116..9d982de331a 100644 --- a/includes/legacy/class-wc-legacy-coupon.php +++ b/includes/legacy/class-wc-legacy-coupon.php @@ -30,6 +30,7 @@ abstract class WC_Legacy_Coupon extends WC_Data { 'type', 'discount_type', 'amount', + 'coupon_amount', 'code', 'individual_use', 'product_ids', @@ -82,6 +83,7 @@ abstract class WC_Legacy_Coupon extends WC_Data { $value = $this->get_discount_type(); break; case 'amount' : + case 'coupon_amount' : $value = $this->get_amount(); break; case 'code' : diff --git a/tests/unit-tests/coupon/data.php b/tests/unit-tests/coupon/data.php index 5af592b8841..52819e32dfa 100644 --- a/tests/unit-tests/coupon/data.php +++ b/tests/unit-tests/coupon/data.php @@ -39,6 +39,7 @@ class WC_Tests_Coupon_Data extends WC_Unit_Test_Case { 'type', 'discount_type', 'amount', + 'coupon_amount', 'code', 'individual_use', 'product_ids', @@ -68,6 +69,7 @@ class WC_Tests_Coupon_Data extends WC_Unit_Test_Case { $this->assertEquals( $coupon->get_discount_type(), $coupon->type ); $this->assertEquals( $coupon->get_discount_type(), $coupon->discount_type ); $this->assertEquals( $coupon->get_amount(), $coupon->amount ); + $this->assertEquals( $coupon->get_amount(), $coupon->coupon_amount ); $this->assertEquals( $coupon->get_code(), $coupon->code ); $this->assertEquals( $coupon->get_individual_use(), ( 'yes' === $coupon->individual_use ? true : false ) ); $this->assertEquals( $coupon->get_product_ids(), $coupon->product_ids ); From 9724b67ab8ae4f27e28042471805a1720e1bcfe3 Mon Sep 17 00:00:00 2001 From: Justin Shreve Date: Thu, 9 Mar 2017 13:36:23 -0800 Subject: [PATCH 10/59] Fix order item meta functions cache busting. The cache busting currently in `wc_add_order_item_meta`, `wc_update_order_item_meta`, and `wc_delete_order_item_meta` doesn't actually bust anything. The cache line looks like it is from 2.6. The relevent cache to bust is actually in the `order-items` group and has a different key/prefix. This bug allows your meta to get out of sync if you use these functions and then try to access a value from a CRUD object. You can see this in the `test_wc_order_item_meta_functions` test I've added. If you keep your `wc-order-item-functions.php` as is, the asserts against `$item->get_meta` will fail. To test: * `phpunit --filter=test_wc_order_item_meta_functions`. * Try before applying the `wc-order-item-functions.php` changes and after. --- includes/wc-order-item-functions.php | 12 ++--- tests/unit-tests/order-items/functions.php | 62 ++++++++++++++++++++++ 2 files changed, 68 insertions(+), 6 deletions(-) create mode 100644 tests/unit-tests/order-items/functions.php diff --git a/includes/wc-order-item-functions.php b/includes/wc-order-item-functions.php index 4666541ca4d..368008ed1bb 100644 --- a/includes/wc-order-item-functions.php +++ b/includes/wc-order-item-functions.php @@ -98,8 +98,8 @@ function wc_delete_order_item( $item_id ) { function wc_update_order_item_meta( $item_id, $meta_key, $meta_value, $prev_value = '' ) { $data_store = WC_Data_Store::load( 'order-item' ); if ( $data_store->update_metadata( $item_id, $meta_key, $meta_value, $prev_value ) ) { - $cache_key = WC_Cache_Helper::get_cache_prefix( 'orders' ) . 'item_meta_array_' . $item_id; - wp_cache_delete( $cache_key, 'orders' ); + $cache_key = WC_Cache_Helper::get_cache_prefix( 'order-items' ) . 'object_meta_' . $item_id; + wp_cache_delete( $cache_key, 'order-items' ); return true; } return false; @@ -118,8 +118,8 @@ function wc_update_order_item_meta( $item_id, $meta_key, $meta_value, $prev_valu function wc_add_order_item_meta( $item_id, $meta_key, $meta_value, $unique = false ) { $data_store = WC_Data_Store::load( 'order-item' ); if ( $meta_id = $data_store->add_metadata( $item_id, $meta_key, $meta_value, $unique ) ) { - $cache_key = WC_Cache_Helper::get_cache_prefix( 'orders' ) . 'item_meta_array_' . $item_id; - wp_cache_delete( $cache_key, 'orders' ); + $cache_key = WC_Cache_Helper::get_cache_prefix( 'order-items' ) . 'object_meta_' . $item_id; + wp_cache_delete( $cache_key, 'order-items' ); return $meta_id; } return 0; @@ -138,8 +138,8 @@ function wc_add_order_item_meta( $item_id, $meta_key, $meta_value, $unique = fal function wc_delete_order_item_meta( $item_id, $meta_key, $meta_value = '', $delete_all = false ) { $data_store = WC_Data_Store::load( 'order-item' ); if ( $data_store->delete_metadata( $item_id, $meta_key, $meta_value, $delete_all ) ) { - $cache_key = WC_Cache_Helper::get_cache_prefix( 'orders' ) . 'item_meta_array_' . $item_id; - wp_cache_delete( $cache_key, 'orders' ); + $cache_key = WC_Cache_Helper::get_cache_prefix( 'order-items' ) . 'object_meta_' . $item_id; + wp_cache_delete( $cache_key, 'order-items' ); return true; } return false; diff --git a/tests/unit-tests/order-items/functions.php b/tests/unit-tests/order-items/functions.php new file mode 100644 index 00000000000..f80ae8b7721 --- /dev/null +++ b/tests/unit-tests/order-items/functions.php @@ -0,0 +1,62 @@ +set_props( array( + 'product' => WC_Helper_Product::create_simple_product(), + 'quantity' => 4, + ) ); + $order->add_item( $item_1 ); + $order->save(); + + $item = current( $order->get_items() ); + $item_id = $item->get_id(); + + // Test that the initial key doesn't exist. + $item = new WC_Order_Item_Product( $item_id );; + $this->assertEmpty( $item->get_meta( '_test_key' ) ); + $this->assertEmpty( wc_get_order_item_meta( $item_id, '_test_key' ) ); + + // Test making sure cache is properly busted when adding meta. + wc_add_order_item_meta( $item_id, '_test_key', $meta_value ); + $item = new WC_Order_Item_Product( $item_id ); + $item_meta = $item->get_meta( '_test_key' ); + $this->assertEquals( $meta_value, $item_meta ); + $this->assertEquals( $meta_value, wc_get_order_item_meta( $item_id, '_test_key' ) ); + + // Test making sure cache is properly busted when updating meta. + wc_update_order_item_meta( $item_id, '_test_key', $meta_value2 ); + $item = new WC_Order_Item_Product( $item_id ); + $item_meta = $item->get_meta( '_test_key' ); + $this->assertEquals( $meta_value2, $item_meta ); + $this->assertEquals( $meta_value2, wc_get_order_item_meta( $item_id, '_test_key' ) ); + + // Test making sure cache is properly busted when deleting meta. + wc_delete_order_item_meta( $item_id, '_test_key' ); + $item = new WC_Order_Item_Product( $item_id ); + $item_meta = $item->get_meta( '_test_key' ); + $this->assertEmpty( $item->get_meta( '_test_key' ) ); + $this->assertEmpty( wc_get_order_item_meta( $item_id, '_test_key' ) ); + } + +} From 377fbf9c44b6a065cbc45fb07bd19cc5c9eeb2b1 Mon Sep 17 00:00:00 2001 From: Claudio Sanches Date: Fri, 10 Mar 2017 00:47:17 -0300 Subject: [PATCH 11/59] Use WordPress wp_get_password_hint() function to display passowd hint Closes #13534 --- includes/class-wc-frontend-scripts.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/includes/class-wc-frontend-scripts.php b/includes/class-wc-frontend-scripts.php index f3d413b3fc5..2478c84c1b2 100644 --- a/includes/class-wc-frontend-scripts.php +++ b/includes/class-wc-frontend-scripts.php @@ -556,7 +556,7 @@ class WC_Frontend_Scripts { return array( 'min_password_strength' => apply_filters( 'woocommerce_min_password_strength', 3 ), 'i18n_password_error' => esc_attr__( 'Please enter a stronger password.', 'woocommerce' ), - 'i18n_password_hint' => esc_attr__( 'The password should be at least seven characters long. To make it stronger, use upper and lower case letters, numbers and symbols like ! " ? $ % ^ & ).', 'woocommerce' ), + 'i18n_password_hint' => esc_attr( wp_get_password_hint() ), ); break; } From e2552c0a8c08735a78c6b14f5ac61fec5d463def Mon Sep 17 00:00:00 2001 From: Mike Jolley Date: Fri, 10 Mar 2017 12:01:40 +0000 Subject: [PATCH 12/59] Unit tests --- tests/unit-tests/crud/meta.php | 115 +++++++++++++++++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 tests/unit-tests/crud/meta.php diff --git a/tests/unit-tests/crud/meta.php b/tests/unit-tests/crud/meta.php new file mode 100644 index 00000000000..0a9d604d3be --- /dev/null +++ b/tests/unit-tests/crud/meta.php @@ -0,0 +1,115 @@ +item_id == $object_id ) ? WC_Order_Factory::get_order_item( $object_id ) : wc_get_order( $object_id ); + $object->add_meta_data( 'random_other', 'This might disappear :cry:.' ); + $object->save(); // 'random_other' will be saved correctly, 'random' will also be saved + + // Now set some meta data for it using the pre-CRUD approach + if ( $this->item_id == $object_id ) { + wc_update_order_item_meta( $object_id, 'random_other_pre_crud', 'This might disappear too :sob:.' ); + } else { + update_post_meta( $object_id, 'random_other_pre_crud', 'This might disappear too :sob:.' ); + } + } + + /** + * Instantiate an instance of an order line item, save meta on that item, then trigger a hook which only passes the + * item's ID to callbacks. Afterwards, save the existing item without that other meta here and log the result to + * show that data has been removed. + */ + function test_disappearing_item_meta() { + // Setup for testing by making an item. + $item = new WC_Order_Item_Product(); + $this->item_id = $item->save(); + + $item = WC_Order_Factory::get_order_item( $this->item_id ); + + // First make sure we're starting from a clean slate + $item->delete_meta_data( 'random' ); + $item->delete_meta_data( 'random_other' ); + $item->delete_meta_data( 'random_other_pre_crud' ); + $item->save(); + + // Now add one piece of meta + $item->add_meta_data( 'random', (string) rand( 0, 0xffffff ) ); + $item->save(); // 'random' will be saved correctly + + // Run callback that passes just the item's ID to mimic this kind of normal behaviour in WP/WC + $this->add_different_object_meta( $this->item_id ); + + // Resave our current item that has our meta data, but not the other piece of meta data + $item->save(); + + // Get a new instance that should have both 'random' and 'random_other' set on it + $new_item = WC_Order_Factory::get_order_item( $this->item_id ); + + // The original $item should have 1 item of meta - random. + $this->assertCount( 1, $item->get_meta_data() ); + $this->assertTrue( in_array( 'random', wp_list_pluck( $item->get_meta_data(), 'key' ) ) ); + + // The new $item should have 3 items of meta since it's freshly loaded. + $this->assertCount( 3, $new_item->get_meta_data() ); + $this->assertTrue( in_array( 'random', wp_list_pluck( $new_item->get_meta_data(), 'key' ) ) ); + $this->assertTrue( in_array( 'random_other', wp_list_pluck( $new_item->get_meta_data(), 'key' ) ) ); + $this->assertTrue( in_array( 'random_other_pre_crud', wp_list_pluck( $new_item->get_meta_data(), 'key' ) ) ); + } + + /** + * Instantiate an instance of an order, save meta on that order, then trigger a hook which only passes the order's ID to + * callbacks. Afterwards, save the existing order without that other meta here and log the result to show that data + * has been removed. + */ + function test_disappearing_order_meta() { + // Setup for testing by making an item. + $order = new WC_Order(); + $this->order_id = $order->save(); + + $order = wc_get_order( $this->order_id ); + + // First make sure we're starting from a clean slate + $order->delete_meta_data( 'random' ); + $order->delete_meta_data( 'random_other' ); + $order->delete_meta_data( 'random_other_pre_crud' ); + $order->save(); + + // Now add one piece of meta + $order->add_meta_data( 'random', (string) rand( 0, 0xffffff ) ); + $order->save(); // 'random' will be saved correctly + + // Run callback that passes just the item's ID to mimic this kind of normal behaviour in WP/WC + $this->add_different_object_meta( $this->order_id ); + + // Resave our current item that has our meta data, but not the other piece of meta data + $order->save(); + + // Get a new instance of the same order. It should have both 'random' and 'random_other' set on it + $new_order = wc_get_order( $this->order_id ); + + // The original $order should have 1 item of meta - random. + $this->assertCount( 1, $order->get_meta_data() ); + $this->assertTrue( in_array( 'random', wp_list_pluck( $order->get_meta_data(), 'key' ) ) ); + + // The new $order should have 3 items of meta since it's freshly loaded. + $this->assertCount( 3, $new_order->get_meta_data() ); + $this->assertTrue( in_array( 'random', wp_list_pluck( $new_order->get_meta_data(), 'key' ) ) ); + $this->assertTrue( in_array( 'random_other', wp_list_pluck( $new_order->get_meta_data(), 'key' ) ) ); + $this->assertTrue( in_array( 'random_other_pre_crud', wp_list_pluck( $new_order->get_meta_data(), 'key' ) ) ); + } +} From 2894d5aab01875e5a26b20f643b546c99fc279e7 Mon Sep 17 00:00:00 2001 From: Mike Jolley Date: Fri, 10 Mar 2017 12:21:42 +0000 Subject: [PATCH 13/59] Make needs_processing public so the data store can access. --- includes/class-wc-order.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/includes/class-wc-order.php b/includes/class-wc-order.php index f17827b66be..cc85071dfca 100644 --- a/includes/class-wc-order.php +++ b/includes/class-wc-order.php @@ -1297,7 +1297,7 @@ class WC_Order extends WC_Abstract_Order { * @since 2.7.0 * @return bool */ - protected function needs_processing() { + public function needs_processing() { $needs_processing = false; if ( sizeof( $this->get_items() ) > 0 ) { From f77b03ef94cf27130b66e2eb0b0f89ece99af498 Mon Sep 17 00:00:00 2001 From: Konstantinos Kouratoras Date: Fri, 10 Mar 2017 17:01:47 +0200 Subject: [PATCH 14/59] Add missing trailing punctuation Trailing punctuation is missing, causing duplication of the same text in translation, although it exists in another file: https://github.com/woocommerce/woocommerce/blob/master/includes/api/class-wc-rest-settings-options-controller.php#L482 --- includes/api/class-wc-rest-settings-controller.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/includes/api/class-wc-rest-settings-controller.php b/includes/api/class-wc-rest-settings-controller.php index 6bc43e9cd3a..d84de791a70 100644 --- a/includes/api/class-wc-rest-settings-controller.php +++ b/includes/api/class-wc-rest-settings-controller.php @@ -210,7 +210,7 @@ class WC_REST_Settings_Controller extends WC_REST_Controller { ), ), 'description' => array( - 'description' => __( 'A human readable translation wrapped description. Meant to be used in interfaces', 'woocommerce' ), + 'description' => __( 'A human readable translation wrapped description. Meant to be used in interfaces.', 'woocommerce' ), 'type' => 'string', 'arg_options' => array( 'sanitize_callback' => 'sanitize_text_field', From d70723553d21237e9666305d43fac00fdf4f717e Mon Sep 17 00:00:00 2001 From: Claudiu Lodromanean Date: Fri, 10 Mar 2017 09:18:14 -0800 Subject: [PATCH 15/59] CHange vary to update --- includes/data-stores/class-wc-coupon-data-store-cpt.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/includes/data-stores/class-wc-coupon-data-store-cpt.php b/includes/data-stores/class-wc-coupon-data-store-cpt.php index b45350f8499..c69cbb943ba 100644 --- a/includes/data-stores/class-wc-coupon-data-store-cpt.php +++ b/includes/data-stores/class-wc-coupon-data-store-cpt.php @@ -231,7 +231,7 @@ class WC_Coupon_Data_Store_CPT extends WC_Data_Store_WP implements WC_Coupon_Dat * @return int New usage count */ public function increase_usage_count( &$coupon, $used_by = '' ) { - $new_count = $this->vary_usage_count_meta( $coupon, 'increase' ); + $new_count = $this->update_usage_count_meta( $coupon, 'increase' ); if ( $used_by ) { add_post_meta( $coupon->get_id(), '_used_by', strtolower( $used_by ) ); $coupon->set_used_by( (array) get_post_meta( $coupon->get_id(), '_used_by' ) ); @@ -249,7 +249,7 @@ class WC_Coupon_Data_Store_CPT extends WC_Data_Store_WP implements WC_Coupon_Dat */ public function decrease_usage_count( &$coupon, $used_by = '' ) { global $wpdb; - $new_count = $this->vary_usage_count_meta( $coupon, 'decrease' ); + $new_count = $this->update_usage_count_meta( $coupon, 'decrease' ); if ( $used_by ) { /** * We're doing this the long way because `delete_post_meta( $id, $key, $value )` deletes. @@ -272,7 +272,7 @@ class WC_Coupon_Data_Store_CPT extends WC_Data_Store_WP implements WC_Coupon_Dat * @param string $operation 'increase' or 'decrease' * @return int New usage count */ - private function vary_usage_count_meta( &$coupon, $operation = 'increase' ) { + private function update_usage_count_meta( &$coupon, $operation = 'increase' ) { global $wpdb; $id = $coupon->get_id(); $operator = ( 'increase' === $operation ) ? '+' : '-'; From e6a17a0fb37519b61e5a1d1da074c6b821d63eb8 Mon Sep 17 00:00:00 2001 From: Claudiu Lodromanean Date: Fri, 10 Mar 2017 09:27:39 -0800 Subject: [PATCH 16/59] Dont use set_prop on increase/decrease to prevent overwriting data --- includes/class-wc-coupon.php | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/includes/class-wc-coupon.php b/includes/class-wc-coupon.php index 591d8671006..a368e3a6df0 100644 --- a/includes/class-wc-coupon.php +++ b/includes/class-wc-coupon.php @@ -710,7 +710,12 @@ class WC_Coupon extends WC_Legacy_Coupon { public function increase_usage_count( $used_by = '' ) { if ( $this->get_id() && $this->data_store ) { $new_count = $this->data_store->increase_usage_count( $this, $used_by ); - $this->set_prop( 'usage_count', $new_count ); + + // Bypass set_prop and remove pending changes to prevent overwriting usage_count on coupon save. + $this->data['usage_count'] = $new_count; + if ( isset( $this->changes['usage_count'] ) ) { + unset( $this->changes['usage_count'] ); + } } } @@ -722,7 +727,12 @@ class WC_Coupon extends WC_Legacy_Coupon { public function decrease_usage_count( $used_by = '' ) { if ( $this->get_id() && $this->get_usage_count() > 0 && $this->data_store ) { $new_count = $this->data_store->decrease_usage_count( $this, $used_by ); - $this->set_prop( 'usage_count', $new_count ); + + // Bypass set_prop and remove pending changes to prevent overwriting usage_count on coupon save. + $this->data['usage_count'] = $new_count; + if ( isset( $this->changes['usage_count'] ) ) { + unset( $this->changes['usage_count'] ); + } } } From d630b948e3c4ba9c4369d75a98feab01fafe7fc0 Mon Sep 17 00:00:00 2001 From: Claudiu Lodromanean Date: Fri, 10 Mar 2017 10:28:56 -0800 Subject: [PATCH 17/59] Better increase/decreasing --- includes/class-wc-coupon.php | 4 ++-- .../data-stores/class-wc-coupon-data-store-cpt.php | 10 ++++------ 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/includes/class-wc-coupon.php b/includes/class-wc-coupon.php index a368e3a6df0..c55c06ec927 100644 --- a/includes/class-wc-coupon.php +++ b/includes/class-wc-coupon.php @@ -711,7 +711,7 @@ class WC_Coupon extends WC_Legacy_Coupon { if ( $this->get_id() && $this->data_store ) { $new_count = $this->data_store->increase_usage_count( $this, $used_by ); - // Bypass set_prop and remove pending changes to prevent overwriting usage_count on coupon save. + // Bypass set_prop and remove pending changes since the count gets written by the data store. $this->data['usage_count'] = $new_count; if ( isset( $this->changes['usage_count'] ) ) { unset( $this->changes['usage_count'] ); @@ -728,7 +728,7 @@ class WC_Coupon extends WC_Legacy_Coupon { if ( $this->get_id() && $this->get_usage_count() > 0 && $this->data_store ) { $new_count = $this->data_store->decrease_usage_count( $this, $used_by ); - // Bypass set_prop and remove pending changes to prevent overwriting usage_count on coupon save. + // Bypass set_prop and remove pending changes since the count gets written by the data store. $this->data['usage_count'] = $new_count; if ( isset( $this->changes['usage_count'] ) ) { unset( $this->changes['usage_count'] ); diff --git a/includes/data-stores/class-wc-coupon-data-store-cpt.php b/includes/data-stores/class-wc-coupon-data-store-cpt.php index c69cbb943ba..26795426294 100644 --- a/includes/data-stores/class-wc-coupon-data-store-cpt.php +++ b/includes/data-stores/class-wc-coupon-data-store-cpt.php @@ -276,14 +276,12 @@ class WC_Coupon_Data_Store_CPT extends WC_Data_Store_WP implements WC_Coupon_Dat global $wpdb; $id = $coupon->get_id(); $operator = ( 'increase' === $operation ) ? '+' : '-'; - $new_count = ( 'increase' === $operation ) ? ( $coupon->get_usage_count( 'edit' ) + 1 ) : ( $coupon->get_usage_count( 'edit' ) - 1 ); - $updated = $wpdb->query( $wpdb->prepare( "UPDATE $wpdb->postmeta SET meta_value = meta_value {$operator} 1 WHERE meta_key = 'usage_count' AND post_id = %d;", $id ) ); - if ( ! $updated ) { - add_post_meta( $id, 'usage_count', $new_count, true ); - } + add_post_meta( $id, 'usage_count', $coupon->get_usage_count( 'edit' ), true ); + $wpdb->query( $wpdb->prepare( "UPDATE $wpdb->postmeta SET meta_value = meta_value {$operator} 1 WHERE meta_key = 'usage_count' AND post_id = %d;", $id ) ); - return $new_count; + // Get the latest value direct from the DB, instead of possibly the WP meta cache. + return (int) $wpdb->get_var( $wpdb->prepare( "SELECT meta_value FROM $wpdb->postmeta WHERE meta_key = 'usage_count' AND post_id = %d;", $id ) ); } /** From 73ed27318999417577c612bda0735cc8b16415fa Mon Sep 17 00:00:00 2001 From: Claudiu Lodromanean Date: Fri, 10 Mar 2017 10:32:27 -0800 Subject: [PATCH 18/59] Use active voice --- includes/class-wc-coupon.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/includes/class-wc-coupon.php b/includes/class-wc-coupon.php index c55c06ec927..2a69a35d991 100644 --- a/includes/class-wc-coupon.php +++ b/includes/class-wc-coupon.php @@ -711,7 +711,7 @@ class WC_Coupon extends WC_Legacy_Coupon { if ( $this->get_id() && $this->data_store ) { $new_count = $this->data_store->increase_usage_count( $this, $used_by ); - // Bypass set_prop and remove pending changes since the count gets written by the data store. + // Bypass set_prop and remove pending changes since the data store saves the count already. $this->data['usage_count'] = $new_count; if ( isset( $this->changes['usage_count'] ) ) { unset( $this->changes['usage_count'] ); @@ -728,7 +728,7 @@ class WC_Coupon extends WC_Legacy_Coupon { if ( $this->get_id() && $this->get_usage_count() > 0 && $this->data_store ) { $new_count = $this->data_store->decrease_usage_count( $this, $used_by ); - // Bypass set_prop and remove pending changes since the count gets written by the data store. + // Bypass set_prop and remove pending changes since the data store saves the count already. $this->data['usage_count'] = $new_count; if ( isset( $this->changes['usage_count'] ) ) { unset( $this->changes['usage_count'] ); From ff4b79a6fc0c9708f94feeca5b3e22151f6b8076 Mon Sep 17 00:00:00 2001 From: Mike Jolley Date: Fri, 10 Mar 2017 20:20:07 +0000 Subject: [PATCH 19/59] Status transition hooks should never run when objects are read regardless of status being set. Fixes #13547 --- includes/class-wc-order.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/includes/class-wc-order.php b/includes/class-wc-order.php index cc85071dfca..57527f4083e 100644 --- a/includes/class-wc-order.php +++ b/includes/class-wc-order.php @@ -226,7 +226,7 @@ class WC_Order extends WC_Abstract_Order { public function set_status( $new_status, $note = '', $manual_update = false ) { $result = parent::set_status( $new_status ); - if ( ! empty( $result['from'] ) && $result['from'] !== $result['to'] ) { + if ( true === $this->object_read && ! empty( $result['from'] ) && $result['from'] !== $result['to'] ) { $this->status_transition = array( 'from' => ! empty( $this->status_transition['from'] ) ? $this->status_transition['from'] : $result['from'], 'to' => $result['to'], From 1a99235dc85193b43b43e03fa225aef9a69fde89 Mon Sep 17 00:00:00 2001 From: fuzzguard Date: Sat, 11 Mar 2017 11:43:21 +1100 Subject: [PATCH 20/59] Adding in proper error handling from 'lostpassword_post' Since WP version 4.4.0 the 'lostpassword_post' hook has had the ability to handle error messages from the WP_Error class. This allows errors to to occur BEFORE the username or email address are validated against the WP. wp-login.php /** * Fires before errors are returned from a password reset request. * * @since 2.1.0 * @since 4.4.0 Added the `$errors` parameter. * * @param WP_Error $errors A WP_Error object containing any errors generated * by using invalid credentials. */ do_action( 'lostpassword_post', $errors ); if ( $errors->get_error_code() ) return $errors; Proposal is to have this same process be respected by WooCommerce Lost Password process. --- includes/shortcodes/class-wc-shortcode-my-account.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/includes/shortcodes/class-wc-shortcode-my-account.php b/includes/shortcodes/class-wc-shortcode-my-account.php index 33b93e25836..c8749ab1461 100644 --- a/includes/shortcodes/class-wc-shortcode-my-account.php +++ b/includes/shortcodes/class-wc-shortcode-my-account.php @@ -246,8 +246,14 @@ class WC_Shortcode_My_Account { if ( ! $user_data && is_email( $login ) && apply_filters( 'woocommerce_get_username_from_email', true ) ) { $user_data = get_user_by( 'email', $login ); } + + $errors = new WP_Error(); + do_action( 'lostpassword_post', $errors ); - do_action( 'lostpassword_post' ); + if ( $errors->get_error_code() ) { + wc_add_notice( $allow->get_error_message(), 'error' ); + return false; + } if ( ! $user_data ) { wc_add_notice( __( 'Invalid username or email.', 'woocommerce' ), 'error' ); From 91ce342e7cd50cd510a292e01cb9d164f2a1dc52 Mon Sep 17 00:00:00 2001 From: JeroenSormani Date: Sat, 11 Mar 2017 19:01:58 +0100 Subject: [PATCH 21/59] Type cast ->get_weight() on WC_Cart::get_cart_contents_weight calculation --- includes/class-wc-cart.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/includes/class-wc-cart.php b/includes/class-wc-cart.php index 419191f2a89..88f5af13619 100644 --- a/includes/class-wc-cart.php +++ b/includes/class-wc-cart.php @@ -358,7 +358,7 @@ class WC_Cart { $weight = 0; foreach ( $this->get_cart() as $cart_item_key => $values ) { - $weight += $values['data']->get_weight() * $values['quantity']; + $weight += (float) $values['data']->get_weight() * $values['quantity']; } return apply_filters( 'woocommerce_cart_contents_weight', $weight ); From d2457fe922570dd307139b773bffd96f51d9abf2 Mon Sep 17 00:00:00 2001 From: Rami Yushuvaev Date: Mon, 13 Mar 2017 07:39:46 +0200 Subject: [PATCH 22/59] i18n: escape translation strings and fix wrong usage of i18n functions --- .../abstracts/abstract-wc-payment-gateway.php | 2 +- .../admin/class-wc-admin-setup-wizard.php | 124 +++++++++--------- .../class-wc-admin-webhooks-table-list.php | 6 +- .../class-wc-meta-box-product-reviews.php | 2 +- .../views/html-product-attribute.php | 4 +- .../views/html-product-data-shipping.php | 2 +- .../views/html-product-data-variations.php | 2 +- .../meta-boxes/views/html-variation-admin.php | 6 +- .../settings/class-wc-settings-checkout.php | 2 +- .../settings/class-wc-settings-emails.php | 8 +- .../settings/views/html-settings-tax.php | 2 +- .../admin/views/html-bulk-edit-product.php | 16 +-- includes/class-wc-download-handler.php | 2 +- includes/class-wc-install.php | 8 +- .../gateways/class-wc-payment-gateway-cc.php | 6 +- .../class-wc-payment-gateway-echeck.php | 4 +- includes/wc-template-functions.php | 18 +-- includes/wc-term-functions.php | 4 +- .../class-wc-widget-layered-nav-filters.php | 2 +- .../widgets/class-wc-widget-price-filter.php | 4 +- .../widgets/class-wc-widget-rating-filter.php | 2 +- .../class-wc-widget-recent-reviews.php | 2 +- templates/cart/shipping-calculator.php | 4 +- templates/loop/sale-flash.php | 2 +- templates/single-product-reviews.php | 20 +-- .../single-product/add-to-cart/variable.php | 2 +- templates/single-product/meta.php | 2 +- templates/single-product/sale-flash.php | 2 +- 28 files changed, 130 insertions(+), 130 deletions(-) diff --git a/includes/abstracts/abstract-wc-payment-gateway.php b/includes/abstracts/abstract-wc-payment-gateway.php index 3c3c24248e1..77221aefcb2 100644 --- a/includes/abstracts/abstract-wc-payment-gateway.php +++ b/includes/abstracts/abstract-wc-payment-gateway.php @@ -447,7 +447,7 @@ abstract class WC_Payment_Gateway extends WC_Settings_API { * @since 2.6.0 */ public function save_payment_method_checkbox() { - echo sprintf( + printf( '

diff --git a/includes/admin/class-wc-admin-setup-wizard.php b/includes/admin/class-wc-admin-setup-wizard.php index 445edb2eb9d..f5f935300b9 100644 --- a/includes/admin/class-wc-admin-setup-wizard.php +++ b/includes/admin/class-wc-admin-setup-wizard.php @@ -142,7 +142,7 @@ class WC_Admin_Setup_Wizard { - <?php _e( 'WooCommerce › Setup Wizard', 'woocommerce' ); ?> + <?php esc_html_e( 'WooCommerce › Setup Wizard', 'woocommerce' ); ?> @@ -158,7 +158,7 @@ class WC_Admin_Setup_Wizard { public function setup_wizard_footer() { ?> step ) : ?> - + @@ -200,12 +200,12 @@ class WC_Admin_Setup_Wizard { */ public function wc_setup_introduction() { ?> -

+

Itโ€™s completely optional and shouldnโ€™t take longer than five minutes.', 'woocommerce' ); ?>

-

+

- - + +

-

+

pages. The following will be created automatically (if they do not already exist):', 'woocommerce' ), esc_url( admin_url( 'edit.php?post_type=page' ) ) ); ?>

- - + + - + - + @@ -253,7 +253,7 @@ class WC_Admin_Setup_Wizard {

- +

@@ -289,11 +289,11 @@ class WC_Admin_Setup_Wizard { $dimension_unit = get_option( 'woocommerce_dimension_unit', 'cm' ); $weight_unit = get_option( 'woocommerce_weight_unit', 'kg' ); ?> -

+

- +
- +
- + - + - + - + - + - + - + - +

- +

@@ -409,29 +409,29 @@ class WC_Admin_Setup_Wizard { */ public function wc_setup_shipping_taxes() { ?> -

+

-

+

- + - + - + - - - - + + + + @@ -490,7 +490,7 @@ class WC_Admin_Setup_Wizard {
name="woocommerce_calc_shipping" class="input-checkbox" value="1" /> - +
id="woocommerce_calc_taxes" name="woocommerce_calc_taxes" class="input-checkbox" value="1" /> - +
-
- +
+

- +

@@ -639,7 +639,7 @@ class WC_Admin_Setup_Wizard { public function wc_setup_payments() { $gateways = $this->get_wizard_payment_gateways(); ?> -

+

Additional payment methods can be installed later and managed from the checkout settings screen.', 'woocommerce' ), esc_url( admin_url( 'admin.php?page=wc-addons&view=payment-gateways' ) ), esc_url( admin_url( 'admin.php?page=wc-settings&tab=checkout' ) ) ); ?>

@@ -683,7 +683,7 @@ class WC_Admin_Setup_Wizard {

- +

@@ -746,31 +746,31 @@ class WC_Admin_Setup_Wizard { -

+

', '' ); ?>

- - + +

-

+

    -
  • +

    -
  • - -
  • +
  • + +
diff --git a/includes/admin/class-wc-admin-webhooks-table-list.php b/includes/admin/class-wc-admin-webhooks-table-list.php index 0b21bfb4e01..201fdb292d5 100644 --- a/includes/admin/class-wc-admin-webhooks-table-list.php +++ b/includes/admin/class-wc-admin-webhooks-table-list.php @@ -101,12 +101,12 @@ class WC_Admin_Webhooks_Table_List extends WP_List_Table { if ( current_user_can( $post_type_object->cap->delete_post, $the_webhook->id ) ) { if ( 'trash' == $post_status ) { - $actions['untrash'] = '' . __( 'Restore', 'woocommerce' ) . ''; + $actions['untrash'] = '' . esc_html__( 'Restore', 'woocommerce' ) . ''; } elseif ( EMPTY_TRASH_DAYS ) { - $actions['trash'] = '' . __( 'Trash', 'woocommerce' ) . ''; + $actions['trash'] = '' . esc_html__( 'Trash', 'woocommerce' ) . ''; } if ( 'trash' == $post_status || ! EMPTY_TRASH_DAYS ) { - $actions['delete'] = '' . __( 'Delete permanently', 'woocommerce' ) . ''; + $actions['delete'] = '' . esc_html__( 'Delete permanently', 'woocommerce' ) . ''; } } diff --git a/includes/admin/meta-boxes/class-wc-meta-box-product-reviews.php b/includes/admin/meta-boxes/class-wc-meta-box-product-reviews.php index 5c25a24c977..59bcaeef561 100644 --- a/includes/admin/meta-boxes/class-wc-meta-box-product-reviews.php +++ b/includes/admin/meta-boxes/class-wc-meta-box-product-reviews.php @@ -27,7 +27,7 @@ class WC_Meta_Box_Product_Reviews { ?> " placeholder="" /> @@ -66,7 +66,7 @@ - + diff --git a/includes/admin/meta-boxes/views/html-product-data-shipping.php b/includes/admin/meta-boxes/views/html-product-data-shipping.php index 16c5d6beef4..e4240541f07 100644 --- a/includes/admin/meta-boxes/views/html-product-data-shipping.php +++ b/includes/admin/meta-boxes/views/html-product-data-shipping.php @@ -16,7 +16,7 @@ if ( wc_product_dimensions_enabled() ) { ?>

- + diff --git a/includes/admin/meta-boxes/views/html-product-data-variations.php b/includes/admin/meta-boxes/views/html-product-data-variations.php index bedac760319..2962100cae6 100644 --- a/includes/admin/meta-boxes/views/html-product-data-variations.php +++ b/includes/admin/meta-boxes/views/html-product-data-variations.php @@ -18,7 +18,7 @@ $selected_value = isset( $default_attributes[ sanitize_title( $attribute->get_name() ) ] ) ? $default_attributes[ sanitize_title( $attribute->get_name() ) ] : ''; ?> "> is_taxonomy() ) : ?> get_terms() as $option ) : ?> @@ -50,7 +50,7 @@ if ( ! defined( 'ABSPATH' ) ) {