WC_REST_Coupons_V1_Controller

REST API Coupons controller class.

Defined (1)

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

/includes/api/v1/class-wc-rest-coupons-controller.php  
  1. class WC_REST_Coupons_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 = 'coupons'; 
  14.  
  15. /** 
  16. * Post type. 
  17. * @var string 
  18. */ 
  19. protected $post_type = 'shop_coupon'; 
  20.  
  21. /** 
  22. * Coupons 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 coupons. 
  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. 'code' => array( 
  44. 'description' => __( 'Coupon code.', 'woocommerce' ),  
  45. 'required' => true,  
  46. 'type' => 'string',  
  47. ),  
  48. ) ),  
  49. ),  
  50. 'schema' => array( $this, 'get_public_item_schema' ),  
  51. ) ); 
  52.  
  53. register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P<id>[\d]+)', array( 
  54. 'args' => array( 
  55. 'id' => array( 
  56. 'description' => __( 'Unique identifier for the resource.', 'woocommerce' ),  
  57. 'type' => 'integer',  
  58. ),  
  59. ),  
  60. array( 
  61. 'methods' => WP_REST_Server::READABLE,  
  62. 'callback' => array( $this, 'get_item' ),  
  63. 'permission_callback' => array( $this, 'get_item_permissions_check' ),  
  64. 'args' => array( 
  65. 'context' => $this->get_context_param( array( 'default' => 'view' ) ),  
  66. ),  
  67. ),  
  68. array( 
  69. 'methods' => WP_REST_Server::EDITABLE,  
  70. 'callback' => array( $this, 'update_item' ),  
  71. 'permission_callback' => array( $this, 'update_item_permissions_check' ),  
  72. 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),  
  73. ),  
  74. array( 
  75. 'methods' => WP_REST_Server::DELETABLE,  
  76. 'callback' => array( $this, 'delete_item' ),  
  77. 'permission_callback' => array( $this, 'delete_item_permissions_check' ),  
  78. 'args' => array( 
  79. 'force' => array( 
  80. 'default' => false,  
  81. 'type' => 'boolean',  
  82. 'description' => __( 'Whether to bypass trash and force deletion.', 'woocommerce' ),  
  83. ),  
  84. ),  
  85. ),  
  86. 'schema' => array( $this, 'get_public_item_schema' ),  
  87. ) ); 
  88.  
  89. register_rest_route( $this->namespace, '/' . $this->rest_base . '/batch', array( 
  90. array( 
  91. 'methods' => WP_REST_Server::EDITABLE,  
  92. 'callback' => array( $this, 'batch_items' ),  
  93. 'permission_callback' => array( $this, 'batch_items_permissions_check' ),  
  94. 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),  
  95. ),  
  96. 'schema' => array( $this, 'get_public_batch_schema' ),  
  97. ) ); 
  98.  
  99. /** 
  100. * Query args. 
  101. * @param array $args Query args 
  102. * @param WP_REST_Request $request Request data. 
  103. * @return array 
  104. */ 
  105. public function query_args( $args, $request ) { 
  106. if ( ! empty( $request['code'] ) ) { 
  107. $id = wc_get_coupon_id_by_code( $request['code'] ); 
  108. $args['post__in'] = array( $id ); 
  109.  
  110. return $args; 
  111.  
  112. /** 
  113. * Prepare a single coupon output for response. 
  114. * @param WP_Post $post Post object. 
  115. * @param WP_REST_Request $request Request object. 
  116. * @return WP_REST_Response $data 
  117. */ 
  118. public function prepare_item_for_response( $post, $request ) { 
  119. $coupon = new WC_Coupon( (int) $post->ID ); 
  120. $_data = $coupon->get_data(); 
  121.  
  122. $format_decimal = array( 'amount', 'minimum_amount', 'maximum_amount' ); 
  123. $format_date = array( 'date_created', 'date_modified' ); 
  124. $format_date_utc = array( 'date_expires' ); 
  125. $format_null = array( 'usage_limit', 'usage_limit_per_user' ); 
  126.  
  127. // Format decimal values. 
  128. foreach ( $format_decimal as $key ) { 
  129. $_data[ $key ] = wc_format_decimal( $_data[ $key ], 2 ); 
  130.  
  131. // Format date values. 
  132. foreach ( $format_date as $key ) { 
  133. $_data[ $key ] = $_data[ $key ] ? wc_rest_prepare_date_response( $_data[ $key ], false ) : null; 
  134. foreach ( $format_date_utc as $key ) { 
  135. $_data[ $key ] = $_data[ $key ] ? wc_rest_prepare_date_response( $_data[ $key ] ) : null; 
  136.  
  137. // Format null values. 
  138. foreach ( $format_null as $key ) { 
  139. $_data[ $key ] = $_data[ $key ] ? $_data[ $key ] : null; 
  140.  
  141. $data = array( 
  142. 'id' => $_data['id'],  
  143. 'code' => $_data['code'],  
  144. 'date_created' => $_data['date_created'],  
  145. 'date_modified' => $_data['date_modified'],  
  146. 'discount_type' => $_data['discount_type'],  
  147. 'description' => $_data['description'],  
  148. 'amount' => $_data['amount'],  
  149. 'expiry_date' => $_data['date_expires'],  
  150. 'usage_count' => $_data['usage_count'],  
  151. 'individual_use' => $_data['individual_use'],  
  152. 'product_ids' => $_data['product_ids'],  
  153. 'exclude_product_ids' => $_data['excluded_product_ids'],  
  154. 'usage_limit' => $_data['usage_limit'],  
  155. 'usage_limit_per_user' => $_data['usage_limit_per_user'],  
  156. 'limit_usage_to_x_items' => $_data['limit_usage_to_x_items'],  
  157. 'free_shipping' => $_data['free_shipping'],  
  158. 'product_categories' => $_data['product_categories'],  
  159. 'excluded_product_categories' => $_data['excluded_product_categories'],  
  160. 'exclude_sale_items' => $_data['exclude_sale_items'],  
  161. 'minimum_amount' => $_data['minimum_amount'],  
  162. 'maximum_amount' => $_data['maximum_amount'],  
  163. 'email_restrictions' => $_data['email_restrictions'],  
  164. 'used_by' => $_data['used_by'],  
  165. ); 
  166.  
  167. $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; 
  168. $data = $this->add_additional_fields_to_object( $data, $request ); 
  169. $data = $this->filter_response_by_context( $data, $context ); 
  170. $response = rest_ensure_response( $data ); 
  171. $response->add_links( $this->prepare_links( $post, $request ) ); 
  172.  
  173. /** 
  174. * Filter the data for a response. 
  175. * The dynamic portion of the hook name, $this->post_type, refers to post_type of the post being 
  176. * prepared for the response. 
  177. * @param WP_REST_Response $response The response object. 
  178. * @param WP_Post $post Post object. 
  179. * @param WP_REST_Request $request Request object. 
  180. */ 
  181. return apply_filters( "woocommerce_rest_prepare_{$this->post_type}", $response, $post, $request ); 
  182.  
  183. /** 
  184. * Only reutrn writeable props from schema. 
  185. * @param array $schema 
  186. * @return bool 
  187. */ 
  188. protected function filter_writable_props( $schema ) { 
  189. return empty( $schema['readonly'] ); 
  190.  
  191. /** 
  192. * Prepare a single coupon for create or update. 
  193. * @param WP_REST_Request $request Request object. 
  194. * @return WP_Error|stdClass $data Post object. 
  195. */ 
  196. protected function prepare_item_for_database( $request ) { 
  197. $id = isset( $request['id'] ) ? absint( $request['id'] ) : 0; 
  198. $coupon = new WC_Coupon( $id ); 
  199. $schema = $this->get_item_schema(); 
  200. $data_keys = array_keys( array_filter( $schema['properties'], array( $this, 'filter_writable_props' ) ) ); 
  201.  
  202. // Update to schema to make compatible with CRUD schema. 
  203. if ( $request['exclude_product_ids'] ) { 
  204. $request['excluded_product_ids'] = $request['exclude_product_ids']; 
  205. if ( $request['expiry_date'] ) { 
  206. $request['date_expires'] = $request['expiry_date']; 
  207.  
  208. // Validate required POST fields. 
  209. if ( 'POST' === $request->get_method() && 0 === $coupon->get_id() ) { 
  210. if ( empty( $request['code'] ) ) { 
  211. return new WP_Error( 'woocommerce_rest_empty_coupon_code', sprintf( __( 'The coupon code cannot be empty.', 'woocommerce' ), 'code' ), array( 'status' => 400 ) ); 
  212.  
  213. // Handle all writable props. 
  214. foreach ( $data_keys as $key ) { 
  215. $value = $request[ $key ]; 
  216.  
  217. if ( ! is_null( $value ) ) { 
  218. switch ( $key ) { 
  219. case 'code' : 
  220. $coupon_code = wc_format_coupon_code( $value ); 
  221. $id = $coupon->get_id() ? $coupon->get_id() : 0; 
  222. $id_from_code = wc_get_coupon_id_by_code( $coupon_code, $id ); 
  223.  
  224. if ( $id_from_code ) { 
  225. return new WP_Error( 'woocommerce_rest_coupon_code_already_exists', __( 'The coupon code already exists', 'woocommerce' ), array( 'status' => 400 ) ); 
  226.  
  227. $coupon->set_code( $coupon_code ); 
  228. break; 
  229. case 'description' : 
  230. $coupon->set_description( wp_filter_post_kses( $value ) ); 
  231. break; 
  232. case 'expiry_date' : 
  233. $coupon->set_date_expires( $value ); 
  234. break; 
  235. default : 
  236. if ( is_callable( array( $coupon, "set_{$key}" ) ) ) { 
  237. $coupon->{"set_{$key}"}( $value ); 
  238. break; 
  239.  
  240. /** 
  241. * Filter the query_vars used in `get_items` for the constructed query. 
  242. * The dynamic portion of the hook name, $this->post_type, refers to post_type of the post being 
  243. * prepared for insertion. 
  244. * @param WC_Coupon $coupon The coupon object. 
  245. * @param WP_REST_Request $request Request object. 
  246. */ 
  247. return apply_filters( "woocommerce_rest_pre_insert_{$this->post_type}", $coupon, $request ); 
  248.  
  249. /** 
  250. * Create a single item. 
  251. * @param WP_REST_Request $request Full details about the request. 
  252. * @return WP_Error|WP_REST_Response 
  253. */ 
  254. public function create_item( $request ) { 
  255. if ( ! empty( $request['id'] ) ) { 
  256. /** translators: %s: post type */ 
  257. return new WP_Error( "woocommerce_rest_{$this->post_type}_exists", sprintf( __( 'Cannot create existing %s.', 'woocommerce' ), $this->post_type ), array( 'status' => 400 ) ); 
  258.  
  259. $coupon_id = $this->save_coupon( $request ); 
  260. if ( is_wp_error( $coupon_id ) ) { 
  261. return $coupon_id; 
  262.  
  263. $post = get_post( $coupon_id ); 
  264. $this->update_additional_fields_for_object( $post, $request ); 
  265.  
  266. $this->add_post_meta_fields( $post, $request ); 
  267.  
  268. /** 
  269. * Fires after a single item is created or updated via the REST API. 
  270. * @param WP_Post $post Post object. 
  271. * @param WP_REST_Request $request Request object. 
  272. * @param boolean $creating True when creating item, false when updating. 
  273. */ 
  274. do_action( "woocommerce_rest_insert_{$this->post_type}", $post, $request, true ); 
  275. $request->set_param( 'context', 'edit' ); 
  276. $response = $this->prepare_item_for_response( $post, $request ); 
  277. $response = rest_ensure_response( $response ); 
  278. $response->set_status( 201 ); 
  279. $response->header( 'Location', rest_url( sprintf( '/%s/%s/%d', $this->namespace, $this->rest_base, $post->ID ) ) ); 
  280.  
  281. return $response; 
  282.  
  283. /** 
  284. * Update a single coupon. 
  285. * @param WP_REST_Request $request Full details about the request. 
  286. * @return WP_Error|WP_REST_Response 
  287. */ 
  288. public function update_item( $request ) { 
  289. try { 
  290. $post_id = (int) $request['id']; 
  291.  
  292. if ( empty( $post_id ) || get_post_type( $post_id ) !== $this->post_type ) { 
  293. return new WP_Error( "woocommerce_rest_{$this->post_type}_invalid_id", __( 'ID is invalid.', 'woocommerce' ), array( 'status' => 400 ) ); 
  294.  
  295. $coupon_id = $this->save_coupon( $request ); 
  296. if ( is_wp_error( $coupon_id ) ) { 
  297. return $coupon_id; 
  298.  
  299. $post = get_post( $coupon_id ); 
  300. $this->update_additional_fields_for_object( $post, $request ); 
  301.  
  302. /** 
  303. * Fires after a single item is created or updated via the REST API. 
  304. * @param WP_Post $post Post object. 
  305. * @param WP_REST_Request $request Request object. 
  306. * @param boolean $creating True when creating item, false when updating. 
  307. */ 
  308. do_action( "woocommerce_rest_insert_{$this->post_type}", $post, $request, false ); 
  309. $request->set_param( 'context', 'edit' ); 
  310. $response = $this->prepare_item_for_response( $post, $request ); 
  311. return rest_ensure_response( $response ); 
  312.  
  313. } catch ( Exception $e ) { 
  314. return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) ); 
  315.  
  316. /** 
  317. * Saves a coupon to the database. 
  318. * @since 3.0.0 
  319. * @param WP_REST_Request $request Full details about the request. 
  320. * @return WP_Error|int 
  321. */ 
  322. protected function save_coupon( $request ) { 
  323. try { 
  324. $coupon = $this->prepare_item_for_database( $request ); 
  325.  
  326. if ( is_wp_error( $coupon ) ) { 
  327. return $coupon; 
  328.  
  329. $coupon->save(); 
  330. return $coupon->get_id(); 
  331. } catch ( WC_Data_Exception $e ) { 
  332. return new WP_Error( $e->getErrorCode(), $e->getMessage(), $e->getErrorData() ); 
  333. } catch ( WC_REST_Exception $e ) { 
  334. return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) ); 
  335.  
  336. /** 
  337. * Get the Coupon's schema, conforming to JSON Schema. 
  338. * @return array 
  339. */ 
  340. public function get_item_schema() { 
  341. $schema = array( 
  342. '$schema' => 'http://json-schema.org/draft-04/schema#',  
  343. 'title' => $this->post_type,  
  344. 'type' => 'object',  
  345. 'properties' => array( 
  346. 'id' => array( 
  347. 'description' => __( 'Unique identifier for the object.', 'woocommerce' ),  
  348. 'type' => 'integer',  
  349. 'context' => array( 'view', 'edit' ),  
  350. 'readonly' => true,  
  351. ),  
  352. 'code' => array( 
  353. 'description' => __( 'Coupon code.', 'woocommerce' ),  
  354. 'type' => 'string',  
  355. 'context' => array( 'view', 'edit' ),  
  356. ),  
  357. 'date_created' => array( 
  358. 'description' => __( "The date the coupon was created, in the site's timezone.", 'woocommerce' ),  
  359. 'type' => 'date-time',  
  360. 'context' => array( 'view', 'edit' ),  
  361. 'readonly' => true,  
  362. ),  
  363. 'date_modified' => array( 
  364. 'description' => __( "The date the coupon was last modified, in the site's timezone.", 'woocommerce' ),  
  365. 'type' => 'date-time',  
  366. 'context' => array( 'view', 'edit' ),  
  367. 'readonly' => true,  
  368. ),  
  369. 'description' => array( 
  370. 'description' => __( 'Coupon description.', 'woocommerce' ),  
  371. 'type' => 'string',  
  372. 'context' => array( 'view', 'edit' ),  
  373. ),  
  374. 'discount_type' => array( 
  375. 'description' => __( 'Determines the type of discount that will be applied.', 'woocommerce' ),  
  376. 'type' => 'string',  
  377. 'default' => 'fixed_cart',  
  378. 'enum' => array_keys( wc_get_coupon_types() ),  
  379. 'context' => array( 'view', 'edit' ),  
  380. ),  
  381. 'amount' => array( 
  382. 'description' => __( 'The amount of discount. Should always be numeric, even if setting a percentage.', 'woocommerce' ),  
  383. 'type' => 'string',  
  384. 'context' => array( 'view', 'edit' ),  
  385. ),  
  386. 'expiry_date' => array( 
  387. 'description' => __( 'UTC DateTime when the coupon expires.', 'woocommerce' ),  
  388. 'type' => 'string',  
  389. 'context' => array( 'view', 'edit' ),  
  390. ),  
  391. 'usage_count' => array( 
  392. 'description' => __( 'Number of times the coupon has been used already.', 'woocommerce' ),  
  393. 'type' => 'integer',  
  394. 'context' => array( 'view', 'edit' ),  
  395. 'readonly' => true,  
  396. ),  
  397. 'individual_use' => array( 
  398. 'description' => __( 'If true, the coupon can only be used individually. Other applied coupons will be removed from the cart.', 'woocommerce' ),  
  399. 'type' => 'boolean',  
  400. 'default' => false,  
  401. 'context' => array( 'view', 'edit' ),  
  402. ),  
  403. 'product_ids' => array( 
  404. 'description' => __( "List of product IDs the coupon can be used on.", 'woocommerce' ),  
  405. 'type' => 'array',  
  406. 'items' => array( 
  407. 'type' => 'integer',  
  408. ),  
  409. 'context' => array( 'view', 'edit' ),  
  410. ),  
  411. 'exclude_product_ids' => array( 
  412. 'description' => __( "List of product IDs the coupon cannot be used on.", 'woocommerce' ),  
  413. 'type' => 'array',  
  414. 'items' => array( 
  415. 'type' => 'integer',  
  416. ),  
  417. 'context' => array( 'view', 'edit' ),  
  418. ),  
  419. 'usage_limit' => array( 
  420. 'description' => __( 'How many times the coupon can be used in total.', 'woocommerce' ),  
  421. 'type' => 'integer',  
  422. 'context' => array( 'view', 'edit' ),  
  423. ),  
  424. 'usage_limit_per_user' => array( 
  425. 'description' => __( 'How many times the coupon can be used per customer.', 'woocommerce' ),  
  426. 'type' => 'integer',  
  427. 'context' => array( 'view', 'edit' ),  
  428. ),  
  429. 'limit_usage_to_x_items' => array( 
  430. 'description' => __( 'Max number of items in the cart the coupon can be applied to.', 'woocommerce' ),  
  431. 'type' => 'integer',  
  432. 'context' => array( 'view', 'edit' ),  
  433. ),  
  434. 'free_shipping' => array( 
  435. 'description' => __( 'If true and if the free shipping method requires a coupon, this coupon will enable free shipping.', 'woocommerce' ),  
  436. 'type' => 'boolean',  
  437. 'default' => false,  
  438. 'context' => array( 'view', 'edit' ),  
  439. ),  
  440. 'product_categories' => array( 
  441. 'description' => __( "List of category IDs the coupon applies to.", 'woocommerce' ),  
  442. 'type' => 'array',  
  443. 'items' => array( 
  444. 'type' => 'integer',  
  445. ),  
  446. 'context' => array( 'view', 'edit' ),  
  447. ),  
  448. 'excluded_product_categories' => array( 
  449. 'description' => __( "List of category IDs the coupon does not apply to.", 'woocommerce' ),  
  450. 'type' => 'array',  
  451. 'items' => array( 
  452. 'type' => 'integer',  
  453. ),  
  454. 'context' => array( 'view', 'edit' ),  
  455. ),  
  456. 'exclude_sale_items' => array( 
  457. 'description' => __( 'If true, this coupon will not be applied to items that have sale prices.', 'woocommerce' ),  
  458. 'type' => 'boolean',  
  459. 'default' => false,  
  460. 'context' => array( 'view', 'edit' ),  
  461. ),  
  462. 'minimum_amount' => array( 
  463. 'description' => __( 'Minimum order amount that needs to be in the cart before coupon applies.', 'woocommerce' ),  
  464. 'type' => 'string',  
  465. 'context' => array( 'view', 'edit' ),  
  466. ),  
  467. 'maximum_amount' => array( 
  468. 'description' => __( 'Maximum order amount allowed when using the coupon.', 'woocommerce' ),  
  469. 'type' => 'string',  
  470. 'context' => array( 'view', 'edit' ),  
  471. ),  
  472. 'email_restrictions' => array( 
  473. 'description' => __( 'List of email addresses that can use this coupon.', 'woocommerce' ),  
  474. 'type' => 'array',  
  475. 'items' => array( 
  476. 'type' => 'string',  
  477. ),  
  478. 'context' => array( 'view', 'edit' ),  
  479. ),  
  480. 'used_by' => array( 
  481. 'description' => __( 'List of user IDs (or guest email addresses) that have used the coupon.', 'woocommerce' ),  
  482. 'type' => 'array',  
  483. 'items' => array( 
  484. 'type' => 'integer',  
  485. ),  
  486. 'context' => array( 'view', 'edit' ),  
  487. 'readonly' => true,  
  488. ),  
  489. ),  
  490. ); 
  491.  
  492. return $this->add_additional_fields_schema( $schema ); 
  493.  
  494. /** 
  495. * Get the query params for collections of attachments. 
  496. * @return array 
  497. */ 
  498. public function get_collection_params() { 
  499. $params = parent::get_collection_params(); 
  500.  
  501. $params['code'] = array( 
  502. 'description' => __( 'Limit result set to resources with a specific code.', 'woocommerce' ),  
  503. 'type' => 'string',  
  504. 'sanitize_callback' => 'sanitize_text_field',  
  505. 'validate_callback' => 'rest_validate_request_arg',  
  506. ); 
  507.  
  508. return $params;