1 <?php
  2 /**
  3  * WooCommerce API Reports Class
  4  *
  5  * Handles requests to the /reports endpoint
  6  *
  7  * @author      WooThemes
  8  * @category    API
  9  * @package     WooCommerce/API
 10  * @since       2.1
 11  */
 12 
 13 if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
 14 
 15 
 16 class WC_API_Reports extends WC_API_Resource {
 17 
 18     /** @var string $base the route base */
 19     protected $base = '/reports';
 20 
 21     /** @var WC_Admin_Report instance */
 22     private $report;
 23 
 24     /**
 25      * Register the routes for this class
 26      *
 27      * GET /reports
 28      * GET /reports/sales
 29      *
 30      * @since 2.1
 31      * @param array $routes
 32      * @return array
 33      */
 34     public function register_routes( $routes ) {
 35 
 36         # GET /reports
 37         $routes[ $this->base ] = array(
 38             array( array( $this, 'get_reports' ),     WC_API_Server::READABLE ),
 39         );
 40 
 41         # GET /reports/sales
 42         $routes[ $this->base . '/sales'] = array(
 43             array( array( $this, 'get_sales_report' ), WC_API_Server::READABLE ),
 44         );
 45 
 46         # GET /reports/sales/top_sellers
 47         $routes[ $this->base . '/sales/top_sellers' ] = array(
 48             array( array( $this, 'get_top_sellers_report' ), WC_API_Server::READABLE ),
 49         );
 50 
 51         return $routes;
 52     }
 53 
 54     /**
 55      * Get a simple listing of available reports
 56      *
 57      * @since 2.1
 58      * @return array
 59      */
 60     public function get_reports() {
 61 
 62         return array( 'reports' => array( 'sales', 'sales/top_sellers' ) );
 63     }
 64 
 65     /**
 66      * Get the sales report
 67      *
 68      * @since 2.1
 69      * @param string $fields fields to include in response
 70      * @param array $filter date filtering
 71      * @return array
 72      */
 73     public function get_sales_report( $fields = null, $filter = array() ) {
 74 
 75         // check user permissions
 76         $check = $this->validate_request();
 77 
 78         if ( is_wp_error( $check ) )
 79             return $check;
 80 
 81         // set date filtering
 82         $this->setup_report( $filter );
 83 
 84         // total sales, taxes, shipping, and order count
 85         $totals = $this->report->get_order_report_data( array(
 86             'data' => array(
 87                 '_order_total' => array(
 88                     'type'     => 'meta',
 89                     'function' => 'SUM',
 90                     'name'     => 'sales'
 91                 ),
 92                 '_order_tax' => array(
 93                     'type'            => 'meta',
 94                     'function'        => 'SUM',
 95                     'name'            => 'tax'
 96                 ),
 97                 '_order_shipping_tax' => array(
 98                     'type'            => 'meta',
 99                     'function'        => 'SUM',
100                     'name'            => 'shipping_tax'
101                 ),
102                 '_order_shipping' => array(
103                     'type'     => 'meta',
104                     'function' => 'SUM',
105                     'name'     => 'shipping'
106                 ),
107                 'ID' => array(
108                     'type'     => 'post_data',
109                     'function' => 'COUNT',
110                     'name'     => 'order_count'
111                 )
112             ),
113             'filter_range' => true,
114         ) );
115 
116         // total items ordered
117         $total_items = absint( $this->report->get_order_report_data( array(
118             'data' => array(
119                 '_qty' => array(
120                     'type'            => 'order_item_meta',
121                     'order_item_type' => 'line_item',
122                     'function'        => 'SUM',
123                     'name'            => 'order_item_qty'
124                 )
125             ),
126             'query_type' => 'get_var',
127             'filter_range' => true,
128         ) ) );
129 
130         // total discount used
131         $total_discount = $this->report->get_order_report_data( array(
132             'data' => array(
133                 'discount_amount' => array(
134                     'type'            => 'order_item_meta',
135                     'order_item_type' => 'coupon',
136                     'function'        => 'SUM',
137                     'name'            => 'discount_amount'
138                 )
139             ),
140             'where' => array(
141                 array(
142                     'key'      => 'order_item_type',
143                     'value'    => 'coupon',
144                     'operator' => '='
145                 )
146             ),
147             'query_type' => 'get_var',
148             'filter_range' => true,
149         ) );
150 
151         // new customers
152         $users_query = new WP_User_Query(
153             array(
154                 'fields'  => array( 'user_registered' ),
155                 'role'    => 'customer',
156             )
157         );
158 
159         $customers = $users_query->get_results();
160 
161         foreach ( $customers as $key => $customer ) {
162             if ( strtotime( $customer->user_registered ) < $this->report->start_date || strtotime( $customer->user_registered ) > $this->report->end_date )
163                 unset( $customers[ $key ] );
164         }
165 
166         $total_customers = count( $customers );
167 
168         // get order totals grouped by period
169         $orders = $this->report->get_order_report_data( array(
170             'data' => array(
171                 '_order_total' => array(
172                     'type'     => 'meta',
173                     'function' => 'SUM',
174                     'name'     => 'total_sales'
175                 ),
176                 '_order_shipping' => array(
177                     'type'     => 'meta',
178                     'function' => 'SUM',
179                     'name'     => 'total_shipping'
180                 ),
181                 '_order_tax' => array(
182                     'type'     => 'meta',
183                     'function' => 'SUM',
184                     'name'     => 'total_tax'
185                 ),
186                 '_order_shipping_tax' => array(
187                     'type'     => 'meta',
188                     'function' => 'SUM',
189                     'name'     => 'total_shipping_tax'
190                 ),
191                 'ID' => array(
192                     'type'     => 'post_data',
193                     'function' => 'COUNT',
194                     'name'     => 'total_orders',
195                     'distinct' => true,
196                 ),
197                 'post_date' => array(
198                     'type'     => 'post_data',
199                     'function' => '',
200                     'name'     => 'post_date'
201                 ),
202             ),
203             'group_by'     => $this->report->group_by_query,
204             'order_by'     => 'post_date ASC',
205             'query_type'   => 'get_results',
206             'filter_range' => true,
207         ) );
208 
209         // get order item totals grouped by period
210         $order_items = $this->report->get_order_report_data( array(
211             'data' => array(
212                 '_qty' => array(
213                     'type'            => 'order_item_meta',
214                     'order_item_type' => 'line_item',
215                     'function'        => 'SUM',
216                     'name'            => 'order_item_count'
217                 ),
218                 'post_date' => array(
219                     'type'     => 'post_data',
220                     'function' => '',
221                     'name'     => 'post_date'
222                 ),
223             ),
224             'where' => array(
225                 array(
226                     'key'      => 'order_item_type',
227                     'value'    => 'line_item',
228                     'operator' => '='
229                 )
230             ),
231             'group_by'     => $this->report->group_by_query,
232             'order_by'     => 'post_date ASC',
233             'query_type'   => 'get_results',
234             'filter_range' => true,
235         ) );
236 
237         // get discount totals grouped by period
238         $discounts = $this->report->get_order_report_data( array(
239             'data' => array(
240                 'discount_amount' => array(
241                     'type'            => 'order_item_meta',
242                     'order_item_type' => 'coupon',
243                     'function'        => 'SUM',
244                     'name'            => 'discount_amount'
245                 ),
246                 'post_date' => array(
247                     'type'     => 'post_data',
248                     'function' => '',
249                     'name'     => 'post_date'
250                 ),
251             ),
252             'where' => array(
253                 array(
254                     'key'      => 'order_item_type',
255                     'value'    => 'coupon',
256                     'operator' => '='
257                 )
258             ),
259             'group_by'     => $this->report->group_by_query . ', order_item_name',
260             'order_by'     => 'post_date ASC',
261             'query_type'   => 'get_results',
262             'filter_range' => true,
263         ) );
264 
265         $period_totals = array();
266 
267         // setup period totals by ensuring each period in the interval has data
268         for ( $i = 0; $i <= $this->report->chart_interval; $i ++ ) {
269 
270             switch ( $this->report->chart_groupby ) {
271                 case 'day' :
272                     $time = date( 'Y-m-d', strtotime( "+{$i} DAY", $this->report->start_date ) );
273                     break;
274                 case 'month' :
275                     $time = date( 'Y-m', strtotime( "+{$i} MONTH", $this->report->start_date ) );
276                     break;
277             }
278 
279             // set the customer signups for each period
280             $customer_count = 0;
281             foreach ( $customers as $customer ) {
282 
283                 if ( date( ( 'day' == $this->report->chart_groupby ) ? 'Y-m-d' : 'Y-m', strtotime( $customer->user_registered ) ) == $time ) {
284                     $customer_count++;
285                 }
286             }
287 
288             $period_totals[ $time ] = array(
289                 'sales'     => wc_format_decimal( 0.00, 2 ),
290                 'orders'    => 0,
291                 'items'     => 0,
292                 'tax'       => wc_format_decimal( 0.00, 2 ),
293                 'shipping'  => wc_format_decimal( 0.00, 2 ),
294                 'discount'  => wc_format_decimal( 0.00, 2 ),
295                 'customers' => $customer_count,
296             );
297         }
298 
299         // add total sales, total order count, total tax and total shipping for each period
300         foreach ( $orders as $order ) {
301 
302             $time = ( 'day' === $this->report->chart_groupby ) ? date( 'Y-m-d', strtotime( $order->post_date ) ) : date( 'Y-m', strtotime( $order->post_date ) );
303 
304             if ( ! isset( $period_totals[ $time ] ) )
305                 continue;
306 
307             $period_totals[ $time ]['sales']    = wc_format_decimal( $order->total_sales, 2 );
308             $period_totals[ $time ]['orders']   = (int) $order->total_orders;
309             $period_totals[ $time ]['tax']      = wc_format_decimal( $order->total_tax + $order->total_shipping_tax, 2 );
310             $period_totals[ $time ]['shipping'] = wc_format_decimal( $order->total_shipping, 2 );
311         }
312 
313         // add total order items for each period
314         foreach ( $order_items as $order_item ) {
315 
316             $time = ( 'day' === $this->report->chart_groupby ) ? date( 'Y-m-d', strtotime( $order_item->post_date ) ) : date( 'Y-m', strtotime( $order_item->post_date ) );
317 
318             if ( ! isset( $period_totals[ $time ] ) )
319                 continue;
320 
321             $period_totals[ $time ]['items'] = (int) $order_item->order_item_count;
322         }
323 
324         // add total discount for each period
325         foreach ( $discounts as $discount ) {
326 
327             $time = ( 'day' === $this->report->chart_groupby ) ? date( 'Y-m-d', strtotime( $discount->post_date ) ) : date( 'Y-m', strtotime( $discount->post_date ) );
328 
329             if ( ! isset( $period_totals[ $time ] ) )
330                 continue;
331 
332             $period_totals[ $time ]['discount'] = wc_format_decimal( $discount->discount_amount, 2 );
333         }
334 
335         $sales_data = array(
336             'total_sales'       => wc_format_decimal( $totals->sales, 2 ),
337             'average_sales'     => wc_format_decimal( $totals->sales / ( $this->report->chart_interval + 1 ), 2 ),
338             'total_orders'      => (int) $totals->order_count,
339             'total_items'       => $total_items,
340             'total_tax'         => wc_format_decimal( $totals->tax + $totals->shipping_tax, 2 ),
341             'total_shipping'    => wc_format_decimal( $totals->shipping, 2 ),
342             'total_discount'    => is_null( $total_discount ) ? wc_format_decimal( 0.00, 2 ) : wc_format_decimal( $total_discount, 2 ),
343             'totals_grouped_by' => $this->report->chart_groupby,
344             'totals'            => $period_totals,
345             'total_customers'   => $total_customers,
346         );
347 
348         return array( 'sales' => apply_filters( 'woocommerce_api_report_response', $sales_data, $this->report, $fields, $this->server ) );
349     }
350 
351     /**
352      * Get the top sellers report
353      *
354      * @since 2.1
355      * @param string $fields fields to include in response
356      * @param array $filter date filtering
357      * @return array
358      */
359     public function get_top_sellers_report( $fields = null, $filter = array() ) {
360 
361         // check user permissions
362         $check = $this->validate_request();
363 
364         if ( is_wp_error( $check ) ) {
365             return $check;
366         }
367 
368         // set date filtering
369         $this->setup_report( $filter );
370 
371         $top_sellers = $this->report->get_order_report_data( array(
372             'data' => array(
373                 '_product_id' => array(
374                     'type'            => 'order_item_meta',
375                     'order_item_type' => 'line_item',
376                     'function'        => '',
377                     'name'            => 'product_id'
378                 ),
379                 '_qty' => array(
380                     'type'            => 'order_item_meta',
381                     'order_item_type' => 'line_item',
382                     'function'        => 'SUM',
383                     'name'            => 'order_item_qty'
384                 )
385             ),
386             'order_by'     => 'order_item_qty DESC',
387             'group_by'     => 'product_id',
388             'limit'        => isset( $filter['limit'] ) ? absint( $filter['limit'] ) : 12,
389             'query_type'   => 'get_results',
390             'filter_range' => true,
391         ) );
392 
393         $top_sellers_data = array();
394 
395         foreach ( $top_sellers as $top_seller ) {
396 
397             $product = get_product( $top_seller->product_id );
398 
399             $top_sellers_data[] = array(
400                 'title'      => $product->get_title(),
401                 'product_id' => $top_seller->product_id,
402                 'quantity'   => $top_seller->order_item_qty,
403             );
404         }
405 
406         return array( 'top_sellers' => apply_filters( 'woocommerce_api_report_response', $top_sellers_data, $this->report, $fields, $this->server ) );
407     }
408 
409     /**
410      * Setup the report object and parse any date filtering
411      *
412      * @since 2.1
413      * @param array $filter date filtering
414      */
415     private function setup_report( $filter ) {
416 
417         include_once( WC()->plugin_path() . '/includes/admin/reports/class-wc-admin-report.php' );
418 
419         $this->report = new WC_Admin_Report();
420 
421         if ( empty( $filter['period'] ) ) {
422 
423             // custom date range
424             $filter['period'] = 'custom';
425 
426             if ( ! empty( $filter['date_min'] ) || ! empty( $filter['date_max'] ) ) {
427 
428                 // overwrite _GET to make use of WC_Admin_Report::calculate_current_range() for custom date ranges
429                 $_GET['start_date'] = $this->server->parse_datetime( $filter['date_min'] );
430                 $_GET['end_date'] = isset( $filter['date_max'] ) ? $this->server->parse_datetime( $filter['date_max'] ) : null;
431 
432             } else {
433 
434                 // default custom range to today
435                 $_GET['start_date'] = $_GET['end_date'] = date( 'Y-m-d', current_time( 'timestamp' ) );
436             }
437 
438         } else {
439 
440             // ensure period is valid
441             if ( ! in_array( $filter['period'], array( 'week', 'month', 'last_month', 'year' ) ) ) {
442                 $filter['period'] = 'week';
443             }
444 
445             // TODO: change WC_Admin_Report class to use "week" instead, as it's more consistent with other periods
446             // allow "week" for period instead of "7day"
447             if ( 'week' === $filter['period'] ) {
448                 $filter['period'] = '7day';
449             }
450         }
451 
452         $this->report->calculate_current_range( $filter['period'] );
453     }
454 
455     /**
456      * Verify that the current user has permission to view reports
457      *
458      * @since 2.1
459      * @see WC_API_Resource::validate_request()
460      * @param null $id unused
461      * @param null $type unused
462      * @param null $context unused
463      * @return bool true if the request is valid and should be processed, false otherwise
464      */
465     protected function validate_request( $id = null, $type = null, $context = null ) {
466 
467         if ( ! current_user_can( 'view_woocommerce_reports' ) ) {
468 
469             return new WP_Error( 'woocommerce_api_user_cannot_read_report', __( 'You do not have permission to read this report', 'woocommerce' ), array( 'status' => 401 ) );
470 
471         } else {
472 
473             return true;
474         }
475     }
476 }
477 
WooCommerce API documentation generated by ApiGen 2.8.0