WC_API_Products

The WooCommerce WC API Products class.

Defined (3)

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

/includes/api/legacy/v1/class-wc-api-products.php  
  1. class WC_API_Products extends WC_API_Resource { 
  2.  
  3. /** @var string $base the route base */ 
  4. protected $base = '/products'; 
  5.  
  6. /** 
  7. * Register the routes for this class 
  8. * GET /products 
  9. * GET /products/count 
  10. * GET /products/<id> 
  11. * GET /products/<id>/reviews 
  12. * @since 2.1 
  13. * @param array $routes 
  14. * @return array 
  15. */ 
  16. public function register_routes( $routes ) { 
  17.  
  18. # GET /products 
  19. $routes[ $this->base ] = array( 
  20. array( array( $this, 'get_products' ), WC_API_Server::READABLE ),  
  21. ); 
  22.  
  23. # GET /products/count 
  24. $routes[ $this->base . '/count' ] = array( 
  25. array( array( $this, 'get_products_count' ), WC_API_Server::READABLE ),  
  26. ); 
  27.  
  28. # GET /products/<id> 
  29. $routes[ $this->base . '/(?P<id>\d+)' ] = array( 
  30. array( array( $this, 'get_product' ), WC_API_Server::READABLE ),  
  31. ); 
  32.  
  33. # GET /products/<id>/reviews 
  34. $routes[ $this->base . '/(?P<id>\d+)/reviews' ] = array( 
  35. array( array( $this, 'get_product_reviews' ), WC_API_Server::READABLE ),  
  36. ); 
  37.  
  38. return $routes; 
  39.  
  40. /** 
  41. * Get all products 
  42. * @since 2.1 
  43. * @param string $fields 
  44. * @param string $type 
  45. * @param array $filter 
  46. * @param int $page 
  47. * @return array 
  48. */ 
  49. public function get_products( $fields = null, $type = null, $filter = array(), $page = 1 ) { 
  50.  
  51. if ( ! empty( $type ) ) 
  52. $filter['type'] = $type; 
  53.  
  54. $filter['page'] = $page; 
  55.  
  56. $query = $this->query_products( $filter ); 
  57.  
  58. $products = array(); 
  59.  
  60. foreach ( $query->posts as $product_id ) { 
  61.  
  62. if ( ! $this->is_readable( $product_id ) ) 
  63. continue; 
  64.  
  65. $products[] = current( $this->get_product( $product_id, $fields ) ); 
  66.  
  67. $this->server->add_pagination_headers( $query ); 
  68.  
  69. return array( 'products' => $products ); 
  70.  
  71. /** 
  72. * Get the product for the given ID 
  73. * @since 2.1 
  74. * @param int $id the product ID 
  75. * @param string $fields 
  76. * @return array 
  77. */ 
  78. public function get_product( $id, $fields = null ) { 
  79.  
  80. $id = $this->validate_request( $id, 'product', 'read' ); 
  81.  
  82. if ( is_wp_error( $id ) ) 
  83. return $id; 
  84.  
  85. $product = wc_get_product( $id ); 
  86.  
  87. // add data that applies to every product type 
  88. $product_data = $this->get_product_data( $product ); 
  89.  
  90. // add variations to variable products 
  91. if ( $product->is_type( 'variable' ) && $product->has_child() ) { 
  92. $product_data['variations'] = $this->get_variation_data( $product ); 
  93.  
  94. // add the parent product data to an individual variation 
  95. if ( $product->is_type( 'variation' ) ) { 
  96. $product_data['parent'] = $this->get_product_data( $product->get_parent_id() ); 
  97.  
  98. return array( 'product' => apply_filters( 'woocommerce_api_product_response', $product_data, $product, $fields, $this->server ) ); 
  99.  
  100. /** 
  101. * Get the total number of orders 
  102. * @since 2.1 
  103. * @param string $type 
  104. * @param array $filter 
  105. * @return array 
  106. */ 
  107. public function get_products_count( $type = null, $filter = array() ) { 
  108.  
  109. if ( ! empty( $type ) ) 
  110. $filter['type'] = $type; 
  111.  
  112. if ( ! current_user_can( 'read_private_products' ) ) 
  113. return new WP_Error( 'woocommerce_api_user_cannot_read_products_count', __( 'You do not have permission to read the products count', 'woocommerce' ), array( 'status' => 401 ) ); 
  114.  
  115. $query = $this->query_products( $filter ); 
  116.  
  117. return array( 'count' => (int) $query->found_posts ); 
  118.  
  119. /** 
  120. * Edit a product 
  121. * @param int $id the product ID 
  122. * @param array $data 
  123. * @return array 
  124. */ 
  125. public function edit_product( $id, $data ) { 
  126.  
  127. $id = $this->validate_request( $id, 'product', 'edit' ); 
  128.  
  129. if ( is_wp_error( $id ) ) 
  130. return $id; 
  131.  
  132. return $this->get_product( $id ); 
  133.  
  134. /** 
  135. * Delete a product 
  136. * @param int $id the product ID 
  137. * @param bool $force true to permanently delete order, false to move to trash 
  138. * @return array 
  139. */ 
  140. public function delete_product( $id, $force = false ) { 
  141.  
  142. $id = $this->validate_request( $id, 'product', 'delete' ); 
  143.  
  144. if ( is_wp_error( $id ) ) 
  145. return $id; 
  146.  
  147. return $this->delete( $id, 'product', ( 'true' === $force ) ); 
  148.  
  149. /** 
  150. * Get the reviews for a product 
  151. * @since 2.1 
  152. * @param int $id the product ID to get reviews for 
  153. * @param string $fields fields to include in response 
  154. * @return array 
  155. */ 
  156. public function get_product_reviews( $id, $fields = null ) { 
  157.  
  158. $id = $this->validate_request( $id, 'product', 'read' ); 
  159.  
  160. if ( is_wp_error( $id ) ) 
  161. return $id; 
  162.  
  163. $args = array( 
  164. 'post_id' => $id,  
  165. 'approve' => 'approve',  
  166. ); 
  167.  
  168. $comments = get_comments( $args ); 
  169.  
  170. $reviews = array(); 
  171.  
  172. foreach ( $comments as $comment ) { 
  173.  
  174. $reviews[] = array( 
  175. 'id' => $comment->comment_ID,  
  176. 'created_at' => $this->server->format_datetime( $comment->comment_date_gmt ),  
  177. 'review' => $comment->comment_content,  
  178. 'rating' => get_comment_meta( $comment->comment_ID, 'rating', true ),  
  179. 'reviewer_name' => $comment->comment_author,  
  180. 'reviewer_email' => $comment->comment_author_email,  
  181. 'verified' => wc_review_is_from_verified_owner( $comment->comment_ID ),  
  182. ); 
  183.  
  184. return array( 'product_reviews' => apply_filters( 'woocommerce_api_product_reviews_response', $reviews, $id, $fields, $comments, $this->server ) ); 
  185.  
  186. /** 
  187. * Helper method to get product post objects 
  188. * @since 2.1 
  189. * @param array $args request arguments for filtering query 
  190. * @return WP_Query 
  191. */ 
  192. private function query_products( $args ) { 
  193.  
  194. // set base query arguments 
  195. $query_args = array( 
  196. 'fields' => 'ids',  
  197. 'post_type' => 'product',  
  198. 'post_status' => 'publish',  
  199. 'meta_query' => array(),  
  200. ); 
  201.  
  202. if ( ! empty( $args['type'] ) ) { 
  203.  
  204. $types = explode( ', ', $args['type'] ); 
  205.  
  206. $query_args['tax_query'] = array( 
  207. array( 
  208. 'taxonomy' => 'product_type',  
  209. 'field' => 'slug',  
  210. 'terms' => $types,  
  211. ),  
  212. ); 
  213.  
  214. unset( $args['type'] ); 
  215.  
  216. $query_args = $this->merge_query_args( $query_args, $args ); 
  217.  
  218. return new WP_Query( $query_args ); 
  219.  
  220. /** 
  221. * Get standard product data that applies to every product type 
  222. * @since 2.1 
  223. * @param WC_Product|int $product 
  224. * @return array 
  225. */ 
  226. private function get_product_data( $product ) { 
  227. if ( is_numeric( $product ) ) { 
  228. $product = wc_get_product( $product ); 
  229.  
  230. return array( 
  231. 'title' => $product->get_name(),  
  232. 'id' => $product->get_id(),  
  233. 'created_at' => $this->server->format_datetime( $product->get_date_created(), false, true ),  
  234. 'updated_at' => $this->server->format_datetime( $product->get_date_modified(), false, true ),  
  235. 'type' => $product->get_type(),  
  236. 'status' => $product->get_status(),  
  237. 'downloadable' => $product->is_downloadable(),  
  238. 'virtual' => $product->is_virtual(),  
  239. 'permalink' => $product->get_permalink(),  
  240. 'sku' => $product->get_sku(),  
  241. 'price' => wc_format_decimal( $product->get_price(), 2 ),  
  242. 'regular_price' => wc_format_decimal( $product->get_regular_price(), 2 ),  
  243. 'sale_price' => $product->get_sale_price() ? wc_format_decimal( $product->get_sale_price(), 2 ) : null,  
  244. 'price_html' => $product->get_price_html(),  
  245. 'taxable' => $product->is_taxable(),  
  246. 'tax_status' => $product->get_tax_status(),  
  247. 'tax_class' => $product->get_tax_class(),  
  248. 'managing_stock' => $product->managing_stock(),  
  249. 'stock_quantity' => $product->get_stock_quantity(),  
  250. 'in_stock' => $product->is_in_stock(),  
  251. 'backorders_allowed' => $product->backorders_allowed(),  
  252. 'backordered' => $product->is_on_backorder(),  
  253. 'sold_individually' => $product->is_sold_individually(),  
  254. 'purchaseable' => $product->is_purchasable(),  
  255. 'featured' => $product->is_featured(),  
  256. 'visible' => $product->is_visible(),  
  257. 'catalog_visibility' => $product->get_catalog_visibility(),  
  258. 'on_sale' => $product->is_on_sale(),  
  259. 'weight' => $product->get_weight() ? wc_format_decimal( $product->get_weight(), 2 ) : null,  
  260. 'dimensions' => array( 
  261. 'length' => $product->get_length(),  
  262. 'width' => $product->get_width(),  
  263. 'height' => $product->get_height(),  
  264. 'unit' => get_option( 'woocommerce_dimension_unit' ),  
  265. ),  
  266. 'shipping_required' => $product->needs_shipping(),  
  267. 'shipping_taxable' => $product->is_shipping_taxable(),  
  268. 'shipping_class' => $product->get_shipping_class(),  
  269. 'shipping_class_id' => ( 0 !== $product->get_shipping_class_id() ) ? $product->get_shipping_class_id() : null,  
  270. 'description' => apply_filters( 'the_content', $product->get_description() ),  
  271. 'short_description' => apply_filters( 'woocommerce_short_description', $product->get_short_description() ),  
  272. 'reviews_allowed' => $product->get_reviews_allowed(),  
  273. 'average_rating' => wc_format_decimal( $product->get_average_rating(), 2 ),  
  274. 'rating_count' => $product->get_rating_count(),  
  275. 'related_ids' => array_map( 'absint', array_values( wc_get_related_products( $product->get_id() ) ) ),  
  276. 'upsell_ids' => array_map( 'absint', $product->get_upsell_ids() ),  
  277. 'cross_sell_ids' => array_map( 'absint', $product->get_cross_sell_ids() ),  
  278. 'categories' => wc_get_object_terms( $product->get_id(), 'product_cat', 'name' ),  
  279. 'tags' => wc_get_object_terms( $product->get_id(), 'product_tag', 'name' ),  
  280. 'images' => $this->get_images( $product ),  
  281. 'featured_src' => wp_get_attachment_url( get_post_thumbnail_id( $product->get_id() ) ),  
  282. 'attributes' => $this->get_attributes( $product ),  
  283. 'downloads' => $this->get_downloads( $product ),  
  284. 'download_limit' => $product->get_download_limit(),  
  285. 'download_expiry' => $product->get_download_expiry(),  
  286. 'download_type' => 'standard',  
  287. 'purchase_note' => apply_filters( 'the_content', $product->get_purchase_note() ),  
  288. 'total_sales' => $product->get_total_sales(),  
  289. 'variations' => array(),  
  290. 'parent' => array(),  
  291. ); 
  292.  
  293. /** 
  294. * Get an individual variation's data 
  295. * @since 2.1 
  296. * @param WC_Product $product 
  297. * @return array 
  298. */ 
  299. private function get_variation_data( $product ) { 
  300. $variations = array(); 
  301.  
  302. foreach ( $product->get_children() as $child_id ) { 
  303. $variation = wc_get_product( $child_id ); 
  304.  
  305. if ( ! $variation || ! $variation->exists() ) { 
  306. continue; 
  307.  
  308. $variations[] = array( 
  309. 'id' => $variation->get_id(),  
  310. 'created_at' => $this->server->format_datetime( $variation->get_date_created(), false, true ),  
  311. 'updated_at' => $this->server->format_datetime( $variation->get_date_modified(), false, true ),  
  312. 'downloadable' => $variation->is_downloadable(),  
  313. 'virtual' => $variation->is_virtual(),  
  314. 'permalink' => $variation->get_permalink(),  
  315. 'sku' => $variation->get_sku(),  
  316. 'price' => wc_format_decimal( $variation->get_price(), 2 ),  
  317. 'regular_price' => wc_format_decimal( $variation->get_regular_price(), 2 ),  
  318. 'sale_price' => $variation->get_sale_price() ? wc_format_decimal( $variation->get_sale_price(), 2 ) : null,  
  319. 'taxable' => $variation->is_taxable(),  
  320. 'tax_status' => $variation->get_tax_status(),  
  321. 'tax_class' => $variation->get_tax_class(),  
  322. 'stock_quantity' => (int) $variation->get_stock_quantity(),  
  323. 'in_stock' => $variation->is_in_stock(),  
  324. 'backordered' => $variation->is_on_backorder(),  
  325. 'purchaseable' => $variation->is_purchasable(),  
  326. 'visible' => $variation->variation_is_visible(),  
  327. 'on_sale' => $variation->is_on_sale(),  
  328. 'weight' => $variation->get_weight() ? wc_format_decimal( $variation->get_weight(), 2 ) : null,  
  329. 'dimensions' => array( 
  330. 'length' => $variation->get_length(),  
  331. 'width' => $variation->get_width(),  
  332. 'height' => $variation->get_height(),  
  333. 'unit' => get_option( 'woocommerce_dimension_unit' ),  
  334. ),  
  335. 'shipping_class' => $variation->get_shipping_class(),  
  336. 'shipping_class_id' => ( 0 !== $variation->get_shipping_class_id() ) ? $variation->get_shipping_class_id() : null,  
  337. 'image' => $this->get_images( $variation ),  
  338. 'attributes' => $this->get_attributes( $variation ),  
  339. 'downloads' => $this->get_downloads( $variation ),  
  340. 'download_limit' => (int) $product->get_download_limit(),  
  341. 'download_expiry' => (int) $product->get_download_expiry(),  
  342. ); 
  343.  
  344. return $variations; 
  345.  
  346. /** 
  347. * Get the images for a product or product variation 
  348. * @since 2.1 
  349. * @param WC_Product|WC_Product_Variation $product 
  350. * @return array 
  351. */ 
  352. private function get_images( $product ) { 
  353. $images = $attachment_ids = array(); 
  354. $product_image = $product->get_image_id(); 
  355.  
  356. // Add featured image. 
  357. if ( ! empty( $product_image ) ) { 
  358. $attachment_ids[] = $product_image; 
  359.  
  360. // add gallery images. 
  361. $attachment_ids = array_merge( $attachment_ids, $product->get_gallery_image_ids() ); 
  362.  
  363. // Build image data. 
  364. foreach ( $attachment_ids as $position => $attachment_id ) { 
  365.  
  366. $attachment_post = get_post( $attachment_id ); 
  367.  
  368. if ( is_null( $attachment_post ) ) { 
  369. continue; 
  370.  
  371. $attachment = wp_get_attachment_image_src( $attachment_id, 'full' ); 
  372.  
  373. if ( ! is_array( $attachment ) ) { 
  374. continue; 
  375.  
  376. $images[] = array( 
  377. 'id' => (int) $attachment_id,  
  378. 'created_at' => $this->server->format_datetime( $attachment_post->post_date_gmt ),  
  379. 'updated_at' => $this->server->format_datetime( $attachment_post->post_modified_gmt ),  
  380. 'src' => current( $attachment ),  
  381. 'title' => get_the_title( $attachment_id ),  
  382. 'alt' => get_post_meta( $attachment_id, '_wp_attachment_image_alt', true ),  
  383. 'position' => $position,  
  384. ); 
  385.  
  386. // Set a placeholder image if the product has no images set. 
  387. if ( empty( $images ) ) { 
  388.  
  389. $images[] = array( 
  390. 'id' => 0,  
  391. 'created_at' => $this->server->format_datetime( time() ), // default to now 
  392. 'updated_at' => $this->server->format_datetime( time() ),  
  393. 'src' => wc_placeholder_img_src(),  
  394. 'title' => __( 'Placeholder', 'woocommerce' ),  
  395. 'alt' => __( 'Placeholder', 'woocommerce' ),  
  396. 'position' => 0,  
  397. ); 
  398.  
  399. return $images; 
  400.  
  401. /** 
  402. * Get attribute options. 
  403. * @param int $product_id 
  404. * @param array $attribute 
  405. * @return array 
  406. */ 
  407. protected function get_attribute_options( $product_id, $attribute ) { 
  408. if ( isset( $attribute['is_taxonomy'] ) && $attribute['is_taxonomy'] ) { 
  409. return wc_get_product_terms( $product_id, $attribute['name'], array( 'fields' => 'names' ) ); 
  410. } elseif ( isset( $attribute['value'] ) ) { 
  411. return array_map( 'trim', explode( '|', $attribute['value'] ) ); 
  412.  
  413. return array(); 
  414.  
  415. /** 
  416. * Get the attributes for a product or product variation 
  417. * @since 2.1 
  418. * @param WC_Product|WC_Product_Variation $product 
  419. * @return array 
  420. */ 
  421. private function get_attributes( $product ) { 
  422.  
  423. $attributes = array(); 
  424.  
  425. if ( $product->is_type( 'variation' ) ) { 
  426.  
  427. // variation attributes 
  428. foreach ( $product->get_variation_attributes() as $attribute_name => $attribute ) { 
  429.  
  430. // taxonomy-based attributes are prefixed with `pa_`, otherwise simply `attribute_` 
  431. $attributes[] = array( 
  432. 'name' => ucwords( str_replace( 'attribute_', '', str_replace( 'pa_', '', $attribute_name ) ) ),  
  433. 'option' => $attribute,  
  434. ); 
  435. } else { 
  436.  
  437. foreach ( $product->get_attributes() as $attribute ) { 
  438. $attributes[] = array( 
  439. 'name' => ucwords( str_replace( 'pa_', '', $attribute['name'] ) ),  
  440. 'position' => $attribute['position'],  
  441. 'visible' => (bool) $attribute['is_visible'],  
  442. 'variation' => (bool) $attribute['is_variation'],  
  443. 'options' => $this->get_attribute_options( $product->get_id(), $attribute ),  
  444. ); 
  445.  
  446. return $attributes; 
  447.  
  448. /** 
  449. * Get the downloads for a product or product variation 
  450. * @since 2.1 
  451. * @param WC_Product|WC_Product_Variation $product 
  452. * @return array 
  453. */ 
  454. private function get_downloads( $product ) { 
  455.  
  456. $downloads = array(); 
  457.  
  458. if ( $product->is_downloadable() ) { 
  459.  
  460. foreach ( $product->get_downloads() as $file_id => $file ) { 
  461.  
  462. $downloads[] = array( 
  463. 'id' => $file_id, // do not cast as int as this is a hash 
  464. 'name' => $file['name'],  
  465. 'file' => $file['file'],  
  466. ); 
  467.  
  468. return $downloads; 
/includes/api/legacy/v2/class-wc-api-products.php  
  1. class WC_API_Products extends WC_API_Resource { 
  2.  
  3. /** @var string $base the route base */ 
  4. protected $base = '/products'; 
  5.  
  6. /** 
  7. * Register the routes for this class 
  8. * GET/POST /products 
  9. * GET /products/count 
  10. * GET/PUT/DELETE /products/<id> 
  11. * GET /products/<id>/reviews 
  12. * @since 2.1 
  13. * @param array $routes 
  14. * @return array 
  15. */ 
  16. public function register_routes( $routes ) { 
  17.  
  18. # GET/POST /products 
  19. $routes[ $this->base ] = array( 
  20. array( array( $this, 'get_products' ), WC_API_Server::READABLE ),  
  21. array( array( $this, 'create_product' ), WC_API_SERVER::CREATABLE | WC_API_Server::ACCEPT_DATA ),  
  22. ); 
  23.  
  24. # GET /products/count 
  25. $routes[ $this->base . '/count' ] = array( 
  26. array( array( $this, 'get_products_count' ), WC_API_Server::READABLE ),  
  27. ); 
  28.  
  29. # GET/PUT/DELETE /products/<id> 
  30. $routes[ $this->base . '/(?P<id>\d+)' ] = array( 
  31. array( array( $this, 'get_product' ), WC_API_Server::READABLE ),  
  32. array( array( $this, 'edit_product' ), WC_API_Server::EDITABLE | WC_API_Server::ACCEPT_DATA ),  
  33. array( array( $this, 'delete_product' ), WC_API_Server::DELETABLE ),  
  34. ); 
  35.  
  36. # GET /products/<id>/reviews 
  37. $routes[ $this->base . '/(?P<id>\d+)/reviews' ] = array( 
  38. array( array( $this, 'get_product_reviews' ), WC_API_Server::READABLE ),  
  39. ); 
  40.  
  41. # GET /products/<id>/orders 
  42. $routes[ $this->base . '/(?P<id>\d+)/orders' ] = array( 
  43. array( array( $this, 'get_product_orders' ), WC_API_Server::READABLE ),  
  44. ); 
  45.  
  46. # GET /products/categories 
  47. $routes[ $this->base . '/categories' ] = array( 
  48. array( array( $this, 'get_product_categories' ), WC_API_Server::READABLE ),  
  49. ); 
  50.  
  51. # GET /products/categories/<id> 
  52. $routes[ $this->base . '/categories/(?P<id>\d+)' ] = array( 
  53. array( array( $this, 'get_product_category' ), WC_API_Server::READABLE ),  
  54. ); 
  55.  
  56. # GET/POST /products/attributes 
  57. $routes[ $this->base . '/attributes' ] = array( 
  58. array( array( $this, 'get_product_attributes' ), WC_API_Server::READABLE ),  
  59. array( array( $this, 'create_product_attribute' ), WC_API_SERVER::CREATABLE | WC_API_Server::ACCEPT_DATA ),  
  60. ); 
  61.  
  62. # GET/PUT/DELETE /attributes/<id> 
  63. $routes[ $this->base . '/attributes/(?P<id>\d+)' ] = array( 
  64. array( array( $this, 'get_product_attribute' ), WC_API_Server::READABLE ),  
  65. array( array( $this, 'edit_product_attribute' ), WC_API_Server::EDITABLE | WC_API_Server::ACCEPT_DATA ),  
  66. array( array( $this, 'delete_product_attribute' ), WC_API_Server::DELETABLE ),  
  67. ); 
  68.  
  69. # GET /products/sku/<product sku> 
  70. $routes[ $this->base . '/sku/(?P<sku>\w[\w\s\-]*)' ] = array( 
  71. array( array( $this, 'get_product_by_sku' ), WC_API_Server::READABLE ),  
  72. ); 
  73.  
  74. # POST|PUT /products/bulk 
  75. $routes[ $this->base . '/bulk' ] = array( 
  76. array( array( $this, 'bulk' ), WC_API_Server::EDITABLE | WC_API_Server::ACCEPT_DATA ),  
  77. ); 
  78.  
  79. return $routes; 
  80.  
  81. /** 
  82. * Get all products 
  83. * @since 2.1 
  84. * @param string $fields 
  85. * @param string $type 
  86. * @param array $filter 
  87. * @param int $page 
  88. * @return array 
  89. */ 
  90. public function get_products( $fields = null, $type = null, $filter = array(), $page = 1 ) { 
  91.  
  92. if ( ! empty( $type ) ) { 
  93. $filter['type'] = $type; 
  94.  
  95. $filter['page'] = $page; 
  96.  
  97. $query = $this->query_products( $filter ); 
  98.  
  99. $products = array(); 
  100.  
  101. foreach ( $query->posts as $product_id ) { 
  102.  
  103. if ( ! $this->is_readable( $product_id ) ) { 
  104. continue; 
  105.  
  106. $products[] = current( $this->get_product( $product_id, $fields ) ); 
  107.  
  108. $this->server->add_pagination_headers( $query ); 
  109.  
  110. return array( 'products' => $products ); 
  111.  
  112. /** 
  113. * Get the product for the given ID 
  114. * @since 2.1 
  115. * @param int $id the product ID 
  116. * @param string $fields 
  117. * @return array 
  118. */ 
  119. public function get_product( $id, $fields = null ) { 
  120.  
  121. $id = $this->validate_request( $id, 'product', 'read' ); 
  122.  
  123. if ( is_wp_error( $id ) ) { 
  124. return $id; 
  125.  
  126. $product = wc_get_product( $id ); 
  127.  
  128. // add data that applies to every product type 
  129. $product_data = $this->get_product_data( $product ); 
  130.  
  131. // add variations to variable products 
  132. if ( $product->is_type( 'variable' ) && $product->has_child() ) { 
  133. $product_data['variations'] = $this->get_variation_data( $product ); 
  134.  
  135. // add the parent product data to an individual variation 
  136. if ( $product->is_type( 'variation' ) && $product->get_parent_id() ) { 
  137. $_product = wc_get_product( $product->get_parent_id() ); 
  138. $product_data['parent'] = $this->get_product_data( $_product ); 
  139.  
  140. return array( 'product' => apply_filters( 'woocommerce_api_product_response', $product_data, $product, $fields, $this->server ) ); 
  141.  
  142. /** 
  143. * Get the total number of products 
  144. * @since 2.1 
  145. * @param string $type 
  146. * @param array $filter 
  147. * @return array 
  148. */ 
  149. public function get_products_count( $type = null, $filter = array() ) { 
  150. try { 
  151. if ( ! current_user_can( 'read_private_products' ) ) { 
  152. throw new WC_API_Exception( 'woocommerce_api_user_cannot_read_products_count', __( 'You do not have permission to read the products count', 'woocommerce' ), 401 ); 
  153.  
  154. if ( ! empty( $type ) ) { 
  155. $filter['type'] = $type; 
  156.  
  157. $query = $this->query_products( $filter ); 
  158.  
  159. return array( 'count' => (int) $query->found_posts ); 
  160. } catch ( WC_API_Exception $e ) { 
  161. return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) ); 
  162.  
  163. /** 
  164. * Create a new product 
  165. * @since 2.2 
  166. * @param array $data posted data 
  167. * @return array 
  168. */ 
  169. public function create_product( $data ) { 
  170. $id = 0; 
  171.  
  172. try { 
  173. if ( ! isset( $data['product'] ) ) { 
  174. throw new WC_API_Exception( 'woocommerce_api_missing_product_data', sprintf( __( 'No %1$s data specified to create %1$s', 'woocommerce' ), 'product' ), 400 ); 
  175.  
  176. $data = $data['product']; 
  177.  
  178. // Check permissions 
  179. if ( ! current_user_can( 'publish_products' ) ) { 
  180. throw new WC_API_Exception( 'woocommerce_api_user_cannot_create_product', __( 'You do not have permission to create products', 'woocommerce' ), 401 ); 
  181.  
  182. $data = apply_filters( 'woocommerce_api_create_product_data', $data, $this ); 
  183.  
  184. // Check if product title is specified 
  185. if ( ! isset( $data['title'] ) ) { 
  186. throw new WC_API_Exception( 'woocommerce_api_missing_product_title', sprintf( __( 'Missing parameter %s', 'woocommerce' ), 'title' ), 400 ); 
  187.  
  188. // Check product type 
  189. if ( ! isset( $data['type'] ) ) { 
  190. $data['type'] = 'simple'; 
  191.  
  192. // Set visible visibility when not sent 
  193. if ( ! isset( $data['catalog_visibility'] ) ) { 
  194. $data['catalog_visibility'] = 'visible'; 
  195.  
  196. // Validate the product type 
  197. if ( ! in_array( wc_clean( $data['type'] ), array_keys( wc_get_product_types() ) ) ) { 
  198. throw new WC_API_Exception( 'woocommerce_api_invalid_product_type', sprintf( __( 'Invalid product type - the product type must be any of these: %s', 'woocommerce' ), implode( ', ', array_keys( wc_get_product_types() ) ) ), 400 ); 
  199.  
  200. // Enable description html tags. 
  201. $post_content = isset( $data['description'] ) ? wc_clean( $data['description'] ) : ''; 
  202. if ( $post_content && isset( $data['enable_html_description'] ) && true === $data['enable_html_description'] ) { 
  203.  
  204. $post_content = $data['description']; 
  205.  
  206. // Enable short description html tags. 
  207. $post_excerpt = isset( $data['short_description'] ) ? wc_clean( $data['short_description'] ) : ''; 
  208. if ( $post_excerpt && isset( $data['enable_html_short_description'] ) && true === $data['enable_html_short_description'] ) { 
  209. $post_excerpt = $data['short_description']; 
  210.  
  211. $classname = WC_Product_Factory::get_classname_from_product_type( $data['type'] ); 
  212. if ( ! class_exists( $classname ) ) { 
  213. $classname = 'WC_Product_Simple'; 
  214. $product = new $classname(); 
  215.  
  216. $product->set_name( wc_clean( $data['title'] ) ); 
  217. $product->set_status( isset( $data['status'] ) ? wc_clean( $data['status'] ) : 'publish' ); 
  218. $product->set_short_description( isset( $data['short_description'] ) ? $post_excerpt : '' ); 
  219. $product->set_description( isset( $data['description'] ) ? $post_content : '' ); 
  220.  
  221. // Attempts to create the new product. 
  222. $product->save(); 
  223. $id = $product->get_id(); 
  224.  
  225. // Checks for an error in the product creation 
  226. if ( 0 >= $id ) { 
  227. throw new WC_API_Exception( 'woocommerce_api_cannot_create_product', $id->get_error_message(), 400 ); 
  228.  
  229. // Check for featured/gallery images, upload it and set it 
  230. if ( isset( $data['images'] ) ) { 
  231. $product = $this->save_product_images( $product, $data['images'] ); 
  232.  
  233. // Save product meta fields 
  234. $product = $this->save_product_meta( $product, $data ); 
  235. $product->save(); 
  236.  
  237. // Save variations 
  238. if ( isset( $data['type'] ) && 'variable' == $data['type'] && isset( $data['variations'] ) && is_array( $data['variations'] ) ) { 
  239. $this->save_variations( $product, $data ); 
  240.  
  241. do_action( 'woocommerce_api_create_product', $id, $data ); 
  242.  
  243. // Clear cache/transients 
  244. wc_delete_product_transients( $id ); 
  245.  
  246. $this->server->send_status( 201 ); 
  247.  
  248. return $this->get_product( $id ); 
  249. } catch ( WC_Data_Exception $e ) { 
  250. $this->clear_product( $id ); 
  251. return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) ); 
  252. } catch ( WC_API_Exception $e ) { 
  253. $this->clear_product( $id ); 
  254. return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) ); 
  255.  
  256. /** 
  257. * Edit a product 
  258. * @since 2.2 
  259. * @param int $id the product ID 
  260. * @param array $data 
  261. * @return array 
  262. */ 
  263. public function edit_product( $id, $data ) { 
  264. try { 
  265. if ( ! isset( $data['product'] ) ) { 
  266. throw new WC_API_Exception( 'woocommerce_api_missing_product_data', sprintf( __( 'No %1$s data specified to edit %1$s', 'woocommerce' ), 'product' ), 400 ); 
  267.  
  268. $data = $data['product']; 
  269.  
  270. $id = $this->validate_request( $id, 'product', 'edit' ); 
  271.  
  272. if ( is_wp_error( $id ) ) { 
  273. return $id; 
  274.  
  275. $product = wc_get_product( $id ); 
  276.  
  277. $data = apply_filters( 'woocommerce_api_edit_product_data', $data, $this ); 
  278.  
  279. // Product title. 
  280. if ( isset( $data['title'] ) ) { 
  281. $product->set_name( wc_clean( $data['title'] ) ); 
  282.  
  283. // Product name (slug). 
  284. if ( isset( $data['name'] ) ) { 
  285. $product->set_slug( wc_clean( $data['name'] ) ); 
  286.  
  287. // Product status. 
  288. if ( isset( $data['status'] ) ) { 
  289. $product->set_status( wc_clean( $data['status'] ) ); 
  290.  
  291. // Product short description. 
  292. if ( isset( $data['short_description'] ) ) { 
  293. // Enable short description html tags. 
  294. $post_excerpt = ( isset( $data['enable_html_short_description'] ) && true === $data['enable_html_short_description'] ) ? $data['short_description'] : wc_clean( $data['short_description'] ); 
  295. $product->set_short_description( $post_excerpt ); 
  296.  
  297. // Product description. 
  298. if ( isset( $data['description'] ) ) { 
  299. // Enable description html tags. 
  300. $post_content = ( isset( $data['enable_html_description'] ) && true === $data['enable_html_description'] ) ? $data['description'] : wc_clean( $data['description'] ); 
  301. $product->set_description( $post_content ); 
  302.  
  303. // Validate the product type. 
  304. if ( isset( $data['type'] ) && ! in_array( wc_clean( $data['type'] ), array_keys( wc_get_product_types() ) ) ) { 
  305. throw new WC_API_Exception( 'woocommerce_api_invalid_product_type', sprintf( __( 'Invalid product type - the product type must be any of these: %s', 'woocommerce' ), implode( ', ', array_keys( wc_get_product_types() ) ) ), 400 ); 
  306.  
  307. // Check for featured/gallery images, upload it and set it. 
  308. if ( isset( $data['images'] ) ) { 
  309. $product = $this->save_product_images( $product, $data['images'] ); 
  310.  
  311. // Save product meta fields. 
  312. $product = $this->save_product_meta( $product, $data ); 
  313.  
  314. // Save variations. 
  315. if ( $product->is_type( 'variable' ) ) { 
  316. if ( isset( $data['variations'] ) && is_array( $data['variations'] ) ) { 
  317. $this->save_variations( $product, $data ); 
  318. } else { 
  319. // Just sync variations. 
  320. $product = WC_Product_Variable::sync( $product, false ); 
  321.  
  322. $product->save(); 
  323.  
  324. do_action( 'woocommerce_api_edit_product', $id, $data ); 
  325.  
  326. // Clear cache/transients. 
  327. wc_delete_product_transients( $id ); 
  328.  
  329. return $this->get_product( $id ); 
  330. } catch ( WC_Data_Exception $e ) { 
  331. return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) ); 
  332. } catch ( WC_API_Exception $e ) { 
  333. return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) ); 
  334.  
  335. /** 
  336. * Delete a product. 
  337. * @since 2.2 
  338. * @param int $id the product ID. 
  339. * @param bool $force true to permanently delete order, false to move to trash. 
  340. * @return array 
  341. */ 
  342. public function delete_product( $id, $force = false ) { 
  343.  
  344. $id = $this->validate_request( $id, 'product', 'delete' ); 
  345.  
  346. if ( is_wp_error( $id ) ) { 
  347. return $id; 
  348.  
  349. $product = wc_get_product( $id ); 
  350.  
  351. do_action( 'woocommerce_api_delete_product', $id, $this ); 
  352.  
  353. // If we're forcing, then delete permanently. 
  354. if ( $force ) { 
  355. if ( $product->is_type( 'variable' ) ) { 
  356. foreach ( $product->get_children() as $child_id ) { 
  357. $child = wc_get_product( $child_id ); 
  358. $child->delete( true ); 
  359. } elseif ( $product->is_type( 'grouped' ) ) { 
  360. foreach ( $product->get_children() as $child_id ) { 
  361. $child = wc_get_product( $child_id ); 
  362. $child->set_parent_id( 0 ); 
  363. $child->save(); 
  364.  
  365. $product->delete( true ); 
  366. $result = $product->get_id() > 0 ? false : true; 
  367. } else { 
  368. $product->delete(); 
  369. $result = 'trash' === $product->get_status(); 
  370.  
  371. if ( ! $result ) { 
  372. return new WP_Error( 'woocommerce_api_cannot_delete_product', sprintf( __( 'This %s cannot be deleted', 'woocommerce' ), 'product' ), array( 'status' => 500 ) ); 
  373.  
  374. // Delete parent product transients. 
  375. if ( $parent_id = wp_get_post_parent_id( $id ) ) { 
  376. wc_delete_product_transients( $parent_id ); 
  377.  
  378. if ( $force ) { 
  379. return array( 'message' => sprintf( __( 'Permanently deleted %s', 'woocommerce' ), 'product' ) ); 
  380. } else { 
  381. $this->server->send_status( '202' ); 
  382.  
  383. return array( 'message' => sprintf( __( 'Deleted %s', 'woocommerce' ), 'product' ) ); 
  384.  
  385. /** 
  386. * Get the reviews for a product 
  387. * @since 2.1 
  388. * @param int $id the product ID to get reviews for 
  389. * @param string $fields fields to include in response 
  390. * @return array 
  391. */ 
  392. public function get_product_reviews( $id, $fields = null ) { 
  393.  
  394. $id = $this->validate_request( $id, 'product', 'read' ); 
  395.  
  396. if ( is_wp_error( $id ) ) { 
  397. return $id; 
  398.  
  399. $comments = get_approved_comments( $id ); 
  400. $reviews = array(); 
  401.  
  402. foreach ( $comments as $comment ) { 
  403.  
  404. $reviews[] = array( 
  405. 'id' => intval( $comment->comment_ID ),  
  406. 'created_at' => $this->server->format_datetime( $comment->comment_date_gmt ),  
  407. 'review' => $comment->comment_content,  
  408. 'rating' => get_comment_meta( $comment->comment_ID, 'rating', true ),  
  409. 'reviewer_name' => $comment->comment_author,  
  410. 'reviewer_email' => $comment->comment_author_email,  
  411. 'verified' => wc_review_is_from_verified_owner( $comment->comment_ID ),  
  412. ); 
  413.  
  414. return array( 'product_reviews' => apply_filters( 'woocommerce_api_product_reviews_response', $reviews, $id, $fields, $comments, $this->server ) ); 
  415.  
  416. /** 
  417. * Get the orders for a product 
  418. * @since 2.4.0 
  419. * @param int $id the product ID to get orders for 
  420. * @param string fields fields to retrieve 
  421. * @param string $filter filters to include in response 
  422. * @param string $status the order status to retrieve 
  423. * @param $page $page page to retrieve 
  424. * @return array 
  425. */ 
  426. public function get_product_orders( $id, $fields = null, $filter = array(), $status = null, $page = 1 ) { 
  427. global $wpdb; 
  428.  
  429. $id = $this->validate_request( $id, 'product', 'read' ); 
  430.  
  431. if ( is_wp_error( $id ) ) { 
  432. return $id; 
  433.  
  434. $order_ids = $wpdb->get_col( $wpdb->prepare( " 
  435. SELECT order_id 
  436. FROM {$wpdb->prefix}woocommerce_order_items 
  437. WHERE order_item_id IN ( SELECT order_item_id FROM {$wpdb->prefix}woocommerce_order_itemmeta WHERE meta_key = '_product_id' AND meta_value = %d ) 
  438. AND order_item_type = 'line_item' 
  439. ", $id ) ); 
  440.  
  441. if ( empty( $order_ids ) ) { 
  442. return array( 'orders' => array() ); 
  443.  
  444. $filter = array_merge( $filter, array( 
  445. 'in' => implode( ', ', $order_ids ),  
  446. ) ); 
  447.  
  448. $orders = WC()->api->WC_API_Orders->get_orders( $fields, $filter, $status, $page ); 
  449.  
  450. return array( 'orders' => apply_filters( 'woocommerce_api_product_orders_response', $orders['orders'], $id, $filter, $fields, $this->server ) ); 
  451.  
  452. /** 
  453. * Get a listing of product categories 
  454. * @since 2.2 
  455. * @param string|null $fields fields to limit response to 
  456. * @return array 
  457. */ 
  458. public function get_product_categories( $fields = null ) { 
  459. try { 
  460. // Permissions check 
  461. if ( ! current_user_can( 'manage_product_terms' ) ) { 
  462. throw new WC_API_Exception( 'woocommerce_api_user_cannot_read_product_categories', __( 'You do not have permission to read product categories', 'woocommerce' ), 401 ); 
  463.  
  464. $product_categories = array(); 
  465.  
  466. $terms = get_terms( 'product_cat', array( 'hide_empty' => false, 'fields' => 'ids' ) ); 
  467.  
  468. foreach ( $terms as $term_id ) { 
  469. $product_categories[] = current( $this->get_product_category( $term_id, $fields ) ); 
  470.  
  471. return array( 'product_categories' => apply_filters( 'woocommerce_api_product_categories_response', $product_categories, $terms, $fields, $this ) ); 
  472. } catch ( WC_API_Exception $e ) { 
  473. return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) ); 
  474.  
  475. /** 
  476. * Get the product category for the given ID 
  477. * @since 2.2 
  478. * @param string $id product category term ID 
  479. * @param string|null $fields fields to limit response to 
  480. * @return array 
  481. */ 
  482. public function get_product_category( $id, $fields = null ) { 
  483. try { 
  484. $id = absint( $id ); 
  485.  
  486. // Validate ID 
  487. if ( empty( $id ) ) { 
  488. throw new WC_API_Exception( 'woocommerce_api_invalid_product_category_id', __( 'Invalid product category ID', 'woocommerce' ), 400 ); 
  489.  
  490. // Permissions check 
  491. if ( ! current_user_can( 'manage_product_terms' ) ) { 
  492. throw new WC_API_Exception( 'woocommerce_api_user_cannot_read_product_categories', __( 'You do not have permission to read product categories', 'woocommerce' ), 401 ); 
  493.  
  494. $term = get_term( $id, 'product_cat' ); 
  495.  
  496. if ( is_wp_error( $term ) || is_null( $term ) ) { 
  497. throw new WC_API_Exception( 'woocommerce_api_invalid_product_category_id', __( 'A product category with the provided ID could not be found', 'woocommerce' ), 404 ); 
  498.  
  499. $term_id = intval( $term->term_id ); 
  500.  
  501. // Get category display type 
  502. $display_type = get_woocommerce_term_meta( $term_id, 'display_type' ); 
  503.  
  504. // Get category image 
  505. $image = ''; 
  506. if ( $image_id = get_woocommerce_term_meta( $term_id, 'thumbnail_id' ) ) { 
  507. $image = wp_get_attachment_url( $image_id ); 
  508.  
  509. $product_category = array( 
  510. 'id' => $term_id,  
  511. 'name' => $term->name,  
  512. 'slug' => $term->slug,  
  513. 'parent' => $term->parent,  
  514. 'description' => $term->description,  
  515. 'display' => $display_type ? $display_type : 'default',  
  516. 'image' => $image ? esc_url( $image ) : '',  
  517. 'count' => intval( $term->count ),  
  518. ); 
  519.  
  520. return array( 'product_category' => apply_filters( 'woocommerce_api_product_category_response', $product_category, $id, $fields, $term, $this ) ); 
  521. } catch ( WC_API_Exception $e ) { 
  522. return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) ); 
  523.  
  524. /** 
  525. * Helper method to get product post objects 
  526. * @since 2.1 
  527. * @param array $args request arguments for filtering query 
  528. * @return WP_Query 
  529. */ 
  530. private function query_products( $args ) { 
  531.  
  532. // Set base query arguments 
  533. $query_args = array( 
  534. 'fields' => 'ids',  
  535. 'post_type' => 'product',  
  536. 'post_status' => 'publish',  
  537. 'meta_query' => array(),  
  538. ); 
  539.  
  540. if ( ! empty( $args['type'] ) ) { 
  541.  
  542. $types = explode( ', ', $args['type'] ); 
  543.  
  544. $query_args['tax_query'] = array( 
  545. array( 
  546. 'taxonomy' => 'product_type',  
  547. 'field' => 'slug',  
  548. 'terms' => $types,  
  549. ),  
  550. ); 
  551.  
  552. unset( $args['type'] ); 
  553.  
  554. // Filter products by category 
  555. if ( ! empty( $args['category'] ) ) { 
  556. $query_args['product_cat'] = $args['category']; 
  557.  
  558. // Filter by specific sku 
  559. if ( ! empty( $args['sku'] ) ) { 
  560. if ( ! is_array( $query_args['meta_query'] ) ) { 
  561. $query_args['meta_query'] = array(); 
  562.  
  563. $query_args['meta_query'][] = array( 
  564. 'key' => '_sku',  
  565. 'value' => $args['sku'],  
  566. 'compare' => '=',  
  567. ); 
  568.  
  569. $query_args['post_type'] = array( 'product', 'product_variation' ); 
  570.  
  571. $query_args = $this->merge_query_args( $query_args, $args ); 
  572.  
  573. return new WP_Query( $query_args ); 
  574.  
  575. /** 
  576. * Get standard product data that applies to every product type 
  577. * @since 2.1 
  578. * @param WC_Product|int $product 
  579. * @return WC_Product 
  580. */ 
  581. private function get_product_data( $product ) { 
  582. if ( is_numeric( $product ) ) { 
  583. $product = wc_get_product( $product ); 
  584.  
  585. $prices_precision = wc_get_price_decimals(); 
  586. return array( 
  587. 'title' => $product->get_name(),  
  588. 'id' => $product->get_id(),  
  589. 'created_at' => $this->server->format_datetime( $product->get_date_created(), false, true ),  
  590. 'updated_at' => $this->server->format_datetime( $product->get_date_modified(), false, true ),  
  591. 'type' => $product->get_type(),  
  592. 'status' => $product->get_status(),  
  593. 'downloadable' => $product->is_downloadable(),  
  594. 'virtual' => $product->is_virtual(),  
  595. 'permalink' => $product->get_permalink(),  
  596. 'sku' => $product->get_sku(),  
  597. 'price' => wc_format_decimal( $product->get_price(), $prices_precision ),  
  598. 'regular_price' => wc_format_decimal( $product->get_regular_price(), $prices_precision ),  
  599. 'sale_price' => $product->get_sale_price() ? wc_format_decimal( $product->get_sale_price(), $prices_precision ) : null,  
  600. 'price_html' => $product->get_price_html(),  
  601. 'taxable' => $product->is_taxable(),  
  602. 'tax_status' => $product->get_tax_status(),  
  603. 'tax_class' => $product->get_tax_class(),  
  604. 'managing_stock' => $product->managing_stock(),  
  605. 'stock_quantity' => $product->get_stock_quantity(),  
  606. 'in_stock' => $product->is_in_stock(),  
  607. 'backorders_allowed' => $product->backorders_allowed(),  
  608. 'backordered' => $product->is_on_backorder(),  
  609. 'sold_individually' => $product->is_sold_individually(),  
  610. 'purchaseable' => $product->is_purchasable(),  
  611. 'featured' => $product->is_featured(),  
  612. 'visible' => $product->is_visible(),  
  613. 'catalog_visibility' => $product->get_catalog_visibility(),  
  614. 'on_sale' => $product->is_on_sale(),  
  615. 'product_url' => $product->is_type( 'external' ) ? $product->get_product_url() : '',  
  616. 'button_text' => $product->is_type( 'external' ) ? $product->get_button_text() : '',  
  617. 'weight' => $product->get_weight() ? wc_format_decimal( $product->get_weight(), 2 ) : null,  
  618. 'dimensions' => array( 
  619. 'length' => $product->get_length(),  
  620. 'width' => $product->get_width(),  
  621. 'height' => $product->get_height(),  
  622. 'unit' => get_option( 'woocommerce_dimension_unit' ),  
  623. ),  
  624. 'shipping_required' => $product->needs_shipping(),  
  625. 'shipping_taxable' => $product->is_shipping_taxable(),  
  626. 'shipping_class' => $product->get_shipping_class(),  
  627. 'shipping_class_id' => ( 0 !== $product->get_shipping_class_id() ) ? $product->get_shipping_class_id() : null,  
  628. 'description' => wpautop( do_shortcode( $product->get_description() ) ),  
  629. 'short_description' => apply_filters( 'woocommerce_short_description', $product->get_short_description() ),  
  630. 'reviews_allowed' => $product->get_reviews_allowed(),  
  631. 'average_rating' => wc_format_decimal( $product->get_average_rating(), 2 ),  
  632. 'rating_count' => $product->get_rating_count(),  
  633. 'related_ids' => array_map( 'absint', array_values( wc_get_related_products( $product->get_id() ) ) ),  
  634. 'upsell_ids' => array_map( 'absint', $product->get_upsell_ids() ),  
  635. 'cross_sell_ids' => array_map( 'absint', $product->get_cross_sell_ids() ),  
  636. 'parent_id' => $product->get_parent_id(),  
  637. 'categories' => wc_get_object_terms( $product->get_id(), 'product_cat', 'name' ),  
  638. 'tags' => wc_get_object_terms( $product->get_id(), 'product_tag', 'name' ),  
  639. 'images' => $this->get_images( $product ),  
  640. 'featured_src' => wp_get_attachment_url( get_post_thumbnail_id( $product->get_id() ) ),  
  641. 'attributes' => $this->get_attributes( $product ),  
  642. 'downloads' => $this->get_downloads( $product ),  
  643. 'download_limit' => $product->get_download_limit(),  
  644. 'download_expiry' => $product->get_download_expiry(),  
  645. 'download_type' => 'standard',  
  646. 'purchase_note' => wpautop( do_shortcode( wp_kses_post( $product->get_purchase_note() ) ) ),  
  647. 'total_sales' => $product->get_total_sales(),  
  648. 'variations' => array(),  
  649. 'parent' => array(),  
  650. ); 
  651.  
  652. /** 
  653. * Get an individual variation's data 
  654. * @since 2.1 
  655. * @param WC_Product $product 
  656. * @return array 
  657. */ 
  658. private function get_variation_data( $product ) { 
  659. $prices_precision = wc_get_price_decimals(); 
  660. $variations = array(); 
  661.  
  662. foreach ( $product->get_children() as $child_id ) { 
  663.  
  664. $variation = wc_get_product( $child_id ); 
  665.  
  666. if ( ! $variation || ! $variation->exists() ) { 
  667. continue; 
  668.  
  669. $variations[] = array( 
  670. 'id' => $variation->get_id(),  
  671. 'created_at' => $this->server->format_datetime( $variation->get_date_created(), false, true ),  
  672. 'updated_at' => $this->server->format_datetime( $variation->get_date_modified(), false, true ),  
  673. 'downloadable' => $variation->is_downloadable(),  
  674. 'virtual' => $variation->is_virtual(),  
  675. 'permalink' => $variation->get_permalink(),  
  676. 'sku' => $variation->get_sku(),  
  677. 'price' => wc_format_decimal( $variation->get_price(), $prices_precision ),  
  678. 'regular_price' => wc_format_decimal( $variation->get_regular_price(), $prices_precision ),  
  679. 'sale_price' => $variation->get_sale_price() ? wc_format_decimal( $variation->get_sale_price(), $prices_precision ) : null,  
  680. 'taxable' => $variation->is_taxable(),  
  681. 'tax_status' => $variation->get_tax_status(),  
  682. 'tax_class' => $variation->get_tax_class(),  
  683. 'managing_stock' => $variation->managing_stock(),  
  684. 'stock_quantity' => (int) $variation->get_stock_quantity(),  
  685. 'in_stock' => $variation->is_in_stock(),  
  686. 'backordered' => $variation->is_on_backorder(),  
  687. 'purchaseable' => $variation->is_purchasable(),  
  688. 'visible' => $variation->variation_is_visible(),  
  689. 'on_sale' => $variation->is_on_sale(),  
  690. 'weight' => $variation->get_weight() ? wc_format_decimal( $variation->get_weight(), 2 ) : null,  
  691. 'dimensions' => array( 
  692. 'length' => $variation->get_length(),  
  693. 'width' => $variation->get_width(),  
  694. 'height' => $variation->get_height(),  
  695. 'unit' => get_option( 'woocommerce_dimension_unit' ),  
  696. ),  
  697. 'shipping_class' => $variation->get_shipping_class(),  
  698. 'shipping_class_id' => ( 0 !== $variation->get_shipping_class_id() ) ? $variation->get_shipping_class_id() : null,  
  699. 'image' => $this->get_images( $variation ),  
  700. 'attributes' => $this->get_attributes( $variation ),  
  701. 'downloads' => $this->get_downloads( $variation ),  
  702. 'download_limit' => (int) $product->get_download_limit(),  
  703. 'download_expiry' => (int) $product->get_download_expiry(),  
  704. ); 
  705.  
  706. return $variations; 
  707.  
  708. /** 
  709. * Save default attributes. 
  710. * @since 3.0.0 
  711. * @param WC_Product $product 
  712. * @param array $request 
  713. * @return WC_Product 
  714. */ 
  715. protected function save_default_attributes( $product, $request ) { 
  716. // Update default attributes options setting. 
  717. if ( isset( $request['default_attribute'] ) ) { 
  718. $request['default_attributes'] = $request['default_attribute']; 
  719.  
  720. if ( isset( $request['default_attributes'] ) && is_array( $request['default_attributes'] ) ) { 
  721. $attributes = $product->get_attributes(); 
  722. $default_attributes = array(); 
  723.  
  724. foreach ( $request['default_attributes'] as $default_attr_key => $default_attr ) { 
  725. if ( ! isset( $default_attr['name'] ) ) { 
  726. continue; 
  727.  
  728. $taxonomy = sanitize_title( $default_attr['name'] ); 
  729.  
  730. if ( isset( $default_attr['slug'] ) ) { 
  731. $taxonomy = $this->get_attribute_taxonomy_by_slug( $default_attr['slug'] ); 
  732.  
  733. if ( isset( $attributes[ $taxonomy ] ) ) { 
  734. $_attribute = $attributes[ $taxonomy ]; 
  735.  
  736. if ( $_attribute['is_variation'] ) { 
  737. $value = ''; 
  738.  
  739. if ( isset( $default_attr['option'] ) ) { 
  740. if ( $_attribute['is_taxonomy'] ) { 
  741. // Don't use wc_clean as it destroys sanitized characters. 
  742. $value = sanitize_title( trim( stripslashes( $default_attr['option'] ) ) ); 
  743. } else { 
  744. $value = wc_clean( trim( stripslashes( $default_attr['option'] ) ) ); 
  745.  
  746. if ( $value ) { 
  747. $default_attributes[ $taxonomy ] = $value; 
  748.  
  749. $product->set_default_attributes( $default_attributes ); 
  750.  
  751. return $product; 
  752.  
  753. /** 
  754. * Save product meta 
  755. * @since 2.2 
  756. * @param WC_Product $product 
  757. * @param array $data 
  758. * @return WC_Product 
  759. * @throws WC_API_Exception 
  760. */ 
  761. protected function save_product_meta( $product, $data ) { 
  762. global $wpdb; 
  763.  
  764. // Virtual 
  765. if ( isset( $data['virtual'] ) ) { 
  766. $product->set_virtual( $data['virtual'] ); 
  767.  
  768. // Tax status 
  769. if ( isset( $data['tax_status'] ) ) { 
  770. $product->set_tax_status( wc_clean( $data['tax_status'] ) ); 
  771.  
  772. // Tax Class 
  773. if ( isset( $data['tax_class'] ) ) { 
  774. $product->set_tax_class( wc_clean( $data['tax_class'] ) ); 
  775.  
  776. // Catalog Visibility 
  777. if ( isset( $data['catalog_visibility'] ) ) { 
  778. $product->set_catalog_visibility( wc_clean( $data['catalog_visibility'] ) ); 
  779.  
  780. // Purchase Note 
  781. if ( isset( $data['purchase_note'] ) ) { 
  782. $product->set_purchase_note( wc_clean( $data['purchase_note'] ) ); 
  783.  
  784. // Featured Product 
  785. if ( isset( $data['featured'] ) ) { 
  786. $product->set_featured( $data['featured'] ); 
  787.  
  788. // Shipping data 
  789. $product = $this->save_product_shipping_data( $product, $data ); 
  790.  
  791. // SKU 
  792. if ( isset( $data['sku'] ) ) { 
  793. $sku = $product->get_sku(); 
  794. $new_sku = wc_clean( $data['sku'] ); 
  795.  
  796. if ( '' == $new_sku ) { 
  797. $product->set_sku( '' ); 
  798. } elseif ( $new_sku !== $sku ) { 
  799. if ( ! empty( $new_sku ) ) { 
  800. $unique_sku = wc_product_has_unique_sku( $product->get_id(), $new_sku ); 
  801. if ( ! $unique_sku ) { 
  802. throw new WC_API_Exception( 'woocommerce_api_product_sku_already_exists', __( 'The SKU already exists on another product.', 'woocommerce' ), 400 ); 
  803. } else { 
  804. $product->set_sku( $new_sku ); 
  805. } else { 
  806. $product->set_sku( '' ); 
  807.  
  808. // Attributes 
  809. if ( isset( $data['attributes'] ) ) { 
  810. $attributes = array(); 
  811.  
  812. foreach ( $data['attributes'] as $attribute ) { 
  813. $is_taxonomy = 0; 
  814. $taxonomy = 0; 
  815.  
  816. if ( ! isset( $attribute['name'] ) ) { 
  817. continue; 
  818.  
  819. $attribute_slug = sanitize_title( $attribute['name'] ); 
  820.  
  821. if ( isset( $attribute['slug'] ) ) { 
  822. $taxonomy = $this->get_attribute_taxonomy_by_slug( $attribute['slug'] ); 
  823. $attribute_slug = sanitize_title( $attribute['slug'] ); 
  824.  
  825. if ( $taxonomy ) { 
  826. $is_taxonomy = 1; 
  827.  
  828. if ( $is_taxonomy ) { 
  829.  
  830. $attribute_id = wc_attribute_taxonomy_id_by_name( $attribute['name'] ); 
  831.  
  832. if ( isset( $attribute['options'] ) ) { 
  833. $options = $attribute['options']; 
  834.  
  835. if ( ! is_array( $attribute['options'] ) ) { 
  836. // Text based attributes - Posted values are term names 
  837. $options = explode( WC_DELIMITER, $options ); 
  838.  
  839. $values = array_map( 'wc_sanitize_term_text_based', $options ); 
  840. $values = array_filter( $values, 'strlen' ); 
  841. } else { 
  842. $values = array(); 
  843.  
  844. // Update post terms 
  845. if ( taxonomy_exists( $taxonomy ) ) { 
  846. wp_set_object_terms( $product->get_id(), $values, $taxonomy ); 
  847.  
  848. if ( ! empty( $values ) ) { 
  849. // Add attribute to array, but don't set values. 
  850. $attribute_object = new WC_Product_Attribute(); 
  851. $attribute_object->set_id( $attribute_id ); 
  852. $attribute_object->set_name( $taxonomy ); 
  853. $attribute_object->set_options( $values ); 
  854. $attribute_object->set_position( isset( $attribute['position'] ) ? absint( $attribute['position'] ) : 0 ); 
  855. $attribute_object->set_visible( ( isset( $attribute['visible'] ) && $attribute['visible'] ) ? 1 : 0 ); 
  856. $attribute_object->set_variation( ( isset( $attribute['variation'] ) && $attribute['variation'] ) ? 1 : 0 ); 
  857. $attributes[] = $attribute_object; 
  858. } elseif ( isset( $attribute['options'] ) ) { 
  859. // Array based 
  860. if ( is_array( $attribute['options'] ) ) { 
  861. $values = $attribute['options']; 
  862.  
  863. // Text based, separate by pipe 
  864. } else { 
  865. $values = array_map( 'wc_clean', explode( WC_DELIMITER, $attribute['options'] ) ); 
  866.  
  867. // Custom attribute - Add attribute to array and set the values. 
  868. $attribute_object = new WC_Product_Attribute(); 
  869. $attribute_object->set_name( $attribute['name'] ); 
  870. $attribute_object->set_options( $values ); 
  871. $attribute_object->set_position( isset( $attribute['position'] ) ? absint( $attribute['position'] ) : 0 ); 
  872. $attribute_object->set_visible( ( isset( $attribute['visible'] ) && $attribute['visible'] ) ? 1 : 0 ); 
  873. $attribute_object->set_variation( ( isset( $attribute['variation'] ) && $attribute['variation'] ) ? 1 : 0 ); 
  874. $attributes[] = $attribute_object; 
  875.  
  876. uasort( $attributes, 'wc_product_attribute_uasort_comparison' ); 
  877.  
  878. $product->set_attributes( $attributes ); 
  879.  
  880. // Sales and prices 
  881. if ( in_array( $product->get_type(), array( 'variable', 'grouped' ) ) ) { 
  882.  
  883. // Variable and grouped products have no prices. 
  884. $product->set_regular_price( '' ); 
  885. $product->set_sale_price( '' ); 
  886. $product->set_date_on_sale_to( '' ); 
  887. $product->set_date_on_sale_from( '' ); 
  888. $product->set_price( '' ); 
  889.  
  890. } else { 
  891.  
  892. // Regular Price 
  893. if ( isset( $data['regular_price'] ) ) { 
  894. $regular_price = ( '' === $data['regular_price'] ) ? '' : $data['regular_price']; 
  895. } else { 
  896. $regular_price = $product->get_regular_price(); 
  897.  
  898. // Sale Price 
  899. if ( isset( $data['sale_price'] ) ) { 
  900. $sale_price = ( '' === $data['sale_price'] ) ? '' : $data['sale_price']; 
  901. } else { 
  902. $sale_price = $product->get_sale_price(); 
  903.  
  904. $product->set_regular_price( $regular_price ); 
  905. $product->set_sale_price( $sale_price ); 
  906.  
  907. if ( isset( $data['sale_price_dates_from'] ) ) { 
  908. $date_from = $data['sale_price_dates_from']; 
  909. } else { 
  910. $date_from = $product->get_date_on_sale_from() ? date( 'Y-m-d', $product->get_date_on_sale_from()->getTimestamp() ) : ''; 
  911.  
  912. if ( isset( $data['sale_price_dates_to'] ) ) { 
  913. $date_to = $data['sale_price_dates_to']; 
  914. } else { 
  915. $date_to = $product->get_date_on_sale_to() ? date( 'Y-m-d', $product->get_date_on_sale_to()->getTimestamp() ) : ''; 
  916.  
  917. if ( $date_to && ! $date_from ) { 
  918. $date_from = strtotime( 'NOW', current_time( 'timestamp', true ) ); 
  919.  
  920. $product->set_date_on_sale_to( $date_to ); 
  921. $product->set_date_on_sale_from( $date_from ); 
  922. if ( $product->is_on_sale() ) { 
  923. $product->set_price( $product->get_sale_price() ); 
  924. } else { 
  925. $product->set_price( $product->get_regular_price() ); 
  926.  
  927. // Product parent ID for groups 
  928. if ( isset( $data['parent_id'] ) ) { 
  929. $product->set_parent_id( absint( $data['parent_id'] ) ); 
  930.  
  931. // Sold Individually 
  932. if ( isset( $data['sold_individually'] ) ) { 
  933. $product->set_sold_individually( true === $data['sold_individually'] ? 'yes' : '' ); 
  934.  
  935. // Stock status 
  936. if ( isset( $data['in_stock'] ) ) { 
  937. $stock_status = ( true === $data['in_stock'] ) ? 'instock' : 'outofstock'; 
  938. } else { 
  939. $stock_status = $product->get_stock_status(); 
  940.  
  941. if ( '' === $stock_status ) { 
  942. $stock_status = 'instock'; 
  943.  
  944. // Stock Data 
  945. if ( 'yes' == get_option( 'woocommerce_manage_stock' ) ) { 
  946. // Manage stock 
  947. if ( isset( $data['managing_stock'] ) ) { 
  948. $managing_stock = ( true === $data['managing_stock'] ) ? 'yes' : 'no'; 
  949. $product->set_manage_stock( $managing_stock ); 
  950. } else { 
  951. $managing_stock = $product->get_manage_stock() ? 'yes' : 'no'; 
  952.  
  953. // Backorders 
  954. if ( isset( $data['backorders'] ) ) { 
  955. if ( 'notify' == $data['backorders'] ) { 
  956. $backorders = 'notify'; 
  957. } else { 
  958. $backorders = ( true === $data['backorders'] ) ? 'yes' : 'no'; 
  959.  
  960. $product->set_backorders( $backorders ); 
  961. } else { 
  962. $backorders = $product->get_backorders(); 
  963.  
  964. if ( $product->is_type( 'grouped' ) ) { 
  965. $product->set_manage_stock( 'no' ); 
  966. $product->set_backorders( 'no' ); 
  967. $product->set_stock_quantity( '' ); 
  968. $product->set_stock_status( $stock_status ); 
  969. } elseif ( $product->is_type( 'external' ) ) { 
  970. $product->set_manage_stock( 'no' ); 
  971. $product->set_backorders( 'no' ); 
  972. $product->set_stock_quantity( '' ); 
  973. $product->set_stock_status( 'instock' ); 
  974. } elseif ( 'yes' == $managing_stock ) { 
  975. $product->set_backorders( $backorders ); 
  976.  
  977. // Stock status is always determined by children so sync later. 
  978. if ( ! $product->is_type( 'variable' ) ) { 
  979. $product->set_stock_status( $stock_status ); 
  980.  
  981. // Stock quantity 
  982. if ( isset( $data['stock_quantity'] ) ) { 
  983. $product->set_stock_quantity( wc_stock_amount( $data['stock_quantity'] ) ); 
  984. } else { 
  985. // Don't manage stock. 
  986. $product->set_manage_stock( 'no' ); 
  987. $product->set_backorders( $backorders ); 
  988. $product->set_stock_quantity( '' ); 
  989. $product->set_stock_status( $stock_status ); 
  990. } elseif ( ! $product->is_type( 'variable' ) ) { 
  991. $product->set_stock_status( $stock_status ); 
  992.  
  993. // Upsells 
  994. if ( isset( $data['upsell_ids'] ) ) { 
  995. $upsells = array(); 
  996. $ids = $data['upsell_ids']; 
  997.  
  998. if ( ! empty( $ids ) ) { 
  999. foreach ( $ids as $id ) { 
  1000. if ( $id && $id > 0 ) { 
  1001. $upsells[] = $id; 
  1002.  
  1003. $product->set_upsell_ids( $upsells ); 
  1004. } else { 
  1005. $product->set_upsell_ids( array() ); 
  1006.  
  1007. // Cross sells 
  1008. if ( isset( $data['cross_sell_ids'] ) ) { 
  1009. $crosssells = array(); 
  1010. $ids = $data['cross_sell_ids']; 
  1011.  
  1012. if ( ! empty( $ids ) ) { 
  1013. foreach ( $ids as $id ) { 
  1014. if ( $id && $id > 0 ) { 
  1015. $crosssells[] = $id; 
  1016.  
  1017. $product->set_cross_sell_ids( $crosssells ); 
  1018. } else { 
  1019. $product->set_cross_sell_ids( array() ); 
  1020.  
  1021. // Product categories 
  1022. if ( isset( $data['categories'] ) && is_array( $data['categories'] ) ) { 
  1023. $product->set_category_ids( $data['categories'] ); 
  1024.  
  1025. // Product tags 
  1026. if ( isset( $data['tags'] ) && is_array( $data['tags'] ) ) { 
  1027. $product->set_tag_ids( $data['tags'] ); 
  1028.  
  1029. // Downloadable 
  1030. if ( isset( $data['downloadable'] ) ) { 
  1031. $is_downloadable = ( true === $data['downloadable'] ) ? 'yes' : 'no'; 
  1032. $product->set_downloadable( $is_downloadable ); 
  1033. } else { 
  1034. $is_downloadable = $product->get_downloadable() ? 'yes' : 'no'; 
  1035.  
  1036. // Downloadable options 
  1037. if ( 'yes' == $is_downloadable ) { 
  1038.  
  1039. // Downloadable files 
  1040. if ( isset( $data['downloads'] ) && is_array( $data['downloads'] ) ) { 
  1041. $product = $this->save_downloadable_files( $product, $data['downloads'] ); 
  1042.  
  1043. // Download limit 
  1044. if ( isset( $data['download_limit'] ) ) { 
  1045. $product->set_download_limit( $data['download_limit'] ); 
  1046.  
  1047. // Download expiry 
  1048. if ( isset( $data['download_expiry'] ) ) { 
  1049. $product->set_download_expiry( $data['download_expiry'] ); 
  1050.  
  1051. // Product url 
  1052. if ( $product->is_type( 'external' ) ) { 
  1053. if ( isset( $data['product_url'] ) ) { 
  1054. $product->set_product_url( $data['product_url'] ); 
  1055.  
  1056. if ( isset( $data['button_text'] ) ) { 
  1057. $product->set_button_text( $data['button_text'] ); 
  1058.  
  1059. // Reviews allowed 
  1060. if ( isset( $data['reviews_allowed'] ) ) { 
  1061. $product->set_reviews_allowed( $data['reviews_allowed'] ); 
  1062.  
  1063. // Save default attributes for variable products. 
  1064. if ( $product->is_type( 'variable' ) ) { 
  1065. $product = $this->save_default_attributes( $product, $data ); 
  1066.  
  1067. // Do action for product type 
  1068. do_action( 'woocommerce_api_process_product_meta_' . $product->get_type(), $product->get_id(), $data ); 
  1069.  
  1070. return $product; 
  1071.  
  1072. /** 
  1073. * Save variations 
  1074. * @since 2.2 
  1075. * @param WC_Product $product 
  1076. * @param array $request 
  1077. * @return WC_Product 
  1078. * @throws WC_API_Exception 
  1079. */ 
  1080. protected function save_variations( $product, $request ) { 
  1081. global $wpdb; 
  1082.  
  1083. $id = $product->get_id(); 
  1084. $attributes = $product->get_attributes(); 
  1085.  
  1086. foreach ( $request['variations'] as $menu_order => $data ) { 
  1087. $variation_id = isset( $data['id'] ) ? absint( $data['id'] ) : 0; 
  1088. $variation = new WC_Product_Variation( $variation_id ); 
  1089.  
  1090. // Create initial name and status. 
  1091. if ( ! $variation->get_slug() ) { 
  1092. /** translators: 1: variation id 2: product name */ 
  1093. $variation->set_name( sprintf( __( 'Variation #%1$s of %2$s', 'woocommerce' ), $variation->get_id(), $product->get_name() ) ); 
  1094. $variation->set_status( isset( $data['visible'] ) && false === $data['visible'] ? 'private' : 'publish' ); 
  1095.  
  1096. // Parent ID. 
  1097. $variation->set_parent_id( $product->get_id() ); 
  1098.  
  1099. // Menu order. 
  1100. $variation->set_menu_order( $menu_order ); 
  1101.  
  1102. // Status. 
  1103. if ( isset( $data['visible'] ) ) { 
  1104. $variation->set_status( false === $data['visible'] ? 'private' : 'publish' ); 
  1105.  
  1106. // SKU. 
  1107. if ( isset( $data['sku'] ) ) { 
  1108. $variation->set_sku( wc_clean( $data['sku'] ) ); 
  1109.  
  1110. // Thumbnail. 
  1111. if ( isset( $data['image'] ) && is_array( $data['image'] ) ) { 
  1112. $image = current( $data['image'] ); 
  1113. if ( is_array( $image ) ) { 
  1114. $image['position'] = 0; 
  1115.  
  1116. $variation = $this->save_product_images( $variation, array( $image ) ); 
  1117.  
  1118. // Virtual variation. 
  1119. if ( isset( $data['virtual'] ) ) { 
  1120. $variation->set_virtual( $data['virtual'] ); 
  1121.  
  1122. // Downloadable variation. 
  1123. if ( isset( $data['downloadable'] ) ) { 
  1124. $is_downloadable = $data['downloadable']; 
  1125. $variation->set_downloadable( $is_downloadable ); 
  1126. } else { 
  1127. $is_downloadable = $variation->get_downloadable(); 
  1128.  
  1129. // Downloads. 
  1130. if ( $is_downloadable ) { 
  1131. // Downloadable files. 
  1132. if ( isset( $data['downloads'] ) && is_array( $data['downloads'] ) ) { 
  1133. $variation = $this->save_downloadable_files( $variation, $data['downloads'] ); 
  1134.  
  1135. // Download limit. 
  1136. if ( isset( $data['download_limit'] ) ) { 
  1137. $variation->set_download_limit( $data['download_limit'] ); 
  1138.  
  1139. // Download expiry. 
  1140. if ( isset( $data['download_expiry'] ) ) { 
  1141. $variation->set_download_expiry( $data['download_expiry'] ); 
  1142.  
  1143. // Shipping data. 
  1144. $variation = $this->save_product_shipping_data( $variation, $data ); 
  1145.  
  1146. // Stock handling. 
  1147. $manage_stock = (bool) $variation->get_manage_stock(); 
  1148. if ( isset( $data['managing_stock'] ) ) { 
  1149. $manage_stock = $data['managing_stock']; 
  1150. $variation->set_manage_stock( $manage_stock ); 
  1151.  
  1152. $stock_status = $variation->get_stock_status(); 
  1153. if ( isset( $data['in_stock'] ) ) { 
  1154. $stock_status = true === $data['in_stock'] ? 'instock' : 'outofstock'; 
  1155. $variation->set_stock_status( $stock_status ); 
  1156.  
  1157. $backorders = $variation->get_backorders(); 
  1158. if ( isset( $data['backorders'] ) ) { 
  1159. $backorders = $data['backorders']; 
  1160. $variation->set_backorders( $backorders ); 
  1161.  
  1162. if ( $manage_stock ) { 
  1163. if ( isset( $data['stock_quantity'] ) ) { 
  1164. $variation->set_stock_quantity( $data['stock_quantity'] ); 
  1165. } else { 
  1166. $variation->set_backorders( 'no' ); 
  1167. $variation->set_stock_quantity( '' ); 
  1168.  
  1169. // Regular Price. 
  1170. if ( isset( $data['regular_price'] ) ) { 
  1171. $variation->set_regular_price( $data['regular_price'] ); 
  1172.  
  1173. // Sale Price. 
  1174. if ( isset( $data['sale_price'] ) ) { 
  1175. $variation->set_sale_price( $data['sale_price'] ); 
  1176.  
  1177. if ( isset( $data['sale_price_dates_from'] ) ) { 
  1178. $variation->set_date_on_sale_from( $data['sale_price_dates_from'] ); 
  1179.  
  1180. if ( isset( $data['sale_price_dates_to'] ) ) { 
  1181. $variation->set_date_on_sale_to( $data['sale_price_dates_to'] ); 
  1182.  
  1183. // Tax class. 
  1184. if ( isset( $data['tax_class'] ) ) { 
  1185. $variation->set_tax_class( $data['tax_class'] ); 
  1186.  
  1187. // Update taxonomies. 
  1188. if ( isset( $data['attributes'] ) ) { 
  1189. $_attributes = array(); 
  1190.  
  1191. foreach ( $data['attributes'] as $attribute_key => $attribute ) { 
  1192. if ( ! isset( $attribute['name'] ) ) { 
  1193. continue; 
  1194.  
  1195. $taxonomy = 0; 
  1196. $_attribute = array(); 
  1197.  
  1198. if ( isset( $attribute['slug'] ) ) { 
  1199. $taxonomy = $this->get_attribute_taxonomy_by_slug( $attribute['slug'] ); 
  1200.  
  1201. if ( ! $taxonomy ) { 
  1202. $taxonomy = sanitize_title( $attribute['name'] ); 
  1203.  
  1204. if ( isset( $attributes[ $taxonomy ] ) ) { 
  1205. $_attribute = $attributes[ $taxonomy ]; 
  1206.  
  1207. if ( isset( $_attribute['is_variation'] ) && $_attribute['is_variation'] ) { 
  1208. $_attribute_key = sanitize_title( $_attribute['name'] ); 
  1209.  
  1210. if ( isset( $_attribute['is_taxonomy'] ) && $_attribute['is_taxonomy'] ) { 
  1211. // Don't use wc_clean as it destroys sanitized characters 
  1212. $_attribute_value = isset( $attribute['option'] ) ? sanitize_title( stripslashes( $attribute['option'] ) ) : ''; 
  1213. } else { 
  1214. $_attribute_value = isset( $attribute['option'] ) ? wc_clean( stripslashes( $attribute['option'] ) ) : ''; 
  1215.  
  1216. $_attributes[ $_attribute_key ] = $_attribute_value; 
  1217.  
  1218. $variation->set_attributes( $_attributes ); 
  1219.  
  1220. $variation->save(); 
  1221.  
  1222. do_action( 'woocommerce_api_save_product_variation', $variation_id, $menu_order, $variation ); 
  1223.  
  1224. return true; 
  1225.  
  1226. /** 
  1227. * Save product shipping data 
  1228. * @since 2.2 
  1229. * @param WC_Product $product 
  1230. * @param array $data 
  1231. * @return WC_Product 
  1232. */ 
  1233. private function save_product_shipping_data( $product, $data ) { 
  1234. if ( isset( $data['weight'] ) ) { 
  1235. $product->set_weight( '' === $data['weight'] ? '' : wc_format_decimal( $data['weight'] ) ); 
  1236.  
  1237. // Product dimensions 
  1238. if ( isset( $data['dimensions'] ) ) { 
  1239. // Height 
  1240. if ( isset( $data['dimensions']['height'] ) ) { 
  1241. $product->set_height( '' === $data['dimensions']['height'] ? '' : wc_format_decimal( $data['dimensions']['height'] ) ); 
  1242.  
  1243. // Width 
  1244. if ( isset( $data['dimensions']['width'] ) ) { 
  1245. $product->set_width( '' === $data['dimensions']['width'] ? '' : wc_format_decimal( $data['dimensions']['width'] ) ); 
  1246.  
  1247. // Length 
  1248. if ( isset( $data['dimensions']['length'] ) ) { 
  1249. $product->set_length( '' === $data['dimensions']['length'] ? '' : wc_format_decimal( $data['dimensions']['length'] ) ); 
  1250.  
  1251. // Virtual 
  1252. if ( isset( $data['virtual'] ) ) { 
  1253. $virtual = ( true === $data['virtual'] ) ? 'yes' : 'no'; 
  1254.  
  1255. if ( 'yes' == $virtual ) { 
  1256. $product->set_weight( '' ); 
  1257. $product->set_height( '' ); 
  1258. $product->set_length( '' ); 
  1259. $product->set_width( '' ); 
  1260.  
  1261. // Shipping class 
  1262. if ( isset( $data['shipping_class'] ) ) { 
  1263. $data_store = $product->get_data_store(); 
  1264. $shipping_class_id = $data_store->get_shipping_class_id_by_slug( wc_clean( $data['shipping_class'] ) ); 
  1265. if ( $shipping_class_id ) { 
  1266. $product->set_shipping_class_id( $shipping_class_id ); 
  1267.  
  1268. return $product; 
  1269.  
  1270. /** 
  1271. * Save downloadable files 
  1272. * @since 2.2 
  1273. * @param WC_Product $product 
  1274. * @param array $downloads 
  1275. * @param int $deprecated Deprecated since 3.0. 
  1276. * @return WC_Product 
  1277. */ 
  1278. private function save_downloadable_files( $product, $downloads, $deprecated = 0 ) { 
  1279. if ( $deprecated ) { 
  1280. wc_deprecated_argument( 'variation_id', '3.0', 'save_downloadable_files() does not require a variation_id anymore.' ); 
  1281.  
  1282. $files = array(); 
  1283. foreach ( $downloads as $key => $file ) { 
  1284. if ( isset( $file['url'] ) ) { 
  1285. $file['file'] = $file['url']; 
  1286.  
  1287. if ( empty( $file['file'] ) ) { 
  1288. continue; 
  1289.  
  1290. $download = new WC_Product_Download(); 
  1291. $download->set_id( $key ); 
  1292. $download->set_name( $file['name'] ? $file['name'] : wc_get_filename_from_url( $file['file'] ) ); 
  1293. $download->set_file( apply_filters( 'woocommerce_file_download_path', $file['file'], $product, $key ) ); 
  1294. $files[] = $download; 
  1295. $product->set_downloads( $files ); 
  1296.  
  1297. return $product; 
  1298.  
  1299. /** 
  1300. * Get attribute taxonomy by slug. 
  1301. * @since 2.2 
  1302. * @param string $slug 
  1303. * @return string|null 
  1304. */ 
  1305. private function get_attribute_taxonomy_by_slug( $slug ) { 
  1306. $taxonomy = null; 
  1307. $attribute_taxonomies = wc_get_attribute_taxonomies(); 
  1308.  
  1309. foreach ( $attribute_taxonomies as $key => $tax ) { 
  1310. if ( $slug == $tax->attribute_name ) { 
  1311. $taxonomy = 'pa_' . $tax->attribute_name; 
  1312.  
  1313. break; 
  1314.  
  1315. return $taxonomy; 
  1316.  
  1317. /** 
  1318. * Get the images for a product or product variation 
  1319. * @since 2.1 
  1320. * @param WC_Product|WC_Product_Variation $product 
  1321. * @return array 
  1322. */ 
  1323. private function get_images( $product ) { 
  1324. $images = $attachment_ids = array(); 
  1325. $product_image = $product->get_image_id(); 
  1326.  
  1327. // Add featured image. 
  1328. if ( ! empty( $product_image ) ) { 
  1329. $attachment_ids[] = $product_image; 
  1330.  
  1331. // Add gallery images. 
  1332. $attachment_ids = array_merge( $attachment_ids, $product->get_gallery_image_ids() ); 
  1333.  
  1334. // Build image data. 
  1335. foreach ( $attachment_ids as $position => $attachment_id ) { 
  1336.  
  1337. $attachment_post = get_post( $attachment_id ); 
  1338.  
  1339. if ( is_null( $attachment_post ) ) { 
  1340. continue; 
  1341.  
  1342. $attachment = wp_get_attachment_image_src( $attachment_id, 'full' ); 
  1343.  
  1344. if ( ! is_array( $attachment ) ) { 
  1345. continue; 
  1346.  
  1347. $images[] = array( 
  1348. 'id' => (int) $attachment_id,  
  1349. 'created_at' => $this->server->format_datetime( $attachment_post->post_date_gmt ),  
  1350. 'updated_at' => $this->server->format_datetime( $attachment_post->post_modified_gmt ),  
  1351. 'src' => current( $attachment ),  
  1352. 'title' => get_the_title( $attachment_id ),  
  1353. 'alt' => get_post_meta( $attachment_id, '_wp_attachment_image_alt', true ),  
  1354. 'position' => (int) $position,  
  1355. ); 
  1356.  
  1357. // Set a placeholder image if the product has no images set. 
  1358. if ( empty( $images ) ) { 
  1359.  
  1360. $images[] = array( 
  1361. 'id' => 0,  
  1362. 'created_at' => $this->server->format_datetime( time() ), // Default to now. 
  1363. 'updated_at' => $this->server->format_datetime( time() ),  
  1364. 'src' => wc_placeholder_img_src(),  
  1365. 'title' => __( 'Placeholder', 'woocommerce' ),  
  1366. 'alt' => __( 'Placeholder', 'woocommerce' ),  
  1367. 'position' => 0,  
  1368. ); 
  1369.  
  1370. return $images; 
  1371.  
  1372. /** 
  1373. * Save product images 
  1374. * @since 2.2 
  1375. * @param WC_Product $product 
  1376. * @param array $images 
  1377. * @throws WC_API_Exception 
  1378. */ 
  1379. protected function save_product_images( $product, $images ) { 
  1380. if ( is_array( $images ) ) { 
  1381. $gallery = array(); 
  1382.  
  1383. foreach ( $images as $image ) { 
  1384. if ( isset( $image['position'] ) && 0 == $image['position'] ) { 
  1385. $attachment_id = isset( $image['id'] ) ? absint( $image['id'] ) : 0; 
  1386.  
  1387. if ( 0 === $attachment_id && isset( $image['src'] ) ) { 
  1388. $upload = $this->upload_product_image( esc_url_raw( $image['src'] ) ); 
  1389.  
  1390. if ( is_wp_error( $upload ) ) { 
  1391. throw new WC_API_Exception( 'woocommerce_api_cannot_upload_product_image', $upload->get_error_message(), 400 ); 
  1392.  
  1393. $attachment_id = $this->set_product_image_as_attachment( $upload, $product->get_id() ); 
  1394.  
  1395. $product->set_image_id( $attachment_id ); 
  1396. } else { 
  1397. $attachment_id = isset( $image['id'] ) ? absint( $image['id'] ) : 0; 
  1398.  
  1399. if ( 0 === $attachment_id && isset( $image['src'] ) ) { 
  1400. $upload = $this->upload_product_image( esc_url_raw( $image['src'] ) ); 
  1401.  
  1402. if ( is_wp_error( $upload ) ) { 
  1403. throw new WC_API_Exception( 'woocommerce_api_cannot_upload_product_image', $upload->get_error_message(), 400 ); 
  1404.  
  1405. $gallery[] = $this->set_product_image_as_attachment( $upload, $product->get_id() ); 
  1406. } else { 
  1407. $gallery[] = $attachment_id; 
  1408.  
  1409. if ( ! empty( $gallery ) ) { 
  1410. $product->set_gallery_image_ids( $gallery ); 
  1411. } else { 
  1412. $product->set_image_id( '' ); 
  1413. $product->set_gallery_image_ids( array() ); 
  1414.  
  1415. return $product; 
  1416.  
  1417. /** 
  1418. * Upload image from URL 
  1419. * @since 2.2 
  1420. * @param string $image_url 
  1421. * @return int|WP_Error attachment id 
  1422. * @throws WC_API_Exception 
  1423. */ 
  1424. public function upload_product_image( $image_url ) { 
  1425. $file_name = basename( current( explode( '?', $image_url ) ) ); 
  1426. $parsed_url = @parse_url( $image_url ); 
  1427.  
  1428. // Check parsed URL 
  1429. if ( ! $parsed_url || ! is_array( $parsed_url ) ) { 
  1430. throw new WC_API_Exception( 'woocommerce_api_invalid_product_image', sprintf( __( 'Invalid URL %s.', 'woocommerce' ), $image_url ), 400 ); 
  1431.  
  1432. // Ensure url is valid 
  1433. $image_url = str_replace( ' ', '%20', $image_url ); 
  1434.  
  1435. // Get the file 
  1436. $response = wp_safe_remote_get( $image_url, array( 
  1437. 'timeout' => 10,  
  1438. ) ); 
  1439.  
  1440. if ( is_wp_error( $response ) ) { 
  1441. throw new WC_API_Exception( 'woocommerce_api_invalid_remote_product_image', sprintf( __( 'Error getting remote image %s.', 'woocommerce' ), $image_url ) . ' ' . sprintf( __( 'Error: %s.', 'woocommerce' ), $response->get_error_message() ), 400 ); 
  1442. } elseif ( 200 !== wp_remote_retrieve_response_code( $response ) ) { 
  1443. throw new WC_API_Exception( 'woocommerce_api_invalid_remote_product_image', sprintf( __( 'Error getting remote image %s.', 'woocommerce' ), $image_url ), 400 ); 
  1444.  
  1445. // Ensure we have a file name and type 
  1446. $wp_filetype = wp_check_filetype( $file_name, wc_rest_allowed_image_mime_types() ); 
  1447.  
  1448. if ( ! $wp_filetype['type'] ) { 
  1449. $headers = wp_remote_retrieve_headers( $response ); 
  1450. if ( isset( $headers['content-disposition'] ) && strstr( $headers['content-disposition'], 'filename=' ) ) { 
  1451. $disposition = end( explode( 'filename=', $headers['content-disposition'] ) ); 
  1452. $disposition = sanitize_file_name( $disposition ); 
  1453. $file_name = $disposition; 
  1454. } elseif ( isset( $headers['content-type'] ) && strstr( $headers['content-type'], 'image/' ) ) { 
  1455. $file_name = 'image.' . str_replace( 'image/', '', $headers['content-type'] ); 
  1456. unset( $headers ); 
  1457.  
  1458. // Recheck filetype 
  1459. $wp_filetype = wp_check_filetype( $file_name, wc_rest_allowed_image_mime_types() ); 
  1460.  
  1461. if ( ! $wp_filetype['type'] ) { 
  1462. throw new WC_API_Exception( 'woocommerce_api_invalid_product_image', __( 'Invalid image type.', 'woocommerce' ), 400 ); 
  1463.  
  1464. // Upload the file 
  1465. $upload = wp_upload_bits( $file_name, '', wp_remote_retrieve_body( $response ) ); 
  1466.  
  1467. if ( $upload['error'] ) { 
  1468. throw new WC_API_Exception( 'woocommerce_api_product_image_upload_error', $upload['error'], 400 ); 
  1469.  
  1470. // Get filesize 
  1471. $filesize = filesize( $upload['file'] ); 
  1472.  
  1473. if ( 0 == $filesize ) { 
  1474. @unlink( $upload['file'] ); 
  1475. unset( $upload ); 
  1476. throw new WC_API_Exception( 'woocommerce_api_product_image_upload_file_error', __( 'Zero size file downloaded.', 'woocommerce' ), 400 ); 
  1477.  
  1478. unset( $response ); 
  1479.  
  1480. return $upload; 
  1481.  
  1482. /** 
  1483. * Sets product image as attachment and returns the attachment ID. 
  1484. * @since 2.2 
  1485. * @param array $upload 
  1486. * @param int $id 
  1487. * @return int 
  1488. */ 
  1489. protected function set_product_image_as_attachment( $upload, $id ) { 
  1490. $info = wp_check_filetype( $upload['file'] ); 
  1491. $title = ''; 
  1492. $content = ''; 
  1493.  
  1494. if ( $image_meta = @wp_read_image_metadata( $upload['file'] ) ) { 
  1495. if ( trim( $image_meta['title'] ) && ! is_numeric( sanitize_title( $image_meta['title'] ) ) ) { 
  1496. $title = wc_clean( $image_meta['title'] ); 
  1497. if ( trim( $image_meta['caption'] ) ) { 
  1498. $content = wc_clean( $image_meta['caption'] ); 
  1499.  
  1500. $attachment = array( 
  1501. 'post_mime_type' => $info['type'],  
  1502. 'guid' => $upload['url'],  
  1503. 'post_parent' => $id,  
  1504. 'post_title' => $title,  
  1505. 'post_content' => $content,  
  1506. ); 
  1507.  
  1508. $attachment_id = wp_insert_attachment( $attachment, $upload['file'], $id ); 
  1509. if ( ! is_wp_error( $attachment_id ) ) { 
  1510. wp_update_attachment_metadata( $attachment_id, wp_generate_attachment_metadata( $attachment_id, $upload['file'] ) ); 
  1511.  
  1512. return $attachment_id; 
  1513.  
  1514. /** 
  1515. * Get attribute options. 
  1516. * @param int $product_id 
  1517. * @param array $attribute 
  1518. * @return array 
  1519. */ 
  1520. protected function get_attribute_options( $product_id, $attribute ) { 
  1521. if ( isset( $attribute['is_taxonomy'] ) && $attribute['is_taxonomy'] ) { 
  1522. return wc_get_product_terms( $product_id, $attribute['name'], array( 'fields' => 'names' ) ); 
  1523. } elseif ( isset( $attribute['value'] ) ) { 
  1524. return array_map( 'trim', explode( '|', $attribute['value'] ) ); 
  1525.  
  1526. return array(); 
  1527.  
  1528. /** 
  1529. * Get the attributes for a product or product variation 
  1530. * @since 2.1 
  1531. * @param WC_Product|WC_Product_Variation $product 
  1532. * @return array 
  1533. */ 
  1534. private function get_attributes( $product ) { 
  1535.  
  1536. $attributes = array(); 
  1537.  
  1538. if ( $product->is_type( 'variation' ) ) { 
  1539.  
  1540. // variation attributes 
  1541. foreach ( $product->get_variation_attributes() as $attribute_name => $attribute ) { 
  1542.  
  1543. // taxonomy-based attributes are prefixed with `pa_`, otherwise simply `attribute_` 
  1544. $attributes[] = array( 
  1545. 'name' => wc_attribute_label( str_replace( 'attribute_', '', $attribute_name ) ),  
  1546. 'slug' => str_replace( 'attribute_', '', str_replace( 'pa_', '', $attribute_name ) ),  
  1547. 'option' => $attribute,  
  1548. ); 
  1549. } else { 
  1550.  
  1551. foreach ( $product->get_attributes() as $attribute ) { 
  1552. $attributes[] = array( 
  1553. 'name' => wc_attribute_label( $attribute['name'] ),  
  1554. 'slug' => str_replace( 'pa_', '', $attribute['name'] ),  
  1555. 'position' => (int) $attribute['position'],  
  1556. 'visible' => (bool) $attribute['is_visible'],  
  1557. 'variation' => (bool) $attribute['is_variation'],  
  1558. 'options' => $this->get_attribute_options( $product->get_id(), $attribute ),  
  1559. ); 
  1560.  
  1561. return $attributes; 
  1562.  
  1563. /** 
  1564. * Get the downloads for a product or product variation 
  1565. * @since 2.1 
  1566. * @param WC_Product|WC_Product_Variation $product 
  1567. * @return array 
  1568. */ 
  1569. private function get_downloads( $product ) { 
  1570.  
  1571. $downloads = array(); 
  1572.  
  1573. if ( $product->is_downloadable() ) { 
  1574.  
  1575. foreach ( $product->get_downloads() as $file_id => $file ) { 
  1576.  
  1577. $downloads[] = array( 
  1578. 'id' => $file_id, // do not cast as int as this is a hash 
  1579. 'name' => $file['name'],  
  1580. 'file' => $file['file'],  
  1581. ); 
  1582.  
  1583. return $downloads; 
  1584.  
  1585. /** 
  1586. * Get a listing of product attributes 
  1587. * @since 2.4.0 
  1588. * @param string|null $fields fields to limit response to 
  1589. * @return array 
  1590. */ 
  1591. public function get_product_attributes( $fields = null ) { 
  1592. try { 
  1593. // Permissions check 
  1594. if ( ! current_user_can( 'manage_product_terms' ) ) { 
  1595. throw new WC_API_Exception( 'woocommerce_api_user_cannot_read_product_attributes', __( 'You do not have permission to read product attributes', 'woocommerce' ), 401 ); 
  1596.  
  1597. $product_attributes = array(); 
  1598. $attribute_taxonomies = wc_get_attribute_taxonomies(); 
  1599.  
  1600. foreach ( $attribute_taxonomies as $attribute ) { 
  1601. $product_attributes[] = array( 
  1602. 'id' => intval( $attribute->attribute_id ),  
  1603. 'name' => $attribute->attribute_label,  
  1604. 'slug' => wc_attribute_taxonomy_name( $attribute->attribute_name ),  
  1605. 'type' => $attribute->attribute_type,  
  1606. 'order_by' => $attribute->attribute_orderby,  
  1607. 'has_archives' => (bool) $attribute->attribute_public,  
  1608. ); 
  1609.  
  1610. return array( 'product_attributes' => apply_filters( 'woocommerce_api_product_attributes_response', $product_attributes, $attribute_taxonomies, $fields, $this ) ); 
  1611. } catch ( WC_API_Exception $e ) { 
  1612. return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) ); 
  1613.  
  1614. /** 
  1615. * Get the product attribute for the given ID 
  1616. * @since 2.4.0 
  1617. * @param string $id product attribute term ID 
  1618. * @param string|null $fields fields to limit response to 
  1619. * @return array 
  1620. */ 
  1621. public function get_product_attribute( $id, $fields = null ) { 
  1622. global $wpdb; 
  1623.  
  1624. try { 
  1625. $id = absint( $id ); 
  1626.  
  1627. // Validate ID 
  1628. if ( empty( $id ) ) { 
  1629. throw new WC_API_Exception( 'woocommerce_api_invalid_product_attribute_id', __( 'Invalid product attribute ID', 'woocommerce' ), 400 ); 
  1630.  
  1631. // Permissions check 
  1632. if ( ! current_user_can( 'manage_product_terms' ) ) { 
  1633. throw new WC_API_Exception( 'woocommerce_api_user_cannot_read_product_categories', __( 'You do not have permission to read product attributes', 'woocommerce' ), 401 ); 
  1634.  
  1635. $attribute = $wpdb->get_row( $wpdb->prepare( " 
  1636. SELECT * 
  1637. FROM {$wpdb->prefix}woocommerce_attribute_taxonomies 
  1638. WHERE attribute_id = %d 
  1639. ", $id ) ); 
  1640.  
  1641. if ( is_wp_error( $attribute ) || is_null( $attribute ) ) { 
  1642. throw new WC_API_Exception( 'woocommerce_api_invalid_product_attribute_id', __( 'A product attribute with the provided ID could not be found', 'woocommerce' ), 404 ); 
  1643.  
  1644. $product_attribute = array( 
  1645. 'id' => intval( $attribute->attribute_id ),  
  1646. 'name' => $attribute->attribute_label,  
  1647. 'slug' => wc_attribute_taxonomy_name( $attribute->attribute_name ),  
  1648. 'type' => $attribute->attribute_type,  
  1649. 'order_by' => $attribute->attribute_orderby,  
  1650. 'has_archives' => (bool) $attribute->attribute_public,  
  1651. ); 
  1652.  
  1653. return array( 'product_attribute' => apply_filters( 'woocommerce_api_product_attribute_response', $product_attribute, $id, $fields, $attribute, $this ) ); 
  1654. } catch ( WC_API_Exception $e ) { 
  1655. return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) ); 
  1656.  
  1657. /** 
  1658. * Validate attribute data. 
  1659. * @since 2.4.0 
  1660. * @param string $name 
  1661. * @param string $slug 
  1662. * @param string $type 
  1663. * @param string $order_by 
  1664. * @param bool $new_data 
  1665. * @return bool 
  1666. * @throws WC_API_Exception 
  1667. */ 
  1668. protected function validate_attribute_data( $name, $slug, $type, $order_by, $new_data = true ) { 
  1669. if ( empty( $name ) ) { 
  1670. throw new WC_API_Exception( 'woocommerce_api_missing_product_attribute_name', sprintf( __( 'Missing parameter %s', 'woocommerce' ), 'name' ), 400 ); 
  1671.  
  1672. if ( strlen( $slug ) >= 28 ) { 
  1673. throw new WC_API_Exception( 'woocommerce_api_invalid_product_attribute_slug_too_long', sprintf( __( 'Slug "%s" is too long (28 characters max). Shorten it, please.', 'woocommerce' ), $slug ), 400 ); 
  1674. } elseif ( wc_check_if_attribute_name_is_reserved( $slug ) ) { 
  1675. throw new WC_API_Exception( 'woocommerce_api_invalid_product_attribute_slug_reserved_name', sprintf( __( 'Slug "%s" is not allowed because it is a reserved term. Change it, please.', 'woocommerce' ), $slug ), 400 ); 
  1676. } elseif ( $new_data && taxonomy_exists( wc_attribute_taxonomy_name( $slug ) ) ) { 
  1677. throw new WC_API_Exception( 'woocommerce_api_invalid_product_attribute_slug_already_exists', sprintf( __( 'Slug "%s" is already in use. Change it, please.', 'woocommerce' ), $slug ), 400 ); 
  1678.  
  1679. // Validate the attribute type 
  1680. if ( ! in_array( wc_clean( $type ), array_keys( wc_get_attribute_types() ) ) ) { 
  1681. throw new WC_API_Exception( 'woocommerce_api_invalid_product_attribute_type', sprintf( __( 'Invalid product attribute type - the product attribute type must be any of these: %s', 'woocommerce' ), implode( ', ', array_keys( wc_get_attribute_types() ) ) ), 400 ); 
  1682.  
  1683. // Validate the attribute order by 
  1684. if ( ! in_array( wc_clean( $order_by ), array( 'menu_order', 'name', 'name_num', 'id' ) ) ) { 
  1685. throw new WC_API_Exception( 'woocommerce_api_invalid_product_attribute_order_by', sprintf( __( 'Invalid product attribute order_by type - the product attribute order_by type must be any of these: %s', 'woocommerce' ), implode( ', ', array( 'menu_order', 'name', 'name_num', 'id' ) ) ), 400 ); 
  1686.  
  1687. return true; 
  1688.  
  1689. /** 
  1690. * Create a new product attribute 
  1691. * @since 2.4.0 
  1692. * @param array $data posted data 
  1693. * @return array 
  1694. */ 
  1695. public function create_product_attribute( $data ) { 
  1696. global $wpdb; 
  1697.  
  1698. try { 
  1699. if ( ! isset( $data['product_attribute'] ) ) { 
  1700. throw new WC_API_Exception( 'woocommerce_api_missing_product_attribute_data', sprintf( __( 'No %1$s data specified to create %1$s', 'woocommerce' ), 'product_attribute' ), 400 ); 
  1701.  
  1702. $data = $data['product_attribute']; 
  1703.  
  1704. // Check permissions 
  1705. if ( ! current_user_can( 'manage_product_terms' ) ) { 
  1706. throw new WC_API_Exception( 'woocommerce_api_user_cannot_create_product_attribute', __( 'You do not have permission to create product attributes', 'woocommerce' ), 401 ); 
  1707.  
  1708. $data = apply_filters( 'woocommerce_api_create_product_attribute_data', $data, $this ); 
  1709.  
  1710. if ( ! isset( $data['name'] ) ) { 
  1711. $data['name'] = ''; 
  1712.  
  1713. // Set the attribute slug 
  1714. if ( ! isset( $data['slug'] ) ) { 
  1715. $data['slug'] = wc_sanitize_taxonomy_name( stripslashes( $data['name'] ) ); 
  1716. } else { 
  1717. $data['slug'] = preg_replace( '/^pa\_/', '', wc_sanitize_taxonomy_name( stripslashes( $data['slug'] ) ) ); 
  1718.  
  1719. // Set attribute type when not sent 
  1720. if ( ! isset( $data['type'] ) ) { 
  1721. $data['type'] = 'select'; 
  1722.  
  1723. // Set order by when not sent 
  1724. if ( ! isset( $data['order_by'] ) ) { 
  1725. $data['order_by'] = 'menu_order'; 
  1726.  
  1727. // Validate the attribute data 
  1728. $this->validate_attribute_data( $data['name'], $data['slug'], $data['type'], $data['order_by'], true ); 
  1729.  
  1730. $insert = $wpdb->insert( 
  1731. $wpdb->prefix . 'woocommerce_attribute_taxonomies',  
  1732. array( 
  1733. 'attribute_label' => $data['name'],  
  1734. 'attribute_name' => $data['slug'],  
  1735. 'attribute_type' => $data['type'],  
  1736. 'attribute_orderby' => $data['order_by'],  
  1737. 'attribute_public' => isset( $data['has_archives'] ) && true === $data['has_archives'] ? 1 : 0,  
  1738. ),  
  1739. array( '%s', '%s', '%s', '%s', '%d' ) 
  1740. ); 
  1741.  
  1742. // Checks for an error in the product creation 
  1743. if ( is_wp_error( $insert ) ) { 
  1744. throw new WC_API_Exception( 'woocommerce_api_cannot_create_product_attribute', $insert->get_error_message(), 400 ); 
  1745.  
  1746. $id = $wpdb->insert_id; 
  1747.  
  1748. do_action( 'woocommerce_api_create_product_attribute', $id, $data ); 
  1749.  
  1750. // Clear transients 
  1751. delete_transient( 'wc_attribute_taxonomies' ); 
  1752.  
  1753. $this->server->send_status( 201 ); 
  1754.  
  1755. return $this->get_product_attribute( $id ); 
  1756. } catch ( WC_API_Exception $e ) { 
  1757. return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) ); 
  1758.  
  1759. /** 
  1760. * Edit a product attribute 
  1761. * @since 2.4.0 
  1762. * @param int $id the attribute ID 
  1763. * @param array $data 
  1764. * @return array 
  1765. */ 
  1766. public function edit_product_attribute( $id, $data ) { 
  1767. global $wpdb; 
  1768.  
  1769. try { 
  1770. if ( ! isset( $data['product_attribute'] ) ) { 
  1771. throw new WC_API_Exception( 'woocommerce_api_missing_product_attribute_data', sprintf( __( 'No %1$s data specified to edit %1$s', 'woocommerce' ), 'product_attribute' ), 400 ); 
  1772.  
  1773. $id = absint( $id ); 
  1774. $data = $data['product_attribute']; 
  1775.  
  1776. // Check permissions 
  1777. if ( ! current_user_can( 'manage_product_terms' ) ) { 
  1778. throw new WC_API_Exception( 'woocommerce_api_user_cannot_edit_product_attribute', __( 'You do not have permission to edit product attributes', 'woocommerce' ), 401 ); 
  1779.  
  1780. $data = apply_filters( 'woocommerce_api_edit_product_attribute_data', $data, $this ); 
  1781. $attribute = $this->get_product_attribute( $id ); 
  1782.  
  1783. if ( is_wp_error( $attribute ) ) { 
  1784. return $attribute; 
  1785.  
  1786. $attribute_name = isset( $data['name'] ) ? $data['name'] : $attribute['product_attribute']['name']; 
  1787. $attribute_type = isset( $data['type'] ) ? $data['type'] : $attribute['product_attribute']['type']; 
  1788. $attribute_order_by = isset( $data['order_by'] ) ? $data['order_by'] : $attribute['product_attribute']['order_by']; 
  1789.  
  1790. if ( isset( $data['slug'] ) ) { 
  1791. $attribute_slug = wc_sanitize_taxonomy_name( stripslashes( $data['slug'] ) ); 
  1792. } else { 
  1793. $attribute_slug = $attribute['product_attribute']['slug']; 
  1794. $attribute_slug = preg_replace( '/^pa\_/', '', $attribute_slug ); 
  1795.  
  1796. if ( isset( $data['has_archives'] ) ) { 
  1797. $attribute_public = true === $data['has_archives'] ? 1 : 0; 
  1798. } else { 
  1799. $attribute_public = $attribute['product_attribute']['has_archives']; 
  1800.  
  1801. // Validate the attribute data 
  1802. $this->validate_attribute_data( $attribute_name, $attribute_slug, $attribute_type, $attribute_order_by, false ); 
  1803.  
  1804. $update = $wpdb->update( 
  1805. $wpdb->prefix . 'woocommerce_attribute_taxonomies',  
  1806. array( 
  1807. 'attribute_label' => $attribute_name,  
  1808. 'attribute_name' => $attribute_slug,  
  1809. 'attribute_type' => $attribute_type,  
  1810. 'attribute_orderby' => $attribute_order_by,  
  1811. 'attribute_public' => $attribute_public,  
  1812. ),  
  1813. array( 'attribute_id' => $id ),  
  1814. array( '%s', '%s', '%s', '%s', '%d' ),  
  1815. array( '%d' ) 
  1816. ); 
  1817.  
  1818. // Checks for an error in the product creation 
  1819. if ( false === $update ) { 
  1820. throw new WC_API_Exception( 'woocommerce_api_cannot_edit_product_attribute', __( 'Could not edit the attribute', 'woocommerce' ), 400 ); 
  1821.  
  1822. do_action( 'woocommerce_api_edit_product_attribute', $id, $data ); 
  1823.  
  1824. // Clear transients 
  1825. delete_transient( 'wc_attribute_taxonomies' ); 
  1826.  
  1827. return $this->get_product_attribute( $id ); 
  1828. } catch ( WC_API_Exception $e ) { 
  1829. return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) ); 
  1830.  
  1831. /** 
  1832. * Delete a product attribute 
  1833. * @since 2.4.0 
  1834. * @param int $id the product attribute ID 
  1835. * @return array 
  1836. */ 
  1837. public function delete_product_attribute( $id ) { 
  1838. global $wpdb; 
  1839.  
  1840. try { 
  1841. // Check permissions 
  1842. if ( ! current_user_can( 'manage_product_terms' ) ) { 
  1843. throw new WC_API_Exception( 'woocommerce_api_user_cannot_delete_product_attribute', __( 'You do not have permission to delete product attributes', 'woocommerce' ), 401 ); 
  1844.  
  1845. $id = absint( $id ); 
  1846.  
  1847. $attribute_name = $wpdb->get_var( $wpdb->prepare( " 
  1848. SELECT attribute_name 
  1849. FROM {$wpdb->prefix}woocommerce_attribute_taxonomies 
  1850. WHERE attribute_id = %d 
  1851. ", $id ) ); 
  1852.  
  1853. if ( is_null( $attribute_name ) ) { 
  1854. throw new WC_API_Exception( 'woocommerce_api_invalid_product_attribute_id', __( 'A product attribute with the provided ID could not be found', 'woocommerce' ), 404 ); 
  1855.  
  1856. $deleted = $wpdb->delete( 
  1857. $wpdb->prefix . 'woocommerce_attribute_taxonomies',  
  1858. array( 'attribute_id' => $id ),  
  1859. array( '%d' ) 
  1860. ); 
  1861.  
  1862. if ( false === $deleted ) { 
  1863. throw new WC_API_Exception( 'woocommerce_api_cannot_delete_product_attribute', __( 'Could not delete the attribute', 'woocommerce' ), 401 ); 
  1864.  
  1865. $taxonomy = wc_attribute_taxonomy_name( $attribute_name ); 
  1866.  
  1867. if ( taxonomy_exists( $taxonomy ) ) { 
  1868. $terms = get_terms( $taxonomy, 'orderby=name&hide_empty=0' ); 
  1869. foreach ( $terms as $term ) { 
  1870. wp_delete_term( $term->term_id, $taxonomy ); 
  1871.  
  1872. do_action( 'woocommerce_attribute_deleted', $id, $attribute_name, $taxonomy ); 
  1873. do_action( 'woocommerce_api_delete_product_attribute', $id, $this ); 
  1874.  
  1875. // Clear transients 
  1876. delete_transient( 'wc_attribute_taxonomies' ); 
  1877.  
  1878. return array( 'message' => sprintf( __( 'Deleted %s', 'woocommerce' ), 'product_attribute' ) ); 
  1879. } catch ( WC_API_Exception $e ) { 
  1880. return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) ); 
  1881.  
  1882. /** 
  1883. * Get product by SKU 
  1884. * @deprecated 2.4.0 
  1885. * @since 2.3.0 
  1886. * @param int $sku the product SKU 
  1887. * @param string $fields 
  1888. * @return array 
  1889. */ 
  1890. public function get_product_by_sku( $sku, $fields = null ) { 
  1891. try { 
  1892. $id = wc_get_product_id_by_sku( $sku ); 
  1893.  
  1894. if ( empty( $id ) ) { 
  1895. throw new WC_API_Exception( 'woocommerce_api_invalid_product_sku', __( 'Invalid product SKU', 'woocommerce' ), 404 ); 
  1896.  
  1897. return $this->get_product( $id, $fields ); 
  1898. } catch ( WC_API_Exception $e ) { 
  1899. return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) ); 
  1900.  
  1901. /** 
  1902. * Clear product 
  1903. */ 
  1904. protected function clear_product( $product_id ) { 
  1905. if ( ! is_numeric( $product_id ) || 0 >= $product_id ) { 
  1906. return; 
  1907.  
  1908. // Delete product attachments 
  1909. $attachments = get_children( array( 
  1910. 'post_parent' => $product_id,  
  1911. 'post_status' => 'any',  
  1912. 'post_type' => 'attachment',  
  1913. ) ); 
  1914.  
  1915. foreach ( (array) $attachments as $attachment ) { 
  1916. wp_delete_attachment( $attachment->ID, true ); 
  1917.  
  1918. // Delete product 
  1919. $product = wc_get_product( $product_id ); 
  1920. $product->delete(); 
  1921.  
  1922. /** 
  1923. * Bulk update or insert products 
  1924. * Accepts an array with products in the formats supported by 
  1925. * WC_API_Products->create_product() and WC_API_Products->edit_product() 
  1926. * @since 2.4.0 
  1927. * @param array $data 
  1928. * @return array 
  1929. */ 
  1930. public function bulk( $data ) { 
  1931.  
  1932. try { 
  1933. if ( ! isset( $data['products'] ) ) { 
  1934. throw new WC_API_Exception( 'woocommerce_api_missing_products_data', sprintf( __( 'No %1$s data specified to create/edit %1$s', 'woocommerce' ), 'products' ), 400 ); 
  1935.  
  1936. $data = $data['products']; 
  1937. $limit = apply_filters( 'woocommerce_api_bulk_limit', 100, 'products' ); 
  1938.  
  1939. // Limit bulk operation 
  1940. if ( count( $data ) > $limit ) { 
  1941. throw new WC_API_Exception( 'woocommerce_api_products_request_entity_too_large', sprintf( __( 'Unable to accept more than %s items for this request.', 'woocommerce' ), $limit ), 413 ); 
  1942.  
  1943. $products = array(); 
  1944.  
  1945. foreach ( $data as $_product ) { 
  1946. $product_id = 0; 
  1947. $product_sku = ''; 
  1948.  
  1949. // Try to get the product ID 
  1950. if ( isset( $_product['id'] ) ) { 
  1951. $product_id = intval( $_product['id'] ); 
  1952.  
  1953. if ( ! $product_id && isset( $_product['sku'] ) ) { 
  1954. $product_sku = wc_clean( $_product['sku'] ); 
  1955. $product_id = wc_get_product_id_by_sku( $product_sku ); 
  1956.  
  1957. if ( $product_id ) { 
  1958.  
  1959. // Product exists / edit product 
  1960. $edit = $this->edit_product( $product_id, array( 'product' => $_product ) ); 
  1961.  
  1962. if ( is_wp_error( $edit ) ) { 
  1963. $products[] = array( 
  1964. 'id' => $product_id,  
  1965. 'sku' => $product_sku,  
  1966. 'error' => array( 'code' => $edit->get_error_code(), 'message' => $edit->get_error_message() ),  
  1967. ); 
  1968. } else { 
  1969. $products[] = $edit['product']; 
  1970. } else { 
  1971.  
  1972. // Product don't exists / create product 
  1973. $new = $this->create_product( array( 'product' => $_product ) ); 
  1974.  
  1975. if ( is_wp_error( $new ) ) { 
  1976. $products[] = array( 
  1977. 'id' => $product_id,  
  1978. 'sku' => $product_sku,  
  1979. 'error' => array( 'code' => $new->get_error_code(), 'message' => $new->get_error_message() ),  
  1980. ); 
  1981. } else { 
  1982. $products[] = $new['product']; 
  1983.  
  1984. return array( 'products' => apply_filters( 'woocommerce_api_products_bulk_response', $products, $this ) ); 
  1985. } catch ( WC_API_Exception $e ) { 
  1986. return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) ); 
/includes/api/legacy/v3/class-wc-api-products.php  
  1. class WC_API_Products extends WC_API_Resource { 
  2.  
  3. /** @var string $base the route base */ 
  4. protected $base = '/products'; 
  5.  
  6. /** 
  7. * Register the routes for this class 
  8. * GET/POST /products 
  9. * GET /products/count 
  10. * GET/PUT/DELETE /products/<id> 
  11. * GET /products/<id>/reviews 
  12. * @since 2.1 
  13. * @param array $routes 
  14. * @return array 
  15. */ 
  16. public function register_routes( $routes ) { 
  17.  
  18. # GET/POST /products 
  19. $routes[ $this->base ] = array( 
  20. array( array( $this, 'get_products' ), WC_API_Server::READABLE ),  
  21. array( array( $this, 'create_product' ), WC_API_SERVER::CREATABLE | WC_API_Server::ACCEPT_DATA ),  
  22. ); 
  23.  
  24. # GET /products/count 
  25. $routes[ $this->base . '/count' ] = array( 
  26. array( array( $this, 'get_products_count' ), WC_API_Server::READABLE ),  
  27. ); 
  28.  
  29. # GET/PUT/DELETE /products/<id> 
  30. $routes[ $this->base . '/(?P<id>\d+)' ] = array( 
  31. array( array( $this, 'get_product' ), WC_API_Server::READABLE ),  
  32. array( array( $this, 'edit_product' ), WC_API_Server::EDITABLE | WC_API_Server::ACCEPT_DATA ),  
  33. array( array( $this, 'delete_product' ), WC_API_Server::DELETABLE ),  
  34. ); 
  35.  
  36. # GET /products/<id>/reviews 
  37. $routes[ $this->base . '/(?P<id>\d+)/reviews' ] = array( 
  38. array( array( $this, 'get_product_reviews' ), WC_API_Server::READABLE ),  
  39. ); 
  40.  
  41. # GET /products/<id>/orders 
  42. $routes[ $this->base . '/(?P<id>\d+)/orders' ] = array( 
  43. array( array( $this, 'get_product_orders' ), WC_API_Server::READABLE ),  
  44. ); 
  45.  
  46. # GET/POST /products/categories 
  47. $routes[ $this->base . '/categories' ] = array( 
  48. array( array( $this, 'get_product_categories' ), WC_API_Server::READABLE ),  
  49. array( array( $this, 'create_product_category' ), WC_API_Server::CREATABLE | WC_API_Server::ACCEPT_DATA ),  
  50. ); 
  51.  
  52. # GET/PUT/DELETE /products/categories/<id> 
  53. $routes[ $this->base . '/categories/(?P<id>\d+)' ] = array( 
  54. array( array( $this, 'get_product_category' ), WC_API_Server::READABLE ),  
  55. array( array( $this, 'edit_product_category' ), WC_API_Server::EDITABLE | WC_API_Server::ACCEPT_DATA ),  
  56. array( array( $this, 'delete_product_category' ), WC_API_Server::DELETABLE ),  
  57. ); 
  58.  
  59. # GET/POST /products/tags 
  60. $routes[ $this->base . '/tags' ] = array( 
  61. array( array( $this, 'get_product_tags' ), WC_API_Server::READABLE ),  
  62. array( array( $this, 'create_product_tag' ), WC_API_Server::CREATABLE | WC_API_Server::ACCEPT_DATA ),  
  63. ); 
  64.  
  65. # GET/PUT/DELETE /products/tags/<id> 
  66. $routes[ $this->base . '/tags/(?P<id>\d+)' ] = array( 
  67. array( array( $this, 'get_product_tag' ), WC_API_Server::READABLE ),  
  68. array( array( $this, 'edit_product_tag' ), WC_API_Server::EDITABLE | WC_API_Server::ACCEPT_DATA ),  
  69. array( array( $this, 'delete_product_tag' ), WC_API_Server::DELETABLE ),  
  70. ); 
  71.  
  72. # GET/POST /products/shipping_classes 
  73. $routes[ $this->base . '/shipping_classes' ] = array( 
  74. array( array( $this, 'get_product_shipping_classes' ), WC_API_Server::READABLE ),  
  75. array( array( $this, 'create_product_shipping_class' ), WC_API_Server::CREATABLE | WC_API_Server::ACCEPT_DATA ),  
  76. ); 
  77.  
  78. # GET/PUT/DELETE /products/shipping_classes/<id> 
  79. $routes[ $this->base . '/shipping_classes/(?P<id>\d+)' ] = array( 
  80. array( array( $this, 'get_product_shipping_class' ), WC_API_Server::READABLE ),  
  81. array( array( $this, 'edit_product_shipping_class' ), WC_API_Server::EDITABLE | WC_API_Server::ACCEPT_DATA ),  
  82. array( array( $this, 'delete_product_shipping_class' ), WC_API_Server::DELETABLE ),  
  83. ); 
  84.  
  85. # GET/POST /products/attributes 
  86. $routes[ $this->base . '/attributes' ] = array( 
  87. array( array( $this, 'get_product_attributes' ), WC_API_Server::READABLE ),  
  88. array( array( $this, 'create_product_attribute' ), WC_API_SERVER::CREATABLE | WC_API_Server::ACCEPT_DATA ),  
  89. ); 
  90.  
  91. # GET/PUT/DELETE /products/attributes/<id> 
  92. $routes[ $this->base . '/attributes/(?P<id>\d+)' ] = array( 
  93. array( array( $this, 'get_product_attribute' ), WC_API_Server::READABLE ),  
  94. array( array( $this, 'edit_product_attribute' ), WC_API_Server::EDITABLE | WC_API_Server::ACCEPT_DATA ),  
  95. array( array( $this, 'delete_product_attribute' ), WC_API_Server::DELETABLE ),  
  96. ); 
  97.  
  98. # GET/POST /products/attributes/<attribute_id>/terms 
  99. $routes[ $this->base . '/attributes/(?P<attribute_id>\d+)/terms' ] = array( 
  100. array( array( $this, 'get_product_attribute_terms' ), WC_API_Server::READABLE ),  
  101. array( array( $this, 'create_product_attribute_term' ), WC_API_SERVER::CREATABLE | WC_API_Server::ACCEPT_DATA ),  
  102. ); 
  103.  
  104. # GET/PUT/DELETE /products/attributes/<attribute_id>/terms/<id> 
  105. $routes[ $this->base . '/attributes/(?P<attribute_id>\d+)/terms/(?P<id>\d+)' ] = array( 
  106. array( array( $this, 'get_product_attribute_term' ), WC_API_Server::READABLE ),  
  107. array( array( $this, 'edit_product_attribute_term' ), WC_API_Server::EDITABLE | WC_API_Server::ACCEPT_DATA ),  
  108. array( array( $this, 'delete_product_attribute_term' ), WC_API_Server::DELETABLE ),  
  109. ); 
  110.  
  111. # POST|PUT /products/bulk 
  112. $routes[ $this->base . '/bulk' ] = array( 
  113. array( array( $this, 'bulk' ), WC_API_Server::EDITABLE | WC_API_Server::ACCEPT_DATA ),  
  114. ); 
  115.  
  116. return $routes; 
  117.  
  118. /** 
  119. * Get all products 
  120. * @since 2.1 
  121. * @param string $fields 
  122. * @param string $type 
  123. * @param array $filter 
  124. * @param int $page 
  125. * @return array 
  126. */ 
  127. public function get_products( $fields = null, $type = null, $filter = array(), $page = 1 ) { 
  128.  
  129. if ( ! empty( $type ) ) { 
  130. $filter['type'] = $type; 
  131.  
  132. $filter['page'] = $page; 
  133.  
  134. $query = $this->query_products( $filter ); 
  135.  
  136. $products = array(); 
  137.  
  138. foreach ( $query->posts as $product_id ) { 
  139.  
  140. if ( ! $this->is_readable( $product_id ) ) { 
  141. continue; 
  142.  
  143. $products[] = current( $this->get_product( $product_id, $fields ) ); 
  144.  
  145. $this->server->add_pagination_headers( $query ); 
  146.  
  147. return array( 'products' => $products ); 
  148.  
  149. /** 
  150. * Get the product for the given ID 
  151. * @since 2.1 
  152. * @param int $id the product ID 
  153. * @param string $fields 
  154. * @return array 
  155. */ 
  156. public function get_product( $id, $fields = null ) { 
  157.  
  158. $id = $this->validate_request( $id, 'product', 'read' ); 
  159.  
  160. if ( is_wp_error( $id ) ) { 
  161. return $id; 
  162.  
  163. $product = wc_get_product( $id ); 
  164.  
  165. // add data that applies to every product type 
  166. $product_data = $this->get_product_data( $product ); 
  167.  
  168. // add variations to variable products 
  169. if ( $product->is_type( 'variable' ) && $product->has_child() ) { 
  170. $product_data['variations'] = $this->get_variation_data( $product ); 
  171.  
  172. // add the parent product data to an individual variation 
  173. if ( $product->is_type( 'variation' ) && $product->get_parent_id() ) { 
  174. $product_data['parent'] = $this->get_product_data( $product->get_parent_id() ); 
  175.  
  176. // Add grouped products data 
  177. if ( $product->is_type( 'grouped' ) && $product->has_child() ) { 
  178. $product_data['grouped_products'] = $this->get_grouped_products_data( $product ); 
  179.  
  180. if ( $product->is_type( 'simple' ) ) { 
  181. $parent_id = $product->get_parent_id(); 
  182. if ( ! empty( $parent_id ) ) { 
  183. $_product = wc_get_product( $parent_id ); 
  184. $product_data['parent'] = $this->get_product_data( $_product ); 
  185.  
  186. return array( 'product' => apply_filters( 'woocommerce_api_product_response', $product_data, $product, $fields, $this->server ) ); 
  187.  
  188. /** 
  189. * Get the total number of products 
  190. * @since 2.1 
  191. * @param string $type 
  192. * @param array $filter 
  193. * @return array 
  194. */ 
  195. public function get_products_count( $type = null, $filter = array() ) { 
  196. try { 
  197. if ( ! current_user_can( 'read_private_products' ) ) { 
  198. throw new WC_API_Exception( 'woocommerce_api_user_cannot_read_products_count', __( 'You do not have permission to read the products count', 'woocommerce' ), 401 ); 
  199.  
  200. if ( ! empty( $type ) ) { 
  201. $filter['type'] = $type; 
  202.  
  203. $query = $this->query_products( $filter ); 
  204.  
  205. return array( 'count' => (int) $query->found_posts ); 
  206. } catch ( WC_API_Exception $e ) { 
  207. return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) ); 
  208.  
  209. /** 
  210. * Create a new product. 
  211. * @since 2.2 
  212. * @param array $data posted data 
  213. * @return array 
  214. */ 
  215. public function create_product( $data ) { 
  216. $id = 0; 
  217.  
  218. try { 
  219. if ( ! isset( $data['product'] ) ) { 
  220. throw new WC_API_Exception( 'woocommerce_api_missing_product_data', sprintf( __( 'No %1$s data specified to create %1$s', 'woocommerce' ), 'product' ), 400 ); 
  221.  
  222. $data = $data['product']; 
  223.  
  224. // Check permissions. 
  225. if ( ! current_user_can( 'publish_products' ) ) { 
  226. throw new WC_API_Exception( 'woocommerce_api_user_cannot_create_product', __( 'You do not have permission to create products', 'woocommerce' ), 401 ); 
  227.  
  228. $data = apply_filters( 'woocommerce_api_create_product_data', $data, $this ); 
  229.  
  230. // Check if product title is specified. 
  231. if ( ! isset( $data['title'] ) ) { 
  232. throw new WC_API_Exception( 'woocommerce_api_missing_product_title', sprintf( __( 'Missing parameter %s', 'woocommerce' ), 'title' ), 400 ); 
  233.  
  234. // Check product type. 
  235. if ( ! isset( $data['type'] ) ) { 
  236. $data['type'] = 'simple'; 
  237.  
  238. // Set visible visibility when not sent. 
  239. if ( ! isset( $data['catalog_visibility'] ) ) { 
  240. $data['catalog_visibility'] = 'visible'; 
  241.  
  242. // Validate the product type. 
  243. if ( ! in_array( wc_clean( $data['type'] ), array_keys( wc_get_product_types() ) ) ) { 
  244. throw new WC_API_Exception( 'woocommerce_api_invalid_product_type', sprintf( __( 'Invalid product type - the product type must be any of these: %s', 'woocommerce' ), implode( ', ', array_keys( wc_get_product_types() ) ) ), 400 ); 
  245.  
  246. // Enable description html tags. 
  247. $post_content = isset( $data['description'] ) ? wc_clean( $data['description'] ) : ''; 
  248. if ( $post_content && isset( $data['enable_html_description'] ) && true === $data['enable_html_description'] ) { 
  249.  
  250. $post_content = $data['description']; 
  251.  
  252. // Enable short description html tags. 
  253. $post_excerpt = isset( $data['short_description'] ) ? wc_clean( $data['short_description'] ) : ''; 
  254. if ( $post_excerpt && isset( $data['enable_html_short_description'] ) && true === $data['enable_html_short_description'] ) { 
  255. $post_excerpt = $data['short_description']; 
  256.  
  257. $classname = WC_Product_Factory::get_classname_from_product_type( $data['type'] ); 
  258. if ( ! class_exists( $classname ) ) { 
  259. $classname = 'WC_Product_Simple'; 
  260. $product = new $classname(); 
  261.  
  262. $product->set_name( wc_clean( $data['title'] ) ); 
  263. $product->set_status( isset( $data['status'] ) ? wc_clean( $data['status'] ) : 'publish' ); 
  264. $product->set_short_description( isset( $data['short_description'] ) ? $post_excerpt : '' ); 
  265. $product->set_description( isset( $data['description'] ) ? $post_content : '' ); 
  266. $product->set_menu_order( isset( $data['menu_order'] ) ? intval( $data['menu_order'] ) : 0 ); 
  267.  
  268. if ( ! empty( $data['name'] ) ) { 
  269. $product->set_slug( sanitize_title( $data['name'] ) ); 
  270.  
  271. // Attempts to create the new product. 
  272. $product->save(); 
  273. $id = $product->get_id(); 
  274.  
  275. // Checks for an error in the product creation. 
  276. if ( 0 >= $id ) { 
  277. throw new WC_API_Exception( 'woocommerce_api_cannot_create_product', $id->get_error_message(), 400 ); 
  278.  
  279. // Check for featured/gallery images, upload it and set it. 
  280. if ( isset( $data['images'] ) ) { 
  281. $product = $this->save_product_images( $product, $data['images'] ); 
  282.  
  283. // Save product meta fields. 
  284. $product = $this->save_product_meta( $product, $data ); 
  285. $product->save(); 
  286.  
  287. // Save variations. 
  288. if ( isset( $data['type'] ) && 'variable' == $data['type'] && isset( $data['variations'] ) && is_array( $data['variations'] ) ) { 
  289. $this->save_variations( $product, $data ); 
  290.  
  291. do_action( 'woocommerce_api_create_product', $id, $data ); 
  292.  
  293. // Clear cache/transients. 
  294. wc_delete_product_transients( $id ); 
  295.  
  296. $this->server->send_status( 201 ); 
  297.  
  298. return $this->get_product( $id ); 
  299. } catch ( WC_Data_Exception $e ) { 
  300. $this->clear_product( $id ); 
  301. return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) ); 
  302. } catch ( WC_API_Exception $e ) { 
  303. $this->clear_product( $id ); 
  304. return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) ); 
  305.  
  306. /** 
  307. * Edit a product 
  308. * @since 2.2 
  309. * @param int $id the product ID 
  310. * @param array $data 
  311. * @return array 
  312. */ 
  313. public function edit_product( $id, $data ) { 
  314. try { 
  315. if ( ! isset( $data['product'] ) ) { 
  316. throw new WC_API_Exception( 'woocommerce_api_missing_product_data', sprintf( __( 'No %1$s data specified to edit %1$s', 'woocommerce' ), 'product' ), 400 ); 
  317.  
  318. $data = $data['product']; 
  319.  
  320. $id = $this->validate_request( $id, 'product', 'edit' ); 
  321.  
  322. if ( is_wp_error( $id ) ) { 
  323. return $id; 
  324.  
  325. $product = wc_get_product( $id ); 
  326.  
  327. $data = apply_filters( 'woocommerce_api_edit_product_data', $data, $this ); 
  328.  
  329. // Product title. 
  330. if ( isset( $data['title'] ) ) { 
  331. $product->set_name( wc_clean( $data['title'] ) ); 
  332.  
  333. // Product name (slug). 
  334. if ( isset( $data['name'] ) ) { 
  335. $product->set_slug( wc_clean( $data['name'] ) ); 
  336.  
  337. // Product status. 
  338. if ( isset( $data['status'] ) ) { 
  339. $product->set_status( wc_clean( $data['status'] ) ); 
  340.  
  341. // Product short description. 
  342. if ( isset( $data['short_description'] ) ) { 
  343. // Enable short description html tags. 
  344. $post_excerpt = ( isset( $data['enable_html_short_description'] ) && true === $data['enable_html_short_description'] ) ? $data['short_description'] : wc_clean( $data['short_description'] ); 
  345. $product->set_short_description( $post_excerpt ); 
  346.  
  347. // Product description. 
  348. if ( isset( $data['description'] ) ) { 
  349. // Enable description html tags. 
  350. $post_content = ( isset( $data['enable_html_description'] ) && true === $data['enable_html_description'] ) ? $data['description'] : wc_clean( $data['description'] ); 
  351. $product->set_description( $post_content ); 
  352.  
  353. // Validate the product type. 
  354. if ( isset( $data['type'] ) && ! in_array( wc_clean( $data['type'] ), array_keys( wc_get_product_types() ) ) ) { 
  355. throw new WC_API_Exception( 'woocommerce_api_invalid_product_type', sprintf( __( 'Invalid product type - the product type must be any of these: %s', 'woocommerce' ), implode( ', ', array_keys( wc_get_product_types() ) ) ), 400 ); 
  356.  
  357. // Menu order. 
  358. if ( isset( $data['menu_order'] ) ) { 
  359. $product->set_menu_order( intval( $data['menu_order'] ) ); 
  360.  
  361. // Check for featured/gallery images, upload it and set it. 
  362. if ( isset( $data['images'] ) ) { 
  363. $product = $this->save_product_images( $product, $data['images'] ); 
  364.  
  365. // Save product meta fields. 
  366. $product = $this->save_product_meta( $product, $data ); 
  367.  
  368. // Save variations. 
  369. if ( $product->is_type( 'variable' ) ) { 
  370. if ( isset( $data['variations'] ) && is_array( $data['variations'] ) ) { 
  371. $this->save_variations( $product, $data ); 
  372. } else { 
  373. // Just sync variations. 
  374. $product = WC_Product_Variable::sync( $product, false ); 
  375.  
  376. $product->save(); 
  377.  
  378. do_action( 'woocommerce_api_edit_product', $id, $data ); 
  379.  
  380. // Clear cache/transients. 
  381. wc_delete_product_transients( $id ); 
  382.  
  383. return $this->get_product( $id ); 
  384. } catch ( WC_Data_Exception $e ) { 
  385. return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) ); 
  386. } catch ( WC_API_Exception $e ) { 
  387. return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) ); 
  388.  
  389. /** 
  390. * Delete a product. 
  391. * @since 2.2 
  392. * @param int $id the product ID. 
  393. * @param bool $force true to permanently delete order, false to move to trash. 
  394. * @return array 
  395. */ 
  396. public function delete_product( $id, $force = false ) { 
  397.  
  398. $id = $this->validate_request( $id, 'product', 'delete' ); 
  399.  
  400. if ( is_wp_error( $id ) ) { 
  401. return $id; 
  402.  
  403. $product = wc_get_product( $id ); 
  404.  
  405. do_action( 'woocommerce_api_delete_product', $id, $this ); 
  406.  
  407. // If we're forcing, then delete permanently. 
  408. if ( $force ) { 
  409. if ( $product->is_type( 'variable' ) ) { 
  410. foreach ( $product->get_children() as $child_id ) { 
  411. $child = wc_get_product( $child_id ); 
  412. $child->delete( true ); 
  413. } elseif ( $product->is_type( 'grouped' ) ) { 
  414. foreach ( $product->get_children() as $child_id ) { 
  415. $child = wc_get_product( $child_id ); 
  416. $child->set_parent_id( 0 ); 
  417. $child->save(); 
  418.  
  419. $product->delete( true ); 
  420. $result = $product->get_id() > 0 ? false : true; 
  421. } else { 
  422. $product->delete(); 
  423. $result = 'trash' === $product->get_status(); 
  424.  
  425. if ( ! $result ) { 
  426. return new WP_Error( 'woocommerce_api_cannot_delete_product', sprintf( __( 'This %s cannot be deleted', 'woocommerce' ), 'product' ), array( 'status' => 500 ) ); 
  427.  
  428. // Delete parent product transients. 
  429. if ( $parent_id = wp_get_post_parent_id( $id ) ) { 
  430. wc_delete_product_transients( $parent_id ); 
  431.  
  432. if ( $force ) { 
  433. return array( 'message' => sprintf( __( 'Permanently deleted %s', 'woocommerce' ), 'product' ) ); 
  434. } else { 
  435. $this->server->send_status( '202' ); 
  436.  
  437. return array( 'message' => sprintf( __( 'Deleted %s', 'woocommerce' ), 'product' ) ); 
  438.  
  439. /** 
  440. * Get the reviews for a product 
  441. * @since 2.1 
  442. * @param int $id the product ID to get reviews for 
  443. * @param string $fields fields to include in response 
  444. * @return array 
  445. */ 
  446. public function get_product_reviews( $id, $fields = null ) { 
  447.  
  448. $id = $this->validate_request( $id, 'product', 'read' ); 
  449.  
  450. if ( is_wp_error( $id ) ) { 
  451. return $id; 
  452.  
  453. $comments = get_approved_comments( $id ); 
  454. $reviews = array(); 
  455.  
  456. foreach ( $comments as $comment ) { 
  457.  
  458. $reviews[] = array( 
  459. 'id' => intval( $comment->comment_ID ),  
  460. 'created_at' => $this->server->format_datetime( $comment->comment_date_gmt ),  
  461. 'review' => $comment->comment_content,  
  462. 'rating' => get_comment_meta( $comment->comment_ID, 'rating', true ),  
  463. 'reviewer_name' => $comment->comment_author,  
  464. 'reviewer_email' => $comment->comment_author_email,  
  465. 'verified' => wc_review_is_from_verified_owner( $comment->comment_ID ),  
  466. ); 
  467.  
  468. return array( 'product_reviews' => apply_filters( 'woocommerce_api_product_reviews_response', $reviews, $id, $fields, $comments, $this->server ) ); 
  469.  
  470. /** 
  471. * Get the orders for a product 
  472. * @since 2.4.0 
  473. * @param int $id the product ID to get orders for 
  474. * @param string fields fields to retrieve 
  475. * @param string $filter filters to include in response 
  476. * @param string $status the order status to retrieve 
  477. * @param $page $page page to retrieve 
  478. * @return array 
  479. */ 
  480. public function get_product_orders( $id, $fields = null, $filter = array(), $status = null, $page = 1 ) { 
  481. global $wpdb; 
  482.  
  483. $id = $this->validate_request( $id, 'product', 'read' ); 
  484.  
  485. if ( is_wp_error( $id ) ) { 
  486. return $id; 
  487.  
  488. $order_ids = $wpdb->get_col( $wpdb->prepare( " 
  489. SELECT order_id 
  490. FROM {$wpdb->prefix}woocommerce_order_items 
  491. WHERE order_item_id IN ( SELECT order_item_id FROM {$wpdb->prefix}woocommerce_order_itemmeta WHERE meta_key = '_product_id' AND meta_value = %d ) 
  492. AND order_item_type = 'line_item' 
  493. ", $id ) ); 
  494.  
  495. if ( empty( $order_ids ) ) { 
  496. return array( 'orders' => array() ); 
  497.  
  498. $filter = array_merge( $filter, array( 
  499. 'in' => implode( ', ', $order_ids ),  
  500. ) ); 
  501.  
  502. $orders = WC()->api->WC_API_Orders->get_orders( $fields, $filter, $status, $page ); 
  503.  
  504. return array( 'orders' => apply_filters( 'woocommerce_api_product_orders_response', $orders['orders'], $id, $filter, $fields, $this->server ) ); 
  505.  
  506. /** 
  507. * Get a listing of product categories 
  508. * @since 2.2 
  509. * @param string|null $fields fields to limit response to 
  510. * @return array 
  511. */ 
  512. public function get_product_categories( $fields = null ) { 
  513. try { 
  514. // Permissions check 
  515. if ( ! current_user_can( 'manage_product_terms' ) ) { 
  516. throw new WC_API_Exception( 'woocommerce_api_user_cannot_read_product_categories', __( 'You do not have permission to read product categories', 'woocommerce' ), 401 ); 
  517.  
  518. $product_categories = array(); 
  519.  
  520. $terms = get_terms( 'product_cat', array( 'hide_empty' => false, 'fields' => 'ids' ) ); 
  521.  
  522. foreach ( $terms as $term_id ) { 
  523. $product_categories[] = current( $this->get_product_category( $term_id, $fields ) ); 
  524.  
  525. return array( 'product_categories' => apply_filters( 'woocommerce_api_product_categories_response', $product_categories, $terms, $fields, $this ) ); 
  526. } catch ( WC_API_Exception $e ) { 
  527. return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) ); 
  528.  
  529. /** 
  530. * Get the product category for the given ID 
  531. * @since 2.2 
  532. * @param string $id product category term ID 
  533. * @param string|null $fields fields to limit response to 
  534. * @return array 
  535. */ 
  536. public function get_product_category( $id, $fields = null ) { 
  537. try { 
  538. $id = absint( $id ); 
  539.  
  540. // Validate ID 
  541. if ( empty( $id ) ) { 
  542. throw new WC_API_Exception( 'woocommerce_api_invalid_product_category_id', __( 'Invalid product category ID', 'woocommerce' ), 400 ); 
  543.  
  544. // Permissions check 
  545. if ( ! current_user_can( 'manage_product_terms' ) ) { 
  546. throw new WC_API_Exception( 'woocommerce_api_user_cannot_read_product_categories', __( 'You do not have permission to read product categories', 'woocommerce' ), 401 ); 
  547.  
  548. $term = get_term( $id, 'product_cat' ); 
  549.  
  550. if ( is_wp_error( $term ) || is_null( $term ) ) { 
  551. throw new WC_API_Exception( 'woocommerce_api_invalid_product_category_id', __( 'A product category with the provided ID could not be found', 'woocommerce' ), 404 ); 
  552.  
  553. $term_id = intval( $term->term_id ); 
  554.  
  555. // Get category display type 
  556. $display_type = get_woocommerce_term_meta( $term_id, 'display_type' ); 
  557.  
  558. // Get category image 
  559. $image = ''; 
  560. if ( $image_id = get_woocommerce_term_meta( $term_id, 'thumbnail_id' ) ) { 
  561. $image = wp_get_attachment_url( $image_id ); 
  562.  
  563. $product_category = array( 
  564. 'id' => $term_id,  
  565. 'name' => $term->name,  
  566. 'slug' => $term->slug,  
  567. 'parent' => $term->parent,  
  568. 'description' => $term->description,  
  569. 'display' => $display_type ? $display_type : 'default',  
  570. 'image' => $image ? esc_url( $image ) : '',  
  571. 'count' => intval( $term->count ),  
  572. ); 
  573.  
  574. return array( 'product_category' => apply_filters( 'woocommerce_api_product_category_response', $product_category, $id, $fields, $term, $this ) ); 
  575. } catch ( WC_API_Exception $e ) { 
  576. return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) ); 
  577.  
  578. /** 
  579. * Create a new product category. 
  580. * @since 2.5.0 
  581. * @param array $data Posted data 
  582. * @return array|WP_Error Product category if succeed, otherwise WP_Error 
  583. * will be returned 
  584. */ 
  585. public function create_product_category( $data ) { 
  586. global $wpdb; 
  587.  
  588. try { 
  589. if ( ! isset( $data['product_category'] ) ) { 
  590. throw new WC_API_Exception( 'woocommerce_api_missing_product_category_data', sprintf( __( 'No %1$s data specified to create %1$s', 'woocommerce' ), 'product_category' ), 400 ); 
  591.  
  592. // Check permissions 
  593. if ( ! current_user_can( 'manage_product_terms' ) ) { 
  594. throw new WC_API_Exception( 'woocommerce_api_user_cannot_create_product_category', __( 'You do not have permission to create product categories', 'woocommerce' ), 401 ); 
  595.  
  596. $defaults = array( 
  597. 'name' => '',  
  598. 'slug' => '',  
  599. 'description' => '',  
  600. 'parent' => 0,  
  601. 'display' => 'default',  
  602. 'image' => '',  
  603. ); 
  604.  
  605. $data = wp_parse_args( $data['product_category'], $defaults ); 
  606. $data = apply_filters( 'woocommerce_api_create_product_category_data', $data, $this ); 
  607.  
  608. // Check parent. 
  609. $data['parent'] = absint( $data['parent'] ); 
  610. if ( $data['parent'] ) { 
  611. $parent = get_term_by( 'id', $data['parent'], 'product_cat' ); 
  612. if ( ! $parent ) { 
  613. throw new WC_API_Exception( 'woocommerce_api_invalid_product_category_parent', __( 'Product category parent is invalid', 'woocommerce' ), 400 ); 
  614.  
  615. // If value of image is numeric, assume value as image_id. 
  616. $image = $data['image']; 
  617. $image_id = 0; 
  618. if ( is_numeric( $image ) ) { 
  619. $image_id = absint( $image ); 
  620. } elseif ( ! empty( $image ) ) { 
  621. $upload = $this->upload_product_category_image( esc_url_raw( $image ) ); 
  622. $image_id = $this->set_product_category_image_as_attachment( $upload ); 
  623.  
  624. $insert = wp_insert_term( $data['name'], 'product_cat', $data ); 
  625. if ( is_wp_error( $insert ) ) { 
  626. throw new WC_API_Exception( 'woocommerce_api_cannot_create_product_category', $insert->get_error_message(), 400 ); 
  627.  
  628. $id = $insert['term_id']; 
  629.  
  630. update_woocommerce_term_meta( $id, 'display_type', 'default' === $data['display'] ? '' : sanitize_text_field( $data['display'] ) ); 
  631.  
  632. // Check if image_id is a valid image attachment before updating the term meta. 
  633. if ( $image_id && wp_attachment_is_image( $image_id ) ) { 
  634. update_woocommerce_term_meta( $id, 'thumbnail_id', $image_id ); 
  635.  
  636. do_action( 'woocommerce_api_create_product_category', $id, $data ); 
  637.  
  638. $this->server->send_status( 201 ); 
  639.  
  640. return $this->get_product_category( $id ); 
  641. } catch ( WC_API_Exception $e ) { 
  642. return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) ); 
  643.  
  644. /** 
  645. * Edit a product category. 
  646. * @since 2.5.0 
  647. * @param int $id Product category term ID 
  648. * @param array $data Posted data 
  649. * @return array|WP_Error Product category if succeed, otherwise WP_Error 
  650. * will be returned 
  651. */ 
  652. public function edit_product_category( $id, $data ) { 
  653. global $wpdb; 
  654.  
  655. try { 
  656. if ( ! isset( $data['product_category'] ) ) { 
  657. throw new WC_API_Exception( 'woocommerce_api_missing_product_category', sprintf( __( 'No %1$s data specified to edit %1$s', 'woocommerce' ), 'product_category' ), 400 ); 
  658.  
  659. $id = absint( $id ); 
  660. $data = $data['product_category']; 
  661.  
  662. // Check permissions. 
  663. if ( ! current_user_can( 'manage_product_terms' ) ) { 
  664. throw new WC_API_Exception( 'woocommerce_api_user_cannot_edit_product_category', __( 'You do not have permission to edit product categories', 'woocommerce' ), 401 ); 
  665.  
  666. $data = apply_filters( 'woocommerce_api_edit_product_category_data', $data, $this ); 
  667. $category = $this->get_product_category( $id ); 
  668.  
  669. if ( is_wp_error( $category ) ) { 
  670. return $category; 
  671.  
  672. if ( isset( $data['image'] ) ) { 
  673. $image_id = 0; 
  674.  
  675. // If value of image is numeric, assume value as image_id. 
  676. $image = $data['image']; 
  677. if ( is_numeric( $image ) ) { 
  678. $image_id = absint( $image ); 
  679. } elseif ( ! empty( $image ) ) { 
  680. $upload = $this->upload_product_category_image( esc_url_raw( $image ) ); 
  681. $image_id = $this->set_product_category_image_as_attachment( $upload ); 
  682.  
  683. // In case client supplies invalid image or wants to unset category image. 
  684. if ( ! wp_attachment_is_image( $image_id ) ) { 
  685. $image_id = ''; 
  686.  
  687. $update = wp_update_term( $id, 'product_cat', $data ); 
  688. if ( is_wp_error( $update ) ) { 
  689. throw new WC_API_Exception( 'woocommerce_api_cannot_edit_product_catgory', __( 'Could not edit the category', 'woocommerce' ), 400 ); 
  690.  
  691. if ( ! empty( $data['display'] ) ) { 
  692. update_woocommerce_term_meta( $id, 'display_type', 'default' === $data['display'] ? '' : sanitize_text_field( $data['display'] ) ); 
  693.  
  694. if ( isset( $image_id ) ) { 
  695. update_woocommerce_term_meta( $id, 'thumbnail_id', $image_id ); 
  696.  
  697. do_action( 'woocommerce_api_edit_product_category', $id, $data ); 
  698.  
  699. return $this->get_product_category( $id ); 
  700. } catch ( WC_API_Exception $e ) { 
  701. return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) ); 
  702.  
  703. /** 
  704. * Delete a product category. 
  705. * @since 2.5.0 
  706. * @param int $id Product category term ID 
  707. * @return array|WP_Error Success message if succeed, otherwise WP_Error 
  708. * will be returned 
  709. */ 
  710. public function delete_product_category( $id ) { 
  711. global $wpdb; 
  712.  
  713. try { 
  714. // Check permissions 
  715. if ( ! current_user_can( 'manage_product_terms' ) ) { 
  716. throw new WC_API_Exception( 'woocommerce_api_user_cannot_delete_product_category', __( 'You do not have permission to delete product category', 'woocommerce' ), 401 ); 
  717.  
  718. $id = absint( $id ); 
  719. $deleted = wp_delete_term( $id, 'product_cat' ); 
  720. if ( ! $deleted || is_wp_error( $deleted ) ) { 
  721. throw new WC_API_Exception( 'woocommerce_api_cannot_delete_product_category', __( 'Could not delete the category', 'woocommerce' ), 401 ); 
  722.  
  723. // When a term is deleted, delete its meta. 
  724. if ( get_option( 'db_version' ) < 34370 ) { 
  725. $wpdb->delete( $wpdb->woocommerce_termmeta, array( 'woocommerce_term_id' => $id ), array( '%d' ) ); 
  726.  
  727. do_action( 'woocommerce_api_delete_product_category', $id, $this ); 
  728.  
  729. return array( 'message' => sprintf( __( 'Deleted %s', 'woocommerce' ), 'product_category' ) ); 
  730. } catch ( WC_API_Exception $e ) { 
  731. return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) ); 
  732.  
  733. /** 
  734. * Get a listing of product tags. 
  735. * @since 2.5.0 
  736. * @param string|null $fields Fields to limit response to 
  737. * @return array Product tags 
  738. */ 
  739. public function get_product_tags( $fields = null ) { 
  740. try { 
  741. // Permissions check 
  742. if ( ! current_user_can( 'manage_product_terms' ) ) { 
  743. throw new WC_API_Exception( 'woocommerce_api_user_cannot_read_product_tags', __( 'You do not have permission to read product tags', 'woocommerce' ), 401 ); 
  744.  
  745. $product_tags = array(); 
  746.  
  747. $terms = get_terms( 'product_tag', array( 'hide_empty' => false, 'fields' => 'ids' ) ); 
  748.  
  749. foreach ( $terms as $term_id ) { 
  750. $product_tags[] = current( $this->get_product_tag( $term_id, $fields ) ); 
  751.  
  752. return array( 'product_tags' => apply_filters( 'woocommerce_api_product_tags_response', $product_tags, $terms, $fields, $this ) ); 
  753. } catch ( WC_API_Exception $e ) { 
  754. return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) ); 
  755.  
  756. /** 
  757. * Get the product tag for the given ID. 
  758. * @since 2.5.0 
  759. * @param string $id Product tag term ID 
  760. * @param string|null $fields Fields to limit response to 
  761. * @return array Product tag 
  762. */ 
  763. public function get_product_tag( $id, $fields = null ) { 
  764. try { 
  765. $id = absint( $id ); 
  766.  
  767. // Validate ID 
  768. if ( empty( $id ) ) { 
  769. throw new WC_API_Exception( 'woocommerce_api_invalid_product_tag_id', __( 'Invalid product tag ID', 'woocommerce' ), 400 ); 
  770.  
  771. // Permissions check 
  772. if ( ! current_user_can( 'manage_product_terms' ) ) { 
  773. throw new WC_API_Exception( 'woocommerce_api_user_cannot_read_product_tags', __( 'You do not have permission to read product tags', 'woocommerce' ), 401 ); 
  774.  
  775. $term = get_term( $id, 'product_tag' ); 
  776.  
  777. if ( is_wp_error( $term ) || is_null( $term ) ) { 
  778. throw new WC_API_Exception( 'woocommerce_api_invalid_product_tag_id', __( 'A product tag with the provided ID could not be found', 'woocommerce' ), 404 ); 
  779.  
  780. $term_id = intval( $term->term_id ); 
  781.  
  782. $tag = array( 
  783. 'id' => $term_id,  
  784. 'name' => $term->name,  
  785. 'slug' => $term->slug,  
  786. 'description' => $term->description,  
  787. 'count' => intval( $term->count ),  
  788. ); 
  789.  
  790. return array( 'product_tag' => apply_filters( 'woocommerce_api_product_tag_response', $tag, $id, $fields, $term, $this ) ); 
  791. } catch ( WC_API_Exception $e ) { 
  792. return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) ); 
  793.  
  794. /** 
  795. * Create a new product tag. 
  796. * @since 2.5.0 
  797. * @param array $data Posted data 
  798. * @return array|WP_Error Product tag if succeed, otherwise WP_Error 
  799. * will be returned 
  800. */ 
  801. public function create_product_tag( $data ) { 
  802. try { 
  803. if ( ! isset( $data['product_tag'] ) ) { 
  804. throw new WC_API_Exception( 'woocommerce_api_missing_product_tag_data', sprintf( __( 'No %1$s data specified to create %1$s', 'woocommerce' ), 'product_tag' ), 400 ); 
  805.  
  806. // Check permissions 
  807. if ( ! current_user_can( 'manage_product_terms' ) ) { 
  808. throw new WC_API_Exception( 'woocommerce_api_user_cannot_create_product_tag', __( 'You do not have permission to create product tags', 'woocommerce' ), 401 ); 
  809.  
  810. $defaults = array( 
  811. 'name' => '',  
  812. 'slug' => '',  
  813. 'description' => '',  
  814. ); 
  815.  
  816. $data = wp_parse_args( $data['product_tag'], $defaults ); 
  817. $data = apply_filters( 'woocommerce_api_create_product_tag_data', $data, $this ); 
  818.  
  819. $insert = wp_insert_term( $data['name'], 'product_tag', $data ); 
  820. if ( is_wp_error( $insert ) ) { 
  821. throw new WC_API_Exception( 'woocommerce_api_cannot_create_product_tag', $insert->get_error_message(), 400 ); 
  822. $id = $insert['term_id']; 
  823.  
  824. do_action( 'woocommerce_api_create_product_tag', $id, $data ); 
  825.  
  826. $this->server->send_status( 201 ); 
  827.  
  828. return $this->get_product_tag( $id ); 
  829. } catch ( WC_API_Exception $e ) { 
  830. return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) ); 
  831.  
  832. /** 
  833. * Edit a product tag. 
  834. * @since 2.5.0 
  835. * @param int $id Product tag term ID 
  836. * @param array $data Posted data 
  837. * @return array|WP_Error Product tag if succeed, otherwise WP_Error 
  838. * will be returned 
  839. */ 
  840. public function edit_product_tag( $id, $data ) { 
  841. try { 
  842. if ( ! isset( $data['product_tag'] ) ) { 
  843. throw new WC_API_Exception( 'woocommerce_api_missing_product_tag', sprintf( __( 'No %1$s data specified to edit %1$s', 'woocommerce' ), 'product_tag' ), 400 ); 
  844.  
  845. $id = absint( $id ); 
  846. $data = $data['product_tag']; 
  847.  
  848. // Check permissions. 
  849. if ( ! current_user_can( 'manage_product_terms' ) ) { 
  850. throw new WC_API_Exception( 'woocommerce_api_user_cannot_edit_product_tag', __( 'You do not have permission to edit product tags', 'woocommerce' ), 401 ); 
  851.  
  852. $data = apply_filters( 'woocommerce_api_edit_product_tag_data', $data, $this ); 
  853. $tag = $this->get_product_tag( $id ); 
  854.  
  855. if ( is_wp_error( $tag ) ) { 
  856. return $tag; 
  857.  
  858. $update = wp_update_term( $id, 'product_tag', $data ); 
  859. if ( is_wp_error( $update ) ) { 
  860. throw new WC_API_Exception( 'woocommerce_api_cannot_edit_product_tag', __( 'Could not edit the tag', 'woocommerce' ), 400 ); 
  861.  
  862. do_action( 'woocommerce_api_edit_product_tag', $id, $data ); 
  863.  
  864. return $this->get_product_tag( $id ); 
  865. } catch ( WC_API_Exception $e ) { 
  866. return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) ); 
  867.  
  868. /** 
  869. * Delete a product tag. 
  870. * @since 2.5.0 
  871. * @param int $id Product tag term ID 
  872. * @return array|WP_Error Success message if succeed, otherwise WP_Error 
  873. * will be returned 
  874. */ 
  875. public function delete_product_tag( $id ) { 
  876. try { 
  877. // Check permissions 
  878. if ( ! current_user_can( 'manage_product_terms' ) ) { 
  879. throw new WC_API_Exception( 'woocommerce_api_user_cannot_delete_product_tag', __( 'You do not have permission to delete product tag', 'woocommerce' ), 401 ); 
  880.  
  881. $id = absint( $id ); 
  882. $deleted = wp_delete_term( $id, 'product_tag' ); 
  883. if ( ! $deleted || is_wp_error( $deleted ) ) { 
  884. throw new WC_API_Exception( 'woocommerce_api_cannot_delete_product_tag', __( 'Could not delete the tag', 'woocommerce' ), 401 ); 
  885.  
  886. do_action( 'woocommerce_api_delete_product_tag', $id, $this ); 
  887.  
  888. return array( 'message' => sprintf( __( 'Deleted %s', 'woocommerce' ), 'product_tag' ) ); 
  889. } catch ( WC_API_Exception $e ) { 
  890. return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) ); 
  891.  
  892. /** 
  893. * Helper method to get product post objects 
  894. * @since 2.1 
  895. * @param array $args request arguments for filtering query 
  896. * @return WP_Query 
  897. */ 
  898. private function query_products( $args ) { 
  899.  
  900. // Set base query arguments 
  901. $query_args = array( 
  902. 'fields' => 'ids',  
  903. 'post_type' => 'product',  
  904. 'post_status' => 'publish',  
  905. 'meta_query' => array(),  
  906. ); 
  907.  
  908. // Taxonomy query to filter products by type, category, tag, shipping class, and 
  909. // attribute. 
  910. $tax_query = array(); 
  911.  
  912. // Map between taxonomy name and arg's key. 
  913. $taxonomies_arg_map = array( 
  914. 'product_type' => 'type',  
  915. 'product_cat' => 'category',  
  916. 'product_tag' => 'tag',  
  917. 'product_shipping_class' => 'shipping_class',  
  918. ); 
  919.  
  920. // Add attribute taxonomy names into the map. 
  921. foreach ( wc_get_attribute_taxonomy_names() as $attribute_name ) { 
  922. $taxonomies_arg_map[ $attribute_name ] = $attribute_name; 
  923.  
  924. // Set tax_query for each passed arg. 
  925. foreach ( $taxonomies_arg_map as $tax_name => $arg ) { 
  926. if ( ! empty( $args[ $arg ] ) ) { 
  927. $terms = explode( ', ', $args[ $arg ] ); 
  928.  
  929. $tax_query[] = array( 
  930. 'taxonomy' => $tax_name,  
  931. 'field' => 'slug',  
  932. 'terms' => $terms,  
  933. ); 
  934.  
  935. unset( $args[ $arg ] ); 
  936.  
  937. if ( ! empty( $tax_query ) ) { 
  938. $query_args['tax_query'] = $tax_query; 
  939.  
  940. // Filter by specific sku 
  941. if ( ! empty( $args['sku'] ) ) { 
  942. if ( ! is_array( $query_args['meta_query'] ) ) { 
  943. $query_args['meta_query'] = array(); 
  944.  
  945. $query_args['meta_query'][] = array( 
  946. 'key' => '_sku',  
  947. 'value' => $args['sku'],  
  948. 'compare' => '=',  
  949. ); 
  950.  
  951. $query_args['post_type'] = array( 'product', 'product_variation' ); 
  952.  
  953. $query_args = $this->merge_query_args( $query_args, $args ); 
  954.  
  955. return new WP_Query( $query_args ); 
  956.  
  957. /** 
  958. * Get standard product data that applies to every product type 
  959. * @since 2.1 
  960. * @param WC_Product|int $product 
  961. * @return WC_Product 
  962. */ 
  963. private function get_product_data( $product ) { 
  964. if ( is_numeric( $product ) ) { 
  965. $product = wc_get_product( $product ); 
  966.  
  967. return array( 
  968. 'title' => $product->get_name(),  
  969. 'id' => $product->get_id(),  
  970. 'created_at' => $this->server->format_datetime( $product->get_date_created(), false, true ),  
  971. 'updated_at' => $this->server->format_datetime( $product->get_date_modified(), false, true ),  
  972. 'type' => $product->get_type(),  
  973. 'status' => $product->get_status(),  
  974. 'downloadable' => $product->is_downloadable(),  
  975. 'virtual' => $product->is_virtual(),  
  976. 'permalink' => $product->get_permalink(),  
  977. 'sku' => $product->get_sku(),  
  978. 'price' => $product->get_price(),  
  979. 'regular_price' => $product->get_regular_price(),  
  980. 'sale_price' => $product->get_sale_price() ? $product->get_sale_price() : null,  
  981. 'price_html' => $product->get_price_html(),  
  982. 'taxable' => $product->is_taxable(),  
  983. 'tax_status' => $product->get_tax_status(),  
  984. 'tax_class' => $product->get_tax_class(),  
  985. 'managing_stock' => $product->managing_stock(),  
  986. 'stock_quantity' => $product->get_stock_quantity(),  
  987. 'in_stock' => $product->is_in_stock(),  
  988. 'backorders_allowed' => $product->backorders_allowed(),  
  989. 'backordered' => $product->is_on_backorder(),  
  990. 'sold_individually' => $product->is_sold_individually(),  
  991. 'purchaseable' => $product->is_purchasable(),  
  992. 'featured' => $product->is_featured(),  
  993. 'visible' => $product->is_visible(),  
  994. 'catalog_visibility' => $product->get_catalog_visibility(),  
  995. 'on_sale' => $product->is_on_sale(),  
  996. 'product_url' => $product->is_type( 'external' ) ? $product->get_product_url() : '',  
  997. 'button_text' => $product->is_type( 'external' ) ? $product->get_button_text() : '',  
  998. 'weight' => $product->get_weight() ? $product->get_weight() : null,  
  999. 'dimensions' => array( 
  1000. 'length' => $product->get_length(),  
  1001. 'width' => $product->get_width(),  
  1002. 'height' => $product->get_height(),  
  1003. 'unit' => get_option( 'woocommerce_dimension_unit' ),  
  1004. ),  
  1005. 'shipping_required' => $product->needs_shipping(),  
  1006. 'shipping_taxable' => $product->is_shipping_taxable(),  
  1007. 'shipping_class' => $product->get_shipping_class(),  
  1008. 'shipping_class_id' => ( 0 !== $product->get_shipping_class_id() ) ? $product->get_shipping_class_id() : null,  
  1009. 'description' => wpautop( do_shortcode( $product->get_description() ) ),  
  1010. 'short_description' => apply_filters( 'woocommerce_short_description', $product->get_short_description() ),  
  1011. 'reviews_allowed' => $product->get_reviews_allowed(),  
  1012. 'average_rating' => wc_format_decimal( $product->get_average_rating(), 2 ),  
  1013. 'rating_count' => $product->get_rating_count(),  
  1014. 'related_ids' => array_map( 'absint', array_values( wc_get_related_products( $product->get_id() ) ) ),  
  1015. 'upsell_ids' => array_map( 'absint', $product->get_upsell_ids() ),  
  1016. 'cross_sell_ids' => array_map( 'absint', $product->get_cross_sell_ids() ),  
  1017. 'parent_id' => $product->get_parent_id(),  
  1018. 'categories' => wc_get_object_terms( $product->get_id(), 'product_cat', 'name' ),  
  1019. 'tags' => wc_get_object_terms( $product->get_id(), 'product_tag', 'name' ),  
  1020. 'images' => $this->get_images( $product ),  
  1021. 'featured_src' => wp_get_attachment_url( get_post_thumbnail_id( $product->get_id() ) ),  
  1022. 'attributes' => $this->get_attributes( $product ),  
  1023. 'downloads' => $this->get_downloads( $product ),  
  1024. 'download_limit' => $product->get_download_limit(),  
  1025. 'download_expiry' => $product->get_download_expiry(),  
  1026. 'download_type' => 'standard',  
  1027. 'purchase_note' => wpautop( do_shortcode( wp_kses_post( $product->get_purchase_note() ) ) ),  
  1028. 'total_sales' => $product->get_total_sales(),  
  1029. 'variations' => array(),  
  1030. 'parent' => array(),  
  1031. 'grouped_products' => array(),  
  1032. 'menu_order' => $this->get_product_menu_order( $product ),  
  1033. ); 
  1034.  
  1035. /** 
  1036. * Get product menu order. 
  1037. * @since 2.5.3 
  1038. * @param WC_Product $product 
  1039. * @return int 
  1040. */ 
  1041. private function get_product_menu_order( $product ) { 
  1042. $menu_order = $product->get_menu_order(); 
  1043.  
  1044. return apply_filters( 'woocommerce_api_product_menu_order', $menu_order, $product ); 
  1045.  
  1046. /** 
  1047. * Get an individual variation's data. 
  1048. * @since 2.1 
  1049. * @param WC_Product $product 
  1050. * @return array 
  1051. */ 
  1052. private function get_variation_data( $product ) { 
  1053. $variations = array(); 
  1054.  
  1055. foreach ( $product->get_children() as $child_id ) { 
  1056. $variation = wc_get_product( $child_id ); 
  1057.  
  1058. if ( ! $variation || ! $variation->exists() ) { 
  1059. continue; 
  1060.  
  1061. $variations[] = array( 
  1062. 'id' => $variation->get_id(),  
  1063. 'created_at' => $this->server->format_datetime( $variation->get_date_created(), false, true ),  
  1064. 'updated_at' => $this->server->format_datetime( $variation->get_date_modified(), false, true ),  
  1065. 'downloadable' => $variation->is_downloadable(),  
  1066. 'virtual' => $variation->is_virtual(),  
  1067. 'permalink' => $variation->get_permalink(),  
  1068. 'sku' => $variation->get_sku(),  
  1069. 'price' => $variation->get_price(),  
  1070. 'regular_price' => $variation->get_regular_price(),  
  1071. 'sale_price' => $variation->get_sale_price() ? $variation->get_sale_price() : null,  
  1072. 'taxable' => $variation->is_taxable(),  
  1073. 'tax_status' => $variation->get_tax_status(),  
  1074. 'tax_class' => $variation->get_tax_class(),  
  1075. 'managing_stock' => $variation->managing_stock(),  
  1076. 'stock_quantity' => $variation->get_stock_quantity(),  
  1077. 'in_stock' => $variation->is_in_stock(),  
  1078. 'backorders_allowed' => $variation->backorders_allowed(),  
  1079. 'backordered' => $variation->is_on_backorder(),  
  1080. 'purchaseable' => $variation->is_purchasable(),  
  1081. 'visible' => $variation->variation_is_visible(),  
  1082. 'on_sale' => $variation->is_on_sale(),  
  1083. 'weight' => $variation->get_weight() ? $variation->get_weight() : null,  
  1084. 'dimensions' => array( 
  1085. 'length' => $variation->get_length(),  
  1086. 'width' => $variation->get_width(),  
  1087. 'height' => $variation->get_height(),  
  1088. 'unit' => get_option( 'woocommerce_dimension_unit' ),  
  1089. ),  
  1090. 'shipping_class' => $variation->get_shipping_class(),  
  1091. 'shipping_class_id' => ( 0 !== $variation->get_shipping_class_id() ) ? $variation->get_shipping_class_id() : null,  
  1092. 'image' => $this->get_images( $variation ),  
  1093. 'attributes' => $this->get_attributes( $variation ),  
  1094. 'downloads' => $this->get_downloads( $variation ),  
  1095. 'download_limit' => (int) $product->get_download_limit(),  
  1096. 'download_expiry' => (int) $product->get_download_expiry(),  
  1097. ); 
  1098.  
  1099. return $variations; 
  1100.  
  1101. /** 
  1102. * Get grouped products data 
  1103. * @since 2.5.0 
  1104. * @param WC_Product $product 
  1105. * @return array 
  1106. */ 
  1107. private function get_grouped_products_data( $product ) { 
  1108. $products = array(); 
  1109.  
  1110. foreach ( $product->get_children() as $child_id ) { 
  1111. $_product = wc_get_product( $child_id ); 
  1112.  
  1113. if ( ! $_product || ! $_product->exists() ) { 
  1114. continue; 
  1115.  
  1116. $products[] = $this->get_product_data( $_product ); 
  1117.  
  1118.  
  1119. return $products; 
  1120.  
  1121. /** 
  1122. * Save default attributes. 
  1123. * @since 3.0.0 
  1124. * @param WC_Product $product 
  1125. * @param WP_REST_Request $request 
  1126. * @return WC_Product 
  1127. */ 
  1128. protected function save_default_attributes( $product, $request ) { 
  1129. // Update default attributes options setting. 
  1130. if ( isset( $request['default_attribute'] ) ) { 
  1131. $request['default_attributes'] = $request['default_attribute']; 
  1132.  
  1133. if ( isset( $request['default_attributes'] ) && is_array( $request['default_attributes'] ) ) { 
  1134. $attributes = $product->get_attributes(); 
  1135. $default_attributes = array(); 
  1136.  
  1137. foreach ( $request['default_attributes'] as $default_attr_key => $default_attr ) { 
  1138. if ( ! isset( $default_attr['name'] ) ) { 
  1139. continue; 
  1140.  
  1141. $taxonomy = sanitize_title( $default_attr['name'] ); 
  1142.  
  1143. if ( isset( $default_attr['slug'] ) ) { 
  1144. $taxonomy = $this->get_attribute_taxonomy_by_slug( $default_attr['slug'] ); 
  1145.  
  1146. if ( isset( $attributes[ $taxonomy ] ) ) { 
  1147. $_attribute = $attributes[ $taxonomy ]; 
  1148.  
  1149. if ( $_attribute['is_variation'] ) { 
  1150. $value = ''; 
  1151.  
  1152. if ( isset( $default_attr['option'] ) ) { 
  1153. if ( $_attribute['is_taxonomy'] ) { 
  1154. // Don't use wc_clean as it destroys sanitized characters 
  1155. $value = sanitize_title( trim( stripslashes( $default_attr['option'] ) ) ); 
  1156. } else { 
  1157. $value = wc_clean( trim( stripslashes( $default_attr['option'] ) ) ); 
  1158.  
  1159. if ( $value ) { 
  1160. $default_attributes[ $taxonomy ] = $value; 
  1161.  
  1162. $product->set_default_attributes( $default_attributes ); 
  1163.  
  1164. return $product; 
  1165.  
  1166. /** 
  1167. * Save product meta. 
  1168. * @since 2.2 
  1169. * @param WC_Product $product 
  1170. * @param array $data 
  1171. * @return WC_Product 
  1172. * @throws WC_API_Exception 
  1173. */ 
  1174. protected function save_product_meta( $product, $data ) { 
  1175. global $wpdb; 
  1176.  
  1177. // Virtual. 
  1178. if ( isset( $data['virtual'] ) ) { 
  1179. $product->set_virtual( $data['virtual'] ); 
  1180.  
  1181. // Tax status. 
  1182. if ( isset( $data['tax_status'] ) ) { 
  1183. $product->set_tax_status( wc_clean( $data['tax_status'] ) ); 
  1184.  
  1185. // Tax Class. 
  1186. if ( isset( $data['tax_class'] ) ) { 
  1187. $product->set_tax_class( wc_clean( $data['tax_class'] ) ); 
  1188.  
  1189. // Catalog Visibility. 
  1190. if ( isset( $data['catalog_visibility'] ) ) { 
  1191. $product->set_catalog_visibility( wc_clean( $data['catalog_visibility'] ) ); 
  1192.  
  1193. // Purchase Note. 
  1194. if ( isset( $data['purchase_note'] ) ) { 
  1195. $product->set_purchase_note( wc_clean( $data['purchase_note'] ) ); 
  1196.  
  1197. // Featured Product. 
  1198. if ( isset( $data['featured'] ) ) { 
  1199. $product->set_featured( $data['featured'] ); 
  1200.  
  1201. // Shipping data. 
  1202. $product = $this->save_product_shipping_data( $product, $data ); 
  1203.  
  1204. // SKU. 
  1205. if ( isset( $data['sku'] ) ) { 
  1206. $sku = $product->get_sku(); 
  1207. $new_sku = wc_clean( $data['sku'] ); 
  1208.  
  1209. if ( '' == $new_sku ) { 
  1210. $product->set_sku( '' ); 
  1211. } elseif ( $new_sku !== $sku ) { 
  1212. if ( ! empty( $new_sku ) ) { 
  1213. $unique_sku = wc_product_has_unique_sku( $product->get_id(), $new_sku ); 
  1214. if ( ! $unique_sku ) { 
  1215. throw new WC_API_Exception( 'woocommerce_api_product_sku_already_exists', __( 'The SKU already exists on another product.', 'woocommerce' ), 400 ); 
  1216. } else { 
  1217. $product->set_sku( $new_sku ); 
  1218. } else { 
  1219. $product->set_sku( '' ); 
  1220.  
  1221. // Attributes. 
  1222. if ( isset( $data['attributes'] ) ) { 
  1223. $attributes = array(); 
  1224.  
  1225. foreach ( $data['attributes'] as $attribute ) { 
  1226. $is_taxonomy = 0; 
  1227. $taxonomy = 0; 
  1228.  
  1229. if ( ! isset( $attribute['name'] ) ) { 
  1230. continue; 
  1231.  
  1232. $attribute_slug = sanitize_title( $attribute['name'] ); 
  1233.  
  1234. if ( isset( $attribute['slug'] ) ) { 
  1235. $taxonomy = $this->get_attribute_taxonomy_by_slug( $attribute['slug'] ); 
  1236. $attribute_slug = sanitize_title( $attribute['slug'] ); 
  1237.  
  1238. if ( $taxonomy ) { 
  1239. $is_taxonomy = 1; 
  1240.  
  1241. if ( $is_taxonomy ) { 
  1242.  
  1243. $attribute_id = wc_attribute_taxonomy_id_by_name( $attribute['name'] ); 
  1244.  
  1245. if ( isset( $attribute['options'] ) ) { 
  1246. $options = $attribute['options']; 
  1247.  
  1248. if ( ! is_array( $attribute['options'] ) ) { 
  1249. // Text based attributes - Posted values are term names. 
  1250. $options = explode( WC_DELIMITER, $options ); 
  1251.  
  1252. $values = array_map( 'wc_sanitize_term_text_based', $options ); 
  1253. $values = array_filter( $values, 'strlen' ); 
  1254. } else { 
  1255. $values = array(); 
  1256.  
  1257. // Update post terms 
  1258. if ( taxonomy_exists( $taxonomy ) ) { 
  1259. wp_set_object_terms( $product->get_id(), $values, $taxonomy ); 
  1260.  
  1261. if ( ! empty( $values ) ) { 
  1262. // Add attribute to array, but don't set values. 
  1263. $attribute_object = new WC_Product_Attribute(); 
  1264. $attribute_object->set_id( $attribute_id ); 
  1265. $attribute_object->set_name( $taxonomy ); 
  1266. $attribute_object->set_options( $values ); 
  1267. $attribute_object->set_position( isset( $attribute['position'] ) ? absint( $attribute['position'] ) : 0 ); 
  1268. $attribute_object->set_visible( ( isset( $attribute['visible'] ) && $attribute['visible'] ) ? 1 : 0 ); 
  1269. $attribute_object->set_variation( ( isset( $attribute['variation'] ) && $attribute['variation'] ) ? 1 : 0 ); 
  1270. $attributes[] = $attribute_object; 
  1271. } elseif ( isset( $attribute['options'] ) ) { 
  1272. // Array based. 
  1273. if ( is_array( $attribute['options'] ) ) { 
  1274. $values = $attribute['options']; 
  1275.  
  1276. // Text based, separate by pipe. 
  1277. } else { 
  1278. $values = array_map( 'wc_clean', explode( WC_DELIMITER, $attribute['options'] ) ); 
  1279.  
  1280. // Custom attribute - Add attribute to array and set the values. 
  1281. $attribute_object = new WC_Product_Attribute(); 
  1282. $attribute_object->set_name( $attribute['name'] ); 
  1283. $attribute_object->set_options( $values ); 
  1284. $attribute_object->set_position( isset( $attribute['position'] ) ? absint( $attribute['position'] ) : 0 ); 
  1285. $attribute_object->set_visible( ( isset( $attribute['visible'] ) && $attribute['visible'] ) ? 1 : 0 ); 
  1286. $attribute_object->set_variation( ( isset( $attribute['variation'] ) && $attribute['variation'] ) ? 1 : 0 ); 
  1287. $attributes[] = $attribute_object; 
  1288.  
  1289. uasort( $attributes, 'wc_product_attribute_uasort_comparison' ); 
  1290.  
  1291. $product->set_attributes( $attributes ); 
  1292.  
  1293. // Sales and prices. 
  1294. if ( in_array( $product->get_type(), array( 'variable', 'grouped' ) ) ) { 
  1295.  
  1296. // Variable and grouped products have no prices. 
  1297. $product->set_regular_price( '' ); 
  1298. $product->set_sale_price( '' ); 
  1299. $product->set_date_on_sale_to( '' ); 
  1300. $product->set_date_on_sale_from( '' ); 
  1301. $product->set_price( '' ); 
  1302.  
  1303. } else { 
  1304.  
  1305. // Regular Price 
  1306. if ( isset( $data['regular_price'] ) ) { 
  1307. $regular_price = ( '' === $data['regular_price'] ) ? '' : $data['regular_price']; 
  1308. } else { 
  1309. $regular_price = $product->get_regular_price(); 
  1310.  
  1311. // Sale Price 
  1312. if ( isset( $data['sale_price'] ) ) { 
  1313. $sale_price = ( '' === $data['sale_price'] ) ? '' : $data['sale_price']; 
  1314. } else { 
  1315. $sale_price = $product->get_sale_price(); 
  1316.  
  1317. $product->set_regular_price( $regular_price ); 
  1318. $product->set_sale_price( $sale_price ); 
  1319.  
  1320. if ( isset( $data['sale_price_dates_from'] ) ) { 
  1321. $date_from = $data['sale_price_dates_from']; 
  1322. } else { 
  1323. $date_from = $product->get_date_on_sale_from() ? date( 'Y-m-d', $product->get_date_on_sale_from()->getTimestamp() ) : ''; 
  1324.  
  1325. if ( isset( $data['sale_price_dates_to'] ) ) { 
  1326. $date_to = $data['sale_price_dates_to']; 
  1327. } else { 
  1328. $date_to = $product->get_date_on_sale_to() ? date( 'Y-m-d', $product->get_date_on_sale_to()->getTimestamp() ) : ''; 
  1329.  
  1330. if ( $date_to && ! $date_from ) { 
  1331. $date_from = strtotime( 'NOW', current_time( 'timestamp', true ) ); 
  1332.  
  1333. $product->set_date_on_sale_to( $date_to ); 
  1334. $product->set_date_on_sale_from( $date_from ); 
  1335. if ( $product->is_on_sale() ) { 
  1336. $product->set_price( $product->get_sale_price() ); 
  1337. } else { 
  1338. $product->set_price( $product->get_regular_price() ); 
  1339.  
  1340. // Product parent ID for groups. 
  1341. if ( isset( $data['parent_id'] ) ) { 
  1342. $product->set_parent_id( absint( $data['parent_id'] ) ); 
  1343.  
  1344. // Sold Individually. 
  1345. if ( isset( $data['sold_individually'] ) ) { 
  1346. $product->set_sold_individually( true === $data['sold_individually'] ? 'yes' : '' ); 
  1347.  
  1348. // Stock status. 
  1349. if ( isset( $data['in_stock'] ) ) { 
  1350. $stock_status = ( true === $data['in_stock'] ) ? 'instock' : 'outofstock'; 
  1351. } else { 
  1352. $stock_status = $product->get_stock_status(); 
  1353.  
  1354. if ( '' === $stock_status ) { 
  1355. $stock_status = 'instock'; 
  1356.  
  1357. // Stock Data. 
  1358. if ( 'yes' == get_option( 'woocommerce_manage_stock' ) ) { 
  1359. // Manage stock. 
  1360. if ( isset( $data['managing_stock'] ) ) { 
  1361. $managing_stock = ( true === $data['managing_stock'] ) ? 'yes' : 'no'; 
  1362. $product->set_manage_stock( $managing_stock ); 
  1363. } else { 
  1364. $managing_stock = $product->get_manage_stock() ? 'yes' : 'no'; 
  1365.  
  1366. // Backorders. 
  1367. if ( isset( $data['backorders'] ) ) { 
  1368. if ( 'notify' === $data['backorders'] ) { 
  1369. $backorders = 'notify'; 
  1370. } else { 
  1371. $backorders = ( true === $data['backorders'] ) ? 'yes' : 'no'; 
  1372.  
  1373. $product->set_backorders( $backorders ); 
  1374. } else { 
  1375. $backorders = $product->get_backorders(); 
  1376.  
  1377. if ( $product->is_type( 'grouped' ) ) { 
  1378. $product->set_manage_stock( 'no' ); 
  1379. $product->set_backorders( 'no' ); 
  1380. $product->set_stock_quantity( '' ); 
  1381. $product->set_stock_status( $stock_status ); 
  1382. } elseif ( $product->is_type( 'external' ) ) { 
  1383. $product->set_manage_stock( 'no' ); 
  1384. $product->set_backorders( 'no' ); 
  1385. $product->set_stock_quantity( '' ); 
  1386. $product->set_stock_status( 'instock' ); 
  1387. } elseif ( 'yes' == $managing_stock ) { 
  1388. $product->set_backorders( $backorders ); 
  1389.  
  1390. // Stock status is always determined by children so sync later. 
  1391. if ( ! $product->is_type( 'variable' ) ) { 
  1392. $product->set_stock_status( $stock_status ); 
  1393.  
  1394. // Stock quantity. 
  1395. if ( isset( $data['stock_quantity'] ) ) { 
  1396. $product->set_stock_quantity( wc_stock_amount( $data['stock_quantity'] ) ); 
  1397. } elseif ( isset( $data['inventory_delta'] ) ) { 
  1398. $stock_quantity = wc_stock_amount( $product->get_stock_quantity() ); 
  1399. $stock_quantity += wc_stock_amount( $data['inventory_delta'] ); 
  1400. $product->set_stock_quantity( wc_stock_amount( $stock_quantity ) ); 
  1401. } else { 
  1402. // Don't manage stock. 
  1403. $product->set_manage_stock( 'no' ); 
  1404. $product->set_backorders( $backorders ); 
  1405. $product->set_stock_quantity( '' ); 
  1406. $product->set_stock_status( $stock_status ); 
  1407. } elseif ( ! $product->is_type( 'variable' ) ) { 
  1408. $product->set_stock_status( $stock_status ); 
  1409.  
  1410. // Upsells. 
  1411. if ( isset( $data['upsell_ids'] ) ) { 
  1412. $upsells = array(); 
  1413. $ids = $data['upsell_ids']; 
  1414.  
  1415. if ( ! empty( $ids ) ) { 
  1416. foreach ( $ids as $id ) { 
  1417. if ( $id && $id > 0 ) { 
  1418. $upsells[] = $id; 
  1419.  
  1420. $product->set_upsell_ids( $upsells ); 
  1421. } else { 
  1422. $product->set_upsell_ids( array() ); 
  1423.  
  1424. // Cross sells. 
  1425. if ( isset( $data['cross_sell_ids'] ) ) { 
  1426. $crosssells = array(); 
  1427. $ids = $data['cross_sell_ids']; 
  1428.  
  1429. if ( ! empty( $ids ) ) { 
  1430. foreach ( $ids as $id ) { 
  1431. if ( $id && $id > 0 ) { 
  1432. $crosssells[] = $id; 
  1433.  
  1434. $product->set_cross_sell_ids( $crosssells ); 
  1435. } else { 
  1436. $product->set_cross_sell_ids( array() ); 
  1437.  
  1438. // Product categories. 
  1439. if ( isset( $data['categories'] ) && is_array( $data['categories'] ) ) { 
  1440. $product->set_category_ids( $data['categories'] ); 
  1441.  
  1442. // Product tags. 
  1443. if ( isset( $data['tags'] ) && is_array( $data['tags'] ) ) { 
  1444. $product->set_tag_ids( $data['tags'] ); 
  1445.  
  1446. // Downloadable. 
  1447. if ( isset( $data['downloadable'] ) ) { 
  1448. $is_downloadable = ( true === $data['downloadable'] ) ? 'yes' : 'no'; 
  1449. $product->set_downloadable( $is_downloadable ); 
  1450. } else { 
  1451. $is_downloadable = $product->get_downloadable() ? 'yes' : 'no'; 
  1452.  
  1453. // Downloadable options. 
  1454. if ( 'yes' == $is_downloadable ) { 
  1455.  
  1456. // Downloadable files. 
  1457. if ( isset( $data['downloads'] ) && is_array( $data['downloads'] ) ) { 
  1458. $product = $this->save_downloadable_files( $product, $data['downloads'] ); 
  1459.  
  1460. // Download limit. 
  1461. if ( isset( $data['download_limit'] ) ) { 
  1462. $product->set_download_limit( $data['download_limit'] ); 
  1463.  
  1464. // Download expiry. 
  1465. if ( isset( $data['download_expiry'] ) ) { 
  1466. $product->set_download_expiry( $data['download_expiry'] ); 
  1467.  
  1468. // Product url. 
  1469. if ( $product->is_type( 'external' ) ) { 
  1470. if ( isset( $data['product_url'] ) ) { 
  1471. $product->set_product_url( $data['product_url'] ); 
  1472.  
  1473. if ( isset( $data['button_text'] ) ) { 
  1474. $product->set_button_text( $data['button_text'] ); 
  1475.  
  1476. // Reviews allowed. 
  1477. if ( isset( $data['reviews_allowed'] ) ) { 
  1478. $product->set_reviews_allowed( $data['reviews_allowed'] ); 
  1479.  
  1480. // Save default attributes for variable products. 
  1481. if ( $product->is_type( 'variable' ) ) { 
  1482. $product = $this->save_default_attributes( $product, $data ); 
  1483.  
  1484. // Do action for product type 
  1485. do_action( 'woocommerce_api_process_product_meta_' . $product->get_type(), $product->get_id(), $data ); 
  1486.  
  1487. return $product; 
  1488.  
  1489. /** 
  1490. * Save variations. 
  1491. * @since 2.2 
  1492. * @param WC_Product $product 
  1493. * @param array $request 
  1494. * @return WC_Product 
  1495. * @throws WC_API_Exception 
  1496. */ 
  1497. protected function save_variations( $product, $request ) { 
  1498. global $wpdb; 
  1499.  
  1500. $id = $product->get_id(); 
  1501. $variations = $request['variations']; 
  1502. $attributes = $product->get_attributes(); 
  1503.  
  1504. foreach ( $variations as $menu_order => $data ) { 
  1505. $variation_id = isset( $data['id'] ) ? absint( $data['id'] ) : 0; 
  1506. $variation = new WC_Product_Variation( $variation_id ); 
  1507.  
  1508. // Create initial name and status. 
  1509. if ( ! $variation->get_slug() ) { 
  1510. /** translators: 1: variation id 2: product name */ 
  1511. $variation->set_name( sprintf( __( 'Variation #%1$s of %2$s', 'woocommerce' ), $variation->get_id(), $product->get_name() ) ); 
  1512. $variation->set_status( isset( $data['visible'] ) && false === $data['visible'] ? 'private' : 'publish' ); 
  1513.  
  1514. // Parent ID. 
  1515. $variation->set_parent_id( $product->get_id() ); 
  1516.  
  1517. // Menu order. 
  1518. $variation->set_menu_order( $menu_order ); 
  1519.  
  1520. // Status. 
  1521. if ( isset( $data['visible'] ) ) { 
  1522. $variation->set_status( false === $data['visible'] ? 'private' : 'publish' ); 
  1523.  
  1524. // SKU. 
  1525. if ( isset( $data['sku'] ) ) { 
  1526. $variation->set_sku( wc_clean( $data['sku'] ) ); 
  1527.  
  1528. // Thumbnail. 
  1529. if ( isset( $data['image'] ) && is_array( $data['image'] ) ) { 
  1530. $image = current( $data['image'] ); 
  1531. if ( is_array( $image ) ) { 
  1532. $image['position'] = 0; 
  1533.  
  1534. $variation = $this->save_product_images( $variation, array( $image ) ); 
  1535.  
  1536. // Virtual variation. 
  1537. if ( isset( $data['virtual'] ) ) { 
  1538. $variation->set_virtual( $data['virtual'] ); 
  1539.  
  1540. // Downloadable variation. 
  1541. if ( isset( $data['downloadable'] ) ) { 
  1542. $is_downloadable = $data['downloadable']; 
  1543. $variation->set_downloadable( $is_downloadable ); 
  1544. } else { 
  1545. $is_downloadable = $variation->get_downloadable(); 
  1546.  
  1547. // Downloads. 
  1548. if ( $is_downloadable ) { 
  1549. // Downloadable files. 
  1550. if ( isset( $data['downloads'] ) && is_array( $data['downloads'] ) ) { 
  1551. $variation = $this->save_downloadable_files( $variation, $data['downloads'] ); 
  1552.  
  1553. // Download limit. 
  1554. if ( isset( $data['download_limit'] ) ) { 
  1555. $variation->set_download_limit( $data['download_limit'] ); 
  1556.  
  1557. // Download expiry. 
  1558. if ( isset( $data['download_expiry'] ) ) { 
  1559. $variation->set_download_expiry( $data['download_expiry'] ); 
  1560.  
  1561. // Shipping data. 
  1562. $variation = $this->save_product_shipping_data( $variation, $data ); 
  1563.  
  1564. // Stock handling. 
  1565. $manage_stock = (bool) $variation->get_manage_stock(); 
  1566. if ( isset( $data['managing_stock'] ) ) { 
  1567. $manage_stock = $data['managing_stock']; 
  1568. $variation->set_manage_stock( $manage_stock ); 
  1569.  
  1570. $stock_status = $variation->get_stock_status(); 
  1571. if ( isset( $data['in_stock'] ) ) { 
  1572. $stock_status = true === $data['in_stock'] ? 'instock' : 'outofstock'; 
  1573. $variation->set_stock_status( $stock_status ); 
  1574.  
  1575. $backorders = $variation->get_backorders(); 
  1576. if ( isset( $data['backorders'] ) ) { 
  1577. $backorders = $data['backorders']; 
  1578. $variation->set_backorders( $backorders ); 
  1579.  
  1580. if ( $manage_stock ) { 
  1581. if ( isset( $data['stock_quantity'] ) ) { 
  1582. $variation->set_stock_quantity( $data['stock_quantity'] ); 
  1583. } elseif ( isset( $data['inventory_delta'] ) ) { 
  1584. $stock_quantity = wc_stock_amount( $variation->get_stock_quantity() ); 
  1585. $stock_quantity += wc_stock_amount( $data['inventory_delta'] ); 
  1586. $variation->set_stock_quantity( $stock_quantity ); 
  1587. } else { 
  1588. $variation->set_backorders( 'no' ); 
  1589. $variation->set_stock_quantity( '' ); 
  1590.  
  1591. // Regular Price. 
  1592. if ( isset( $data['regular_price'] ) ) { 
  1593. $variation->set_regular_price( $data['regular_price'] ); 
  1594.  
  1595. // Sale Price. 
  1596. if ( isset( $data['sale_price'] ) ) { 
  1597. $variation->set_sale_price( $data['sale_price'] ); 
  1598.  
  1599. if ( isset( $data['sale_price_dates_from'] ) ) { 
  1600. $variation->set_date_on_sale_from( $data['sale_price_dates_from'] ); 
  1601.  
  1602. if ( isset( $data['sale_price_dates_to'] ) ) { 
  1603. $variation->set_date_on_sale_to( $data['sale_price_dates_to'] ); 
  1604.  
  1605. // Tax class. 
  1606. if ( isset( $data['tax_class'] ) ) { 
  1607. $variation->set_tax_class( $data['tax_class'] ); 
  1608.  
  1609. // Description. 
  1610. if ( isset( $data['description'] ) ) { 
  1611. $variation->set_description( wp_kses_post( $data['description'] ) ); 
  1612.  
  1613. // Update taxonomies. 
  1614. if ( isset( $data['attributes'] ) ) { 
  1615. $_attributes = array(); 
  1616.  
  1617. foreach ( $data['attributes'] as $attribute_key => $attribute ) { 
  1618. if ( ! isset( $attribute['name'] ) ) { 
  1619. continue; 
  1620.  
  1621. $taxonomy = 0; 
  1622. $_attribute = array(); 
  1623.  
  1624. if ( isset( $attribute['slug'] ) ) { 
  1625. $taxonomy = $this->get_attribute_taxonomy_by_slug( $attribute['slug'] ); 
  1626.  
  1627. if ( ! $taxonomy ) { 
  1628. $taxonomy = sanitize_title( $attribute['name'] ); 
  1629.  
  1630. if ( isset( $attributes[ $taxonomy ] ) ) { 
  1631. $_attribute = $attributes[ $taxonomy ]; 
  1632.  
  1633. if ( isset( $_attribute['is_variation'] ) && $_attribute['is_variation'] ) { 
  1634. $_attribute_key = sanitize_title( $_attribute['name'] ); 
  1635.  
  1636. if ( isset( $_attribute['is_taxonomy'] ) && $_attribute['is_taxonomy'] ) { 
  1637. // Don't use wc_clean as it destroys sanitized characters. 
  1638. $_attribute_value = isset( $attribute['option'] ) ? sanitize_title( stripslashes( $attribute['option'] ) ) : ''; 
  1639. } else { 
  1640. $_attribute_value = isset( $attribute['option'] ) ? wc_clean( stripslashes( $attribute['option'] ) ) : ''; 
  1641.  
  1642. $_attributes[ $_attribute_key ] = $_attribute_value; 
  1643.  
  1644. $variation->set_attributes( $_attributes ); 
  1645.  
  1646. $variation->save(); 
  1647.  
  1648. do_action( 'woocommerce_api_save_product_variation', $variation_id, $menu_order, $variation ); 
  1649.  
  1650. return true; 
  1651.  
  1652. /** 
  1653. * Save product shipping data 
  1654. * @since 2.2 
  1655. * @param WC_Product $product 
  1656. * @param array $data 
  1657. * @return WC_Product 
  1658. */ 
  1659. private function save_product_shipping_data( $product, $data ) { 
  1660. if ( isset( $data['weight'] ) ) { 
  1661. $product->set_weight( '' === $data['weight'] ? '' : wc_format_decimal( $data['weight'] ) ); 
  1662.  
  1663. // Product dimensions 
  1664. if ( isset( $data['dimensions'] ) ) { 
  1665. // Height 
  1666. if ( isset( $data['dimensions']['height'] ) ) { 
  1667. $product->set_height( '' === $data['dimensions']['height'] ? '' : wc_format_decimal( $data['dimensions']['height'] ) ); 
  1668.  
  1669. // Width 
  1670. if ( isset( $data['dimensions']['width'] ) ) { 
  1671. $product->set_width( '' === $data['dimensions']['width'] ? '' : wc_format_decimal( $data['dimensions']['width'] ) ); 
  1672.  
  1673. // Length 
  1674. if ( isset( $data['dimensions']['length'] ) ) { 
  1675. $product->set_length( '' === $data['dimensions']['length'] ? '' : wc_format_decimal( $data['dimensions']['length'] ) ); 
  1676.  
  1677. // Virtual 
  1678. if ( isset( $data['virtual'] ) ) { 
  1679. $virtual = ( true === $data['virtual'] ) ? 'yes' : 'no'; 
  1680.  
  1681. if ( 'yes' == $virtual ) { 
  1682. $product->set_weight( '' ); 
  1683. $product->set_height( '' ); 
  1684. $product->set_length( '' ); 
  1685. $product->set_width( '' ); 
  1686.  
  1687. // Shipping class 
  1688. if ( isset( $data['shipping_class'] ) ) { 
  1689. $data_store = $product->get_data_store(); 
  1690. $shipping_class_id = $data_store->get_shipping_class_id_by_slug( wc_clean( $data['shipping_class'] ) ); 
  1691. if ( $shipping_class_id ) { 
  1692. $product->set_shipping_class_id( $shipping_class_id ); 
  1693.  
  1694. return $product; 
  1695.  
  1696. /** 
  1697. * Save downloadable files 
  1698. * @since 2.2 
  1699. * @param WC_Product $product 
  1700. * @param array $downloads 
  1701. * @param int $deprecated Deprecated since 3.0. 
  1702. * @return WC_Product 
  1703. */ 
  1704. private function save_downloadable_files( $product, $downloads, $deprecated = 0 ) { 
  1705. if ( $deprecated ) { 
  1706. wc_deprecated_argument( 'variation_id', '3.0', 'save_downloadable_files() does not require a variation_id anymore.' ); 
  1707.  
  1708. $files = array(); 
  1709. foreach ( $downloads as $key => $file ) { 
  1710. if ( isset( $file['url'] ) ) { 
  1711. $file['file'] = $file['url']; 
  1712.  
  1713. if ( empty( $file['file'] ) ) { 
  1714. continue; 
  1715.  
  1716. $download = new WC_Product_Download(); 
  1717. $download->set_id( $key ); 
  1718. $download->set_name( $file['name'] ? $file['name'] : wc_get_filename_from_url( $file['file'] ) ); 
  1719. $download->set_file( apply_filters( 'woocommerce_file_download_path', $file['file'], $product, $key ) ); 
  1720. $files[] = $download; 
  1721. $product->set_downloads( $files ); 
  1722.  
  1723. return $product; 
  1724.  
  1725. /** 
  1726. * Get attribute taxonomy by slug. 
  1727. * @since 2.2 
  1728. * @param string $slug 
  1729. * @return string|null 
  1730. */ 
  1731. private function get_attribute_taxonomy_by_slug( $slug ) { 
  1732. $taxonomy = null; 
  1733. $attribute_taxonomies = wc_get_attribute_taxonomies(); 
  1734.  
  1735. foreach ( $attribute_taxonomies as $key => $tax ) { 
  1736. if ( $slug == $tax->attribute_name ) { 
  1737. $taxonomy = 'pa_' . $tax->attribute_name; 
  1738.  
  1739. break; 
  1740.  
  1741. return $taxonomy; 
  1742.  
  1743. /** 
  1744. * Get the images for a product or product variation 
  1745. * @since 2.1 
  1746. * @param WC_Product|WC_Product_Variation $product 
  1747. * @return array 
  1748. */ 
  1749. private function get_images( $product ) { 
  1750. $images = $attachment_ids = array(); 
  1751. $product_image = $product->get_image_id(); 
  1752.  
  1753. // Add featured image. 
  1754. if ( ! empty( $product_image ) ) { 
  1755. $attachment_ids[] = $product_image; 
  1756.  
  1757. // Add gallery images. 
  1758. $attachment_ids = array_merge( $attachment_ids, $product->get_gallery_image_ids() ); 
  1759.  
  1760. // Build image data. 
  1761. foreach ( $attachment_ids as $position => $attachment_id ) { 
  1762.  
  1763. $attachment_post = get_post( $attachment_id ); 
  1764.  
  1765. if ( is_null( $attachment_post ) ) { 
  1766. continue; 
  1767.  
  1768. $attachment = wp_get_attachment_image_src( $attachment_id, 'full' ); 
  1769.  
  1770. if ( ! is_array( $attachment ) ) { 
  1771. continue; 
  1772.  
  1773. $images[] = array( 
  1774. 'id' => (int) $attachment_id,  
  1775. 'created_at' => $this->server->format_datetime( $attachment_post->post_date_gmt ),  
  1776. 'updated_at' => $this->server->format_datetime( $attachment_post->post_modified_gmt ),  
  1777. 'src' => current( $attachment ),  
  1778. 'title' => get_the_title( $attachment_id ),  
  1779. 'alt' => get_post_meta( $attachment_id, '_wp_attachment_image_alt', true ),  
  1780. 'position' => (int) $position,  
  1781. ); 
  1782.  
  1783. // Set a placeholder image if the product has no images set. 
  1784. if ( empty( $images ) ) { 
  1785.  
  1786. $images[] = array( 
  1787. 'id' => 0,  
  1788. 'created_at' => $this->server->format_datetime( time() ), // Default to now. 
  1789. 'updated_at' => $this->server->format_datetime( time() ),  
  1790. 'src' => wc_placeholder_img_src(),  
  1791. 'title' => __( 'Placeholder', 'woocommerce' ),  
  1792. 'alt' => __( 'Placeholder', 'woocommerce' ),  
  1793. 'position' => 0,  
  1794. ); 
  1795.  
  1796. return $images; 
  1797.  
  1798. /** 
  1799. * Save product images. 
  1800. * @since 2.2 
  1801. * @param WC_Product $product 
  1802. * @param array $images 
  1803. * @throws WC_API_Exception 
  1804. * @return WC_Product 
  1805. */ 
  1806. protected function save_product_images( $product, $images ) { 
  1807. if ( is_array( $images ) ) { 
  1808. $gallery = array(); 
  1809.  
  1810. foreach ( $images as $image ) { 
  1811. if ( isset( $image['position'] ) && 0 == $image['position'] ) { 
  1812. $attachment_id = isset( $image['id'] ) ? absint( $image['id'] ) : 0; 
  1813.  
  1814. if ( 0 === $attachment_id && isset( $image['src'] ) ) { 
  1815. $upload = $this->upload_product_image( esc_url_raw( $image['src'] ) ); 
  1816.  
  1817. if ( is_wp_error( $upload ) ) { 
  1818. throw new WC_API_Exception( 'woocommerce_api_cannot_upload_product_image', $upload->get_error_message(), 400 ); 
  1819.  
  1820. $attachment_id = $this->set_product_image_as_attachment( $upload, $product->get_id() ); 
  1821.  
  1822. $product->set_image_id( $attachment_id ); 
  1823. } else { 
  1824. $attachment_id = isset( $image['id'] ) ? absint( $image['id'] ) : 0; 
  1825.  
  1826. if ( 0 === $attachment_id && isset( $image['src'] ) ) { 
  1827. $upload = $this->upload_product_image( esc_url_raw( $image['src'] ) ); 
  1828.  
  1829. if ( is_wp_error( $upload ) ) { 
  1830. throw new WC_API_Exception( 'woocommerce_api_cannot_upload_product_image', $upload->get_error_message(), 400 ); 
  1831.  
  1832. $attachment_id = $this->set_product_image_as_attachment( $upload, $product->get_id() ); 
  1833.  
  1834. $gallery[] = $attachment_id; 
  1835.  
  1836. // Set the image alt if present. 
  1837. if ( ! empty( $image['alt'] ) && $attachment_id ) { 
  1838. update_post_meta( $attachment_id, '_wp_attachment_image_alt', wc_clean( $image['alt'] ) ); 
  1839.  
  1840. // Set the image title if present. 
  1841. if ( ! empty( $image['title'] ) && $attachment_id ) { 
  1842. wp_update_post( array( 'ID' => $attachment_id, 'post_title' => $image['title'] ) ); 
  1843.  
  1844. if ( ! empty( $gallery ) ) { 
  1845. $product->set_gallery_image_ids( $gallery ); 
  1846. } else { 
  1847. $product->set_image_id( '' ); 
  1848. $product->set_gallery_image_ids( array() ); 
  1849.  
  1850. return $product; 
  1851.  
  1852. /** 
  1853. * Upload image from URL 
  1854. * @since 2.2 
  1855. * @param string $image_url 
  1856. * @return int|WP_Error attachment id 
  1857. */ 
  1858. public function upload_product_image( $image_url ) { 
  1859. return $this->upload_image_from_url( $image_url, 'product_image' ); 
  1860.  
  1861. /** 
  1862. * Upload product category image from URL. 
  1863. * @since 2.5.0 
  1864. * @param string $image_url 
  1865. * @return int|WP_Error attachment id 
  1866. */ 
  1867. public function upload_product_category_image( $image_url ) { 
  1868. return $this->upload_image_from_url( $image_url, 'product_category_image' ); 
  1869.  
  1870. /** 
  1871. * Upload image from URL. 
  1872. * @throws WC_API_Exception 
  1873. * @since 2.5.0 
  1874. * @param string $image_url 
  1875. * @param string $upload_for 
  1876. * @return int|WP_Error Attachment id 
  1877. */ 
  1878. protected function upload_image_from_url( $image_url, $upload_for = 'product_image' ) { 
  1879. $file_name = basename( current( explode( '?', $image_url ) ) ); 
  1880. $parsed_url = @parse_url( $image_url ); 
  1881.  
  1882. // Check parsed URL. 
  1883. if ( ! $parsed_url || ! is_array( $parsed_url ) ) { 
  1884. throw new WC_API_Exception( 'woocommerce_api_invalid_' . $upload_for, sprintf( __( 'Invalid URL %s.', 'woocommerce' ), $image_url ), 400 ); 
  1885.  
  1886. // Ensure url is valid. 
  1887. $image_url = str_replace( ' ', '%20', $image_url ); 
  1888.  
  1889. // Get the file. 
  1890. $response = wp_safe_remote_get( $image_url, array( 
  1891. 'timeout' => 10,  
  1892. ) ); 
  1893.  
  1894. if ( is_wp_error( $response ) ) { 
  1895. throw new WC_API_Exception( 'woocommerce_api_invalid_remote_' . $upload_for, sprintf( __( 'Error getting remote image %s.', 'woocommerce' ), $image_url ) . ' ' . sprintf( __( 'Error: %s.', 'woocommerce' ), $response->get_error_message() ), 400 ); 
  1896. } elseif ( 200 !== wp_remote_retrieve_response_code( $response ) ) { 
  1897. throw new WC_API_Exception( 'woocommerce_api_invalid_remote_' . $upload_for, sprintf( __( 'Error getting remote image %s.', 'woocommerce' ), $image_url ), 400 ); 
  1898.  
  1899. // Ensure we have a file name and type. 
  1900. $wp_filetype = wp_check_filetype( $file_name, wc_rest_allowed_image_mime_types() ); 
  1901.  
  1902. if ( ! $wp_filetype['type'] ) { 
  1903. $headers = wp_remote_retrieve_headers( $response ); 
  1904. if ( isset( $headers['content-disposition'] ) && strstr( $headers['content-disposition'], 'filename=' ) ) { 
  1905. $disposition = end( explode( 'filename=', $headers['content-disposition'] ) ); 
  1906. $disposition = sanitize_file_name( $disposition ); 
  1907. $file_name = $disposition; 
  1908. } elseif ( isset( $headers['content-type'] ) && strstr( $headers['content-type'], 'image/' ) ) { 
  1909. $file_name = 'image.' . str_replace( 'image/', '', $headers['content-type'] ); 
  1910. unset( $headers ); 
  1911.  
  1912. // Recheck filetype 
  1913. $wp_filetype = wp_check_filetype( $file_name, wc_rest_allowed_image_mime_types() ); 
  1914.  
  1915. if ( ! $wp_filetype['type'] ) { 
  1916. throw new WC_API_Exception( 'woocommerce_api_invalid_' . $upload_for, __( 'Invalid image type.', 'woocommerce' ), 400 ); 
  1917.  
  1918. // Upload the file. 
  1919. $upload = wp_upload_bits( $file_name, '', wp_remote_retrieve_body( $response ) ); 
  1920.  
  1921. if ( $upload['error'] ) { 
  1922. throw new WC_API_Exception( 'woocommerce_api_' . $upload_for . '_upload_error', $upload['error'], 400 ); 
  1923.  
  1924. // Get filesize. 
  1925. $filesize = filesize( $upload['file'] ); 
  1926.  
  1927. if ( 0 == $filesize ) { 
  1928. @unlink( $upload['file'] ); 
  1929. unset( $upload ); 
  1930. throw new WC_API_Exception( 'woocommerce_api_' . $upload_for . '_upload_file_error', __( 'Zero size file downloaded.', 'woocommerce' ), 400 ); 
  1931.  
  1932. unset( $response ); 
  1933.  
  1934. do_action( 'woocommerce_api_uploaded_image_from_url', $upload, $image_url, $upload_for ); 
  1935.  
  1936. return $upload; 
  1937.  
  1938. /** 
  1939. * Sets product image as attachment and returns the attachment ID. 
  1940. * @since 2.2 
  1941. * @param array $upload 
  1942. * @param int $id 
  1943. * @return int 
  1944. */ 
  1945. protected function set_product_image_as_attachment( $upload, $id ) { 
  1946. return $this->set_uploaded_image_as_attachment( $upload, $id ); 
  1947.  
  1948. /** 
  1949. * Sets uploaded category image as attachment and returns the attachment ID. 
  1950. * @since 2.5.0 
  1951. * @param integer $upload Upload information from wp_upload_bits 
  1952. * @return int Attachment ID 
  1953. */ 
  1954. protected function set_product_category_image_as_attachment( $upload ) { 
  1955. return $this->set_uploaded_image_as_attachment( $upload ); 
  1956.  
  1957. /** 
  1958. * Set uploaded image as attachment. 
  1959. * @since 2.5.0 
  1960. * @param array $upload Upload information from wp_upload_bits 
  1961. * @param int $id Post ID. Default to 0. 
  1962. * @return int Attachment ID 
  1963. */ 
  1964. protected function set_uploaded_image_as_attachment( $upload, $id = 0 ) { 
  1965. $info = wp_check_filetype( $upload['file'] ); 
  1966. $title = ''; 
  1967. $content = ''; 
  1968.  
  1969. if ( $image_meta = @wp_read_image_metadata( $upload['file'] ) ) { 
  1970. if ( trim( $image_meta['title'] ) && ! is_numeric( sanitize_title( $image_meta['title'] ) ) ) { 
  1971. $title = wc_clean( $image_meta['title'] ); 
  1972. if ( trim( $image_meta['caption'] ) ) { 
  1973. $content = wc_clean( $image_meta['caption'] ); 
  1974.  
  1975. $attachment = array( 
  1976. 'post_mime_type' => $info['type'],  
  1977. 'guid' => $upload['url'],  
  1978. 'post_parent' => $id,  
  1979. 'post_title' => $title,  
  1980. 'post_content' => $content,  
  1981. ); 
  1982.  
  1983. $attachment_id = wp_insert_attachment( $attachment, $upload['file'], $id ); 
  1984. if ( ! is_wp_error( $attachment_id ) ) { 
  1985. wp_update_attachment_metadata( $attachment_id, wp_generate_attachment_metadata( $attachment_id, $upload['file'] ) ); 
  1986.  
  1987. return $attachment_id; 
  1988.  
  1989. /** 
  1990. * Get attribute options. 
  1991. * @param int $product_id 
  1992. * @param array $attribute 
  1993. * @return array 
  1994. */ 
  1995. protected function get_attribute_options( $product_id, $attribute ) { 
  1996. if ( isset( $attribute['is_taxonomy'] ) && $attribute['is_taxonomy'] ) { 
  1997. return wc_get_product_terms( $product_id, $attribute['name'], array( 'fields' => 'names' ) ); 
  1998. } elseif ( isset( $attribute['value'] ) ) { 
  1999. return array_map( 'trim', explode( '|', $attribute['value'] ) ); 
  2000.  
  2001. return array(); 
  2002.  
  2003. /** 
  2004. * Get the attributes for a product or product variation 
  2005. * @since 2.1 
  2006. * @param WC_Product|WC_Product_Variation $product 
  2007. * @return array 
  2008. */ 
  2009. private function get_attributes( $product ) { 
  2010.  
  2011. $attributes = array(); 
  2012.  
  2013. if ( $product->is_type( 'variation' ) ) { 
  2014.  
  2015. // variation attributes 
  2016. foreach ( $product->get_variation_attributes() as $attribute_name => $attribute ) { 
  2017.  
  2018. // taxonomy-based attributes are prefixed with `pa_`, otherwise simply `attribute_` 
  2019. $attributes[] = array( 
  2020. 'name' => wc_attribute_label( str_replace( 'attribute_', '', $attribute_name ), $product ),  
  2021. 'slug' => str_replace( 'attribute_', '', str_replace( 'pa_', '', $attribute_name ) ),  
  2022. 'option' => $attribute,  
  2023. ); 
  2024. } else { 
  2025.  
  2026. foreach ( $product->get_attributes() as $attribute ) { 
  2027. $attributes[] = array( 
  2028. 'name' => wc_attribute_label( $attribute['name'], $product ),  
  2029. 'slug' => str_replace( 'pa_', '', $attribute['name'] ),  
  2030. 'position' => (int) $attribute['position'],  
  2031. 'visible' => (bool) $attribute['is_visible'],  
  2032. 'variation' => (bool) $attribute['is_variation'],  
  2033. 'options' => $this->get_attribute_options( $product->get_id(), $attribute ),  
  2034. ); 
  2035.  
  2036. return $attributes; 
  2037.  
  2038. /** 
  2039. * Get the downloads for a product or product variation 
  2040. * @since 2.1 
  2041. * @param WC_Product|WC_Product_Variation $product 
  2042. * @return array 
  2043. */ 
  2044. private function get_downloads( $product ) { 
  2045.  
  2046. $downloads = array(); 
  2047.  
  2048. if ( $product->is_downloadable() ) { 
  2049.  
  2050. foreach ( $product->get_downloads() as $file_id => $file ) { 
  2051.  
  2052. $downloads[] = array( 
  2053. 'id' => $file_id, // do not cast as int as this is a hash 
  2054. 'name' => $file['name'],  
  2055. 'file' => $file['file'],  
  2056. ); 
  2057.  
  2058. return $downloads; 
  2059.  
  2060. /** 
  2061. * Get a listing of product attributes 
  2062. * @since 2.5.0 
  2063. * @param string|null $fields fields to limit response to 
  2064. * @return array 
  2065. */ 
  2066. public function get_product_attributes( $fields = null ) { 
  2067. try { 
  2068. // Permissions check. 
  2069. if ( ! current_user_can( 'manage_product_terms' ) ) { 
  2070. throw new WC_API_Exception( 'woocommerce_api_user_cannot_read_product_attributes', __( 'You do not have permission to read product attributes', 'woocommerce' ), 401 ); 
  2071.  
  2072. $product_attributes = array(); 
  2073. $attribute_taxonomies = wc_get_attribute_taxonomies(); 
  2074.  
  2075. foreach ( $attribute_taxonomies as $attribute ) { 
  2076. $product_attributes[] = array( 
  2077. 'id' => intval( $attribute->attribute_id ),  
  2078. 'name' => $attribute->attribute_label,  
  2079. 'slug' => wc_attribute_taxonomy_name( $attribute->attribute_name ),  
  2080. 'type' => $attribute->attribute_type,  
  2081. 'order_by' => $attribute->attribute_orderby,  
  2082. 'has_archives' => (bool) $attribute->attribute_public,  
  2083. ); 
  2084.  
  2085. return array( 'product_attributes' => apply_filters( 'woocommerce_api_product_attributes_response', $product_attributes, $attribute_taxonomies, $fields, $this ) ); 
  2086. } catch ( WC_API_Exception $e ) { 
  2087. return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) ); 
  2088.  
  2089. /** 
  2090. * Get the product attribute for the given ID 
  2091. * @since 2.5.0 
  2092. * @param string $id product attribute term ID 
  2093. * @param string|null $fields fields to limit response to 
  2094. * @return array 
  2095. */ 
  2096. public function get_product_attribute( $id, $fields = null ) { 
  2097. global $wpdb; 
  2098.  
  2099. try { 
  2100. $id = absint( $id ); 
  2101.  
  2102. // Validate ID 
  2103. if ( empty( $id ) ) { 
  2104. throw new WC_API_Exception( 'woocommerce_api_invalid_product_attribute_id', __( 'Invalid product attribute ID', 'woocommerce' ), 400 ); 
  2105.  
  2106. // Permissions check 
  2107. if ( ! current_user_can( 'manage_product_terms' ) ) { 
  2108. throw new WC_API_Exception( 'woocommerce_api_user_cannot_read_product_attributes', __( 'You do not have permission to read product attributes', 'woocommerce' ), 401 ); 
  2109.  
  2110. $attribute = $wpdb->get_row( $wpdb->prepare( " 
  2111. SELECT * 
  2112. FROM {$wpdb->prefix}woocommerce_attribute_taxonomies 
  2113. WHERE attribute_id = %d 
  2114. ", $id ) ); 
  2115.  
  2116. if ( is_wp_error( $attribute ) || is_null( $attribute ) ) { 
  2117. throw new WC_API_Exception( 'woocommerce_api_invalid_product_attribute_id', __( 'A product attribute with the provided ID could not be found', 'woocommerce' ), 404 ); 
  2118.  
  2119. $product_attribute = array( 
  2120. 'id' => intval( $attribute->attribute_id ),  
  2121. 'name' => $attribute->attribute_label,  
  2122. 'slug' => wc_attribute_taxonomy_name( $attribute->attribute_name ),  
  2123. 'type' => $attribute->attribute_type,  
  2124. 'order_by' => $attribute->attribute_orderby,  
  2125. 'has_archives' => (bool) $attribute->attribute_public,  
  2126. ); 
  2127.  
  2128. return array( 'product_attribute' => apply_filters( 'woocommerce_api_product_attribute_response', $product_attribute, $id, $fields, $attribute, $this ) ); 
  2129. } catch ( WC_API_Exception $e ) { 
  2130. return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) ); 
  2131.  
  2132. /** 
  2133. * Validate attribute data. 
  2134. * @since 2.5.0 
  2135. * @param string $name 
  2136. * @param string $slug 
  2137. * @param string $type 
  2138. * @param string $order_by 
  2139. * @param bool $new_data 
  2140. * @return bool 
  2141. * @throws WC_API_Exception 
  2142. */ 
  2143. protected function validate_attribute_data( $name, $slug, $type, $order_by, $new_data = true ) { 
  2144. if ( empty( $name ) ) { 
  2145. throw new WC_API_Exception( 'woocommerce_api_missing_product_attribute_name', sprintf( __( 'Missing parameter %s', 'woocommerce' ), 'name' ), 400 ); 
  2146.  
  2147. if ( strlen( $slug ) >= 28 ) { 
  2148. throw new WC_API_Exception( 'woocommerce_api_invalid_product_attribute_slug_too_long', sprintf( __( 'Slug "%s" is too long (28 characters max). Shorten it, please.', 'woocommerce' ), $slug ), 400 ); 
  2149. } elseif ( wc_check_if_attribute_name_is_reserved( $slug ) ) { 
  2150. throw new WC_API_Exception( 'woocommerce_api_invalid_product_attribute_slug_reserved_name', sprintf( __( 'Slug "%s" is not allowed because it is a reserved term. Change it, please.', 'woocommerce' ), $slug ), 400 ); 
  2151. } elseif ( $new_data && taxonomy_exists( wc_attribute_taxonomy_name( $slug ) ) ) { 
  2152. throw new WC_API_Exception( 'woocommerce_api_invalid_product_attribute_slug_already_exists', sprintf( __( 'Slug "%s" is already in use. Change it, please.', 'woocommerce' ), $slug ), 400 ); 
  2153.  
  2154. // Validate the attribute type 
  2155. if ( ! in_array( wc_clean( $type ), array_keys( wc_get_attribute_types() ) ) ) { 
  2156. throw new WC_API_Exception( 'woocommerce_api_invalid_product_attribute_type', sprintf( __( 'Invalid product attribute type - the product attribute type must be any of these: %s', 'woocommerce' ), implode( ', ', array_keys( wc_get_attribute_types() ) ) ), 400 ); 
  2157.  
  2158. // Validate the attribute order by 
  2159. if ( ! in_array( wc_clean( $order_by ), array( 'menu_order', 'name', 'name_num', 'id' ) ) ) { 
  2160. throw new WC_API_Exception( 'woocommerce_api_invalid_product_attribute_order_by', sprintf( __( 'Invalid product attribute order_by type - the product attribute order_by type must be any of these: %s', 'woocommerce' ), implode( ', ', array( 'menu_order', 'name', 'name_num', 'id' ) ) ), 400 ); 
  2161.  
  2162. return true; 
  2163.  
  2164. /** 
  2165. * Create a new product attribute. 
  2166. * @since 2.5.0 
  2167. * @param array $data Posted data. 
  2168. * @return array 
  2169. */ 
  2170. public function create_product_attribute( $data ) { 
  2171. global $wpdb; 
  2172.  
  2173. try { 
  2174. if ( ! isset( $data['product_attribute'] ) ) { 
  2175. throw new WC_API_Exception( 'woocommerce_api_missing_product_attribute_data', sprintf( __( 'No %1$s data specified to create %1$s', 'woocommerce' ), 'product_attribute' ), 400 ); 
  2176.  
  2177. $data = $data['product_attribute']; 
  2178.  
  2179. // Check permissions. 
  2180. if ( ! current_user_can( 'manage_product_terms' ) ) { 
  2181. throw new WC_API_Exception( 'woocommerce_api_user_cannot_create_product_attribute', __( 'You do not have permission to create product attributes', 'woocommerce' ), 401 ); 
  2182.  
  2183. $data = apply_filters( 'woocommerce_api_create_product_attribute_data', $data, $this ); 
  2184.  
  2185. if ( ! isset( $data['name'] ) ) { 
  2186. $data['name'] = ''; 
  2187.  
  2188. // Set the attribute slug. 
  2189. if ( ! isset( $data['slug'] ) ) { 
  2190. $data['slug'] = wc_sanitize_taxonomy_name( stripslashes( $data['name'] ) ); 
  2191. } else { 
  2192. $data['slug'] = preg_replace( '/^pa\_/', '', wc_sanitize_taxonomy_name( stripslashes( $data['slug'] ) ) ); 
  2193.  
  2194. // Set attribute type when not sent. 
  2195. if ( ! isset( $data['type'] ) ) { 
  2196. $data['type'] = 'select'; 
  2197.  
  2198. // Set order by when not sent. 
  2199. if ( ! isset( $data['order_by'] ) ) { 
  2200. $data['order_by'] = 'menu_order'; 
  2201.  
  2202. // Validate the attribute data. 
  2203. $this->validate_attribute_data( $data['name'], $data['slug'], $data['type'], $data['order_by'], true ); 
  2204.  
  2205. $insert = $wpdb->insert( 
  2206. $wpdb->prefix . 'woocommerce_attribute_taxonomies',  
  2207. array( 
  2208. 'attribute_label' => $data['name'],  
  2209. 'attribute_name' => $data['slug'],  
  2210. 'attribute_type' => $data['type'],  
  2211. 'attribute_orderby' => $data['order_by'],  
  2212. 'attribute_public' => isset( $data['has_archives'] ) && true === $data['has_archives'] ? 1 : 0,  
  2213. ),  
  2214. array( '%s', '%s', '%s', '%s', '%d' ) 
  2215. ); 
  2216.  
  2217. // Checks for an error in the product creation. 
  2218. if ( is_wp_error( $insert ) ) { 
  2219. throw new WC_API_Exception( 'woocommerce_api_cannot_create_product_attribute', $insert->get_error_message(), 400 ); 
  2220.  
  2221. $id = $wpdb->insert_id; 
  2222.  
  2223. do_action( 'woocommerce_api_create_product_attribute', $id, $data ); 
  2224.  
  2225. // Clear transients. 
  2226. wp_schedule_single_event( time(), 'woocommerce_flush_rewrite_rules' ); 
  2227. delete_transient( 'wc_attribute_taxonomies' ); 
  2228.  
  2229. $this->server->send_status( 201 ); 
  2230.  
  2231. return $this->get_product_attribute( $id ); 
  2232. } catch ( WC_API_Exception $e ) { 
  2233. return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) ); 
  2234.  
  2235. /** 
  2236. * Edit a product attribute. 
  2237. * @since 2.5.0 
  2238. * @param int $id the attribute ID. 
  2239. * @param array $data 
  2240. * @return array 
  2241. */ 
  2242. public function edit_product_attribute( $id, $data ) { 
  2243. global $wpdb; 
  2244.  
  2245. try { 
  2246. if ( ! isset( $data['product_attribute'] ) ) { 
  2247. throw new WC_API_Exception( 'woocommerce_api_missing_product_attribute_data', sprintf( __( 'No %1$s data specified to edit %1$s', 'woocommerce' ), 'product_attribute' ), 400 ); 
  2248.  
  2249. $id = absint( $id ); 
  2250. $data = $data['product_attribute']; 
  2251.  
  2252. // Check permissions. 
  2253. if ( ! current_user_can( 'manage_product_terms' ) ) { 
  2254. throw new WC_API_Exception( 'woocommerce_api_user_cannot_edit_product_attribute', __( 'You do not have permission to edit product attributes', 'woocommerce' ), 401 ); 
  2255.  
  2256. $data = apply_filters( 'woocommerce_api_edit_product_attribute_data', $data, $this ); 
  2257. $attribute = $this->get_product_attribute( $id ); 
  2258.  
  2259. if ( is_wp_error( $attribute ) ) { 
  2260. return $attribute; 
  2261.  
  2262. $attribute_name = isset( $data['name'] ) ? $data['name'] : $attribute['product_attribute']['name']; 
  2263. $attribute_type = isset( $data['type'] ) ? $data['type'] : $attribute['product_attribute']['type']; 
  2264. $attribute_order_by = isset( $data['order_by'] ) ? $data['order_by'] : $attribute['product_attribute']['order_by']; 
  2265.  
  2266. if ( isset( $data['slug'] ) ) { 
  2267. $attribute_slug = wc_sanitize_taxonomy_name( stripslashes( $data['slug'] ) ); 
  2268. } else { 
  2269. $attribute_slug = $attribute['product_attribute']['slug']; 
  2270. $attribute_slug = preg_replace( '/^pa\_/', '', $attribute_slug ); 
  2271.  
  2272. if ( isset( $data['has_archives'] ) ) { 
  2273. $attribute_public = true === $data['has_archives'] ? 1 : 0; 
  2274. } else { 
  2275. $attribute_public = $attribute['product_attribute']['has_archives']; 
  2276.  
  2277. // Validate the attribute data. 
  2278. $this->validate_attribute_data( $attribute_name, $attribute_slug, $attribute_type, $attribute_order_by, false ); 
  2279.  
  2280. $update = $wpdb->update( 
  2281. $wpdb->prefix . 'woocommerce_attribute_taxonomies',  
  2282. array( 
  2283. 'attribute_label' => $attribute_name,  
  2284. 'attribute_name' => $attribute_slug,  
  2285. 'attribute_type' => $attribute_type,  
  2286. 'attribute_orderby' => $attribute_order_by,  
  2287. 'attribute_public' => $attribute_public,  
  2288. ),  
  2289. array( 'attribute_id' => $id ),  
  2290. array( '%s', '%s', '%s', '%s', '%d' ),  
  2291. array( '%d' ) 
  2292. ); 
  2293.  
  2294. // Checks for an error in the product creation. 
  2295. if ( false === $update ) { 
  2296. throw new WC_API_Exception( 'woocommerce_api_cannot_edit_product_attribute', __( 'Could not edit the attribute', 'woocommerce' ), 400 ); 
  2297.  
  2298. do_action( 'woocommerce_api_edit_product_attribute', $id, $data ); 
  2299.  
  2300. // Clear transients. 
  2301. wp_schedule_single_event( time(), 'woocommerce_flush_rewrite_rules' ); 
  2302. delete_transient( 'wc_attribute_taxonomies' ); 
  2303.  
  2304. return $this->get_product_attribute( $id ); 
  2305. } catch ( WC_API_Exception $e ) { 
  2306. return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) ); 
  2307.  
  2308. /** 
  2309. * Delete a product attribute. 
  2310. * @since 2.5.0 
  2311. * @param int $id the product attribute ID. 
  2312. * @return array 
  2313. */ 
  2314. public function delete_product_attribute( $id ) { 
  2315. global $wpdb; 
  2316.  
  2317. try { 
  2318. // Check permissions. 
  2319. if ( ! current_user_can( 'manage_product_terms' ) ) { 
  2320. throw new WC_API_Exception( 'woocommerce_api_user_cannot_delete_product_attribute', __( 'You do not have permission to delete product attributes', 'woocommerce' ), 401 ); 
  2321.  
  2322. $id = absint( $id ); 
  2323.  
  2324. $attribute_name = $wpdb->get_var( $wpdb->prepare( " 
  2325. SELECT attribute_name 
  2326. FROM {$wpdb->prefix}woocommerce_attribute_taxonomies 
  2327. WHERE attribute_id = %d 
  2328. ", $id ) ); 
  2329.  
  2330. if ( is_null( $attribute_name ) ) { 
  2331. throw new WC_API_Exception( 'woocommerce_api_invalid_product_attribute_id', __( 'A product attribute with the provided ID could not be found', 'woocommerce' ), 404 ); 
  2332.  
  2333. $deleted = $wpdb->delete( 
  2334. $wpdb->prefix . 'woocommerce_attribute_taxonomies',  
  2335. array( 'attribute_id' => $id ),  
  2336. array( '%d' ) 
  2337. ); 
  2338.  
  2339. if ( false === $deleted ) { 
  2340. throw new WC_API_Exception( 'woocommerce_api_cannot_delete_product_attribute', __( 'Could not delete the attribute', 'woocommerce' ), 401 ); 
  2341.  
  2342. $taxonomy = wc_attribute_taxonomy_name( $attribute_name ); 
  2343.  
  2344. if ( taxonomy_exists( $taxonomy ) ) { 
  2345. $terms = get_terms( $taxonomy, 'orderby=name&hide_empty=0' ); 
  2346. foreach ( $terms as $term ) { 
  2347. wp_delete_term( $term->term_id, $taxonomy ); 
  2348.  
  2349. do_action( 'woocommerce_attribute_deleted', $id, $attribute_name, $taxonomy ); 
  2350. do_action( 'woocommerce_api_delete_product_attribute', $id, $this ); 
  2351.  
  2352. // Clear transients. 
  2353. wp_schedule_single_event( time(), 'woocommerce_flush_rewrite_rules' ); 
  2354. delete_transient( 'wc_attribute_taxonomies' ); 
  2355.  
  2356. return array( 'message' => sprintf( __( 'Deleted %s', 'woocommerce' ), 'product_attribute' ) ); 
  2357. } catch ( WC_API_Exception $e ) { 
  2358. return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) ); 
  2359.  
  2360. /** 
  2361. * Get a listing of product attribute terms. 
  2362. * @since 2.5.0 
  2363. * @param int $attribute_id Attribute ID. 
  2364. * @param string|null $fields Fields to limit response to. 
  2365. * @return array 
  2366. */ 
  2367. public function get_product_attribute_terms( $attribute_id, $fields = null ) { 
  2368. try { 
  2369. // Permissions check. 
  2370. if ( ! current_user_can( 'manage_product_terms' ) ) { 
  2371. throw new WC_API_Exception( 'woocommerce_api_user_cannot_read_product_attribute_terms', __( 'You do not have permission to read product attribute terms', 'woocommerce' ), 401 ); 
  2372.  
  2373. $taxonomy = wc_attribute_taxonomy_name_by_id( $attribute_id ); 
  2374.  
  2375. if ( ! $taxonomy ) { 
  2376. throw new WC_API_Exception( 'woocommerce_api_invalid_product_attribute_id', __( 'A product attribute with the provided ID could not be found', 'woocommerce' ), 404 ); 
  2377.  
  2378. $args = array( 'hide_empty' => false ); 
  2379. $orderby = wc_attribute_orderby( $taxonomy ); 
  2380.  
  2381. switch ( $orderby ) { 
  2382. case 'name' : 
  2383. $args['orderby'] = 'name'; 
  2384. $args['menu_order'] = false; 
  2385. break; 
  2386. case 'id' : 
  2387. $args['orderby'] = 'id'; 
  2388. $args['order'] = 'ASC'; 
  2389. $args['menu_order'] = false; 
  2390. break; 
  2391. case 'menu_order' : 
  2392. $args['menu_order'] = 'ASC'; 
  2393. break; 
  2394.  
  2395. $terms = get_terms( $taxonomy, $args ); 
  2396. $attribute_terms = array(); 
  2397.  
  2398. foreach ( $terms as $term ) { 
  2399. $attribute_terms[] = array( 
  2400. 'id' => $term->term_id,  
  2401. 'slug' => $term->slug,  
  2402. 'name' => $term->name,  
  2403. 'count' => $term->count,  
  2404. ); 
  2405.  
  2406. return array( 'product_attribute_terms' => apply_filters( 'woocommerce_api_product_attribute_terms_response', $attribute_terms, $terms, $fields, $this ) ); 
  2407. } catch ( WC_API_Exception $e ) { 
  2408. return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) ); 
  2409.  
  2410. /** 
  2411. * Get the product attribute term for the given ID. 
  2412. * @since 2.5.0 
  2413. * @param int $attribute_id Attribute ID. 
  2414. * @param string $id Product attribute term ID. 
  2415. * @param string|null $fields Fields to limit response to. 
  2416. * @return array 
  2417. */ 
  2418. public function get_product_attribute_term( $attribute_id, $id, $fields = null ) { 
  2419. global $wpdb; 
  2420.  
  2421. try { 
  2422. $id = absint( $id ); 
  2423.  
  2424. // Validate ID 
  2425. if ( empty( $id ) ) { 
  2426. throw new WC_API_Exception( 'woocommerce_api_invalid_product_attribute_term_id', __( 'Invalid product attribute ID', 'woocommerce' ), 400 ); 
  2427.  
  2428. // Permissions check 
  2429. if ( ! current_user_can( 'manage_product_terms' ) ) { 
  2430. throw new WC_API_Exception( 'woocommerce_api_user_cannot_read_product_attribute_terms', __( 'You do not have permission to read product attribute terms', 'woocommerce' ), 401 ); 
  2431.  
  2432. $taxonomy = wc_attribute_taxonomy_name_by_id( $attribute_id ); 
  2433.  
  2434. if ( ! $taxonomy ) { 
  2435. throw new WC_API_Exception( 'woocommerce_api_invalid_product_attribute_id', __( 'A product attribute with the provided ID could not be found', 'woocommerce' ), 404 ); 
  2436.  
  2437. $term = get_term( $id, $taxonomy ); 
  2438.  
  2439. if ( is_wp_error( $term ) || is_null( $term ) ) { 
  2440. throw new WC_API_Exception( 'woocommerce_api_invalid_product_attribute_term_id', __( 'A product attribute term with the provided ID could not be found', 'woocommerce' ), 404 ); 
  2441.  
  2442. $attribute_term = array( 
  2443. 'id' => $term->term_id,  
  2444. 'name' => $term->name,  
  2445. 'slug' => $term->slug,  
  2446. 'count' => $term->count,  
  2447. ); 
  2448.  
  2449. return array( 'product_attribute_term' => apply_filters( 'woocommerce_api_product_attribute_response', $attribute_term, $id, $fields, $term, $this ) ); 
  2450. } catch ( WC_API_Exception $e ) { 
  2451. return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) ); 
  2452.  
  2453. /** 
  2454. * Create a new product attribute term. 
  2455. * @since 2.5.0 
  2456. * @param int $attribute_id Attribute ID. 
  2457. * @param array $data Posted data. 
  2458. * @return array 
  2459. */ 
  2460. public function create_product_attribute_term( $attribute_id, $data ) { 
  2461. global $wpdb; 
  2462.  
  2463. try { 
  2464. if ( ! isset( $data['product_attribute_term'] ) ) { 
  2465. throw new WC_API_Exception( 'woocommerce_api_missing_product_attribute_term_data', sprintf( __( 'No %1$s data specified to create %1$s', 'woocommerce' ), 'product_attribute_term' ), 400 ); 
  2466.  
  2467. $data = $data['product_attribute_term']; 
  2468.  
  2469. // Check permissions. 
  2470. if ( ! current_user_can( 'manage_product_terms' ) ) { 
  2471. throw new WC_API_Exception( 'woocommerce_api_user_cannot_create_product_attribute', __( 'You do not have permission to create product attributes', 'woocommerce' ), 401 ); 
  2472.  
  2473. $taxonomy = wc_attribute_taxonomy_name_by_id( $attribute_id ); 
  2474.  
  2475. if ( ! $taxonomy ) { 
  2476. throw new WC_API_Exception( 'woocommerce_api_invalid_product_attribute_id', __( 'A product attribute with the provided ID could not be found', 'woocommerce' ), 404 ); 
  2477.  
  2478. $data = apply_filters( 'woocommerce_api_create_product_attribute_term_data', $data, $this ); 
  2479.  
  2480. // Check if attribute term name is specified. 
  2481. if ( ! isset( $data['name'] ) ) { 
  2482. throw new WC_API_Exception( 'woocommerce_api_missing_product_attribute_term_name', sprintf( __( 'Missing parameter %s', 'woocommerce' ), 'name' ), 400 ); 
  2483.  
  2484. $args = array(); 
  2485.  
  2486. // Set the attribute term slug. 
  2487. if ( isset( $data['slug'] ) ) { 
  2488. $args['slug'] = sanitize_title( wp_unslash( $data['slug'] ) ); 
  2489.  
  2490. $term = wp_insert_term( $data['name'], $taxonomy, $args ); 
  2491.  
  2492. // Checks for an error in the term creation. 
  2493. if ( is_wp_error( $term ) ) { 
  2494. throw new WC_API_Exception( 'woocommerce_api_cannot_create_product_attribute', $term->get_error_message(), 400 ); 
  2495.  
  2496. $id = $term['term_id']; 
  2497.  
  2498. do_action( 'woocommerce_api_create_product_attribute_term', $id, $data ); 
  2499.  
  2500. $this->server->send_status( 201 ); 
  2501.  
  2502. return $this->get_product_attribute_term( $attribute_id, $id ); 
  2503. } catch ( WC_API_Exception $e ) { 
  2504. return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) ); 
  2505.  
  2506. /** 
  2507. * Edit a product attribute term. 
  2508. * @since 2.5.0 
  2509. * @param int $attribute_id Attribute ID. 
  2510. * @param int $id the attribute ID. 
  2511. * @param array $data 
  2512. * @return array 
  2513. */ 
  2514. public function edit_product_attribute_term( $attribute_id, $id, $data ) { 
  2515. global $wpdb; 
  2516.  
  2517. try { 
  2518. if ( ! isset( $data['product_attribute_term'] ) ) { 
  2519. throw new WC_API_Exception( 'woocommerce_api_missing_product_attribute_term_data', sprintf( __( 'No %1$s data specified to edit %1$s', 'woocommerce' ), 'product_attribute_term' ), 400 ); 
  2520.  
  2521. $id = absint( $id ); 
  2522. $data = $data['product_attribute_term']; 
  2523.  
  2524. // Check permissions. 
  2525. if ( ! current_user_can( 'manage_product_terms' ) ) { 
  2526. throw new WC_API_Exception( 'woocommerce_api_user_cannot_edit_product_attribute', __( 'You do not have permission to edit product attributes', 'woocommerce' ), 401 ); 
  2527.  
  2528. $taxonomy = wc_attribute_taxonomy_name_by_id( $attribute_id ); 
  2529.  
  2530. if ( ! $taxonomy ) { 
  2531. throw new WC_API_Exception( 'woocommerce_api_invalid_product_attribute_id', __( 'A product attribute with the provided ID could not be found', 'woocommerce' ), 404 ); 
  2532.  
  2533. $data = apply_filters( 'woocommerce_api_edit_product_attribute_term_data', $data, $this ); 
  2534.  
  2535. $args = array(); 
  2536.  
  2537. // Update name. 
  2538. if ( isset( $data['name'] ) ) { 
  2539. $args['name'] = wc_clean( wp_unslash( $data['name'] ) ); 
  2540.  
  2541. // Update slug. 
  2542. if ( isset( $data['slug'] ) ) { 
  2543. $args['slug'] = sanitize_title( wp_unslash( $data['slug'] ) ); 
  2544.  
  2545. $term = wp_update_term( $id, $taxonomy, $args ); 
  2546.  
  2547. if ( is_wp_error( $term ) ) { 
  2548. throw new WC_API_Exception( 'woocommerce_api_cannot_edit_product_attribute_term', $term->get_error_message(), 400 ); 
  2549.  
  2550. do_action( 'woocommerce_api_edit_product_attribute_term', $id, $data ); 
  2551.  
  2552. return $this->get_product_attribute_term( $attribute_id, $id ); 
  2553. } catch ( WC_API_Exception $e ) { 
  2554. return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) ); 
  2555.  
  2556. /** 
  2557. * Delete a product attribute term. 
  2558. * @since 2.5.0 
  2559. * @param int $attribute_id Attribute ID. 
  2560. * @param int $id the product attribute ID. 
  2561. * @return array 
  2562. */ 
  2563. public function delete_product_attribute_term( $attribute_id, $id ) { 
  2564. global $wpdb; 
  2565.  
  2566. try { 
  2567. // Check permissions. 
  2568. if ( ! current_user_can( 'manage_product_terms' ) ) { 
  2569. throw new WC_API_Exception( 'woocommerce_api_user_cannot_delete_product_attribute_term', __( 'You do not have permission to delete product attribute terms', 'woocommerce' ), 401 ); 
  2570.  
  2571. $taxonomy = wc_attribute_taxonomy_name_by_id( $attribute_id ); 
  2572.  
  2573. if ( ! $taxonomy ) { 
  2574. throw new WC_API_Exception( 'woocommerce_api_invalid_product_attribute_id', __( 'A product attribute with the provided ID could not be found', 'woocommerce' ), 404 ); 
  2575.  
  2576. $id = absint( $id ); 
  2577. $term = wp_delete_term( $id, $taxonomy ); 
  2578.  
  2579. if ( ! $term ) { 
  2580. throw new WC_API_Exception( 'woocommerce_api_cannot_delete_product_attribute_term', sprintf( __( 'This %s cannot be deleted', 'woocommerce' ), 'product_attribute_term' ), 500 ); 
  2581. } elseif ( is_wp_error( $term ) ) { 
  2582. throw new WC_API_Exception( 'woocommerce_api_cannot_delete_product_attribute_term', $term->get_error_message(), 400 ); 
  2583.  
  2584. do_action( 'woocommerce_api_delete_product_attribute_term', $id, $this ); 
  2585.  
  2586. return array( 'message' => sprintf( __( 'Deleted %s', 'woocommerce' ), 'product_attribute' ) ); 
  2587. } catch ( WC_API_Exception $e ) { 
  2588. return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) ); 
  2589.  
  2590. /** 
  2591. * Clear product 
  2592. */ 
  2593. protected function clear_product( $product_id ) { 
  2594. if ( ! is_numeric( $product_id ) || 0 >= $product_id ) { 
  2595. return; 
  2596.  
  2597. // Delete product attachments 
  2598. $attachments = get_children( array( 
  2599. 'post_parent' => $product_id,  
  2600. 'post_status' => 'any',  
  2601. 'post_type' => 'attachment',  
  2602. ) ); 
  2603.  
  2604. foreach ( (array) $attachments as $attachment ) { 
  2605. wp_delete_attachment( $attachment->ID, true ); 
  2606.  
  2607. // Delete product 
  2608. $product = wc_get_product( $product_id ); 
  2609. $product->delete( true ); 
  2610.  
  2611. /** 
  2612. * Bulk update or insert products 
  2613. * Accepts an array with products in the formats supported by 
  2614. * WC_API_Products->create_product() and WC_API_Products->edit_product() 
  2615. * @since 2.4.0 
  2616. * @param array $data 
  2617. * @return array 
  2618. */ 
  2619. public function bulk( $data ) { 
  2620.  
  2621. try { 
  2622. if ( ! isset( $data['products'] ) ) { 
  2623. throw new WC_API_Exception( 'woocommerce_api_missing_products_data', sprintf( __( 'No %1$s data specified to create/edit %1$s', 'woocommerce' ), 'products' ), 400 ); 
  2624.  
  2625. $data = $data['products']; 
  2626. $limit = apply_filters( 'woocommerce_api_bulk_limit', 100, 'products' ); 
  2627.  
  2628. // Limit bulk operation 
  2629. if ( count( $data ) > $limit ) { 
  2630. throw new WC_API_Exception( 'woocommerce_api_products_request_entity_too_large', sprintf( __( 'Unable to accept more than %s items for this request.', 'woocommerce' ), $limit ), 413 ); 
  2631.  
  2632. $products = array(); 
  2633.  
  2634. foreach ( $data as $_product ) { 
  2635. $product_id = 0; 
  2636. $product_sku = ''; 
  2637.  
  2638. // Try to get the product ID 
  2639. if ( isset( $_product['id'] ) ) { 
  2640. $product_id = intval( $_product['id'] ); 
  2641.  
  2642. if ( ! $product_id && isset( $_product['sku'] ) ) { 
  2643. $product_sku = wc_clean( $_product['sku'] ); 
  2644. $product_id = wc_get_product_id_by_sku( $product_sku ); 
  2645.  
  2646. if ( $product_id ) { 
  2647.  
  2648. // Product exists / edit product 
  2649. $edit = $this->edit_product( $product_id, array( 'product' => $_product ) ); 
  2650.  
  2651. if ( is_wp_error( $edit ) ) { 
  2652. $products[] = array( 
  2653. 'id' => $product_id,  
  2654. 'sku' => $product_sku,  
  2655. 'error' => array( 'code' => $edit->get_error_code(), 'message' => $edit->get_error_message() ),  
  2656. ); 
  2657. } else { 
  2658. $products[] = $edit['product']; 
  2659. } else { 
  2660.  
  2661. // Product don't exists / create product 
  2662. $new = $this->create_product( array( 'product' => $_product ) ); 
  2663.  
  2664. if ( is_wp_error( $new ) ) { 
  2665. $products[] = array( 
  2666. 'id' => $product_id,  
  2667. 'sku' => $product_sku,  
  2668. 'error' => array( 'code' => $new->get_error_code(), 'message' => $new->get_error_message() ),  
  2669. ); 
  2670. } else { 
  2671. $products[] = $new['product']; 
  2672.  
  2673. return array( 'products' => apply_filters( 'woocommerce_api_products_bulk_response', $products, $this ) ); 
  2674. } catch ( WC_API_Exception $e ) { 
  2675. return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) ); 
  2676.  
  2677. /** 
  2678. * Get a listing of product shipping classes. 
  2679. * @since 2.5.0 
  2680. * @param string|null $fields Fields to limit response to 
  2681. * @return array|WP_Error List of product shipping classes if succeed,  
  2682. * otherwise WP_Error will be returned 
  2683. */ 
  2684. public function get_product_shipping_classes( $fields = null ) { 
  2685. try { 
  2686. // Permissions check 
  2687. if ( ! current_user_can( 'manage_product_terms' ) ) { 
  2688. throw new WC_API_Exception( 'woocommerce_api_user_cannot_read_product_shipping_classes', __( 'You do not have permission to read product shipping classes', 'woocommerce' ), 401 ); 
  2689.  
  2690. $product_shipping_classes = array(); 
  2691.  
  2692. $terms = get_terms( 'product_shipping_class', array( 'hide_empty' => false, 'fields' => 'ids' ) ); 
  2693.  
  2694. foreach ( $terms as $term_id ) { 
  2695. $product_shipping_classes[] = current( $this->get_product_shipping_class( $term_id, $fields ) ); 
  2696.  
  2697. return array( 'product_shipping_classes' => apply_filters( 'woocommerce_api_product_shipping_classes_response', $product_shipping_classes, $terms, $fields, $this ) ); 
  2698. } catch ( WC_API_Exception $e ) { 
  2699. return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) ); 
  2700.  
  2701. /** 
  2702. * Get the product shipping class for the given ID. 
  2703. * @since 2.5.0 
  2704. * @param string $id Product shipping class term ID 
  2705. * @param string|null $fields Fields to limit response to 
  2706. * @return array|WP_Error Product shipping class if succeed, otherwise 
  2707. * WP_Error will be returned 
  2708. */ 
  2709. public function get_product_shipping_class( $id, $fields = null ) { 
  2710. try { 
  2711. $id = absint( $id ); 
  2712. if ( ! $id ) { 
  2713. throw new WC_API_Exception( 'woocommerce_api_invalid_product_shipping_class_id', __( 'Invalid product shipping class ID', 'woocommerce' ), 400 ); 
  2714.  
  2715. // Permissions check 
  2716. if ( ! current_user_can( 'manage_product_terms' ) ) { 
  2717. throw new WC_API_Exception( 'woocommerce_api_user_cannot_read_product_shipping_classes', __( 'You do not have permission to read product shipping classes', 'woocommerce' ), 401 ); 
  2718.  
  2719. $term = get_term( $id, 'product_shipping_class' ); 
  2720.  
  2721. if ( is_wp_error( $term ) || is_null( $term ) ) { 
  2722. throw new WC_API_Exception( 'woocommerce_api_invalid_product_shipping_class_id', __( 'A product shipping class with the provided ID could not be found', 'woocommerce' ), 404 ); 
  2723.  
  2724. $term_id = intval( $term->term_id ); 
  2725.  
  2726. $product_shipping_class = array( 
  2727. 'id' => $term_id,  
  2728. 'name' => $term->name,  
  2729. 'slug' => $term->slug,  
  2730. 'parent' => $term->parent,  
  2731. 'description' => $term->description,  
  2732. 'count' => intval( $term->count ),  
  2733. ); 
  2734.  
  2735. return array( 'product_shipping_class' => apply_filters( 'woocommerce_api_product_shipping_class_response', $product_shipping_class, $id, $fields, $term, $this ) ); 
  2736. } catch ( WC_API_Exception $e ) { 
  2737. return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) ); 
  2738.  
  2739. /** 
  2740. * Create a new product shipping class. 
  2741. * @since 2.5.0 
  2742. * @param array $data Posted data 
  2743. * @return array|WP_Error Product shipping class if succeed, otherwise 
  2744. * WP_Error will be returned 
  2745. */ 
  2746. public function create_product_shipping_class( $data ) { 
  2747. global $wpdb; 
  2748.  
  2749. try { 
  2750. if ( ! isset( $data['product_shipping_class'] ) ) { 
  2751. throw new WC_API_Exception( 'woocommerce_api_missing_product_shipping_class_data', sprintf( __( 'No %1$s data specified to create %1$s', 'woocommerce' ), 'product_shipping_class' ), 400 ); 
  2752.  
  2753. // Check permissions 
  2754. if ( ! current_user_can( 'manage_product_terms' ) ) { 
  2755. throw new WC_API_Exception( 'woocommerce_api_user_cannot_create_product_shipping_class', __( 'You do not have permission to create product shipping classes', 'woocommerce' ), 401 ); 
  2756.  
  2757. $defaults = array( 
  2758. 'name' => '',  
  2759. 'slug' => '',  
  2760. 'description' => '',  
  2761. 'parent' => 0,  
  2762. ); 
  2763.  
  2764. $data = wp_parse_args( $data['product_shipping_class'], $defaults ); 
  2765. $data = apply_filters( 'woocommerce_api_create_product_shipping_class_data', $data, $this ); 
  2766.  
  2767. // Check parent. 
  2768. $data['parent'] = absint( $data['parent'] ); 
  2769. if ( $data['parent'] ) { 
  2770. $parent = get_term_by( 'id', $data['parent'], 'product_shipping_class' ); 
  2771. if ( ! $parent ) { 
  2772. throw new WC_API_Exception( 'woocommerce_api_invalid_product_shipping_class_parent', __( 'Product shipping class parent is invalid', 'woocommerce' ), 400 ); 
  2773.  
  2774. $insert = wp_insert_term( $data['name'], 'product_shipping_class', $data ); 
  2775. if ( is_wp_error( $insert ) ) { 
  2776. throw new WC_API_Exception( 'woocommerce_api_cannot_create_product_shipping_class', $insert->get_error_message(), 400 ); 
  2777.  
  2778. $id = $insert['term_id']; 
  2779.  
  2780. do_action( 'woocommerce_api_create_product_shipping_class', $id, $data ); 
  2781.  
  2782. $this->server->send_status( 201 ); 
  2783.  
  2784. return $this->get_product_shipping_class( $id ); 
  2785. } catch ( WC_API_Exception $e ) { 
  2786. return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) ); 
  2787.  
  2788. /** 
  2789. * Edit a product shipping class. 
  2790. * @since 2.5.0 
  2791. * @param int $id Product shipping class term ID 
  2792. * @param array $data Posted data 
  2793. * @return array|WP_Error Product shipping class if succeed, otherwise 
  2794. * WP_Error will be returned 
  2795. */ 
  2796. public function edit_product_shipping_class( $id, $data ) { 
  2797. global $wpdb; 
  2798.  
  2799. try { 
  2800. if ( ! isset( $data['product_shipping_class'] ) ) { 
  2801. throw new WC_API_Exception( 'woocommerce_api_missing_product_shipping_class', sprintf( __( 'No %1$s data specified to edit %1$s', 'woocommerce' ), 'product_shipping_class' ), 400 ); 
  2802.  
  2803. $id = absint( $id ); 
  2804. $data = $data['product_shipping_class']; 
  2805.  
  2806. // Check permissions 
  2807. if ( ! current_user_can( 'manage_product_terms' ) ) { 
  2808. throw new WC_API_Exception( 'woocommerce_api_user_cannot_edit_product_shipping_class', __( 'You do not have permission to edit product shipping classes', 'woocommerce' ), 401 ); 
  2809.  
  2810. $data = apply_filters( 'woocommerce_api_edit_product_shipping_class_data', $data, $this ); 
  2811. $shipping_class = $this->get_product_shipping_class( $id ); 
  2812.  
  2813. if ( is_wp_error( $shipping_class ) ) { 
  2814. return $shipping_class; 
  2815.  
  2816. $update = wp_update_term( $id, 'product_shipping_class', $data ); 
  2817. if ( is_wp_error( $update ) ) { 
  2818. throw new WC_API_Exception( 'woocommerce_api_cannot_edit_product_shipping_class', __( 'Could not edit the shipping class', 'woocommerce' ), 400 ); 
  2819.  
  2820. do_action( 'woocommerce_api_edit_product_shipping_class', $id, $data ); 
  2821.  
  2822. return $this->get_product_shipping_class( $id ); 
  2823. } catch ( WC_API_Exception $e ) { 
  2824. return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) ); 
  2825.  
  2826. /** 
  2827. * Delete a product shipping class. 
  2828. * @since 2.5.0 
  2829. * @param int $id Product shipping class term ID 
  2830. * @return array|WP_Error Success message if succeed, otherwise WP_Error 
  2831. * will be returned 
  2832. */ 
  2833. public function delete_product_shipping_class( $id ) { 
  2834. global $wpdb; 
  2835.  
  2836. try { 
  2837. // Check permissions 
  2838. if ( ! current_user_can( 'manage_product_terms' ) ) { 
  2839. throw new WC_API_Exception( 'woocommerce_api_user_cannot_delete_product_shipping_class', __( 'You do not have permission to delete product shipping classes', 'woocommerce' ), 401 ); 
  2840.  
  2841. $id = absint( $id ); 
  2842. $deleted = wp_delete_term( $id, 'product_shipping_class' ); 
  2843. if ( ! $deleted || is_wp_error( $deleted ) ) { 
  2844. throw new WC_API_Exception( 'woocommerce_api_cannot_delete_product_shipping_class', __( 'Could not delete the shipping class', 'woocommerce' ), 401 ); 
  2845.  
  2846. do_action( 'woocommerce_api_delete_product_shipping_class', $id, $this ); 
  2847.  
  2848. return array( 'message' => sprintf( __( 'Deleted %s', 'woocommerce' ), 'product_shipping_class' ) ); 
  2849. } catch ( WC_API_Exception $e ) { 
  2850. return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) );