WC_Webhook

WooCommerce Webhook class.

Defined (1)

The class is defined in the following location(s).

/includes/class-wc-webhook.php  
  1. class WC_Webhook { 
  2.  
  3. /** @var int webhook ID (post ID) */ 
  4. public $id; 
  5.  
  6. /** 
  7. * Setup webhook & load post data. 
  8. * @since 2.2 
  9. * @param string|int $id 
  10. */ 
  11. public function __construct( $id ) { 
  12.  
  13. $id = absint( $id ); 
  14.  
  15. if ( ! $id ) { 
  16. return; 
  17.  
  18. $this->id = $id; 
  19. $this->post_data = get_post( $id ); 
  20.  
  21.  
  22. /** 
  23. * Magic isset as a wrapper around metadata_exists(). 
  24. * @since 2.2 
  25. * @param string $key 
  26. * @return bool true if $key isset, false otherwise 
  27. */ 
  28. public function __isset( $key ) { 
  29. if ( ! $this->id ) { 
  30. return false; 
  31. return metadata_exists( 'post', $this->id, '_' . $key ); 
  32.  
  33.  
  34. /** 
  35. * Magic get, wraps get_post_meta() for all keys except $status. 
  36. * @since 2.2 
  37. * @param string $key 
  38. * @return mixed value 
  39. */ 
  40. public function __get( $key ) { 
  41.  
  42. if ( 'status' === $key ) { 
  43. $value = $this->get_status(); 
  44. } else { 
  45. $value = get_post_meta( $this->id, '_' . $key, true ); 
  46.  
  47. return $value; 
  48.  
  49.  
  50. /** 
  51. * Enqueue the hooks associated with the webhook. 
  52. * @since 2.2 
  53. */ 
  54. public function enqueue() { 
  55. $hooks = $this->get_hooks(); 
  56. $url = $this->get_delivery_url(); 
  57.  
  58. if ( is_array( $hooks ) && ! empty( $url ) ) { 
  59. foreach ( $hooks as $hook ) { 
  60. add_action( $hook, array( $this, 'process' ) ); 
  61.  
  62.  
  63. /** 
  64. * Process the webhook for delivery by verifying that it should be delivered. 
  65. * and scheduling the delivery (in the background by default, or immediately). 
  66. * @since 2.2 
  67. * @param mixed $arg the first argument provided from the associated hooks 
  68. */ 
  69. public function process( $arg ) { 
  70.  
  71. // verify that webhook should be processed for delivery 
  72. if ( ! $this->should_deliver( $arg ) ) { 
  73. return; 
  74.  
  75. // webhooks are processed in the background by default 
  76. // so as to avoid delays or failures in delivery from affecting the 
  77. // user who triggered it 
  78. if ( apply_filters( 'woocommerce_webhook_deliver_async', true, $this, $arg ) ) { 
  79.  
  80. // deliver in background 
  81. wp_schedule_single_event( time(), 'woocommerce_deliver_webhook_async', array( $this->id, $arg ) ); 
  82.  
  83. } else { 
  84.  
  85. // deliver immediately 
  86. $this->deliver( $arg ); 
  87.  
  88. /** 
  89. * Helper to check if the webhook should be delivered, as some hooks. 
  90. * (like `wp_trash_post`) will fire for every post type, not just ours. 
  91. * @since 2.2 
  92. * @param mixed $arg first hook argument 
  93. * @return bool true if webhook should be delivered, false otherwise 
  94. */ 
  95. private function should_deliver( $arg ) { 
  96. $should_deliver = true; 
  97. $current_action = current_action(); 
  98.  
  99. // only active webhooks can be delivered 
  100. if ( 'active' != $this->get_status() ) { 
  101. $should_deliver = false; 
  102. } elseif ( in_array( $current_action, array( 'delete_post', 'wp_trash_post' ), true ) ) { 
  103. // Only deliver deleted event for coupons, orders, and products. 
  104. if ( isset( $GLOBALS['post_type'] ) && ! in_array( $GLOBALS['post_type'], array( 'shop_coupon', 'shop_order', 'product' ) ) ) { 
  105. $should_deliver = false; 
  106.  
  107. // Check if is delivering for the correct resource. 
  108. if ( isset( $GLOBALS['post_type'] ) && str_replace( 'shop_', '', $GLOBALS['post_type'] ) !== $this->get_resource() ) { 
  109. $should_deliver = false; 
  110. } elseif ( 'delete_user' == $current_action ) { 
  111. $user = get_userdata( absint( $arg ) ); 
  112.  
  113. // only deliver deleted customer event for users with customer role 
  114. if ( ! $user || ! in_array( 'customer', (array) $user->roles ) ) { 
  115. $should_deliver = false; 
  116.  
  117. // check if the custom order type has chosen to exclude order webhooks from triggering along with its own webhooks. 
  118. } elseif ( 'order' == $this->get_resource() && ! in_array( get_post_type( absint( $arg ) ), wc_get_order_types( 'order-webhooks' ) ) ) { 
  119. $should_deliver = false; 
  120.  
  121. } elseif ( 0 === strpos( $current_action, 'woocommerce_process_shop' ) || 0 === strpos( $current_action, 'woocommerce_process_product' ) ) { 
  122. // the `woocommerce_process_shop_*` and `woocommerce_process_product_*` hooks 
  123. // fire for create and update of products and orders, so check the post 
  124. // creation date to determine the actual event 
  125. $resource = get_post( absint( $arg ) ); 
  126.  
  127. // Drafts don't have post_date_gmt so calculate it here 
  128. $gmt_date = get_gmt_from_date( $resource->post_date ); 
  129.  
  130. // a resource is considered created when the hook is executed within 10 seconds of the post creation date 
  131. $resource_created = ( ( time() - 10 ) <= strtotime( $gmt_date ) ); 
  132.  
  133. if ( 'created' == $this->get_event() && ! $resource_created ) { 
  134. $should_deliver = false; 
  135. } elseif ( 'updated' == $this->get_event() && $resource_created ) { 
  136. $should_deliver = false; 
  137.  
  138. /** 
  139. * Let other plugins intercept deliver for some messages queue like rabbit/zeromq 
  140. */ 
  141. return apply_filters( 'woocommerce_webhook_should_deliver', $should_deliver, $this, $arg ); 
  142.  
  143.  
  144. /** 
  145. * Deliver the webhook payload using wp_safe_remote_request(). 
  146. * @since 2.2 
  147. * @param mixed $arg First hook argument. 
  148. */ 
  149. public function deliver( $arg ) { 
  150. $payload = $this->build_payload( $arg ); 
  151.  
  152. // Setup request args. 
  153. $http_args = array( 
  154. 'method' => 'POST',  
  155. 'timeout' => MINUTE_IN_SECONDS,  
  156. 'redirection' => 0,  
  157. 'httpversion' => '1.0',  
  158. 'blocking' => true,  
  159. 'user-agent' => sprintf( 'WooCommerce/%s Hookshot (WordPress/%s)', WC_VERSION, $GLOBALS['wp_version'] ),  
  160. 'body' => trim( json_encode( $payload ) ),  
  161. 'headers' => array( 'Content-Type' => 'application/json' ),  
  162. 'cookies' => array(),  
  163. ); 
  164.  
  165. $http_args = apply_filters( 'woocommerce_webhook_http_args', $http_args, $arg, $this->id ); 
  166.  
  167. // Add custom headers. 
  168. $http_args['headers']['X-WC-Webhook-Source'] = home_url( '/' ); // Since 2.6.0. 
  169. $http_args['headers']['X-WC-Webhook-Topic'] = $this->get_topic(); 
  170. $http_args['headers']['X-WC-Webhook-Resource'] = $this->get_resource(); 
  171. $http_args['headers']['X-WC-Webhook-Event'] = $this->get_event(); 
  172. $http_args['headers']['X-WC-Webhook-Signature'] = $this->generate_signature( $http_args['body'] ); 
  173. $http_args['headers']['X-WC-Webhook-ID'] = $this->id; 
  174. $http_args['headers']['X-WC-Webhook-Delivery-ID'] = $delivery_id = $this->get_new_delivery_id(); 
  175.  
  176. $start_time = microtime( true ); 
  177.  
  178. // Webhook away! 
  179. $response = wp_safe_remote_request( $this->get_delivery_url(), $http_args ); 
  180.  
  181. $duration = round( microtime( true ) - $start_time, 5 ); 
  182.  
  183. $this->log_delivery( $delivery_id, $http_args, $response, $duration ); 
  184.  
  185. do_action( 'woocommerce_webhook_delivery', $http_args, $response, $duration, $arg, $this->id ); 
  186.  
  187. /** 
  188. * Get Legacy API payload. 
  189. * @since 3.0.0 
  190. * @param string $resource Resource type. 
  191. * @param int $resource_id Resource ID. 
  192. * @param string $event Event type. 
  193. * @return array 
  194. */ 
  195. private function get_legacy_api_payload( $resource, $resource_id, $event ) { 
  196. // Include & load API classes. 
  197. WC()->api->includes(); 
  198. WC()->api->register_resources( new WC_API_Server( '/' ) ); 
  199.  
  200. switch ( $resource ) { 
  201. case 'coupon' : 
  202. $payload = WC()->api->WC_API_Coupons->get_coupon( $resource_id ); 
  203. break; 
  204.  
  205. case 'customer' : 
  206. $payload = WC()->api->WC_API_Customers->get_customer( $resource_id ); 
  207. break; 
  208.  
  209. case 'order' : 
  210. $payload = WC()->api->WC_API_Orders->get_order( $resource_id, null, apply_filters( 'woocommerce_webhook_order_payload_filters', array() ) ); 
  211. break; 
  212.  
  213. case 'product' : 
  214. // Bulk and quick edit action hooks return a product object instead of an ID. 
  215. if ( 'updated' === $event && is_a( $resource_id, 'WC_Product' ) ) { 
  216. $resource_id = $resource_id->get_id(); 
  217. $payload = WC()->api->WC_API_Products->get_product( $resource_id ); 
  218. break; 
  219.  
  220. // Custom topics include the first hook argument. 
  221. case 'action' : 
  222. $payload = array( 
  223. 'action' => current( $this->get_hooks() ),  
  224. 'arg' => $resource_id,  
  225. ); 
  226. break; 
  227.  
  228. default : 
  229. $payload = array(); 
  230. break; 
  231.  
  232. return $payload; 
  233.  
  234. /** 
  235. * Get WP API integration payload. 
  236. * @since 3.0.0 
  237. * @param string $resource Resource type. 
  238. * @param int $resource_id Resource ID. 
  239. * @param string $event Event type. 
  240. * @return array 
  241. */ 
  242. private function get_wp_api_payload( $resource, $resource_id, $event ) { 
  243. $version_suffix = 'wp_api_v1' === $this->get_api_version() ? '_V1' : ''; 
  244.  
  245. switch ( $resource ) { 
  246. case 'coupon' : 
  247. case 'customer' : 
  248. case 'order' : 
  249. case 'product' : 
  250. $class = 'WC_REST_' . ucfirst( $resource ) . 's' . $version_suffix . '_Controller'; 
  251. $request = new WP_REST_Request( 'GET' ); 
  252. $controller = new $class; 
  253.  
  254. // Bulk and quick edit action hooks return a product object instead of an ID. 
  255. if ( 'product' === $resource && 'updated' === $event && is_a( $resource_id, 'WC_Product' ) ) { 
  256. $resource_id = $resource_id->get_id(); 
  257.  
  258. $request->set_param( 'id', $resource_id ); 
  259. $result = $controller->get_item( $request ); 
  260. $payload = isset( $result->data ) ? $result->data : array(); 
  261. break; 
  262.  
  263. // Custom topics include the first hook argument. 
  264. case 'action' : 
  265. $payload = array( 
  266. 'action' => current( $this->get_hooks() ),  
  267. 'arg' => $resource_id,  
  268. ); 
  269. break; 
  270.  
  271. default : 
  272. $payload = array(); 
  273. break; 
  274.  
  275. return $payload; 
  276.  
  277. /** 
  278. * Build the payload data for the webhook. 
  279. * @since 2.2 
  280. * @param mixed $resource_id first hook argument, typically the resource ID 
  281. * @return mixed payload data 
  282. */ 
  283. private function build_payload( $resource_id ) { 
  284. // build the payload with the same user context as the user who created 
  285. // the webhook -- this avoids permission errors as background processing 
  286. // runs with no user context 
  287. $current_user = get_current_user_id(); 
  288. wp_set_current_user( $this->get_user_id() ); 
  289.  
  290. $resource = $this->get_resource(); 
  291. $event = $this->get_event(); 
  292.  
  293. // If a resource has been deleted, just include the ID. 
  294. if ( 'deleted' == $event ) { 
  295. $payload = array( 
  296. 'id' => $resource_id,  
  297. ); 
  298. } else { 
  299. if ( in_array( $this->get_api_version(), array( 'wp_api_v1', 'wp_api_v2' ), true ) ) { 
  300. $payload = $this->get_wp_api_payload( $resource, $resource_id, $event ); 
  301. } else { 
  302. $payload = $this->get_legacy_api_payload( $resource, $resource_id, $event ); 
  303.  
  304. // Restore the current user. 
  305. wp_set_current_user( $current_user ); 
  306.  
  307. return apply_filters( 'woocommerce_webhook_payload', $payload, $resource, $resource_id, $this->id ); 
  308.  
  309.  
  310. /** 
  311. * Generate a base64-encoded HMAC-SHA256 signature of the payload body so the. 
  312. * recipient can verify the authenticity of the webhook. Note that the signature. 
  313. * is calculated after the body has already been encoded (JSON by default). 
  314. * @since 2.2 
  315. * @param string $payload payload data to hash 
  316. * @return string hash 
  317. */ 
  318. public function generate_signature( $payload ) { 
  319.  
  320. $hash_algo = apply_filters( 'woocommerce_webhook_hash_algorithm', 'sha256', $payload, $this->id ); 
  321.  
  322. return base64_encode( hash_hmac( $hash_algo, $payload, $this->get_secret(), true ) ); 
  323.  
  324.  
  325. /** 
  326. * Create a new comment for log the delivery request/response and. 
  327. * return the ID for inclusion in the webhook request. 
  328. * @since 2.2 
  329. * @return int delivery (comment) ID 
  330. */ 
  331. public function get_new_delivery_id() { 
  332.  
  333. $comment_data = apply_filters( 'woocommerce_new_webhook_delivery_data', array( 
  334. 'comment_author' => __( 'WooCommerce', 'woocommerce' ),  
  335. 'comment_author_email' => sanitize_email( sprintf( '%s@%s', strtolower( __( 'WooCommerce', 'woocommerce' ) ), isset( $_SERVER['HTTP_HOST'] ) ? str_replace( 'www.', '', $_SERVER['HTTP_HOST'] ) : 'noreply.com' ) ),  
  336. 'comment_post_ID' => $this->id,  
  337. 'comment_agent' => 'WooCommerce Hookshot',  
  338. 'comment_type' => 'webhook_delivery',  
  339. 'comment_parent' => 0,  
  340. 'comment_approved' => 1,  
  341. ), $this->id ); 
  342.  
  343. $comment_id = wp_insert_comment( $comment_data ); 
  344.  
  345. return $comment_id; 
  346.  
  347.  
  348. /** 
  349. * Log the delivery request/response. 
  350. * @since 2.2 
  351. * @param int $delivery_id previously created comment ID 
  352. * @param array $request request data 
  353. * @param array|WP_Error $response response data 
  354. * @param float $duration request duration 
  355. */ 
  356. public function log_delivery( $delivery_id, $request, $response, $duration ) { 
  357.  
  358. // save request data 
  359. add_comment_meta( $delivery_id, '_request_method', $request['method'] ); 
  360. add_comment_meta( $delivery_id, '_request_headers', array_merge( array( 'User-Agent' => $request['user-agent'] ), $request['headers'] ) ); 
  361. add_comment_meta( $delivery_id, '_request_body', $request['body'] ); 
  362.  
  363. // parse response 
  364. if ( is_wp_error( $response ) ) { 
  365. $response_code = $response->get_error_code(); 
  366. $response_message = $response->get_error_message(); 
  367. $response_headers = array(); 
  368. $response_body = ''; 
  369.  
  370. } else { 
  371. $response_code = wp_remote_retrieve_response_code( $response ); 
  372. $response_message = wp_remote_retrieve_response_message( $response ); 
  373. $response_headers = wp_remote_retrieve_headers( $response ); 
  374. $response_body = wp_remote_retrieve_body( $response ); 
  375.  
  376. // save response data 
  377. add_comment_meta( $delivery_id, '_response_code', $response_code ); 
  378. add_comment_meta( $delivery_id, '_response_message', $response_message ); 
  379. add_comment_meta( $delivery_id, '_response_headers', $response_headers ); 
  380. add_comment_meta( $delivery_id, '_response_body', $response_body ); 
  381.  
  382. // save duration 
  383. add_comment_meta( $delivery_id, '_duration', $duration ); 
  384.  
  385. // set a summary for quick display 
  386. $args = array( 
  387. 'comment_ID' => $delivery_id,  
  388. 'comment_content' => sprintf( 'HTTP %s %s: %s', $response_code, $response_message, $response_body ),  
  389. ); 
  390.  
  391. wp_update_comment( $args ); 
  392.  
  393. // track failures 
  394. if ( intval( $response_code ) >= 200 && intval( $response_code ) < 300 ) { 
  395. delete_post_meta( $this->id, '_failure_count' ); 
  396. } else { 
  397. $this->failed_delivery(); 
  398.  
  399. // keep the 25 most recent delivery logs 
  400. $log = wp_count_comments( $this->id ); 
  401. if ( $log->total_comments > apply_filters( 'woocommerce_max_webhook_delivery_logs', 25 ) ) { 
  402. global $wpdb; 
  403.  
  404. $comment_id = $wpdb->get_var( $wpdb->prepare( "SELECT comment_ID FROM {$wpdb->comments} WHERE comment_post_ID = %d ORDER BY comment_date_gmt ASC LIMIT 1", $this->id ) ); 
  405.  
  406. if ( $comment_id ) { 
  407. wp_delete_comment( $comment_id, true ); 
  408.  
  409. /** 
  410. * Track consecutive delivery failures and automatically disable the webhook. 
  411. * if more than 5 consecutive failures occur. A failure is defined as a. 
  412. * non-2xx response. 
  413. * @since 2.2 
  414. */ 
  415. private function failed_delivery() { 
  416.  
  417. $failures = $this->get_failure_count(); 
  418.  
  419. if ( $failures > apply_filters( 'woocommerce_max_webhook_delivery_failures', 5 ) ) { 
  420.  
  421. $this->update_status( 'disabled' ); 
  422.  
  423. } else { 
  424.  
  425. update_post_meta( $this->id, '_failure_count', ++$failures ); 
  426.  
  427.  
  428. /** 
  429. * Get the delivery logs for this webhook. 
  430. * @since 2.2 
  431. * @return array 
  432. */ 
  433. public function get_delivery_logs() { 
  434.  
  435. $args = array( 
  436. 'post_id' => $this->id,  
  437. 'status' => 'approve',  
  438. 'type' => 'webhook_delivery',  
  439. ); 
  440.  
  441. remove_filter( 'comments_clauses', array( 'WC_Comments', 'exclude_webhook_comments' ), 10, 1 ); 
  442.  
  443. $logs = get_comments( $args ); 
  444.  
  445. add_filter( 'comments_clauses', array( 'WC_Comments', 'exclude_webhook_comments' ), 10, 1 ); 
  446.  
  447. $delivery_logs = array(); 
  448.  
  449. foreach ( $logs as $log ) { 
  450.  
  451. $log = $this->get_delivery_log( $log->comment_ID ); 
  452.  
  453. $delivery_logs[] = ( ! empty( $log ) ? $log : array() ); 
  454.  
  455. return $delivery_logs; 
  456.  
  457.  
  458. /** 
  459. * Get the delivery log specified by the ID. The delivery log includes: 
  460. * + duration 
  461. * + summary 
  462. * + request method/url 
  463. * + request headers/body 
  464. * + response code/message/headers/body 
  465. * @since 2.2 
  466. * @param int $delivery_id 
  467. * @return bool|array false if invalid delivery ID, array of log data otherwise 
  468. */ 
  469. public function get_delivery_log( $delivery_id ) { 
  470.  
  471. $log = get_comment( $delivery_id ); 
  472.  
  473. // valid comment and ensure delivery log belongs to this webhook 
  474. if ( is_null( $log ) || $log->comment_post_ID != $this->id ) { 
  475. return false; 
  476.  
  477. $delivery_log = array( 
  478. 'id' => intval( $delivery_id ),  
  479. 'duration' => get_comment_meta( $delivery_id, '_duration', true ),  
  480. 'summary' => $log->comment_content,  
  481. 'request_method' => get_comment_meta( $delivery_id, '_request_method', true ),  
  482. 'request_url' => $this->get_delivery_url(),  
  483. 'request_headers' => get_comment_meta( $delivery_id, '_request_headers', true ),  
  484. 'request_body' => get_comment_meta( $delivery_id, '_request_body', true ),  
  485. 'response_code' => get_comment_meta( $delivery_id, '_response_code', true ),  
  486. 'response_message' => get_comment_meta( $delivery_id, '_response_message', true ),  
  487. 'response_headers' => get_comment_meta( $delivery_id, '_response_headers', true ),  
  488. 'response_body' => get_comment_meta( $delivery_id, '_response_body', true ),  
  489. 'comment' => $log,  
  490. ); 
  491.  
  492. return apply_filters( 'woocommerce_webhook_delivery_log', $delivery_log, $delivery_id, $this->id ); 
  493.  
  494. /** 
  495. * Set the webhook topic and associated hooks. The topic resource & event. 
  496. * are also saved separately. 
  497. * @since 2.2 
  498. * @param string $topic 
  499. */ 
  500. public function set_topic( $topic ) { 
  501.  
  502. $topic = strtolower( $topic ); 
  503.  
  504. list( $resource, $event ) = explode( '.', $topic ); 
  505.  
  506. update_post_meta( $this->id, '_topic', $topic ); 
  507. update_post_meta( $this->id, '_resource', $resource ); 
  508. update_post_meta( $this->id, '_event', $event ); 
  509.  
  510. // custom topics are mapped to a single hook 
  511. if ( 'action' === $resource ) { 
  512.  
  513. update_post_meta( $this->id, '_hooks', array( $event ) ); 
  514.  
  515. } else { 
  516.  
  517. // API topics have multiple hooks 
  518. update_post_meta( $this->id, '_hooks', $this->get_topic_hooks( $topic ) ); 
  519.  
  520. /** 
  521. * Get the associated hook names for a topic. 
  522. * @since 2.2 
  523. * @param string $topic 
  524. * @return array hook names 
  525. */ 
  526. private function get_topic_hooks( $topic ) { 
  527.  
  528. $topic_hooks = array( 
  529. 'coupon.created' => array( 
  530. 'woocommerce_process_shop_coupon_meta',  
  531. 'woocommerce_api_create_coupon',  
  532. ),  
  533. 'coupon.updated' => array( 
  534. 'woocommerce_process_shop_coupon_meta',  
  535. 'woocommerce_api_edit_coupon',  
  536. ),  
  537. 'coupon.deleted' => array( 
  538. 'wp_trash_post',  
  539. ),  
  540. 'customer.created' => array( 
  541. 'user_register',  
  542. 'woocommerce_created_customer',  
  543. 'woocommerce_api_create_customer',  
  544. ),  
  545. 'customer.updated' => array( 
  546. 'profile_update',  
  547. 'woocommerce_api_edit_customer',  
  548. 'woocommerce_customer_save_address',  
  549. ),  
  550. 'customer.deleted' => array( 
  551. 'delete_user',  
  552. ),  
  553. 'order.created' => array( 
  554. 'woocommerce_checkout_order_processed',  
  555. 'woocommerce_process_shop_order_meta',  
  556. 'woocommerce_api_create_order',  
  557. ),  
  558. 'order.updated' => array( 
  559. 'woocommerce_process_shop_order_meta',  
  560. 'woocommerce_api_edit_order',  
  561. 'woocommerce_order_edit_status',  
  562. 'woocommerce_order_status_changed',  
  563. ),  
  564. 'order.deleted' => array( 
  565. 'wp_trash_post',  
  566. ),  
  567. 'product.created' => array( 
  568. 'woocommerce_process_product_meta',  
  569. 'woocommerce_api_create_product',  
  570. ),  
  571. 'product.updated' => array( 
  572. 'woocommerce_process_product_meta',  
  573. 'woocommerce_api_edit_product',  
  574. 'woocommerce_product_quick_edit_save',  
  575. 'woocommerce_product_bulk_edit_save',  
  576. ),  
  577. 'product.deleted' => array( 
  578. 'wp_trash_post',  
  579. ),  
  580. ); 
  581.  
  582. $topic_hooks = apply_filters( 'woocommerce_webhook_topic_hooks', $topic_hooks, $this ); 
  583.  
  584. return isset( $topic_hooks[ $topic ] ) ? $topic_hooks[ $topic ] : array(); 
  585.  
  586. /** 
  587. * Send a test ping to the delivery URL, sent when the webhook is first created. 
  588. * @since 2.2 
  589. * @return bool|WP_Error 
  590. */ 
  591. public function deliver_ping() { 
  592. $args = array( 
  593. 'user-agent' => sprintf( 'WooCommerce/%s Hookshot (WordPress/%s)', WC_VERSION, $GLOBALS['wp_version'] ),  
  594. 'body' => "webhook_id={$this->id}",  
  595. ); 
  596.  
  597. $test = wp_safe_remote_post( $this->get_delivery_url(), $args ); 
  598. $response_code = wp_remote_retrieve_response_code( $test ); 
  599.  
  600. if ( is_wp_error( $test ) ) { 
  601. return new WP_Error( 'error', sprintf( __( 'Error: Delivery URL cannot be reached: %s', 'woocommerce' ), $test->get_error_message() ) ); 
  602.  
  603. if ( 200 !== $response_code ) { 
  604. return new WP_Error( 'error', sprintf( __( 'Error: Delivery URL returned response code: %s', 'woocommerce' ), absint( $response_code ) ) ); 
  605.  
  606. return true; 
  607.  
  608. /** 
  609. * Get the webhook status: 
  610. * + `active` - delivers payload. 
  611. * + `paused` - does not deliver payload, paused by admin. 
  612. * + `disabled` - does not delivery payload, paused automatically due to. 
  613. * consecutive failures. 
  614. * @since 2.2 
  615. * @return string status 
  616. */ 
  617. public function get_status() { 
  618.  
  619. switch ( $this->get_post_data()->post_status ) { 
  620.  
  621. case 'publish': 
  622. $status = 'active'; 
  623. break; 
  624.  
  625. case 'draft': 
  626. $status = 'paused'; 
  627. break; 
  628.  
  629. case 'pending': 
  630. $status = 'disabled'; 
  631. break; 
  632.  
  633. default: 
  634. $status = 'paused'; 
  635.  
  636. return apply_filters( 'woocommerce_webhook_status', $status, $this->id ); 
  637.  
  638. /** 
  639. * Get the webhook i18n status. 
  640. * @return string 
  641. */ 
  642. public function get_i18n_status() { 
  643. $status = $this->get_status(); 
  644. $statuses = wc_get_webhook_statuses(); 
  645.  
  646. return isset( $statuses[ $status ] ) ? $statuses[ $status ] : $status; 
  647.  
  648. /** 
  649. * Update the webhook status, see get_status() for valid statuses. 
  650. * @since 2.2 
  651. * @param $status 
  652. */ 
  653. public function update_status( $status ) { 
  654. global $wpdb; 
  655.  
  656. switch ( $status ) { 
  657.  
  658. case 'active' : 
  659. $post_status = 'publish'; 
  660. break; 
  661.  
  662. case 'paused' : 
  663. $post_status = 'draft'; 
  664. break; 
  665.  
  666. case 'disabled' : 
  667. $post_status = 'pending'; 
  668. break; 
  669.  
  670. default : 
  671. $post_status = 'draft'; 
  672. break; 
  673.  
  674. $wpdb->update( $wpdb->posts, array( 'post_status' => $post_status ), array( 'ID' => $this->id ) ); 
  675. clean_post_cache( $this->id ); 
  676.  
  677. /** 
  678. * Set the delivery URL. 
  679. * @since 2.2 
  680. * @param string $url 
  681. */ 
  682. public function set_delivery_url( $url ) { 
  683. if ( update_post_meta( $this->id, '_delivery_url', esc_url_raw( $url, array( 'http', 'https' ) ) ) ) { 
  684. update_post_meta( $this->id, '_webhook_pending_delivery', true ); 
  685.  
  686. /** 
  687. * Get the delivery URL. 
  688. * @since 2.2 
  689. * @return string 
  690. */ 
  691. public function get_delivery_url() { 
  692.  
  693. return apply_filters( 'woocommerce_webhook_delivery_url', $this->delivery_url, $this->id ); 
  694.  
  695. /** 
  696. * Set the secret used for generating the HMAC-SHA256 signature. 
  697. * @since 2.2 
  698. * @param string $secret 
  699. */ 
  700. public function set_secret( $secret ) { 
  701.  
  702. update_post_meta( $this->id, '_secret', $secret ); 
  703.  
  704. /** 
  705. * Get the secret used for generating the HMAC-SHA256 signature. 
  706. * @since 2.2 
  707. * @return string 
  708. */ 
  709. public function get_secret() { 
  710. return apply_filters( 'woocommerce_webhook_secret', $this->secret, $this->id ); 
  711.  
  712. /** 
  713. * Get the friendly name for the webhook. 
  714. * @since 2.2 
  715. * @return string 
  716. */ 
  717. public function get_name() { 
  718. return apply_filters( 'woocommerce_webhook_name', $this->get_post_data()->post_title, $this->id ); 
  719.  
  720. /** 
  721. * Get the webhook topic, e.g. `order.created`. 
  722. * @since 2.2 
  723. * @return string 
  724. */ 
  725. public function get_topic() { 
  726. return apply_filters( 'woocommerce_webhook_topic', $this->topic, $this->id ); 
  727.  
  728. /** 
  729. * Get the hook names for the webhook. 
  730. * @since 2.2 
  731. * @return array hook names 
  732. */ 
  733. public function get_hooks() { 
  734. return apply_filters( 'woocommerce_webhook_hooks', $this->hooks, $this->id ); 
  735.  
  736. /** 
  737. * Get the resource for the webhook, e.g. `order`. 
  738. * @since 2.2 
  739. * @return string 
  740. */ 
  741. public function get_resource() { 
  742. return apply_filters( 'woocommerce_webhook_resource', $this->resource, $this->id ); 
  743.  
  744. /** 
  745. * Get the event for the webhook, e.g. `created`. 
  746. * @since 2.2 
  747. * @return string 
  748. */ 
  749. public function get_event() { 
  750. return apply_filters( 'woocommerce_webhook_event', $this->event, $this->id ); 
  751.  
  752. /** 
  753. * Get the failure count. 
  754. * @since 2.2 
  755. * @return int 
  756. */ 
  757. public function get_failure_count() { 
  758. return intval( $this->failure_count ); 
  759.  
  760. /** 
  761. * Get the user ID for this webhook. 
  762. * @since 2.2 
  763. * @return int|string user ID 
  764. */ 
  765. public function get_user_id() { 
  766. return $this->get_post_data()->post_author; 
  767.  
  768. /** 
  769. * Get the post data for the webhook. 
  770. * @since 2.2 
  771. * @return null|WP_Post 
  772. */ 
  773. public function get_post_data() { 
  774. return $this->post_data; 
  775.  
  776. /** 
  777. * Set API version. 
  778. * @since 3.0.0 
  779. * @param string $version REST API version. 
  780. */ 
  781. public function set_api_version( $version ) { 
  782. $versions = array( 
  783. 'wp_api_v2',  
  784. 'wp_api_v1',  
  785. 'legacy_v3',  
  786. ); 
  787.  
  788. if ( ! in_array( $version, $versions, true ) ) { 
  789. $version = 'wp_api_v2'; 
  790.  
  791. update_post_meta( $this->id, '_api_version', $version ); 
  792.  
  793. /** 
  794. * API version. 
  795. * @since 3.0.0 
  796. * @return string 
  797. */ 
  798. public function get_api_version() { 
  799. return $this->api_version ? $this->api_version : 'legacy_v3';