2014-07-30 20:23:22 +00:00
< ? php
/**
2017-08-17 00:20:02 +00:00
* Webhook
2014-07-30 20:23:22 +00:00
*
2015-11-03 13:31:20 +00:00
* This class handles storing and retrieving webhook data from the associated .
2014-07-30 20:23:22 +00:00
*
* Webhooks are enqueued to their associated actions , delivered , and logged .
*
2017-08-17 00:20:02 +00:00
* @ version 3.2 . 0
2020-08-05 16:36:24 +00:00
* @ package WooCommerce\Webhooks
2017-08-17 00:20:02 +00:00
* @ since 2.2 . 0
2014-07-30 20:23:22 +00:00
*/
2020-01-29 05:21:29 +00:00
use Automattic\Jetpack\Constants ;
2020-10-01 08:57:12 +00:00
use Automattic\WooCommerce\Utilities\NumberUtil ;
2020-01-29 05:21:29 +00:00
2018-03-21 03:22:04 +00:00
defined ( 'ABSPATH' ) || exit ;
2014-07-30 20:23:22 +00:00
2020-08-26 20:50:34 +00:00
require_once __DIR__ . '/legacy/class-wc-legacy-webhook.php' ;
2014-07-30 20:23:22 +00:00
2017-08-17 00:20:02 +00:00
/**
* Webhook class .
*/
class WC_Webhook extends WC_Legacy_Webhook {
2014-07-30 20:23:22 +00:00
2019-12-06 01:54:24 +00:00
/**
* Store which object IDs this webhook has processed ( ie scheduled to be delivered )
* within the current page request .
*
* @ var array
*/
protected $processed = array ();
2014-07-30 20:23:22 +00:00
/**
2017-08-17 00:20:02 +00:00
* Stores webhook data .
2014-07-30 20:23:22 +00:00
*
2017-08-17 00:20:02 +00:00
* @ var array
2014-07-30 20:23:22 +00:00
*/
2017-08-17 00:20:02 +00:00
protected $data = array (
2017-08-17 00:40:31 +00:00
'date_created' => null ,
'date_modified' => null ,
2017-08-17 20:54:31 +00:00
'status' => 'disabled' ,
2017-08-17 00:40:31 +00:00
'delivery_url' => '' ,
'secret' => '' ,
'name' => '' ,
'topic' => '' ,
'hooks' => '' ,
'resource' => '' ,
'event' => '' ,
'failure_count' => 0 ,
'user_id' => 0 ,
2019-04-25 14:41:31 +00:00
'api_version' => 3 ,
2017-08-17 00:40:31 +00:00
'pending_delivery' => false ,
2017-08-17 00:20:02 +00:00
);
2014-07-30 20:23:22 +00:00
/**
2017-08-17 00:20:02 +00:00
* Load webhook data based on how WC_Webhook is called .
2014-07-30 20:23:22 +00:00
*
2017-08-17 00:20:02 +00:00
* @ param WC_Webhook | int $data Webhook ID or data .
* @ throws Exception If webhook cannot be read / found and $data is set .
2014-07-30 20:23:22 +00:00
*/
2017-08-17 00:20:02 +00:00
public function __construct ( $data = 0 ) {
parent :: __construct ( $data );
2014-07-30 20:23:22 +00:00
2017-08-17 00:20:02 +00:00
if ( $data instanceof WC_Webhook ) {
$this -> set_id ( absint ( $data -> get_id () ) );
} elseif ( is_numeric ( $data ) ) {
$this -> set_id ( $data );
2014-07-30 20:23:22 +00:00
}
2017-08-17 00:20:02 +00:00
$this -> data_store = WC_Data_Store :: load ( 'webhook' );
2014-07-30 20:23:22 +00:00
2019-01-10 18:38:58 +00:00
// If we have an ID, load the webhook from the DB.
2017-08-17 00:20:02 +00:00
if ( $this -> get_id () ) {
try {
$this -> data_store -> read ( $this );
} catch ( Exception $e ) {
$this -> set_id ( 0 );
$this -> set_object_read ( true );
}
} else {
$this -> set_object_read ( true );
}
}
2014-07-30 20:23:22 +00:00
/**
2015-11-03 13:31:20 +00:00
* Enqueue the hooks associated with the webhook .
2014-07-30 20:23:22 +00:00
*
2017-08-17 00:20:02 +00:00
* @ since 2.2 . 0
2014-07-30 20:23:22 +00:00
*/
public function enqueue () {
2014-12-17 03:44:35 +00:00
$hooks = $this -> get_hooks ();
2014-12-17 03:48:29 +00:00
$url = $this -> get_delivery_url ();
2014-12-17 03:44:35 +00:00
2014-12-17 03:48:29 +00:00
if ( is_array ( $hooks ) && ! empty ( $url ) ) {
2014-12-17 03:44:35 +00:00
foreach ( $hooks as $hook ) {
2014-12-16 22:16:38 +00:00
add_action ( $hook , array ( $this , 'process' ) );
}
2014-07-30 20:23:22 +00:00
}
}
/**
2015-11-03 13:31:20 +00:00
* Process the webhook for delivery by verifying that it should be delivered .
* and scheduling the delivery ( in the background by default , or immediately ) .
2014-07-30 20:23:22 +00:00
*
2019-01-21 16:02:28 +00:00
* @ since 2.2 . 0
* @ param mixed $arg The first argument provided from the associated hooks .
* @ return mixed $arg Returns the argument in case the webhook was hooked into a filter .
2014-07-30 20:23:22 +00:00
*/
public function process ( $arg ) {
2017-08-17 00:20:02 +00:00
// Verify that webhook should be processed for delivery.
2014-07-30 20:23:22 +00:00
if ( ! $this -> should_deliver ( $arg ) ) {
return ;
}
2019-12-06 01:54:24 +00:00
// Mark this $arg as processed to ensure it doesn't get processed again within the current request.
$this -> processed [] = $arg ;
2017-12-01 16:33:30 +00:00
/**
* Process webhook delivery .
*
* @ since 3.3 . 0
* @ hooked wc_webhook_process_delivery - 10
*/
do_action ( 'woocommerce_webhook_process_delivery' , $this , $arg );
2019-01-21 16:02:28 +00:00
return $arg ;
2014-07-30 20:23:22 +00:00
}
/**
2015-11-03 13:31:20 +00:00
* Helper to check if the webhook should be delivered , as some hooks .
2014-07-30 20:23:22 +00:00
* ( like `wp_trash_post` ) will fire for every post type , not just ours .
*
2017-08-17 00:20:02 +00:00
* @ since 2.2 . 0
* @ param mixed $arg First hook argument .
* @ return bool True if webhook should be delivered , false otherwise .
2014-07-30 20:23:22 +00:00
*/
private function should_deliver ( $arg ) {
2019-12-06 01:54:24 +00:00
$should_deliver = $this -> is_active () && $this -> is_valid_topic () && $this -> is_valid_action ( $arg ) && $this -> is_valid_resource ( $arg ) && ! $this -> is_already_processed ( $arg );
2019-02-13 16:20:09 +00:00
/**
* 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 ) {
2016-03-03 12:52:58 +00:00
$current_action = current_action ();
2019-02-13 16:20:09 +00:00
$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 ;
}
2014-07-30 20:23:22 +00:00
2019-02-13 16:20:09 +00:00
if ( 0 === strpos ( $current_action , 'woocommerce_process_shop' ) || 0 === strpos ( $current_action , 'woocommerce_process_product' ) ) {
$return = $this -> is_valid_processing_action ( $arg );
}
2014-07-30 20:23:22 +00:00
2019-02-13 16:20:09 +00:00
return $return ;
}
2014-07-30 20:23:22 +00:00
2019-02-13 16:20:09 +00:00
/**
* 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 ;
2018-03-19 23:22:39 +00:00
}
2014-07-30 20:23:22 +00:00
2019-02-13 16:20:09 +00:00
// Check if is delivering for the correct resource.
if ( isset ( $GLOBALS [ 'post_type' ] ) && str_replace ( 'shop_' , '' , $GLOBALS [ 'post_type' ] ) !== $this -> get_resource () ) {
return false ;
2018-10-29 17:29:55 +00:00
}
2019-02-13 16:20:09 +00:00
return true ;
}
2018-10-29 17:29:55 +00:00
2019-02-13 16:20:09 +00:00
/**
* 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 ;
2014-07-30 20:23:22 +00:00
}
2019-02-13 16:46:49 +00:00
/**
* 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.
2019-02-13 17:55:47 +00:00
if ( in_array ( $status , array ( 'auto-draft' , 'new' ), true ) ) {
2019-02-13 16:46:49 +00:00
return false ;
}
// Ignore standard drafts for orders.
if ( 'order' === $resource && 'draft' === $status ) {
return false ;
}
2019-04-23 08:43:49 +00:00
// Check registered order types for order types args.
if ( 'order' === $resource && ! in_array ( get_post_type ( absint ( $arg ) ), wc_get_order_types ( 'order-webhooks' ), true ) ) {
return false ;
}
2019-02-13 16:46:49 +00:00
}
return true ;
}
2019-12-06 01:54:24 +00:00
/**
* Checks if the specified resource has already been queued for delivery within the current request .
*
* Helps avoid duplication of data being sent for topics that have more than one hook defined .
*
* @ param mixed $arg First hook argument .
*
* @ return bool
*/
protected function is_already_processed ( $arg ) {
return false !== array_search ( $arg , $this -> processed , true );
}
2014-07-30 20:23:22 +00:00
/**
2015-11-03 13:31:20 +00:00
* Deliver the webhook payload using wp_safe_remote_request () .
2014-07-30 20:23:22 +00:00
*
2017-08-17 00:20:02 +00:00
* @ since 2.2 . 0
2016-04-28 17:45:13 +00:00
* @ param mixed $arg First hook argument .
2014-07-30 20:23:22 +00:00
*/
public function deliver ( $arg ) {
2017-12-01 15:48:31 +00:00
$start_time = microtime ( true );
$payload = $this -> build_payload ( $arg );
2014-07-30 20:23:22 +00:00
2016-04-28 17:45:13 +00:00
// Setup request args.
2014-07-30 20:23:22 +00:00
$http_args = array (
'method' => 'POST' ,
'timeout' => MINUTE_IN_SECONDS ,
'redirection' => 0 ,
'httpversion' => '1.0' ,
'blocking' => true ,
2020-02-01 06:18:47 +00:00
'user-agent' => sprintf ( 'WooCommerce/%s Hookshot (WordPress/%s)' , Constants :: get_constant ( 'WC_VERSION' ), $GLOBALS [ 'wp_version' ] ),
2017-08-17 00:20:02 +00:00
'body' => trim ( wp_json_encode ( $payload ) ),
'headers' => array (
'Content-Type' => 'application/json' ,
),
2014-07-30 20:23:22 +00:00
'cookies' => array (),
);
2017-08-17 00:20:02 +00:00
$http_args = apply_filters ( 'woocommerce_webhook_http_args' , $http_args , $arg , $this -> get_id () );
2014-07-30 20:23:22 +00:00
2016-04-28 17:45:13 +00:00
// Add custom headers.
2017-11-30 18:05:36 +00:00
$delivery_id = $this -> get_new_delivery_id ();
2016-04-28 17:45:13 +00:00
$http_args [ 'headers' ][ 'X-WC-Webhook-Source' ] = home_url ( '/' ); // Since 2.6.0.
2014-07-30 20:23:22 +00:00
$http_args [ 'headers' ][ 'X-WC-Webhook-Topic' ] = $this -> get_topic ();
$http_args [ 'headers' ][ 'X-WC-Webhook-Resource' ] = $this -> get_resource ();
$http_args [ 'headers' ][ 'X-WC-Webhook-Event' ] = $this -> get_event ();
$http_args [ 'headers' ][ 'X-WC-Webhook-Signature' ] = $this -> generate_signature ( $http_args [ 'body' ] );
2017-08-17 00:20:02 +00:00
$http_args [ 'headers' ][ 'X-WC-Webhook-ID' ] = $this -> get_id ();
$http_args [ 'headers' ][ 'X-WC-Webhook-Delivery-ID' ] = $delivery_id ;
2014-07-30 20:23:22 +00:00
2016-04-28 17:45:13 +00:00
// Webhook away!
2015-05-20 16:03:37 +00:00
$response = wp_safe_remote_request ( $this -> get_delivery_url (), $http_args );
2014-07-30 20:23:22 +00:00
2020-10-01 08:57:12 +00:00
$duration = NumberUtil :: round ( microtime ( true ) - $start_time , 5 );
2014-07-30 20:23:22 +00:00
$this -> log_delivery ( $delivery_id , $http_args , $response , $duration );
2017-08-17 00:20:02 +00:00
do_action ( 'woocommerce_webhook_delivery' , $http_args , $response , $duration , $arg , $this -> get_id () );
2014-07-30 20:23:22 +00:00
}
2016-11-23 00:59:55 +00:00
/**
* Get Legacy API payload .
*
2017-03-15 16:36:53 +00:00
* @ since 3.0 . 0
2016-11-23 00:59:55 +00:00
* @ param string $resource Resource type .
* @ param int $resource_id Resource ID .
* @ param string $event Event type .
* @ return array
*/
private function get_legacy_api_payload ( $resource , $resource_id , $event ) {
// Include & load API classes.
WC () -> api -> includes ();
WC () -> api -> register_resources ( new WC_API_Server ( '/' ) );
switch ( $resource ) {
2017-11-08 12:46:23 +00:00
case 'coupon' :
2016-11-23 00:59:55 +00:00
$payload = WC () -> api -> WC_API_Coupons -> get_coupon ( $resource_id );
break ;
2017-11-08 12:46:23 +00:00
case 'customer' :
2016-11-23 00:59:55 +00:00
$payload = WC () -> api -> WC_API_Customers -> get_customer ( $resource_id );
break ;
2017-11-08 12:46:23 +00:00
case 'order' :
2016-11-23 00:59:55 +00:00
$payload = WC () -> api -> WC_API_Orders -> get_order ( $resource_id , null , apply_filters ( 'woocommerce_webhook_order_payload_filters' , array () ) );
break ;
2017-11-08 12:46:23 +00:00
case 'product' :
2016-11-23 00:59:55 +00:00
// Bulk and quick edit action hooks return a product object instead of an ID.
if ( 'updated' === $event && is_a ( $resource_id , 'WC_Product' ) ) {
$resource_id = $resource_id -> get_id ();
}
$payload = WC () -> api -> WC_API_Products -> get_product ( $resource_id );
break ;
// Custom topics include the first hook argument.
2017-11-08 12:46:23 +00:00
case 'action' :
2016-11-23 00:59:55 +00:00
$payload = array (
'action' => current ( $this -> get_hooks () ),
'arg' => $resource_id ,
);
break ;
2017-11-08 12:46:23 +00:00
default :
2016-11-23 00:59:55 +00:00
$payload = array ();
break ;
}
return $payload ;
}
/**
* Get WP API integration payload .
*
2017-03-15 16:36:53 +00:00
* @ since 3.0 . 0
2016-11-23 00:59:55 +00:00
* @ param string $resource Resource type .
* @ param int $resource_id Resource ID .
* @ param string $event Event type .
* @ return array
*/
private function get_wp_api_payload ( $resource , $resource_id , $event ) {
switch ( $resource ) {
2017-11-08 12:46:23 +00:00
case 'coupon' :
case 'customer' :
case 'order' :
case 'product' :
2016-11-23 00:59:55 +00:00
// Bulk and quick edit action hooks return a product object instead of an ID.
if ( 'product' === $resource && 'updated' === $event && is_a ( $resource_id , 'WC_Product' ) ) {
$resource_id = $resource_id -> get_id ();
}
2019-06-21 12:59:32 +00:00
$version = str_replace ( 'wp_api_' , '' , $this -> get_api_version () );
$payload = wc () -> api -> get_endpoint_data ( " /wc/ { $version } / { $resource } s/ { $resource_id } " );
2016-11-23 00:59:55 +00:00
break ;
// Custom topics include the first hook argument.
2017-11-08 12:46:23 +00:00
case 'action' :
2016-11-23 00:59:55 +00:00
$payload = array (
'action' => current ( $this -> get_hooks () ),
'arg' => $resource_id ,
);
break ;
2017-11-08 12:46:23 +00:00
default :
2016-11-23 00:59:55 +00:00
$payload = array ();
break ;
}
return $payload ;
}
2014-07-30 20:23:22 +00:00
/**
2015-11-03 13:31:20 +00:00
* Build the payload data for the webhook .
2014-07-30 20:23:22 +00:00
*
2017-08-17 00:20:02 +00:00
* @ since 2.2 . 0
* @ param mixed $resource_id First hook argument , typically the resource ID .
* @ return mixed Payload data .
2014-07-30 20:23:22 +00:00
*/
2017-04-24 11:22:47 +00:00
public function build_payload ( $resource_id ) {
2017-08-17 00:20:02 +00:00
// Build the payload with the same user context as the user who created
2014-07-30 20:23:22 +00:00
// the webhook -- this avoids permission errors as background processing
2017-08-17 00:20:02 +00:00
// runs with no user context.
2014-07-30 20:23:22 +00:00
$current_user = get_current_user_id ();
wp_set_current_user ( $this -> get_user_id () );
$resource = $this -> get_resource ();
$event = $this -> get_event ();
2016-11-23 00:59:55 +00:00
// If a resource has been deleted, just include the ID.
2017-08-17 00:20:02 +00:00
if ( 'deleted' === $event ) {
2014-07-30 20:23:22 +00:00
$payload = array (
'id' => $resource_id ,
);
} else {
2018-10-29 22:31:47 +00:00
if ( in_array ( $this -> get_api_version (), wc_get_webhook_rest_api_versions (), true ) ) {
2016-11-23 00:59:55 +00:00
$payload = $this -> get_wp_api_payload ( $resource , $resource_id , $event );
} else {
$payload = $this -> get_legacy_api_payload ( $resource , $resource_id , $event );
2014-07-30 20:23:22 +00:00
}
}
2016-11-23 00:59:55 +00:00
// Restore the current user.
2014-07-30 20:23:22 +00:00
wp_set_current_user ( $current_user );
2017-08-17 00:20:02 +00:00
return apply_filters ( 'woocommerce_webhook_payload' , $payload , $resource , $resource_id , $this -> get_id () );
2014-07-30 20:23:22 +00:00
}
/**
2020-10-01 08:53:38 +00:00
* Generate a base64 - encoded HMAC - SHA256 signature of the payload body so the
* recipient can verify the authenticity of the webhook . Note that the signature
2015-11-03 13:31:20 +00:00
* is calculated after the body has already been encoded ( JSON by default ) .
2014-07-30 20:23:22 +00:00
*
2017-08-17 00:20:02 +00:00
* @ since 2.2 . 0
* @ param string $payload Payload data to hash .
* @ return string
2014-07-30 20:23:22 +00:00
*/
public function generate_signature ( $payload ) {
2017-08-17 00:20:02 +00:00
$hash_algo = apply_filters ( 'woocommerce_webhook_hash_algorithm' , 'sha256' , $payload , $this -> get_id () );
2014-07-30 20:23:22 +00:00
2020-10-01 08:53:38 +00:00
// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
2019-02-12 18:21:29 +00:00
return base64_encode ( hash_hmac ( $hash_algo , $payload , wp_specialchars_decode ( $this -> get_secret (), ENT_QUOTES ), true ) );
2014-07-30 20:23:22 +00:00
}
/**
2017-11-10 10:20:59 +00:00
* Generate a new unique hash as a delivery id based on current time and wehbook id .
* Return the hash for inclusion in the webhook request .
2014-07-30 20:23:22 +00:00
*
2017-08-17 00:20:02 +00:00
* @ since 2.2 . 0
2017-11-10 10:20:59 +00:00
* @ return string
2014-07-30 20:23:22 +00:00
*/
public function get_new_delivery_id () {
2017-11-10 10:20:59 +00:00
// Since we no longer use comments to store delivery logs, we generate a unique hash instead based on current time and webhook ID.
return wp_hash ( $this -> get_id () . strtotime ( 'now' ) );
2014-07-30 20:23:22 +00:00
}
/**
2015-11-03 13:31:20 +00:00
* Log the delivery request / response .
2014-07-30 20:23:22 +00:00
*
2017-08-17 00:20:02 +00:00
* @ since 2.2 . 0
2017-11-10 10:20:59 +00:00
* @ param string $delivery_id Previously created hash .
2017-08-17 00:20:02 +00:00
* @ param array $request Request data .
* @ param array | WP_Error $response Response data .
* @ param float $duration Request duration .
2014-07-30 20:23:22 +00:00
*/
public function log_delivery ( $delivery_id , $request , $response , $duration ) {
2017-11-30 18:05:36 +00:00
$logger = wc_get_logger ();
2017-11-10 10:20:59 +00:00
$message = array (
'Webhook Delivery' => array (
'Delivery ID' => $delivery_id ,
'Date' => date_i18n ( __ ( 'M j, Y @ G:i' , 'woocommerce' ), strtotime ( 'now' ), true ),
2017-11-17 07:03:32 +00:00
'URL' => $this -> get_delivery_url (),
2017-11-10 10:20:59 +00:00
'Duration' => $duration ,
'Request' => array (
'Method' => $request [ 'method' ],
2017-11-20 06:11:10 +00:00
'Headers' => array_merge (
array (
'User-Agent' => $request [ 'user-agent' ],
),
$request [ 'headers' ]
),
2017-11-10 10:20:59 +00:00
),
2017-11-30 18:05:36 +00:00
'Body' => wp_slash ( $request [ 'body' ] ),
2017-11-20 06:11:10 +00:00
),
2017-11-10 10:20:59 +00:00
);
2014-07-30 20:23:22 +00:00
2017-08-17 00:20:02 +00:00
// Parse response.
2014-07-30 20:23:22 +00:00
if ( is_wp_error ( $response ) ) {
$response_code = $response -> get_error_code ();
$response_message = $response -> get_error_message ();
2016-07-25 11:52:20 +00:00
$response_headers = array ();
$response_body = '' ;
2014-07-30 20:23:22 +00:00
} else {
$response_code = wp_remote_retrieve_response_code ( $response );
$response_message = wp_remote_retrieve_response_message ( $response );
$response_headers = wp_remote_retrieve_headers ( $response );
$response_body = wp_remote_retrieve_body ( $response );
}
2018-04-30 15:24:23 +00:00
$message [ 'Webhook Delivery' ][ 'Response' ] = array (
'Code' => $response_code ,
'Message' => $response_message ,
'Headers' => $response_headers ,
'Body' => $response_body ,
);
2020-01-29 05:21:29 +00:00
if ( ! Constants :: is_true ( 'WP_DEBUG' ) ) {
2018-04-30 15:24:23 +00:00
$message [ 'Webhook Delivery' ][ 'Body' ] = 'Webhook body is not logged unless WP_DEBUG mode is turned on. This is to avoid the storing of personal data in the logs.' ;
$message [ 'Webhook Delivery' ][ 'Response' ][ 'Body' ] = 'Webhook body is not logged unless WP_DEBUG mode is turned on. This is to avoid the storing of personal data in the logs.' ;
}
2017-11-30 18:05:36 +00:00
$logger -> info (
2019-01-10 18:38:26 +00:00
wc_print_r ( $message , true ),
array (
2017-11-30 18:05:36 +00:00
'source' => 'webhooks-delivery' ,
)
);
2014-07-30 20:23:22 +00:00
2017-08-17 00:20:02 +00:00
// Track failures.
2018-10-03 20:38:53 +00:00
// Check for a success, which is a 2xx, 301 or 302 Response Code.
2018-10-03 15:40:49 +00:00
if ( intval ( $response_code ) >= 200 && intval ( $response_code ) < 303 ) {
2017-12-01 14:08:13 +00:00
$this -> set_failure_count ( 0 );
$this -> save ();
2014-07-30 20:23:22 +00:00
} else {
$this -> failed_delivery ();
}
}
/**
2015-11-03 13:31:20 +00:00
* Track consecutive delivery failures and automatically disable the webhook .
* if more than 5 consecutive failures occur . A failure is defined as a .
* non - 2 xx response .
2014-07-30 20:23:22 +00:00
*
2017-08-17 00:20:02 +00:00
* @ since 2.2 . 0
2014-07-30 20:23:22 +00:00
*/
private function failed_delivery () {
$failures = $this -> get_failure_count ();
if ( $failures > apply_filters ( 'woocommerce_max_webhook_delivery_failures' , 5 ) ) {
2017-12-01 14:08:13 +00:00
$this -> set_status ( 'disabled' );
do_action ( 'woocommerce_webhook_disabled_due_delivery_failures' , $this -> get_id () );
2014-07-30 20:23:22 +00:00
} else {
2017-12-01 14:08:13 +00:00
$this -> set_failure_count ( ++ $failures );
2014-07-30 20:23:22 +00:00
}
2017-12-01 14:08:13 +00:00
$this -> save ();
2014-07-30 20:23:22 +00:00
}
/**
2015-11-03 13:31:20 +00:00
* Get the delivery logs for this webhook .
2014-07-30 20:23:22 +00:00
*
2017-11-30 18:20:20 +00:00
* @ since 3.3 . 0
* @ return string
2014-07-30 20:23:22 +00:00
*/
public function get_delivery_logs () {
2017-11-17 07:41:14 +00:00
return esc_url ( add_query_arg ( 'log_file' , wc_get_log_file_name ( 'webhooks-delivery' ), admin_url ( 'admin.php?page=wc-status&tab=logs' ) ) );
2014-07-30 20:23:22 +00:00
}
/**
* Get the delivery log specified by the ID . The delivery log includes :
*
* + duration
* + summary
* + request method / url
* + request headers / body
* + response code / message / headers / body
*
* @ since 2.2
2017-11-20 06:13:55 +00:00
* @ deprecated 3.3 . 0
2017-08-17 00:20:02 +00:00
* @ param int $delivery_id Delivery ID .
2017-11-17 12:43:44 +00:00
* @ return void
2014-07-30 20:23:22 +00:00
*/
public function get_delivery_log ( $delivery_id ) {
2017-11-17 12:43:44 +00:00
wc_deprecated_function ( 'WC_Webhook::get_delivery_log' , '3.3' );
2014-07-30 20:23:22 +00:00
}
/**
2015-11-03 13:31:20 +00:00
* Send a test ping to the delivery URL , sent when the webhook is first created .
2014-07-30 20:23:22 +00:00
*
2017-08-17 00:20:02 +00:00
* @ since 2.2 . 0
2015-12-04 12:47:30 +00:00
* @ return bool | WP_Error
2014-07-30 20:23:22 +00:00
*/
public function deliver_ping () {
$args = array (
2020-02-01 06:18:47 +00:00
'user-agent' => sprintf ( 'WooCommerce/%s Hookshot (WordPress/%s)' , Constants :: get_constant ( 'WC_VERSION' ), $GLOBALS [ 'wp_version' ] ),
2017-08-17 00:20:02 +00:00
'body' => 'webhook_id=' . $this -> get_id (),
2014-07-30 20:23:22 +00:00
);
2015-12-04 12:47:30 +00:00
$test = wp_safe_remote_post ( $this -> get_delivery_url (), $args );
$response_code = wp_remote_retrieve_response_code ( $test );
if ( is_wp_error ( $test ) ) {
2017-08-17 00:20:02 +00:00
/* translators: error message */
2015-12-09 15:20:53 +00:00
return new WP_Error ( 'error' , sprintf ( __ ( 'Error: Delivery URL cannot be reached: %s' , 'woocommerce' ), $test -> get_error_message () ) );
2015-12-04 12:47:30 +00:00
}
if ( 200 !== $response_code ) {
2017-08-17 00:20:02 +00:00
/* translators: error message */
2015-12-09 15:20:53 +00:00
return new WP_Error ( 'error' , sprintf ( __ ( 'Error: Delivery URL returned response code: %s' , 'woocommerce' ), absint ( $response_code ) ) );
2015-12-04 12:47:30 +00:00
}
2017-11-30 18:48:16 +00:00
$this -> set_pending_delivery ( false );
$this -> save ();
2017-06-16 12:56:50 +00:00
2015-12-04 12:47:30 +00:00
return true ;
2014-07-30 20:23:22 +00:00
}
2017-08-17 00:20:02 +00:00
/*
2017-11-30 18:05:36 +00:00
|--------------------------------------------------------------------------
| Getters
|--------------------------------------------------------------------------
*/
2017-08-17 00:20:02 +00:00
/**
* Get the friendly name for the webhook .
*
* @ since 2.2 . 0
* @ param string $context What the value is for .
* Valid values are 'view' and 'edit' .
* @ return string
*/
public function get_name ( $context = 'view' ) {
return apply_filters ( 'woocommerce_webhook_name' , $this -> get_prop ( 'name' , $context ), $this -> get_id () );
}
2014-07-30 20:23:22 +00:00
/**
2017-08-17 00:20:02 +00:00
* Get the webhook status .
2014-07-30 20:23:22 +00:00
*
2017-08-17 00:20:02 +00:00
* - 'active' - delivers payload .
* - 'paused' - does not deliver payload , paused by admin .
* - 'disabled' - does not delivery payload , paused automatically due to consecutive failures .
2014-07-30 20:23:22 +00:00
*
2017-08-17 00:20:02 +00:00
* @ since 2.2 . 0
* @ param string $context What the value is for .
* Valid values are 'view' and 'edit' .
2014-07-30 20:23:22 +00:00
* @ return string status
*/
2017-08-17 00:20:02 +00:00
public function get_status ( $context = 'view' ) {
return apply_filters ( 'woocommerce_webhook_status' , $this -> get_prop ( 'status' , $context ), $this -> get_id () );
2014-07-30 20:23:22 +00:00
}
2017-08-17 00:40:31 +00:00
/**
2019-08-20 06:43:50 +00:00
* Get webhook created date .
2017-08-17 00:40:31 +00:00
*
* @ since 3.2 . 0
* @ param string $context What the value is for .
* Valid values are 'view' and 'edit' .
* @ return WC_DateTime | null Object if the date is set or null if there is no date .
*/
public function get_date_created ( $context = 'view' ) {
return $this -> get_prop ( 'date_created' , $context );
}
/**
2019-08-20 06:43:50 +00:00
* Get webhook modified date .
2017-08-17 00:40:31 +00:00
*
* @ since 3.2 . 0
* @ param string $context What the value is for .
* Valid values are 'view' and 'edit' .
* @ return WC_DateTime | null Object if the date is set or null if there is no date .
*/
public function get_date_modified ( $context = 'view' ) {
return $this -> get_prop ( 'date_modified' , $context );
}
2014-12-16 17:29:37 +00:00
/**
2017-08-17 00:20:02 +00:00
* Get the secret used for generating the HMAC - SHA256 signature .
2014-12-16 17:29:37 +00:00
*
2017-08-17 00:20:02 +00:00
* @ since 2.2 . 0
* @ param string $context What the value is for .
* Valid values are 'view' and 'edit' .
2014-12-16 17:29:37 +00:00
* @ return string
*/
2017-08-17 00:20:02 +00:00
public function get_secret ( $context = 'view' ) {
return apply_filters ( 'woocommerce_webhook_secret' , $this -> get_prop ( 'secret' , $context ), $this -> get_id () );
2014-12-16 17:29:37 +00:00
}
2014-07-30 20:23:22 +00:00
/**
2017-08-17 00:20:02 +00:00
* Get the webhook topic , e . g . `order.created` .
2014-07-30 20:23:22 +00:00
*
2017-08-17 00:20:02 +00:00
* @ since 2.2 . 0
* @ param string $context What the value is for .
* Valid values are 'view' and 'edit' .
* @ return string
2014-07-30 20:23:22 +00:00
*/
2017-08-17 00:20:02 +00:00
public function get_topic ( $context = 'view' ) {
return apply_filters ( 'woocommerce_webhook_topic' , $this -> get_prop ( 'topic' , $context ), $this -> get_id () );
}
2014-07-30 20:23:22 +00:00
2017-08-17 00:20:02 +00:00
/**
* Get the delivery URL .
*
* @ since 2.2 . 0
* @ param string $context What the value is for .
* Valid values are 'view' and 'edit' .
* @ return string
*/
public function get_delivery_url ( $context = 'view' ) {
return apply_filters ( 'woocommerce_webhook_delivery_url' , $this -> get_prop ( 'delivery_url' , $context ), $this -> get_id () );
}
2014-07-30 20:23:22 +00:00
2017-08-17 00:20:02 +00:00
/**
* Get the user ID for this webhook .
*
* @ since 2.2 . 0
* @ param string $context What the value is for .
* Valid values are 'view' and 'edit' .
* @ return int
*/
public function get_user_id ( $context = 'view' ) {
return $this -> get_prop ( 'user_id' , $context );
}
2014-07-30 20:23:22 +00:00
2017-08-17 00:20:02 +00:00
/**
* API version .
*
* @ since 3.0 . 0
* @ param string $context What the value is for .
* Valid values are 'view' and 'edit' .
* @ return string
*/
public function get_api_version ( $context = 'view' ) {
$version = $this -> get_prop ( 'api_version' , $context );
2014-07-30 20:23:22 +00:00
2017-08-17 00:20:02 +00:00
return 0 < $version ? 'wp_api_v' . $version : 'legacy_v3' ;
}
2014-07-30 20:23:22 +00:00
2017-08-17 00:20:02 +00:00
/**
* Get the failure count .
*
* @ since 2.2 . 0
* @ param string $context What the value is for .
* Valid values are 'view' and 'edit' .
* @ return int
*/
public function get_failure_count ( $context = 'view' ) {
return $this -> get_prop ( 'failure_count' , $context );
2014-07-30 20:23:22 +00:00
}
/**
2017-08-17 00:20:02 +00:00
* Get pending delivery .
2014-07-30 20:23:22 +00:00
*
2017-08-17 00:20:02 +00:00
* @ since 3.2 . 0
* @ param string $context What the value is for .
* Valid values are 'view' and 'edit' .
* @ return bool
2014-07-30 20:23:22 +00:00
*/
2017-08-17 00:20:02 +00:00
public function get_pending_delivery ( $context = 'view' ) {
return $this -> get_prop ( 'pending_delivery' , $context );
2014-07-30 20:23:22 +00:00
}
2017-08-17 00:20:02 +00:00
/*
2017-11-30 18:05:36 +00:00
|--------------------------------------------------------------------------
| Setters
|--------------------------------------------------------------------------
2017-08-17 00:20:02 +00:00
*/
2014-07-30 20:23:22 +00:00
/**
2017-08-17 00:20:02 +00:00
* Set webhook name .
2014-07-30 20:23:22 +00:00
*
2017-08-17 00:20:02 +00:00
* @ since 3.2 . 0
* @ param string $name Webhook name .
2014-07-30 20:23:22 +00:00
*/
2017-08-17 00:20:02 +00:00
public function set_name ( $name ) {
$this -> set_prop ( 'name' , $name );
}
2014-07-30 20:23:22 +00:00
2017-08-17 00:40:31 +00:00
/**
2017-08-17 03:08:32 +00:00
* Set webhook created date .
2017-08-17 00:40:31 +00:00
*
* @ since 3.2 . 0
* @ param string | integer | null $date UTC timestamp , or ISO 8601 DateTime .
* If the DateTime string has no timezone or offset ,
* WordPress site timezone will be assumed .
* Null if their is no date .
*/
public function set_date_created ( $date = null ) {
$this -> set_date_prop ( 'date_created' , $date );
}
/**
2017-08-17 03:08:32 +00:00
* Set webhook modified date .
2017-08-17 00:40:31 +00:00
*
* @ since 3.2 . 0
* @ param string | integer | null $date UTC timestamp , or ISO 8601 DateTime .
* If the DateTime string has no timezone or offset ,
* WordPress site timezone will be assumed .
* Null if their is no date .
*/
public function set_date_modified ( $date = null ) {
$this -> set_date_prop ( 'date_modified' , $date );
}
2017-08-17 00:20:02 +00:00
/**
* Set status .
*
* @ since 3.2 . 0
* @ param string $status Status .
*/
public function set_status ( $status ) {
2017-08-17 21:36:42 +00:00
if ( ! array_key_exists ( $status , wc_get_webhook_statuses () ) ) {
2017-08-17 20:54:31 +00:00
$status = 'disabled' ;
}
2017-08-17 00:20:02 +00:00
$this -> set_prop ( 'status' , $status );
2014-07-30 20:23:22 +00:00
}
/**
2015-11-03 13:31:20 +00:00
* Set the secret used for generating the HMAC - SHA256 signature .
2014-07-30 20:23:22 +00:00
*
2017-08-17 00:20:02 +00:00
* @ since 2.2 . 0
* @ param string $secret Secret .
2014-07-30 20:23:22 +00:00
*/
public function set_secret ( $secret ) {
2017-08-17 00:20:02 +00:00
$this -> set_prop ( 'secret' , $secret );
2014-07-30 20:23:22 +00:00
}
/**
2017-08-17 00:20:02 +00:00
* Set the webhook topic and associated hooks .
* The topic resource & event are also saved separately .
2014-07-30 20:23:22 +00:00
*
2017-08-17 00:20:02 +00:00
* @ since 2.2 . 0
* @ param string $topic Webhook topic .
2014-07-30 20:23:22 +00:00
*/
2017-08-17 00:20:02 +00:00
public function set_topic ( $topic ) {
2017-08-17 20:57:09 +00:00
$topic = wc_clean ( $topic );
2017-08-17 21:36:42 +00:00
if ( ! wc_is_webhook_valid_topic ( $topic ) ) {
2017-08-17 20:57:09 +00:00
$topic = '' ;
}
2017-08-17 00:20:02 +00:00
$this -> set_prop ( 'topic' , $topic );
2014-07-30 20:23:22 +00:00
}
/**
2017-08-17 00:20:02 +00:00
* Set the delivery URL .
2014-07-30 20:23:22 +00:00
*
2017-08-17 00:20:02 +00:00
* @ since 2.2 . 0
* @ param string $url Delivery URL .
2014-07-30 20:23:22 +00:00
*/
2017-08-17 00:20:02 +00:00
public function set_delivery_url ( $url ) {
$this -> set_prop ( 'delivery_url' , esc_url_raw ( $url , array ( 'http' , 'https' ) ) );
2014-07-30 20:23:22 +00:00
}
/**
2017-08-17 00:20:02 +00:00
* Set user ID .
2014-07-30 20:23:22 +00:00
*
2017-08-17 00:20:02 +00:00
* @ since 3.2 . 0
* @ param int $user_id User ID .
2014-07-30 20:23:22 +00:00
*/
2017-08-17 00:20:02 +00:00
public function set_user_id ( $user_id ) {
$this -> set_prop ( 'user_id' , ( int ) $user_id );
2014-07-30 20:23:22 +00:00
}
/**
2017-08-17 00:20:02 +00:00
* Set API version .
2014-07-30 20:23:22 +00:00
*
2017-08-17 00:20:02 +00:00
* @ since 3.0 . 0
2017-08-17 15:18:32 +00:00
* @ param int | string $version REST API version .
2014-07-30 20:23:22 +00:00
*/
2017-08-17 00:20:02 +00:00
public function set_api_version ( $version ) {
2017-08-17 15:18:32 +00:00
if ( ! is_numeric ( $version ) ) {
$version = $this -> data_store -> get_api_version_number ( $version );
}
$this -> set_prop ( 'api_version' , ( int ) $version );
2014-07-30 20:23:22 +00:00
}
/**
2017-08-17 00:20:02 +00:00
* Set pending delivery .
2014-07-30 20:23:22 +00:00
*
2017-08-17 00:20:02 +00:00
* @ since 3.2 . 0
* @ param bool $pending_delivery Set true if is pending for delivery .
2014-07-30 20:23:22 +00:00
*/
2017-08-17 00:20:02 +00:00
public function set_pending_delivery ( $pending_delivery ) {
$this -> set_prop ( 'pending_delivery' , ( bool ) $pending_delivery );
2014-07-30 20:23:22 +00:00
}
/**
2017-08-17 00:20:02 +00:00
* Set failure count .
2014-07-30 20:23:22 +00:00
*
2017-08-17 00:20:02 +00:00
* @ since 3.2 . 0
* @ param bool $failure_count Total of failures .
2014-07-30 20:23:22 +00:00
*/
2017-08-17 00:20:02 +00:00
public function set_failure_count ( $failure_count ) {
$this -> set_prop ( 'failure_count' , intval ( $failure_count ) );
2014-07-30 20:23:22 +00:00
}
2017-08-17 00:20:02 +00:00
/*
2017-11-30 18:05:36 +00:00
|--------------------------------------------------------------------------
| Non - CRUD Getters
|--------------------------------------------------------------------------
*/
2017-08-17 00:20:02 +00:00
2014-07-30 20:23:22 +00:00
/**
2017-08-17 00:20:02 +00:00
* Get the associated hook names for a topic .
2014-07-30 20:23:22 +00:00
*
2017-08-17 00:20:02 +00:00
* @ since 2.2 . 0
* @ param string $topic Topic name .
* @ return array
2014-07-30 20:23:22 +00:00
*/
2017-08-17 00:20:02 +00:00
private function get_topic_hooks ( $topic ) {
$topic_hooks = array (
2017-11-30 18:05:36 +00:00
'coupon.created' => array (
2017-08-17 00:20:02 +00:00
'woocommerce_process_shop_coupon_meta' ,
'woocommerce_new_coupon' ,
),
2017-11-30 18:05:36 +00:00
'coupon.updated' => array (
2017-08-17 00:20:02 +00:00
'woocommerce_process_shop_coupon_meta' ,
'woocommerce_update_coupon' ,
),
2017-11-30 18:05:36 +00:00
'coupon.deleted' => array (
2017-08-17 00:20:02 +00:00
'wp_trash_post' ,
),
2017-11-30 18:05:36 +00:00
'coupon.restored' => array (
2017-08-17 00:20:02 +00:00
'untrashed_post' ,
),
'customer.created' => array (
'user_register' ,
'woocommerce_created_customer' ,
'woocommerce_new_customer' ,
),
'customer.updated' => array (
'profile_update' ,
'woocommerce_update_customer' ,
),
'customer.deleted' => array (
'delete_user' ,
),
'order.created' => array (
'woocommerce_new_order' ,
),
2017-11-30 18:05:36 +00:00
'order.updated' => array (
2017-08-17 00:20:02 +00:00
'woocommerce_update_order' ,
2018-11-28 18:32:36 +00:00
'woocommerce_order_refunded' ,
2017-08-17 00:20:02 +00:00
),
2017-11-30 18:05:36 +00:00
'order.deleted' => array (
2017-08-17 00:20:02 +00:00
'wp_trash_post' ,
),
2017-11-30 18:05:36 +00:00
'order.restored' => array (
2017-08-17 00:20:02 +00:00
'untrashed_post' ,
),
2017-11-30 18:05:36 +00:00
'product.created' => array (
2017-08-17 00:20:02 +00:00
'woocommerce_process_product_meta' ,
'woocommerce_new_product' ,
'woocommerce_new_product_variation' ,
),
2017-11-30 18:05:36 +00:00
'product.updated' => array (
2017-08-17 00:20:02 +00:00
'woocommerce_process_product_meta' ,
'woocommerce_update_product' ,
'woocommerce_update_product_variation' ,
),
2017-11-30 18:05:36 +00:00
'product.deleted' => array (
2017-08-17 00:20:02 +00:00
'wp_trash_post' ,
),
'product.restored' => array (
'untrashed_post' ,
),
);
$topic_hooks = apply_filters ( 'woocommerce_webhook_topic_hooks' , $topic_hooks , $this );
return isset ( $topic_hooks [ $topic ] ) ? $topic_hooks [ $topic ] : array ();
2014-07-30 20:23:22 +00:00
}
/**
2017-08-17 00:20:02 +00:00
* Get the hook names for the webhook .
2014-07-30 20:23:22 +00:00
*
2017-08-17 00:20:02 +00:00
* @ since 2.2 . 0
* @ return array
2014-07-30 20:23:22 +00:00
*/
2017-08-17 00:20:02 +00:00
public function get_hooks () {
if ( 'action' === $this -> get_resource () ) {
$hooks = array ( $this -> get_event () );
} else {
$hooks = $this -> get_topic_hooks ( $this -> get_topic () );
}
return apply_filters ( 'woocommerce_webhook_hooks' , $hooks , $this -> get_id () );
2014-07-30 20:23:22 +00:00
}
/**
2017-08-17 00:20:02 +00:00
* Get the resource for the webhook , e . g . `order` .
2014-07-30 20:23:22 +00:00
*
2017-08-17 00:20:02 +00:00
* @ since 2.2 . 0
* @ return string
2014-07-30 20:23:22 +00:00
*/
2017-08-17 00:20:02 +00:00
public function get_resource () {
$topic = explode ( '.' , $this -> get_topic () );
return apply_filters ( 'woocommerce_webhook_resource' , $topic [ 0 ], $this -> get_id () );
2014-07-30 20:23:22 +00:00
}
2016-11-22 23:49:04 +00:00
/**
2017-08-17 00:20:02 +00:00
* Get the event for the webhook , e . g . `created` .
2016-11-22 23:49:04 +00:00
*
2017-08-17 00:20:02 +00:00
* @ since 2.2 . 0
* @ return string
2016-11-22 23:49:04 +00:00
*/
2017-08-17 00:20:02 +00:00
public function get_event () {
$topic = explode ( '.' , $this -> get_topic () );
2016-11-22 23:49:04 +00:00
2017-08-17 00:20:02 +00:00
return apply_filters ( 'woocommerce_webhook_event' , isset ( $topic [ 1 ] ) ? $topic [ 1 ] : '' , $this -> get_id () );
2016-11-22 23:49:04 +00:00
}
/**
2017-08-17 00:20:02 +00:00
* Get the webhook i18n status .
2016-11-22 23:49:04 +00:00
*
* @ return string
*/
2017-08-17 00:20:02 +00:00
public function get_i18n_status () {
$status = $this -> get_status ();
$statuses = wc_get_webhook_statuses ();
return isset ( $statuses [ $status ] ) ? $statuses [ $status ] : $status ;
2016-11-22 23:49:04 +00:00
}
2014-07-30 20:23:22 +00:00
}