Merge pull request #22731 from woocommerce/update/21952

Prevent webhooks for drafts and correct create vs updated for manual orders
This commit is contained in:
Mike Jolley 2019-02-19 09:50:51 +00:00 committed by GitHub
commit 2529cd60cd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 194 additions and 88 deletions

View File

@ -484,14 +484,6 @@ class WC_Post_Types {
$order_statuses = apply_filters(
'woocommerce_register_shop_order_post_statuses',
array(
'wc-auto-draft' => array(
'public' => false,
'exclude_from_search' => false,
'show_in_admin_all_list' => true,
'show_in_admin_status_list' => true,
/* translators: %s: number of orders */
'label_count' => _n_noop( 'Draft <span class="count">(%s)</span>', 'Draft <span class="count">(%s)</span>', 'woocommerce' ),
),
'wc-pending' => array(
'label' => _x( 'Pending payment', 'Order status', 'woocommerce' ),
'public' => false,

View File

@ -123,62 +123,158 @@ class WC_Webhook extends WC_Legacy_Webhook {
* @return bool True if webhook should be delivered, false otherwise.
*/
private function should_deliver( $arg ) {
$should_deliver = true;
$current_action = current_action();
$should_deliver = $this->is_active() && $this->is_valid_topic() && $this->is_valid_action( $arg ) && $this->is_valid_resource( $arg );
// Only active webhooks can be delivered.
if ( 'active' !== $this->get_status() ) {
$should_deliver = false;
} elseif ( in_array( $current_action, array( 'delete_post', 'wp_trash_post', 'untrashed_post' ), true ) ) {
// Only deliver deleted/restored event for coupons, orders, and products.
if ( isset( $GLOBALS['post_type'] ) && ! in_array( $GLOBALS['post_type'], array( 'shop_coupon', 'shop_order', 'product' ), true ) ) {
$should_deliver = false;
}
// Check if is delivering for the correct resource.
if ( isset( $GLOBALS['post_type'] ) && str_replace( 'shop_', '', $GLOBALS['post_type'] ) !== $this->get_resource() ) {
$should_deliver = false;
}
} elseif ( 'delete_user' === $current_action ) {
$user = get_userdata( absint( $arg ) );
// Only deliver deleted customer event for users with customer role.
if ( ! $user || ! in_array( 'customer', (array) $user->roles, true ) ) {
$should_deliver = false;
}
} elseif ( 'order' === $this->get_resource() && ! in_array( get_post_type( absint( $arg ) ), wc_get_order_types( 'order-webhooks' ), true ) ) {
// Only if the custom order type has chosen to exclude order webhooks from triggering along with its own webhooks.
$should_deliver = false;
} elseif ( 0 === strpos( $current_action, 'woocommerce_process_shop' ) || 0 === strpos( $current_action, 'woocommerce_process_product' ) ) {
// The `woocommerce_process_shop_*` and `woocommerce_process_product_*` hooks
// fire for create and update of products and orders, so check the post
// creation date to determine the actual event.
$resource = get_post( absint( $arg ) );
// Drafts don't have post_date_gmt so calculate it here.
$gmt_date = get_gmt_from_date( $resource->post_date );
// A resource is considered created when the hook is executed within 10 seconds of the post creation date.
$resource_created = ( ( time() - 10 ) <= strtotime( $gmt_date ) );
if ( 'created' === $this->get_event() && ! $resource_created ) {
$should_deliver = false;
} elseif ( 'updated' === $this->get_event() && $resource_created ) {
$should_deliver = false;
}
}
if ( ! wc_is_webhook_valid_topic( $this->get_topic() ) ) {
$should_deliver = false;
}
/*
/**
* Let other plugins intercept deliver for some messages queue like rabbit/zeromq.
*
* @param bool $should_deliver True if the webhook should be sent, or false to not send it.
* @param WC_Webhook $this The current webhook class.
* @param mixed $arg First hook argument.
*/
return apply_filters( 'woocommerce_webhook_should_deliver', $should_deliver, $this, $arg );
}
/**
* Returns if webhook is active.
*
* @since 3.6.0
* @return bool True if validation passes.
*/
private function is_active() {
return 'active' === $this->get_status();
}
/**
* Returns if topic is valid.
*
* @since 3.6.0
* @return bool True if validation passes.
*/
private function is_valid_topic() {
return wc_is_webhook_valid_topic( $this->get_topic() );
}
/**
* Validates the criteria for certain actions.
*
* @since 3.6.0
* @param mixed $arg First hook argument.
* @return bool True if validation passes.
*/
private function is_valid_action( $arg ) {
$current_action = current_action();
$return = true;
switch ( $current_action ) {
case 'delete_post':
case 'wp_trash_post':
case 'untrashed_post':
$return = $this->is_valid_post_action( $arg );
break;
case 'delete_user':
$return = $this->is_valid_user_action( $arg );
break;
}
if ( 0 === strpos( $current_action, 'woocommerce_process_shop' ) || 0 === strpos( $current_action, 'woocommerce_process_product' ) ) {
$return = $this->is_valid_processing_action( $arg );
}
return $return;
}
/**
* Validates post actions.
*
* @since 3.6.0
* @param mixed $arg First hook argument.
* @return bool True if validation passes.
*/
private function is_valid_post_action( $arg ) {
// Only deliver deleted/restored event for coupons, orders, and products.
if ( isset( $GLOBALS['post_type'] ) && ! in_array( $GLOBALS['post_type'], array( 'shop_coupon', 'shop_order', 'product' ), true ) ) {
return false;
}
// Check if is delivering for the correct resource.
if ( isset( $GLOBALS['post_type'] ) && str_replace( 'shop_', '', $GLOBALS['post_type'] ) !== $this->get_resource() ) {
return false;
}
return true;
}
/**
* Validates user actions.
*
* @since 3.6.0
* @param mixed $arg First hook argument.
* @return bool True if validation passes.
*/
private function is_valid_user_action( $arg ) {
$user = get_userdata( absint( $arg ) );
// Only deliver deleted customer event for users with customer role.
if ( ! $user || ! in_array( 'customer', (array) $user->roles, true ) ) {
return false;
}
return true;
}
/**
* Validates WC processing actions.
*
* @since 3.6.0
* @param mixed $arg First hook argument.
* @return bool True if validation passes.
*/
private function is_valid_processing_action( $arg ) {
// The `woocommerce_process_shop_*` and `woocommerce_process_product_*` hooks
// fire for create and update of products and orders, so check the post
// creation date to determine the actual event.
$resource = get_post( absint( $arg ) );
// Drafts don't have post_date_gmt so calculate it here.
$gmt_date = get_gmt_from_date( $resource->post_date );
// A resource is considered created when the hook is executed within 10 seconds of the post creation date.
$resource_created = ( ( time() - 10 ) <= strtotime( $gmt_date ) );
if ( 'created' === $this->get_event() && ! $resource_created ) {
return false;
} elseif ( 'updated' === $this->get_event() && $resource_created ) {
return false;
}
return true;
}
/**
* Checks the resource for this webhook is valid e.g. valid post status.
*
* @since 3.6.0
* @param mixed $arg First hook argument.
* @return bool True if validation passes.
*/
private function is_valid_resource( $arg ) {
$resource = $this->get_resource();
if ( in_array( $resource, array( 'order', 'product', 'coupon' ), true ) ) {
$status = get_post_status( absint( $arg ) );
// Ignore auto drafts for all resources.
if ( in_array( $status, array( 'auto-draft', 'new' ), true ) ) {
return false;
}
// Ignore standard drafts for orders.
if ( 'order' === $resource && 'draft' === $status ) {
return false;
}
}
return true;
}
/**
* Deliver the webhook payload using wp_safe_remote_request().
*
@ -868,11 +964,9 @@ class WC_Webhook extends WC_Legacy_Webhook {
'delete_user',
),
'order.created' => array(
'woocommerce_process_shop_order_meta',
'woocommerce_new_order',
),
'order.updated' => array(
'woocommerce_process_shop_order_meta',
'woocommerce_update_order',
'woocommerce_order_refunded',
),

View File

@ -58,6 +58,9 @@ abstract class Abstract_WC_Order_Data_Store_CPT extends WC_Data_Store_WP impleme
$order->set_date_created( current_time( 'timestamp', true ) );
$order->set_currency( $order->get_currency() ? $order->get_currency() : get_woocommerce_currency() );
$raw_status = $order->get_status( 'edit' ) ? $order->get_status( 'edit' ) : apply_filters( 'woocommerce_default_order_status', 'pending' );
$status = wc_is_order_status( 'wc-' . $raw_status ) ? 'wc-' . $raw_status : $raw_status;
$id = wp_insert_post(
apply_filters(
'woocommerce_new_order_data',
@ -65,7 +68,7 @@ abstract class Abstract_WC_Order_Data_Store_CPT extends WC_Data_Store_WP impleme
'post_date' => gmdate( 'Y-m-d H:i:s', $order->get_date_created( 'edit' )->getOffsetTimestamp() ),
'post_date_gmt' => gmdate( 'Y-m-d H:i:s', $order->get_date_created( 'edit' )->getTimestamp() ),
'post_type' => $order->get_type( 'edit' ),
'post_status' => 'wc-' . ( $order->get_status( 'edit' ) ? $order->get_status( 'edit' ) : apply_filters( 'woocommerce_default_order_status', 'pending' ) ),
'post_status' => $status,
'ping_status' => 'closed',
'post_author' => 1,
'post_title' => $this->get_post_title(),
@ -73,7 +76,8 @@ abstract class Abstract_WC_Order_Data_Store_CPT extends WC_Data_Store_WP impleme
'post_parent' => $order->get_parent_id( 'edit' ),
'post_excerpt' => $this->get_post_excerpt( $order ),
)
), true
),
true
);
if ( $id && ! is_wp_error( $id ) ) {
@ -119,7 +123,7 @@ abstract class Abstract_WC_Order_Data_Store_CPT extends WC_Data_Store_WP impleme
* stored. @todo When meta is flattened, handle this during migration.
*/
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 ) );
$order->set_discount_total( (float) get_post_meta( $order->get_id(), '_cart_discount', true ) - (float) get_post_meta( $order->get_id(), '_cart_discount_tax', true ) );
}
}
@ -140,10 +144,12 @@ abstract class Abstract_WC_Order_Data_Store_CPT extends WC_Data_Store_WP impleme
// Only update the post when the post data changes.
if ( array_intersect( array( 'date_created', 'date_modified', 'status', 'parent_id', 'post_excerpt' ), array_keys( $changes ) ) ) {
$raw_status = $order->get_status( 'edit' ) ? $order->get_status( 'edit' ) : apply_filters( 'woocommerce_default_order_status', 'pending' );
$status = wc_is_order_status( 'wc-' . $raw_status ) ? 'wc-' . $raw_status : $raw_status;
$post_data = array(
'post_date' => gmdate( 'Y-m-d H:i:s', $order->get_date_created( 'edit' )->getOffsetTimestamp() ),
'post_date_gmt' => gmdate( 'Y-m-d H:i:s', $order->get_date_created( 'edit' )->getTimestamp() ),
'post_status' => 'wc-' . ( $order->get_status( 'edit' ) ? $order->get_status( 'edit' ) : apply_filters( 'woocommerce_default_order_status', 'pending' ) ),
'post_status' => $status,
'post_parent' => $order->get_parent_id(),
'post_excerpt' => $this->get_post_excerpt( $order ),
'post_modified' => isset( $changes['date_modified'] ) ? gmdate( 'Y-m-d H:i:s', $order->get_date_modified( 'edit' )->getOffsetTimestamp() ) : current_time( 'mysql' ),

View File

@ -156,10 +156,20 @@ class WC_Order_Data_Store_CPT extends Abstract_WC_Order_Data_Store_CPT implement
$order->set_date_paid( $order->get_date_created( 'edit' ) );
}
// Also grab the current status so we can compare.
$previous_status = get_post_status( $order->get_id() );
// Update the order.
parent::update( $order );
do_action( 'woocommerce_update_order', $order->get_id() );
// Fire a hook depending on the status - this should be considered a creation if it was previously draft status.
$new_status = $order->get_status( 'edit' );
if ( $new_status !== $previous_status && in_array( $previous_status, array( 'new', 'auto-draft', 'draft' ), true ) ) {
do_action( 'woocommerce_new_order', $order->get_id() );
} else {
do_action( 'woocommerce_update_order', $order->get_id() );
}
}
/**
@ -482,8 +492,10 @@ class WC_Order_Data_Store_CPT extends Abstract_WC_Order_Data_Store_CPT implement
* @var array
*/
$search_fields = array_map(
'wc_clean', apply_filters(
'woocommerce_shop_order_search_fields', array(
'wc_clean',
apply_filters(
'woocommerce_shop_order_search_fields',
array(
'_billing_address_index',
'_shipping_address_index',
'_billing_last_name',

View File

@ -1,15 +1,18 @@
<?php
/**
* Webhook CRUD
* @package WooCommerce\Tests\CRUD
*/
/**
* WC_Tests_CRUD_Webhooks class
*/
class WC_Tests_CRUD_Webhooks extends WC_Unit_Test_Case {
/**
* Test: get_id
*/
function test_get_id() {
public function test_get_id() {
$object = new WC_Webhook();
$id = $object->save();
$this->assertEquals( $id, $object->get_id() );
@ -19,7 +22,7 @@ class WC_Tests_CRUD_Webhooks extends WC_Unit_Test_Case {
/**
* Test: get_data
*/
function test_get_data() {
public function test_get_data() {
$object = new WC_Webhook();
$this->assertInternalType( 'array', $object->get_data() );
}
@ -27,7 +30,7 @@ class WC_Tests_CRUD_Webhooks extends WC_Unit_Test_Case {
/**
* Test: get_name
*/
function test_get_name() {
public function test_get_name() {
$object = new WC_Webhook();
$expected = 'test';
$object->set_name( $expected );
@ -37,7 +40,7 @@ class WC_Tests_CRUD_Webhooks extends WC_Unit_Test_Case {
/**
* Test: get_date_created
*/
function test_get_date_created() {
public function test_get_date_created() {
$object = new WC_Webhook();
$object->set_date_created( '2016-12-12' );
$this->assertEquals( '1481500800', $object->get_date_created()->getOffsetTimestamp() );
@ -49,7 +52,7 @@ class WC_Tests_CRUD_Webhooks extends WC_Unit_Test_Case {
/**
* Test: get_date_modified
*/
function test_get_date_modified() {
public function test_get_date_modified() {
$object = new WC_Webhook();
$object->set_date_modified( '2016-12-12' );
$this->assertEquals( '1481500800', $object->get_date_modified()->getOffsetTimestamp() );
@ -61,8 +64,8 @@ class WC_Tests_CRUD_Webhooks extends WC_Unit_Test_Case {
/**
* Test: get_status
*/
function test_get_status() {
$object = new WC_Webhook();
public function test_get_status() {
$object = new WC_Webhook();
$this->assertEquals( 'disabled', $object->get_status() );
$expected = 'active';
@ -73,7 +76,7 @@ class WC_Tests_CRUD_Webhooks extends WC_Unit_Test_Case {
/**
* Test: get_secret
*/
function test_get_secret() {
public function test_get_secret() {
$object = new WC_Webhook();
$expected = 'secret';
$object->set_secret( $expected );
@ -83,7 +86,7 @@ class WC_Tests_CRUD_Webhooks extends WC_Unit_Test_Case {
/**
* Test: get_topic
*/
function test_get_topic() {
public function test_get_topic() {
$object = new WC_Webhook();
$expected = 'order.created';
$object->set_topic( $expected );
@ -93,7 +96,7 @@ class WC_Tests_CRUD_Webhooks extends WC_Unit_Test_Case {
/**
* Test: get_delivery_url
*/
function test_get_delivery_url() {
public function test_get_delivery_url() {
$object = new WC_Webhook();
$expected = 'https://woocommerce.com';
$object->set_delivery_url( $expected );
@ -103,7 +106,7 @@ class WC_Tests_CRUD_Webhooks extends WC_Unit_Test_Case {
/**
* Test: get_user_id
*/
function test_get_user_id() {
public function test_get_user_id() {
$object = new WC_Webhook();
$expected = 1;
$object->set_user_id( $expected );
@ -113,7 +116,7 @@ class WC_Tests_CRUD_Webhooks extends WC_Unit_Test_Case {
/**
* Test: get_api_version
*/
function test_get_api_version() {
public function test_get_api_version() {
$object = new WC_Webhook();
$expected = 'wp_api_v2';
$object->set_api_version( $expected );
@ -123,7 +126,7 @@ class WC_Tests_CRUD_Webhooks extends WC_Unit_Test_Case {
/**
* Test: get_failure_count
*/
function test_get_failure_count() {
public function test_get_failure_count() {
$object = new WC_Webhook();
$expected = 1;
$object->set_failure_count( $expected );
@ -133,7 +136,7 @@ class WC_Tests_CRUD_Webhooks extends WC_Unit_Test_Case {
/**
* Test: get_pending_delivery
*/
function test_get_pending_delivery() {
public function test_get_pending_delivery() {
$object = new WC_Webhook();
$expected = true;
$object->set_pending_delivery( $expected );
@ -143,11 +146,10 @@ class WC_Tests_CRUD_Webhooks extends WC_Unit_Test_Case {
/**
* Test: get_hooks
*/
function test_get_hooks() {
public function test_get_hooks() {
$object = new WC_Webhook();
$object->set_topic( 'order.created' );
$expected = array(
'woocommerce_process_shop_order_meta',
'woocommerce_new_order',
);
$this->assertEquals( $expected, $object->get_hooks() );
@ -156,7 +158,7 @@ class WC_Tests_CRUD_Webhooks extends WC_Unit_Test_Case {
/**
* Test: get_resource
*/
function test_get_resource() {
public function test_get_resource() {
$object = new WC_Webhook();
$object->set_topic( 'order.created' );
$this->assertEquals( 'order', $object->get_resource() );
@ -165,7 +167,7 @@ class WC_Tests_CRUD_Webhooks extends WC_Unit_Test_Case {
/**
* Test: get_event
*/
function test_get_event() {
public function test_get_event() {
$object = new WC_Webhook();
$object->set_topic( 'order.created' );
$this->assertEquals( 'created', $object->get_event() );
@ -174,7 +176,7 @@ class WC_Tests_CRUD_Webhooks extends WC_Unit_Test_Case {
/**
* Test: get_i18n_status
*/
function test_get_i18n_status() {
public function test_get_i18n_status() {
$object = new WC_Webhook();
$object->set_status( 'active' );
$this->assertEquals( 'Active', $object->get_i18n_status() );
@ -183,7 +185,7 @@ class WC_Tests_CRUD_Webhooks extends WC_Unit_Test_Case {
/**
* Test: generate_signature
*/
function test_generate_signature() {
public function test_generate_signature() {
$object = new WC_Webhook();
$this->assertEquals( 'GBDo00G55h6IiV+6CxqivQPLbI//KzaOZm747971tPs=', $object->generate_signature( 'secret' ) );
}