WC_REST_Products_V1_Controller

REST API Products controller class.

Defined (1)

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

/includes/api/v1/class-wc-rest-products-controller.php  
  1. class WC_REST_Products_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 = 'products'; 
  14.  
  15. /** 
  16. * Post type. 
  17. * @var string 
  18. */ 
  19. protected $post_type = 'product'; 
  20.  
  21. /** 
  22. * Initialize product actions. 
  23. */ 
  24. public function __construct() { 
  25. add_filter( "woocommerce_rest_{$this->post_type}_query", array( $this, 'query_args' ), 10, 2 ); 
  26. add_action( "woocommerce_rest_insert_{$this->post_type}", array( $this, 'clear_transients' ) ); 
  27.  
  28. /** 
  29. * Register the routes for products. 
  30. */ 
  31. public function register_routes() { 
  32. register_rest_route( $this->namespace, '/' . $this->rest_base, array( 
  33. array( 
  34. 'methods' => WP_REST_Server::READABLE,  
  35. 'callback' => array( $this, 'get_items' ),  
  36. 'permission_callback' => array( $this, 'get_items_permissions_check' ),  
  37. 'args' => $this->get_collection_params(),  
  38. ),  
  39. array( 
  40. 'methods' => WP_REST_Server::CREATABLE,  
  41. 'callback' => array( $this, 'create_item' ),  
  42. 'permission_callback' => array( $this, 'create_item_permissions_check' ),  
  43. 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ),  
  44. ),  
  45. 'schema' => array( $this, 'get_public_item_schema' ),  
  46. ) ); 
  47.  
  48. register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P<id>[\d]+)', array( 
  49. 'args' => array( 
  50. 'id' => array( 
  51. 'description' => __( 'Unique identifier for the resource.', 'woocommerce' ),  
  52. 'type' => 'integer',  
  53. ),  
  54. ),  
  55. array( 
  56. 'methods' => WP_REST_Server::READABLE,  
  57. 'callback' => array( $this, 'get_item' ),  
  58. 'permission_callback' => array( $this, 'get_item_permissions_check' ),  
  59. 'args' => array( 
  60. 'context' => $this->get_context_param( array( 'default' => 'view' ) ),  
  61. ),  
  62. ),  
  63. array( 
  64. 'methods' => WP_REST_Server::EDITABLE,  
  65. 'callback' => array( $this, 'update_item' ),  
  66. 'permission_callback' => array( $this, 'update_item_permissions_check' ),  
  67. 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),  
  68. ),  
  69. array( 
  70. 'methods' => WP_REST_Server::DELETABLE,  
  71. 'callback' => array( $this, 'delete_item' ),  
  72. 'permission_callback' => array( $this, 'delete_item_permissions_check' ),  
  73. 'args' => array( 
  74. 'force' => array( 
  75. 'default' => false,  
  76. 'description' => __( 'Whether to bypass trash and force deletion.', 'woocommerce' ),  
  77. 'type' => 'boolean',  
  78. ),  
  79. ),  
  80. ),  
  81. 'schema' => array( $this, 'get_public_item_schema' ),  
  82. ) ); 
  83.  
  84. register_rest_route( $this->namespace, '/' . $this->rest_base . '/batch', array( 
  85. array( 
  86. 'methods' => WP_REST_Server::EDITABLE,  
  87. 'callback' => array( $this, 'batch_items' ),  
  88. 'permission_callback' => array( $this, 'batch_items_permissions_check' ),  
  89. 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),  
  90. ),  
  91. 'schema' => array( $this, 'get_public_batch_schema' ),  
  92. ) ); 
  93.  
  94. /** 
  95. * Get post types. 
  96. * @return array 
  97. */ 
  98. protected function get_post_types() { 
  99. return array( 'product', 'product_variation' ); 
  100.  
  101. /** 
  102. * Query args. 
  103. * @param array $args Request args. 
  104. * @param WP_REST_Request $request Request data. 
  105. * @return array 
  106. */ 
  107. public function query_args( $args, $request ) { 
  108. // Set post_status. 
  109. $args['post_status'] = $request['status']; 
  110.  
  111. // Taxonomy query to filter products by type, category,  
  112. // tag, shipping class, and attribute. 
  113. $tax_query = array(); 
  114.  
  115. // Map between taxonomy name and arg's key. 
  116. $taxonomies = array( 
  117. 'product_cat' => 'category',  
  118. 'product_tag' => 'tag',  
  119. 'product_shipping_class' => 'shipping_class',  
  120. ); 
  121.  
  122. // Set tax_query for each passed arg. 
  123. foreach ( $taxonomies as $taxonomy => $key ) { 
  124. if ( ! empty( $request[ $key ] ) && is_array( $request[ $key ] ) ) { 
  125. $request[ $key ] = array_filter( $request[ $key ] ); 
  126.  
  127. if ( ! empty( $request[ $key ] ) ) { 
  128. $tax_query[] = array( 
  129. 'taxonomy' => $taxonomy,  
  130. 'field' => 'term_id',  
  131. 'terms' => $request[ $key ],  
  132. ); 
  133.  
  134. // Filter product type by slug. 
  135. if ( ! empty( $request['type'] ) ) { 
  136. $tax_query[] = array( 
  137. 'taxonomy' => 'product_type',  
  138. 'field' => 'slug',  
  139. 'terms' => $request['type'],  
  140. ); 
  141.  
  142. // Filter by attribute and term. 
  143. if ( ! empty( $request['attribute'] ) && ! empty( $request['attribute_term'] ) ) { 
  144. if ( in_array( $request['attribute'], wc_get_attribute_taxonomy_names(), true ) ) { 
  145. $tax_query[] = array( 
  146. 'taxonomy' => $request['attribute'],  
  147. 'field' => 'term_id',  
  148. 'terms' => $request['attribute_term'],  
  149. ); 
  150.  
  151. if ( ! empty( $tax_query ) ) { 
  152. $args['tax_query'] = $tax_query; 
  153.  
  154. // Filter by sku. 
  155. if ( ! empty( $request['sku'] ) ) { 
  156. $skus = explode( ', ', $request['sku'] ); 
  157. // Include the current string as a SKU too. 
  158. if ( 1 < count( $skus ) ) { 
  159. $skus[] = $request['sku']; 
  160.  
  161. $args['meta_query'] = $this->add_meta_query( $args, array( 
  162. 'key' => '_sku',  
  163. 'value' => $skus,  
  164. 'compare' => 'IN',  
  165. ) ); 
  166.  
  167. // Apply all WP_Query filters again. 
  168. if ( is_array( $request['filter'] ) ) { 
  169. $args = array_merge( $args, $request['filter'] ); 
  170. unset( $args['filter'] ); 
  171.  
  172. // Force the post_type argument, since it's not a user input variable. 
  173. if ( ! empty( $request['sku'] ) ) { 
  174. $args['post_type'] = array( 'product', 'product_variation' ); 
  175. } else { 
  176. $args['post_type'] = $this->post_type; 
  177.  
  178. return $args; 
  179.  
  180. /** 
  181. * Get the downloads for a product or product variation. 
  182. * @param WC_Product|WC_Product_Variation $product Product instance. 
  183. * @return array 
  184. */ 
  185. protected function get_downloads( $product ) { 
  186. $downloads = array(); 
  187.  
  188. if ( $product->is_downloadable() ) { 
  189. foreach ( $product->get_downloads() as $file_id => $file ) { 
  190. $downloads[] = array( 
  191. 'id' => $file_id, // MD5 hash. 
  192. 'name' => $file['name'],  
  193. 'file' => $file['file'],  
  194. ); 
  195.  
  196. return $downloads; 
  197.  
  198. /** 
  199. * Get taxonomy terms. 
  200. * @param WC_Product $product Product instance. 
  201. * @param string $taxonomy Taxonomy slug. 
  202. * @return array 
  203. */ 
  204. protected function get_taxonomy_terms( $product, $taxonomy = 'cat' ) { 
  205. $terms = array(); 
  206.  
  207. foreach ( wc_get_object_terms( $product->get_id(), 'product_' . $taxonomy ) as $term ) { 
  208. $terms[] = array( 
  209. 'id' => $term->term_id,  
  210. 'name' => $term->name,  
  211. 'slug' => $term->slug,  
  212. ); 
  213.  
  214. return $terms; 
  215.  
  216. /** 
  217. * Get the images for a product or product variation. 
  218. * @param WC_Product|WC_Product_Variation $product Product instance. 
  219. * @return array 
  220. */ 
  221. protected function get_images( $product ) { 
  222. $images = array(); 
  223. $attachment_ids = array(); 
  224.  
  225. // Add featured image. 
  226. if ( has_post_thumbnail( $product->get_id() ) ) { 
  227. $attachment_ids[] = $product->get_image_id(); 
  228.  
  229. // Add gallery images. 
  230. $attachment_ids = array_merge( $attachment_ids, $product->get_gallery_image_ids() ); 
  231.  
  232. // Build image data. 
  233. foreach ( $attachment_ids as $position => $attachment_id ) { 
  234. $attachment_post = get_post( $attachment_id ); 
  235. if ( is_null( $attachment_post ) ) { 
  236. continue; 
  237.  
  238. $attachment = wp_get_attachment_image_src( $attachment_id, 'full' ); 
  239. if ( ! is_array( $attachment ) ) { 
  240. continue; 
  241.  
  242. $images[] = array( 
  243. 'id' => (int) $attachment_id,  
  244. 'date_created' => wc_rest_prepare_date_response( $attachment_post->post_date_gmt ),  
  245. 'date_modified' => wc_rest_prepare_date_response( $attachment_post->post_modified_gmt ),  
  246. 'src' => current( $attachment ),  
  247. 'name' => get_the_title( $attachment_id ),  
  248. 'alt' => get_post_meta( $attachment_id, '_wp_attachment_image_alt', true ),  
  249. 'position' => (int) $position,  
  250. ); 
  251.  
  252. // Set a placeholder image if the product has no images set. 
  253. if ( empty( $images ) ) { 
  254. $images[] = array( 
  255. 'id' => 0,  
  256. 'date_created' => wc_rest_prepare_date_response( current_time( 'mysql' ) ), // Default to now. 
  257. 'date_modified' => wc_rest_prepare_date_response( current_time( 'mysql' ) ),  
  258. 'src' => wc_placeholder_img_src(),  
  259. 'name' => __( 'Placeholder', 'woocommerce' ),  
  260. 'alt' => __( 'Placeholder', 'woocommerce' ),  
  261. 'position' => 0,  
  262. ); 
  263.  
  264. return $images; 
  265.  
  266. /** 
  267. * Get attribute taxonomy label. 
  268. * @param string $name Taxonomy name. 
  269. * @return string 
  270. */ 
  271. protected function get_attribute_taxonomy_label( $name ) { 
  272. $tax = get_taxonomy( $name ); 
  273. $labels = get_taxonomy_labels( $tax ); 
  274.  
  275. return $labels->singular_name; 
  276.  
  277. /** 
  278. * Get default attributes. 
  279. * @param WC_Product $product Product instance. 
  280. * @return array 
  281. */ 
  282. protected function get_default_attributes( $product ) { 
  283. $default = array(); 
  284.  
  285. if ( $product->is_type( 'variable' ) ) { 
  286. foreach ( array_filter( (array) $product->get_default_attributes(), 'strlen' ) as $key => $value ) { 
  287. if ( 0 === strpos( $key, 'pa_' ) ) { 
  288. $default[] = array( 
  289. 'id' => wc_attribute_taxonomy_id_by_name( $key ),  
  290. 'name' => $this->get_attribute_taxonomy_label( $key ),  
  291. 'option' => $value,  
  292. ); 
  293. } else { 
  294. $default[] = array( 
  295. 'id' => 0,  
  296. 'name' => str_replace( 'pa_', '', $key ),  
  297. 'option' => $value,  
  298. ); 
  299.  
  300. return $default; 
  301.  
  302. /** 
  303. * Get attribute options. 
  304. * @param int $product_id Product ID. 
  305. * @param array $attribute Attribute data. 
  306. * @return array 
  307. */ 
  308. protected function get_attribute_options( $product_id, $attribute ) { 
  309. if ( isset( $attribute['is_taxonomy'] ) && $attribute['is_taxonomy'] ) { 
  310. return wc_get_product_terms( $product_id, $attribute['name'], array( 'fields' => 'names' ) ); 
  311. } elseif ( isset( $attribute['value'] ) ) { 
  312. return array_map( 'trim', explode( '|', $attribute['value'] ) ); 
  313.  
  314. return array(); 
  315.  
  316. /** 
  317. * Get the attributes for a product or product variation. 
  318. * @param WC_Product|WC_Product_Variation $product Product instance. 
  319. * @return array 
  320. */ 
  321. protected function get_attributes( $product ) { 
  322. $attributes = array(); 
  323.  
  324. if ( $product->is_type( 'variation' ) ) { 
  325. // Variation attributes. 
  326. foreach ( $product->get_variation_attributes() as $attribute_name => $attribute ) { 
  327. $name = str_replace( 'attribute_', '', $attribute_name ); 
  328.  
  329. if ( ! $attribute ) { 
  330. continue; 
  331.  
  332. // Taxonomy-based attributes are prefixed with `pa_`, otherwise simply `attribute_`. 
  333. if ( 0 === strpos( $attribute_name, 'attribute_pa_' ) ) { 
  334. $option_term = get_term_by( 'slug', $attribute, $name ); 
  335. $attributes[] = array( 
  336. 'id' => wc_attribute_taxonomy_id_by_name( $name ),  
  337. 'name' => $this->get_attribute_taxonomy_label( $name ),  
  338. 'option' => $option_term && ! is_wp_error( $option_term ) ? $option_term->name : $attribute,  
  339. ); 
  340. } else { 
  341. $attributes[] = array( 
  342. 'id' => 0,  
  343. 'name' => $name,  
  344. 'option' => $attribute,  
  345. ); 
  346. } else { 
  347. foreach ( $product->get_attributes() as $attribute ) { 
  348. if ( $attribute['is_taxonomy'] ) { 
  349. $attributes[] = array( 
  350. 'id' => wc_attribute_taxonomy_id_by_name( $attribute['name'] ),  
  351. 'name' => $this->get_attribute_taxonomy_label( $attribute['name'] ),  
  352. 'position' => (int) $attribute['position'],  
  353. 'visible' => (bool) $attribute['is_visible'],  
  354. 'variation' => (bool) $attribute['is_variation'],  
  355. 'options' => $this->get_attribute_options( $product->get_id(), $attribute ),  
  356. ); 
  357. } else { 
  358. $attributes[] = array( 
  359. 'id' => 0,  
  360. 'name' => $attribute['name'],  
  361. 'position' => (int) $attribute['position'],  
  362. 'visible' => (bool) $attribute['is_visible'],  
  363. 'variation' => (bool) $attribute['is_variation'],  
  364. 'options' => $this->get_attribute_options( $product->get_id(), $attribute ),  
  365. ); 
  366.  
  367. return $attributes; 
  368.  
  369. /** 
  370. * Get product menu order. 
  371. * @deprecated 3.0.0 
  372. * @param WC_Product $product Product instance. 
  373. * @return int 
  374. */ 
  375. protected function get_product_menu_order( $product ) { 
  376. return $product->get_menu_order(); 
  377.  
  378. /** 
  379. * Get product data. 
  380. * @param WC_Product $product Product instance. 
  381. * @return array 
  382. */ 
  383. protected function get_product_data( $product ) { 
  384. $data = array( 
  385. 'id' => $product->get_id(),  
  386. 'name' => $product->get_name(),  
  387. 'slug' => $product->get_slug(),  
  388. 'permalink' => $product->get_permalink(),  
  389. 'date_created' => wc_rest_prepare_date_response( $product->get_date_created() ),  
  390. 'date_modified' => wc_rest_prepare_date_response( $product->get_date_modified() ),  
  391. 'type' => $product->get_type(),  
  392. 'status' => $product->get_status(),  
  393. 'featured' => $product->is_featured(),  
  394. 'catalog_visibility' => $product->get_catalog_visibility(),  
  395. 'description' => wpautop( do_shortcode( $product->get_description() ) ),  
  396. 'short_description' => apply_filters( 'woocommerce_short_description', $product->get_short_description() ),  
  397. 'sku' => $product->get_sku(),  
  398. 'price' => $product->get_price(),  
  399. 'regular_price' => $product->get_regular_price(),  
  400. 'sale_price' => $product->get_sale_price() ? $product->get_sale_price() : '',  
  401. 'date_on_sale_from' => $product->get_date_on_sale_from() ? date( 'Y-m-d', $product->get_date_on_sale_from()->getTimestamp() ) : '',  
  402. 'date_on_sale_to' => $product->get_date_on_sale_to() ? date( 'Y-m-d', $product->get_date_on_sale_to()->getTimestamp() ) : '',  
  403. 'price_html' => $product->get_price_html(),  
  404. 'on_sale' => $product->is_on_sale(),  
  405. 'purchasable' => $product->is_purchasable(),  
  406. 'total_sales' => $product->get_total_sales(),  
  407. 'virtual' => $product->is_virtual(),  
  408. 'downloadable' => $product->is_downloadable(),  
  409. 'downloads' => $this->get_downloads( $product ),  
  410. 'download_limit' => $product->get_download_limit(),  
  411. 'download_expiry' => $product->get_download_expiry(),  
  412. 'download_type' => 'standard',  
  413. 'external_url' => $product->is_type( 'external' ) ? $product->get_product_url() : '',  
  414. 'button_text' => $product->is_type( 'external' ) ? $product->get_button_text() : '',  
  415. 'tax_status' => $product->get_tax_status(),  
  416. 'tax_class' => $product->get_tax_class(),  
  417. 'manage_stock' => $product->managing_stock(),  
  418. 'stock_quantity' => $product->get_stock_quantity(),  
  419. 'in_stock' => $product->is_in_stock(),  
  420. 'backorders' => $product->get_backorders(),  
  421. 'backorders_allowed' => $product->backorders_allowed(),  
  422. 'backordered' => $product->is_on_backorder(),  
  423. 'sold_individually' => $product->is_sold_individually(),  
  424. 'weight' => $product->get_weight(),  
  425. 'dimensions' => array( 
  426. 'length' => $product->get_length(),  
  427. 'width' => $product->get_width(),  
  428. 'height' => $product->get_height(),  
  429. ),  
  430. 'shipping_required' => $product->needs_shipping(),  
  431. 'shipping_taxable' => $product->is_shipping_taxable(),  
  432. 'shipping_class' => $product->get_shipping_class(),  
  433. 'shipping_class_id' => $product->get_shipping_class_id(),  
  434. 'reviews_allowed' => $product->get_reviews_allowed(),  
  435. 'average_rating' => wc_format_decimal( $product->get_average_rating(), 2 ),  
  436. 'rating_count' => $product->get_rating_count(),  
  437. 'related_ids' => array_map( 'absint', array_values( wc_get_related_products( $product->get_id() ) ) ),  
  438. 'upsell_ids' => array_map( 'absint', $product->get_upsell_ids() ),  
  439. 'cross_sell_ids' => array_map( 'absint', $product->get_cross_sell_ids() ),  
  440. 'parent_id' => $product->get_parent_id(),  
  441. 'purchase_note' => wpautop( do_shortcode( wp_kses_post( $product->get_purchase_note() ) ) ),  
  442. 'categories' => $this->get_taxonomy_terms( $product ),  
  443. 'tags' => $this->get_taxonomy_terms( $product, 'tag' ),  
  444. 'images' => $this->get_images( $product ),  
  445. 'attributes' => $this->get_attributes( $product ),  
  446. 'default_attributes' => $this->get_default_attributes( $product ),  
  447. 'variations' => array(),  
  448. 'grouped_products' => array(),  
  449. 'menu_order' => $product->get_menu_order(),  
  450. ); 
  451.  
  452. return $data; 
  453.  
  454. /** 
  455. * Get an individual variation's data. 
  456. * @param WC_Product $product Product instance. 
  457. * @return array 
  458. */ 
  459. protected function get_variation_data( $product ) { 
  460. $variations = array(); 
  461.  
  462. foreach ( $product->get_children() as $child_id ) { 
  463. $variation = wc_get_product( $child_id ); 
  464. if ( ! $variation || ! $variation->exists() ) { 
  465. continue; 
  466.  
  467. $variations[] = array( 
  468. 'id' => $variation->get_id(),  
  469. 'date_created' => wc_rest_prepare_date_response( $variation->get_date_created() ),  
  470. 'date_modified' => wc_rest_prepare_date_response( $variation->get_date_modified() ),  
  471. 'permalink' => $variation->get_permalink(),  
  472. 'sku' => $variation->get_sku(),  
  473. 'price' => $variation->get_price(),  
  474. 'regular_price' => $variation->get_regular_price(),  
  475. 'sale_price' => $variation->get_sale_price(),  
  476. 'date_on_sale_from' => $variation->get_date_on_sale_from() ? date( 'Y-m-d', $variation->get_date_on_sale_from()->getTimestamp() ) : '',  
  477. 'date_on_sale_to' => $variation->get_date_on_sale_to() ? date( 'Y-m-d', $variation->get_date_on_sale_to()->getTimestamp() ) : '',  
  478. 'on_sale' => $variation->is_on_sale(),  
  479. 'purchasable' => $variation->is_purchasable(),  
  480. 'visible' => $variation->is_visible(),  
  481. 'virtual' => $variation->is_virtual(),  
  482. 'downloadable' => $variation->is_downloadable(),  
  483. 'downloads' => $this->get_downloads( $variation ),  
  484. 'download_limit' => '' !== $variation->get_download_limit() ? (int) $variation->get_download_limit() : -1,  
  485. 'download_expiry' => '' !== $variation->get_download_expiry() ? (int) $variation->get_download_expiry() : -1,  
  486. 'tax_status' => $variation->get_tax_status(),  
  487. 'tax_class' => $variation->get_tax_class(),  
  488. 'manage_stock' => $variation->managing_stock(),  
  489. 'stock_quantity' => $variation->get_stock_quantity(),  
  490. 'in_stock' => $variation->is_in_stock(),  
  491. 'backorders' => $variation->get_backorders(),  
  492. 'backorders_allowed' => $variation->backorders_allowed(),  
  493. 'backordered' => $variation->is_on_backorder(),  
  494. 'weight' => $variation->get_weight(),  
  495. 'dimensions' => array( 
  496. 'length' => $variation->get_length(),  
  497. 'width' => $variation->get_width(),  
  498. 'height' => $variation->get_height(),  
  499. ),  
  500. 'shipping_class' => $variation->get_shipping_class(),  
  501. 'shipping_class_id' => $variation->get_shipping_class_id(),  
  502. 'image' => $this->get_images( $variation ),  
  503. 'attributes' => $this->get_attributes( $variation ),  
  504. ); 
  505.  
  506. return $variations; 
  507.  
  508. /** 
  509. * Prepare a single product output for response. 
  510. * @param WP_Post $post Post object. 
  511. * @param WP_REST_Request $request Request object. 
  512. * @return WP_REST_Response 
  513. */ 
  514. public function prepare_item_for_response( $post, $request ) { 
  515. $product = wc_get_product( $post ); 
  516. $data = $this->get_product_data( $product ); 
  517.  
  518. // Add variations to variable products. 
  519. if ( $product->is_type( 'variable' ) && $product->has_child() ) { 
  520. $data['variations'] = $this->get_variation_data( $product ); 
  521.  
  522. // Add grouped products data. 
  523. if ( $product->is_type( 'grouped' ) && $product->has_child() ) { 
  524. $data['grouped_products'] = $product->get_children(); 
  525.  
  526. $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; 
  527. $data = $this->add_additional_fields_to_object( $data, $request ); 
  528. $data = $this->filter_response_by_context( $data, $context ); 
  529.  
  530. // Wrap the data in a response object. 
  531. $response = rest_ensure_response( $data ); 
  532.  
  533. $response->add_links( $this->prepare_links( $product, $request ) ); 
  534.  
  535. /** 
  536. * Filter the data for a response. 
  537. * The dynamic portion of the hook name, $this->post_type, refers to post_type of the post being 
  538. * prepared for the response. 
  539. * @param WP_REST_Response $response The response object. 
  540. * @param WP_Post $post Post object. 
  541. * @param WP_REST_Request $request Request object. 
  542. */ 
  543. return apply_filters( "woocommerce_rest_prepare_{$this->post_type}", $response, $post, $request ); 
  544.  
  545. /** 
  546. * Prepare links for the request. 
  547. * @param WC_Product $product Product object. 
  548. * @param WP_REST_Request $request Request object. 
  549. * @return array Links for the given product. 
  550. */ 
  551. protected function prepare_links( $product, $request ) { 
  552. $links = array( 
  553. 'self' => array( 
  554. 'href' => rest_url( sprintf( '/%s/%s/%d', $this->namespace, $this->rest_base, $product->get_id() ) ),  
  555. ),  
  556. 'collection' => array( 
  557. 'href' => rest_url( sprintf( '/%s/%s', $this->namespace, $this->rest_base ) ),  
  558. ),  
  559. ); 
  560.  
  561. if ( $product->get_parent_id() ) { 
  562. $links['up'] = array( 
  563. 'href' => rest_url( sprintf( '/%s/products/%d', $this->namespace, $product->get_parent_id() ) ),  
  564. ); 
  565.  
  566. return $links; 
  567.  
  568. /** 
  569. * Prepare a single product for create or update. 
  570. * @param WP_REST_Request $request Request object. 
  571. * @return WP_Error|stdClass $data Post object. 
  572. */ 
  573. protected function prepare_item_for_database( $request ) { 
  574. $id = isset( $request['id'] ) ? absint( $request['id'] ) : 0; 
  575.  
  576. // Type is the most important part here because we need to be using the correct class and methods. 
  577. if ( isset( $request['type'] ) ) { 
  578. $classname = WC_Product_Factory::get_classname_from_product_type( $request['type'] ); 
  579.  
  580. if ( ! class_exists( $classname ) ) { 
  581. $classname = 'WC_Product_Simple'; 
  582.  
  583. $product = new $classname( $id ); 
  584. } elseif ( isset( $request['id'] ) ) { 
  585. $product = wc_get_product( $id ); 
  586. } else { 
  587. $product = new WC_Product_Simple(); 
  588.  
  589. // Post title. 
  590. if ( isset( $request['name'] ) ) { 
  591. $product->set_name( wp_filter_post_kses( $request['name'] ) ); 
  592.  
  593. // Post content. 
  594. if ( isset( $request['description'] ) ) { 
  595. $product->set_description( wp_filter_post_kses( $request['description'] ) ); 
  596.  
  597. // Post excerpt. 
  598. if ( isset( $request['short_description'] ) ) { 
  599. $product->set_short_description( wp_filter_post_kses( $request['short_description'] ) ); 
  600.  
  601. // Post status. 
  602. if ( isset( $request['status'] ) ) { 
  603. $product->set_status( get_post_status_object( $request['status'] ) ? $request['status'] : 'draft' ); 
  604.  
  605. // Post slug. 
  606. if ( isset( $request['slug'] ) ) { 
  607. $product->set_slug( $request['slug'] ); 
  608.  
  609. // Menu order. 
  610. if ( isset( $request['menu_order'] ) ) { 
  611. $product->set_menu_order( $request['menu_order'] ); 
  612.  
  613. // Comment status. 
  614. if ( isset( $request['reviews_allowed'] ) ) { 
  615. $product->set_reviews_allowed( $request['reviews_allowed'] ); 
  616.  
  617. /** 
  618. * Filter the query_vars used in `get_items` for the constructed query. 
  619. * The dynamic portion of the hook name, $this->post_type, refers to post_type of the post being 
  620. * prepared for insertion. 
  621. * @param WC_Product $product An object representing a single item prepared 
  622. * for inserting or updating the database. 
  623. * @param WP_REST_Request $request Request object. 
  624. */ 
  625. return apply_filters( "woocommerce_rest_pre_insert_{$this->post_type}", $product, $request ); 
  626.  
  627. /** 
  628. * Create a single product. 
  629. * @param WP_REST_Request $request Full details about the request. 
  630. * @return WP_Error|WP_REST_Response 
  631. */ 
  632. public function create_item( $request ) { 
  633. if ( ! empty( $request['id'] ) ) { 
  634. return new WP_Error( "woocommerce_rest_{$this->post_type}_exists", sprintf( __( 'Cannot create existing %s.', 'woocommerce' ), $this->post_type ), array( 'status' => 400 ) ); 
  635.  
  636. $product_id = 0; 
  637.  
  638. try { 
  639. $product_id = $this->save_product( $request ); 
  640. $post = get_post( $product_id ); 
  641. $this->update_additional_fields_for_object( $post, $request ); 
  642. $this->update_post_meta_fields( $post, $request ); 
  643.  
  644. /** 
  645. * Fires after a single item is created or updated via the REST API. 
  646. * @param WP_Post $post Post data. 
  647. * @param WP_REST_Request $request Request object. 
  648. * @param boolean $creating True when creating item, false when updating. 
  649. */ 
  650. do_action( 'woocommerce_rest_insert_product', $post, $request, true ); 
  651. $request->set_param( 'context', 'edit' ); 
  652. $response = $this->prepare_item_for_response( $post, $request ); 
  653. $response = rest_ensure_response( $response ); 
  654. $response->set_status( 201 ); 
  655. $response->header( 'Location', rest_url( sprintf( '/%s/%s/%d', $this->namespace, $this->rest_base, $post->ID ) ) ); 
  656.  
  657. return $response; 
  658. } catch ( WC_Data_Exception $e ) { 
  659. $this->delete_post( $product_id ); 
  660. return new WP_Error( $e->getErrorCode(), $e->getMessage(), $e->getErrorData() ); 
  661. } catch ( WC_REST_Exception $e ) { 
  662. $this->delete_post( $product_id ); 
  663. return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) ); 
  664.  
  665. /** 
  666. * Update a single product. 
  667. * @param WP_REST_Request $request Full details about the request. 
  668. * @return WP_Error|WP_REST_Response 
  669. */ 
  670. public function update_item( $request ) { 
  671. $post_id = (int) $request['id']; 
  672.  
  673. if ( empty( $post_id ) || get_post_type( $post_id ) !== $this->post_type ) { 
  674. return new WP_Error( "woocommerce_rest_{$this->post_type}_invalid_id", __( 'ID is invalid.', 'woocommerce' ), array( 'status' => 400 ) ); 
  675.  
  676. try { 
  677. $product_id = $this->save_product( $request ); 
  678. $post = get_post( $product_id ); 
  679. $this->update_additional_fields_for_object( $post, $request ); 
  680. $this->update_post_meta_fields( $post, $request ); 
  681.  
  682. /** 
  683. * Fires after a single item is created or updated via the REST API. 
  684. * @param WP_Post $post Post data. 
  685. * @param WP_REST_Request $request Request object. 
  686. * @param boolean $creating True when creating item, false when updating. 
  687. */ 
  688. do_action( 'woocommerce_rest_insert_product', $post, $request, false ); 
  689. $request->set_param( 'context', 'edit' ); 
  690. $response = $this->prepare_item_for_response( $post, $request ); 
  691.  
  692. return rest_ensure_response( $response ); 
  693. } catch ( WC_Data_Exception $e ) { 
  694. return new WP_Error( $e->getErrorCode(), $e->getMessage(), $e->getErrorData() ); 
  695. } catch ( WC_REST_Exception $e ) { 
  696. return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) ); 
  697.  
  698. /** 
  699. * Saves a product to the database. 
  700. * @param WP_REST_Request $request Full details about the request. 
  701. * @return int 
  702. */ 
  703. public function save_product( $request ) { 
  704. $product = $this->prepare_item_for_database( $request ); 
  705. return $product->save(); 
  706.  
  707. /** 
  708. * Save product images. 
  709. * @deprecated 3.0.0 
  710. * @param int $product_id 
  711. * @param array $images 
  712. * @throws WC_REST_Exception 
  713. */ 
  714. protected function save_product_images( $product_id, $images ) { 
  715. $product = wc_get_product( $product_id ); 
  716.  
  717. return set_product_images( $product, $images ); 
  718.  
  719. /** 
  720. * Set product images. 
  721. * @throws WC_REST_Exception REST API exceptions. 
  722. * @param WC_Product $product Product instance. 
  723. * @param array $images Images data. 
  724. * @return WC_Product 
  725. */ 
  726. protected function set_product_images( $product, $images ) { 
  727. if ( is_array( $images ) ) { 
  728. $gallery = array(); 
  729.  
  730. foreach ( $images as $image ) { 
  731. $attachment_id = isset( $image['id'] ) ? absint( $image['id'] ) : 0; 
  732.  
  733. if ( 0 === $attachment_id && isset( $image['src'] ) ) { 
  734. $upload = wc_rest_upload_image_from_url( esc_url_raw( $image['src'] ) ); 
  735.  
  736. if ( is_wp_error( $upload ) ) { 
  737. if ( ! apply_filters( 'woocommerce_rest_suppress_image_upload_error', false, $upload, $product->get_id(), $images ) ) { 
  738. throw new WC_REST_Exception( 'woocommerce_product_image_upload_error', $upload->get_error_message(), 400 ); 
  739. } else { 
  740. continue; 
  741.  
  742. $attachment_id = wc_rest_set_uploaded_image_as_attachment( $upload, $product->get_id() ); 
  743.  
  744. if ( ! wp_attachment_is_image( $attachment_id ) ) { 
  745. throw new WC_REST_Exception( 'woocommerce_product_invalid_image_id', sprintf( __( '#%s is an invalid image ID.', 'woocommerce' ), $attachment_id ), 400 ); 
  746.  
  747. if ( isset( $image['position'] ) && 0 === absint( $image['position'] ) ) { 
  748. $product->set_image_id( $attachment_id ); 
  749. } else { 
  750. $gallery[] = $attachment_id; 
  751.  
  752. // Set the image alt if present. 
  753. if ( ! empty( $image['alt'] ) ) { 
  754. update_post_meta( $attachment_id, '_wp_attachment_image_alt', wc_clean( $image['alt'] ) ); 
  755.  
  756. // Set the image name if present. 
  757. if ( ! empty( $image['name'] ) ) { 
  758. wp_update_post( array( 'ID' => $attachment_id, 'post_title' => $image['name'] ) ); 
  759.  
  760. if ( ! empty( $gallery ) ) { 
  761. $product->set_gallery_image_ids( $gallery ); 
  762. } else { 
  763. $product->set_image_id( '' ); 
  764. $product->set_gallery_image_ids( array() ); 
  765.  
  766. return $product; 
  767.  
  768. /** 
  769. * Save product shipping data. 
  770. * @param WC_Product $product Product instance. 
  771. * @param array $data Shipping data. 
  772. * @return WC_Product 
  773. */ 
  774. protected function save_product_shipping_data( $product, $data ) { 
  775. // Virtual. 
  776. if ( isset( $data['virtual'] ) && true === $data['virtual'] ) { 
  777. $product->set_weight( '' ); 
  778. $product->set_height( '' ); 
  779. $product->set_length( '' ); 
  780. $product->set_width( '' ); 
  781. } else { 
  782. if ( isset( $data['weight'] ) ) { 
  783. $product->set_weight( $data['weight'] ); 
  784.  
  785. // Height. 
  786. if ( isset( $data['dimensions']['height'] ) ) { 
  787. $product->set_height( $data['dimensions']['height'] ); 
  788.  
  789. // Width. 
  790. if ( isset( $data['dimensions']['width'] ) ) { 
  791. $product->set_width( $data['dimensions']['width'] ); 
  792.  
  793. // Length. 
  794. if ( isset( $data['dimensions']['length'] ) ) { 
  795. $product->set_length( $data['dimensions']['length'] ); 
  796.  
  797. // Shipping class. 
  798. if ( isset( $data['shipping_class'] ) ) { 
  799. $shipping_class_term = get_term_by( 'slug', wc_clean( $data['shipping_class'] ), 'product_shipping_class' ); 
  800.  
  801. if ( $shipping_class_term ) { 
  802. $product->set_shipping_class_id( $shipping_class_term->term_id ); 
  803.  
  804. return $product; 
  805.  
  806. /** 
  807. * Save downloadable files. 
  808. * @param WC_Product $product Product instance. 
  809. * @param array $downloads Downloads data. 
  810. * @param int $deprecated Deprecated since 3.0. 
  811. * @return WC_Product 
  812. */ 
  813. protected function save_downloadable_files( $product, $downloads, $deprecated = 0 ) { 
  814. if ( $deprecated ) { 
  815. wc_deprecated_argument( 'variation_id', '3.0', 'save_downloadable_files() not requires a variation_id anymore.' ); 
  816.  
  817. $files = array(); 
  818. foreach ( $downloads as $key => $file ) { 
  819. if ( empty( $file['file'] ) ) { 
  820. continue; 
  821.  
  822. $download = new WC_Product_Download(); 
  823. $download->set_id( $key ); 
  824. $download->set_name( $file['name'] ? $file['name'] : wc_get_filename_from_url( $file['file'] ) ); 
  825. $download->set_file( apply_filters( 'woocommerce_file_download_path', $file['file'], $product, $key ) ); 
  826. $files[] = $download; 
  827. $product->set_downloads( $files ); 
  828.  
  829. return $product; 
  830.  
  831. /** 
  832. * Save taxonomy terms. 
  833. * @param WC_Product $product Product instance. 
  834. * @param array $terms Terms data. 
  835. * @param string $taxonomy Taxonomy name. 
  836. * @return WC_Product 
  837. */ 
  838. protected function save_taxonomy_terms( $product, $terms, $taxonomy = 'cat' ) { 
  839. $term_ids = wp_list_pluck( $terms, 'id' ); 
  840.  
  841. if ( 'cat' === $taxonomy ) { 
  842. $product->set_category_ids( $term_ids ); 
  843. } elseif ( 'tag' === $taxonomy ) { 
  844. $product->set_tag_ids( $term_ids ); 
  845.  
  846. return $product; 
  847.  
  848. /** 
  849. * Save default attributes. 
  850. * @since 3.0.0 
  851. * @param WC_Product $product Product instance. 
  852. * @param WP_REST_Request $request Request data. 
  853. * @return WC_Product 
  854. */ 
  855. protected function save_default_attributes( $product, $request ) { 
  856. if ( isset( $request['default_attributes'] ) && is_array( $request['default_attributes'] ) ) { 
  857. $attributes = $product->get_attributes(); 
  858. $default_attributes = array(); 
  859.  
  860. foreach ( $request['default_attributes'] as $attribute ) { 
  861. $attribute_id = 0; 
  862. $attribute_name = ''; 
  863.  
  864. // Check ID for global attributes or name for product attributes. 
  865. if ( ! empty( $attribute['id'] ) ) { 
  866. $attribute_id = absint( $attribute['id'] ); 
  867. $attribute_name = wc_attribute_taxonomy_name_by_id( $attribute_id ); 
  868. } elseif ( ! empty( $attribute['name'] ) ) { 
  869. $attribute_name = sanitize_title( $attribute['name'] ); 
  870.  
  871. if ( ! $attribute_id && ! $attribute_name ) { 
  872. continue; 
  873.  
  874. if ( isset( $attributes[ $attribute_name ] ) ) { 
  875. $_attribute = $attributes[ $attribute_name ]; 
  876.  
  877. if ( $_attribute['is_variation'] ) { 
  878. $value = isset( $attribute['option'] ) ? wc_clean( stripslashes( $attribute['option'] ) ) : ''; 
  879.  
  880. if ( ! empty( $_attribute['is_taxonomy'] ) ) { 
  881. // If dealing with a taxonomy, we need to get the slug from the name posted to the API. 
  882. $term = get_term_by( 'name', $value, $attribute_name ); 
  883.  
  884. if ( $term && ! is_wp_error( $term ) ) { 
  885. $value = $term->slug; 
  886. } else { 
  887. $value = sanitize_title( $value ); 
  888.  
  889. if ( $value ) { 
  890. $default_attributes[ $attribute_name ] = $value; 
  891.  
  892. $product->set_default_attributes( $default_attributes ); 
  893.  
  894. return $product; 
  895.  
  896. /** 
  897. * Save product meta. 
  898. * @deprecated 3.0.0 
  899. * @param WC_Product $product 
  900. * @param WP_REST_Request $request 
  901. * @return bool 
  902. * @throws WC_REST_Exception 
  903. */ 
  904. protected function save_product_meta( $product, $request ) { 
  905. $product = $this->set_product_meta( $product, $request ); 
  906. $product->save(); 
  907.  
  908. return true; 
  909.  
  910. /** 
  911. * Set product meta. 
  912. * @throws WC_REST_Exception REST API exceptions. 
  913. * @param WC_Product $product Product instance. 
  914. * @param WP_REST_Request $request Request data. 
  915. * @return WC_Product 
  916. */ 
  917. protected function set_product_meta( $product, $request ) { 
  918. // Virtual. 
  919. if ( isset( $request['virtual'] ) ) { 
  920. $product->set_virtual( $request['virtual'] ); 
  921.  
  922. // Tax status. 
  923. if ( isset( $request['tax_status'] ) ) { 
  924. $product->set_tax_status( $request['tax_status'] ); 
  925.  
  926. // Tax Class. 
  927. if ( isset( $request['tax_class'] ) ) { 
  928. $product->set_tax_class( $request['tax_class'] ); 
  929.  
  930. // Catalog Visibility. 
  931. if ( isset( $request['catalog_visibility'] ) ) { 
  932. $product->set_catalog_visibility( $request['catalog_visibility'] ); 
  933.  
  934. // Purchase Note. 
  935. if ( isset( $request['purchase_note'] ) ) { 
  936. $product->set_purchase_note( wc_clean( $request['purchase_note'] ) ); 
  937.  
  938. // Featured Product. 
  939. if ( isset( $request['featured'] ) ) { 
  940. $product->set_featured( $request['featured'] ); 
  941.  
  942. // Shipping data. 
  943. $product = $this->save_product_shipping_data( $product, $request ); 
  944.  
  945. // SKU. 
  946. if ( isset( $request['sku'] ) ) { 
  947. $product->set_sku( wc_clean( $request['sku'] ) ); 
  948.  
  949. // Attributes. 
  950. if ( isset( $request['attributes'] ) ) { 
  951. $attributes = array(); 
  952.  
  953. foreach ( $request['attributes'] as $attribute ) { 
  954. $attribute_id = 0; 
  955. $attribute_name = ''; 
  956.  
  957. // Check ID for global attributes or name for product attributes. 
  958. if ( ! empty( $attribute['id'] ) ) { 
  959. $attribute_id = absint( $attribute['id'] ); 
  960. $attribute_name = wc_attribute_taxonomy_name_by_id( $attribute_id ); 
  961. } elseif ( ! empty( $attribute['name'] ) ) { 
  962. $attribute_name = wc_clean( $attribute['name'] ); 
  963.  
  964. if ( ! $attribute_id && ! $attribute_name ) { 
  965. continue; 
  966.  
  967. if ( $attribute_id ) { 
  968.  
  969. if ( isset( $attribute['options'] ) ) { 
  970. $options = $attribute['options']; 
  971.  
  972. if ( ! is_array( $attribute['options'] ) ) { 
  973. // Text based attributes - Posted values are term names. 
  974. $options = explode( WC_DELIMITER, $options ); 
  975.  
  976. $values = array_map( 'wc_sanitize_term_text_based', $options ); 
  977. $values = array_filter( $values, 'strlen' ); 
  978. } else { 
  979. $values = array(); 
  980.  
  981. if ( ! empty( $values ) ) { 
  982. // Add attribute to array, but don't set values. 
  983. $attribute_object = new WC_Product_Attribute(); 
  984. $attribute_object->set_id( $attribute_id ); 
  985. $attribute_object->set_name( $attribute_name ); 
  986. $attribute_object->set_options( $values ); 
  987. $attribute_object->set_position( isset( $attribute['position'] ) ? (string) absint( $attribute['position'] ) : '0' ); 
  988. $attribute_object->set_visible( ( isset( $attribute['visible'] ) && $attribute['visible'] ) ? 1 : 0 ); 
  989. $attribute_object->set_variation( ( isset( $attribute['variation'] ) && $attribute['variation'] ) ? 1 : 0 ); 
  990. $attributes[] = $attribute_object; 
  991. } elseif ( isset( $attribute['options'] ) ) { 
  992. // Custom attribute - Add attribute to array and set the values. 
  993. if ( is_array( $attribute['options'] ) ) { 
  994. $values = $attribute['options']; 
  995. } else { 
  996. $values = explode( WC_DELIMITER, $attribute['options'] ); 
  997. $attribute_object = new WC_Product_Attribute(); 
  998. $attribute_object->set_name( $attribute_name ); 
  999. $attribute_object->set_options( $values ); 
  1000. $attribute_object->set_position( isset( $attribute['position'] ) ? (string) absint( $attribute['position'] ) : '0' ); 
  1001. $attribute_object->set_visible( ( isset( $attribute['visible'] ) && $attribute['visible'] ) ? 1 : 0 ); 
  1002. $attribute_object->set_variation( ( isset( $attribute['variation'] ) && $attribute['variation'] ) ? 1 : 0 ); 
  1003. $attributes[] = $attribute_object; 
  1004. $product->set_attributes( $attributes ); 
  1005.  
  1006. // Sales and prices. 
  1007. if ( in_array( $product->get_type(), array( 'variable', 'grouped' ), true ) ) { 
  1008. $product->set_regular_price( '' ); 
  1009. $product->set_sale_price( '' ); 
  1010. $product->set_date_on_sale_to( '' ); 
  1011. $product->set_date_on_sale_from( '' ); 
  1012. $product->set_price( '' ); 
  1013. } else { 
  1014. // Regular Price. 
  1015. if ( isset( $request['regular_price'] ) ) { 
  1016. $product->set_regular_price( $request['regular_price'] ); 
  1017.  
  1018. // Sale Price. 
  1019. if ( isset( $request['sale_price'] ) ) { 
  1020. $product->set_sale_price( $request['sale_price'] ); 
  1021.  
  1022. if ( isset( $request['date_on_sale_from'] ) ) { 
  1023. $product->set_date_on_sale_from( $request['date_on_sale_from'] ); 
  1024.  
  1025. if ( isset( $request['date_on_sale_to'] ) ) { 
  1026. $product->set_date_on_sale_to( $request['date_on_sale_to'] ); 
  1027.  
  1028. // Product parent ID for groups. 
  1029. if ( isset( $request['parent_id'] ) ) { 
  1030. $product->set_parent_id( $request['parent_id'] ); 
  1031.  
  1032. // Sold individually. 
  1033. if ( isset( $request['sold_individually'] ) ) { 
  1034. $product->set_sold_individually( $request['sold_individually'] ); 
  1035.  
  1036. // Stock status. 
  1037. if ( isset( $request['in_stock'] ) ) { 
  1038. $stock_status = true === $request['in_stock'] ? 'instock' : 'outofstock'; 
  1039. } else { 
  1040. $stock_status = $product->get_stock_status(); 
  1041.  
  1042. // Stock data. 
  1043. if ( 'yes' === get_option( 'woocommerce_manage_stock' ) ) { 
  1044. // Manage stock. 
  1045. if ( isset( $request['manage_stock'] ) ) { 
  1046. $product->set_manage_stock( $request['manage_stock'] ); 
  1047.  
  1048. // Backorders. 
  1049. if ( isset( $request['backorders'] ) ) { 
  1050. $product->set_backorders( $request['backorders'] ); 
  1051.  
  1052. if ( $product->is_type( 'grouped' ) ) { 
  1053. $product->set_manage_stock( 'no' ); 
  1054. $product->set_backorders( 'no' ); 
  1055. $product->set_stock_quantity( '' ); 
  1056. $product->set_stock_status( $stock_status ); 
  1057. } elseif ( $product->is_type( 'external' ) ) { 
  1058. $product->set_manage_stock( 'no' ); 
  1059. $product->set_backorders( 'no' ); 
  1060. $product->set_stock_quantity( '' ); 
  1061. $product->set_stock_status( 'instock' ); 
  1062. } elseif ( $product->get_manage_stock() ) { 
  1063. // Stock status is always determined by children so sync later. 
  1064. if ( ! $product->is_type( 'variable' ) ) { 
  1065. $product->set_stock_status( $stock_status ); 
  1066.  
  1067. // Stock quantity. 
  1068. if ( isset( $request['stock_quantity'] ) ) { 
  1069. $product->set_stock_quantity( wc_stock_amount( $request['stock_quantity'] ) ); 
  1070. } elseif ( isset( $request['inventory_delta'] ) ) { 
  1071. $stock_quantity = wc_stock_amount( $product->get_stock_quantity() ); 
  1072. $stock_quantity += wc_stock_amount( $request['inventory_delta'] ); 
  1073. $product->set_stock_quantity( wc_stock_amount( $stock_quantity ) ); 
  1074. } else { 
  1075. // Don't manage stock. 
  1076. $product->set_manage_stock( 'no' ); 
  1077. $product->set_stock_quantity( '' ); 
  1078. $product->set_stock_status( $stock_status ); 
  1079. } elseif ( ! $product->is_type( 'variable' ) ) { 
  1080. $product->set_stock_status( $stock_status ); 
  1081.  
  1082. // Upsells. 
  1083. if ( isset( $request['upsell_ids'] ) ) { 
  1084. $upsells = array(); 
  1085. $ids = $request['upsell_ids']; 
  1086.  
  1087. if ( ! empty( $ids ) ) { 
  1088. foreach ( $ids as $id ) { 
  1089. if ( $id && $id > 0 ) { 
  1090. $upsells[] = $id; 
  1091.  
  1092. $product->set_upsell_ids( $upsells ); 
  1093.  
  1094. // Cross sells. 
  1095. if ( isset( $request['cross_sell_ids'] ) ) { 
  1096. $crosssells = array(); 
  1097. $ids = $request['cross_sell_ids']; 
  1098.  
  1099. if ( ! empty( $ids ) ) { 
  1100. foreach ( $ids as $id ) { 
  1101. if ( $id && $id > 0 ) { 
  1102. $crosssells[] = $id; 
  1103.  
  1104. $product->set_cross_sell_ids( $crosssells ); 
  1105.  
  1106. // Product categories. 
  1107. if ( isset( $request['categories'] ) && is_array( $request['categories'] ) ) { 
  1108. $product = $this->save_taxonomy_terms( $product, $request['categories'] ); 
  1109.  
  1110. // Product tags. 
  1111. if ( isset( $request['tags'] ) && is_array( $request['tags'] ) ) { 
  1112. $product = $this->save_taxonomy_terms( $product, $request['tags'], 'tag' ); 
  1113.  
  1114. // Downloadable. 
  1115. if ( isset( $request['downloadable'] ) ) { 
  1116. $product->set_downloadable( $request['downloadable'] ); 
  1117.  
  1118. // Downloadable options. 
  1119. if ( $product->get_downloadable() ) { 
  1120.  
  1121. // Downloadable files. 
  1122. if ( isset( $request['downloads'] ) && is_array( $request['downloads'] ) ) { 
  1123. $product = $this->save_downloadable_files( $product, $request['downloads'] ); 
  1124.  
  1125. // Download limit. 
  1126. if ( isset( $request['download_limit'] ) ) { 
  1127. $product->set_download_limit( $request['download_limit'] ); 
  1128.  
  1129. // Download expiry. 
  1130. if ( isset( $request['download_expiry'] ) ) { 
  1131. $product->set_download_expiry( $request['download_expiry'] ); 
  1132.  
  1133. // Product url and button text for external products. 
  1134. if ( $product->is_type( 'external' ) ) { 
  1135. if ( isset( $request['external_url'] ) ) { 
  1136. $product->set_product_url( $request['external_url'] ); 
  1137.  
  1138. if ( isset( $request['button_text'] ) ) { 
  1139. $product->set_button_text( $request['button_text'] ); 
  1140.  
  1141. // Save default attributes for variable products. 
  1142. if ( $product->is_type( 'variable' ) ) { 
  1143. $product = $this->save_default_attributes( $product, $request ); 
  1144.  
  1145. return $product; 
  1146.  
  1147. /** 
  1148. * Save variations. 
  1149. * @throws WC_REST_Exception REST API exceptions. 
  1150. * @param WC_Product $product Product instance. 
  1151. * @param WP_REST_Request $request Request data. 
  1152. * @return bool 
  1153. */ 
  1154. protected function save_variations_data( $product, $request ) { 
  1155. foreach ( $request['variations'] as $menu_order => $data ) { 
  1156. $variation = new WC_Product_Variation( isset( $data['id'] ) ? absint( $data['id'] ) : 0 ); 
  1157.  
  1158. // Create initial name and status. 
  1159. if ( ! $variation->get_slug() ) { 
  1160. /** translators: 1: variation id 2: product name */ 
  1161. $variation->set_name( sprintf( __( 'Variation #%1$s of %2$s', 'woocommerce' ), $variation->get_id(), $product->get_name() ) ); 
  1162. $variation->set_status( isset( $data['visible'] ) && false === $data['visible'] ? 'private' : 'publish' ); 
  1163.  
  1164. // Parent ID. 
  1165. $variation->set_parent_id( $product->get_id() ); 
  1166.  
  1167. // Menu order. 
  1168. $variation->set_menu_order( $menu_order ); 
  1169.  
  1170. // Status. 
  1171. if ( isset( $data['visible'] ) ) { 
  1172. $variation->set_status( false === $data['visible'] ? 'private' : 'publish' ); 
  1173.  
  1174. // SKU. 
  1175. if ( isset( $data['sku'] ) ) { 
  1176. $variation->set_sku( wc_clean( $data['sku'] ) ); 
  1177.  
  1178. // Thumbnail. 
  1179. if ( isset( $data['image'] ) && is_array( $data['image'] ) ) { 
  1180. $image = $data['image']; 
  1181. $image = current( $image ); 
  1182. if ( is_array( $image ) ) { 
  1183. $image['position'] = 0; 
  1184.  
  1185. $variation = $this->set_product_images( $variation, array( $image ) ); 
  1186.  
  1187. // Virtual variation. 
  1188. if ( isset( $data['virtual'] ) ) { 
  1189. $variation->set_virtual( $data['virtual'] ); 
  1190.  
  1191. // Downloadable variation. 
  1192. if ( isset( $data['downloadable'] ) ) { 
  1193. $variation->set_downloadable( $data['downloadable'] ); 
  1194.  
  1195. // Downloads. 
  1196. if ( $variation->get_downloadable() ) { 
  1197. // Downloadable files. 
  1198. if ( isset( $data['downloads'] ) && is_array( $data['downloads'] ) ) { 
  1199. $variation = $this->save_downloadable_files( $variation, $data['downloads'] ); 
  1200.  
  1201. // Download limit. 
  1202. if ( isset( $data['download_limit'] ) ) { 
  1203. $variation->set_download_limit( $data['download_limit'] ); 
  1204.  
  1205. // Download expiry. 
  1206. if ( isset( $data['download_expiry'] ) ) { 
  1207. $variation->set_download_expiry( $data['download_expiry'] ); 
  1208.  
  1209. // Shipping data. 
  1210. $variation = $this->save_product_shipping_data( $variation, $data ); 
  1211.  
  1212. // Stock handling. 
  1213. if ( isset( $data['manage_stock'] ) ) { 
  1214. $variation->set_manage_stock( $data['manage_stock'] ); 
  1215.  
  1216. if ( isset( $data['in_stock'] ) ) { 
  1217. $variation->set_stock_status( true === $data['in_stock'] ? 'instock' : 'outofstock' ); 
  1218.  
  1219. if ( isset( $data['backorders'] ) ) { 
  1220. $variation->set_backorders( $data['backorders'] ); 
  1221.  
  1222. if ( $variation->get_manage_stock() ) { 
  1223. if ( isset( $data['stock_quantity'] ) ) { 
  1224. $variation->set_stock_quantity( $data['stock_quantity'] ); 
  1225. } elseif ( isset( $data['inventory_delta'] ) ) { 
  1226. $stock_quantity = wc_stock_amount( $variation->get_stock_quantity() ); 
  1227. $stock_quantity += wc_stock_amount( $data['inventory_delta'] ); 
  1228. $variation->set_stock_quantity( $stock_quantity ); 
  1229. } else { 
  1230. $variation->set_backorders( 'no' ); 
  1231. $variation->set_stock_quantity( '' ); 
  1232.  
  1233. // Regular Price. 
  1234. if ( isset( $data['regular_price'] ) ) { 
  1235. $variation->set_regular_price( $data['regular_price'] ); 
  1236.  
  1237. // Sale Price. 
  1238. if ( isset( $data['sale_price'] ) ) { 
  1239. $variation->set_sale_price( $data['sale_price'] ); 
  1240.  
  1241. if ( isset( $data['date_on_sale_from'] ) ) { 
  1242. $variation->set_date_on_sale_from( $data['date_on_sale_from'] ); 
  1243.  
  1244. if ( isset( $data['date_on_sale_to'] ) ) { 
  1245. $variation->set_date_on_sale_to( $data['date_on_sale_to'] ); 
  1246.  
  1247. // Tax class. 
  1248. if ( isset( $data['tax_class'] ) ) { 
  1249. $variation->set_tax_class( $data['tax_class'] ); 
  1250.  
  1251. // Description. 
  1252. if ( isset( $data['description'] ) ) { 
  1253. $variation->set_description( wp_kses_post( $data['description'] ) ); 
  1254.  
  1255. // Update taxonomies. 
  1256. if ( isset( $data['attributes'] ) ) { 
  1257. $attributes = array(); 
  1258. $parent_attributes = $product->get_attributes(); 
  1259.  
  1260. foreach ( $data['attributes'] as $attribute ) { 
  1261. $attribute_id = 0; 
  1262. $attribute_name = ''; 
  1263.  
  1264. // Check ID for global attributes or name for product attributes. 
  1265. if ( ! empty( $attribute['id'] ) ) { 
  1266. $attribute_id = absint( $attribute['id'] ); 
  1267. $attribute_name = wc_attribute_taxonomy_name_by_id( $attribute_id ); 
  1268. } elseif ( ! empty( $attribute['name'] ) ) { 
  1269. $attribute_name = sanitize_title( $attribute['name'] ); 
  1270.  
  1271. if ( ! $attribute_id && ! $attribute_name ) { 
  1272. continue; 
  1273.  
  1274. if ( ! isset( $parent_attributes[ $attribute_name ] ) || ! $parent_attributes[ $attribute_name ]->get_variation() ) { 
  1275. continue; 
  1276.  
  1277. $attribute_key = sanitize_title( $parent_attributes[ $attribute_name ]->get_name() ); 
  1278. $attribute_value = isset( $attribute['option'] ) ? wc_clean( stripslashes( $attribute['option'] ) ) : ''; 
  1279.  
  1280. if ( $parent_attributes[ $attribute_name ]->is_taxonomy() ) { 
  1281. // If dealing with a taxonomy, we need to get the slug from the name posted to the API. 
  1282. $term = get_term_by( 'name', $attribute_value, $attribute_name ); 
  1283.  
  1284. if ( $term && ! is_wp_error( $term ) ) { 
  1285. $attribute_value = $term->slug; 
  1286. } else { 
  1287. $attribute_value = sanitize_title( $attribute_value ); 
  1288.  
  1289. $attributes[ $attribute_key ] = $attribute_value; 
  1290.  
  1291. $variation->set_attributes( $attributes ); 
  1292.  
  1293. $variation->save(); 
  1294.  
  1295. do_action( 'woocommerce_rest_save_product_variation', $variation->get_id(), $menu_order, $data ); 
  1296.  
  1297. return true; 
  1298.  
  1299. /** 
  1300. * Add post meta fields. 
  1301. * @param WP_Post $post Post data. 
  1302. * @param WP_REST_Request $request Request data. 
  1303. * @return bool|WP_Error 
  1304. */ 
  1305. protected function add_post_meta_fields( $post, $request ) { 
  1306. return $this->update_post_meta_fields( $post, $request ); 
  1307.  
  1308. /** 
  1309. * Update post meta fields. 
  1310. * @param WP_Post $post Post data. 
  1311. * @param WP_REST_Request $request Request data. 
  1312. * @return bool|WP_Error 
  1313. */ 
  1314. protected function update_post_meta_fields( $post, $request ) { 
  1315. $product = wc_get_product( $post ); 
  1316.  
  1317. // Check for featured/gallery images, upload it and set it. 
  1318. if ( isset( $request['images'] ) ) { 
  1319. $product = $this->set_product_images( $product, $request['images'] ); 
  1320.  
  1321. // Save product meta fields. 
  1322. $product = $this->set_product_meta( $product, $request ); 
  1323.  
  1324. // Save the product data. 
  1325. $product->save(); 
  1326.  
  1327. // Save variations. 
  1328. if ( $product->is_type( 'variable' ) ) { 
  1329. if ( isset( $request['variations'] ) && is_array( $request['variations'] ) ) { 
  1330. $this->save_variations_data( $product, $request ); 
  1331.  
  1332. // Clear caches here so in sync with any new variations/children. 
  1333. wc_delete_product_transients( $product->get_id() ); 
  1334. wp_cache_delete( 'product-' . $product->get_id(), 'products' ); 
  1335.  
  1336. return true; 
  1337.  
  1338. /** 
  1339. * Clear cache/transients. 
  1340. * @param WP_Post $post Post data. 
  1341. */ 
  1342. public function clear_transients( $post ) { 
  1343. wc_delete_product_transients( $post->ID ); 
  1344.  
  1345. /** 
  1346. * Delete post. 
  1347. * @param int|WP_Post $id Post ID or WP_Post instance. 
  1348. */ 
  1349. protected function delete_post( $id ) { 
  1350. if ( ! empty( $id->ID ) ) { 
  1351. $id = $id->ID; 
  1352. } elseif ( ! is_numeric( $id ) || 0 >= $id ) { 
  1353. return; 
  1354.  
  1355. // Delete product attachments. 
  1356. $attachments = get_posts( array( 
  1357. 'post_parent' => $id,  
  1358. 'post_status' => 'any',  
  1359. 'post_type' => 'attachment',  
  1360. ) ); 
  1361.  
  1362. foreach ( (array) $attachments as $attachment ) { 
  1363. wp_delete_attachment( $attachment->ID, true ); 
  1364.  
  1365. // Delete product. 
  1366. $product = wc_get_product( $id ); 
  1367. $product->delete( true ); 
  1368.  
  1369. /** 
  1370. * Delete a single item. 
  1371. * @param WP_REST_Request $request Full details about the request. 
  1372. * @return WP_REST_Response|WP_Error 
  1373. */ 
  1374. public function delete_item( $request ) { 
  1375. $id = (int) $request['id']; 
  1376. $force = (bool) $request['force']; 
  1377. $post = get_post( $id ); 
  1378. $product = wc_get_product( $id ); 
  1379.  
  1380. if ( ! empty( $post->post_type ) && 'product_variation' === $post->post_type && 'product' === $this->post_type ) { 
  1381. return new WP_Error( "woocommerce_rest_invalid_{$this->post_type}_id", __( 'To manipulate product variations you should use the /products/<product_id>/variations/<id> endpoint.', 'woocommerce' ), array( 'status' => 404 ) ); 
  1382. } elseif ( empty( $id ) || empty( $post->ID ) || $post->post_type !== $this->post_type ) { 
  1383. return new WP_Error( "woocommerce_rest_{$this->post_type}_invalid_id", __( 'Invalid post ID.', 'woocommerce' ), array( 'status' => 404 ) ); 
  1384.  
  1385. $supports_trash = EMPTY_TRASH_DAYS > 0; 
  1386.  
  1387. /** 
  1388. * Filter whether an item is trashable. 
  1389. * Return false to disable trash support for the item. 
  1390. * @param boolean $supports_trash Whether the item type support trashing. 
  1391. * @param WP_Post $post The Post object being considered for trashing support. 
  1392. */ 
  1393. $supports_trash = apply_filters( "woocommerce_rest_{$this->post_type}_trashable", $supports_trash, $post ); 
  1394.  
  1395. if ( ! wc_rest_check_post_permissions( $this->post_type, 'delete', $post->ID ) ) { 
  1396. /** translators: %s: post type */ 
  1397. 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() ) ); 
  1398.  
  1399. $request->set_param( 'context', 'edit' ); 
  1400. $response = $this->prepare_item_for_response( $post, $request ); 
  1401.  
  1402. // If we're forcing, then delete permanently. 
  1403. if ( $force ) { 
  1404. if ( $product->is_type( 'variable' ) ) { 
  1405. foreach ( $product->get_children() as $child_id ) { 
  1406. $child = wc_get_product( $child_id ); 
  1407. $child->delete( true ); 
  1408. } elseif ( $product->is_type( 'grouped' ) ) { 
  1409. foreach ( $product->get_children() as $child_id ) { 
  1410. $child = wc_get_product( $child_id ); 
  1411. $child->set_parent_id( 0 ); 
  1412. $child->save(); 
  1413.  
  1414. $product->delete( true ); 
  1415. $result = $product->get_id() > 0 ? false : true; 
  1416. } else { 
  1417. // If we don't support trashing for this type, error out. 
  1418. if ( ! $supports_trash ) { 
  1419. /** translators: %s: post type */ 
  1420. return new WP_Error( 'woocommerce_rest_trash_not_supported', sprintf( __( 'The %s does not support trashing.', 'woocommerce' ), $this->post_type ), array( 'status' => 501 ) ); 
  1421.  
  1422. // Otherwise, only trash if we haven't already. 
  1423. if ( 'trash' === $post->post_status ) { 
  1424. /** translators: %s: post type */ 
  1425. return new WP_Error( 'woocommerce_rest_already_trashed', sprintf( __( 'The %s has already been deleted.', 'woocommerce' ), $this->post_type ), array( 'status' => 410 ) ); 
  1426.  
  1427. // (Note that internally this falls through to `wp_delete_post` if 
  1428. // the trash is disabled.) 
  1429. $product->delete(); 
  1430. $result = 'trash' === $product->get_status(); 
  1431.  
  1432. if ( ! $result ) { 
  1433. /** translators: %s: post type */ 
  1434. return new WP_Error( 'woocommerce_rest_cannot_delete', sprintf( __( 'The %s cannot be deleted.', 'woocommerce' ), $this->post_type ), array( 'status' => 500 ) ); 
  1435.  
  1436. // Delete parent product transients. 
  1437. if ( $parent_id = wp_get_post_parent_id( $id ) ) { 
  1438. wc_delete_product_transients( $parent_id ); 
  1439.  
  1440. /** 
  1441. * Fires after a single item is deleted or trashed via the REST API. 
  1442. * @param object $post The deleted or trashed item. 
  1443. * @param WP_REST_Response $response The response data. 
  1444. * @param WP_REST_Request $request The request sent to the API. 
  1445. */ 
  1446. do_action( "woocommerce_rest_delete_{$this->post_type}", $post, $response, $request ); 
  1447.  
  1448. return $response; 
  1449.  
  1450. /** 
  1451. * Get the Product's schema, conforming to JSON Schema. 
  1452. * @return array 
  1453. */ 
  1454. public function get_item_schema() { 
  1455. $weight_unit = get_option( 'woocommerce_weight_unit' ); 
  1456. $dimension_unit = get_option( 'woocommerce_dimension_unit' ); 
  1457. $schema = array( 
  1458. '$schema' => 'http://json-schema.org/draft-04/schema#',  
  1459. 'title' => $this->post_type,  
  1460. 'type' => 'object',  
  1461. 'properties' => array( 
  1462. 'id' => array( 
  1463. 'description' => __( 'Unique identifier for the resource.', 'woocommerce' ),  
  1464. 'type' => 'integer',  
  1465. 'context' => array( 'view', 'edit' ),  
  1466. 'readonly' => true,  
  1467. ),  
  1468. 'name' => array( 
  1469. 'description' => __( 'Product name.', 'woocommerce' ),  
  1470. 'type' => 'string',  
  1471. 'context' => array( 'view', 'edit' ),  
  1472. ),  
  1473. 'slug' => array( 
  1474. 'description' => __( 'Product slug.', 'woocommerce' ),  
  1475. 'type' => 'string',  
  1476. 'context' => array( 'view', 'edit' ),  
  1477. ),  
  1478. 'permalink' => array( 
  1479. 'description' => __( 'Product URL.', 'woocommerce' ),  
  1480. 'type' => 'string',  
  1481. 'format' => 'uri',  
  1482. 'context' => array( 'view', 'edit' ),  
  1483. 'readonly' => true,  
  1484. ),  
  1485. 'date_created' => array( 
  1486. 'description' => __( "The date the product was created, in the site's timezone.", 'woocommerce' ),  
  1487. 'type' => 'date-time',  
  1488. 'context' => array( 'view', 'edit' ),  
  1489. 'readonly' => true,  
  1490. ),  
  1491. 'date_modified' => array( 
  1492. 'description' => __( "The date the product was last modified, in the site's timezone.", 'woocommerce' ),  
  1493. 'type' => 'date-time',  
  1494. 'context' => array( 'view', 'edit' ),  
  1495. 'readonly' => true,  
  1496. ),  
  1497. 'type' => array( 
  1498. 'description' => __( 'Product type.', 'woocommerce' ),  
  1499. 'type' => 'string',  
  1500. 'default' => 'simple',  
  1501. 'enum' => array_keys( wc_get_product_types() ),  
  1502. 'context' => array( 'view', 'edit' ),  
  1503. ),  
  1504. 'status' => array( 
  1505. 'description' => __( 'Product status (post status).', 'woocommerce' ),  
  1506. 'type' => 'string',  
  1507. 'default' => 'publish',  
  1508. 'enum' => array_keys( get_post_statuses() ),  
  1509. 'context' => array( 'view', 'edit' ),  
  1510. ),  
  1511. 'featured' => array( 
  1512. 'description' => __( 'Featured product.', 'woocommerce' ),  
  1513. 'type' => 'boolean',  
  1514. 'default' => false,  
  1515. 'context' => array( 'view', 'edit' ),  
  1516. ),  
  1517. 'catalog_visibility' => array( 
  1518. 'description' => __( 'Catalog visibility.', 'woocommerce' ),  
  1519. 'type' => 'string',  
  1520. 'default' => 'visible',  
  1521. 'enum' => array( 'visible', 'catalog', 'search', 'hidden' ),  
  1522. 'context' => array( 'view', 'edit' ),  
  1523. ),  
  1524. 'description' => array( 
  1525. 'description' => __( 'Product description.', 'woocommerce' ),  
  1526. 'type' => 'string',  
  1527. 'context' => array( 'view', 'edit' ),  
  1528. ),  
  1529. 'short_description' => array( 
  1530. 'description' => __( 'Product short description.', 'woocommerce' ),  
  1531. 'type' => 'string',  
  1532. 'context' => array( 'view', 'edit' ),  
  1533. ),  
  1534. 'sku' => array( 
  1535. 'description' => __( 'Unique identifier.', 'woocommerce' ),  
  1536. 'type' => 'string',  
  1537. 'context' => array( 'view', 'edit' ),  
  1538. ),  
  1539. 'price' => array( 
  1540. 'description' => __( 'Current product price.', 'woocommerce' ),  
  1541. 'type' => 'string',  
  1542. 'context' => array( 'view', 'edit' ),  
  1543. 'readonly' => true,  
  1544. ),  
  1545. 'regular_price' => array( 
  1546. 'description' => __( 'Product regular price.', 'woocommerce' ),  
  1547. 'type' => 'string',  
  1548. 'context' => array( 'view', 'edit' ),  
  1549. ),  
  1550. 'sale_price' => array( 
  1551. 'description' => __( 'Product sale price.', 'woocommerce' ),  
  1552. 'type' => 'string',  
  1553. 'context' => array( 'view', 'edit' ),  
  1554. ),  
  1555. 'date_on_sale_from' => array( 
  1556. 'description' => __( 'Start date of sale price.', 'woocommerce' ),  
  1557. 'type' => 'string',  
  1558. 'context' => array( 'view', 'edit' ),  
  1559. ),  
  1560. 'date_on_sale_to' => array( 
  1561. 'description' => __( 'End data of sale price.', 'woocommerce' ),  
  1562. 'type' => 'string',  
  1563. 'context' => array( 'view', 'edit' ),  
  1564. ),  
  1565. 'price_html' => array( 
  1566. 'description' => __( 'Price formatted in HTML.', 'woocommerce' ),  
  1567. 'type' => 'string',  
  1568. 'context' => array( 'view', 'edit' ),  
  1569. 'readonly' => true,  
  1570. ),  
  1571. 'on_sale' => array( 
  1572. 'description' => __( 'Shows if the product is on sale.', 'woocommerce' ),  
  1573. 'type' => 'boolean',  
  1574. 'context' => array( 'view', 'edit' ),  
  1575. 'readonly' => true,  
  1576. ),  
  1577. 'purchasable' => array( 
  1578. 'description' => __( 'Shows if the product can be bought.', 'woocommerce' ),  
  1579. 'type' => 'boolean',  
  1580. 'context' => array( 'view', 'edit' ),  
  1581. 'readonly' => true,  
  1582. ),  
  1583. 'total_sales' => array( 
  1584. 'description' => __( 'Amount of sales.', 'woocommerce' ),  
  1585. 'type' => 'integer',  
  1586. 'context' => array( 'view', 'edit' ),  
  1587. 'readonly' => true,  
  1588. ),  
  1589. 'virtual' => array( 
  1590. 'description' => __( 'If the product is virtual.', 'woocommerce' ),  
  1591. 'type' => 'boolean',  
  1592. 'default' => false,  
  1593. 'context' => array( 'view', 'edit' ),  
  1594. ),  
  1595. 'downloadable' => array( 
  1596. 'description' => __( 'If the product is downloadable.', 'woocommerce' ),  
  1597. 'type' => 'boolean',  
  1598. 'default' => false,  
  1599. 'context' => array( 'view', 'edit' ),  
  1600. ),  
  1601. 'downloads' => array( 
  1602. 'description' => __( 'List of downloadable files.', 'woocommerce' ),  
  1603. 'type' => 'array',  
  1604. 'context' => array( 'view', 'edit' ),  
  1605. 'items' => array( 
  1606. 'type' => 'object',  
  1607. 'properties' => array( 
  1608. 'id' => array( 
  1609. 'description' => __( 'File MD5 hash.', 'woocommerce' ),  
  1610. 'type' => 'string',  
  1611. 'context' => array( 'view', 'edit' ),  
  1612. 'readonly' => true,  
  1613. ),  
  1614. 'name' => array( 
  1615. 'description' => __( 'File name.', 'woocommerce' ),  
  1616. 'type' => 'string',  
  1617. 'context' => array( 'view', 'edit' ),  
  1618. ),  
  1619. 'file' => array( 
  1620. 'description' => __( 'File URL.', 'woocommerce' ),  
  1621. 'type' => 'string',  
  1622. 'context' => array( 'view', 'edit' ),  
  1623. ),  
  1624. ),  
  1625. ),  
  1626. ),  
  1627. 'download_limit' => array( 
  1628. 'description' => __( 'Number of times downloadable files can be downloaded after purchase.', 'woocommerce' ),  
  1629. 'type' => 'integer',  
  1630. 'default' => -1,  
  1631. 'context' => array( 'view', 'edit' ),  
  1632. ),  
  1633. 'download_expiry' => array( 
  1634. 'description' => __( 'Number of days until access to downloadable files expires.', 'woocommerce' ),  
  1635. 'type' => 'integer',  
  1636. 'default' => -1,  
  1637. 'context' => array( 'view', 'edit' ),  
  1638. ),  
  1639. 'download_type' => array( 
  1640. 'description' => __( 'Download type, this controls the schema on the front-end.', 'woocommerce' ),  
  1641. 'type' => 'string',  
  1642. 'default' => 'standard',  
  1643. 'enum' => array( 'standard' ),  
  1644. 'context' => array( 'view', 'edit' ),  
  1645. ),  
  1646. 'external_url' => array( 
  1647. 'description' => __( 'Product external URL. Only for external products.', 'woocommerce' ),  
  1648. 'type' => 'string',  
  1649. 'format' => 'uri',  
  1650. 'context' => array( 'view', 'edit' ),  
  1651. ),  
  1652. 'button_text' => array( 
  1653. 'description' => __( 'Product external button text. Only for external products.', 'woocommerce' ),  
  1654. 'type' => 'string',  
  1655. 'context' => array( 'view', 'edit' ),  
  1656. ),  
  1657. 'tax_status' => array( 
  1658. 'description' => __( 'Tax status.', 'woocommerce' ),  
  1659. 'type' => 'string',  
  1660. 'default' => 'taxable',  
  1661. 'enum' => array( 'taxable', 'shipping', 'none' ),  
  1662. 'context' => array( 'view', 'edit' ),  
  1663. ),  
  1664. 'tax_class' => array( 
  1665. 'description' => __( 'Tax class.', 'woocommerce' ),  
  1666. 'type' => 'string',  
  1667. 'context' => array( 'view', 'edit' ),  
  1668. ),  
  1669. 'manage_stock' => array( 
  1670. 'description' => __( 'Stock management at product level.', 'woocommerce' ),  
  1671. 'type' => 'boolean',  
  1672. 'default' => false,  
  1673. 'context' => array( 'view', 'edit' ),  
  1674. ),  
  1675. 'stock_quantity' => array( 
  1676. 'description' => __( 'Stock quantity.', 'woocommerce' ),  
  1677. 'type' => 'integer',  
  1678. 'context' => array( 'view', 'edit' ),  
  1679. ),  
  1680. 'in_stock' => array( 
  1681. 'description' => __( 'Controls whether or not the product is listed as "in stock" or "out of stock" on the frontend.', 'woocommerce' ),  
  1682. 'type' => 'boolean',  
  1683. 'default' => true,  
  1684. 'context' => array( 'view', 'edit' ),  
  1685. ),  
  1686. 'backorders' => array( 
  1687. 'description' => __( 'If managing stock, this controls if backorders are allowed.', 'woocommerce' ),  
  1688. 'type' => 'string',  
  1689. 'default' => 'no',  
  1690. 'enum' => array( 'no', 'notify', 'yes' ),  
  1691. 'context' => array( 'view', 'edit' ),  
  1692. ),  
  1693. 'backorders_allowed' => array( 
  1694. 'description' => __( 'Shows if backorders are allowed.', 'woocommerce' ),  
  1695. 'type' => 'boolean',  
  1696. 'context' => array( 'view', 'edit' ),  
  1697. 'readonly' => true,  
  1698. ),  
  1699. 'backordered' => array( 
  1700. 'description' => __( 'Shows if the product is on backordered.', 'woocommerce' ),  
  1701. 'type' => 'boolean',  
  1702. 'context' => array( 'view', 'edit' ),  
  1703. 'readonly' => true,  
  1704. ),  
  1705. 'sold_individually' => array( 
  1706. 'description' => __( 'Allow one item to be bought in a single order.', 'woocommerce' ),  
  1707. 'type' => 'boolean',  
  1708. 'default' => false,  
  1709. 'context' => array( 'view', 'edit' ),  
  1710. ),  
  1711. 'weight' => array( 
  1712. /** translators: %s: weight unit */ 
  1713. 'description' => sprintf( __( 'Product weight (%s).', 'woocommerce' ), $weight_unit ),  
  1714. 'type' => 'string',  
  1715. 'context' => array( 'view', 'edit' ),  
  1716. ),  
  1717. 'dimensions' => array( 
  1718. 'description' => __( 'Product dimensions.', 'woocommerce' ),  
  1719. 'type' => 'object',  
  1720. 'context' => array( 'view', 'edit' ),  
  1721. 'properties' => array( 
  1722. 'length' => array( 
  1723. /** translators: %s: dimension unit */ 
  1724. 'description' => sprintf( __( 'Product length (%s).', 'woocommerce' ), $dimension_unit ),  
  1725. 'type' => 'string',  
  1726. 'context' => array( 'view', 'edit' ),  
  1727. ),  
  1728. 'width' => array( 
  1729. /** translators: %s: dimension unit */ 
  1730. 'description' => sprintf( __( 'Product width (%s).', 'woocommerce' ), $dimension_unit ),  
  1731. 'type' => 'string',  
  1732. 'context' => array( 'view', 'edit' ),  
  1733. ),  
  1734. 'height' => array( 
  1735. /** translators: %s: dimension unit */ 
  1736. 'description' => sprintf( __( 'Product height (%s).', 'woocommerce' ), $dimension_unit ),  
  1737. 'type' => 'string',  
  1738. 'context' => array( 'view', 'edit' ),  
  1739. ),  
  1740. ),  
  1741. ),  
  1742. 'shipping_required' => array( 
  1743. 'description' => __( 'Shows if the product need to be shipped.', 'woocommerce' ),  
  1744. 'type' => 'boolean',  
  1745. 'context' => array( 'view', 'edit' ),  
  1746. 'readonly' => true,  
  1747. ),  
  1748. 'shipping_taxable' => array( 
  1749. 'description' => __( 'Shows whether or not the product shipping is taxable.', 'woocommerce' ),  
  1750. 'type' => 'boolean',  
  1751. 'context' => array( 'view', 'edit' ),  
  1752. 'readonly' => true,  
  1753. ),  
  1754. 'shipping_class' => array( 
  1755. 'description' => __( 'Shipping class slug.', 'woocommerce' ),  
  1756. 'type' => 'string',  
  1757. 'context' => array( 'view', 'edit' ),  
  1758. ),  
  1759. 'shipping_class_id' => array( 
  1760. 'description' => __( 'Shipping class ID.', 'woocommerce' ),  
  1761. 'type' => 'string',  
  1762. 'context' => array( 'view', 'edit' ),  
  1763. 'readonly' => true,  
  1764. ),  
  1765. 'reviews_allowed' => array( 
  1766. 'description' => __( 'Allow reviews.', 'woocommerce' ),  
  1767. 'type' => 'boolean',  
  1768. 'default' => true,  
  1769. 'context' => array( 'view', 'edit' ),  
  1770. ),  
  1771. 'average_rating' => array( 
  1772. 'description' => __( 'Reviews average rating.', 'woocommerce' ),  
  1773. 'type' => 'string',  
  1774. 'context' => array( 'view', 'edit' ),  
  1775. 'readonly' => true,  
  1776. ),  
  1777. 'rating_count' => array( 
  1778. 'description' => __( 'Amount of reviews that the product have.', 'woocommerce' ),  
  1779. 'type' => 'integer',  
  1780. 'context' => array( 'view', 'edit' ),  
  1781. 'readonly' => true,  
  1782. ),  
  1783. 'related_ids' => array( 
  1784. 'description' => __( 'List of related products IDs.', 'woocommerce' ),  
  1785. 'type' => 'array',  
  1786. 'items' => array( 
  1787. 'type' => 'integer',  
  1788. ),  
  1789. 'context' => array( 'view', 'edit' ),  
  1790. 'readonly' => true,  
  1791. ),  
  1792. 'upsell_ids' => array( 
  1793. 'description' => __( 'List of up-sell products IDs.', 'woocommerce' ),  
  1794. 'type' => 'array',  
  1795. 'items' => array( 
  1796. 'type' => 'integer',  
  1797. ),  
  1798. 'context' => array( 'view', 'edit' ),  
  1799. ),  
  1800. 'cross_sell_ids' => array( 
  1801. 'description' => __( 'List of cross-sell products IDs.', 'woocommerce' ),  
  1802. 'type' => 'array',  
  1803. 'items' => array( 
  1804. 'type' => 'integer',  
  1805. ),  
  1806. 'context' => array( 'view', 'edit' ),  
  1807. ),  
  1808. 'parent_id' => array( 
  1809. 'description' => __( 'Product parent ID.', 'woocommerce' ),  
  1810. 'type' => 'integer',  
  1811. 'context' => array( 'view', 'edit' ),  
  1812. ),  
  1813. 'purchase_note' => array( 
  1814. 'description' => __( 'Optional note to send the customer after purchase.', 'woocommerce' ),  
  1815. 'type' => 'string',  
  1816. 'context' => array( 'view', 'edit' ),  
  1817. ),  
  1818. 'categories' => array( 
  1819. 'description' => __( 'List of categories.', 'woocommerce' ),  
  1820. 'type' => 'array',  
  1821. 'context' => array( 'view', 'edit' ),  
  1822. 'items' => array( 
  1823. 'type' => 'object',  
  1824. 'properties' => array( 
  1825. 'id' => array( 
  1826. 'description' => __( 'Category ID.', 'woocommerce' ),  
  1827. 'type' => 'integer',  
  1828. 'context' => array( 'view', 'edit' ),  
  1829. ),  
  1830. 'name' => array( 
  1831. 'description' => __( 'Category name.', 'woocommerce' ),  
  1832. 'type' => 'string',  
  1833. 'context' => array( 'view', 'edit' ),  
  1834. 'readonly' => true,  
  1835. ),  
  1836. 'slug' => array( 
  1837. 'description' => __( 'Category slug.', 'woocommerce' ),  
  1838. 'type' => 'string',  
  1839. 'context' => array( 'view', 'edit' ),  
  1840. 'readonly' => true,  
  1841. ),  
  1842. ),  
  1843. ),  
  1844. ),  
  1845. 'tags' => array( 
  1846. 'description' => __( 'List of tags.', 'woocommerce' ),  
  1847. 'type' => 'array',  
  1848. 'context' => array( 'view', 'edit' ),  
  1849. 'items' => array( 
  1850. 'type' => 'object',  
  1851. 'properties' => array( 
  1852. 'id' => array( 
  1853. 'description' => __( 'Tag ID.', 'woocommerce' ),  
  1854. 'type' => 'integer',  
  1855. 'context' => array( 'view', 'edit' ),  
  1856. ),  
  1857. 'name' => array( 
  1858. 'description' => __( 'Tag name.', 'woocommerce' ),  
  1859. 'type' => 'string',  
  1860. 'context' => array( 'view', 'edit' ),  
  1861. 'readonly' => true,  
  1862. ),  
  1863. 'slug' => array( 
  1864. 'description' => __( 'Tag slug.', 'woocommerce' ),  
  1865. 'type' => 'string',  
  1866. 'context' => array( 'view', 'edit' ),  
  1867. 'readonly' => true,  
  1868. ),  
  1869. ),  
  1870. ),  
  1871. ),  
  1872. 'images' => array( 
  1873. 'description' => __( 'List of images.', 'woocommerce' ),  
  1874. 'type' => 'object',  
  1875. 'context' => array( 'view', 'edit' ),  
  1876. 'items' => array( 
  1877. 'type' => 'object',  
  1878. 'properties' => array( 
  1879. 'id' => array( 
  1880. 'description' => __( 'Image ID.', 'woocommerce' ),  
  1881. 'type' => 'integer',  
  1882. 'context' => array( 'view', 'edit' ),  
  1883. ),  
  1884. 'date_created' => array( 
  1885. 'description' => __( "The date the image was created, in the site's timezone.", 'woocommerce' ),  
  1886. 'type' => 'date-time',  
  1887. 'context' => array( 'view', 'edit' ),  
  1888. 'readonly' => true,  
  1889. ),  
  1890. 'date_modified' => array( 
  1891. 'description' => __( "The date the image was last modified, in the site's timezone.", 'woocommerce' ),  
  1892. 'type' => 'date-time',  
  1893. 'context' => array( 'view', 'edit' ),  
  1894. 'readonly' => true,  
  1895. ),  
  1896. 'src' => array( 
  1897. 'description' => __( 'Image URL.', 'woocommerce' ),  
  1898. 'type' => 'string',  
  1899. 'format' => 'uri',  
  1900. 'context' => array( 'view', 'edit' ),  
  1901. ),  
  1902. 'name' => array( 
  1903. 'description' => __( 'Image name.', 'woocommerce' ),  
  1904. 'type' => 'string',  
  1905. 'context' => array( 'view', 'edit' ),  
  1906. ),  
  1907. 'alt' => array( 
  1908. 'description' => __( 'Image alternative text.', 'woocommerce' ),  
  1909. 'type' => 'string',  
  1910. 'context' => array( 'view', 'edit' ),  
  1911. ),  
  1912. 'position' => array( 
  1913. 'description' => __( 'Image position. 0 means that the image is featured.', 'woocommerce' ),  
  1914. 'type' => 'integer',  
  1915. 'context' => array( 'view', 'edit' ),  
  1916. ),  
  1917. ),  
  1918. ),  
  1919. ),  
  1920. 'attributes' => array( 
  1921. 'description' => __( 'List of attributes.', 'woocommerce' ),  
  1922. 'type' => 'array',  
  1923. 'context' => array( 'view', 'edit' ),  
  1924. 'items' => array( 
  1925. 'type' => 'object',  
  1926. 'properties' => array( 
  1927. 'id' => array( 
  1928. 'description' => __( 'Attribute ID.', 'woocommerce' ),  
  1929. 'type' => 'integer',  
  1930. 'context' => array( 'view', 'edit' ),  
  1931. ),  
  1932. 'name' => array( 
  1933. 'description' => __( 'Attribute name.', 'woocommerce' ),  
  1934. 'type' => 'string',  
  1935. 'context' => array( 'view', 'edit' ),  
  1936. ),  
  1937. 'position' => array( 
  1938. 'description' => __( 'Attribute position.', 'woocommerce' ),  
  1939. 'type' => 'integer',  
  1940. 'context' => array( 'view', 'edit' ),  
  1941. ),  
  1942. 'visible' => array( 
  1943. 'description' => __( "Define if the attribute is visible on the \"Additional information\" tab in the product's page.", 'woocommerce' ),  
  1944. 'type' => 'boolean',  
  1945. 'default' => false,  
  1946. 'context' => array( 'view', 'edit' ),  
  1947. ),  
  1948. 'variation' => array( 
  1949. 'description' => __( 'Define if the attribute can be used as variation.', 'woocommerce' ),  
  1950. 'type' => 'boolean',  
  1951. 'default' => false,  
  1952. 'context' => array( 'view', 'edit' ),  
  1953. ),  
  1954. 'options' => array( 
  1955. 'description' => __( 'List of available term names of the attribute.', 'woocommerce' ),  
  1956. 'type' => 'array',  
  1957. 'context' => array( 'view', 'edit' ),  
  1958. ),  
  1959. ),  
  1960. ),  
  1961. ),  
  1962. 'default_attributes' => array( 
  1963. 'description' => __( 'Defaults variation attributes.', 'woocommerce' ),  
  1964. 'type' => 'array',  
  1965. 'context' => array( 'view', 'edit' ),  
  1966. 'items' => array( 
  1967. 'type' => 'object',  
  1968. 'properties' => array( 
  1969. 'id' => array( 
  1970. 'description' => __( 'Attribute ID.', 'woocommerce' ),  
  1971. 'type' => 'integer',  
  1972. 'context' => array( 'view', 'edit' ),  
  1973. ),  
  1974. 'name' => array( 
  1975. 'description' => __( 'Attribute name.', 'woocommerce' ),  
  1976. 'type' => 'string',  
  1977. 'context' => array( 'view', 'edit' ),  
  1978. ),  
  1979. 'option' => array( 
  1980. 'description' => __( 'Selected attribute term name.', 'woocommerce' ),  
  1981. 'type' => 'string',  
  1982. 'context' => array( 'view', 'edit' ),  
  1983. ),  
  1984. ),  
  1985. ),  
  1986. ),  
  1987. 'variations' => array( 
  1988. 'description' => __( 'List of variations.', 'woocommerce' ),  
  1989. 'type' => 'array',  
  1990. 'context' => array( 'view', 'edit' ),  
  1991. 'items' => array( 
  1992. 'type' => 'object',  
  1993. 'properties' => array( 
  1994. 'id' => array( 
  1995. 'description' => __( 'Variation ID.', 'woocommerce' ),  
  1996. 'type' => 'integer',  
  1997. 'context' => array( 'view', 'edit' ),  
  1998. 'readonly' => true,  
  1999. ),  
  2000. 'date_created' => array( 
  2001. 'description' => __( "The date the variation was created, in the site's timezone.", 'woocommerce' ),  
  2002. 'type' => 'date-time',  
  2003. 'context' => array( 'view', 'edit' ),  
  2004. 'readonly' => true,  
  2005. ),  
  2006. 'date_modified' => array( 
  2007. 'description' => __( "The date the variation was last modified, in the site's timezone.", 'woocommerce' ),  
  2008. 'type' => 'date-time',  
  2009. 'context' => array( 'view', 'edit' ),  
  2010. 'readonly' => true,  
  2011. ),  
  2012. 'permalink' => array( 
  2013. 'description' => __( 'Variation URL.', 'woocommerce' ),  
  2014. 'type' => 'string',  
  2015. 'format' => 'uri',  
  2016. 'context' => array( 'view', 'edit' ),  
  2017. 'readonly' => true,  
  2018. ),  
  2019. 'sku' => array( 
  2020. 'description' => __( 'Unique identifier.', 'woocommerce' ),  
  2021. 'type' => 'string',  
  2022. 'context' => array( 'view', 'edit' ),  
  2023. ),  
  2024. 'price' => array( 
  2025. 'description' => __( 'Current variation price.', 'woocommerce' ),  
  2026. 'type' => 'string',  
  2027. 'context' => array( 'view', 'edit' ),  
  2028. 'readonly' => true,  
  2029. ),  
  2030. 'regular_price' => array( 
  2031. 'description' => __( 'Variation regular price.', 'woocommerce' ),  
  2032. 'type' => 'string',  
  2033. 'context' => array( 'view', 'edit' ),  
  2034. ),  
  2035. 'sale_price' => array( 
  2036. 'description' => __( 'Variation sale price.', 'woocommerce' ),  
  2037. 'type' => 'string',  
  2038. 'context' => array( 'view', 'edit' ),  
  2039. ),  
  2040. 'date_on_sale_from' => array( 
  2041. 'description' => __( 'Start date of sale price.', 'woocommerce' ),  
  2042. 'type' => 'string',  
  2043. 'context' => array( 'view', 'edit' ),  
  2044. ),  
  2045. 'date_on_sale_to' => array( 
  2046. 'description' => __( 'End data of sale price.', 'woocommerce' ),  
  2047. 'type' => 'string',  
  2048. 'context' => array( 'view', 'edit' ),  
  2049. ),  
  2050. 'on_sale' => array( 
  2051. 'description' => __( 'Shows if the variation is on sale.', 'woocommerce' ),  
  2052. 'type' => 'boolean',  
  2053. 'context' => array( 'view', 'edit' ),  
  2054. 'readonly' => true,  
  2055. ),  
  2056. 'purchasable' => array( 
  2057. 'description' => __( 'Shows if the variation can be bought.', 'woocommerce' ),  
  2058. 'type' => 'boolean',  
  2059. 'context' => array( 'view', 'edit' ),  
  2060. 'readonly' => true,  
  2061. ),  
  2062. 'visible' => array( 
  2063. 'description' => __( 'If the variation is visible.', 'woocommerce' ),  
  2064. 'type' => 'boolean',  
  2065. 'context' => array( 'view', 'edit' ),  
  2066. ),  
  2067. 'virtual' => array( 
  2068. 'description' => __( 'If the variation is virtual.', 'woocommerce' ),  
  2069. 'type' => 'boolean',  
  2070. 'default' => false,  
  2071. 'context' => array( 'view', 'edit' ),  
  2072. ),  
  2073. 'downloadable' => array( 
  2074. 'description' => __( 'If the variation is downloadable.', 'woocommerce' ),  
  2075. 'type' => 'boolean',  
  2076. 'default' => false,  
  2077. 'context' => array( 'view', 'edit' ),  
  2078. ),  
  2079. 'downloads' => array( 
  2080. 'description' => __( 'List of downloadable files.', 'woocommerce' ),  
  2081. 'type' => 'array',  
  2082. 'context' => array( 'view', 'edit' ),  
  2083. 'items' => array( 
  2084. 'type' => 'object',  
  2085. 'properties' => array( 
  2086. 'id' => array( 
  2087. 'description' => __( 'File MD5 hash.', 'woocommerce' ),  
  2088. 'type' => 'string',  
  2089. 'context' => array( 'view', 'edit' ),  
  2090. 'readonly' => true,  
  2091. ),  
  2092. 'name' => array( 
  2093. 'description' => __( 'File name.', 'woocommerce' ),  
  2094. 'type' => 'string',  
  2095. 'context' => array( 'view', 'edit' ),  
  2096. ),  
  2097. 'file' => array( 
  2098. 'description' => __( 'File URL.', 'woocommerce' ),  
  2099. 'type' => 'string',  
  2100. 'context' => array( 'view', 'edit' ),  
  2101. ),  
  2102. ),  
  2103. ),  
  2104. ),  
  2105. 'download_limit' => array( 
  2106. 'description' => __( 'Number of times downloadable files can be downloaded after purchase.', 'woocommerce' ),  
  2107. 'type' => 'integer',  
  2108. 'default' => null,  
  2109. 'context' => array( 'view', 'edit' ),  
  2110. ),  
  2111. 'download_expiry' => array( 
  2112. 'description' => __( 'Number of days until access to downloadable files expires.', 'woocommerce' ),  
  2113. 'type' => 'integer',  
  2114. 'default' => null,  
  2115. 'context' => array( 'view', 'edit' ),  
  2116. ),  
  2117. 'tax_status' => array( 
  2118. 'description' => __( 'Tax status.', 'woocommerce' ),  
  2119. 'type' => 'string',  
  2120. 'default' => 'taxable',  
  2121. 'enum' => array( 'taxable', 'shipping', 'none' ),  
  2122. 'context' => array( 'view', 'edit' ),  
  2123. ),  
  2124. 'tax_class' => array( 
  2125. 'description' => __( 'Tax class.', 'woocommerce' ),  
  2126. 'type' => 'string',  
  2127. 'context' => array( 'view', 'edit' ),  
  2128. ),  
  2129. 'manage_stock' => array( 
  2130. 'description' => __( 'Stock management at variation level.', 'woocommerce' ),  
  2131. 'type' => 'boolean',  
  2132. 'default' => false,  
  2133. 'context' => array( 'view', 'edit' ),  
  2134. ),  
  2135. 'stock_quantity' => array( 
  2136. 'description' => __( 'Stock quantity.', 'woocommerce' ),  
  2137. 'type' => 'integer',  
  2138. 'context' => array( 'view', 'edit' ),  
  2139. ),  
  2140. 'in_stock' => array( 
  2141. 'description' => __( 'Controls whether or not the variation is listed as "in stock" or "out of stock" on the frontend.', 'woocommerce' ),  
  2142. 'type' => 'boolean',  
  2143. 'default' => true,  
  2144. 'context' => array( 'view', 'edit' ),  
  2145. ),  
  2146. 'backorders' => array( 
  2147. 'description' => __( 'If managing stock, this controls if backorders are allowed.', 'woocommerce' ),  
  2148. 'type' => 'string',  
  2149. 'default' => 'no',  
  2150. 'enum' => array( 'no', 'notify', 'yes' ),  
  2151. 'context' => array( 'view', 'edit' ),  
  2152. ),  
  2153. 'backorders_allowed' => array( 
  2154. 'description' => __( 'Shows if backorders are allowed.', 'woocommerce' ),  
  2155. 'type' => 'boolean',  
  2156. 'context' => array( 'view', 'edit' ),  
  2157. 'readonly' => true,  
  2158. ),  
  2159. 'backordered' => array( 
  2160. 'description' => __( 'Shows if the variation is on backordered.', 'woocommerce' ),  
  2161. 'type' => 'boolean',  
  2162. 'context' => array( 'view', 'edit' ),  
  2163. 'readonly' => true,  
  2164. ),  
  2165. 'weight' => array( 
  2166. /** translators: %s: weight unit */ 
  2167. 'description' => sprintf( __( 'Variation weight (%s).', 'woocommerce' ), $weight_unit ),  
  2168. 'type' => 'string',  
  2169. 'context' => array( 'view', 'edit' ),  
  2170. ),  
  2171. 'dimensions' => array( 
  2172. 'description' => __( 'Variation dimensions.', 'woocommerce' ),  
  2173. 'type' => 'object',  
  2174. 'context' => array( 'view', 'edit' ),  
  2175. 'properties' => array( 
  2176. 'length' => array( 
  2177. /** translators: %s: dimension unit */ 
  2178. 'description' => sprintf( __( 'Variation length (%s).', 'woocommerce' ), $dimension_unit ),  
  2179. 'type' => 'string',  
  2180. 'context' => array( 'view', 'edit' ),  
  2181. ),  
  2182. 'width' => array( 
  2183. /** translators: %s: dimension unit */ 
  2184. 'description' => sprintf( __( 'Variation width (%s).', 'woocommerce' ), $dimension_unit ),  
  2185. 'type' => 'string',  
  2186. 'context' => array( 'view', 'edit' ),  
  2187. ),  
  2188. 'height' => array( 
  2189. /** translators: %s: dimension unit */ 
  2190. 'description' => sprintf( __( 'Variation height (%s).', 'woocommerce' ), $dimension_unit ),  
  2191. 'type' => 'string',  
  2192. 'context' => array( 'view', 'edit' ),  
  2193. ),  
  2194. ),  
  2195. ),  
  2196. 'shipping_class' => array( 
  2197. 'description' => __( 'Shipping class slug.', 'woocommerce' ),  
  2198. 'type' => 'string',  
  2199. 'context' => array( 'view', 'edit' ),  
  2200. ),  
  2201. 'shipping_class_id' => array( 
  2202. 'description' => __( 'Shipping class ID.', 'woocommerce' ),  
  2203. 'type' => 'string',  
  2204. 'context' => array( 'view', 'edit' ),  
  2205. 'readonly' => true,  
  2206. ),  
  2207. 'image' => array( 
  2208. 'description' => __( 'Variation image data.', 'woocommerce' ),  
  2209. 'type' => 'object',  
  2210. 'context' => array( 'view', 'edit' ),  
  2211. 'properties' => array( 
  2212. 'id' => array( 
  2213. 'description' => __( 'Image ID.', 'woocommerce' ),  
  2214. 'type' => 'integer',  
  2215. 'context' => array( 'view', 'edit' ),  
  2216. ),  
  2217. 'date_created' => array( 
  2218. 'description' => __( "The date the image was created, in the site's timezone.", 'woocommerce' ),  
  2219. 'type' => 'date-time',  
  2220. 'context' => array( 'view', 'edit' ),  
  2221. 'readonly' => true,  
  2222. ),  
  2223. 'date_modified' => array( 
  2224. 'description' => __( "The date the image was last modified, in the site's timezone.", 'woocommerce' ),  
  2225. 'type' => 'date-time',  
  2226. 'context' => array( 'view', 'edit' ),  
  2227. 'readonly' => true,  
  2228. ),  
  2229. 'src' => array( 
  2230. 'description' => __( 'Image URL.', 'woocommerce' ),  
  2231. 'type' => 'string',  
  2232. 'format' => 'uri',  
  2233. 'context' => array( 'view', 'edit' ),  
  2234. ),  
  2235. 'name' => array( 
  2236. 'description' => __( 'Image name.', 'woocommerce' ),  
  2237. 'type' => 'string',  
  2238. 'context' => array( 'view', 'edit' ),  
  2239. ),  
  2240. 'alt' => array( 
  2241. 'description' => __( 'Image alternative text.', 'woocommerce' ),  
  2242. 'type' => 'string',  
  2243. 'context' => array( 'view', 'edit' ),  
  2244. ),  
  2245. 'position' => array( 
  2246. 'description' => __( 'Image position. 0 means that the image is featured.', 'woocommerce' ),  
  2247. 'type' => 'integer',  
  2248. 'context' => array( 'view', 'edit' ),  
  2249. ),  
  2250. ),  
  2251. ),  
  2252. 'attributes' => array( 
  2253. 'description' => __( 'List of attributes.', 'woocommerce' ),  
  2254. 'type' => 'array',  
  2255. 'context' => array( 'view', 'edit' ),  
  2256. 'items' => array( 
  2257. 'type' => 'object',  
  2258. 'properties' => array( 
  2259. 'id' => array( 
  2260. 'description' => __( 'Attribute ID.', 'woocommerce' ),  
  2261. 'type' => 'integer',  
  2262. 'context' => array( 'view', 'edit' ),  
  2263. ),  
  2264. 'name' => array( 
  2265. 'description' => __( 'Attribute name.', 'woocommerce' ),  
  2266. 'type' => 'string',  
  2267. 'context' => array( 'view', 'edit' ),  
  2268. ),  
  2269. 'option' => array( 
  2270. 'description' => __( 'Selected attribute term name.', 'woocommerce' ),  
  2271. 'type' => 'string',  
  2272. 'context' => array( 'view', 'edit' ),  
  2273. ),  
  2274. ),  
  2275. ),  
  2276. ),  
  2277. ),  
  2278. ),  
  2279. ),  
  2280. 'grouped_products' => array( 
  2281. 'description' => __( 'List of grouped products ID.', 'woocommerce' ),  
  2282. 'type' => 'array',  
  2283. 'items' => array( 
  2284. 'type' => 'integer',  
  2285. ),  
  2286. 'context' => array( 'view', 'edit' ),  
  2287. 'readonly' => true,  
  2288. ),  
  2289. 'menu_order' => array( 
  2290. 'description' => __( 'Menu order, used to custom sort products.', 'woocommerce' ),  
  2291. 'type' => 'integer',  
  2292. 'context' => array( 'view', 'edit' ),  
  2293. ),  
  2294. ),  
  2295. ); 
  2296.  
  2297. return $this->add_additional_fields_schema( $schema ); 
  2298.  
  2299. /** 
  2300. * Get the query params for collections of attachments. 
  2301. * @return array 
  2302. */ 
  2303. public function get_collection_params() { 
  2304. $params = parent::get_collection_params(); 
  2305.  
  2306. $params['slug'] = array( 
  2307. 'description' => __( 'Limit result set to products with a specific slug.', 'woocommerce' ),  
  2308. 'type' => 'string',  
  2309. 'validate_callback' => 'rest_validate_request_arg',  
  2310. ); 
  2311. $params['status'] = array( 
  2312. 'default' => 'any',  
  2313. 'description' => __( 'Limit result set to products assigned a specific status.', 'woocommerce' ),  
  2314. 'type' => 'string',  
  2315. 'enum' => array_merge( array( 'any' ), array_keys( get_post_statuses() ) ),  
  2316. 'sanitize_callback' => 'sanitize_key',  
  2317. 'validate_callback' => 'rest_validate_request_arg',  
  2318. ); 
  2319. $params['type'] = array( 
  2320. 'description' => __( 'Limit result set to products assigned a specific type.', 'woocommerce' ),  
  2321. 'type' => 'string',  
  2322. 'enum' => array_keys( wc_get_product_types() ),  
  2323. 'sanitize_callback' => 'sanitize_key',  
  2324. 'validate_callback' => 'rest_validate_request_arg',  
  2325. ); 
  2326. $params['category'] = array( 
  2327. 'description' => __( 'Limit result set to products assigned a specific category ID.', 'woocommerce' ),  
  2328. 'type' => 'string',  
  2329. 'sanitize_callback' => 'wp_parse_id_list',  
  2330. 'validate_callback' => 'rest_validate_request_arg',  
  2331. ); 
  2332. $params['tag'] = array( 
  2333. 'description' => __( 'Limit result set to products assigned a specific tag ID.', 'woocommerce' ),  
  2334. 'type' => 'string',  
  2335. 'sanitize_callback' => 'wp_parse_id_list',  
  2336. 'validate_callback' => 'rest_validate_request_arg',  
  2337. ); 
  2338. $params['shipping_class'] = array( 
  2339. 'description' => __( 'Limit result set to products assigned a specific shipping class ID.', 'woocommerce' ),  
  2340. 'type' => 'string',  
  2341. 'sanitize_callback' => 'wp_parse_id_list',  
  2342. 'validate_callback' => 'rest_validate_request_arg',  
  2343. ); 
  2344. $params['attribute'] = array( 
  2345. 'description' => __( 'Limit result set to products with a specific attribute.', 'woocommerce' ),  
  2346. 'type' => 'string',  
  2347. 'sanitize_callback' => 'sanitize_text_field',  
  2348. 'validate_callback' => 'rest_validate_request_arg',  
  2349. ); 
  2350. $params['attribute_term'] = array( 
  2351. 'description' => __( 'Limit result set to products with a specific attribute term ID (required an assigned attribute).', 'woocommerce' ),  
  2352. 'type' => 'string',  
  2353. 'sanitize_callback' => 'wp_parse_id_list',  
  2354. 'validate_callback' => 'rest_validate_request_arg',  
  2355. ); 
  2356. $params['sku'] = array( 
  2357. 'description' => __( 'Limit result set to products with a specific SKU.', 'woocommerce' ),  
  2358. 'type' => 'string',  
  2359. 'sanitize_callback' => 'sanitize_text_field',  
  2360. 'validate_callback' => 'rest_validate_request_arg',  
  2361. ); 
  2362.  
  2363. return $params;