PluginProbe ʕ •ᴥ•ʔ
WooCommerce / 10.9.1
WooCommerce v10.9.1
10.9.3 10.9.2 10.9.1 10.9.0 10.9.0-rc.1 10.9.0-beta.2 10.9.0-beta.1 10.8.1 10.8.0 10.8.0-rc.1 10.8.0-beta.2 10.8.0-beta.1 7.8.0-beta.1 7.8.0-beta.2 7.8.0-rc.1 7.8.0-rc.2 7.8.1 7.8.2 7.8.3 7.8.4 7.9.0 7.9.0-beta.1 7.9.0-beta.2 7.9.0-rc.2 7.9.0-rc.3 7.9.1 7.9.2 8.0.0 8.0.0-beta.1 8.0.0-beta.2 8.0.0-rc.1 8.0.0-rc.2 8.0.1 8.0.2 8.0.3 8.0.4 8.0.5 8.1.0 8.1.0-beta.1 8.1.0-rc.1 8.1.0-rc.2 8.1.1 8.1.2 8.1.3 8.1.4 8.2.0 8.2.0-beta.1 8.2.0-rc.1 8.2.0-rc.2 8.2.1 8.2.2 8.2.3 8.2.4 8.2.5 8.3.0 8.3.0-beta.1 8.3.0-rc.1 8.3.0-rc.2 8.3.1 8.3.2 8.3.3 8.3.4 8.4.0 8.4.0-beta.1 8.4.0-rc.1 8.4.1 8.4.2 8.4.3 8.5.0 8.5.0-beta.1 8.5.0-rc.1 8.5.1 8.5.2 8.5.3 8.5.4 8.5.5 8.6.0 8.6.0-beta.1 8.6.0-rc.1 8.6.1 8.6.2 8.6.3 8.6.4 8.7.0 8.7.0-beta.1 8.7.0-beta.2 8.7.0-rc.1 8.7.1 8.7.2 8.7.3 8.8.0 8.8.0-beta.1 8.8.0-rc.1 8.8.1 8.8.2 8.8.3 8.8.4 8.8.5 8.8.6 8.8.7 8.9.0 8.9.0-beta.1 8.9.0-rc.1 8.9.1 8.9.2 8.9.3 8.9.4 8.9.5 9.0.0 9.0.0-beta.1 9.0.0-beta.2 9.0.0-rc.1 9.0.1 9.0.2 9.0.3 9.0.4 9.1.0 9.1.0-beta.1 9.1.0-rc.1 9.1.1 9.1.2 9.1.3 9.1.4 9.1.5 9.1.6 9.2.0 9.2.0-beta.1 9.2.0-rc.1 9.2.1 9.2.2 9.2.3 9.2.4 9.2.5 9.3.0 9.3.0-beta.1 9.3.0-rc.1 9.3.1 9.3.2 9.3.3 9.3.4 9.3.5 9.3.6 9.4.0 9.4.0-beta.1 9.4.0-beta.2 9.4.0-rc.1 9.4.0-rc.2 9.4.0-rc.3 9.4.0-rc.4 9.4.1 9.4.2 9.4.3 9.4.4 9.4.5 9.5.0 9.5.0-beta.1 9.5.0-beta.2 9.5.0-rc.1 9.5.1 9.5.2 9.5.3 9.5.4 9.6.0 9.6.0-beta.1 9.6.0-beta.2 9.6.0-rc.1 9.6.1 9.6.2 9.6.3 9.6.4 9.7.0 9.7.0-beta.1 9.7.0-rc.1 9.7.1 9.7.2 9.7.3 9.8.0 9.8.0-beta.1 9.8.0-rc.1 9.8.1 9.8.2 9.8.3 9.8.4 9.8.5 9.8.6 9.8.7 9.9.0 9.9.0-beta.1 9.9.0-rc.1 9.9.1 9.9.2 9.9.3 9.9.4 9.9.5 9.9.6 9.9.7 3.7.3 7.1.2 3.8.0 7.2.0 3.8.0-beta.1 7.2.0-beta.1 3.8.0-rc.1 7.2.0-beta.2 3.8.0-rc.2 7.2.0-rc.1 3.8.1 7.2.0-rc.2 3.8.2 7.2.1 3.8.3 7.2.2 3.9.0 7.2.3 3.9.0-beta.1 7.2.4 3.9.0-beta.2 7.3.0 3.9.0-rc.1 7.3.0-beta.1 3.9.0-rc.2 7.3.0-beta.2 3.9.0-rc.3 7.3.0-rc.1 3.9.0-rc.4 7.3.0-rc.2 3.9.1 7.3.1 3.9.2 7.4.0 3.9.3 7.4.0-beta.1 3.9.4 7.4.0-beta.2 3.9.5 7.4.0-rc.1 4.0.0 7.4.0-rc.2 4.0.0-beta.1 7.4.1 4.0.0-rc.1 7.4.2 4.0.0-rc.2 7.5.0 4.0.1 7.5.0-beta.1 4.0.2 7.5.0-beta.2 4.0.3 7.5.0-rc.1 4.0.4 7.5.1 4.1.0 7.5.2 4.1.0-beta.1 7.6.0 4.1.0-beta.2 7.6.0-beta.1 4.1.0-rc.1 7.6.0-beta.2 4.1.0-rc.2 7.6.0-rc.1 4.1.1 7.6.0-rc.2 4.1.2 7.6.0-rc.3 4.1.3 7.6.1 4.1.4 7.6.2 4.2.0 7.7.0 4.2.0-RC.1 7.7.0-beta.1 4.2.0-RC.2 7.7.0-beta.2 4.2.0-beta.1 7.7.0-rc.1 4.2.1 7.7.1 4.2.2 7.7.2 4.2.3 7.7.3 4.2.4 7.8.0 4.2.5 4.3.0 4.3.0-beta.1 4.3.0-rc.1 4.3.0-rc.2 4.3.0-rc.3 4.3.1 4.3.2 4.3.3 4.3.4 4.3.5 4.3.6 4.4.0 4.4.0-beta.1 4.4.0-rc.1 4.4.1 4.4.2 4.4.3 4.4.4 4.5.0 4.5.0-beta.1 4.5.0-rc.1 4.5.0-rc.3 4.5.1 4.5.2 4.5.3 4.5.4 4.5.5 4.6.0 4.6.0-beta.1 4.6.0-rc.1 4.6.1 4.6.2 4.6.3 4.6.4 4.6.5 4.7.0 4.7.0-beta.1 4.7.0-beta.2 4.7.0-rc.1 4.7.1 4.7.1-beta.1 4.7.2 4.7.3 4.7.4 4.8.0 4.8.0-beta.1 4.8.0-rc.1 4.8.0-rc.2 4.8.1 4.8.2 4.8.3 4.9.0 4.9.0-beta.1 4.9.0-rc.1 4.9.0-rc.2 4.9.1 4.9.2 4.9.3 4.9.4 4.9.5 5.0.0 5.0.0-beta.1 5.0.0-beta.2 5.0.0-rc.1 5.0.0-rc.2 5.0.0-rc.3 5.0.1 5.0.2 5.0.3 5.1.0 5.1.0-beta.1 5.1.0-rc.1 trunk 5.1.1 10.0.0 5.1.2 10.0.0-rc.1 5.1.3 10.0.0-rc.2 5.2.0 10.0.1 5.2.0-beta.1 10.0.2 5.2.0-rc.1 10.0.3 5.2.0-rc.2 10.0.4 5.2.1 10.0.5 5.2.2 10.0.6 5.2.3 10.1.0 5.2.4 10.1.0-rc.1 5.2.5 10.1.0-rc.2 5.3.0 10.1.0-rc.3 5.3.0-beta.1 10.1.0-rc.4 5.3.0-rc.1 10.1.1 5.3.0-rc.2 10.1.2 5.3.1 10.1.3 5.3.2 10.1.4 5.3.3 10.2.0 5.4.0 10.2.0-beta.1 5.4.0-beta.1 10.2.0-beta.2 5.4.0-rc.1 10.2.0-rc.1 5.4.1 10.2.1 5.4.2 10.2.2 5.4.3 10.2.3 5.4.4 10.2.4 5.4.5 10.3.0 5.5.0 10.3.0-beta.1 5.5.0-beta.1 10.3.0-beta.2 5.5.0-rc.1 10.3.0-rc.1 5.5.0-rc.2 10.3.0-rc.2 5.5.1 10.3.1 5.5.2 10.3.2 5.5.3 10.3.3 5.5.4 10.3.4 5.5.5 10.3.5 5.6.0 10.3.6 5.6.0-beta.1 10.3.7 5.6.0-rc.1 10.3.8 5.6.0-rc.2 10.4.0 5.6.1 10.4.0-beta.1 5.6.2 10.4.0-beta.2 5.6.3 10.4.0-rc.1 5.7.0 10.4.1 5.7.0-beta.1 10.4.2 5.7.0-rc.1 10.4.3 5.7.1 10.4.4 5.7.2 10.5.0 5.7.3 10.5.0-beta.1 5.8.0 10.5.0-beta.2 5.8.0-beta.1 10.5.0-rc.1 5.8.0-beta.2 10.5.0-rc.2 5.8.0-rc.1 10.5.0-rc.3 5.8.1 10.5.1 5.8.2 10.5.2 5.9.0 10.5.3 5.9.0-beta.1 10.6.0 5.9.0-rc.1 10.6.0-beta.1 5.9.0-rc.2 10.6.0-beta.2 5.9.1 10.6.0-rc.1 5.9.2 10.6.1 6.0.0 10.6.2 6.0.0-beta.1 10.7.0 6.0.0-rc.1 10.7.0-beta.1 6.0.1 10.7.0-beta.2 6.0.2 10.7.0-rc.1 6.1.0 3.0.0 6.1.0-beta.1 3.0.1 6.1.0-rc.1 3.0.2 6.1.0-rc.2 3.0.3 6.1.1 3.0.4 6.1.2 3.0.5 6.1.3 3.0.6 6.2.0 3.0.7 6.2.0-beta.1 3.0.8 6.2.0-rc.1 3.0.9 6.2.0-rc.2 3.1.0 6.2.1 3.1.1 6.2.2 3.1.2 6.2.3 3.2.0 6.3.0 3.2.1 6.3.0-beta.1 3.2.2 6.3.0-rc.1 3.2.3 6.3.0-rc.2 3.2.4 6.3.1 3.2.5 6.3.2 3.2.6 6.4.0 3.3.0 6.4.0-beta.1 3.3.1 6.4.0-rc.1 3.3.2 6.4.1 3.3.2-rc.1 6.4.2 3.3.3 6.5.0 3.3.4 6.5.0-beta.1 3.3.5 6.5.0-rc.1 3.3.6 6.5.0-rc.2 3.4.0 6.5.1 3.4.0-beta.1 6.5.2 3.4.0-rc.2 6.6.0 3.4.1 6.6.0-beta.1 3.4.2 6.6.0-rc.1 3.4.3 6.6.0-rc.2 3.4.4 6.6.1 3.4.5 6.6.2 3.4.6 6.7.0 3.4.7 6.7.0-beta.1 3.4.8 6.7.0-beta.2 3.5.0 6.7.0-rc.1 3.5.0-beta.1 6.7.1 3.5.0-rc.1 6.8.0 3.5.0-rc.2 6.8.0-beta.1 3.5.1 6.8.0-beta.2 3.5.10 6.8.0-rc.1 3.5.2 6.8.1 3.5.3 6.8.2 3.5.4 6.8.3 3.5.5 6.9.0 3.5.6 6.9.0-beta.1 3.5.7 6.9.0-beta.2 3.5.8 6.9.0-rc.1 3.5.9 6.9.1 3.6.0 6.9.2 3.6.0-beta.1 6.9.3 3.6.0-rc.1 6.9.4 3.6.0-rc.2 6.9.5 3.6.0-rc.3 7.0.0 3.6.1 7.0.0-beta.1 3.6.2 7.0.0-beta.2 3.6.3 7.0.0-beta.3 3.6.4 7.0.0-rc.1 3.6.5 7.0.0-rc.2 3.6.6 7.0.1 3.6.7 7.0.2 3.7.0 7.1.0 3.7.0-beta.1 7.1.0-beta.1 3.7.0-rc.1 7.1.0-beta.2 3.7.0-rc.2 7.1.0-rc.1 3.7.1 7.1.0-rc.2 3.7.2 7.1.1
woocommerce / src / Admin / API / Reports / Stock / Controller.php
woocommerce / src / Admin / API / Reports / Stock Last commit date
Stats 4 months ago Controller.php 1 year ago
Controller.php
544 lines
1 <?php
2 /**
3 * REST API Reports stock controller
4 *
5 * Handles requests to the /reports/stock endpoint.
6 */
7
8 namespace Automattic\WooCommerce\Admin\API\Reports\Stock;
9
10 defined( 'ABSPATH' ) || exit;
11
12 use Automattic\WooCommerce\Admin\API\Reports\GenericController;
13 use Automattic\WooCommerce\Admin\API\Reports\ExportableInterface;
14 use Automattic\WooCommerce\Enums\ProductType;
15 use WP_REST_Request;
16 use WP_REST_Response;
17 use Automattic\WooCommerce\Enums\ProductStockStatus;
18
19 /**
20 * REST API Reports stock controller class.
21 *
22 * @internal
23 * @extends GenericController
24 */
25 class Controller extends GenericController implements ExportableInterface {
26
27 /**
28 * Route base.
29 *
30 * @var string
31 */
32 protected $rest_base = 'reports/stock';
33
34 /**
35 * Registered stock status options.
36 *
37 * @var array
38 */
39 protected $status_options;
40
41 /**
42 * Constructor.
43 */
44 public function __construct() {
45 $this->status_options = wc_get_product_stock_status_options();
46 }
47
48 /**
49 * Maps query arguments from the REST request.
50 *
51 * @param WP_REST_Request $request Request array.
52 * @return array
53 */
54 protected function prepare_reports_query( $request ) {
55 $args = array();
56 $args['offset'] = $request['offset'];
57 $args['order'] = $request['order'];
58 $args['orderby'] = $request['orderby'];
59 $args['paged'] = $request['page'];
60 $args['post__in'] = $request['include'];
61 $args['post__not_in'] = $request['exclude'];
62 $args['posts_per_page'] = $request['per_page'];
63 $args['post_parent__in'] = $request['parent'];
64 $args['post_parent__not_in'] = $request['parent_exclude'];
65
66 if ( 'date' === $args['orderby'] ) {
67 $args['orderby'] = 'date ID';
68 } elseif ( 'include' === $args['orderby'] ) {
69 $args['orderby'] = 'post__in';
70 } elseif ( 'id' === $args['orderby'] ) {
71 $args['orderby'] = 'ID'; // ID must be capitalized.
72 }
73
74 $args['post_type'] = array( 'product', 'product_variation' );
75
76 if ( ProductStockStatus::LOW_STOCK === $request['type'] ) {
77 $args['low_in_stock'] = true;
78 } elseif ( in_array( $request['type'], array_keys( $this->status_options ), true ) ) {
79 $args['stock_status'] = $request['type'];
80 }
81
82 $args['ignore_sticky_posts'] = true;
83
84 return $args;
85 }
86
87 /**
88 * Query products.
89 *
90 * @param array $query_args Query args.
91 * @return array
92 */
93 protected function get_products( $query_args ) {
94 $query = new \WP_Query();
95 $result = $query->query( $query_args );
96
97 $total_posts = $query->found_posts;
98 if ( $total_posts < 1 && isset( $query_args['paged'] ) && absint( $query_args['paged'] ) > 1 ) {
99 // Out-of-bounds, run the query again without LIMIT for total count.
100 unset( $query_args['paged'] );
101 $count_query = new \WP_Query();
102 $count_query->query( $query_args );
103 $total_posts = $count_query->found_posts;
104 }
105
106 return array(
107 'objects' => array_map( 'wc_get_product', $result ),
108 'total' => (int) $total_posts,
109 'pages' => (int) ceil( $total_posts / (int) $query->query_vars['posts_per_page'] ),
110 );
111 }
112
113 /**
114 * Get all reports.
115 *
116 * @param WP_REST_Request $request Request data.
117 * @return array|WP_Error
118 */
119 public function get_items( $request ) {
120 add_filter( 'posts_where', array( __CLASS__, 'add_wp_query_filter' ), 10, 2 );
121 add_filter( 'posts_join', array( __CLASS__, 'add_wp_query_join' ), 10, 2 );
122 add_filter( 'posts_groupby', array( __CLASS__, 'add_wp_query_group_by' ), 10, 2 );
123 add_filter( 'posts_clauses', array( __CLASS__, 'add_wp_query_orderby' ), 10, 2 );
124 $query_args = $this->prepare_reports_query( $request );
125 $query_results = $this->get_products( $query_args );
126 remove_filter( 'posts_where', array( __CLASS__, 'add_wp_query_filter' ), 10 );
127 remove_filter( 'posts_join', array( __CLASS__, 'add_wp_query_join' ), 10 );
128 remove_filter( 'posts_groupby', array( __CLASS__, 'add_wp_query_group_by' ), 10 );
129 remove_filter( 'posts_clauses', array( __CLASS__, 'add_wp_query_orderby' ), 10 );
130
131 $objects = array();
132 foreach ( $query_results['objects'] as $object ) {
133 $data = $this->prepare_item_for_response( $object, $request );
134 $objects[] = $this->prepare_response_for_collection( $data );
135 }
136
137 return $this->add_pagination_headers(
138 $request,
139 $objects,
140 (int) $query_results['total'],
141 (int) $query_args['paged'],
142 (int) $query_results['pages']
143 );
144 }
145
146 /**
147 * Add in conditional search filters for products.
148 *
149 * @internal
150 * @param string $where Where clause used to search posts.
151 * @param object $wp_query WP_Query object.
152 * @return string
153 */
154 public static function add_wp_query_filter( $where, $wp_query ) {
155 global $wpdb;
156
157 $stock_status = $wp_query->get( 'stock_status' );
158 if ( $stock_status ) {
159 $where .= $wpdb->prepare(
160 ' AND wc_product_meta_lookup.stock_status = %s ',
161 $stock_status
162 );
163 }
164
165 if ( $wp_query->get( 'low_in_stock' ) ) {
166 // We want products with stock < low stock amount, but greater than no stock amount.
167 $no_stock_amount = absint( max( get_option( 'woocommerce_notify_no_stock_amount' ), 0 ) );
168 $low_stock_amount = absint( max( get_option( 'woocommerce_notify_low_stock_amount' ), 1 ) );
169 $where .= "
170 AND wc_product_meta_lookup.stock_quantity IS NOT NULL
171 AND wc_product_meta_lookup.stock_status = 'instock'
172 AND (
173 (
174 low_stock_amount_meta.meta_value > ''
175 AND wc_product_meta_lookup.stock_quantity <= CAST(low_stock_amount_meta.meta_value AS SIGNED)
176 AND wc_product_meta_lookup.stock_quantity > {$no_stock_amount}
177 )
178 OR (
179 (
180 low_stock_amount_meta.meta_value IS NULL OR low_stock_amount_meta.meta_value <= ''
181 )
182 AND wc_product_meta_lookup.stock_quantity <= {$low_stock_amount}
183 AND wc_product_meta_lookup.stock_quantity > {$no_stock_amount}
184 )
185 )";
186 }
187
188 return $where;
189 }
190
191 /**
192 * Join posts meta tables when product search or low stock query is present.
193 *
194 * @internal
195 * @param string $join Join clause used to search posts.
196 * @param object $wp_query WP_Query object.
197 * @return string
198 */
199 public static function add_wp_query_join( $join, $wp_query ) {
200 global $wpdb;
201
202 $stock_status = $wp_query->get( 'stock_status' );
203 if ( $stock_status ) {
204 $join = self::append_product_sorting_table_join( $join );
205 }
206
207 if ( $wp_query->get( 'low_in_stock' ) ) {
208 $join = self::append_product_sorting_table_join( $join );
209 $join .= " LEFT JOIN {$wpdb->postmeta} AS low_stock_amount_meta ON {$wpdb->posts}.ID = low_stock_amount_meta.post_id AND low_stock_amount_meta.meta_key = '_low_stock_amount' ";
210 }
211
212 return $join;
213 }
214
215 /**
216 * Join wc_product_meta_lookup to posts if not already joined.
217 *
218 * @internal
219 * @param string $sql SQL join.
220 * @return string
221 */
222 protected static function append_product_sorting_table_join( $sql ) {
223 global $wpdb;
224
225 if ( ! strstr( $sql, 'wc_product_meta_lookup' ) ) {
226 $sql .= " LEFT JOIN {$wpdb->wc_product_meta_lookup} wc_product_meta_lookup ON $wpdb->posts.ID = wc_product_meta_lookup.product_id ";
227 }
228 return $sql;
229 }
230
231 /**
232 * Group by post ID to prevent duplicates.
233 *
234 * @internal
235 * @param string $groupby Group by clause used to organize posts.
236 * @param object $wp_query WP_Query object.
237 * @return string
238 */
239 public static function add_wp_query_group_by( $groupby, $wp_query ) {
240 global $wpdb;
241
242 if ( empty( $groupby ) ) {
243 $groupby = $wpdb->posts . '.ID';
244 }
245 return $groupby;
246 }
247
248 /**
249 * Custom orderby clauses using the lookup tables.
250 *
251 * @internal
252 * @param array $args Query args.
253 * @param object $wp_query WP_Query object.
254 * @return array
255 */
256 public static function add_wp_query_orderby( $args, $wp_query ) {
257 global $wpdb;
258
259 $orderby = $wp_query->get( 'orderby' );
260 $order = esc_sql( $wp_query->get( 'order' ) ? $wp_query->get( 'order' ) : 'desc' );
261
262 switch ( $orderby ) {
263 case 'stock_quantity':
264 $args['join'] = self::append_product_sorting_table_join( $args['join'] );
265 $args['orderby'] = " wc_product_meta_lookup.stock_quantity {$order}, wc_product_meta_lookup.product_id {$order} ";
266 break;
267 case 'stock_status':
268 $args['join'] = self::append_product_sorting_table_join( $args['join'] );
269 $args['orderby'] = " wc_product_meta_lookup.stock_status {$order}, wc_product_meta_lookup.stock_quantity {$order} ";
270 break;
271 case 'sku':
272 $args['join'] = self::append_product_sorting_table_join( $args['join'] );
273 $args['orderby'] = " wc_product_meta_lookup.sku {$order}, wc_product_meta_lookup.product_id {$order} ";
274 break;
275 }
276
277 return $args;
278 }
279
280 /**
281 * Prepare a report data item for serialization.
282 *
283 * @param WC_Product $product Report data item as returned from Data Store.
284 * @param WP_REST_Request $request Request object.
285 * @return WP_REST_Response
286 */
287 public function prepare_item_for_response( $product, $request ) {
288 $data = array(
289 'id' => $product->get_id(),
290 'parent_id' => $product->get_parent_id(),
291 'name' => wp_strip_all_tags( $product->get_name() ),
292 'sku' => $product->get_sku(),
293 'stock_status' => $product->get_stock_status(),
294 'stock_quantity' => (float) $product->get_stock_quantity(),
295 'manage_stock' => $product->get_manage_stock(),
296 'low_stock_amount' => $product->get_low_stock_amount(),
297 );
298
299 if ( '' === $data['low_stock_amount'] ) {
300 $data['low_stock_amount'] = absint( max( get_option( 'woocommerce_notify_low_stock_amount' ), 1 ) );
301 }
302
303 $response = parent::prepare_item_for_response( $data, $request );
304 $response->add_links( $this->prepare_links( $product ) );
305
306 /**
307 * Filter a report returned from the API.
308 *
309 * Allows modification of the report data right before it is returned.
310 *
311 * @param WP_REST_Response $response The response object.
312 * @param WC_Product $product The original product object.
313 * @param WP_REST_Request $request Request used to generate the response.
314 */
315 return apply_filters( 'woocommerce_rest_prepare_report_stock', $response, $product, $request );
316 }
317
318 /**
319 * Prepare links for the request.
320 *
321 * @param WC_Product $product Object data.
322 * @return array
323 */
324 protected function prepare_links( $product ) {
325 if ( $product->is_type( ProductType::VARIATION ) ) {
326 $links = array(
327 'product' => array(
328 'href' => rest_url( sprintf( '/%s/products/%d/variations/%d', $this->namespace, $product->get_parent_id(), $product->get_id() ) ),
329 ),
330 'parent' => array(
331 'href' => rest_url( sprintf( '/%s/products/%d', $this->namespace, $product->get_parent_id() ) ),
332 ),
333 );
334 } elseif ( $product->get_parent_id() ) {
335 $links = array(
336 'product' => array(
337 'href' => rest_url( sprintf( '/%s/products/%d', $this->namespace, $product->get_id() ) ),
338 ),
339 'parent' => array(
340 'href' => rest_url( sprintf( '/%s/products/%d', $this->namespace, $product->get_parent_id() ) ),
341 ),
342 );
343 } else {
344 $links = array(
345 'product' => array(
346 'href' => rest_url( sprintf( '/%s/products/%d', $this->namespace, $product->get_id() ) ),
347 ),
348 );
349 }
350
351 return $links;
352 }
353
354 /**
355 * Get the Report's schema, conforming to JSON Schema.
356 *
357 * @return array
358 */
359 public function get_item_schema() {
360 $schema = array(
361 '$schema' => 'http://json-schema.org/draft-04/schema#',
362 'title' => 'report_stock',
363 'type' => 'object',
364 'properties' => array(
365 'id' => array(
366 'description' => __( 'Unique identifier for the resource.', 'woocommerce' ),
367 'type' => 'integer',
368 'context' => array( 'view', 'edit' ),
369 'readonly' => true,
370 ),
371 'parent_id' => array(
372 'description' => __( 'Product parent ID.', 'woocommerce' ),
373 'type' => 'integer',
374 'context' => array( 'view', 'edit' ),
375 'readonly' => true,
376 ),
377 'name' => array(
378 'description' => __( 'Product name.', 'woocommerce' ),
379 'type' => 'string',
380 'context' => array( 'view', 'edit' ),
381 'readonly' => true,
382 ),
383 'sku' => array(
384 'description' => __( 'Unique identifier.', 'woocommerce' ),
385 'type' => 'string',
386 'context' => array( 'view', 'edit' ),
387 'readonly' => true,
388 ),
389 'stock_status' => array(
390 'description' => __( 'Stock status.', 'woocommerce' ),
391 'type' => 'string',
392 'enum' => array_keys( wc_get_product_stock_status_options() ),
393 'context' => array( 'view', 'edit' ),
394 'readonly' => true,
395 ),
396 'stock_quantity' => array(
397 'description' => __( 'Stock quantity.', 'woocommerce' ),
398 'type' => 'integer',
399 'context' => array( 'view', 'edit' ),
400 'readonly' => true,
401 ),
402 'manage_stock' => array(
403 'description' => __( 'Manage stock.', 'woocommerce' ),
404 'type' => 'boolean',
405 'context' => array( 'view', 'edit' ),
406 'readonly' => true,
407 ),
408 ),
409 );
410
411 return $this->add_additional_fields_schema( $schema );
412 }
413
414 /**
415 * Get the query params for collections.
416 *
417 * @return array
418 */
419 public function get_collection_params() {
420 $params = parent::get_collection_params();
421 unset( $params['after'], $params['before'], $params['force_cache_refresh'] );
422 $params['exclude'] = array(
423 'description' => __( 'Ensure result set excludes specific IDs.', 'woocommerce' ),
424 'type' => 'array',
425 'items' => array(
426 'type' => 'integer',
427 ),
428 'default' => array(),
429 'sanitize_callback' => 'wp_parse_id_list',
430 );
431 $params['include'] = array(
432 'description' => __( 'Limit result set to specific ids.', 'woocommerce' ),
433 'type' => 'array',
434 'items' => array(
435 'type' => 'integer',
436 ),
437 'default' => array(),
438 'sanitize_callback' => 'wp_parse_id_list',
439 );
440 $params['offset'] = array(
441 'description' => __( 'Offset the result set by a specific number of items.', 'woocommerce' ),
442 'type' => 'integer',
443 'sanitize_callback' => 'absint',
444 'validate_callback' => 'rest_validate_request_arg',
445 );
446 $params['order']['default'] = 'asc';
447 $params['orderby']['default'] = 'stock_status';
448 $params['orderby']['enum'] = $this->apply_custom_orderby_filters(
449 array(
450 'stock_status',
451 'stock_quantity',
452 'date',
453 'id',
454 'include',
455 'title',
456 'sku',
457 )
458 );
459 $params['parent'] = array(
460 'description' => __( 'Limit result set to those of particular parent IDs.', 'woocommerce' ),
461 'type' => 'array',
462 'items' => array(
463 'type' => 'integer',
464 ),
465 'sanitize_callback' => 'wp_parse_id_list',
466 'default' => array(),
467 );
468 $params['parent_exclude'] = array(
469 'description' => __( 'Limit result set to all items except those of a particular parent ID.', 'woocommerce' ),
470 'type' => 'array',
471 'items' => array(
472 'type' => 'integer',
473 ),
474 'sanitize_callback' => 'wp_parse_id_list',
475 'default' => array(),
476 );
477 $params['type'] = array(
478 'description' => __( 'Limit result set to items assigned a stock report type.', 'woocommerce' ),
479 'type' => 'string',
480 'default' => 'all',
481 'enum' => array_merge( array( 'all', 'lowstock' ), array_keys( wc_get_product_stock_status_options() ) ),
482 );
483
484 return $params;
485 }
486
487 /**
488 * Get the column names for export.
489 *
490 * @return array Key value pair of Column ID => Label.
491 */
492 public function get_export_columns() {
493 $export_columns = array(
494 'title' => __( 'Product / Variation', 'woocommerce' ),
495 'sku' => __( 'SKU', 'woocommerce' ),
496 'stock_status' => __( 'Status', 'woocommerce' ),
497 'stock_quantity' => __( 'Stock', 'woocommerce' ),
498 );
499
500 /**
501 * Filter to add or remove column names from the stock report for
502 * export.
503 *
504 * @since 1.6.0
505 */
506 return apply_filters(
507 'woocommerce_report_stock_export_columns',
508 $export_columns
509 );
510 }
511
512 /**
513 * Get the column values for export.
514 *
515 * @param array $item Single report item/row.
516 * @return array Key value pair of Column ID => Row Value.
517 */
518 public function prepare_item_for_export( $item ) {
519 $status = $item['stock_status'];
520 if ( array_key_exists( $item['stock_status'], $this->status_options ) ) {
521 $status = $this->status_options[ $item['stock_status'] ];
522 }
523
524 $export_item = array(
525 'title' => $item['name'],
526 'sku' => $item['sku'],
527 'stock_status' => $status,
528 'stock_quantity' => $item['stock_quantity'],
529 );
530
531 /**
532 * Filter to prepare extra columns in the export item for the stock
533 * report.
534 *
535 * @since 1.6.0
536 */
537 return apply_filters(
538 'woocommerce_report_stock_prepare_export_item',
539 $export_item,
540 $item
541 );
542 }
543 }
544