PageRenderTime 46ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 0ms

/wp-content/plugins/woocommerce/includes/api/v1/class-wc-api-products.php

https://gitlab.com/webkod3r/tripolis
PHP | 542 lines | 305 code | 104 blank | 133 comment | 25 complexity | d480b4def63c1cccce80e646dc6ab8d6 MD5 | raw file
  1. <?php
  2. /**
  3. * WooCommerce API Products Class
  4. *
  5. * Handles requests to the /products endpoint
  6. *
  7. * @author WooThemes
  8. * @category API
  9. * @package WooCommerce/API
  10. * @since 2.1
  11. * @version 2.1
  12. */
  13. if ( ! defined( 'ABSPATH' ) ) {
  14. exit; // Exit if accessed directly
  15. }
  16. class WC_API_Products extends WC_API_Resource {
  17. /** @var string $base the route base */
  18. protected $base = '/products';
  19. /**
  20. * Register the routes for this class
  21. *
  22. * GET /products
  23. * GET /products/count
  24. * GET /products/<id>
  25. * GET /products/<id>/reviews
  26. *
  27. * @since 2.1
  28. * @param array $routes
  29. * @return array
  30. */
  31. public function register_routes( $routes ) {
  32. # GET /products
  33. $routes[ $this->base ] = array(
  34. array( array( $this, 'get_products' ), WC_API_Server::READABLE ),
  35. );
  36. # GET /products/count
  37. $routes[ $this->base . '/count'] = array(
  38. array( array( $this, 'get_products_count' ), WC_API_Server::READABLE ),
  39. );
  40. # GET /products/<id>
  41. $routes[ $this->base . '/(?P<id>\d+)' ] = array(
  42. array( array( $this, 'get_product' ), WC_API_Server::READABLE ),
  43. );
  44. # GET /products/<id>/reviews
  45. $routes[ $this->base . '/(?P<id>\d+)/reviews' ] = array(
  46. array( array( $this, 'get_product_reviews' ), WC_API_Server::READABLE ),
  47. );
  48. return $routes;
  49. }
  50. /**
  51. * Get all products
  52. *
  53. * @since 2.1
  54. * @param string $fields
  55. * @param string $type
  56. * @param array $filter
  57. * @param int $page
  58. * @return array
  59. */
  60. public function get_products( $fields = null, $type = null, $filter = array(), $page = 1 ) {
  61. if ( ! empty( $type ) )
  62. $filter['type'] = $type;
  63. $filter['page'] = $page;
  64. $query = $this->query_products( $filter );
  65. $products = array();
  66. foreach( $query->posts as $product_id ) {
  67. if ( ! $this->is_readable( $product_id ) )
  68. continue;
  69. $products[] = current( $this->get_product( $product_id, $fields ) );
  70. }
  71. $this->server->add_pagination_headers( $query );
  72. return array( 'products' => $products );
  73. }
  74. /**
  75. * Get the product for the given ID
  76. *
  77. * @since 2.1
  78. * @param int $id the product ID
  79. * @param string $fields
  80. * @return array
  81. */
  82. public function get_product( $id, $fields = null ) {
  83. $id = $this->validate_request( $id, 'product', 'read' );
  84. if ( is_wp_error( $id ) )
  85. return $id;
  86. $product = wc_get_product( $id );
  87. // add data that applies to every product type
  88. $product_data = $this->get_product_data( $product );
  89. // add variations to variable products
  90. if ( $product->is_type( 'variable' ) && $product->has_child() ) {
  91. $product_data['variations'] = $this->get_variation_data( $product );
  92. }
  93. // add the parent product data to an individual variation
  94. if ( $product->is_type( 'variation' ) ) {
  95. $product_data['parent'] = $this->get_product_data( $product->parent );
  96. }
  97. return array( 'product' => apply_filters( 'woocommerce_api_product_response', $product_data, $product, $fields, $this->server ) );
  98. }
  99. /**
  100. * Get the total number of orders
  101. *
  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. if ( ! empty( $type ) )
  109. $filter['type'] = $type;
  110. if ( ! current_user_can( 'read_private_products' ) )
  111. 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 ) );
  112. $query = $this->query_products( $filter );
  113. return array( 'count' => (int) $query->found_posts );
  114. }
  115. /**
  116. * Edit a product
  117. *
  118. * @TODO implement in 2.2
  119. * @param int $id the product ID
  120. * @param array $data
  121. * @return array
  122. */
  123. public function edit_product( $id, $data ) {
  124. $id = $this->validate_request( $id, 'product', 'edit' );
  125. if ( is_wp_error( $id ) )
  126. return $id;
  127. return $this->get_product( $id );
  128. }
  129. /**
  130. * Delete a product
  131. *
  132. * @TODO enable along with PUT/POST in 2.2
  133. * @param int $id the product ID
  134. * @param bool $force true to permanently delete order, false to move to trash
  135. * @return array
  136. */
  137. public function delete_product( $id, $force = false ) {
  138. $id = $this->validate_request( $id, 'product', 'delete' );
  139. if ( is_wp_error( $id ) )
  140. return $id;
  141. return $this->delete( $id, 'product', ( 'true' === $force ) );
  142. }
  143. /**
  144. * Get the reviews for a product
  145. *
  146. * @since 2.1
  147. * @param int $id the product ID to get reviews for
  148. * @param string $fields fields to include in response
  149. * @return array
  150. */
  151. public function get_product_reviews( $id, $fields = null ) {
  152. $id = $this->validate_request( $id, 'product', 'read' );
  153. if ( is_wp_error( $id ) )
  154. return $id;
  155. $args = array(
  156. 'post_id' => $id,
  157. 'approve' => 'approve',
  158. );
  159. $comments = get_comments( $args );
  160. $reviews = array();
  161. foreach ( $comments as $comment ) {
  162. $reviews[] = array(
  163. 'id' => $comment->comment_ID,
  164. 'created_at' => $this->server->format_datetime( $comment->comment_date_gmt ),
  165. 'review' => $comment->comment_content,
  166. 'rating' => get_comment_meta( $comment->comment_ID, 'rating', true ),
  167. 'reviewer_name' => $comment->comment_author,
  168. 'reviewer_email' => $comment->comment_author_email,
  169. 'verified' => wc_review_is_from_verified_owner( $comment->comment_ID ),
  170. );
  171. }
  172. return array( 'product_reviews' => apply_filters( 'woocommerce_api_product_reviews_response', $reviews, $id, $fields, $comments, $this->server ) );
  173. }
  174. /**
  175. * Helper method to get product post objects
  176. *
  177. * @since 2.1
  178. * @param array $args request arguments for filtering query
  179. * @return WP_Query
  180. */
  181. private function query_products( $args ) {
  182. // set base query arguments
  183. $query_args = array(
  184. 'fields' => 'ids',
  185. 'post_type' => 'product',
  186. 'post_status' => 'publish',
  187. 'meta_query' => array(),
  188. );
  189. if ( ! empty( $args['type'] ) ) {
  190. $types = explode( ',', $args['type'] );
  191. $query_args['tax_query'] = array(
  192. array(
  193. 'taxonomy' => 'product_type',
  194. 'field' => 'slug',
  195. 'terms' => $types,
  196. ),
  197. );
  198. unset( $args['type'] );
  199. }
  200. $query_args = $this->merge_query_args( $query_args, $args );
  201. return new WP_Query( $query_args );
  202. }
  203. /**
  204. * Get standard product data that applies to every product type
  205. *
  206. * @since 2.1
  207. * @param WC_Product $product
  208. * @return array
  209. */
  210. private function get_product_data( $product ) {
  211. return array(
  212. 'title' => $product->get_title(),
  213. 'id' => (int) $product->is_type( 'variation' ) ? $product->get_variation_id() : $product->id,
  214. 'created_at' => $this->server->format_datetime( $product->get_post_data()->post_date_gmt ),
  215. 'updated_at' => $this->server->format_datetime( $product->get_post_data()->post_modified_gmt ),
  216. 'type' => $product->product_type,
  217. 'status' => $product->get_post_data()->post_status,
  218. 'downloadable' => $product->is_downloadable(),
  219. 'virtual' => $product->is_virtual(),
  220. 'permalink' => $product->get_permalink(),
  221. 'sku' => $product->get_sku(),
  222. 'price' => wc_format_decimal( $product->get_price(), 2 ),
  223. 'regular_price' => wc_format_decimal( $product->get_regular_price(), 2 ),
  224. 'sale_price' => $product->get_sale_price() ? wc_format_decimal( $product->get_sale_price(), 2 ) : null,
  225. 'price_html' => $product->get_price_html(),
  226. 'taxable' => $product->is_taxable(),
  227. 'tax_status' => $product->get_tax_status(),
  228. 'tax_class' => $product->get_tax_class(),
  229. 'managing_stock' => $product->managing_stock(),
  230. 'stock_quantity' => (int) $product->get_stock_quantity(),
  231. 'in_stock' => $product->is_in_stock(),
  232. 'backorders_allowed' => $product->backorders_allowed(),
  233. 'backordered' => $product->is_on_backorder(),
  234. 'sold_individually' => $product->is_sold_individually(),
  235. 'purchaseable' => $product->is_purchasable(),
  236. 'featured' => $product->is_featured(),
  237. 'visible' => $product->is_visible(),
  238. 'catalog_visibility' => $product->visibility,
  239. 'on_sale' => $product->is_on_sale(),
  240. 'weight' => $product->get_weight() ? wc_format_decimal( $product->get_weight(), 2 ) : null,
  241. 'dimensions' => array(
  242. 'length' => $product->length,
  243. 'width' => $product->width,
  244. 'height' => $product->height,
  245. 'unit' => get_option( 'woocommerce_dimension_unit' ),
  246. ),
  247. 'shipping_required' => $product->needs_shipping(),
  248. 'shipping_taxable' => $product->is_shipping_taxable(),
  249. 'shipping_class' => $product->get_shipping_class(),
  250. 'shipping_class_id' => ( 0 !== $product->get_shipping_class_id() ) ? $product->get_shipping_class_id() : null,
  251. 'description' => apply_filters( 'the_content', $product->get_post_data()->post_content ),
  252. 'short_description' => apply_filters( 'woocommerce_short_description', $product->get_post_data()->post_excerpt ),
  253. 'reviews_allowed' => ( 'open' === $product->get_post_data()->comment_status ),
  254. 'average_rating' => wc_format_decimal( $product->get_average_rating(), 2 ),
  255. 'rating_count' => (int) $product->get_rating_count(),
  256. 'related_ids' => array_map( 'absint', array_values( $product->get_related() ) ),
  257. 'upsell_ids' => array_map( 'absint', $product->get_upsells() ),
  258. 'cross_sell_ids' => array_map( 'absint', $product->get_cross_sells() ),
  259. 'categories' => wp_get_post_terms( $product->id, 'product_cat', array( 'fields' => 'names' ) ),
  260. 'tags' => wp_get_post_terms( $product->id, 'product_tag', array( 'fields' => 'names' ) ),
  261. 'images' => $this->get_images( $product ),
  262. 'featured_src' => wp_get_attachment_url( get_post_thumbnail_id( $product->is_type( 'variation' ) ? $product->variation_id : $product->id ) ),
  263. 'attributes' => $this->get_attributes( $product ),
  264. 'downloads' => $this->get_downloads( $product ),
  265. 'download_limit' => (int) $product->download_limit,
  266. 'download_expiry' => (int) $product->download_expiry,
  267. 'download_type' => $product->download_type,
  268. 'purchase_note' => apply_filters( 'the_content', $product->purchase_note ),
  269. 'total_sales' => metadata_exists( 'post', $product->id, 'total_sales' ) ? (int) get_post_meta( $product->id, 'total_sales', true ) : 0,
  270. 'variations' => array(),
  271. 'parent' => array(),
  272. );
  273. }
  274. /**
  275. * Get an individual variation's data
  276. *
  277. * @since 2.1
  278. * @param WC_Product $product
  279. * @return array
  280. */
  281. private function get_variation_data( $product ) {
  282. $variations = array();
  283. foreach ( $product->get_children() as $child_id ) {
  284. $variation = $product->get_child( $child_id );
  285. if ( ! $variation->exists() )
  286. continue;
  287. $variations[] = array(
  288. 'id' => $variation->get_variation_id(),
  289. 'created_at' => $this->server->format_datetime( $variation->get_post_data()->post_date_gmt ),
  290. 'updated_at' => $this->server->format_datetime( $variation->get_post_data()->post_modified_gmt ),
  291. 'downloadable' => $variation->is_downloadable(),
  292. 'virtual' => $variation->is_virtual(),
  293. 'permalink' => $variation->get_permalink(),
  294. 'sku' => $variation->get_sku(),
  295. 'price' => wc_format_decimal( $variation->get_price(), 2 ),
  296. 'regular_price' => wc_format_decimal( $variation->get_regular_price(), 2 ),
  297. 'sale_price' => $variation->get_sale_price() ? wc_format_decimal( $variation->get_sale_price(), 2 ) : null,
  298. 'taxable' => $variation->is_taxable(),
  299. 'tax_status' => $variation->get_tax_status(),
  300. 'tax_class' => $variation->get_tax_class(),
  301. 'stock_quantity' => (int) $variation->get_stock_quantity(),
  302. 'in_stock' => $variation->is_in_stock(),
  303. 'backordered' => $variation->is_on_backorder(),
  304. 'purchaseable' => $variation->is_purchasable(),
  305. 'visible' => $variation->variation_is_visible(),
  306. 'on_sale' => $variation->is_on_sale(),
  307. 'weight' => $variation->get_weight() ? wc_format_decimal( $variation->get_weight(), 2 ) : null,
  308. 'dimensions' => array(
  309. 'length' => $variation->length,
  310. 'width' => $variation->width,
  311. 'height' => $variation->height,
  312. 'unit' => get_option( 'woocommerce_dimension_unit' ),
  313. ),
  314. 'shipping_class' => $variation->get_shipping_class(),
  315. 'shipping_class_id' => ( 0 !== $variation->get_shipping_class_id() ) ? $variation->get_shipping_class_id() : null,
  316. 'image' => $this->get_images( $variation ),
  317. 'attributes' => $this->get_attributes( $variation ),
  318. 'downloads' => $this->get_downloads( $variation ),
  319. 'download_limit' => (int) $product->download_limit,
  320. 'download_expiry' => (int) $product->download_expiry,
  321. );
  322. }
  323. return $variations;
  324. }
  325. /**
  326. * Get the images for a product or product variation
  327. *
  328. * @since 2.1
  329. * @param WC_Product|WC_Product_Variation $product
  330. * @return array
  331. */
  332. private function get_images( $product ) {
  333. $images = $attachment_ids = array();
  334. if ( $product->is_type( 'variation' ) ) {
  335. if ( has_post_thumbnail( $product->get_variation_id() ) ) {
  336. // add variation image if set
  337. $attachment_ids[] = get_post_thumbnail_id( $product->get_variation_id() );
  338. } elseif ( has_post_thumbnail( $product->id ) ) {
  339. // otherwise use the parent product featured image if set
  340. $attachment_ids[] = get_post_thumbnail_id( $product->id );
  341. }
  342. } else {
  343. // add featured image
  344. if ( has_post_thumbnail( $product->id ) ) {
  345. $attachment_ids[] = get_post_thumbnail_id( $product->id );
  346. }
  347. // add gallery images
  348. $attachment_ids = array_merge( $attachment_ids, $product->get_gallery_attachment_ids() );
  349. }
  350. // build image data
  351. foreach ( $attachment_ids as $position => $attachment_id ) {
  352. $attachment_post = get_post( $attachment_id );
  353. if ( is_null( $attachment_post ) )
  354. continue;
  355. $attachment = wp_get_attachment_image_src( $attachment_id, 'full' );
  356. if ( ! is_array( $attachment ) )
  357. continue;
  358. $images[] = array(
  359. 'id' => (int) $attachment_id,
  360. 'created_at' => $this->server->format_datetime( $attachment_post->post_date_gmt ),
  361. 'updated_at' => $this->server->format_datetime( $attachment_post->post_modified_gmt ),
  362. 'src' => current( $attachment ),
  363. 'title' => get_the_title( $attachment_id ),
  364. 'alt' => get_post_meta( $attachment_id, '_wp_attachment_image_alt', true ),
  365. 'position' => $position,
  366. );
  367. }
  368. // set a placeholder image if the product has no images set
  369. if ( empty( $images ) ) {
  370. $images[] = array(
  371. 'id' => 0,
  372. 'created_at' => $this->server->format_datetime( time() ), // default to now
  373. 'updated_at' => $this->server->format_datetime( time() ),
  374. 'src' => wc_placeholder_img_src(),
  375. 'title' => __( 'Placeholder', 'woocommerce' ),
  376. 'alt' => __( 'Placeholder', 'woocommerce' ),
  377. 'position' => 0,
  378. );
  379. }
  380. return $images;
  381. }
  382. /**
  383. * Get the attributes for a product or product variation
  384. *
  385. * @since 2.1
  386. * @param WC_Product|WC_Product_Variation $product
  387. * @return array
  388. */
  389. private function get_attributes( $product ) {
  390. $attributes = array();
  391. if ( $product->is_type( 'variation' ) ) {
  392. // variation attributes
  393. foreach ( $product->get_variation_attributes() as $attribute_name => $attribute ) {
  394. // taxonomy-based attributes are prefixed with `pa_`, otherwise simply `attribute_`
  395. $attributes[] = array(
  396. 'name' => ucwords( str_replace( 'attribute_', '', str_replace( 'pa_', '', $attribute_name ) ) ),
  397. 'option' => $attribute,
  398. );
  399. }
  400. } else {
  401. foreach ( $product->get_attributes() as $attribute ) {
  402. // taxonomy-based attributes are comma-separated, others are pipe (|) separated
  403. if ( $attribute['is_taxonomy'] )
  404. $options = explode( ',', $product->get_attribute( $attribute['name'] ) );
  405. else
  406. $options = explode( '|', $product->get_attribute( $attribute['name'] ) );
  407. $attributes[] = array(
  408. 'name' => ucwords( str_replace( 'pa_', '', $attribute['name'] ) ),
  409. 'position' => $attribute['position'],
  410. 'visible' => (bool) $attribute['is_visible'],
  411. 'variation' => (bool) $attribute['is_variation'],
  412. 'options' => array_map( 'trim', $options ),
  413. );
  414. }
  415. }
  416. return $attributes;
  417. }
  418. /**
  419. * Get the downloads for a product or product variation
  420. *
  421. * @since 2.1
  422. * @param WC_Product|WC_Product_Variation $product
  423. * @return array
  424. */
  425. private function get_downloads( $product ) {
  426. $downloads = array();
  427. if ( $product->is_downloadable() ) {
  428. foreach ( $product->get_files() as $file_id => $file ) {
  429. $downloads[] = array(
  430. 'id' => $file_id, // do not cast as int as this is a hash
  431. 'name' => $file['name'],
  432. 'file' => $file['file'],
  433. );
  434. }
  435. }
  436. return $downloads;
  437. }
  438. }