/includes/api/class-wc-rest-product-variations-controller.php

  1. <?php 
  2. /** 
  3. * REST API variations controller 
  4. * 
  5. * Handles requests to the /products/<product_id>/variations endpoints. 
  6. * 
  7. * @author WooThemes 
  8. * @category API 
  9. * @package WooCommerce/API 
  10. * @since 3.0.0 
  11. */ 
  12.  
  13. if ( ! defined( 'ABSPATH' ) ) { 
  14. exit; 
  15.  
  16. /** 
  17. * REST API variations controller class. 
  18. * 
  19. * @package WooCommerce/API 
  20. * @extends WC_REST_Products_Controller 
  21. */ 
  22. class WC_REST_Product_Variations_Controller extends WC_REST_Products_Controller { 
  23.  
  24. /** 
  25. * Endpoint namespace. 
  26. * 
  27. * @var string 
  28. */ 
  29. protected $namespace = 'wc/v2'; 
  30.  
  31. /** 
  32. * Route base. 
  33. * 
  34. * @var string 
  35. */ 
  36. protected $rest_base = 'products/(?P<product_id>[\d]+)/variations'; 
  37.  
  38. /** 
  39. * Post type. 
  40. * 
  41. * @var string 
  42. */ 
  43. protected $post_type = 'product_variation'; 
  44.  
  45. /** 
  46. * Initialize product actions (parent). 
  47. */ 
  48. public function __construct() { 
  49. add_filter( "woocommerce_rest_{$this->post_type}_query", array( $this, 'add_product_id' ), 9, 2 ); 
  50. parent::__construct(); 
  51.  
  52. /** 
  53. * Register the routes for products. 
  54. */ 
  55. public function register_routes() { 
  56. register_rest_route( $this->namespace, '/' . $this->rest_base, array( 
  57. 'args' => array( 
  58. 'product_id' => array( 
  59. 'description' => __( 'Unique identifier for the variable product.', 'woocommerce' ),  
  60. 'type' => 'integer',  
  61. ),  
  62. ),  
  63. array( 
  64. 'methods' => WP_REST_Server::READABLE,  
  65. 'callback' => array( $this, 'get_items' ),  
  66. 'permission_callback' => array( $this, 'get_items_permissions_check' ),  
  67. 'args' => $this->get_collection_params(),  
  68. ),  
  69. array( 
  70. 'methods' => WP_REST_Server::CREATABLE,  
  71. 'callback' => array( $this, 'create_item' ),  
  72. 'permission_callback' => array( $this, 'create_item_permissions_check' ),  
  73. 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ),  
  74. ),  
  75. 'schema' => array( $this, 'get_public_item_schema' ),  
  76. ) ); 
  77. register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P<id>[\d]+)', array( 
  78. 'args' => array( 
  79. 'product_id' => array( 
  80. 'description' => __( 'Unique identifier for the variable product.', 'woocommerce' ),  
  81. 'type' => 'integer',  
  82. ),  
  83. 'id' => array( 
  84. 'description' => __( 'Unique identifier for the variation.', 'woocommerce' ),  
  85. 'type' => 'integer',  
  86. ),  
  87. ),  
  88. array( 
  89. 'methods' => WP_REST_Server::READABLE,  
  90. 'callback' => array( $this, 'get_item' ),  
  91. 'permission_callback' => array( $this, 'get_item_permissions_check' ),  
  92. 'args' => array( 
  93. 'context' => $this->get_context_param( array( 'default' => 'view' ) ),  
  94. ),  
  95. ),  
  96. array( 
  97. 'methods' => WP_REST_Server::EDITABLE,  
  98. 'callback' => array( $this, 'update_item' ),  
  99. 'permission_callback' => array( $this, 'update_item_permissions_check' ),  
  100. 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),  
  101. ),  
  102. array( 
  103. 'methods' => WP_REST_Server::DELETABLE,  
  104. 'callback' => array( $this, 'delete_item' ),  
  105. 'permission_callback' => array( $this, 'delete_item_permissions_check' ),  
  106. 'args' => array( 
  107. 'force' => array( 
  108. 'default' => false,  
  109. 'type' => 'boolean',  
  110. 'description' => __( 'Whether to bypass trash and force deletion.', 'woocommerce' ),  
  111. ),  
  112. ),  
  113. ),  
  114. 'schema' => array( $this, 'get_public_item_schema' ),  
  115. ) ); 
  116. register_rest_route( $this->namespace, '/' . $this->rest_base . '/batch', array( 
  117. 'args' => array( 
  118. 'product_id' => array( 
  119. 'description' => __( 'Unique identifier for the variable product.', 'woocommerce' ),  
  120. 'type' => 'integer',  
  121. ),  
  122. ),  
  123. array( 
  124. 'methods' => WP_REST_Server::EDITABLE,  
  125. 'callback' => array( $this, 'batch_items' ),  
  126. 'permission_callback' => array( $this, 'batch_items_permissions_check' ),  
  127. 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),  
  128. ),  
  129. 'schema' => array( $this, 'get_public_batch_schema' ),  
  130. ) ); 
  131.  
  132. /** 
  133. * Get object. 
  134. * 
  135. * @since 3.0.0 
  136. * @param int $id Object ID. 
  137. * @return WC_Data 
  138. */ 
  139. protected function get_object( $id ) { 
  140. return wc_get_product( $id ); 
  141.  
  142. /** 
  143. * Prepare a single variation output for response. 
  144. * 
  145. * @since 3.0.0 
  146. * @param WC_Data $object Object data. 
  147. * @param WP_REST_Request $request Request object. 
  148. * @return WP_REST_Response 
  149. */ 
  150. public function prepare_object_for_response( $object, $request ) { 
  151. $data = array( 
  152. 'id' => $object->get_id(),  
  153. 'date_created' => wc_rest_prepare_date_response( $object->get_date_created(), false ),  
  154. 'date_created_gmt' => wc_rest_prepare_date_response( $object->get_date_created() ),  
  155. 'date_modified' => wc_rest_prepare_date_response( $object->get_date_modified(), false ),  
  156. 'date_modified_gmt' => wc_rest_prepare_date_response( $object->get_date_modified() ),  
  157. 'description' => wc_format_content( $object->get_description() ),  
  158. 'permalink' => $object->get_permalink(),  
  159. 'sku' => $object->get_sku(),  
  160. 'price' => $object->get_price(),  
  161. 'regular_price' => $object->get_regular_price(),  
  162. 'sale_price' => $object->get_sale_price(),  
  163. 'date_on_sale_from' => wc_rest_prepare_date_response( $object->get_date_on_sale_from(), false ),  
  164. 'date_on_sale_from_gmt' => wc_rest_prepare_date_response( $object->get_date_on_sale_from() ),  
  165. 'date_on_sale_to' => wc_rest_prepare_date_response( $object->get_date_on_sale_to(), false ),  
  166. 'date_on_sale_to_gmt' => wc_rest_prepare_date_response( $object->get_date_on_sale_to() ),  
  167. 'on_sale' => $object->is_on_sale(),  
  168. 'visible' => $object->is_visible(),  
  169. 'purchasable' => $object->is_purchasable(),  
  170. 'virtual' => $object->is_virtual(),  
  171. 'downloadable' => $object->is_downloadable(),  
  172. 'downloads' => $this->get_downloads( $object ),  
  173. 'download_limit' => '' !== $object->get_download_limit() ? (int) $object->get_download_limit() : -1,  
  174. 'download_expiry' => '' !== $object->get_download_expiry() ? (int) $object->get_download_expiry() : -1,  
  175. 'tax_status' => $object->get_tax_status(),  
  176. 'tax_class' => $object->get_tax_class(),  
  177. 'manage_stock' => $object->managing_stock(),  
  178. 'stock_quantity' => $object->get_stock_quantity(),  
  179. 'in_stock' => $object->is_in_stock(),  
  180. 'backorders' => $object->get_backorders(),  
  181. 'backorders_allowed' => $object->backorders_allowed(),  
  182. 'backordered' => $object->is_on_backorder(),  
  183. 'weight' => $object->get_weight(),  
  184. 'dimensions' => array( 
  185. 'length' => $object->get_length(),  
  186. 'width' => $object->get_width(),  
  187. 'height' => $object->get_height(),  
  188. ),  
  189. 'shipping_class' => $object->get_shipping_class(),  
  190. 'shipping_class_id' => $object->get_shipping_class_id(),  
  191. 'image' => current( $this->get_images( $object ) ),  
  192. 'attributes' => $this->get_attributes( $object ),  
  193. 'menu_order' => $object->get_menu_order(),  
  194. 'meta_data' => $object->get_meta_data(),  
  195. ); 
  196.  
  197. $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; 
  198. $data = $this->add_additional_fields_to_object( $data, $request ); 
  199. $data = $this->filter_response_by_context( $data, $context ); 
  200. $response = rest_ensure_response( $data ); 
  201. $response->add_links( $this->prepare_links( $object, $request ) ); 
  202.  
  203. /** 
  204. * Filter the data for a response. 
  205. * 
  206. * The dynamic portion of the hook name, $this->post_type,  
  207. * refers to object type being prepared for the response. 
  208. * 
  209. * @param WP_REST_Response $response The response object. 
  210. * @param WC_Data $object Object data. 
  211. * @param WP_REST_Request $request Request object. 
  212. */ 
  213. return apply_filters( "woocommerce_rest_prepare_{$this->post_type}_object", $response, $object, $request ); 
  214.  
  215. /** 
  216. * Prepare objects query. 
  217. * 
  218. * @since 3.0.0 
  219. * @param WP_REST_Request $request Full details about the request. 
  220. * @return array 
  221. */ 
  222. protected function prepare_objects_query( $request ) { 
  223. $args = parent::prepare_objects_query( $request ); 
  224.  
  225. $args['post_parent'] = $request['product_id']; 
  226.  
  227. return $args; 
  228.  
  229. /** 
  230. * Prepare a single variation for create or update. 
  231. * 
  232. * @param WP_REST_Request $request Request object. 
  233. * @param bool $creating If is creating a new object. 
  234. * @return WP_Error|WC_Data 
  235. */ 
  236. protected function prepare_object_for_database( $request, $creating = false ) { 
  237. if ( isset( $request['id'] ) ) { 
  238. $variation = wc_get_product( absint( $request['id'] ) ); 
  239. } else { 
  240. $variation = new WC_Product_Variation(); 
  241.  
  242. $variation->set_parent_id( absint( $request['product_id'] ) ); 
  243.  
  244. // Status. 
  245. if ( isset( $request['visible'] ) ) { 
  246. $variation->set_status( false === $request['visible'] ? 'private' : 'publish' ); 
  247.  
  248. // SKU. 
  249. if ( isset( $request['sku'] ) ) { 
  250. $variation->set_sku( wc_clean( $request['sku'] ) ); 
  251.  
  252. // Thumbnail. 
  253. if ( isset( $request['image'] ) ) { 
  254. if ( is_array( $request['image'] ) ) { 
  255. $image = $request['image']; 
  256. if ( is_array( $image ) ) { 
  257. $image['position'] = 0; 
  258.  
  259. $variation = $this->set_product_images( $variation, array( $image ) ); 
  260. } else { 
  261. $variation->set_image_id( '' ); 
  262.  
  263. // Virtual variation. 
  264. if ( isset( $request['virtual'] ) ) { 
  265. $variation->set_virtual( $request['virtual'] ); 
  266.  
  267. // Downloadable variation. 
  268. if ( isset( $request['downloadable'] ) ) { 
  269. $variation->set_downloadable( $request['downloadable'] ); 
  270.  
  271. // Downloads. 
  272. if ( $variation->get_downloadable() ) { 
  273. // Downloadable files. 
  274. if ( isset( $request['downloads'] ) && is_array( $request['downloads'] ) ) { 
  275. $variation = $this->save_downloadable_files( $variation, $request['downloads'] ); 
  276.  
  277. // Download limit. 
  278. if ( isset( $request['download_limit'] ) ) { 
  279. $variation->set_download_limit( $request['download_limit'] ); 
  280.  
  281. // Download expiry. 
  282. if ( isset( $request['download_expiry'] ) ) { 
  283. $variation->set_download_expiry( $request['download_expiry'] ); 
  284.  
  285. // Shipping data. 
  286. $variation = $this->save_product_shipping_data( $variation, $request ); 
  287.  
  288. // Stock handling. 
  289. if ( isset( $request['manage_stock'] ) ) { 
  290. $variation->set_manage_stock( $request['manage_stock'] ); 
  291.  
  292. if ( isset( $request['in_stock'] ) ) { 
  293. $variation->set_stock_status( true === $request['in_stock'] ? 'instock' : 'outofstock' ); 
  294.  
  295. if ( isset( $request['backorders'] ) ) { 
  296. $variation->set_backorders( $request['backorders'] ); 
  297.  
  298. if ( $variation->get_manage_stock() ) { 
  299. if ( isset( $request['stock_quantity'] ) ) { 
  300. $variation->set_stock_quantity( $request['stock_quantity'] ); 
  301. } elseif ( isset( $request['inventory_delta'] ) ) { 
  302. $stock_quantity = wc_stock_amount( $variation->get_stock_quantity() ); 
  303. $stock_quantity += wc_stock_amount( $request['inventory_delta'] ); 
  304. $variation->set_stock_quantity( $stock_quantity ); 
  305. } else { 
  306. $variation->set_backorders( 'no' ); 
  307. $variation->set_stock_quantity( '' ); 
  308.  
  309. // Regular Price. 
  310. if ( isset( $request['regular_price'] ) ) { 
  311. $variation->set_regular_price( $request['regular_price'] ); 
  312.  
  313. // Sale Price. 
  314. if ( isset( $request['sale_price'] ) ) { 
  315. $variation->set_sale_price( $request['sale_price'] ); 
  316.  
  317. if ( isset( $request['date_on_sale_from'] ) ) { 
  318. $variation->set_date_on_sale_from( $request['date_on_sale_from'] ); 
  319.  
  320. if ( isset( $request['date_on_sale_from_gmt'] ) ) { 
  321. $variation->set_date_on_sale_from( $request['date_on_sale_from_gmt'] ? strtotime( $request['date_on_sale_from_gmt'] ) : null ); 
  322.  
  323. if ( isset( $request['date_on_sale_to'] ) ) { 
  324. $variation->set_date_on_sale_to( $request['date_on_sale_to'] ); 
  325.  
  326. if ( isset( $request['date_on_sale_to_gmt'] ) ) { 
  327. $variation->set_date_on_sale_to( $request['date_on_sale_to_gmt'] ? strtotime( $request['date_on_sale_to_gmt'] ) : null ); 
  328.  
  329. // Tax class. 
  330. if ( isset( $request['tax_class'] ) ) { 
  331. $variation->set_tax_class( $request['tax_class'] ); 
  332.  
  333. // Description. 
  334. if ( isset( $request['description'] ) ) { 
  335. $variation->set_description( wp_kses_post( $request['description'] ) ); 
  336.  
  337. // Update taxonomies. 
  338. if ( isset( $request['attributes'] ) ) { 
  339. $attributes = array(); 
  340. $parent = wc_get_product( $variation->get_parent_id() ); 
  341. $parent_attributes = $parent->get_attributes(); 
  342.  
  343. foreach ( $request['attributes'] as $attribute ) { 
  344. $attribute_id = 0; 
  345. $attribute_name = ''; 
  346.  
  347. // Check ID for global attributes or name for product attributes. 
  348. if ( ! empty( $attribute['id'] ) ) { 
  349. $attribute_id = absint( $attribute['id'] ); 
  350. $attribute_name = wc_attribute_taxonomy_name_by_id( $attribute_id ); 
  351. } elseif ( ! empty( $attribute['name'] ) ) { 
  352. $attribute_name = sanitize_title( $attribute['name'] ); 
  353.  
  354. if ( ! $attribute_id && ! $attribute_name ) { 
  355. continue; 
  356.  
  357. if ( ! isset( $parent_attributes[ $attribute_name ] ) || ! $parent_attributes[ $attribute_name ]->get_variation() ) { 
  358. continue; 
  359.  
  360. $attribute_key = sanitize_title( $parent_attributes[ $attribute_name ]->get_name() ); 
  361. $attribute_value = isset( $attribute['option'] ) ? wc_clean( stripslashes( $attribute['option'] ) ) : ''; 
  362.  
  363. if ( $parent_attributes[ $attribute_name ]->is_taxonomy() ) { 
  364. // If dealing with a taxonomy, we need to get the slug from the name posted to the API. 
  365. $term = get_term_by( 'name', $attribute_value, $attribute_name ); 
  366.  
  367. if ( $term && ! is_wp_error( $term ) ) { 
  368. $attribute_value = $term->slug; 
  369. } else { 
  370. $attribute_value = sanitize_title( $attribute_value ); 
  371.  
  372. $attributes[ $attribute_key ] = $attribute_value; 
  373.  
  374. $variation->set_attributes( $attributes ); 
  375.  
  376. // Menu order. 
  377. if ( $request['menu_order'] ) { 
  378. $variation->set_menu_order( $request['menu_order'] ); 
  379.  
  380. // Meta data. 
  381. if ( is_array( $request['meta_data'] ) ) { 
  382. foreach ( $request['meta_data'] as $meta ) { 
  383. $variation->update_meta_data( $meta['key'], $meta['value'], isset( $meta['id'] ) ? $meta['id'] : '' ); 
  384.  
  385. /** 
  386. * Filters an object before it is inserted via the REST API. 
  387. * 
  388. * The dynamic portion of the hook name, `$this->post_type`,  
  389. * refers to the object type slug. 
  390. * 
  391. * @param WC_Data $variation Object object. 
  392. * @param WP_REST_Request $request Request object. 
  393. * @param bool $creating If is creating a new object. 
  394. */ 
  395. return apply_filters( "woocommerce_rest_pre_insert_{$this->post_type}_object", $variation, $request, $creating ); 
  396.  
  397. /** 
  398. * Clear caches here so in sync with any new variations. 
  399. * 
  400. * @param WC_Data $object Object data. 
  401. */ 
  402. public function clear_transients( $object ) { 
  403. wc_delete_product_transients( $object->get_parent_id() ); 
  404. wp_cache_delete( 'product-' . $object->get_parent_id(), 'products' ); 
  405.  
  406. /** 
  407. * Delete a variation. 
  408. * 
  409. * @param WP_REST_Request $request Full details about the request 
  410. * @return WP_Error|boolean 
  411. */ 
  412. public function delete_item( $request ) { 
  413. $id = absint( is_array( $request['id'] ) ? $request['id']['id'] : $request['id'] ); 
  414. $force = (bool) $request['force']; 
  415. $object = $this->get_object( (int) $request['id'] ); 
  416. $result = false; 
  417.  
  418. if ( ! $object || 0 === $object->get_id() ) { 
  419. return new WP_Error( "woocommerce_rest_{$this->post_type}_invalid_id", __( 'Invalid ID.', 'woocommerce' ), array( 'status' => 404 ) ); 
  420.  
  421. $supports_trash = EMPTY_TRASH_DAYS > 0 && is_callable( array( $object, 'get_status' ) ); 
  422.  
  423. /** 
  424. * Filter whether an object is trashable. 
  425. * 
  426. * Return false to disable trash support for the object. 
  427. * 
  428. * @param boolean $supports_trash Whether the object type support trashing. 
  429. * @param WC_Data $object The object being considered for trashing support. 
  430. */ 
  431. $supports_trash = apply_filters( "woocommerce_rest_{$this->post_type}_object_trashable", $supports_trash, $object ); 
  432.  
  433. if ( ! wc_rest_check_post_permissions( $this->post_type, 'delete', $object->get_id() ) ) { 
  434. /** translators: %s: post type */ 
  435. return new WP_Error( "woocommerce_rest_user_cannot_delete_{$this->post_type}", sprintf( __( 'Sorry, you are not allowed to delete %s.', 'woocommerce' ), $this->post_type ), array( 'status' => rest_authorization_required_code() ) ); 
  436.  
  437. $request->set_param( 'context', 'edit' ); 
  438. $response = $this->prepare_object_for_response( $object, $request ); 
  439.  
  440. // If we're forcing, then delete permanently. 
  441. if ( $force ) { 
  442. $object->delete( true ); 
  443. $result = 0 === $object->get_id(); 
  444. } else { 
  445. // If we don't support trashing for this type, error out. 
  446. if ( ! $supports_trash ) { 
  447. /** translators: %s: post type */ 
  448. return new WP_Error( 'woocommerce_rest_trash_not_supported', sprintf( __( 'The %s does not support trashing.', 'woocommerce' ), $this->post_type ), array( 'status' => 501 ) ); 
  449.  
  450. // Otherwise, only trash if we haven't already. 
  451. if ( is_callable( array( $object, 'get_status' ) ) ) { 
  452. if ( 'trash' === $object->get_status() ) { 
  453. /** translators: %s: post type */ 
  454. return new WP_Error( 'woocommerce_rest_already_trashed', sprintf( __( 'The %s has already been deleted.', 'woocommerce' ), $this->post_type ), array( 'status' => 410 ) ); 
  455.  
  456. $object->delete(); 
  457. $result = 'trash' === $object->get_status(); 
  458.  
  459. if ( ! $result ) { 
  460. /** translators: %s: post type */ 
  461. return new WP_Error( 'woocommerce_rest_cannot_delete', sprintf( __( 'The %s cannot be deleted.', 'woocommerce' ), $this->post_type ), array( 'status' => 500 ) ); 
  462.  
  463. // Delete parent product transients. 
  464. if ( 0 !== $object->get_parent_id() ) { 
  465. wc_delete_product_transients( $object->get_parent_id() ); 
  466.  
  467. /** 
  468. * Fires after a single object is deleted or trashed via the REST API. 
  469. * 
  470. * @param WC_Data $object The deleted or trashed object. 
  471. * @param WP_REST_Response $response The response data. 
  472. * @param WP_REST_Request $request The request sent to the API. 
  473. */ 
  474. do_action( "woocommerce_rest_delete_{$this->post_type}_object", $object, $response, $request ); 
  475.  
  476. return $response; 
  477.  
  478. /** 
  479. * Bulk create, update and delete items. 
  480. * 
  481. * @since 3.0.0 
  482. * @param WP_REST_Request $request Full details about the request. 
  483. * @return array Of WP_Error or WP_REST_Response. 
  484. */ 
  485. public function batch_items( $request ) { 
  486. $items = array_filter( $request->get_params() ); 
  487. $params = $request->get_url_params(); 
  488. $product_id = $params['product_id']; 
  489. $body_params = array(); 
  490.  
  491. foreach ( array( 'update', 'create', 'delete' ) as $batch_type ) { 
  492. if ( ! empty( $items[ $batch_type ] ) ) { 
  493. $injected_items = array(); 
  494. foreach ( $items[ $batch_type ] as $item ) { 
  495. $injected_items[] = is_array( $item ) ? array_merge( array( 'product_id' => $product_id ), $item ) : $item; 
  496. $body_params[ $batch_type ] = $injected_items; 
  497.  
  498. $request = new WP_REST_Request( $request->get_method() ); 
  499. $request->set_body_params( $body_params ); 
  500.  
  501. return parent::batch_items( $request ); 
  502.  
  503. /** 
  504. * Prepare links for the request. 
  505. * 
  506. * @param WC_Data $object Object data. 
  507. * @param WP_REST_Request $request Request object. 
  508. * @return array Links for the given post. 
  509. */ 
  510. protected function prepare_links( $object, $request ) { 
  511. $product_id = (int) $request['product_id']; 
  512. $base = str_replace( '(?P<product_id>[\d]+)', $product_id, $this->rest_base ); 
  513. $links = array( 
  514. 'self' => array( 
  515. 'href' => rest_url( sprintf( '/%s/%s/%d', $this->namespace, $base, $object->get_id() ) ),  
  516. ),  
  517. 'collection' => array( 
  518. 'href' => rest_url( sprintf( '/%s/%s', $this->namespace, $base ) ),  
  519. ),  
  520. 'up' => array( 
  521. 'href' => rest_url( sprintf( '/%s/products/%d', $this->namespace, $product_id ) ),  
  522. ),  
  523. ); 
  524. return $links; 
  525.  
  526. /** 
  527. * Get the Variation's schema, conforming to JSON Schema. 
  528. * 
  529. * @return array 
  530. */ 
  531. public function get_item_schema() { 
  532. $weight_unit = get_option( 'woocommerce_weight_unit' ); 
  533. $dimension_unit = get_option( 'woocommerce_dimension_unit' ); 
  534. $schema = array( 
  535. '$schema' => 'http://json-schema.org/draft-04/schema#',  
  536. 'title' => $this->post_type,  
  537. 'type' => 'object',  
  538. 'properties' => array( 
  539. 'id' => array( 
  540. 'description' => __( 'Unique identifier for the resource.', 'woocommerce' ),  
  541. 'type' => 'integer',  
  542. 'context' => array( 'view', 'edit' ),  
  543. 'readonly' => true,  
  544. ),  
  545. 'date_created' => array( 
  546. 'description' => __( "The date the variation was created, in the site's timezone.", 'woocommerce' ),  
  547. 'type' => 'date-time',  
  548. 'context' => array( 'view', 'edit' ),  
  549. 'readonly' => true,  
  550. ),  
  551. 'date_modified' => array( 
  552. 'description' => __( "The date the variation was last modified, in the site's timezone.", 'woocommerce' ),  
  553. 'type' => 'date-time',  
  554. 'context' => array( 'view', 'edit' ),  
  555. 'readonly' => true,  
  556. ),  
  557. 'description' => array( 
  558. 'description' => __( 'Variation description.', 'woocommerce' ),  
  559. 'type' => 'string',  
  560. 'context' => array( 'view', 'edit' ),  
  561. ),  
  562. 'permalink' => array( 
  563. 'description' => __( 'Variation URL.', 'woocommerce' ),  
  564. 'type' => 'string',  
  565. 'format' => 'uri',  
  566. 'context' => array( 'view', 'edit' ),  
  567. 'readonly' => true,  
  568. ),  
  569. 'sku' => array( 
  570. 'description' => __( 'Unique identifier.', 'woocommerce' ),  
  571. 'type' => 'string',  
  572. 'context' => array( 'view', 'edit' ),  
  573. ),  
  574. 'price' => array( 
  575. 'description' => __( 'Current variation price.', 'woocommerce' ),  
  576. 'type' => 'string',  
  577. 'context' => array( 'view', 'edit' ),  
  578. 'readonly' => true,  
  579. ),  
  580. 'regular_price' => array( 
  581. 'description' => __( 'Variation regular price.', 'woocommerce' ),  
  582. 'type' => 'string',  
  583. 'context' => array( 'view', 'edit' ),  
  584. ),  
  585. 'sale_price' => array( 
  586. 'description' => __( 'Variation sale price.', 'woocommerce' ),  
  587. 'type' => 'string',  
  588. 'context' => array( 'view', 'edit' ),  
  589. ),  
  590. 'date_on_sale_from' => array( 
  591. 'description' => __( "Start date of sale price, in the site's timezone.", 'woocommerce' ),  
  592. 'type' => 'date-time',  
  593. 'context' => array( 'view', 'edit' ),  
  594. ),  
  595. 'date_on_sale_from_gmt' => array( 
  596. 'description' => __( 'Start date of sale price, as GMT.', 'woocommerce' ),  
  597. 'type' => 'date-time',  
  598. 'context' => array( 'view', 'edit' ),  
  599. ),  
  600. 'date_on_sale_to' => array( 
  601. 'description' => __( "End date of sale price, in the site's timezone.", 'woocommerce' ),  
  602. 'type' => 'date-time',  
  603. 'context' => array( 'view', 'edit' ),  
  604. ),  
  605. 'date_on_sale_to_gmt' => array( 
  606. 'description' => __( "End date of sale price, in the site's timezone.", 'woocommerce' ),  
  607. 'type' => 'date-time',  
  608. 'context' => array( 'view', 'edit' ),  
  609. ),  
  610. 'on_sale' => array( 
  611. 'description' => __( 'Shows if the variation is on sale.', 'woocommerce' ),  
  612. 'type' => 'boolean',  
  613. 'context' => array( 'view', 'edit' ),  
  614. 'readonly' => true,  
  615. ),  
  616. 'visible' => array( 
  617. 'description' => __( "Define if the attribute is visible on the \"Additional information\" tab in the product's page.", 'woocommerce' ),  
  618. 'type' => 'boolean',  
  619. 'default' => true,  
  620. 'context' => array( 'view', 'edit' ),  
  621. ),  
  622. 'purchasable' => array( 
  623. 'description' => __( 'Shows if the variation can be bought.', 'woocommerce' ),  
  624. 'type' => 'boolean',  
  625. 'context' => array( 'view', 'edit' ),  
  626. 'readonly' => true,  
  627. ),  
  628. 'virtual' => array( 
  629. 'description' => __( 'If the variation is virtual.', 'woocommerce' ),  
  630. 'type' => 'boolean',  
  631. 'default' => false,  
  632. 'context' => array( 'view', 'edit' ),  
  633. ),  
  634. 'downloadable' => array( 
  635. 'description' => __( 'If the variation is downloadable.', 'woocommerce' ),  
  636. 'type' => 'boolean',  
  637. 'default' => false,  
  638. 'context' => array( 'view', 'edit' ),  
  639. ),  
  640. 'downloads' => array( 
  641. 'description' => __( 'List of downloadable files.', 'woocommerce' ),  
  642. 'type' => 'array',  
  643. 'context' => array( 'view', 'edit' ),  
  644. 'items' => array( 
  645. 'type' => 'object',  
  646. 'properties' => array( 
  647. 'id' => array( 
  648. 'description' => __( 'File MD5 hash.', 'woocommerce' ),  
  649. 'type' => 'string',  
  650. 'context' => array( 'view', 'edit' ),  
  651. 'readonly' => true,  
  652. ),  
  653. 'name' => array( 
  654. 'description' => __( 'File name.', 'woocommerce' ),  
  655. 'type' => 'string',  
  656. 'context' => array( 'view', 'edit' ),  
  657. ),  
  658. 'file' => array( 
  659. 'description' => __( 'File URL.', 'woocommerce' ),  
  660. 'type' => 'string',  
  661. 'context' => array( 'view', 'edit' ),  
  662. ),  
  663. ),  
  664. ),  
  665. ),  
  666. 'download_limit' => array( 
  667. 'description' => __( 'Number of times downloadable files can be downloaded after purchase.', 'woocommerce' ),  
  668. 'type' => 'integer',  
  669. 'default' => -1,  
  670. 'context' => array( 'view', 'edit' ),  
  671. ),  
  672. 'download_expiry' => array( 
  673. 'description' => __( 'Number of days until access to downloadable files expires.', 'woocommerce' ),  
  674. 'type' => 'integer',  
  675. 'default' => -1,  
  676. 'context' => array( 'view', 'edit' ),  
  677. ),  
  678. 'tax_status' => array( 
  679. 'description' => __( 'Tax status.', 'woocommerce' ),  
  680. 'type' => 'string',  
  681. 'default' => 'taxable',  
  682. 'enum' => array( 'taxable', 'shipping', 'none' ),  
  683. 'context' => array( 'view', 'edit' ),  
  684. ),  
  685. 'tax_class' => array( 
  686. 'description' => __( 'Tax class.', 'woocommerce' ),  
  687. 'type' => 'string',  
  688. 'context' => array( 'view', 'edit' ),  
  689. ),  
  690. 'manage_stock' => array( 
  691. 'description' => __( 'Stock management at variation level.', 'woocommerce' ),  
  692. 'type' => 'boolean',  
  693. 'default' => false,  
  694. 'context' => array( 'view', 'edit' ),  
  695. ),  
  696. 'stock_quantity' => array( 
  697. 'description' => __( 'Stock quantity.', 'woocommerce' ),  
  698. 'type' => 'integer',  
  699. 'context' => array( 'view', 'edit' ),  
  700. ),  
  701. 'in_stock' => array( 
  702. 'description' => __( 'Controls whether or not the variation is listed as "in stock" or "out of stock" on the frontend.', 'woocommerce' ),  
  703. 'type' => 'boolean',  
  704. 'default' => true,  
  705. 'context' => array( 'view', 'edit' ),  
  706. ),  
  707. 'backorders' => array( 
  708. 'description' => __( 'If managing stock, this controls if backorders are allowed.', 'woocommerce' ),  
  709. 'type' => 'string',  
  710. 'default' => 'no',  
  711. 'enum' => array( 'no', 'notify', 'yes' ),  
  712. 'context' => array( 'view', 'edit' ),  
  713. ),  
  714. 'backorders_allowed' => array( 
  715. 'description' => __( 'Shows if backorders are allowed.', 'woocommerce' ),  
  716. 'type' => 'boolean',  
  717. 'context' => array( 'view', 'edit' ),  
  718. 'readonly' => true,  
  719. ),  
  720. 'backordered' => array( 
  721. 'description' => __( 'Shows if the variation is on backordered.', 'woocommerce' ),  
  722. 'type' => 'boolean',  
  723. 'context' => array( 'view', 'edit' ),  
  724. 'readonly' => true,  
  725. ),  
  726. 'weight' => array( 
  727. /** translators: %s: weight unit */ 
  728. 'description' => sprintf( __( 'Variation weight (%s).', 'woocommerce' ), $weight_unit ),  
  729. 'type' => 'string',  
  730. 'context' => array( 'view', 'edit' ),  
  731. ),  
  732. 'dimensions' => array( 
  733. 'description' => __( 'Variation dimensions.', 'woocommerce' ),  
  734. 'type' => 'object',  
  735. 'context' => array( 'view', 'edit' ),  
  736. 'properties' => array( 
  737. 'length' => array( 
  738. /** translators: %s: dimension unit */ 
  739. 'description' => sprintf( __( 'Variation length (%s).', 'woocommerce' ), $dimension_unit ),  
  740. 'type' => 'string',  
  741. 'context' => array( 'view', 'edit' ),  
  742. ),  
  743. 'width' => array( 
  744. /** translators: %s: dimension unit */ 
  745. 'description' => sprintf( __( 'Variation width (%s).', 'woocommerce' ), $dimension_unit ),  
  746. 'type' => 'string',  
  747. 'context' => array( 'view', 'edit' ),  
  748. ),  
  749. 'height' => array( 
  750. /** translators: %s: dimension unit */ 
  751. 'description' => sprintf( __( 'Variation height (%s).', 'woocommerce' ), $dimension_unit ),  
  752. 'type' => 'string',  
  753. 'context' => array( 'view', 'edit' ),  
  754. ),  
  755. ),  
  756. ),  
  757. 'shipping_class' => array( 
  758. 'description' => __( 'Shipping class slug.', 'woocommerce' ),  
  759. 'type' => 'string',  
  760. 'context' => array( 'view', 'edit' ),  
  761. ),  
  762. 'shipping_class_id' => array( 
  763. 'description' => __( 'Shipping class ID.', 'woocommerce' ),  
  764. 'type' => 'string',  
  765. 'context' => array( 'view', 'edit' ),  
  766. 'readonly' => true,  
  767. ),  
  768. 'image' => array( 
  769. 'description' => __( 'Variation image data.', 'woocommerce' ),  
  770. 'type' => 'object',  
  771. 'context' => array( 'view', 'edit' ),  
  772. 'properties' => array( 
  773. 'id' => array( 
  774. 'description' => __( 'Image ID.', 'woocommerce' ),  
  775. 'type' => 'integer',  
  776. 'context' => array( 'view', 'edit' ),  
  777. ),  
  778. 'date_created' => array( 
  779. 'description' => __( "The date the image was created, in the site's timezone.", 'woocommerce' ),  
  780. 'type' => 'date-time',  
  781. 'context' => array( 'view', 'edit' ),  
  782. 'readonly' => true,  
  783. ),  
  784. 'date_created_gmt' => array( 
  785. 'description' => __( 'The date the image was created, as GMT.', 'woocommerce' ),  
  786. 'type' => 'date-time',  
  787. 'context' => array( 'view', 'edit' ),  
  788. 'readonly' => true,  
  789. ),  
  790. 'date_modified' => array( 
  791. 'description' => __( "The date the image was last modified, in the site's timezone.", 'woocommerce' ),  
  792. 'type' => 'date-time',  
  793. 'context' => array( 'view', 'edit' ),  
  794. 'readonly' => true,  
  795. ),  
  796. 'date_modified_gmt' => array( 
  797. 'description' => __( 'The date the image was last modified, as GMT.', 'woocommerce' ),  
  798. 'type' => 'date-time',  
  799. 'context' => array( 'view', 'edit' ),  
  800. 'readonly' => true,  
  801. ),  
  802. 'src' => array( 
  803. 'description' => __( 'Image URL.', 'woocommerce' ),  
  804. 'type' => 'string',  
  805. 'format' => 'uri',  
  806. 'context' => array( 'view', 'edit' ),  
  807. ),  
  808. 'name' => array( 
  809. 'description' => __( 'Image name.', 'woocommerce' ),  
  810. 'type' => 'string',  
  811. 'context' => array( 'view', 'edit' ),  
  812. ),  
  813. 'alt' => array( 
  814. 'description' => __( 'Image alternative text.', 'woocommerce' ),  
  815. 'type' => 'string',  
  816. 'context' => array( 'view', 'edit' ),  
  817. ),  
  818. 'position' => array( 
  819. 'description' => __( 'Image position. 0 means that the image is featured.', 'woocommerce' ),  
  820. 'type' => 'integer',  
  821. 'context' => array( 'view', 'edit' ),  
  822. ),  
  823. ),  
  824. ),  
  825. 'attributes' => array( 
  826. 'description' => __( 'List of attributes.', 'woocommerce' ),  
  827. 'type' => 'array',  
  828. 'context' => array( 'view', 'edit' ),  
  829. 'items' => array( 
  830. 'type' => 'object',  
  831. 'properties' => array( 
  832. 'id' => array( 
  833. 'description' => __( 'Attribute ID.', 'woocommerce' ),  
  834. 'type' => 'integer',  
  835. 'context' => array( 'view', 'edit' ),  
  836. ),  
  837. 'name' => array( 
  838. 'description' => __( 'Attribute name.', 'woocommerce' ),  
  839. 'type' => 'string',  
  840. 'context' => array( 'view', 'edit' ),  
  841. ),  
  842. 'option' => array( 
  843. 'description' => __( 'Selected attribute term name.', 'woocommerce' ),  
  844. 'type' => 'string',  
  845. 'context' => array( 'view', 'edit' ),  
  846. ),  
  847. ),  
  848. ),  
  849. ),  
  850. 'menu_order' => array( 
  851. 'description' => __( 'Menu order, used to custom sort products.', 'woocommerce' ),  
  852. 'type' => 'integer',  
  853. 'context' => array( 'view', 'edit' ),  
  854. ),  
  855. 'meta_data' => array( 
  856. 'description' => __( 'Meta data.', 'woocommerce' ),  
  857. 'type' => 'array',  
  858. 'context' => array( 'view', 'edit' ),  
  859. 'items' => array( 
  860. 'type' => 'object',  
  861. 'properties' => array( 
  862. 'id' => array( 
  863. 'description' => __( 'Meta ID.', 'woocommerce' ),  
  864. 'type' => 'integer',  
  865. 'context' => array( 'view', 'edit' ),  
  866. 'readonly' => true,  
  867. ),  
  868. 'key' => array( 
  869. 'description' => __( 'Meta key.', 'woocommerce' ),  
  870. 'type' => 'string',  
  871. 'context' => array( 'view', 'edit' ),  
  872. ),  
  873. 'value' => array( 
  874. 'description' => __( 'Meta value.', 'woocommerce' ),  
  875. 'type' => 'string',  
  876. 'context' => array( 'view', 'edit' ),  
  877. ),  
  878. ),  
  879. ),  
  880. ),  
  881. ),  
  882. ); 
  883.  
  884. return $this->add_additional_fields_schema( $schema ); 
.