WC_POS_API_Products

POS Product Class duck punches the WC REST API.

Defined (1)

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

/includes/api/class-wc-pos-products.php  
  1. class WC_POS_API_Products extends WC_POS_API_Abstract { 
  2.  
  3. /** 
  4. * Product fields used by the POS 
  5. * @var array 
  6. */ 
  7. private $whitelist = array( 
  8. 'title',  
  9. 'name',  
  10. 'id',  
  11. 'created_at',  
  12. 'updated_at',  
  13. 'type',  
  14. 'status',  
  15. 'downloadable',  
  16. 'virtual',  
  17. // 'permalink',  
  18. 'sku',  
  19. 'price',  
  20. 'regular_price',  
  21. 'sale_price',  
  22. 'price_html',  
  23. 'taxable',  
  24. 'tax_status',  
  25. 'tax_class',  
  26. 'managing_stock',  
  27. 'stock_quantity',  
  28. 'in_stock',  
  29. 'backorders_allowed',  
  30. 'backordered',  
  31. 'sold_individually',  
  32. 'purchaseable',  
  33. 'featured',  
  34. 'visible',  
  35. // 'catalog_visibility',  
  36. 'on_sale',  
  37. // 'weight',  
  38. // 'dimensions',  
  39. 'shipping_required',  
  40. 'shipping_taxable',  
  41. 'shipping_class',  
  42. 'shipping_class_id',  
  43. // 'description',  
  44. // 'short_description',  
  45. // 'reviews_allowed',  
  46. // 'average_rating',  
  47. // 'rating_count',  
  48. // 'related_ids',  
  49. // 'upsell_ids',  
  50. // 'cross_sell_ids',  
  51. 'parent_id',  
  52. 'categories',  
  53. // 'tags',  
  54. // 'images',  
  55. 'featured_src',  
  56. 'attributes',  
  57. // 'downloads',  
  58. // 'download_limit',  
  59. // 'download_expiry',  
  60. // 'download_type',  
  61. 'purchase_note',  
  62. 'total_sales',  
  63. 'variations',  
  64. // 'parent',  
  65.  
  66. /** 
  67. * Fields add by POS 
  68. * - product thumbnail 
  69. * - barcode 
  70. */ 
  71. 'featured_src',  
  72. 'barcode' 
  73. ); 
  74.  
  75. /** 
  76. */ 
  77. public function __construct() { 
  78. add_filter( 'woocommerce_api_product_response', array( $this, 'product_response' ), 10, 4 ); 
  79. add_action( 'pre_get_posts', array( $this, 'pre_get_posts' ) ); 
  80. add_filter( 'posts_where', array( $this, 'posts_where' ), 10 , 2 ); 
  81.  
  82. /** 
  83. * Filter each product response from WC REST API for easier handling by the POS 
  84. * - use the thumbnails rather than fullsize 
  85. * - add barcode field 
  86. * - unset unnecessary data 
  87. * @param array $data 
  88. * @param $product 
  89. * @return array modified data array $product_data 
  90. */ 
  91. public function product_response( $data, $product, $fields, $server ) { 
  92. $type = isset( $data['type'] ) ? $data['type'] : ''; 
  93.  
  94. // variable products 
  95. if( $type == 'variable' ) : 
  96. // nested variations 
  97. foreach( $data['variations'] as &$variation ) : 
  98. $_product = wc_get_product( $variation['id'] ); 
  99. $variation = $this->filter_response_data( $variation, $_product ); 
  100. $variation['attributes'] = $this->patch_variation_attributes( $_product ); 
  101. endforeach; 
  102. endif; 
  103.  
  104. // variation 
  105. if( $type == 'variation' ) : 
  106. $data['attributes'] = $this->patch_variation_attributes( $product ); 
  107. endif; 
  108.  
  109. return $this->filter_response_data( $data, $product ); 
  110.  
  111. /** 
  112. * https://github.com/woothemes/woocommerce/issues/8457 
  113. * patches WC_Product_Variable->get_variation_attributes() 
  114. * @param $product 
  115. * @return array 
  116. */ 
  117. private function patch_variation_attributes( $product ) { 
  118. $patched_attributes = array(); 
  119. $attributes = $product->get_attributes(); 
  120. $variation_attributes = $product->get_variation_attributes(); 
  121.  
  122. // patch for corrupted data, depreciate asap 
  123. if( empty( $attributes ) ) { 
  124. $attributes = $product->parent->product_attributes; 
  125. delete_post_meta( $product->variation_id, '_product_attributes' ); 
  126.  
  127. foreach( $variation_attributes as $slug => $option ) { 
  128. $slug = str_replace( 'attribute_', '', $slug ); 
  129.  
  130. if( isset( $attributes[$slug] ) ) { 
  131. $patched_attributes[] = array( 
  132. 'name' => $this->get_variation_name( $attributes[$slug] ),  
  133. 'option' => $this->get_variation_option( $product, $attributes[$slug], $option ) 
  134. ); 
  135.  
  136.  
  137. return $patched_attributes; 
  138.  
  139. /** 
  140. * @param $attribute 
  141. * @return null|string 
  142. */ 
  143. private function get_variation_name( $attribute ) { 
  144. if( $attribute['is_taxonomy'] ) { 
  145. global $wpdb; 
  146. $name = str_replace( 'pa_', '', $attribute['name'] ); 
  147.  
  148. $label = $wpdb->get_var( 
  149. $wpdb->prepare(" 
  150. SELECT attribute_label 
  151. FROM {$wpdb->prefix}woocommerce_attribute_taxonomies 
  152. WHERE attribute_name = %s; 
  153. ", $name ) ); 
  154.  
  155. return $label ? $label : $name; 
  156.  
  157. return $attribute['name']; 
  158.  
  159. /** 
  160. * @param $product 
  161. * @param $option 
  162. * @param $attribute 
  163. * @return mixed 
  164. */ 
  165. private function get_variation_option( $product, $attribute, $option ) { 
  166. $name = $option; 
  167.  
  168. // taxonomy attributes 
  169. if ( $attribute['is_taxonomy'] ) { 
  170. $terms = wp_get_post_terms( $product->parent->id, $attribute['name'] ); 
  171. if( !is_wp_error($terms) ) : foreach( $terms as $term ) : 
  172. if( $option === $term->slug ) $name = $term->name; 
  173. endforeach; endif; 
  174.  
  175. // piped attributes 
  176. } else { 
  177. $values = array_map( 'trim', explode( WC_DELIMITER, $attribute['value'] ) ); 
  178. $options = array_combine( array_map( 'sanitize_title', $values) , $values ); 
  179. if( $options && isset( $options[$option] ) ) { 
  180. $name = $options[$option]; 
  181.  
  182. return $name; 
  183.  
  184. /** 
  185. * Filter product response data 
  186. * - add featured_src 
  187. * - add special key for barcode, defaults to sku 
  188. * @param array $data 
  189. * @param $product 
  190. * @return array 
  191. */ 
  192. private function filter_response_data( array $data, $product ) { 
  193. $id = isset( $data['id'] ) ? $data['id'] : ''; 
  194. $barcode = isset( $data['sku'] ) ? $data['sku'] : ''; 
  195.  
  196. // allow custom barcode field 
  197. $barcode_meta_key = apply_filters( 'woocommerce_pos_barcode_meta_key', '_sku' ); 
  198. if( $barcode_meta_key !== '_sku' ) { 
  199. $barcode = get_post_meta( $id, $barcode_meta_key, true ); 
  200.  
  201. $data['featured_src'] = $this->get_thumbnail( $id ); 
  202. $data['barcode'] = apply_filters( 'woocommerce_pos_product_barcode', $barcode, $id ); 
  203.  
  204. // allow decimal stock quantities, fixed in WC 2.4 
  205. if( version_compare( WC()->version, '2.4', '<' ) ) { 
  206. $data['stock_quantity'] = $product->get_stock_quantity(); 
  207.  
  208. // Transition to name property 
  209. if( isset($data['title']) ) { 
  210. $data['name'] = $data['title']; 
  211. unset($data['title']); 
  212.  
  213. // filter by whitelist 
  214. // - note, this uses the same method as WC REST API fields parameter 
  215. // - this doesn't speed up queries as it should 
  216. // - when WC REST API properly filters requests POS should use fields param 
  217. return array_intersect_key( $data, array_flip( $this->whitelist ) ); 
  218.  
  219. /** 
  220. * Returns thumbnail if it exists, if not, returns the WC placeholder image 
  221. * @param int $id 
  222. * @return string 
  223. */ 
  224. private function get_thumbnail($id) { 
  225. $image = false; 
  226. $thumb_id = get_post_thumbnail_id( $id ); 
  227.  
  228. if( $thumb_id ) 
  229. $image = wp_get_attachment_image_src( $thumb_id, 'shop_thumbnail' ); 
  230.  
  231. if( is_array($image) ) 
  232. return $image[0]; 
  233.  
  234. return wc_placeholder_img_src(); 
  235.  
  236. /** 
  237. * @param $query 
  238. */ 
  239. public function pre_get_posts($query) { 
  240.  
  241. // store original meta_query 
  242. $meta_query = $query->get( 'meta_query' ); 
  243.  
  244. if( isset( $_GET['filter'] ) ) { 
  245.  
  246. $filter = $_GET['filter']; 
  247.  
  248. // barcode moved to posts_where for variation search 
  249. // if( isset($filter['barcode']) ) { 
  250. // $meta_query[] = array( 
  251. // 'key' => $this->barcode_meta_key,  
  252. // 'value' => $filter['barcode'],  
  253. // 'compare' => 'LIKE' 
  254. // ); 
  255. // } 
  256.  
  257. // featured 
  258. // todo: more general meta_key test using $query_args_whitelist 
  259. if( isset($filter['featured']) ) { 
  260. $meta_query[] = array( 
  261. 'key' => '_featured',  
  262. 'value' => $filter['featured'] ? 'yes' : 'no',  
  263. 'compare' => '=' 
  264. ); 
  265.  
  266. // on sale 
  267. // - no easy way to get on_sale items 
  268. // - wc_get_product_ids_on_sale uses cached data, includes variations 
  269. if( isset($filter['on_sale']) ) { 
  270. $sale_ids = array_filter( wc_get_product_ids_on_sale() ); 
  271. $exclude = isset($query->query['post__not_in']) ? $query->query['post__not_in'] : array(); 
  272. $ids = array_diff($sale_ids, $exclude); 
  273. $query->set( 'post__not_in', array() ); 
  274. $query->set( 'post__in', $ids ); 
  275.  
  276.  
  277. // order product alphabetically 
  278. $query->set('orderby', 'post_title'); 
  279. $query->set('order', 'ASC'); 
  280.  
  281. // update the meta_query 
  282. $query->set( 'meta_query', $meta_query ); 
  283.  
  284.  
  285. /** 
  286. * @param $where 
  287. * @param $query 
  288. * @return mixed 
  289. */ 
  290. public function posts_where( $where, $query ) { 
  291. global $wpdb; 
  292.  
  293. if( isset( $_GET['filter'] ) ) { 
  294.  
  295. $filter = $_GET['filter']; 
  296.  
  297. if( isset($filter['barcode']) ) { 
  298.  
  299. $barcode_meta_key = apply_filters( 'woocommerce_pos_barcode_meta_key', '_sku' ); 
  300.  
  301. // gets post ids and parent ids 
  302. $result = $wpdb->get_results( 
  303. $wpdb->prepare(" 
  304. SELECT p.ID, p.post_parent 
  305. FROM $wpdb->posts AS p 
  306. JOIN $wpdb->postmeta AS pm 
  307. ON p.ID = pm.post_id 
  308. WHERE pm.meta_key = %s 
  309. AND pm.meta_value LIKE %s 
  310. ", $barcode_meta_key, '%'.$filter['barcode'].'%' ),  
  311. ARRAY_N 
  312. ); 
  313.  
  314. if($result) { 
  315. $ids = call_user_func_array('array_merge', $result); 
  316. $where .= " AND ID IN (" . implode( ', ', array_unique($ids) ) . ")"; 
  317. } else { 
  318. // no matches 
  319. $where .= " AND 1=0"; 
  320.  
  321.  
  322.  
  323. return $where; 
  324.  
  325. /** 
  326. * Returns array of all product ids 
  327. * @param $updated_at_min 
  328. * @return array 
  329. */ 
  330. public function get_ids($updated_at_min) { 
  331. $args = array( 
  332. 'post_type' => array('product'),  
  333. 'post_status' => array('publish'),  
  334. 'posts_per_page'=> -1,  
  335. 'fields' => 'ids' 
  336. ); 
  337.  
  338. if($updated_at_min) { 
  339. $args['date_query'][] = array( 
  340. 'column' => 'post_modified_gmt',  
  341. 'after' => $updated_at_min,  
  342. 'inclusive' => false 
  343. ); 
  344.  
  345. $query = new WP_Query( $args ); 
  346. return array_map( 'intval', $query->posts ); 
  347.