1 <?php
2
3 4 5 6 7 8 9 10 11
12
13 namespace WC_POS\API;
14
15 use WC_API_Resource;
16 use WC_API_Server;
17
18 class Products extends WC_API_Resource {
19
20
21 protected $base = '/products';
22
23
24 public $barcode_meta_key;
25
26 27 28 29
30 private $whitelist = array(
31 'title',
32 'id',
33 'created_at',
34 'updated_at',
35 'type',
36 'status',
37 'downloadable',
38 'virtual',
39
40 'sku',
41 'price',
42 'regular_price',
43 'sale_price',
44 'price_html',
45 'taxable',
46 'tax_status',
47 'tax_class',
48 'managing_stock',
49 'stock_quantity',
50 'in_stock',
51 'backorders_allowed',
52 'backordered',
53 'sold_individually',
54 'purchaseable',
55 'featured',
56 'visible',
57
58 'on_sale',
59
60
61 'shipping_required',
62 'shipping_taxable',
63 'shipping_class',
64 'shipping_class_id',
65
66
67
68
69
70
71
72
73 'parent_id',
74 'categories',
75 'tags',
76
77
78 'attributes',
79
80
81
82
83 'purchase_note',
84 'total_sales',
85 'variations',
86
87
88 89 90 91 92
93 'featured_src',
94 'barcode'
95 );
96
97
98 99 100
101 public function __construct( WC_API_Server $server ) {
102 parent::__construct( $server );
103
104
105 $this->barcode_meta_key = apply_filters( 'woocommerce_pos_barcode_meta_key', '_sku' );
106 add_filter( 'woocommerce_api_product_response', array( $this, 'product_response' ), 10, 4 );
107
108 if( $server->path === $this->base ){
109 add_action( 'pre_get_posts', array( $this, 'pre_get_posts' ) );
110 add_filter( 'posts_search', array( $this, 'posts_search'), 10, 2 );
111 }
112 }
113
114
115 116 117 118 119 120
121 public function register_routes( $routes ) {
122
123
124 $routes[ $this->base . '/ids'] = array(
125 array( array( $this, 'get_all_ids' ), WC_API_Server::READABLE ),
126 );
127
128 return $routes;
129 }
130
131
132 133 134 135 136 137 138 139 140 141 142
143 public function product_response( $data, $product, $fields, $server ) {
144 $type = isset( $data['type'] ) ? $data['type'] : '';
145
146
147 if( $type == 'variable' ) :
148
149 foreach( $data['variations'] as &$variation ) :
150 $_product = wc_get_product( $variation['id'] );
151 $variation = $this->filter_response_data( $variation, $_product );
152 $variation['attributes'] = $this->patch_variation_attributes( $_product );
153 endforeach;
154 endif;
155
156
157 if( $type == 'variation' ) :
158 $data['attributes'] = $this->patch_variation_attributes( $product );
159 endif;
160
161 return $this->filter_response_data( $data, $product );
162 }
163
164
165 166 167 168 169 170
171 private function patch_variation_attributes( $product ){
172 $patched_attributes = array();
173 $attributes = $product->get_attributes();
174 $variation_attributes = $product->get_variation_attributes();
175
176
177 if( empty( $attributes ) ){
178 $attributes = $product->parent->product_attributes;
179 delete_post_meta( $product->variation_id, '_product_attributes' );
180 }
181
182 foreach( $variation_attributes as $slug => $option ){
183 $slug = str_replace( 'attribute_', '', $slug );
184
185 if( isset( $attributes[$slug] ) ){
186 $patched_attributes[] = array(
187 'name' => $this->get_variation_name( $attributes[$slug] ),
188 'option' => $this->get_variation_option( $product, $attributes[$slug], $option )
189 );
190 }
191
192 }
193
194 return $patched_attributes;
195 }
196
197
198 199 200 201
202 private function get_variation_name( $attribute ){
203 if( $attribute['is_taxonomy'] ){
204 global $wpdb;
205 $name = str_replace( 'pa_', '', $attribute['name'] );
206
207 $label = $wpdb->get_var(
208 $wpdb->prepare("
209 SELECT attribute_label
210 FROM {$wpdb->prefix}woocommerce_attribute_taxonomies
211 WHERE attribute_name = %s;
212 ", $name ) );
213
214 return $label ? $label : $name;
215 }
216
217 return $attribute['name'];
218 }
219
220
221 222 223 224 225 226
227 private function get_variation_option( $product, $attribute, $option ){
228 $name = $option;
229
230
231 if ( $attribute['is_taxonomy'] ) {
232 $terms = wp_get_post_terms( $product->parent->id, $attribute['name'] );
233 if( !is_wp_error($terms) ) : foreach( $terms as $term ) :
234 if( $option === $term->slug ) $name = $term->name;
235 endforeach; endif;
236
237
238 } else {
239 $values = array_map( 'trim', explode( WC_DELIMITER, $attribute['value'] ) );
240 $options = array_combine( array_map( 'sanitize_title', $values) , $values );
241 if( $options && isset( $options[$option] ) ){
242 $name = $options[$option];
243 }
244 }
245
246 return $name;
247 }
248
249
250 251 252 253 254 255 256 257
258 private function filter_response_data( array $data, $product ){
259 $barcode = get_post_meta( $product->id, $this->barcode_meta_key, true );
260
261 $data['featured_src'] = $this->get_thumbnail( $product->id );
262 $data['barcode'] = apply_filters( 'woocommerce_pos_product_barcode', $barcode, $product->id );
263
264
265 if( version_compare( WC()->version, '2.4', '<' ) ){
266 $data['stock_quantity'] = $product->get_stock_quantity();
267 }
268
269
270
271
272
273 return array_intersect_key( $data, array_flip( $this->whitelist ) );
274 }
275
276
277 278 279 280 281
282 private function get_thumbnail($id){
283 $image = false;
284 $thumb_id = get_post_thumbnail_id( $id );
285
286 if( $thumb_id )
287 $image = wp_get_attachment_image_src( $thumb_id, 'shop_thumbnail' );
288
289 if( is_array($image) )
290 return $image[0];
291
292 return wc_placeholder_img_src();
293 }
294
295 296 297
298 public function pre_get_posts(\WP_Query $wp_query){
299 $query_array = isset($wp_query->query['s']) ? $wp_query->query['s'] : '';
300
301 if(!is_array($query_array)){
302 return;
303 }
304
305 foreach( $query_array as $query ){
306 $this->parse_query_array($query, $wp_query);
307 }
308 }
309
310 311 312 313
314 private function parse_query_array( array $query, \WP_Query $wp_query){
315 $type = isset($query['type']) ? $query['type'] : 'string';
316
317 if($type == 'string' && isset($query)){
318 $term = isset($query['query']) ? $query['query'] : '';
319 $this->string_query($term, $wp_query);
320 }
321
322 if($type == 'prefix' && isset($query['prefix']) && isset($query['query'])){
323 $prefix = isset($query['prefix']) ? $query['prefix'] : '';
324 $term = isset($query['query']) ? $query['query'] : '';
325 $this->prefix_query($prefix, $term, $wp_query);
326 }
327
328 }
329
330 331 332 333
334 private function string_query( $term, \WP_Query $wp_query ){
335 global $wpdb;
336 $search_ids = $wpdb->get_col(
337 $wpdb->prepare("
338 SELECT ID
339 FROM $wpdb->posts
340 WHERE post_type = 'product'
341 AND post_status = 'publish'
342 AND post_title LIKE %s
343 ", '%' . $term . '%')
344 );
345 $post__in = $wp_query->get('post__in');
346 $include = empty($post__in) ? $search_ids : array_intersect($post__in, $search_ids);
347 if(empty($include)){
348 $include = array(0);
349 }
350 $wp_query->set( 'post__in', $include );
351 }
352
353 354 355 356 357
358 private function prefix_query( $prefix, $term, \WP_Query $wp_query ){
359
360 $meta_query = $wp_query->get( 'meta_query' );
361 $tax_query = $wp_query->get( 'tax_query' );
362
363
364 if($prefix == 'id'){
365 $term = (int) $term;
366 $post__in = $wp_query->get('post__in');
367 $include = empty($post__in) ? array($term) : array_intersect($post__in, array($term));
368 if(empty($include)){
369 $include = array(0);
370 }
371 $wp_query->set( 'post__in', $include );
372 }
373
374
375 if($prefix == 'featured'){
376 $meta_query[] = array(
377 'key' => '_featured',
378 'value' => $term == 'true' ? 'yes' : 'no',
379 'compare' => '='
380 );
381 }
382
383
384 if($prefix == 'sku'){
385 $meta_query[] = array(
386 'key' => '_sku',
387 'value' => $term,
388 'compare' => 'LIKE'
389 );
390 }
391
392
393 if($prefix == 'barcode'){
394 $meta_query[] = array(
395 'key' => $this->barcode_meta_key,
396 'value' => $term,
397 'compare' => 'LIKE'
398 );
399 }
400
401
402 if($prefix == 'on_sale'){
403 $sale_ids = array_filter( wc_get_product_ids_on_sale() );
404 if($term == 'true'){
405 $post__in = $wp_query->get('post__in');
406 $include = empty($post__in) ? $sale_ids : array_intersect($post__in, $sale_ids);
407 if(empty($include)){
408 $include = array(0);
409 }
410 $wp_query->set( 'post__in', $include );
411 } else {
412 $exclude = $wp_query->get('post__not_in');
413 $wp_query->set( 'post__not_in', array_merge($exclude, $sale_ids) );
414 }
415 }
416
417
418 if($prefix == 'categories' || $prefix == 'cat'){
419 $tax_query[] = array(
420 'taxonomy' => 'product_cat',
421 'field' => 'name',
422 'terms' => array( $term )
423 );
424 }
425
426
427 if($prefix == 'tags' || $prefix == 'tag'){
428 $tax_query[] = array(
429 'taxonomy' => 'product_tag',
430 'field' => 'name',
431 'terms' => array( $term )
432 );
433 }
434
435 $wp_query->set('meta_query', $meta_query);
436 $wp_query->set('tax_query', $tax_query);
437
438 }
439
440 441 442 443 444
445 public function posts_search( $search, \WP_Query $wp_query ){
446 global $wpdb;
447 if(!empty($search)){
448 $term = isset($wp_query->query['s']) ? $wp_query->query['s'] : '';
449 $search = " AND ($wpdb->posts.post_title LIKE '%$term%')";
450 }
451 return $search;
452 }
453
454 455 456 457 458 459
460 public function get_all_ids( $filter = array() ){
461 $args = array(
462 'post_type' => array('product'),
463 'post_status' => array('publish'),
464 'posts_per_page'=> -1,
465 'fields' => 'ids'
466 );
467
468 if( isset( $filter['updated_at_min'] ) ){
469 $args['date_query'][] = array(
470 'column' => 'post_modified_gmt',
471 'after' => $filter['updated_at_min'],
472 'inclusive' => false
473 );
474 }
475
476 $query = new \WP_Query( $args );
477 $this->server->add_pagination_headers($query);
478 return array( 'products' => array_map( array( $this, 'format_id' ), $query->posts ) );
479 }
480
481 482 483 484
485 private function format_id( $id ){
486 return array( 'id' => (int) $id );
487 }
488
489 }