1 <?php
2 3 4 5 6 7 8 9 10 11
12
13 if ( ! defined( 'ABSPATH' ) ) exit;
14
15 class WC_API_Products extends WC_API_Resource {
16
17
18 protected $base = '/products';
19
20 21 22 23 24 25 26 27 28 29 30 31
32 public function register_routes( $routes ) {
33
34
35 $routes[ $this->base ] = array(
36 array( array( $this, 'get_products' ), WC_API_Server::READABLE ),
37 );
38
39
40 $routes[ $this->base . '/count'] = array(
41 array( array( $this, 'get_products_count' ), WC_API_Server::READABLE ),
42 );
43
44
45 $routes[ $this->base . '/(?P<id>\d+)' ] = array(
46 array( array( $this, 'get_product' ), WC_API_Server::READABLE ),
47 );
48
49
50 $routes[ $this->base . '/(?P<id>\d+)/reviews' ] = array(
51 array( array( $this, 'get_product_reviews' ), WC_API_Server::READABLE ),
52 );
53
54 return $routes;
55 }
56
57 58 59 60 61 62 63 64 65 66
67 public function get_products( $fields = null, $type = null, $filter = array(), $page = 1 ) {
68
69 if ( ! empty( $type ) )
70 $filter['type'] = $type;
71
72 $filter['page'] = $page;
73
74 $query = $this->query_products( $filter );
75
76 $products = array();
77
78 foreach( $query->posts as $product_id ) {
79
80 if ( ! $this->is_readable( $product_id ) )
81 continue;
82
83 $products[] = current( $this->get_product( $product_id, $fields ) );
84 }
85
86 $this->server->add_pagination_headers( $query );
87
88 return array( 'products' => $products );
89 }
90
91 92 93 94 95 96 97 98
99 public function get_product( $id, $fields = null ) {
100
101 $id = $this->validate_request( $id, 'product', 'read' );
102
103 if ( is_wp_error( $id ) )
104 return $id;
105
106 $product = get_product( $id );
107
108
109 $product_data = $this->get_product_data( $product );
110
111
112 if ( $product->is_type( 'variable' ) && $product->has_child() ) {
113
114 $product_data['variations'] = $this->get_variation_data( $product );
115 }
116
117
118 if ( $product->is_type( 'variation' ) ) {
119
120 $product_data['parent'] = $this->get_product_data( $product->parent );
121 }
122
123 return array( 'product' => apply_filters( 'woocommerce_api_product_response', $product_data, $product, $fields, $this->server ) );
124 }
125
126 127 128 129 130 131 132 133
134 public function get_products_count( $type = null, $filter = array() ) {
135
136 if ( ! empty( $type ) )
137 $filter['type'] = $type;
138
139 if ( ! current_user_can( 'read_private_products' ) )
140 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 ) );
141
142 $query = $this->query_products( $filter );
143
144 return array( 'count' => (int) $query->found_posts );
145 }
146
147 148 149 150 151 152 153 154
155 public function edit_product( $id, $data ) {
156
157 $id = $this->validate_request( $id, 'product', 'edit' );
158
159 if ( is_wp_error( $id ) )
160 return $id;
161
162 return $this->get_product( $id );
163 }
164
165 166 167 168 169 170 171 172
173 public function delete_product( $id, $force = false ) {
174
175 $id = $this->validate_request( $id, 'product', 'delete' );
176
177 if ( is_wp_error( $id ) )
178 return $id;
179
180 return $this->delete( $id, 'product', ( 'true' === $force ) );
181 }
182
183 184 185 186 187 188 189 190
191 public function get_product_reviews( $id, $fields = null ) {
192
193 $id = $this->validate_request( $id, 'product', 'read' );
194
195 if ( is_wp_error( $id ) )
196 return $id;
197
198 $args = array(
199 'post_id' => $id,
200 'approve' => 'approve',
201 );
202
203 $comments = get_comments( $args );
204
205 $reviews = array();
206
207 foreach ( $comments as $comment ) {
208
209 $reviews[] = array(
210 'id' => $comment->comment_ID,
211 'created_at' => $this->server->format_datetime( $comment->comment_date_gmt ),
212 'review' => $comment->comment_content,
213 'rating' => get_comment_meta( $comment->comment_ID, 'rating', true ),
214 'reviewer_name' => $comment->comment_author,
215 'reviewer_email' => $comment->comment_author_email,
216 'verified' => (bool) wc_customer_bought_product( $comment->comment_author_email, $comment->user_id, $id ),
217 );
218 }
219
220 return array( 'product_reviews' => apply_filters( 'woocommerce_api_product_reviews_response', $reviews, $id, $fields, $comments, $this->server ) );
221 }
222
223 224 225 226 227 228 229
230 private function query_products( $args ) {
231
232
233 $query_args = array(
234 'fields' => 'ids',
235 'post_type' => 'product',
236 'post_status' => 'publish',
237 'meta_query' => array(),
238 );
239
240 if ( ! empty( $args['type'] ) ) {
241
242 $types = explode( ',', $args['type'] );
243
244 $query_args['tax_query'] = array(
245 array(
246 'taxonomy' => 'product_type',
247 'field' => 'slug',
248 'terms' => $types,
249 ),
250 );
251
252 unset( $args['type'] );
253 }
254
255 $query_args = $this->merge_query_args( $query_args, $args );
256
257 return new WP_Query( $query_args );
258 }
259
260 261 262 263 264 265 266
267 private function get_product_data( $product ) {
268
269 return array(
270 'title' => $product->get_title(),
271 'id' => (int) $product->is_type( 'variation' ) ? $product->get_variation_id() : $product->id,
272 'created_at' => $this->server->format_datetime( $product->get_post_data()->post_date_gmt ),
273 'updated_at' => $this->server->format_datetime( $product->get_post_data()->post_modified_gmt ),
274 'type' => $product->product_type,
275 'status' => $product->get_post_data()->post_status,
276 'downloadable' => $product->is_downloadable(),
277 'virtual' => $product->is_virtual(),
278 'permalink' => $product->get_permalink(),
279 'sku' => $product->get_sku(),
280 'price' => wc_format_decimal( $product->get_price(), 2 ),
281 'regular_price' => wc_format_decimal( $product->get_regular_price(), 2 ),
282 'sale_price' => $product->get_sale_price() ? wc_format_decimal( $product->get_sale_price(), 2 ) : null,
283 'price_html' => $product->get_price_html(),
284 'taxable' => $product->is_taxable(),
285 'tax_status' => $product->get_tax_status(),
286 'tax_class' => $product->get_tax_class(),
287 'managing_stock' => $product->managing_stock(),
288 'stock_quantity' => (int) $product->get_stock_quantity(),
289 'in_stock' => $product->is_in_stock(),
290 'backorders_allowed' => $product->backorders_allowed(),
291 'backordered' => $product->is_on_backorder(),
292 'sold_individually' => $product->is_sold_individually(),
293 'purchaseable' => $product->is_purchasable(),
294 'featured' => $product->is_featured(),
295 'visible' => $product->is_visible(),
296 'catalog_visibility' => $product->visibility,
297 'on_sale' => $product->is_on_sale(),
298 'weight' => $product->get_weight() ? wc_format_decimal( $product->get_weight(), 2 ) : null,
299 'dimensions' => array(
300 'length' => $product->length,
301 'width' => $product->width,
302 'height' => $product->height,
303 'unit' => get_option( 'woocommerce_dimension_unit' ),
304 ),
305 'shipping_required' => $product->needs_shipping(),
306 'shipping_taxable' => $product->is_shipping_taxable(),
307 'shipping_class' => $product->get_shipping_class(),
308 'shipping_class_id' => ( 0 !== $product->get_shipping_class_id() ) ? $product->get_shipping_class_id() : null,
309 'description' => apply_filters( 'the_content', $product->get_post_data()->post_content ),
310 'short_description' => apply_filters( 'woocommerce_short_description', $product->get_post_data()->post_excerpt ),
311 'reviews_allowed' => ( 'open' === $product->get_post_data()->comment_status ),
312 'average_rating' => wc_format_decimal( $product->get_average_rating(), 2 ),
313 'rating_count' => (int) $product->get_rating_count(),
314 'related_ids' => array_map( 'absint', array_values( $product->get_related() ) ),
315 'upsell_ids' => array_map( 'absint', $product->get_upsells() ),
316 'cross_sell_ids' => array_map( 'absint', $product->get_cross_sells() ),
317 'categories' => wp_get_post_terms( $product->id, 'product_cat', array( 'fields' => 'names' ) ),
318 'tags' => wp_get_post_terms( $product->id, 'product_tag', array( 'fields' => 'names' ) ),
319 'images' => $this->get_images( $product ),
320 'featured_src' => wp_get_attachment_url( get_post_thumbnail_id( $product->is_type( 'variation' ) ? $product->variation_id : $product->id ) ),
321 'attributes' => $this->get_attributes( $product ),
322 'downloads' => $this->get_downloads( $product ),
323 'download_limit' => (int) $product->download_limit,
324 'download_expiry' => (int) $product->download_expiry,
325 'download_type' => $product->download_type,
326 'purchase_note' => apply_filters( 'the_content', $product->purchase_note ),
327 'total_sales' => metadata_exists( 'post', $product->id, 'total_sales' ) ? (int) get_post_meta( $product->id, 'total_sales', true ) : 0,
328 'variations' => array(),
329 'parent' => array(),
330 );
331 }
332
333 334 335 336 337 338 339
340 private function get_variation_data( $product ) {
341
342 $variations = array();
343
344 foreach ( $product->get_children() as $child_id ) {
345
346 $variation = $product->get_child( $child_id );
347
348 if ( ! $variation->exists() )
349 continue;
350
351 $variations[] = array(
352 'id' => $variation->get_variation_id(),
353 'created_at' => $this->server->format_datetime( $variation->get_post_data()->post_date_gmt ),
354 'updated_at' => $this->server->format_datetime( $variation->get_post_data()->post_modified_gmt ),
355 'downloadable' => $variation->is_downloadable(),
356 'virtual' => $variation->is_virtual(),
357 'permalink' => $variation->get_permalink(),
358 'sku' => $variation->get_sku(),
359 'price' => wc_format_decimal( $variation->get_price(), 2 ),
360 'regular_price' => wc_format_decimal( $variation->get_regular_price(), 2 ),
361 'sale_price' => $variation->get_sale_price() ? wc_format_decimal( $variation->get_sale_price(), 2 ) : null,
362 'taxable' => $variation->is_taxable(),
363 'tax_status' => $variation->get_tax_status(),
364 'tax_class' => $variation->get_tax_class(),
365 'stock_quantity' => (int) $variation->get_stock_quantity(),
366 'in_stock' => $variation->is_in_stock(),
367 'backordered' => $variation->is_on_backorder(),
368 'purchaseable' => $variation->is_purchasable(),
369 'visible' => $variation->variation_is_visible(),
370 'on_sale' => $variation->is_on_sale(),
371 'weight' => $variation->get_weight() ? wc_format_decimal( $variation->get_weight(), 2 ) : null,
372 'dimensions' => array(
373 'length' => $variation->length,
374 'width' => $variation->width,
375 'height' => $variation->height,
376 'unit' => get_option( 'woocommerce_dimension_unit' ),
377 ),
378 'shipping_class' => $variation->get_shipping_class(),
379 'shipping_class_id' => ( 0 !== $variation->get_shipping_class_id() ) ? $variation->get_shipping_class_id() : null,
380 'image' => $this->get_images( $variation ),
381 'attributes' => $this->get_attributes( $variation ),
382 'downloads' => $this->get_downloads( $variation ),
383 'download_limit' => (int) $product->download_limit,
384 'download_expiry' => (int) $product->download_expiry,
385 );
386 }
387
388 return $variations;
389 }
390
391 392 393 394 395 396 397
398 private function get_images( $product ) {
399
400 $images = $attachment_ids = array();
401
402 if ( $product->is_type( 'variation' ) ) {
403
404 if ( has_post_thumbnail( $product->get_variation_id() ) ) {
405
406
407 $attachment_ids[] = get_post_thumbnail_id( $product->get_variation_id() );
408
409 } elseif ( has_post_thumbnail( $product->id ) ) {
410
411
412 $attachment_ids[] = get_post_thumbnail_id( $product->id );
413 }
414
415 } else {
416
417
418 if ( has_post_thumbnail( $product->id ) ) {
419 $attachment_ids[] = get_post_thumbnail_id( $product->id );
420 }
421
422
423 $attachment_ids = array_merge( $attachment_ids, $product->get_gallery_attachment_ids() );
424 }
425
426
427 foreach ( $attachment_ids as $position => $attachment_id ) {
428
429 $attachment_post = get_post( $attachment_id );
430
431 if ( is_null( $attachment_post ) )
432 continue;
433
434 $attachment = wp_get_attachment_image_src( $attachment_id, 'full' );
435
436 if ( ! is_array( $attachment ) )
437 continue;
438
439 $images[] = array(
440 'id' => (int) $attachment_id,
441 'created_at' => $this->server->format_datetime( $attachment_post->post_date_gmt ),
442 'updated_at' => $this->server->format_datetime( $attachment_post->post_modified_gmt ),
443 'src' => current( $attachment ),
444 'title' => get_the_title( $attachment_id ),
445 'alt' => get_post_meta( $attachment_id, '_wp_attachment_image_alt', true ),
446 'position' => $position,
447 );
448 }
449
450
451 if ( empty( $images ) ) {
452
453 $images[] = array(
454 'id' => 0,
455 'created_at' => $this->server->format_datetime( time() ),
456 'updated_at' => $this->server->format_datetime( time() ),
457 'src' => wc_placeholder_img_src(),
458 'title' => __( 'Placeholder', 'woocommerce' ),
459 'alt' => __( 'Placeholder', 'woocommerce' ),
460 'position' => 0,
461 );
462 }
463
464 return $images;
465 }
466
467 468 469 470 471 472 473
474 private function get_attributes( $product ) {
475
476 $attributes = array();
477
478 if ( $product->is_type( 'variation' ) ) {
479
480
481 foreach ( $product->get_variation_attributes() as $attribute_name => $attribute ) {
482
483
484 $attributes[] = array(
485 'name' => ucwords( str_replace( 'attribute_', '', str_replace( 'pa_', '', $attribute_name ) ) ),
486 'option' => $attribute,
487 );
488 }
489
490 } else {
491
492 foreach ( $product->get_attributes() as $attribute ) {
493
494
495 if ( $attribute['is_taxonomy'] )
496 $options = explode( ',', $product->get_attribute( $attribute['name'] ) );
497 else
498 $options = explode( '|', $product->get_attribute( $attribute['name'] ) );
499
500 $attributes[] = array(
501 'name' => ucwords( str_replace( 'pa_', '', $attribute['name'] ) ),
502 'position' => $attribute['position'],
503 'visible' => (bool) $attribute['is_visible'],
504 'variation' => (bool) $attribute['is_variation'],
505 'options' => array_map( 'trim', $options ),
506 );
507 }
508 }
509
510 return $attributes;
511 }
512
513 514 515 516 517 518 519
520 private function get_downloads( $product ) {
521
522 $downloads = array();
523
524 if ( $product->is_downloadable() ) {
525
526 foreach ( $product->get_files() as $file_id => $file ) {
527
528 $downloads[] = array(
529 'id' => $file_id,
530 'name' => $file['name'],
531 'file' => $file['file'],
532 );
533 }
534 }
535
536 return $downloads;
537 }
538
539 }
540