class-wc-api-authentication.php
5 years ago
class-wc-api-coupons.php
5 years ago
class-wc-api-customers.php
5 years ago
class-wc-api-exception.php
5 years ago
class-wc-api-json-handler.php
5 years ago
class-wc-api-orders.php
4 years ago
class-wc-api-products.php
3 years ago
class-wc-api-reports.php
5 years ago
class-wc-api-resource.php
5 years ago
class-wc-api-server.php
5 years ago
class-wc-api-webhooks.php
3 years ago
interface-wc-api-handler.php
5 years ago
class-wc-api-reports.php
330 lines
| 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\RestApi |
| 10 | * @since 2.1 |
| 11 | */ |
| 12 | |
| 13 | if ( ! defined( 'ABSPATH' ) ) { |
| 14 | exit; // Exit if accessed directly |
| 15 | } |
| 16 | |
| 17 | class WC_API_Reports extends WC_API_Resource { |
| 18 | |
| 19 | /** @var string $base the route base */ |
| 20 | protected $base = '/reports'; |
| 21 | |
| 22 | /** @var WC_Admin_Report instance */ |
| 23 | private $report; |
| 24 | |
| 25 | /** |
| 26 | * Register the routes for this class |
| 27 | * |
| 28 | * GET /reports |
| 29 | * GET /reports/sales |
| 30 | * |
| 31 | * @since 2.1 |
| 32 | * @param array $routes |
| 33 | * @return array |
| 34 | */ |
| 35 | public function register_routes( $routes ) { |
| 36 | |
| 37 | # GET /reports |
| 38 | $routes[ $this->base ] = array( |
| 39 | array( array( $this, 'get_reports' ), WC_API_Server::READABLE ), |
| 40 | ); |
| 41 | |
| 42 | # GET /reports/sales |
| 43 | $routes[ $this->base . '/sales' ] = array( |
| 44 | array( array( $this, 'get_sales_report' ), WC_API_Server::READABLE ), |
| 45 | ); |
| 46 | |
| 47 | # GET /reports/sales/top_sellers |
| 48 | $routes[ $this->base . '/sales/top_sellers' ] = array( |
| 49 | array( array( $this, 'get_top_sellers_report' ), WC_API_Server::READABLE ), |
| 50 | ); |
| 51 | |
| 52 | return $routes; |
| 53 | } |
| 54 | |
| 55 | /** |
| 56 | * Get a simple listing of available reports |
| 57 | * |
| 58 | * @since 2.1 |
| 59 | * @return array |
| 60 | */ |
| 61 | public function get_reports() { |
| 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|WP_Error |
| 72 | */ |
| 73 | public function get_sales_report( $fields = null, $filter = array() ) { |
| 74 | |
| 75 | // check user permissions |
| 76 | $check = $this->validate_request(); |
| 77 | |
| 78 | // check for WP_Error |
| 79 | if ( is_wp_error( $check ) ) { |
| 80 | return $check; |
| 81 | } |
| 82 | |
| 83 | // set date filtering |
| 84 | $this->setup_report( $filter ); |
| 85 | |
| 86 | // new customers |
| 87 | $users_query = new WP_User_Query( |
| 88 | array( |
| 89 | 'fields' => array( 'user_registered' ), |
| 90 | 'role' => 'customer', |
| 91 | ) |
| 92 | ); |
| 93 | |
| 94 | $customers = $users_query->get_results(); |
| 95 | |
| 96 | foreach ( $customers as $key => $customer ) { |
| 97 | if ( strtotime( $customer->user_registered ) < $this->report->start_date || strtotime( $customer->user_registered ) > $this->report->end_date ) { |
| 98 | unset( $customers[ $key ] ); |
| 99 | } |
| 100 | } |
| 101 | |
| 102 | $total_customers = count( $customers ); |
| 103 | $report_data = $this->report->get_report_data(); |
| 104 | $period_totals = array(); |
| 105 | |
| 106 | // setup period totals by ensuring each period in the interval has data |
| 107 | for ( $i = 0; $i <= $this->report->chart_interval; $i ++ ) { |
| 108 | |
| 109 | switch ( $this->report->chart_groupby ) { |
| 110 | case 'day' : |
| 111 | $time = date( 'Y-m-d', strtotime( "+{$i} DAY", $this->report->start_date ) ); |
| 112 | break; |
| 113 | default : |
| 114 | $time = date( 'Y-m', strtotime( "+{$i} MONTH", $this->report->start_date ) ); |
| 115 | break; |
| 116 | } |
| 117 | |
| 118 | // set the customer signups for each period |
| 119 | $customer_count = 0; |
| 120 | foreach ( $customers as $customer ) { |
| 121 | if ( date( ( 'day' == $this->report->chart_groupby ) ? 'Y-m-d' : 'Y-m', strtotime( $customer->user_registered ) ) == $time ) { |
| 122 | $customer_count++; |
| 123 | } |
| 124 | } |
| 125 | |
| 126 | $period_totals[ $time ] = array( |
| 127 | 'sales' => wc_format_decimal( 0.00, 2 ), |
| 128 | 'orders' => 0, |
| 129 | 'items' => 0, |
| 130 | 'tax' => wc_format_decimal( 0.00, 2 ), |
| 131 | 'shipping' => wc_format_decimal( 0.00, 2 ), |
| 132 | 'discount' => wc_format_decimal( 0.00, 2 ), |
| 133 | 'customers' => $customer_count, |
| 134 | ); |
| 135 | } |
| 136 | |
| 137 | // add total sales, total order count, total tax and total shipping for each period |
| 138 | foreach ( $report_data->orders as $order ) { |
| 139 | $time = ( 'day' === $this->report->chart_groupby ) ? date( 'Y-m-d', strtotime( $order->post_date ) ) : date( 'Y-m', strtotime( $order->post_date ) ); |
| 140 | |
| 141 | if ( ! isset( $period_totals[ $time ] ) ) { |
| 142 | continue; |
| 143 | } |
| 144 | |
| 145 | $period_totals[ $time ]['sales'] = wc_format_decimal( $order->total_sales, 2 ); |
| 146 | $period_totals[ $time ]['tax'] = wc_format_decimal( $order->total_tax + $order->total_shipping_tax, 2 ); |
| 147 | $period_totals[ $time ]['shipping'] = wc_format_decimal( $order->total_shipping, 2 ); |
| 148 | } |
| 149 | |
| 150 | foreach ( $report_data->order_counts as $order ) { |
| 151 | $time = ( 'day' === $this->report->chart_groupby ) ? date( 'Y-m-d', strtotime( $order->post_date ) ) : date( 'Y-m', strtotime( $order->post_date ) ); |
| 152 | |
| 153 | if ( ! isset( $period_totals[ $time ] ) ) { |
| 154 | continue; |
| 155 | } |
| 156 | |
| 157 | $period_totals[ $time ]['orders'] = (int) $order->count; |
| 158 | } |
| 159 | |
| 160 | // add total order items for each period |
| 161 | foreach ( $report_data->order_items as $order_item ) { |
| 162 | $time = ( 'day' === $this->report->chart_groupby ) ? date( 'Y-m-d', strtotime( $order_item->post_date ) ) : date( 'Y-m', strtotime( $order_item->post_date ) ); |
| 163 | |
| 164 | if ( ! isset( $period_totals[ $time ] ) ) { |
| 165 | continue; |
| 166 | } |
| 167 | |
| 168 | $period_totals[ $time ]['items'] = (int) $order_item->order_item_count; |
| 169 | } |
| 170 | |
| 171 | // add total discount for each period |
| 172 | foreach ( $report_data->coupons as $discount ) { |
| 173 | $time = ( 'day' === $this->report->chart_groupby ) ? date( 'Y-m-d', strtotime( $discount->post_date ) ) : date( 'Y-m', strtotime( $discount->post_date ) ); |
| 174 | |
| 175 | if ( ! isset( $period_totals[ $time ] ) ) { |
| 176 | continue; |
| 177 | } |
| 178 | |
| 179 | $period_totals[ $time ]['discount'] = wc_format_decimal( $discount->discount_amount, 2 ); |
| 180 | } |
| 181 | |
| 182 | $sales_data = array( |
| 183 | 'total_sales' => $report_data->total_sales, |
| 184 | 'net_sales' => $report_data->net_sales, |
| 185 | 'average_sales' => $report_data->average_sales, |
| 186 | 'total_orders' => $report_data->total_orders, |
| 187 | 'total_items' => $report_data->total_items, |
| 188 | 'total_tax' => wc_format_decimal( $report_data->total_tax + $report_data->total_shipping_tax, 2 ), |
| 189 | 'total_shipping' => $report_data->total_shipping, |
| 190 | 'total_refunds' => $report_data->total_refunds, |
| 191 | 'total_discount' => $report_data->total_coupons, |
| 192 | 'totals_grouped_by' => $this->report->chart_groupby, |
| 193 | 'totals' => $period_totals, |
| 194 | 'total_customers' => $total_customers, |
| 195 | ); |
| 196 | |
| 197 | return array( 'sales' => apply_filters( 'woocommerce_api_report_response', $sales_data, $this->report, $fields, $this->server ) ); |
| 198 | } |
| 199 | |
| 200 | /** |
| 201 | * Get the top sellers report |
| 202 | * |
| 203 | * @since 2.1 |
| 204 | * @param string $fields fields to include in response |
| 205 | * @param array $filter date filtering |
| 206 | * @return array|WP_Error |
| 207 | */ |
| 208 | public function get_top_sellers_report( $fields = null, $filter = array() ) { |
| 209 | |
| 210 | // check user permissions |
| 211 | $check = $this->validate_request(); |
| 212 | |
| 213 | if ( is_wp_error( $check ) ) { |
| 214 | return $check; |
| 215 | } |
| 216 | |
| 217 | // set date filtering |
| 218 | $this->setup_report( $filter ); |
| 219 | |
| 220 | $top_sellers = $this->report->get_order_report_data( array( |
| 221 | 'data' => array( |
| 222 | '_product_id' => array( |
| 223 | 'type' => 'order_item_meta', |
| 224 | 'order_item_type' => 'line_item', |
| 225 | 'function' => '', |
| 226 | 'name' => 'product_id', |
| 227 | ), |
| 228 | '_qty' => array( |
| 229 | 'type' => 'order_item_meta', |
| 230 | 'order_item_type' => 'line_item', |
| 231 | 'function' => 'SUM', |
| 232 | 'name' => 'order_item_qty', |
| 233 | ), |
| 234 | ), |
| 235 | 'order_by' => 'order_item_qty DESC', |
| 236 | 'group_by' => 'product_id', |
| 237 | 'limit' => isset( $filter['limit'] ) ? absint( $filter['limit'] ) : 12, |
| 238 | 'query_type' => 'get_results', |
| 239 | 'filter_range' => true, |
| 240 | ) ); |
| 241 | |
| 242 | $top_sellers_data = array(); |
| 243 | |
| 244 | foreach ( $top_sellers as $top_seller ) { |
| 245 | |
| 246 | $product = wc_get_product( $top_seller->product_id ); |
| 247 | |
| 248 | if ( $product ) { |
| 249 | $top_sellers_data[] = array( |
| 250 | 'title' => $product->get_name(), |
| 251 | 'product_id' => $top_seller->product_id, |
| 252 | 'quantity' => $top_seller->order_item_qty, |
| 253 | ); |
| 254 | } |
| 255 | } |
| 256 | |
| 257 | return array( 'top_sellers' => apply_filters( 'woocommerce_api_report_response', $top_sellers_data, $this->report, $fields, $this->server ) ); |
| 258 | } |
| 259 | |
| 260 | /** |
| 261 | * Setup the report object and parse any date filtering |
| 262 | * |
| 263 | * @since 2.1 |
| 264 | * @param array $filter date filtering |
| 265 | */ |
| 266 | private function setup_report( $filter ) { |
| 267 | |
| 268 | include_once( WC()->plugin_path() . '/includes/admin/reports/class-wc-admin-report.php' ); |
| 269 | include_once( WC()->plugin_path() . '/includes/admin/reports/class-wc-report-sales-by-date.php' ); |
| 270 | |
| 271 | $this->report = new WC_Report_Sales_By_Date(); |
| 272 | |
| 273 | if ( empty( $filter['period'] ) ) { |
| 274 | |
| 275 | // custom date range |
| 276 | $filter['period'] = 'custom'; |
| 277 | |
| 278 | if ( ! empty( $filter['date_min'] ) || ! empty( $filter['date_max'] ) ) { |
| 279 | |
| 280 | // overwrite _GET to make use of WC_Admin_Report::calculate_current_range() for custom date ranges |
| 281 | $_GET['start_date'] = $this->server->parse_datetime( $filter['date_min'] ); |
| 282 | $_GET['end_date'] = isset( $filter['date_max'] ) ? $this->server->parse_datetime( $filter['date_max'] ) : null; |
| 283 | |
| 284 | } else { |
| 285 | |
| 286 | // default custom range to today |
| 287 | $_GET['start_date'] = $_GET['end_date'] = date( 'Y-m-d', current_time( 'timestamp' ) ); |
| 288 | } |
| 289 | } else { |
| 290 | |
| 291 | // ensure period is valid |
| 292 | if ( ! in_array( $filter['period'], array( 'week', 'month', 'last_month', 'year' ) ) ) { |
| 293 | $filter['period'] = 'week'; |
| 294 | } |
| 295 | |
| 296 | // TODO: change WC_Admin_Report class to use "week" instead, as it's more consistent with other periods |
| 297 | // allow "week" for period instead of "7day" |
| 298 | if ( 'week' === $filter['period'] ) { |
| 299 | $filter['period'] = '7day'; |
| 300 | } |
| 301 | } |
| 302 | |
| 303 | $this->report->calculate_current_range( $filter['period'] ); |
| 304 | } |
| 305 | |
| 306 | /** |
| 307 | * Verify that the current user has permission to view reports |
| 308 | * |
| 309 | * @since 2.1 |
| 310 | * @see WC_API_Resource::validate_request() |
| 311 | * |
| 312 | * @param null $id unused |
| 313 | * @param null $type unused |
| 314 | * @param null $context unused |
| 315 | * |
| 316 | * @return bool|WP_Error |
| 317 | */ |
| 318 | protected function validate_request( $id = null, $type = null, $context = null ) { |
| 319 | |
| 320 | if ( ! current_user_can( 'view_woocommerce_reports' ) ) { |
| 321 | |
| 322 | return new WP_Error( 'woocommerce_api_user_cannot_read_report', __( 'You do not have permission to read this report', 'woocommerce' ), array( 'status' => 401 ) ); |
| 323 | |
| 324 | } else { |
| 325 | |
| 326 | return true; |
| 327 | } |
| 328 | } |
| 329 | } |
| 330 |