/includes/data-stores/class-wc-product-data-store-cpt.php

  1. <?php 
  2. if ( ! defined( 'ABSPATH' ) ) { 
  3. exit; 
  4.  
  5. /** 
  6. * WC Product Data Store: Stored in CPT. 
  7. * 
  8. * @version 3.0.0 
  9. * @category Class 
  10. * @author WooThemes 
  11. */ 
  12. class WC_Product_Data_Store_CPT extends WC_Data_Store_WP implements WC_Object_Data_Store_Interface, WC_Product_Data_Store_Interface { 
  13.  
  14. /** 
  15. * Data stored in meta keys, but not considered "meta". 
  16. * 
  17. * @since 3.0.0 
  18. * @var array 
  19. */ 
  20. protected $internal_meta_keys = array( 
  21. '_visibility',  
  22. '_sku',  
  23. '_price',  
  24. '_regular_price',  
  25. '_sale_price',  
  26. '_sale_price_dates_from',  
  27. '_sale_price_dates_to',  
  28. 'total_sales',  
  29. '_tax_status',  
  30. '_tax_class',  
  31. '_manage_stock',  
  32. '_stock',  
  33. '_stock_status',  
  34. '_backorders',  
  35. '_sold_individually',  
  36. '_weight',  
  37. '_length',  
  38. '_width',  
  39. '_height',  
  40. '_upsell_ids',  
  41. '_crosssell_ids',  
  42. '_purchase_note',  
  43. '_default_attributes',  
  44. '_product_attributes',  
  45. '_virtual',  
  46. '_downloadable',  
  47. '_featured',  
  48. '_downloadable_files',  
  49. '_wc_rating_count',  
  50. '_wc_average_rating',  
  51. '_wc_review_count',  
  52. '_variation_description',  
  53. '_thumbnail_id',  
  54. '_file_paths',  
  55. '_product_image_gallery',  
  56. '_product_version',  
  57. '_wp_old_slug',  
  58. '_edit_last',  
  59. '_edit_lock',  
  60. ); 
  61.  
  62. /** 
  63. * If we have already saved our extra data, don't do automatic / default handling. 
  64. */ 
  65. protected $extra_data_saved = false; 
  66.  
  67. /** 
  68. * Stores updated props. 
  69. * @var array 
  70. */ 
  71. protected $updated_props = array(); 
  72.  
  73. /** 
  74. |-------------------------------------------------------------------------- 
  75. | CRUD Methods 
  76. |-------------------------------------------------------------------------- 
  77. */ 
  78.  
  79. /** 
  80. * Method to create a new product in the database. 
  81. * 
  82. * @param WC_Product 
  83. */ 
  84. public function create( &$product ) { 
  85. if ( ! $product->get_date_created() ) { 
  86. $product->set_date_created( current_time( 'timestamp', true ) ); 
  87.  
  88. $id = wp_insert_post( apply_filters( 'woocommerce_new_product_data', array( 
  89. 'post_type' => 'product',  
  90. 'post_status' => $product->get_status() ? $product->get_status() : 'publish',  
  91. 'post_author' => get_current_user_id(),  
  92. 'post_title' => $product->get_name() ? $product->get_name() : __( 'Product', 'woocommerce' ),  
  93. 'post_content' => $product->get_description(),  
  94. 'post_excerpt' => $product->get_short_description(),  
  95. 'post_parent' => $product->get_parent_id(),  
  96. 'comment_status' => $product->get_reviews_allowed() ? 'open' : 'closed',  
  97. 'ping_status' => 'closed',  
  98. 'menu_order' => $product->get_menu_order(),  
  99. 'post_date' => gmdate( 'Y-m-d H:i:s', $product->get_date_created( 'edit' )->getOffsetTimestamp() ),  
  100. 'post_date_gmt' => gmdate( 'Y-m-d H:i:s', $product->get_date_created( 'edit' )->getTimestamp() ),  
  101. 'post_name' => $product->get_slug( 'edit' ),  
  102. ) ), true ); 
  103.  
  104. if ( $id && ! is_wp_error( $id ) ) { 
  105. $product->set_id( $id ); 
  106.  
  107. $this->update_post_meta( $product, true ); 
  108. $this->update_terms( $product, true ); 
  109. $this->update_visibility( $product, true ); 
  110. $this->update_attributes( $product, true ); 
  111. $this->update_version_and_type( $product ); 
  112. $this->handle_updated_props( $product ); 
  113.  
  114. $product->save_meta_data(); 
  115. $product->apply_changes(); 
  116.  
  117. $this->clear_caches( $product ); 
  118.  
  119. do_action( 'woocommerce_new_product', $id ); 
  120.  
  121. /** 
  122. * Method to read a product from the database. 
  123. * @param WC_Product 
  124. */ 
  125. public function read( &$product ) { 
  126. $product->set_defaults(); 
  127.  
  128. if ( ! $product->get_id() || ! ( $post_object = get_post( $product->get_id() ) ) || 'product' !== $post_object->post_type ) { 
  129. throw new Exception( __( 'Invalid product.', 'woocommerce' ) ); 
  130.  
  131. $id = $product->get_id(); 
  132.  
  133. $product->set_props( array( 
  134. 'name' => $post_object->post_title,  
  135. 'slug' => $post_object->post_name,  
  136. 'date_created' => 0 < $post_object->post_date_gmt ? wc_string_to_timestamp( $post_object->post_date_gmt ) : null,  
  137. 'date_modified' => 0 < $post_object->post_modified_gmt ? wc_string_to_timestamp( $post_object->post_modified_gmt ) : null,  
  138. 'status' => $post_object->post_status,  
  139. 'description' => $post_object->post_content,  
  140. 'short_description' => $post_object->post_excerpt,  
  141. 'parent_id' => $post_object->post_parent,  
  142. 'menu_order' => $post_object->menu_order,  
  143. 'reviews_allowed' => 'open' === $post_object->comment_status,  
  144. ) ); 
  145.  
  146. $this->read_attributes( $product ); 
  147. $this->read_downloads( $product ); 
  148. $this->read_visibility( $product ); 
  149. $this->read_product_data( $product ); 
  150. $this->read_extra_data( $product ); 
  151. $product->set_object_read( true ); 
  152.  
  153. /** 
  154. * Method to update a product in the database. 
  155. * 
  156. * @param WC_Product 
  157. */ 
  158. public function update( &$product ) { 
  159. $product->save_meta_data(); 
  160. $changes = $product->get_changes(); 
  161.  
  162. // Only update the post when the post data changes. 
  163. if ( array_intersect( array( 'description', 'short_description', 'name', 'parent_id', 'reviews_allowed', 'status', 'menu_order', 'date_created', 'date_modified', 'slug' ), array_keys( $changes ) ) ) { 
  164. $post_data = array( 
  165. 'post_content' => $product->get_description( 'edit' ),  
  166. 'post_excerpt' => $product->get_short_description( 'edit' ),  
  167. 'post_title' => $product->get_name( 'edit' ),  
  168. 'post_parent' => $product->get_parent_id( 'edit' ),  
  169. 'comment_status' => $product->get_reviews_allowed( 'edit' ) ? 'open' : 'closed',  
  170. 'post_status' => $product->get_status( 'edit' ) ? $product->get_status( 'edit' ) : 'publish',  
  171. 'menu_order' => $product->get_menu_order( 'edit' ),  
  172. 'post_name' => $product->get_slug( 'edit' ),  
  173. ); 
  174. if ( $product->get_date_created( 'edit' ) ) { 
  175. $post_data['post_date'] = gmdate( 'Y-m-d H:i:s', $product->get_date_created( 'edit' )->getOffsetTimestamp() ); 
  176. $post_data['post_date_gmt'] = gmdate( 'Y-m-d H:i:s', $product->get_date_created( 'edit' )->getTimestamp() ); 
  177. if ( isset( $changes['date_modified'] ) && $product->get_date_modified( 'edit' ) ) { 
  178. $post_data['post_modified'] = gmdate( 'Y-m-d H:i:s', $product->get_date_modified( 'edit' )->getOffsetTimestamp() ); 
  179. $post_data['post_modified_gmt'] = gmdate( 'Y-m-d H:i:s', $product->get_date_modified( 'edit' )->getTimestamp() ); 
  180. } else { 
  181. $post_data['post_modified'] = current_time( 'mysql' ); 
  182. $post_data['post_modified_gmt'] = current_time( 'mysql', 1 ); 
  183.  
  184. /** 
  185. * When updating this object, to prevent infinite loops, use $wpdb 
  186. * to update data, since wp_update_post spawns more calls to the 
  187. * save_post action. 
  188. * 
  189. * This ensures hooks are fired by either WP itself (admin screen save),  
  190. * or an update purely from CRUD. 
  191. */ 
  192. if ( doing_action( 'save_post' ) ) { 
  193. $GLOBALS['wpdb']->update( $GLOBALS['wpdb']->posts, $post_data, array( 'ID' => $product->get_id() ) ); 
  194. clean_post_cache( $product->get_id() ); 
  195. } else { 
  196. wp_update_post( array_merge( array( 'ID' => $product->get_id() ), $post_data ) ); 
  197. $product->read_meta_data( true ); // Refresh internal meta data, in case things were hooked into `save_post` or another WP hook. 
  198.  
  199. $this->update_post_meta( $product ); 
  200. $this->update_terms( $product ); 
  201. $this->update_visibility( $product ); 
  202. $this->update_attributes( $product ); 
  203. $this->update_version_and_type( $product ); 
  204. $this->handle_updated_props( $product ); 
  205.  
  206. $product->apply_changes(); 
  207.  
  208. $this->clear_caches( $product ); 
  209.  
  210. do_action( 'woocommerce_update_product', $product->get_id() ); 
  211.  
  212. /** 
  213. * Method to delete a product from the database. 
  214. * @param WC_Product 
  215. * @param array $args Array of args to pass to the delete method. 
  216. */ 
  217. public function delete( &$product, $args = array() ) { 
  218. $id = $product->get_id(); 
  219. $post_type = $product->is_type( 'variation' ) ? 'product_variation' : 'product'; 
  220.  
  221. $args = wp_parse_args( $args, array( 
  222. 'force_delete' => false,  
  223. ) ); 
  224.  
  225. if ( $args['force_delete'] ) { 
  226. wp_delete_post( $product->get_id() ); 
  227. $product->set_id( 0 ); 
  228. do_action( 'woocommerce_delete_' . $post_type, $id ); 
  229. } else { 
  230. wp_trash_post( $product->get_id() ); 
  231. $product->set_status( 'trash' ); 
  232. do_action( 'woocommerce_trash_' . $post_type, $id ); 
  233.  
  234. /** 
  235. |-------------------------------------------------------------------------- 
  236. | Additional Methods 
  237. |-------------------------------------------------------------------------- 
  238. */ 
  239.  
  240. /** 
  241. * Read product data. Can be overridden by child classes to load other props. 
  242. * 
  243. * @param WC_Product 
  244. * @since 3.0.0 
  245. */ 
  246. protected function read_product_data( &$product ) { 
  247. $id = $product->get_id(); 
  248.  
  249. if ( '' === ( $review_count = get_post_meta( $id, '_wc_review_count', true ) ) ) { 
  250. WC_Comments::get_review_count_for_product( $product ); 
  251. } else { 
  252. $product->set_review_count( $review_count ); 
  253.  
  254. if ( '' === ( $rating_counts = get_post_meta( $id, '_wc_rating_count', true ) ) ) { 
  255. WC_Comments::get_rating_counts_for_product( $product ); 
  256. } else { 
  257. $product->set_rating_counts( $rating_counts ); 
  258.  
  259. if ( '' === ( $average_rating = get_post_meta( $id, '_wc_average_rating', true ) ) ) { 
  260. WC_Comments::get_average_rating_for_product( $product ); 
  261. } else { 
  262. $product->set_average_rating( $average_rating ); 
  263.  
  264. $product->set_props( array( 
  265. 'sku' => get_post_meta( $id, '_sku', true ),  
  266. 'regular_price' => get_post_meta( $id, '_regular_price', true ),  
  267. 'sale_price' => get_post_meta( $id, '_sale_price', true ),  
  268. 'price' => get_post_meta( $id, '_price', true ),  
  269. 'date_on_sale_from' => get_post_meta( $id, '_sale_price_dates_from', true ),  
  270. 'date_on_sale_to' => get_post_meta( $id, '_sale_price_dates_to', true ),  
  271. 'total_sales' => get_post_meta( $id, 'total_sales', true ),  
  272. 'tax_status' => get_post_meta( $id, '_tax_status', true ),  
  273. 'tax_class' => get_post_meta( $id, '_tax_class', true ),  
  274. 'manage_stock' => get_post_meta( $id, '_manage_stock', true ),  
  275. 'stock_quantity' => get_post_meta( $id, '_stock', true ),  
  276. 'stock_status' => get_post_meta( $id, '_stock_status', true ),  
  277. 'backorders' => get_post_meta( $id, '_backorders', true ),  
  278. 'sold_individually' => get_post_meta( $id, '_sold_individually', true ),  
  279. 'weight' => get_post_meta( $id, '_weight', true ),  
  280. 'length' => get_post_meta( $id, '_length', true ),  
  281. 'width' => get_post_meta( $id, '_width', true ),  
  282. 'height' => get_post_meta( $id, '_height', true ),  
  283. 'upsell_ids' => get_post_meta( $id, '_upsell_ids', true ),  
  284. 'cross_sell_ids' => get_post_meta( $id, '_crosssell_ids', true ),  
  285. 'purchase_note' => get_post_meta( $id, '_purchase_note', true ),  
  286. 'default_attributes' => get_post_meta( $id, '_default_attributes', true ),  
  287. 'category_ids' => $this->get_term_ids( $product, 'product_cat' ),  
  288. 'tag_ids' => $this->get_term_ids( $product, 'product_tag' ),  
  289. 'shipping_class_id' => current( $this->get_term_ids( $product, 'product_shipping_class' ) ),  
  290. 'virtual' => get_post_meta( $id, '_virtual', true ),  
  291. 'downloadable' => get_post_meta( $id, '_downloadable', true ),  
  292. 'gallery_image_ids' => array_filter( explode( ', ', get_post_meta( $id, '_product_image_gallery', true ) ) ),  
  293. 'download_limit' => get_post_meta( $id, '_download_limit', true ),  
  294. 'download_expiry' => get_post_meta( $id, '_download_expiry', true ),  
  295. 'image_id' => get_post_thumbnail_id( $id ),  
  296. ) ); 
  297.  
  298. /** 
  299. * Read extra data associated with the product, like button text or product URL for external products. 
  300. * 
  301. * @param WC_Product 
  302. * @since 3.0.0 
  303. */ 
  304. protected function read_extra_data( &$product ) { 
  305. foreach ( $product->get_extra_data_keys() as $key ) { 
  306. $function = 'set_' . $key; 
  307. if ( is_callable( array( $product, $function ) ) ) { 
  308. $product->{$function}( get_post_meta( $product->get_id(), '_' . $key, true ) ); 
  309.  
  310. /** 
  311. * Convert visibility terms to props. 
  312. * Catalog visibility valid values are 'visible', 'catalog', 'search', and 'hidden'. 
  313. * 
  314. * @param WC_Product 
  315. * @since 3.0.0 
  316. */ 
  317. protected function read_visibility( &$product ) { 
  318. $terms = get_the_terms( $product->get_id(), 'product_visibility' ); 
  319. $term_names = is_array( $terms ) ? wp_list_pluck( $terms, 'name' ) : array(); 
  320. $featured = in_array( 'featured', $term_names ); 
  321. $exclude_search = in_array( 'exclude-from-search', $term_names ); 
  322. $exclude_catalog = in_array( 'exclude-from-catalog', $term_names ); 
  323.  
  324. if ( $exclude_search && $exclude_catalog ) { 
  325. $catalog_visibility = 'hidden'; 
  326. } elseif ( $exclude_search ) { 
  327. $catalog_visibility = 'catalog'; 
  328. } elseif ( $exclude_catalog ) { 
  329. $catalog_visibility = 'search'; 
  330. } else { 
  331. $catalog_visibility = 'visible'; 
  332.  
  333. $product->set_props( array( 
  334. 'featured' => $featured,  
  335. 'catalog_visibility' => $catalog_visibility,  
  336. ) ); 
  337.  
  338. /** 
  339. * Read attributes from post meta. 
  340. * 
  341. * @param WC_Product 
  342. * @since 3.0.0 
  343. */ 
  344. protected function read_attributes( &$product ) { 
  345. $meta_values = get_post_meta( $product->get_id(), '_product_attributes', true ); 
  346.  
  347. if ( ! empty( $meta_values ) && is_array( $meta_values ) ) { 
  348. $attributes = array(); 
  349. foreach ( $meta_values as $meta_value ) { 
  350. $id = 0; 
  351. $meta_value = array_merge( array( 
  352. 'name' => '',  
  353. 'value' => '',  
  354. 'position' => 0,  
  355. 'is_visible' => 0,  
  356. 'is_variation' => 0,  
  357. 'is_taxonomy' => 0,  
  358. ), (array) $meta_value ); 
  359.  
  360. // Check if is a taxonomy attribute. 
  361. if ( ! empty( $meta_value['is_taxonomy'] ) ) { 
  362. if ( ! taxonomy_exists( $meta_value['name'] ) ) { 
  363. continue; 
  364. $id = wc_attribute_taxonomy_id_by_name( $meta_value['name'] ); 
  365. $options = wc_get_object_terms( $product->get_id(), $meta_value['name'], 'term_id' ); 
  366. } else { 
  367. $options = wc_get_text_attributes( $meta_value['value'] ); 
  368.  
  369. $attribute = new WC_Product_Attribute(); 
  370.  
  371. $attribute->set_id( $id ); 
  372. $attribute->set_name( $meta_value['name'] ); 
  373. $attribute->set_options( $options ); 
  374. $attribute->set_position( $meta_value['position'] ); 
  375. $attribute->set_visible( $meta_value['is_visible'] ); 
  376. $attribute->set_variation( $meta_value['is_variation'] ); 
  377. $attributes[] = $attribute; 
  378. $product->set_attributes( $attributes ); 
  379.  
  380. /** 
  381. * Read downloads from post meta. 
  382. * 
  383. * @param WC_Product 
  384. * @since 3.0.0 
  385. */ 
  386. protected function read_downloads( &$product ) { 
  387. $meta_values = array_filter( (array) get_post_meta( $product->get_id(), '_downloadable_files', true ) ); 
  388.  
  389. if ( $meta_values ) { 
  390. $downloads = array(); 
  391. foreach ( $meta_values as $key => $value ) { 
  392. if ( ! isset( $value['name'], $value['file'] ) ) { 
  393. continue; 
  394. $download = new WC_Product_Download(); 
  395. $download->set_id( $key ); 
  396. $download->set_name( $value['name'] ? $value['name'] : wc_get_filename_from_url( $value['file'] ) ); 
  397. $download->set_file( apply_filters( 'woocommerce_file_download_path', $value['file'], $product, $key ) ); 
  398. $downloads[] = $download; 
  399. $product->set_downloads( $downloads ); 
  400.  
  401. /** 
  402. * Helper method that updates all the post meta for a product based on it's settings in the WC_Product class. 
  403. * 
  404. * @param WC_Product 
  405. * @param bool Force update. Used during create. 
  406. * @since 3.0.0 
  407. */ 
  408. protected function update_post_meta( &$product, $force = false ) { 
  409. $meta_key_to_props = array( 
  410. '_sku' => 'sku',  
  411. '_regular_price' => 'regular_price',  
  412. '_sale_price' => 'sale_price',  
  413. '_sale_price_dates_from' => 'date_on_sale_from',  
  414. '_sale_price_dates_to' => 'date_on_sale_to',  
  415. 'total_sales' => 'total_sales',  
  416. '_tax_status' => 'tax_status',  
  417. '_tax_class' => 'tax_class',  
  418. '_manage_stock' => 'manage_stock',  
  419. '_backorders' => 'backorders',  
  420. '_sold_individually' => 'sold_individually',  
  421. '_weight' => 'weight',  
  422. '_length' => 'length',  
  423. '_width' => 'width',  
  424. '_height' => 'height',  
  425. '_upsell_ids' => 'upsell_ids',  
  426. '_crosssell_ids' => 'cross_sell_ids',  
  427. '_purchase_note' => 'purchase_note',  
  428. '_default_attributes' => 'default_attributes',  
  429. '_virtual' => 'virtual',  
  430. '_downloadable' => 'downloadable',  
  431. '_product_image_gallery' => 'gallery_image_ids',  
  432. '_download_limit' => 'download_limit',  
  433. '_download_expiry' => 'download_expiry',  
  434. '_thumbnail_id' => 'image_id',  
  435. '_stock' => 'stock_quantity',  
  436. '_stock_status' => 'stock_status',  
  437. '_wc_average_rating' => 'average_rating',  
  438. '_wc_rating_count' => 'rating_counts',  
  439. '_wc_review_count' => 'review_count',  
  440. ); 
  441.  
  442. // Make sure to take extra data (like product url or text for external products) into account. 
  443. $extra_data_keys = $product->get_extra_data_keys(); 
  444.  
  445. foreach ( $extra_data_keys as $key ) { 
  446. $meta_key_to_props[ '_' . $key ] = $key; 
  447.  
  448. $props_to_update = $force ? $meta_key_to_props : $this->get_props_to_update( $product, $meta_key_to_props ); 
  449.  
  450. foreach ( $props_to_update as $meta_key => $prop ) { 
  451. $value = $product->{"get_$prop"}( 'edit' ); 
  452. switch ( $prop ) { 
  453. case 'virtual' : 
  454. case 'downloadable' : 
  455. case 'manage_stock' : 
  456. case 'sold_individually' : 
  457. $updated = update_post_meta( $product->get_id(), $meta_key, wc_bool_to_string( $value ) ); 
  458. break; 
  459. case 'gallery_image_ids' : 
  460. $updated = update_post_meta( $product->get_id(), $meta_key, implode( ', ', $value ) ); 
  461. break; 
  462. case 'image_id' : 
  463. if ( ! empty( $value ) ) { 
  464. set_post_thumbnail( $product->get_id(), $value ); 
  465. } else { 
  466. delete_post_meta( $product->get_id(), '_thumbnail_id' ); 
  467. $updated = true; 
  468. break; 
  469. case 'date_on_sale_from' : 
  470. case 'date_on_sale_to' : 
  471. $updated = update_post_meta( $product->get_id(), $meta_key, $value ? $value->getTimestamp() : '' ); 
  472. break; 
  473. default : 
  474. $updated = update_post_meta( $product->get_id(), $meta_key, $value ); 
  475. break; 
  476. if ( $updated ) { 
  477. $this->updated_props[] = $prop; 
  478.  
  479. // Update extra data associated with the product like button text or product URL for external products. 
  480. if ( ! $this->extra_data_saved ) { 
  481. foreach ( $extra_data_keys as $key ) { 
  482. if ( ! array_key_exists( $key, $props_to_update ) ) { 
  483. continue; 
  484. $function = 'get_' . $key; 
  485. if ( is_callable( array( $product, $function ) ) ) { 
  486. if ( update_post_meta( $product->get_id(), '_' . $key, $product->{$function}( 'edit' ) ) ) { 
  487. $this->updated_props[] = $key; 
  488.  
  489. if ( $this->update_downloads( $product, $force ) ) { 
  490. $this->updated_props[] = 'downloads'; 
  491.  
  492. /** 
  493. * Handle updated meta props after updating meta data. 
  494. * 
  495. * @since 3.0.0 
  496. * @param WC_Product $product 
  497. */ 
  498. protected function handle_updated_props( &$product ) { 
  499. if ( in_array( 'date_on_sale_from', $this->updated_props ) || in_array( 'date_on_sale_to', $this->updated_props ) || in_array( 'regular_price', $this->updated_props ) || in_array( 'sale_price', $this->updated_props ) ) { 
  500. if ( $product->is_on_sale( 'edit' ) ) { 
  501. update_post_meta( $product->get_id(), '_price', $product->get_sale_price( 'edit' ) ); 
  502. $product->set_price( $product->get_sale_price( 'edit' ) ); 
  503. } else { 
  504. update_post_meta( $product->get_id(), '_price', $product->get_regular_price( 'edit' ) ); 
  505. $product->set_price( $product->get_regular_price( 'edit' ) ); 
  506.  
  507. if ( in_array( 'stock_quantity', $this->updated_props ) ) { 
  508. do_action( $product->is_type( 'variation' ) ? 'woocommerce_variation_set_stock' : 'woocommerce_product_set_stock' , $product ); 
  509.  
  510. if ( in_array( 'stock_status', $this->updated_props ) ) { 
  511. do_action( $product->is_type( 'variation' ) ? 'woocommerce_variation_set_stock_status' : 'woocommerce_product_set_stock_status' , $product->get_id(), $product->get_stock_status(), $product ); 
  512.  
  513. // Trigger action so 3rd parties can deal with updated props. 
  514. do_action( 'woocommerce_product_object_updated_props', $product, $this->updated_props ); 
  515.  
  516. // After handling, we can reset the props array. 
  517. $this->updated_props = array(); 
  518.  
  519. /** 
  520. * For all stored terms in all taxonomies, save them to the DB. 
  521. * 
  522. * @param WC_Product 
  523. * @param bool Force update. Used during create. 
  524. * @since 3.0.0 
  525. */ 
  526. protected function update_terms( &$product, $force = false ) { 
  527. $changes = $product->get_changes(); 
  528.  
  529. if ( $force || array_key_exists( 'category_ids', $changes ) ) { 
  530. wp_set_post_terms( $product->get_id(), $product->get_category_ids( 'edit' ), 'product_cat', false ); 
  531. if ( $force || array_key_exists( 'tag_ids', $changes ) ) { 
  532. wp_set_post_terms( $product->get_id(), $product->get_tag_ids( 'edit' ), 'product_tag', false ); 
  533. if ( $force || array_key_exists( 'shipping_class_id', $changes ) ) { 
  534. wp_set_post_terms( $product->get_id(), array( $product->get_shipping_class_id( 'edit' ) ), 'product_shipping_class', false ); 
  535.  
  536. /** 
  537. * Update visibility terms based on props. 
  538. * 
  539. * @since 3.0.0 
  540. * @param bool Force update. Used during create. 
  541. * @param WC_Product 
  542. */ 
  543. protected function update_visibility( &$product, $force = false ) { 
  544. $changes = $product->get_changes(); 
  545.  
  546. if ( $force || array_intersect( array( 'featured', 'stock_status', 'average_rating', 'catalog_visibility' ), array_keys( $changes ) ) ) { 
  547. $terms = array(); 
  548.  
  549. if ( $product->get_featured() ) { 
  550. $terms[] = 'featured'; 
  551.  
  552. if ( 'outofstock' === $product->get_stock_status() ) { 
  553. $terms[] = 'outofstock'; 
  554.  
  555. $rating = max( array( 5, min( array( 1, round( $product->get_average_rating(), 0 ) ) ) ) ); 
  556. $terms[] = 'rated-' . $rating; 
  557.  
  558. switch ( $product->get_catalog_visibility() ) { 
  559. case 'hidden' : 
  560. $terms[] = 'exclude-from-search'; 
  561. $terms[] = 'exclude-from-catalog'; 
  562. break; 
  563. case 'catalog' : 
  564. $terms[] = 'exclude-from-search'; 
  565. break; 
  566. case 'search' : 
  567. $terms[] = 'exclude-from-catalog'; 
  568. break; 
  569.  
  570. if ( ! is_wp_error( wp_set_post_terms( $product->get_id(), $terms, 'product_visibility', false ) ) ) { 
  571. delete_transient( 'wc_featured_products' ); 
  572. do_action( 'woocommerce_product_set_visibility', $product->get_id(), $product->get_catalog_visibility() ); 
  573.  
  574. /** 
  575. * Update attributes which are a mix of terms and meta data. 
  576. * 
  577. * @param WC_Product 
  578. * @param bool Force update. Used during create. 
  579. * @since 3.0.0 
  580. */ 
  581. protected function update_attributes( &$product, $force = false ) { 
  582. $changes = $product->get_changes(); 
  583.  
  584. if ( $force || array_key_exists( 'attributes', $changes ) ) { 
  585. $attributes = $product->get_attributes(); 
  586. $meta_values = array(); 
  587.  
  588. if ( $attributes ) { 
  589. foreach ( $attributes as $attribute_key => $attribute ) { 
  590. $value = ''; 
  591.  
  592. if ( is_null( $attribute ) ) { 
  593. if ( taxonomy_exists( $attribute_key ) ) { 
  594. // Handle attributes that have been unset. 
  595. wp_set_object_terms( $product->get_id(), array(), $attribute_key ); 
  596. continue; 
  597.  
  598. } elseif ( $attribute->is_taxonomy() ) { 
  599. wp_set_object_terms( $product->get_id(), wp_list_pluck( $attribute->get_terms(), 'term_id' ), $attribute->get_name() ); 
  600. } else { 
  601. $value = wc_implode_text_attributes( $attribute->get_options() ); 
  602.  
  603. // Store in format WC uses in meta. 
  604. $meta_values[ $attribute_key ] = array( 
  605. 'name' => $attribute->get_name(),  
  606. 'value' => $value,  
  607. 'position' => $attribute->get_position(),  
  608. 'is_visible' => $attribute->get_visible() ? 1 : 0,  
  609. 'is_variation' => $attribute->get_variation() ? 1 : 0,  
  610. 'is_taxonomy' => $attribute->is_taxonomy() ? 1 : 0,  
  611. ); 
  612. update_post_meta( $product->get_id(), '_product_attributes', $meta_values ); 
  613.  
  614. /** 
  615. * Update downloads. 
  616. * 
  617. * @since 3.0.0 
  618. * @param WC_Product $product 
  619. * @param bool Force update. Used during create. 
  620. * @return bool If updated or not. 
  621. */ 
  622. protected function update_downloads( &$product, $force = false ) { 
  623. $changes = $product->get_changes(); 
  624.  
  625. if ( $force || array_key_exists( 'downloads', $changes ) ) { 
  626. $downloads = $product->get_downloads(); 
  627. $meta_values = array(); 
  628.  
  629. if ( $downloads ) { 
  630. foreach ( $downloads as $key => $download ) { 
  631. // Store in format WC uses in meta. 
  632. $meta_values[ $key ] = $download->get_data(); 
  633.  
  634. if ( $product->is_type( 'variation' ) ) { 
  635. do_action( 'woocommerce_process_product_file_download_paths', $product->get_parent_id(), $product->get_id(), $downloads ); 
  636. } else { 
  637. do_action( 'woocommerce_process_product_file_download_paths', $product->get_id(), 0, $downloads ); 
  638.  
  639. return update_post_meta( $product->get_id(), '_downloadable_files', $meta_values ); 
  640. return false; 
  641.  
  642. /** 
  643. * Make sure we store the product type and version (to track data changes). 
  644. * 
  645. * @param WC_Product 
  646. * @since 3.0.0 
  647. */ 
  648. protected function update_version_and_type( &$product ) { 
  649. $old_type = WC_Product_Factory::get_product_type( $product->get_id() ); 
  650. $new_type = $product->get_type(); 
  651.  
  652. wp_set_object_terms( $product->get_id(), $new_type, 'product_type' ); 
  653. update_post_meta( $product->get_id(), '_product_version', WC_VERSION ); 
  654.  
  655. // Action for the transition. 
  656. if ( $old_type !== $new_type ) { 
  657. do_action( 'woocommerce_product_type_changed', $product, $old_type, $new_type ); 
  658.  
  659. /** 
  660. * Clear any caches. 
  661. * 
  662. * @param WC_Product 
  663. * @since 3.0.0 
  664. */ 
  665. protected function clear_caches( &$product ) { 
  666. wc_delete_product_transients( $product->get_id() ); 
  667.  
  668. /** 
  669. |-------------------------------------------------------------------------- 
  670. | wc-product-functions.php methods 
  671. |-------------------------------------------------------------------------- 
  672. */ 
  673.  
  674. /** 
  675. * Returns an array of on sale products, as an array of objects with an 
  676. * ID and parent_id present. Example: $return[0]->id, $return[0]->parent_id. 
  677. * 
  678. * @return array 
  679. * @since 3.0.0 
  680. */ 
  681. public function get_on_sale_products() { 
  682. global $wpdb; 
  683.  
  684. $decimals = absint( wc_get_price_decimals() ); 
  685.  
  686. return $wpdb->get_results( " 
  687. SELECT post.ID as id, post.post_parent as parent_id FROM `$wpdb->posts` AS post 
  688. LEFT JOIN `$wpdb->postmeta` AS meta ON post.ID = meta.post_id 
  689. LEFT JOIN `$wpdb->postmeta` AS meta2 ON post.ID = meta2.post_id 
  690. WHERE post.post_type IN ( 'product', 'product_variation' ) 
  691. AND post.post_status = 'publish' 
  692. AND meta.meta_key = '_sale_price' 
  693. AND meta2.meta_key = '_price' 
  694. AND CAST( meta.meta_value AS DECIMAL ) >= 0 
  695. AND CAST( meta.meta_value AS CHAR ) != '' 
  696. AND CAST( meta.meta_value AS DECIMAL( 10, {$decimals} ) ) = CAST( meta2.meta_value AS DECIMAL( 10, {$decimals} ) ) 
  697. GROUP BY post.ID; 
  698. " ); 
  699.  
  700. /** 
  701. * Returns a list of product IDs ( id as key => parent as value) that are 
  702. * featured. Uses get_posts instead of wc_get_products since we want 
  703. * some extra meta queries and ALL products (posts_per_page = -1). 
  704. * 
  705. * @return array 
  706. * @since 3.0.0 
  707. */ 
  708. public function get_featured_product_ids() { 
  709. $product_visibility_term_ids = wc_get_product_visibility_term_ids(); 
  710.  
  711. return get_posts( array( 
  712. 'post_type' => array( 'product', 'product_variation' ),  
  713. 'posts_per_page' => -1,  
  714. 'post_status' => 'publish',  
  715. 'tax_query' => array( 
  716. 'relation' => 'AND',  
  717. array( 
  718. 'taxonomy' => 'product_visibility',  
  719. 'field' => 'term_taxonomy_id',  
  720. 'terms' => array( $product_visibility_term_ids['featured'] ),  
  721. ),  
  722. array( 
  723. 'taxonomy' => 'product_visibility',  
  724. 'field' => 'term_taxonomy_id',  
  725. 'terms' => array( $product_visibility_term_ids['exclude-from-catalog'] ),  
  726. 'operator' => 'NOT IN',  
  727. ),  
  728. ),  
  729. 'fields' => 'id=>parent',  
  730. ) ); 
  731.  
  732. /** 
  733. * Check if product sku is found for any other product IDs. 
  734. * 
  735. * @since 3.0.0 
  736. * @param int $product_id 
  737. * @param string $sku Will be slashed to work around https://core.trac.wordpress.org/ticket/27421 
  738. * @return bool 
  739. */ 
  740. public function is_existing_sku( $product_id, $sku ) { 
  741. global $wpdb; 
  742. return $wpdb->get_var( $wpdb->prepare( " 
  743. SELECT $wpdb->posts.ID 
  744. FROM $wpdb->posts 
  745. LEFT JOIN $wpdb->postmeta ON ( $wpdb->posts.ID = $wpdb->postmeta.post_id ) 
  746. WHERE $wpdb->posts.post_type IN ( 'product', 'product_variation' ) 
  747. AND $wpdb->posts.post_status != 'trash' 
  748. AND $wpdb->postmeta.meta_key = '_sku' AND $wpdb->postmeta.meta_value = '%s' 
  749. AND $wpdb->postmeta.post_id <> %d LIMIT 1 
  750. ", wp_slash( $sku ), $product_id ) ); 
  751.  
  752. /** 
  753. * Return product ID based on SKU. 
  754. * 
  755. * @since 3.0.0 
  756. * @param string $sku 
  757. * @return int 
  758. */ 
  759. public function get_product_id_by_sku( $sku ) { 
  760. global $wpdb; 
  761. return $wpdb->get_var( $wpdb->prepare( " 
  762. SELECT posts.ID 
  763. FROM $wpdb->posts AS posts 
  764. LEFT JOIN $wpdb->postmeta AS postmeta ON ( posts.ID = postmeta.post_id ) 
  765. WHERE posts.post_type IN ( 'product', 'product_variation' ) 
  766. AND posts.post_status != 'trash' 
  767. AND postmeta.meta_key = '_sku' 
  768. AND postmeta.meta_value = '%s' 
  769. LIMIT 1 
  770. ", $sku ) ); 
  771.  
  772. /** 
  773. * Returns an array of IDs of products that have sales starting soon. 
  774. * 
  775. * @since 3.0.0 
  776. * @return array 
  777. */ 
  778. public function get_starting_sales() { 
  779. global $wpdb; 
  780. return $wpdb->get_col( $wpdb->prepare( " 
  781. SELECT postmeta.post_id FROM {$wpdb->postmeta} as postmeta 
  782. LEFT JOIN {$wpdb->postmeta} as postmeta_2 ON postmeta.post_id = postmeta_2.post_id 
  783. LEFT JOIN {$wpdb->postmeta} as postmeta_3 ON postmeta.post_id = postmeta_3.post_id 
  784. WHERE postmeta.meta_key = '_sale_price_dates_from' 
  785. AND postmeta_2.meta_key = '_price' 
  786. AND postmeta_3.meta_key = '_sale_price' 
  787. AND postmeta.meta_value > 0 
  788. AND postmeta.meta_value < %s 
  789. AND postmeta_2.meta_value != postmeta_3.meta_value 
  790. ", current_time( 'timestamp', true ) ) ); 
  791.  
  792. /** 
  793. * Returns an array of IDs of products that have sales which are due to end. 
  794. * 
  795. * @since 3.0.0 
  796. * @return array 
  797. */ 
  798. public function get_ending_sales() { 
  799. global $wpdb; 
  800. return $wpdb->get_col( $wpdb->prepare( " 
  801. SELECT postmeta.post_id FROM {$wpdb->postmeta} as postmeta 
  802. LEFT JOIN {$wpdb->postmeta} as postmeta_2 ON postmeta.post_id = postmeta_2.post_id 
  803. LEFT JOIN {$wpdb->postmeta} as postmeta_3 ON postmeta.post_id = postmeta_3.post_id 
  804. WHERE postmeta.meta_key = '_sale_price_dates_to' 
  805. AND postmeta_2.meta_key = '_price' 
  806. AND postmeta_3.meta_key = '_regular_price' 
  807. AND postmeta.meta_value > 0 
  808. AND postmeta.meta_value < %s 
  809. AND postmeta_2.meta_value != postmeta_3.meta_value 
  810. ", current_time( 'timestamp', true ) ) ); 
  811.  
  812. /** 
  813. * Find a matching (enabled) variation within a variable product. 
  814. * 
  815. * @since 3.0.0 
  816. * @param WC_Product $product Variable product. 
  817. * @param array $match_attributes Array of attributes we want to try to match. 
  818. * @return int Matching variation ID or 0. 
  819. */ 
  820. public function find_matching_product_variation( $product, $match_attributes = array() ) { 
  821. $query_args = array( 
  822. 'post_parent' => $product->get_id(),  
  823. 'post_type' => 'product_variation',  
  824. 'orderby' => 'menu_order',  
  825. 'order' => 'ASC',  
  826. 'fields' => 'ids',  
  827. 'post_status' => 'publish',  
  828. 'numberposts' => 1,  
  829. 'meta_query' => array(),  
  830. ); 
  831.  
  832. // Allow large queries in case user has many variations or attributes. 
  833. $GLOBALS['wpdb']->query( 'SET SESSION SQL_BIG_SELECTS=1' ); 
  834.  
  835. foreach ( $product->get_attributes() as $attribute ) { 
  836. if ( ! $attribute->get_variation() ) { 
  837. continue; 
  838.  
  839. $attribute_field_name = 'attribute_' . sanitize_title( $attribute->get_name() ); 
  840.  
  841. if ( ! isset( $match_attributes[ $attribute_field_name ] ) ) { 
  842. return 0; 
  843.  
  844. // Note not wc_clean here to prevent removal of entities. 
  845. $value = $match_attributes[ $attribute_field_name ]; 
  846.  
  847. $query_args['meta_query'][] = array( 
  848. 'relation' => 'OR',  
  849. array( 
  850. 'key' => $attribute_field_name,  
  851. 'value' => array( '', $value ),  
  852. 'compare' => 'IN',  
  853. ),  
  854. array( 
  855. 'key' => $attribute_field_name,  
  856. 'compare' => 'NOT EXISTS',  
  857. ); 
  858.  
  859. $variations = get_posts( $query_args ); 
  860.  
  861. if ( $variations && ! is_wp_error( $variations ) ) { 
  862. return current( $variations ); 
  863. } elseif ( version_compare( get_post_meta( $product->get_id(), '_product_version', true ), '2.4.0', '<' ) ) { 
  864. /** 
  865. * Pre 2.4 handling where 'slugs' were saved instead of the full text attribute. 
  866. * Fallback is here because there are cases where data will be 'synced' but the product version will remain the same. 
  867. */ 
  868. return ( array_map( 'sanitize_title', $match_attributes ) === $match_attributes ) ? 0 : $this->find_matching_product_variation( $product, array_map( 'sanitize_title', $match_attributes ) ); 
  869.  
  870. return 0; 
  871.  
  872. /** 
  873. * Make sure all variations have a sort order set so they can be reordered correctly. 
  874. * 
  875. * @param int $parent_id 
  876. */ 
  877. public function sort_all_product_variations( $parent_id ) { 
  878. global $wpdb; 
  879. $ids = $wpdb->get_col( $wpdb->prepare( "SELECT ID FROM {$wpdb->posts} WHERE post_type='product_variation' AND post_parent=%d AND post_status='publish' ORDER BY menu_order ASC, ID ASC", $parent_id ) ); 
  880. $index = 0; 
  881.  
  882. foreach ( $ids as $id ) { 
  883. $wpdb->update( $wpdb->posts, array( 'menu_order' => ( $index ++ ) ), array( 'ID' => absint( $id ) ) ); 
  884.  
  885. /** 
  886. * Return a list of related products (using data like categories and IDs). 
  887. * 
  888. * @since 3.0.0 
  889. * @param array $cats_array List of categories IDs. 
  890. * @param array $tags_array List of tags IDs. 
  891. * @param array $exclude_ids Excluded IDs. 
  892. * @param int $limit Limit of results. 
  893. * @param int $product_id 
  894. * @return array 
  895. */ 
  896. public function get_related_products( $cats_array, $tags_array, $exclude_ids, $limit, $product_id ) { 
  897. global $wpdb; 
  898. return $wpdb->get_col( implode( ' ', apply_filters( 'woocommerce_product_related_posts_query', $this->get_related_products_query( $cats_array, $tags_array, $exclude_ids, $limit + 10 ), $product_id ) ) ); 
  899.  
  900. /** 
  901. * Builds the related posts query. 
  902. * 
  903. * @since 3.0.0 
  904. * @param array $cats_array List of categories IDs. 
  905. * @param array $tags_array List of tags IDs. 
  906. * @param array $exclude_ids Excluded IDs. 
  907. * @param int $limit Limit of results. 
  908. * @return string 
  909. */ 
  910. public function get_related_products_query( $cats_array, $tags_array, $exclude_ids, $limit ) { 
  911. global $wpdb; 
  912.  
  913. $include_term_ids = array_merge( $cats_array, $tags_array ); 
  914. $exclude_term_ids = array(); 
  915. $product_visibility_term_ids = wc_get_product_visibility_term_ids(); 
  916.  
  917. if ( $product_visibility_term_ids['exclude-from-catalog'] ) { 
  918. $exclude_term_ids[] = $product_visibility_term_ids['exclude-from-catalog']; 
  919.  
  920. if ( 'yes' === get_option( 'woocommerce_hide_out_of_stock_items' ) && $product_visibility_term_ids['outofstock'] ) { 
  921. $exclude_term_ids[] = $product_visibility_term_ids['outofstock']; 
  922.  
  923. $query = array( 
  924. 'fields' => " 
  925. SELECT DISTINCT ID FROM {$wpdb->posts} p 
  926. ",  
  927. 'join' => '',  
  928. 'where' => " 
  929. WHERE 1=1 
  930. AND p.post_status = 'publish' 
  931. AND p.post_type = 'product' 
  932.  
  933. ",  
  934. 'limits' => " 
  935. LIMIT " . absint( $limit ) . " 
  936. ",  
  937. ); 
  938.  
  939. if ( count( $exclude_term_ids ) ) { 
  940. $query['join'] .= " LEFT JOIN ( SELECT object_id FROM {$wpdb->term_relationships} WHERE term_taxonomy_id IN ( " . implode( ', ', array_map( 'absint', $exclude_term_ids ) ) . " ) ) AS exclude_join ON exclude_join.object_id = p.ID"; 
  941. $query['where'] .= " AND exclude_join.object_id IS NULL"; 
  942.  
  943. if ( count( $include_term_ids ) ) { 
  944. $query['join'] .= " INNER JOIN ( SELECT object_id FROM {$wpdb->term_relationships} INNER JOIN {$wpdb->term_taxonomy} using( term_taxonomy_id ) WHERE term_id IN ( " . implode( ', ', array_map( 'absint', $include_term_ids ) ) . " ) ) AS include_join ON include_join.object_id = p.ID"; 
  945.  
  946. if ( count( $exclude_ids ) ) { 
  947. $query['where'] .= " AND p.ID NOT IN ( " . implode( ', ', array_map( 'absint', $exclude_ids ) ) . " )"; 
  948.  
  949. return $query; 
  950.  
  951. /** 
  952. * Update a product's stock amount directly. 
  953. * 
  954. * Uses queries rather than update_post_meta so we can do this in one query (to avoid stock issues). 
  955. * 
  956. * @since 3.0.0 this supports set, increase and decrease. 
  957. * @param int 
  958. * @param int|null $stock_quantity 
  959. * @param string $operation set, increase and decrease. 
  960. */ 
  961. public function update_product_stock( $product_id_with_stock, $stock_quantity = null, $operation = 'set' ) { 
  962. global $wpdb; 
  963. add_post_meta( $product_id_with_stock, '_stock', 0, true ); 
  964.  
  965. // Update stock in DB directly 
  966. switch ( $operation ) { 
  967. case 'increase' : 
  968. $wpdb->query( $wpdb->prepare( "UPDATE {$wpdb->postmeta} SET meta_value = meta_value + %f WHERE post_id = %d AND meta_key='_stock'", $stock_quantity, $product_id_with_stock ) ); 
  969. break; 
  970. case 'decrease' : 
  971. $wpdb->query( $wpdb->prepare( "UPDATE {$wpdb->postmeta} SET meta_value = meta_value - %f WHERE post_id = %d AND meta_key='_stock'", $stock_quantity, $product_id_with_stock ) ); 
  972. break; 
  973. default : 
  974. $wpdb->query( $wpdb->prepare( "UPDATE {$wpdb->postmeta} SET meta_value = %f WHERE post_id = %d AND meta_key='_stock'", $stock_quantity, $product_id_with_stock ) ); 
  975. break; 
  976.  
  977. wp_cache_delete( $product_id_with_stock, 'post_meta' ); 
  978.  
  979. /** 
  980. * Update a product's sale count directly. 
  981. * 
  982. * Uses queries rather than update_post_meta so we can do this in one query for performance. 
  983. * 
  984. * @since 3.0.0 this supports set, increase and decrease. 
  985. * @param int 
  986. * @param int|null $quantity 
  987. * @param string $operation set, increase and decrease. 
  988. */ 
  989. public function update_product_sales( $product_id, $quantity = null, $operation = 'set' ) { 
  990. global $wpdb; 
  991. add_post_meta( $product_id, 'total_sales', 0, true ); 
  992.  
  993. // Update stock in DB directly 
  994. switch ( $operation ) { 
  995. case 'increase' : 
  996. $wpdb->query( $wpdb->prepare( "UPDATE {$wpdb->postmeta} SET meta_value = meta_value + %f WHERE post_id = %d AND meta_key='total_sales'", $quantity, $product_id ) ); 
  997. break; 
  998. case 'decrease' : 
  999. $wpdb->query( $wpdb->prepare( "UPDATE {$wpdb->postmeta} SET meta_value = meta_value - %f WHERE post_id = %d AND meta_key='total_sales'", $quantity, $product_id ) ); 
  1000. break; 
  1001. default : 
  1002. $wpdb->query( $wpdb->prepare( "UPDATE {$wpdb->postmeta} SET meta_value = %f WHERE post_id = %d AND meta_key='total_sales'", $quantity, $product_id ) ); 
  1003. break; 
  1004.  
  1005. wp_cache_delete( $product_id, 'post_meta' ); 
  1006.  
  1007. /** 
  1008. * Update a products average rating meta. 
  1009. * 
  1010. * @since 3.0.0 
  1011. * @param WC_Product $product 
  1012. */ 
  1013. public function update_average_rating( $product ) { 
  1014. update_post_meta( $product->get_id(), '_wc_average_rating', $product->get_average_rating( 'edit' ) ); 
  1015.  
  1016. /** 
  1017. * Update a products review count meta. 
  1018. * 
  1019. * @since 3.0.0 
  1020. * @param WC_Product $product 
  1021. */ 
  1022. public function update_review_count( $product ) { 
  1023. update_post_meta( $product->get_id(), '_wc_review_count', $product->get_review_count( 'edit' ) ); 
  1024.  
  1025. /** 
  1026. * Update a products rating counts. 
  1027. * 
  1028. * @since 3.0.0 
  1029. * @param WC_Product $product 
  1030. */ 
  1031. public function update_rating_counts( $product ) { 
  1032. update_post_meta( $product->get_id(), '_wc_rating_count', $product->get_rating_counts( 'edit' ) ); 
  1033.  
  1034. /** 
  1035. * Get shipping class ID by slug. 
  1036. * 
  1037. * @since 3.0.0 
  1038. * @param $slug string 
  1039. * @return int|false 
  1040. */ 
  1041. public function get_shipping_class_id_by_slug( $slug ) { 
  1042. $shipping_class_term = get_term_by( 'slug', $slug, 'product_shipping_class' ); 
  1043. if ( $shipping_class_term ) { 
  1044. return $shipping_class_term->term_id; 
  1045. } else { 
  1046. return false; 
  1047.  
  1048. /** 
  1049. * Returns an array of products. 
  1050. * 
  1051. * @param array $args @see wc_get_products 
  1052. * @return array 
  1053. */ 
  1054. public function get_products( $args = array() ) { 
  1055. /** 
  1056. * Generate WP_Query args. 
  1057. */ 
  1058. $wp_query_args = array( 
  1059. 'post_type' => 'variation' === $args['type'] ? 'product_variation' : 'product',  
  1060. 'post_status' => $args['status'],  
  1061. 'posts_per_page' => $args['limit'],  
  1062. 'meta_query' => array(),  
  1063. 'orderby' => $args['orderby'],  
  1064. 'order' => $args['order'],  
  1065. 'tax_query' => array(),  
  1066. ); 
  1067. // Do not load unnecessary post data if the user only wants IDs. 
  1068. if ( 'ids' === $args['return'] ) { 
  1069. $wp_query_args['fields'] = 'ids'; 
  1070.  
  1071. if ( 'variation' !== $args['type'] ) { 
  1072. $wp_query_args['tax_query'][] = array( 
  1073. 'taxonomy' => 'product_type',  
  1074. 'field' => 'slug',  
  1075. 'terms' => $args['type'],  
  1076. ); 
  1077.  
  1078. if ( ! empty( $args['sku'] ) ) { 
  1079. $wp_query_args['meta_query'][] = array( 
  1080. 'key' => '_sku',  
  1081. 'value' => $args['sku'],  
  1082. 'compare' => 'LIKE',  
  1083. ); 
  1084.  
  1085. if ( ! empty( $args['category'] ) ) { 
  1086. $wp_query_args['tax_query'][] = array( 
  1087. 'taxonomy' => 'product_cat',  
  1088. 'field' => 'slug',  
  1089. 'terms' => $args['category'],  
  1090. ); 
  1091.  
  1092. if ( ! empty( $args['tag'] ) ) { 
  1093. $wp_query_args['tax_query'][] = array( 
  1094. 'taxonomy' => 'product_tag',  
  1095. 'field' => 'slug',  
  1096. 'terms' => $args['tag'],  
  1097. ); 
  1098.  
  1099. if ( ! empty( $args['shipping_class'] ) ) { 
  1100. $wp_query_args['tax_query'][] = array( 
  1101. 'taxonomy' => 'product_shipping_class',  
  1102. 'field' => 'slug',  
  1103. 'terms' => $args['shipping_class'],  
  1104. ); 
  1105.  
  1106. if ( ! is_null( $args['parent'] ) ) { 
  1107. $wp_query_args['post_parent'] = absint( $args['parent'] ); 
  1108.  
  1109. if ( ! is_null( $args['offset'] ) ) { 
  1110. $wp_query_args['offset'] = absint( $args['offset'] ); 
  1111. } else { 
  1112. $wp_query_args['paged'] = absint( $args['page'] ); 
  1113.  
  1114. if ( ! empty( $args['include'] ) ) { 
  1115. $wp_query_args['post__in'] = array_map( 'absint', $args['include'] ); 
  1116.  
  1117. if ( ! empty( $args['exclude'] ) ) { 
  1118. $wp_query_args['post__not_in'] = array_map( 'absint', $args['exclude'] ); 
  1119.  
  1120. if ( ! $args['paginate'] ) { 
  1121. $wp_query_args['no_found_rows'] = true; 
  1122.  
  1123. // Get results. 
  1124. $products = new WP_Query( $wp_query_args ); 
  1125.  
  1126. if ( 'objects' === $args['return'] ) { 
  1127. $return = array_filter( array_map( 'wc_get_product', $products->posts ) ); 
  1128. } else { 
  1129. $return = $products->posts; 
  1130.  
  1131. if ( $args['paginate'] ) { 
  1132. return (object) array( 
  1133. 'products' => $return,  
  1134. 'total' => $products->found_posts,  
  1135. 'max_num_pages' => $products->max_num_pages,  
  1136. ); 
  1137. } else { 
  1138. return $return; 
  1139.  
  1140. /** 
  1141. * Search product data for a term and return ids. 
  1142. * 
  1143. * @param string $term 
  1144. * @param string $type of product 
  1145. * @param bool $include_variations in search or not 
  1146. * @return array of ids 
  1147. */ 
  1148. public function search_products( $term, $type = '', $include_variations = false ) { 
  1149. global $wpdb; 
  1150.  
  1151. $search_fields = array_map( 'wc_clean', apply_filters( 'woocommerce_product_search_fields', array( 
  1152. '_sku',  
  1153. ) ) ); 
  1154. $like_term = '%' . $wpdb->esc_like( $term ) . '%'; 
  1155. $post_types = $include_variations ? array( 'product', 'product_variation' ) : array( 'product' ); 
  1156. $post_statuses = current_user_can( 'edit_private_products' ) ? array( 'private', 'publish' ) : array( 'publish' ); 
  1157. $type_join = ''; 
  1158. $type_where = ''; 
  1159.  
  1160. if ( $type ) { 
  1161. if ( in_array( $type, array( 'virtual', 'downloadable' ) ) ) { 
  1162. $type_join = " LEFT JOIN {$wpdb->postmeta} postmeta_type ON posts.ID = postmeta_type.post_id "; 
  1163. $type_where = " AND ( postmeta_type.meta_key = '_{$type}' AND postmeta_type.meta_value = 'yes' ) "; 
  1164.  
  1165. $product_ids = $wpdb->get_col( 
  1166. $wpdb->prepare( " 
  1167. SELECT DISTINCT posts.ID FROM {$wpdb->posts} posts 
  1168. LEFT JOIN {$wpdb->postmeta} postmeta ON posts.ID = postmeta.post_id 
  1169. $type_join 
  1170. WHERE ( 
  1171. posts.post_title LIKE %s 
  1172. OR posts.post_content LIKE %s 
  1173. OR ( 
  1174. postmeta.meta_key = '_sku' AND postmeta.meta_value LIKE %s 
  1175. AND posts.post_type IN ('" . implode( "', '", $post_types ) . "') 
  1176. AND posts.post_status IN ('" . implode( "', '", $post_statuses ) . "') 
  1177. $type_where 
  1178. ORDER BY posts.post_parent ASC, posts.post_title ASC 
  1179. ",  
  1180. $like_term,  
  1181. $like_term,  
  1182. $like_term 
  1183. ); 
  1184.  
  1185. if ( is_numeric( $term ) ) { 
  1186. $post_id = absint( $term ); 
  1187. $post_type = get_post_type( $post_id ); 
  1188.  
  1189. if ( 'product_variation' === $post_type && $include_variations ) { 
  1190. $product_ids[] = $post_id; 
  1191. } elseif ( 'product' === $post_type ) { 
  1192. $product_ids[] = $post_id; 
  1193.  
  1194. $product_ids[] = wp_get_post_parent_id( $post_id ); 
  1195.  
  1196. return wp_parse_id_list( $product_ids ); 
  1197.  
  1198. /** 
  1199. * Get the product type based on product ID. 
  1200. * 
  1201. * @since 3.0.0 
  1202. * @param int $product_id 
  1203. * @return bool|string 
  1204. */ 
  1205. public function get_product_type( $product_id ) { 
  1206. $post_type = get_post_type( $product_id ); 
  1207. if ( 'product_variation' === $post_type ) { 
  1208. return 'variation'; 
  1209. } elseif ( 'product' === $post_type ) { 
  1210. $terms = get_the_terms( $product_id, 'product_type' ); 
  1211. return ! empty( $terms ) ? sanitize_title( current( $terms )->name ) : 'simple'; 
  1212. } else { 
  1213. return false; 
.