WC_REST_Webhooks_V1_Controller

REST API Webhooks controller class.

Defined (1)

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

/includes/api/v1/class-wc-rest-webhooks-controller.php  
  1. class WC_REST_Webhooks_V1_Controller extends WC_REST_Posts_Controller { 
  2.  
  3. /** 
  4. * Endpoint namespace. 
  5. * @var string 
  6. */ 
  7. protected $namespace = 'wc/v1'; 
  8.  
  9. /** 
  10. * Route base. 
  11. * @var string 
  12. */ 
  13. protected $rest_base = 'webhooks'; 
  14.  
  15. /** 
  16. * Post type. 
  17. * @var string 
  18. */ 
  19. protected $post_type = 'shop_webhook'; 
  20.  
  21. /** 
  22. * Initialize Webhooks actions. 
  23. */ 
  24. public function __construct() { 
  25. add_filter( "woocommerce_rest_{$this->post_type}_query", array( $this, 'query_args' ), 10, 2 ); 
  26.  
  27. /** 
  28. * Register the routes for webhooks. 
  29. */ 
  30. public function register_routes() { 
  31. register_rest_route( $this->namespace, '/' . $this->rest_base, array( 
  32. array( 
  33. 'methods' => WP_REST_Server::READABLE,  
  34. 'callback' => array( $this, 'get_items' ),  
  35. 'permission_callback' => array( $this, 'get_items_permissions_check' ),  
  36. 'args' => $this->get_collection_params(),  
  37. ),  
  38. array( 
  39. 'methods' => WP_REST_Server::CREATABLE,  
  40. 'callback' => array( $this, 'create_item' ),  
  41. 'permission_callback' => array( $this, 'create_item_permissions_check' ),  
  42. 'args' => array_merge( $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ), array( 
  43. 'topic' => array( 
  44. 'required' => true,  
  45. 'type' => 'string',  
  46. 'description' => __( 'Webhook topic.', 'woocommerce' ),  
  47. ),  
  48. 'delivery_url' => array( 
  49. 'required' => true,  
  50. 'type' => 'string',  
  51. 'description' => __( 'Webhook delivery URL.', 'woocommerce' ),  
  52. ),  
  53. 'secret' => array( 
  54. 'required' => true,  
  55. 'type' => 'string',  
  56. 'description' => __( 'Webhook secret.', 'woocommerce' ),  
  57. ),  
  58. ) ),  
  59. ),  
  60. 'schema' => array( $this, 'get_public_item_schema' ),  
  61. ) ); 
  62.  
  63. register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P<id>[\d]+)', array( 
  64. 'args' => array( 
  65. 'id' => array( 
  66. 'description' => __( 'Unique identifier for the resource.', 'woocommerce' ),  
  67. 'type' => 'integer',  
  68. ),  
  69. ),  
  70. array( 
  71. 'methods' => WP_REST_Server::READABLE,  
  72. 'callback' => array( $this, 'get_item' ),  
  73. 'permission_callback' => array( $this, 'get_item_permissions_check' ),  
  74. 'args' => array( 
  75. 'context' => $this->get_context_param( array( 'default' => 'view' ) ),  
  76. ),  
  77. ),  
  78. array( 
  79. 'methods' => WP_REST_Server::EDITABLE,  
  80. 'callback' => array( $this, 'update_item' ),  
  81. 'permission_callback' => array( $this, 'update_item_permissions_check' ),  
  82. 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),  
  83. ),  
  84. array( 
  85. 'methods' => WP_REST_Server::DELETABLE,  
  86. 'callback' => array( $this, 'delete_item' ),  
  87. 'permission_callback' => array( $this, 'delete_item_permissions_check' ),  
  88. 'args' => array( 
  89. 'force' => array( 
  90. 'default' => false,  
  91. 'type' => 'boolean',  
  92. 'description' => __( 'Required to be true, as resource does not support trashing.', 'woocommerce' ),  
  93. ),  
  94. ),  
  95. ),  
  96. 'schema' => array( $this, 'get_public_item_schema' ),  
  97. ) ); 
  98.  
  99. register_rest_route( $this->namespace, '/' . $this->rest_base . '/batch', array( 
  100. array( 
  101. 'methods' => WP_REST_Server::EDITABLE,  
  102. 'callback' => array( $this, 'batch_items' ),  
  103. 'permission_callback' => array( $this, 'batch_items_permissions_check' ),  
  104. 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),  
  105. ),  
  106. 'schema' => array( $this, 'get_public_batch_schema' ),  
  107. ) ); 
  108.  
  109. /** 
  110. * Get the default REST API version. 
  111. * @since 3.0.0 
  112. * @return string 
  113. */ 
  114. protected function get_default_api_version() { 
  115. return 'wp_api_v2'; 
  116.  
  117. /** 
  118. * Create a single webhook. 
  119. * @param WP_REST_Request $request Full details about the request. 
  120. * @return WP_Error|WP_REST_Response 
  121. */ 
  122. public function create_item( $request ) { 
  123. if ( ! empty( $request['id'] ) ) { 
  124. /** translators: %s: post type */ 
  125. return new WP_Error( "woocommerce_rest_{$this->post_type}_exists", sprintf( __( 'Cannot create existing %s.', 'woocommerce' ), $this->post_type ), array( 'status' => 400 ) ); 
  126.  
  127. // Validate topic. 
  128. if ( empty( $request['topic'] ) || ! wc_is_webhook_valid_topic( strtolower( $request['topic'] ) ) ) { 
  129. return new WP_Error( "woocommerce_rest_{$this->post_type}_invalid_topic", __( 'Webhook topic is required and must be valid.', 'woocommerce' ), array( 'status' => 400 ) ); 
  130.  
  131. // Validate delivery URL. 
  132. if ( empty( $request['delivery_url'] ) || ! wc_is_valid_url( $request['delivery_url'] ) ) { 
  133. return new WP_Error( "woocommerce_rest_{$this->post_type}_invalid_delivery_url", __( 'Webhook delivery URL must be a valid URL starting with http:// or https://.', 'woocommerce' ), array( 'status' => 400 ) ); 
  134.  
  135. $post = $this->prepare_item_for_database( $request ); 
  136. if ( is_wp_error( $post ) ) { 
  137. return $post; 
  138.  
  139. $post->post_type = $this->post_type; 
  140. $post_id = wp_insert_post( $post, true ); 
  141.  
  142. if ( is_wp_error( $post_id ) ) { 
  143.  
  144. if ( in_array( $post_id->get_error_code(), array( 'db_insert_error' ) ) ) { 
  145. $post_id->add_data( array( 'status' => 500 ) ); 
  146. } else { 
  147. $post_id->add_data( array( 'status' => 400 ) ); 
  148. return $post_id; 
  149. $post->ID = $post_id; 
  150.  
  151. $webhook = new WC_Webhook( $post_id ); 
  152.  
  153. // Set topic. 
  154. $webhook->set_topic( $request['topic'] ); 
  155.  
  156. // Set delivery URL. 
  157. $webhook->set_delivery_url( $request['delivery_url'] ); 
  158.  
  159. // Set secret. 
  160. $webhook->set_secret( ! empty( $request['secret'] ) ? $request['secret'] : '' ); 
  161.  
  162. // Set API version to WP API integration. 
  163. $webhook->set_api_version( $this->get_default_api_version() ); 
  164.  
  165. // Set status. 
  166. if ( ! empty( $request['status'] ) ) { 
  167. $webhook->update_status( $request['status'] ); 
  168.  
  169. $post = get_post( $post_id ); 
  170. $this->update_additional_fields_for_object( $post, $request ); 
  171.  
  172. /** 
  173. * Fires after a single item is created or updated via the REST API. 
  174. * @param WP_Post $post Inserted object. 
  175. * @param WP_REST_Request $request Request object. 
  176. * @param boolean $creating True when creating item, false when updating. 
  177. */ 
  178. do_action( "woocommerce_rest_insert_{$this->post_type}", $post, $request, true ); 
  179.  
  180. $request->set_param( 'context', 'edit' ); 
  181. $response = $this->prepare_item_for_response( $post, $request ); 
  182. $response = rest_ensure_response( $response ); 
  183. $response->set_status( 201 ); 
  184. $response->header( 'Location', rest_url( sprintf( '/%s/%s/%d', $this->namespace, $this->rest_base, $post_id ) ) ); 
  185.  
  186. // Send ping. 
  187. $webhook->deliver_ping(); 
  188.  
  189. // Clear cache. 
  190. delete_transient( 'woocommerce_webhook_ids' ); 
  191.  
  192. return $response; 
  193.  
  194. /** 
  195. * Update a single webhook. 
  196. * @param WP_REST_Request $request Full details about the request. 
  197. * @return WP_Error|WP_REST_Response 
  198. */ 
  199. public function update_item( $request ) { 
  200. $id = (int) $request['id']; 
  201. $post = get_post( $id ); 
  202.  
  203. if ( empty( $id ) || empty( $post->ID ) || $this->post_type !== $post->post_type ) { 
  204. return new WP_Error( "woocommerce_rest_{$this->post_type}_invalid_id", __( 'ID is invalid.', 'woocommerce' ), array( 'status' => 400 ) ); 
  205.  
  206. $webhook = new WC_Webhook( $id ); 
  207.  
  208. // Update topic. 
  209. if ( ! empty( $request['topic'] ) ) { 
  210. if ( wc_is_webhook_valid_topic( strtolower( $request['topic'] ) ) ) { 
  211. $webhook->set_topic( $request['topic'] ); 
  212. } else { 
  213. return new WP_Error( "woocommerce_rest_{$this->post_type}_invalid_topic", __( 'Webhook topic must be valid.', 'woocommerce' ), array( 'status' => 400 ) ); 
  214.  
  215. // Update delivery URL. 
  216. if ( ! empty( $request['delivery_url'] ) ) { 
  217. if ( wc_is_valid_url( $request['delivery_url'] ) ) { 
  218. $webhook->set_delivery_url( $request['delivery_url'] ); 
  219. } else { 
  220. return new WP_Error( "woocommerce_rest_{$this->post_type}_invalid_delivery_url", __( 'Webhook delivery URL must be a valid URL starting with http:// or https://.', 'woocommerce' ), array( 'status' => 400 ) ); 
  221.  
  222. // Update secret. 
  223. if ( ! empty( $request['secret'] ) ) { 
  224. $webhook->set_secret( $request['secret'] ); 
  225.  
  226. // Update status. 
  227. if ( ! empty( $request['status'] ) ) { 
  228. $webhook->update_status( $request['status'] ); 
  229.  
  230. $post = $this->prepare_item_for_database( $request ); 
  231. if ( is_wp_error( $post ) ) { 
  232. return $post; 
  233.  
  234. // Convert the post object to an array, otherwise wp_update_post will expect non-escaped input. 
  235. $post_id = wp_update_post( (array) $post, true ); 
  236. if ( is_wp_error( $post_id ) ) { 
  237. if ( in_array( $post_id->get_error_code(), array( 'db_update_error' ) ) ) { 
  238. $post_id->add_data( array( 'status' => 500 ) ); 
  239. } else { 
  240. $post_id->add_data( array( 'status' => 400 ) ); 
  241. return $post_id; 
  242.  
  243. $post = get_post( $post_id ); 
  244. $this->update_additional_fields_for_object( $post, $request ); 
  245.  
  246. /** 
  247. * Fires after a single item is created or updated via the REST API. 
  248. * @param WP_Post $post Inserted object. 
  249. * @param WP_REST_Request $request Request object. 
  250. * @param boolean $creating True when creating item, false when updating. 
  251. */ 
  252. do_action( "woocommerce_rest_insert_{$this->post_type}", $post, $request, false ); 
  253.  
  254. $request->set_param( 'context', 'edit' ); 
  255. $response = $this->prepare_item_for_response( $post, $request ); 
  256.  
  257. // Clear cache. 
  258. delete_transient( 'woocommerce_webhook_ids' ); 
  259.  
  260. return rest_ensure_response( $response ); 
  261.  
  262. /** 
  263. * Delete a single webhook. 
  264. * @param WP_REST_Request $request Full details about the request. 
  265. * @return WP_REST_Response|WP_Error 
  266. */ 
  267. public function delete_item( $request ) { 
  268. $id = (int) $request['id']; 
  269. $force = isset( $request['force'] ) ? (bool) $request['force'] : false; 
  270.  
  271. // We don't support trashing for this type, error out. 
  272. if ( ! $force ) { 
  273. return new WP_Error( 'woocommerce_rest_trash_not_supported', __( 'Webhooks do not support trashing.', 'woocommerce' ), array( 'status' => 501 ) ); 
  274.  
  275. $post = get_post( $id ); 
  276.  
  277. if ( empty( $id ) || empty( $post->ID ) || $this->post_type !== $post->post_type ) { 
  278. return new WP_Error( "woocommerce_rest_{$this->post_type}_invalid_id", __( 'Invalid post ID.', 'woocommerce' ), array( 'status' => 404 ) ); 
  279.  
  280. $request->set_param( 'context', 'edit' ); 
  281. $response = $this->prepare_item_for_response( $post, $request ); 
  282.  
  283. $result = wp_delete_post( $id, true ); 
  284.  
  285. if ( ! $result ) { 
  286. /** translators: %s: post type */ 
  287. return new WP_Error( 'woocommerce_rest_cannot_delete', sprintf( __( 'The %s cannot be deleted.', 'woocommerce' ), $this->post_type ), array( 'status' => 500 ) ); 
  288.  
  289. /** 
  290. * Fires after a single item is deleted or trashed via the REST API. 
  291. * @param object $post The deleted or trashed item. 
  292. * @param WP_REST_Response $response The response data. 
  293. * @param WP_REST_Request $request The request sent to the API. 
  294. */ 
  295. do_action( "woocommerce_rest_delete_{$this->post_type}", $post, $response, $request ); 
  296.  
  297. // Clear cache. 
  298. delete_transient( 'woocommerce_webhook_ids' ); 
  299.  
  300. return $response; 
  301.  
  302. /** 
  303. * Prepare a single webhook for create or update. 
  304. * @param WP_REST_Request $request Request object. 
  305. * @return WP_Error|stdClass $data Post object. 
  306. */ 
  307. protected function prepare_item_for_database( $request ) { 
  308. global $wpdb; 
  309.  
  310. $data = new stdClass; 
  311.  
  312. // Post ID. 
  313. if ( isset( $request['id'] ) ) { 
  314. $data->ID = absint( $request['id'] ); 
  315.  
  316. // Validate required POST fields. 
  317. if ( 'POST' === $request->get_method() && empty( $data->ID ) ) { 
  318. // @codingStandardsIgnoreStart 
  319. $data->post_title = ! empty( $request['name'] ) ? $request['name'] : sprintf( __( 'Webhook created on %s', 'woocommerce' ), strftime( _x( '%b %d, %Y @ %I:%M %p', 'Webhook created on date parsed by strftime', 'woocommerce' ) ) ); 
  320. // @codingStandardsIgnoreEnd 
  321.  
  322. // Post author. 
  323. $data->post_author = get_current_user_id(); 
  324.  
  325. // Post password. 
  326. $password = strlen( uniqid( 'webhook_' ) ); 
  327. $data->post_password = $password > 20 ? substr( $password, 0, 20 ) : $password; 
  328.  
  329. // Post status. 
  330. $data->post_status = 'publish'; 
  331. } else { 
  332.  
  333. // Allow edit post title. 
  334. if ( ! empty( $request['name'] ) ) { 
  335. $data->post_title = $request['name']; 
  336.  
  337. // Comment status. 
  338. $data->comment_status = 'closed'; 
  339.  
  340. // Ping status. 
  341. $data->ping_status = 'closed'; 
  342.  
  343. /** 
  344. * Filter the query_vars used in `get_items` for the constructed query. 
  345. * The dynamic portion of the hook name, $this->post_type, refers to post_type of the post being 
  346. * prepared for insertion. 
  347. * @param stdClass $data An object representing a single item prepared 
  348. * for inserting or updating the database. 
  349. * @param WP_REST_Request $request Request object. 
  350. */ 
  351. return apply_filters( "woocommerce_rest_pre_insert_{$this->post_type}", $data, $request ); 
  352.  
  353. /** 
  354. * Prepare a single webhook output for response. 
  355. * @param WP_REST_Request $request Request object. 
  356. * @return WP_REST_Response $response Response data. 
  357. */ 
  358. public function prepare_item_for_response( $post, $request ) { 
  359. $id = (int) $post->ID; 
  360. $webhook = new WC_Webhook( $id ); 
  361. $data = array( 
  362. 'id' => $webhook->id,  
  363. 'name' => $webhook->get_name(),  
  364. 'status' => $webhook->get_status(),  
  365. 'topic' => $webhook->get_topic(),  
  366. 'resource' => $webhook->get_resource(),  
  367. 'event' => $webhook->get_event(),  
  368. 'hooks' => $webhook->get_hooks(),  
  369. 'delivery_url' => $webhook->get_delivery_url(),  
  370. 'date_created' => wc_rest_prepare_date_response( $webhook->get_post_data()->post_date_gmt ),  
  371. 'date_modified' => wc_rest_prepare_date_response( $webhook->get_post_data()->post_modified_gmt ),  
  372. ); 
  373.  
  374. $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; 
  375. $data = $this->add_additional_fields_to_object( $data, $request ); 
  376. $data = $this->filter_response_by_context( $data, $context ); 
  377.  
  378. // Wrap the data in a response object. 
  379. $response = rest_ensure_response( $data ); 
  380.  
  381. $response->add_links( $this->prepare_links( $post, $request ) ); 
  382.  
  383. /** 
  384. * Filter webhook object returned from the REST API. 
  385. * @param WP_REST_Response $response The response object. 
  386. * @param WC_Webhook $webhook Webhook object used to create response. 
  387. * @param WP_REST_Request $request Request object. 
  388. */ 
  389. return apply_filters( "woocommerce_rest_prepare_{$this->post_type}", $response, $webhook, $request ); 
  390.  
  391. /** 
  392. * Query args. 
  393. * @param array $args 
  394. * @param WP_REST_Request $request 
  395. * @return array 
  396. */ 
  397. public function query_args( $args, $request ) { 
  398. // Set post_status. 
  399. switch ( $request['status'] ) { 
  400. case 'active' : 
  401. $args['post_status'] = 'publish'; 
  402. break; 
  403. case 'paused' : 
  404. $args['post_status'] = 'draft'; 
  405. break; 
  406. case 'disabled' : 
  407. $args['post_status'] = 'pending'; 
  408. break; 
  409. default : 
  410. $args['post_status'] = 'any'; 
  411. break; 
  412.  
  413. return $args; 
  414.  
  415. /** 
  416. * Get the Webhook's schema, conforming to JSON Schema. 
  417. * @return array 
  418. */ 
  419. public function get_item_schema() { 
  420. $schema = array( 
  421. '$schema' => 'http://json-schema.org/draft-04/schema#',  
  422. 'title' => 'webhook',  
  423. 'type' => 'object',  
  424. 'properties' => array( 
  425. 'id' => array( 
  426. 'description' => __( 'Unique identifier for the resource.', 'woocommerce' ),  
  427. 'type' => 'integer',  
  428. 'context' => array( 'view', 'edit' ),  
  429. 'readonly' => true,  
  430. ),  
  431. 'name' => array( 
  432. 'description' => __( 'A friendly name for the webhook.', 'woocommerce' ),  
  433. 'type' => 'string',  
  434. 'context' => array( 'view', 'edit' ),  
  435. ),  
  436. 'status' => array( 
  437. 'description' => __( 'Webhook status.', 'woocommerce' ),  
  438. 'type' => 'string',  
  439. 'default' => 'active',  
  440. 'enum' => array( 'active', 'paused', 'disabled' ),  
  441. 'context' => array( 'view', 'edit' ),  
  442. 'arg_options' => array( 
  443. 'sanitize_callback' => 'wc_is_webhook_valid_topic',  
  444. ),  
  445. ),  
  446. 'topic' => array( 
  447. 'description' => __( 'Webhook topic.', 'woocommerce' ),  
  448. 'type' => 'string',  
  449. 'context' => array( 'view', 'edit' ),  
  450. ),  
  451. 'resource' => array( 
  452. 'description' => __( 'Webhook resource.', 'woocommerce' ),  
  453. 'type' => 'string',  
  454. 'context' => array( 'view', 'edit' ),  
  455. 'readonly' => true,  
  456. ),  
  457. 'event' => array( 
  458. 'description' => __( 'Webhook event.', 'woocommerce' ),  
  459. 'type' => 'string',  
  460. 'context' => array( 'view', 'edit' ),  
  461. 'readonly' => true,  
  462. ),  
  463. 'hooks' => array( 
  464. 'description' => __( 'WooCommerce action names associated with the webhook.', 'woocommerce' ),  
  465. 'type' => 'array',  
  466. 'context' => array( 'view', 'edit' ),  
  467. 'readonly' => true,  
  468. 'items' => array( 
  469. 'type' => 'string',  
  470. ),  
  471. ),  
  472. 'delivery_url' => array( 
  473. 'description' => __( 'The URL where the webhook payload is delivered.', 'woocommerce' ),  
  474. 'type' => 'string',  
  475. 'format' => 'uri',  
  476. 'context' => array( 'view', 'edit' ),  
  477. 'readonly' => true,  
  478. ),  
  479. 'secret' => array( 
  480. 'description' => __( "Secret key used to generate a hash of the delivered webhook and provided in the request headers. This will default is a MD5 hash from the current user's ID|username if not provided.", 'woocommerce' ),  
  481. 'type' => 'string',  
  482. 'context' => array( 'edit' ),  
  483. ),  
  484. 'date_created' => array( 
  485. 'description' => __( "The date the webhook was created, in the site's timezone.", 'woocommerce' ),  
  486. 'type' => 'date-time',  
  487. 'context' => array( 'view', 'edit' ),  
  488. 'readonly' => true,  
  489. ),  
  490. 'date_modified' => array( 
  491. 'description' => __( "The date the webhook was last modified, in the site's timezone.", 'woocommerce' ),  
  492. 'type' => 'date-time',  
  493. 'context' => array( 'view', 'edit' ),  
  494. 'readonly' => true,  
  495. ),  
  496. ),  
  497. ); 
  498.  
  499. return $this->add_additional_fields_schema( $schema ); 
  500.  
  501. /** 
  502. * Get the query params for collections of attachments. 
  503. * @return array 
  504. */ 
  505. public function get_collection_params() { 
  506. $params = parent::get_collection_params(); 
  507.  
  508. $params['status'] = array( 
  509. 'default' => 'all',  
  510. 'description' => __( 'Limit result set to webhooks assigned a specific status.', 'woocommerce' ),  
  511. 'type' => 'string',  
  512. 'enum' => array( 'all', 'active', 'paused', 'disabled' ),  
  513. 'sanitize_callback' => 'sanitize_key',  
  514. 'validate_callback' => 'rest_validate_request_arg',  
  515. ); 
  516.  
  517. return $params;