/includes/api/class-wc-rest-products-controller.php

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