diff --git a/includes/wc-webhook-functions.php b/includes/wc-webhook-functions.php index 933572520b8..4edaab843db 100644 --- a/includes/wc-webhook-functions.php +++ b/includes/wc-webhook-functions.php @@ -9,21 +9,40 @@ defined( 'ABSPATH' ) || exit; /** - * Process the synchronous web hooks at the end of the request. + * Process the web hooks at the end of the request. * * @since 4.4.0 */ -function wc_webhook_execute_synchronous_queue() { - global $wc_queued_sync_webhooks; - if ( empty( $wc_queued_sync_webhooks ) ) { +function wc_webhook_execute_queue() { + global $wc_queued_webhooks; + if ( empty( $wc_queued_webhooks ) ) { return; } - foreach ( $wc_queued_sync_webhooks as $data ) { - $data['webhook']->deliver( $data['arg'] ); + foreach ( $wc_queued_webhooks as $data ) { + // Webhooks are processed in the background by default + // so as to avoid delays or failures in delivery from affecting the + // user who triggered it. + if ( apply_filters( 'woocommerce_webhook_deliver_async', true, $data['webhook'], $data['arg'] ) ) { + + $queue_args = array( + 'webhook_id' => $data['webhook']->get_id(), + 'arg' => $data['arg'], + ); + + $next_scheduled_date = WC()->queue()->get_next( 'woocommerce_deliver_webhook_async', $queue_args, 'woocommerce-webhooks' ); + + // Make webhooks unique - only schedule one webhook every 10 minutes to maintain backward compatibility with WP Cron behaviour seen in WC < 3.5.0. + if ( is_null( $next_scheduled_date ) || $next_scheduled_date->getTimestamp() >= ( 600 + gmdate( 'U' ) ) ) { + WC()->queue()->add( 'woocommerce_deliver_webhook_async', $queue_args, 'woocommerce-webhooks' ); + } + } else { + // Deliver immediately. + $data['webhook']->deliver( $data['arg'] ); + } } } -register_shutdown_function( 'wc_webhook_execute_synchronous_queue' ); +register_shutdown_function( 'wc_webhook_execute_queue' ); /** * Process webhook delivery. @@ -33,34 +52,15 @@ register_shutdown_function( 'wc_webhook_execute_synchronous_queue' ); * @param array $arg Delivery arguments. */ function wc_webhook_process_delivery( $webhook, $arg ) { - // Webhooks are processed in the background by default - // so as to avoid delays or failures in delivery from affecting the - // user who triggered it. - if ( apply_filters( 'woocommerce_webhook_deliver_async', true, $webhook, $arg ) ) { - - $queue_args = array( - 'webhook_id' => $webhook->get_id(), - 'arg' => $arg, - ); - - $next_scheduled_date = WC()->queue()->get_next( 'woocommerce_deliver_webhook_async', $queue_args, 'woocommerce-webhooks' ); - - // Make webhooks unique - only schedule one webhook every 10 minutes to maintain backward compatibility with WP Cron behaviour seen in WC < 3.5.0. - if ( is_null( $next_scheduled_date ) || $next_scheduled_date->getTimestamp() >= ( 600 + gmdate( 'U' ) ) ) { - WC()->queue()->add( 'woocommerce_deliver_webhook_async', $queue_args, 'woocommerce-webhooks' ); - } - } else { - // We need to queue the webhook so that it can be ran after the request has finished processing. - // This must be done in order to keep parity with how they are executed asynchronously. - global $wc_queued_sync_webhooks; - if ( ! isset( $wc_queued_sync_webhooks ) ) { - $wc_queued_sync_webhooks = array(); - } - $wc_queued_sync_webhooks[] = array( - 'webhook' => $webhook, - 'arg' => $arg, - ); + // We need to queue the webhook so that it can be ran after the request has finished processing. + global $wc_queued_webhooks; + if ( ! isset( $wc_queued_webhooks ) ) { + $wc_queued_webhooks = array(); } + $wc_queued_webhooks[] = array( + 'webhook' => $webhook, + 'arg' => $arg, + ); } add_action( 'woocommerce_webhook_process_delivery', 'wc_webhook_process_delivery', 10, 2 ); diff --git a/tests/legacy/unit-tests/webhooks/functions.php b/tests/legacy/unit-tests/webhooks/functions.php index 35d418fceeb..521011fa5c3 100644 --- a/tests/legacy/unit-tests/webhooks/functions.php +++ b/tests/legacy/unit-tests/webhooks/functions.php @@ -221,6 +221,9 @@ class WC_Tests_Webhook_Functions extends WC_Unit_Test_Case { * will only deliver the payload once per webhook. */ public function test_woocommerce_webhook_is_delivered_only_once() { + global $wc_queued_webhooks; + $this->assertNull( $wc_queued_webhooks ); + $webhook1 = wc_get_webhook( $this->create_webhook( 'customer.created' )->get_id() ); $webhook2 = wc_get_webhook( $this->create_webhook( 'customer.created' )->get_id() ); wc_load_webhooks( 'active' ); @@ -231,6 +234,18 @@ class WC_Tests_Webhook_Functions extends WC_Unit_Test_Case { $this->assertEquals( 1, $this->delivery_counter[ $webhook2->get_id() . $customer1->get_id() ] ); $this->assertEquals( 1, $this->delivery_counter[ $webhook1->get_id() . $customer2->get_id() ] ); $this->assertEquals( 1, $this->delivery_counter[ $webhook2->get_id() . $customer2->get_id() ] ); + + $this->assertCount( 4, $wc_queued_webhooks ); + $this->assertEquals( $webhook2->get_id(), $wc_queued_webhooks[0]['webhook']->get_id() ); + $this->assertEquals( $customer1->get_id(), $wc_queued_webhooks[0]['arg'] ); + $this->assertEquals( $webhook1->get_id(), $wc_queued_webhooks[1]['webhook']->get_id() ); + $this->assertEquals( $customer1->get_id(), $wc_queued_webhooks[1]['arg'] ); + $this->assertEquals( $webhook2->get_id(), $wc_queued_webhooks[2]['webhook']->get_id() ); + $this->assertEquals( $customer2->get_id(), $wc_queued_webhooks[2]['arg'] ); + $this->assertEquals( $webhook1->get_id(), $wc_queued_webhooks[3]['webhook']->get_id() ); + $this->assertEquals( $customer2->get_id(), $wc_queued_webhooks[3]['arg'] ); + $wc_queued_webhooks = null; + $webhook1->delete( true ); $webhook2->delete( true ); $customer1->delete( true ); @@ -238,29 +253,6 @@ class WC_Tests_Webhook_Functions extends WC_Unit_Test_Case { remove_action( 'woocommerce_webhook_process_delivery', array( $this, 'woocommerce_webhook_process_delivery' ), 1, 2 ); } - /** - * Verify that a webhook is queued when intended to be delivered synchronously. This allows us to then execute them - * all in a `register_shutdown_function` after the request has processed. Since async jobs are handled in - * this way, we can be more confident that it is consistent. - */ - public function test_woocommerce_webhook_synchronous_is_queued() { - add_filter( 'woocommerce_webhook_deliver_async', '__return_false' ); - $webhook = wc_get_webhook( $this->create_webhook( 'customer.created' )->get_id() ); - wc_load_webhooks( 'active' ); - add_action( 'woocommerce_webhook_process_delivery', array( $this, 'woocommerce_webhook_process_delivery' ), 1, 2 ); - $customer = WC_Helper_Customer::create_customer( 'test1', 'pw1', 'user1@example.com' ); - - global $wc_queued_sync_webhooks; - $this->assertCount( 1, $wc_queued_sync_webhooks ); - $this->assertEquals( $webhook->get_id(), $wc_queued_sync_webhooks[0]['webhook']->get_id() ); - $this->assertEquals( $customer->get_id(), $wc_queued_sync_webhooks[0]['arg'] ); - - $wc_queued_sync_webhooks = null; - remove_filter( 'woocommerce_webhook_deliver_async', '__return_false' ); - $webhook->delete( true ); - $customer->delete( true ); - } - /** * Helper function to keep track of which webhook (and corresponding arg) has been delivered * within the current request.