Added reporting REST API extensions to wc-admin.
This commit is contained in:
parent
c8ee6cf8e3
commit
d2ac6cdbe1
|
@ -0,0 +1,280 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* REST API Reports categories controller
|
||||||
|
*
|
||||||
|
* Handles requests to the /reports/categories endpoint.
|
||||||
|
*
|
||||||
|
* @package WooCommerce Admin/API
|
||||||
|
*/
|
||||||
|
|
||||||
|
defined( 'ABSPATH' ) || exit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* REST API Reports categories controller class.
|
||||||
|
*
|
||||||
|
* @package WooCommerce/API
|
||||||
|
* @extends WC_REST_Reports_Controller
|
||||||
|
*/
|
||||||
|
class WC_Admin_REST_Reports_Categories_Controller extends WC_REST_Reports_Controller {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Endpoint namespace.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $namespace = 'wc/v3';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Route base.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $rest_base = 'reports/categories';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps query arguments from the REST request.
|
||||||
|
*
|
||||||
|
* @param array $request Request array.
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
protected function prepare_reports_query( $request ) {
|
||||||
|
$args = array();
|
||||||
|
$args['before'] = $request['before'];
|
||||||
|
$args['after'] = $request['after'];
|
||||||
|
$args['interval'] = $request['interval'];
|
||||||
|
$args['page'] = $request['page'];
|
||||||
|
$args['per_page'] = $request['per_page'];
|
||||||
|
$args['orderby'] = $request['orderby'];
|
||||||
|
$args['order'] = $request['order'];
|
||||||
|
$args['categories'] = (array) $request['categories'];
|
||||||
|
|
||||||
|
return $args;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all reports.
|
||||||
|
*
|
||||||
|
* @param WP_REST_Request $request Request data.
|
||||||
|
* @return array|WP_Error
|
||||||
|
*/
|
||||||
|
public function get_items( $request ) {
|
||||||
|
$query_args = $this->prepare_reports_query( $request );
|
||||||
|
$categories_query = new WC_Reports_Categories_Query( $query_args );
|
||||||
|
$report_data = $categories_query->get_data();
|
||||||
|
|
||||||
|
if ( is_wp_error( $report_data ) ) {
|
||||||
|
return $report_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( ! isset( $report_data->data ) || ! isset( $report_data->page_no ) || ! isset( $report_data->pages ) ) {
|
||||||
|
return new WP_Error( 'woocommerce_rest_reports_categories_invalid_response', __( 'Invalid response from data store.', 'woocommerce' ), array( 'status' => 500 ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
$out_data = array();
|
||||||
|
|
||||||
|
foreach ( $report_data->data as $datum ) {
|
||||||
|
$item = $this->prepare_item_for_response( $datum, $request );
|
||||||
|
$out_data[] = $this->prepare_response_for_collection( $item );
|
||||||
|
}
|
||||||
|
|
||||||
|
$response = rest_ensure_response( $out_data );
|
||||||
|
$response->header( 'X-WP-Total', (int) $report_data->total );
|
||||||
|
$response->header( 'X-WP-TotalPages', (int) $report_data->pages );
|
||||||
|
|
||||||
|
$page = $report_data->page_no;
|
||||||
|
$max_pages = $report_data->pages;
|
||||||
|
$base = add_query_arg( $request->get_query_params(), rest_url( sprintf( '/%s/%s', $this->namespace, $this->rest_base ) ) );
|
||||||
|
if ( $page > 1 ) {
|
||||||
|
$prev_page = $page - 1;
|
||||||
|
if ( $prev_page > $max_pages ) {
|
||||||
|
$prev_page = $max_pages;
|
||||||
|
}
|
||||||
|
$prev_link = add_query_arg( 'page', $prev_page, $base );
|
||||||
|
$response->link_header( 'prev', $prev_link );
|
||||||
|
}
|
||||||
|
if ( $max_pages > $page ) {
|
||||||
|
$next_page = $page + 1;
|
||||||
|
$next_link = add_query_arg( 'page', $next_page, $base );
|
||||||
|
$response->link_header( 'next', $next_link );
|
||||||
|
}
|
||||||
|
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepare a report object for serialization.
|
||||||
|
*
|
||||||
|
* @param stdClass $report Report data.
|
||||||
|
* @param WP_REST_Request $request Request object.
|
||||||
|
* @return WP_REST_Response
|
||||||
|
*/
|
||||||
|
public function prepare_item_for_response( $report, $request ) {
|
||||||
|
$data = $report;
|
||||||
|
|
||||||
|
$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
|
||||||
|
$data = $this->add_additional_fields_to_object( $data, $request );
|
||||||
|
$data = $this->filter_response_by_context( $data, $context );
|
||||||
|
|
||||||
|
// Wrap the data in a response object.
|
||||||
|
$response = rest_ensure_response( $data );
|
||||||
|
$response->add_links( $this->prepare_links( $report ) );
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter a report returned from the API.
|
||||||
|
*
|
||||||
|
* Allows modification of the report data right before it is returned.
|
||||||
|
*
|
||||||
|
* @param WP_REST_Response $response The response object.
|
||||||
|
* @param object $report The original report object.
|
||||||
|
* @param WP_REST_Request $request Request used to generate the response.
|
||||||
|
*/
|
||||||
|
return apply_filters( 'woocommerce_rest_prepare_report_categories', $response, $report, $request );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepare links for the request.
|
||||||
|
*
|
||||||
|
* @param WC_Admin_Reports_Query $object Object data.
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
protected function prepare_links( $object ) {
|
||||||
|
$links = array(
|
||||||
|
'category' => array(
|
||||||
|
'href' => rest_url( sprintf( '/%s/products/categories/%d', $this->namespace, $object['category_id'] ) ),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
return $links;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the Report's schema, conforming to JSON Schema.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function get_item_schema() {
|
||||||
|
$schema = array(
|
||||||
|
'$schema' => 'http://json-schema.org/draft-04/schema#',
|
||||||
|
'title' => 'report_categories',
|
||||||
|
'type' => 'object',
|
||||||
|
'properties' => array(
|
||||||
|
'category_id' => array(
|
||||||
|
'description' => __( 'Category ID.', 'woocommerce' ),
|
||||||
|
'type' => 'integer',
|
||||||
|
'context' => array( 'view', 'edit' ),
|
||||||
|
'readonly' => true,
|
||||||
|
),
|
||||||
|
'items_sold' => array(
|
||||||
|
'description' => __( 'Amount of items sold.', 'woocommerce' ),
|
||||||
|
'type' => 'integer',
|
||||||
|
'context' => array( 'view', 'edit' ),
|
||||||
|
'readonly' => true,
|
||||||
|
),
|
||||||
|
'gross_revenue' => array(
|
||||||
|
'description' => __( 'Gross revenue.', 'woocommerce' ),
|
||||||
|
'type' => 'number',
|
||||||
|
'context' => array( 'view', 'edit' ),
|
||||||
|
'readonly' => true,
|
||||||
|
),
|
||||||
|
'orders_count' => array(
|
||||||
|
'description' => __( 'Amount of orders.', 'woocommerce' ),
|
||||||
|
'type' => 'integer',
|
||||||
|
'context' => array( 'view', 'edit' ),
|
||||||
|
'readonly' => true,
|
||||||
|
),
|
||||||
|
'products_count' => array(
|
||||||
|
'description' => __( 'Amount of products.', 'woocommerce' ),
|
||||||
|
'type' => 'integer',
|
||||||
|
'context' => array( 'view', 'edit' ),
|
||||||
|
'readonly' => true,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
return $this->add_additional_fields_schema( $schema );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the query params for collections.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function get_collection_params() {
|
||||||
|
$params = array();
|
||||||
|
$params['context'] = $this->get_context_param( array( 'default' => 'view' ) );
|
||||||
|
$params['page'] = array(
|
||||||
|
'description' => __( 'Current page of the collection.', 'woocommerce' ),
|
||||||
|
'type' => 'integer',
|
||||||
|
'default' => 1,
|
||||||
|
'sanitize_callback' => 'absint',
|
||||||
|
'validate_callback' => 'rest_validate_request_arg',
|
||||||
|
'minimum' => 1,
|
||||||
|
);
|
||||||
|
$params['per_page'] = array(
|
||||||
|
'description' => __( 'Maximum number of items to be returned in result set.', 'woocommerce' ),
|
||||||
|
'type' => 'integer',
|
||||||
|
'default' => 10,
|
||||||
|
'minimum' => 1,
|
||||||
|
'maximum' => 100,
|
||||||
|
'sanitize_callback' => 'absint',
|
||||||
|
'validate_callback' => 'rest_validate_request_arg',
|
||||||
|
);
|
||||||
|
$params['after'] = array(
|
||||||
|
'description' => __( 'Limit response to resources published after a given ISO8601 compliant date.', 'woocommerce' ),
|
||||||
|
'type' => 'string',
|
||||||
|
'format' => 'date-time',
|
||||||
|
'validate_callback' => 'rest_validate_request_arg',
|
||||||
|
);
|
||||||
|
$params['before'] = array(
|
||||||
|
'description' => __( 'Limit response to resources published before a given ISO8601 compliant date.', 'woocommerce' ),
|
||||||
|
'type' => 'string',
|
||||||
|
'format' => 'date-time',
|
||||||
|
'validate_callback' => 'rest_validate_request_arg',
|
||||||
|
);
|
||||||
|
$params['order'] = array(
|
||||||
|
'description' => __( 'Order sort attribute ascending or descending.', 'woocommerce' ),
|
||||||
|
'type' => 'string',
|
||||||
|
'default' => 'desc',
|
||||||
|
'enum' => array( 'asc', 'desc' ),
|
||||||
|
'validate_callback' => 'rest_validate_request_arg',
|
||||||
|
);
|
||||||
|
$params['orderby'] = array(
|
||||||
|
'description' => __( 'Sort collection by object attribute.', 'woocommerce' ),
|
||||||
|
'type' => 'string',
|
||||||
|
'default' => 'date',
|
||||||
|
'enum' => array(
|
||||||
|
'date',
|
||||||
|
'items_sold',
|
||||||
|
'gross_revenue',
|
||||||
|
'orders_count',
|
||||||
|
'products_count',
|
||||||
|
),
|
||||||
|
'validate_callback' => 'rest_validate_request_arg',
|
||||||
|
);
|
||||||
|
$params['interval'] = array(
|
||||||
|
'description' => __( 'Time interval to use for buckets in the returned data.', 'woocommerce' ),
|
||||||
|
'type' => 'string',
|
||||||
|
'default' => 'week',
|
||||||
|
'enum' => array(
|
||||||
|
'hour',
|
||||||
|
'day',
|
||||||
|
'week',
|
||||||
|
'month',
|
||||||
|
'quarter',
|
||||||
|
'year',
|
||||||
|
),
|
||||||
|
'validate_callback' => 'rest_validate_request_arg',
|
||||||
|
);
|
||||||
|
$params['categories'] = array(
|
||||||
|
'description' => __( 'Limit result set to all items that have the specified term assigned in the categories taxonomy.', 'woocommerce' ),
|
||||||
|
'type' => 'array',
|
||||||
|
'sanitize_callback' => 'wp_parse_id_list',
|
||||||
|
'validate_callback' => 'rest_validate_request_arg',
|
||||||
|
'items' => array(
|
||||||
|
'type' => 'integer',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
return $params;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,262 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* REST API Reports controller extended by WC Admin plugin.
|
||||||
|
*
|
||||||
|
* Handles requests to the reports endpoint.
|
||||||
|
*
|
||||||
|
* @package WooCommerce Admin/API
|
||||||
|
*/
|
||||||
|
|
||||||
|
defined( 'ABSPATH' ) || exit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* REST API Reports controller class.
|
||||||
|
*
|
||||||
|
* @package WooCommerce Admin/API
|
||||||
|
* @extends WC_REST_Reports_Controller
|
||||||
|
*/
|
||||||
|
class WC_Admin_REST_Reports_Controller extends WC_REST_Reports_Controller {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Endpoint namespace.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $namespace = 'wc/v3';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Route base.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $rest_base = 'reports';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get reports list.
|
||||||
|
*
|
||||||
|
* @since 3.5.0
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
protected function get_reports() {
|
||||||
|
$reports = parent::get_reports();
|
||||||
|
|
||||||
|
$reports[] = array(
|
||||||
|
'slug' => 'orders/count',
|
||||||
|
'description' => __( 'Orders stats count.', 'woocommerce' ),
|
||||||
|
);
|
||||||
|
$reports[] = array(
|
||||||
|
'slug' => 'products/count',
|
||||||
|
'description' => __( 'Customers stats count.', 'woocommerce' ),
|
||||||
|
);
|
||||||
|
$reports[] = array(
|
||||||
|
'slug' => 'customers/count',
|
||||||
|
'description' => __( 'Customers stats count.', 'woocommerce' ),
|
||||||
|
);
|
||||||
|
$reports[] = array(
|
||||||
|
'slug' => 'coupons/count',
|
||||||
|
'description' => __( 'Coupons stats count.', 'woocommerce' ),
|
||||||
|
);
|
||||||
|
$reports[] = array(
|
||||||
|
'slug' => 'reviews/count',
|
||||||
|
'description' => __( 'Reviews stats count.', 'woocommerce' ),
|
||||||
|
);
|
||||||
|
$reports[] = array(
|
||||||
|
'slug' => 'categories/count',
|
||||||
|
'description' => __( 'Categories stats count.', 'woocommerce' ),
|
||||||
|
);
|
||||||
|
$reports[] = array(
|
||||||
|
'slug' => 'tags/count',
|
||||||
|
'description' => __( 'Tags stats count.', 'woocommerce' ),
|
||||||
|
);
|
||||||
|
$reports[] = array(
|
||||||
|
'slug' => 'attributes/count',
|
||||||
|
'description' => __( 'Attributes stats count.', 'woocommerce' ),
|
||||||
|
);
|
||||||
|
|
||||||
|
return $reports;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register the routes for reports.
|
||||||
|
*/
|
||||||
|
public function register_routes() {
|
||||||
|
register_rest_route( $this->namespace, '/' . $this->rest_base, array(
|
||||||
|
array(
|
||||||
|
'methods' => WP_REST_Server::READABLE,
|
||||||
|
'callback' => array( $this, 'get_items' ),
|
||||||
|
'permission_callback' => array( $this, 'get_items_permissions_check' ),
|
||||||
|
'args' => $this->get_collection_params(),
|
||||||
|
),
|
||||||
|
'schema' => array( $this, 'get_public_item_schema' ),
|
||||||
|
) );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether a given request has permission to read reports.
|
||||||
|
*
|
||||||
|
* @param WP_REST_Request $request Full details about the request.
|
||||||
|
* @return WP_Error|boolean
|
||||||
|
*/
|
||||||
|
public function get_items_permissions_check( $request ) {
|
||||||
|
if ( ! wc_rest_check_manager_permissions( 'reports', 'read' ) ) {
|
||||||
|
return new WP_Error( 'woocommerce_rest_cannot_view', __( 'Sorry, you cannot list resources.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all reports.
|
||||||
|
*
|
||||||
|
* @param WP_REST_Request $request Request data.
|
||||||
|
* @return array|WP_Error
|
||||||
|
*/
|
||||||
|
public function get_items( $request ) {
|
||||||
|
$data = array();
|
||||||
|
$reports = array(
|
||||||
|
array(
|
||||||
|
'slug' => 'revenue/stats',
|
||||||
|
'description' => __( 'Stats about revenue.', 'woocommerce' ),
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
'slug' => 'orders/stats',
|
||||||
|
'description' => __( 'Stats about orders.', 'woocommerce' ),
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
'slug' => 'products',
|
||||||
|
'description' => __( 'Products detailed reports.', 'woocommerce' ),
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
'slug' => 'products/stats',
|
||||||
|
'description' => __( 'Stats about products.', 'woocommerce' ),
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
'slug' => 'categories',
|
||||||
|
'description' => __( 'Product categories detailed reports.', 'woocommerce' ),
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
'slug' => 'categories/stats',
|
||||||
|
'description' => __( 'Stats about product categories.', 'woocommerce' ),
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
'slug' => 'coupons',
|
||||||
|
'description' => __( 'Coupons detailed reports.', 'woocommerce' ),
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
'slug' => 'coupons/stats',
|
||||||
|
'description' => __( 'Stats about coupons.', 'woocommerce' ),
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
'slug' => 'taxes',
|
||||||
|
'description' => __( 'Taxes detailed reports.', 'woocommerce' ),
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
'slug' => 'taxes/stats',
|
||||||
|
'description' => __( 'Stats about taxes.', 'woocommerce' ),
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
'slug' => 'downloads',
|
||||||
|
'description' => __( 'Product downloads detailed reports.', 'woocommerce' ),
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
'slug' => 'downloads/files',
|
||||||
|
'description' => __( 'Product download files detailed reports.', 'woocommerce' ),
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
'slug' => 'downloads/stats',
|
||||||
|
'description' => __( 'Stats about product downloads.', 'woocommerce' ),
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
'slug' => 'customers',
|
||||||
|
'description' => __( 'Customers detailed reports.', 'woocommerce' ),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
foreach ( $reports as $report ) {
|
||||||
|
$item = $this->prepare_item_for_response( (object) $report, $request );
|
||||||
|
$data[] = $this->prepare_response_for_collection( $item );
|
||||||
|
}
|
||||||
|
|
||||||
|
return rest_ensure_response( $data );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepare a report object for serialization.
|
||||||
|
*
|
||||||
|
* @param stdClass $report Report data.
|
||||||
|
* @param WP_REST_Request $request Request object.
|
||||||
|
* @return WP_REST_Response
|
||||||
|
*/
|
||||||
|
public function prepare_item_for_response( $report, $request ) {
|
||||||
|
$data = array(
|
||||||
|
'slug' => $report->slug,
|
||||||
|
'description' => $report->description,
|
||||||
|
);
|
||||||
|
|
||||||
|
$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
|
||||||
|
$data = $this->add_additional_fields_to_object( $data, $request );
|
||||||
|
$data = $this->filter_response_by_context( $data, $context );
|
||||||
|
|
||||||
|
// Wrap the data in a response object.
|
||||||
|
$response = rest_ensure_response( $data );
|
||||||
|
$response->add_links( array(
|
||||||
|
'self' => array(
|
||||||
|
'href' => rest_url( sprintf( '/%s/%s/%s', $this->namespace, $this->rest_base, $report->slug ) ),
|
||||||
|
),
|
||||||
|
'collection' => array(
|
||||||
|
'href' => rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ),
|
||||||
|
),
|
||||||
|
) );
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter a report returned from the API.
|
||||||
|
*
|
||||||
|
* Allows modification of the report data right before it is returned.
|
||||||
|
*
|
||||||
|
* @param WP_REST_Response $response The response object.
|
||||||
|
* @param object $report The original report object.
|
||||||
|
* @param WP_REST_Request $request Request used to generate the response.
|
||||||
|
*/
|
||||||
|
return apply_filters( 'woocommerce_rest_prepare_report', $response, $report, $request );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the Report's schema, conforming to JSON Schema.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function get_item_schema() {
|
||||||
|
$schema = array(
|
||||||
|
'$schema' => 'http://json-schema.org/draft-04/schema#',
|
||||||
|
'title' => 'report',
|
||||||
|
'type' => 'object',
|
||||||
|
'properties' => array(
|
||||||
|
'slug' => array(
|
||||||
|
'description' => __( 'An alphanumeric identifier for the resource.', 'woocommerce' ),
|
||||||
|
'type' => 'string',
|
||||||
|
'context' => array( 'view' ),
|
||||||
|
'readonly' => true,
|
||||||
|
),
|
||||||
|
'description' => array(
|
||||||
|
'description' => __( 'A human-readable description of the resource.', 'woocommerce' ),
|
||||||
|
'type' => 'string',
|
||||||
|
'context' => array( 'view' ),
|
||||||
|
'readonly' => true,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
return $this->add_additional_fields_schema( $schema );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the query params for collections.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function get_collection_params() {
|
||||||
|
return array(
|
||||||
|
'context' => $this->get_context_param( array( 'default' => 'view' ) ),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* REST API Reports coupons controller
|
||||||
|
*
|
||||||
|
* Handles requests to the /reports/coupons endpoint.
|
||||||
|
*
|
||||||
|
* @package WooCommerce Admin/API
|
||||||
|
*/
|
||||||
|
|
||||||
|
defined( 'ABSPATH' ) || exit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* REST API Reports coupons controller class.
|
||||||
|
*
|
||||||
|
* @package WooCommerce/API
|
||||||
|
* @extends WC_REST_Reports_Controller
|
||||||
|
*/
|
||||||
|
class WC_Admin_REST_Reports_Coupons_Controller extends WC_REST_Reports_Controller {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Endpoint namespace.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $namespace = 'wc/v3';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Route base.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $rest_base = 'reports/coupons';
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* REST API Reports coupons stats controller
|
||||||
|
*
|
||||||
|
* Handles requests to the /reports/coupons/stats endpoint.
|
||||||
|
*
|
||||||
|
* @package WooCommerce/API
|
||||||
|
* @since 3.5.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
defined( 'ABSPATH' ) || exit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* REST API Reports coupons stats controller class.
|
||||||
|
*
|
||||||
|
* @package WooCommerce/API
|
||||||
|
* @extends WC_REST_Reports_Controller
|
||||||
|
*/
|
||||||
|
class WC_REST_Reports_Coupons_Stats_Controller extends WC_REST_Reports_Controller {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Endpoint namespace.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $namespace = 'wc/v3';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Route base.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $rest_base = 'reports/coupons/stats';
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* REST API Reports customers controller
|
||||||
|
*
|
||||||
|
* Handles requests to the /reports/customers endpoint.
|
||||||
|
*
|
||||||
|
* @package WooCommerce/API
|
||||||
|
* @since 3.5.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
defined( 'ABSPATH' ) || exit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* REST API Reports customers controller class.
|
||||||
|
*
|
||||||
|
* @package WooCommerce/API
|
||||||
|
* @extends WC_REST_Reports_Controller
|
||||||
|
*/
|
||||||
|
class WC_REST_Reports_Customers_Controller extends WC_REST_Reports_Controller {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Endpoint namespace.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $namespace = 'wc/v3';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Route base.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $rest_base = 'reports/customers';
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* REST API Reports downloads controller
|
||||||
|
*
|
||||||
|
* Handles requests to the /reports/downloads endpoint.
|
||||||
|
*
|
||||||
|
* @package WooCommerce/API
|
||||||
|
* @since 3.5.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
defined( 'ABSPATH' ) || exit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* REST API Reports downloads controller class.
|
||||||
|
*
|
||||||
|
* @package WooCommerce/API
|
||||||
|
* @extends WC_REST_Reports_Controller
|
||||||
|
*/
|
||||||
|
class WC_REST_Reports_Downloads_Controller extends WC_REST_Reports_Controller {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Endpoint namespace.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $namespace = 'wc/v3';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Route base.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $rest_base = 'reports/downloads';
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* REST API Reports downloads files controller
|
||||||
|
*
|
||||||
|
* Handles requests to the /reports/downloads/files endpoint.
|
||||||
|
*
|
||||||
|
* @package WooCommerce/API
|
||||||
|
* @since 3.5.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
defined( 'ABSPATH' ) || exit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* REST API Reports downloads files controller class.
|
||||||
|
*
|
||||||
|
* @package WooCommerce/API
|
||||||
|
* @extends WC_REST_Reports_Controller
|
||||||
|
*/
|
||||||
|
class WC_REST_Reports_Downloads_Files_Controller extends WC_REST_Reports_Controller {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Endpoint namespace.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $namespace = 'wc/v3';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Route base.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $rest_base = 'reports/downloads/files';
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* REST API Reports downloads stats controller
|
||||||
|
*
|
||||||
|
* Handles requests to the /reports/downloads/stats endpoint.
|
||||||
|
*
|
||||||
|
* @package WooCommerce/API
|
||||||
|
* @since 3.5.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
defined( 'ABSPATH' ) || exit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* REST API Reports downloads stats controller class.
|
||||||
|
*
|
||||||
|
* @package WooCommerce/API
|
||||||
|
* @extends WC_REST_Reports_Controller
|
||||||
|
*/
|
||||||
|
class WC_REST_Reports_Downloads_Stats_Controller extends WC_REST_Reports_Controller {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Endpoint namespace.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $namespace = 'wc/v3';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Route base.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $rest_base = 'reports/downloads/stats';
|
||||||
|
}
|
|
@ -0,0 +1,349 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* REST API Reports orders stats controller
|
||||||
|
*
|
||||||
|
* Handles requests to the /reports/orders/stats endpoint.
|
||||||
|
*
|
||||||
|
* @package WooCommerce/API
|
||||||
|
* @since 3.5.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
defined( 'ABSPATH' ) || exit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* REST API Reports orders stats controller class.
|
||||||
|
*
|
||||||
|
* @package WooCommerce/API
|
||||||
|
* @extends WC_REST_Reports_Controller
|
||||||
|
*/
|
||||||
|
class WC_REST_Reports_Orders_Stats_Controller extends WC_REST_Reports_Controller {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Endpoint namespace.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $namespace = 'wc/v3';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Route base.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $rest_base = 'reports/orders/stats';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps query arguments from the REST request.
|
||||||
|
*
|
||||||
|
* @param array $request Request array.
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
protected function prepare_reports_query( $request ) {
|
||||||
|
$args = array();
|
||||||
|
$args['before'] = $request['before'];
|
||||||
|
$args['after'] = $request['after'];
|
||||||
|
$args['interval'] = $request['interval'];
|
||||||
|
$args['page'] = $request['page'];
|
||||||
|
$args['per_page'] = $request['per_page'];
|
||||||
|
$args['orderby'] = $request['orderby'];
|
||||||
|
$args['order'] = $request['order'];
|
||||||
|
$args['categories'] = (array) $request['categories'];
|
||||||
|
$args['coupons'] = (array) $request['coupons'];
|
||||||
|
$args['products'] = (array) $request['products'];
|
||||||
|
$args['order_status'] = (array) $request['order_status'];
|
||||||
|
|
||||||
|
return $args;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all reports.
|
||||||
|
*
|
||||||
|
* @param WP_REST_Request $request Request data.
|
||||||
|
* @return array|WP_Error
|
||||||
|
*/
|
||||||
|
public function get_items( $request ) {
|
||||||
|
$query_args = $this->prepare_reports_query( $request );
|
||||||
|
$orders_query = new WC_Reports_Orders_Stats_Query( $query_args );
|
||||||
|
$report_data = $orders_query->get_data();
|
||||||
|
|
||||||
|
$out_data = array(
|
||||||
|
'totals' => get_object_vars( $report_data->totals ),
|
||||||
|
'intervals' => array(),
|
||||||
|
);
|
||||||
|
|
||||||
|
foreach ( $report_data->intervals as $interval_data ) {
|
||||||
|
$item = $this->prepare_item_for_response( $interval_data, $request );
|
||||||
|
$out_data['intervals'][] = $this->prepare_response_for_collection( $item );
|
||||||
|
}
|
||||||
|
|
||||||
|
$response = rest_ensure_response( $out_data );
|
||||||
|
$response->header( 'X-WP-Total', (int) $report_data->total );
|
||||||
|
$response->header( 'X-WP-TotalPages', (int) $report_data->pages );
|
||||||
|
|
||||||
|
$page = $report_data->page_no;
|
||||||
|
$max_pages = $report_data->pages;
|
||||||
|
$base = add_query_arg( $request->get_query_params(), rest_url( sprintf( '/%s/%s', $this->namespace, $this->rest_base ) ) );
|
||||||
|
if ( $page > 1 ) {
|
||||||
|
$prev_page = $page - 1;
|
||||||
|
if ( $prev_page > $max_pages ) {
|
||||||
|
$prev_page = $max_pages;
|
||||||
|
}
|
||||||
|
$prev_link = add_query_arg( 'page', $prev_page, $base );
|
||||||
|
$response->link_header( 'prev', $prev_link );
|
||||||
|
}
|
||||||
|
if ( $max_pages > $page ) {
|
||||||
|
$next_page = $page + 1;
|
||||||
|
$next_link = add_query_arg( 'page', $next_page, $base );
|
||||||
|
$response->link_header( 'next', $next_link );
|
||||||
|
}
|
||||||
|
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepare a report object for serialization.
|
||||||
|
*
|
||||||
|
* @param Array $report Report data.
|
||||||
|
* @param WP_REST_Request $request Request object.
|
||||||
|
* @return WP_REST_Response
|
||||||
|
*/
|
||||||
|
public function prepare_item_for_response( $report, $request ) {
|
||||||
|
$data = $report;
|
||||||
|
|
||||||
|
$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
|
||||||
|
$data = $this->add_additional_fields_to_object( $data, $request );
|
||||||
|
$data = $this->filter_response_by_context( $data, $context );
|
||||||
|
|
||||||
|
// Wrap the data in a response object.
|
||||||
|
$response = rest_ensure_response( $data );
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter a report returned from the API.
|
||||||
|
*
|
||||||
|
* Allows modification of the report data right before it is returned.
|
||||||
|
*
|
||||||
|
* @param WP_REST_Response $response The response object.
|
||||||
|
* @param object $report The original report object.
|
||||||
|
* @param WP_REST_Request $request Request used to generate the response.
|
||||||
|
*/
|
||||||
|
return apply_filters( 'woocommerce_rest_prepare_report_orders_stats', $response, $report, $request );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the Report's schema, conforming to JSON Schema.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function get_item_schema() {
|
||||||
|
$totals = array(
|
||||||
|
'net_revenue' => array(
|
||||||
|
'description' => __( 'Net revenue.', 'woocommerce' ),
|
||||||
|
'type' => 'number',
|
||||||
|
'context' => array( 'view', 'edit' ),
|
||||||
|
'readonly' => true,
|
||||||
|
),
|
||||||
|
'avg_order_value' => array(
|
||||||
|
'description' => __( 'Average order value.', 'woocommerce' ),
|
||||||
|
'type' => 'number',
|
||||||
|
'context' => array( 'view', 'edit' ),
|
||||||
|
'readonly' => true,
|
||||||
|
),
|
||||||
|
'orders_count' => array(
|
||||||
|
'description' => __( 'Amount of orders', 'woocommerce' ),
|
||||||
|
'type' => 'integer',
|
||||||
|
'context' => array( 'view', 'edit' ),
|
||||||
|
'readonly' => true,
|
||||||
|
),
|
||||||
|
'avg_items_per_order' => array(
|
||||||
|
'description' => __( 'Average items per order', 'woocommerce' ),
|
||||||
|
'type' => 'integer',
|
||||||
|
'context' => array( 'view', 'edit' ),
|
||||||
|
'readonly' => true,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
$schema = array(
|
||||||
|
'$schema' => 'http://json-schema.org/draft-04/schema#',
|
||||||
|
'title' => 'report_orders_stats',
|
||||||
|
'type' => 'object',
|
||||||
|
'properties' => array(
|
||||||
|
'totals' => array(
|
||||||
|
'description' => __( 'Totals data.', 'woocommerce' ),
|
||||||
|
'type' => 'object',
|
||||||
|
'context' => array( 'view', 'edit' ),
|
||||||
|
'readonly' => true,
|
||||||
|
'properties' => $totals,
|
||||||
|
),
|
||||||
|
'intervals' => array(
|
||||||
|
'description' => __( 'Reports data grouped by intervals.', 'woocommerce' ),
|
||||||
|
'type' => 'array',
|
||||||
|
'context' => array( 'view', 'edit' ),
|
||||||
|
'readonly' => true,
|
||||||
|
'items' => array(
|
||||||
|
'type' => 'object',
|
||||||
|
'properties' => array(
|
||||||
|
'interval' => array(
|
||||||
|
'description' => __( 'Type of interval.', 'woocommerce' ),
|
||||||
|
'type' => 'string',
|
||||||
|
'context' => array( 'view', 'edit' ),
|
||||||
|
'readonly' => true,
|
||||||
|
'enum' => array( 'day', 'week', 'month', 'year' ),
|
||||||
|
),
|
||||||
|
'date_start' => array(
|
||||||
|
'description' => __( "The date the report start, in the site's timezone.", 'woocommerce' ),
|
||||||
|
'type' => 'date-time',
|
||||||
|
'context' => array( 'view', 'edit' ),
|
||||||
|
'readonly' => true,
|
||||||
|
),
|
||||||
|
'date_start_gmt' => array(
|
||||||
|
'description' => __( 'The date the report start, as GMT.', 'woocommerce' ),
|
||||||
|
'type' => 'date-time',
|
||||||
|
'context' => array( 'view', 'edit' ),
|
||||||
|
'readonly' => true,
|
||||||
|
),
|
||||||
|
'date_end' => array(
|
||||||
|
'description' => __( "The date the report end, in the site's timezone.", 'woocommerce' ),
|
||||||
|
'type' => 'date-time',
|
||||||
|
'context' => array( 'view', 'edit' ),
|
||||||
|
'readonly' => true,
|
||||||
|
),
|
||||||
|
'date_end_gmt' => array(
|
||||||
|
'description' => __( 'The date the report end, as GMT.', 'woocommerce' ),
|
||||||
|
'type' => 'date-time',
|
||||||
|
'context' => array( 'view', 'edit' ),
|
||||||
|
'readonly' => true,
|
||||||
|
),
|
||||||
|
'subtotals' => array(
|
||||||
|
'description' => __( 'Interval subtotals.', 'woocommerce' ),
|
||||||
|
'type' => 'object',
|
||||||
|
'context' => array( 'view', 'edit' ),
|
||||||
|
'readonly' => true,
|
||||||
|
'properties' => $totals,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
return $this->add_additional_fields_schema( $schema );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the query params for collections.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function get_collection_params() {
|
||||||
|
$params = array();
|
||||||
|
$params['context'] = $this->get_context_param( array( 'default' => 'view' ) );
|
||||||
|
$params['page'] = array(
|
||||||
|
'description' => __( 'Current page of the collection.', 'woocommerce' ),
|
||||||
|
'type' => 'integer',
|
||||||
|
'default' => 1,
|
||||||
|
'sanitize_callback' => 'absint',
|
||||||
|
'validate_callback' => 'rest_validate_request_arg',
|
||||||
|
'minimum' => 1,
|
||||||
|
);
|
||||||
|
$params['per_page'] = array(
|
||||||
|
'description' => __( 'Maximum number of items to be returned in result set.', 'woocommerce' ),
|
||||||
|
'type' => 'integer',
|
||||||
|
'default' => 10,
|
||||||
|
'minimum' => 1,
|
||||||
|
'maximum' => 100,
|
||||||
|
'sanitize_callback' => 'absint',
|
||||||
|
'validate_callback' => 'rest_validate_request_arg',
|
||||||
|
);
|
||||||
|
$params['after'] = array(
|
||||||
|
'description' => __( 'Limit response to resources published after a given ISO8601 compliant date.', 'woocommerce' ),
|
||||||
|
'type' => 'string',
|
||||||
|
'format' => 'date-time',
|
||||||
|
'validate_callback' => 'rest_validate_request_arg',
|
||||||
|
);
|
||||||
|
$params['before'] = array(
|
||||||
|
'description' => __( 'Limit response to resources published before a given ISO8601 compliant date.', 'woocommerce' ),
|
||||||
|
'type' => 'string',
|
||||||
|
'format' => 'date-time',
|
||||||
|
'validate_callback' => 'rest_validate_request_arg',
|
||||||
|
);
|
||||||
|
$params['order'] = array(
|
||||||
|
'description' => __( 'Order sort attribute ascending or descending.', 'woocommerce' ),
|
||||||
|
'type' => 'string',
|
||||||
|
'default' => 'desc',
|
||||||
|
'enum' => array( 'asc', 'desc' ),
|
||||||
|
'validate_callback' => 'rest_validate_request_arg',
|
||||||
|
);
|
||||||
|
$params['orderby'] = array(
|
||||||
|
'description' => __( 'Sort collection by object attribute.', 'woocommerce' ),
|
||||||
|
'type' => 'string',
|
||||||
|
'default' => 'date',
|
||||||
|
'enum' => array(
|
||||||
|
'date',
|
||||||
|
'net_revenue',
|
||||||
|
'orders_count',
|
||||||
|
'avg_order_value',
|
||||||
|
),
|
||||||
|
'validate_callback' => 'rest_validate_request_arg',
|
||||||
|
);
|
||||||
|
$params['interval'] = array(
|
||||||
|
'description' => __( 'Time interval to use for buckets in the returned data.', 'woocommerce' ),
|
||||||
|
'type' => 'string',
|
||||||
|
'default' => 'week',
|
||||||
|
'enum' => array(
|
||||||
|
'hour',
|
||||||
|
'day',
|
||||||
|
'week',
|
||||||
|
'month',
|
||||||
|
'quarter',
|
||||||
|
'year',
|
||||||
|
),
|
||||||
|
'validate_callback' => 'rest_validate_request_arg',
|
||||||
|
);
|
||||||
|
$params['categories'] = array(
|
||||||
|
'description' => __( 'Limit result set to all items that have the specified term assigned in the categories taxonomy.', 'woocommerce' ),
|
||||||
|
'type' => 'string',
|
||||||
|
'sanitize_callback' => 'wp_parse_id_list',
|
||||||
|
'validate_callback' => 'rest_validate_request_arg',
|
||||||
|
);
|
||||||
|
$params['coupons'] = array(
|
||||||
|
'description' => __( 'Limit result set to all items that have the specified coupon assigned.', 'woocommerce' ),
|
||||||
|
'type' => 'string',
|
||||||
|
'sanitize_callback' => 'wp_parse_id_list',
|
||||||
|
'validate_callback' => 'rest_validate_request_arg',
|
||||||
|
);
|
||||||
|
$params['products'] = array(
|
||||||
|
'description' => __( 'Limit result set to all items that have the specified product assigned.', 'woocommerce' ),
|
||||||
|
'type' => 'string',
|
||||||
|
'sanitize_callback' => 'wp_parse_id_list',
|
||||||
|
'validate_callback' => 'rest_validate_request_arg',
|
||||||
|
);
|
||||||
|
$params['order_status'] = array(
|
||||||
|
'default' => array( 'completed', 'processing', 'on-hold' ),
|
||||||
|
'description' => __( 'Limit result set to orders assigned one or more statuses', 'woocommerce' ),
|
||||||
|
'type' => 'array',
|
||||||
|
'sanitize_callback' => 'wp_parse_slug_list',
|
||||||
|
'validate_callback' => 'rest_validate_request_arg',
|
||||||
|
'items' => array(
|
||||||
|
'enum' => $this->get_order_statuses(),
|
||||||
|
'type' => 'string',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
return $params;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get order statuses without prefixes.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
protected function get_order_statuses() {
|
||||||
|
$order_statuses = array();
|
||||||
|
|
||||||
|
foreach ( array_keys( wc_get_order_statuses() ) as $status ) {
|
||||||
|
$order_statuses[] = str_replace( 'wc-', '', $status );
|
||||||
|
}
|
||||||
|
|
||||||
|
return $order_statuses;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,256 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* REST API Reports products controller
|
||||||
|
*
|
||||||
|
* Handles requests to the /reports/products endpoint.
|
||||||
|
*
|
||||||
|
* @package WooCommerce/API
|
||||||
|
* @since 3.5.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
defined( 'ABSPATH' ) || exit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* REST API Reports products controller class.
|
||||||
|
*
|
||||||
|
* @package WooCommerce/API
|
||||||
|
* @extends WC_REST_Reports_Controller
|
||||||
|
*/
|
||||||
|
class WC_REST_Reports_Products_Controller extends WC_REST_Reports_Controller {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Endpoint namespace.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $namespace = 'wc/v3';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Route base.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $rest_base = 'reports/products';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get items.
|
||||||
|
*
|
||||||
|
* @param WP_REST_Request $request Request data.
|
||||||
|
*
|
||||||
|
* @return array|WP_Error
|
||||||
|
*/
|
||||||
|
public function get_items( $request ) {
|
||||||
|
$args = array();
|
||||||
|
$registered = array_keys( $this->get_collection_params() );
|
||||||
|
foreach ( $registered as $param_name ) {
|
||||||
|
if ( isset( $request[ $param_name ] ) ) {
|
||||||
|
$args[ $param_name ] = $request[ $param_name ];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$reports = new WC_Reports_Products_Query( $args );
|
||||||
|
$products_data = $reports->get_data();
|
||||||
|
|
||||||
|
$data = array();
|
||||||
|
|
||||||
|
foreach ( $products_data->data as $product_data ) {
|
||||||
|
$item = $this->prepare_item_for_response( $product_data, $request );
|
||||||
|
$data[] = $this->prepare_response_for_collection( $item );
|
||||||
|
}
|
||||||
|
|
||||||
|
$response = rest_ensure_response( $data );
|
||||||
|
$response->header( 'X-WP-Total', (int) $products_data->total );
|
||||||
|
$response->header( 'X-WP-TotalPages', (int) $products_data->pages );
|
||||||
|
|
||||||
|
$page = $products_data->page_no;
|
||||||
|
$max_pages = $products_data->pages;
|
||||||
|
$base = add_query_arg( $request->get_query_params(), rest_url( sprintf( '/%s/%s', $this->namespace, $this->rest_base ) ) );
|
||||||
|
if ( $page > 1 ) {
|
||||||
|
$prev_page = $page - 1;
|
||||||
|
if ( $prev_page > $max_pages ) {
|
||||||
|
$prev_page = $max_pages;
|
||||||
|
}
|
||||||
|
$prev_link = add_query_arg( 'page', $prev_page, $base );
|
||||||
|
$response->link_header( 'prev', $prev_link );
|
||||||
|
}
|
||||||
|
if ( $max_pages > $page ) {
|
||||||
|
$next_page = $page + 1;
|
||||||
|
$next_link = add_query_arg( 'page', $next_page, $base );
|
||||||
|
$response->link_header( 'next', $next_link );
|
||||||
|
}
|
||||||
|
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepare a report object for serialization.
|
||||||
|
*
|
||||||
|
* @param Array $report Report data.
|
||||||
|
* @param WP_REST_Request $request Request object.
|
||||||
|
* @return WP_REST_Response
|
||||||
|
*/
|
||||||
|
public function prepare_item_for_response( $report, $request ) {
|
||||||
|
$data = $report;
|
||||||
|
|
||||||
|
$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
|
||||||
|
$data = $this->add_additional_fields_to_object( $data, $request );
|
||||||
|
$data = $this->filter_response_by_context( $data, $context );
|
||||||
|
|
||||||
|
// Wrap the data in a response object.
|
||||||
|
$response = rest_ensure_response( $data );
|
||||||
|
$response->add_links( $this->prepare_links( $report ) );
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter a report returned from the API.
|
||||||
|
*
|
||||||
|
* Allows modification of the report data right before it is returned.
|
||||||
|
*
|
||||||
|
* @param WP_REST_Response $response The response object.
|
||||||
|
* @param object $report The original report object.
|
||||||
|
* @param WP_REST_Request $request Request used to generate the response.
|
||||||
|
*/
|
||||||
|
return apply_filters( 'woocommerce_rest_prepare_report_products', $response, $report, $request );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepare links for the request.
|
||||||
|
*
|
||||||
|
* @param Array $object Object data.
|
||||||
|
* @return array Links for the given post.
|
||||||
|
*/
|
||||||
|
protected function prepare_links( $object ) {
|
||||||
|
$links = array(
|
||||||
|
'product' => array(
|
||||||
|
'href' => rest_url( sprintf( '/%s/%s/%d', $this->namespace, 'products', $object['product_id'] ) ),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
return $links;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the Report's schema, conforming to JSON Schema.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function get_item_schema() {
|
||||||
|
$schema = array(
|
||||||
|
'$schema' => 'http://json-schema.org/draft-04/schema#',
|
||||||
|
'title' => 'report_products',
|
||||||
|
'type' => 'object',
|
||||||
|
'properties' => array(
|
||||||
|
'product_id' => array(
|
||||||
|
'type' => 'integer',
|
||||||
|
'readonly' => true,
|
||||||
|
'context' => array( 'view', 'edit' ),
|
||||||
|
'description' => __( 'Product ID.', 'woocommerce' ),
|
||||||
|
),
|
||||||
|
'items_sold' => array(
|
||||||
|
'type' => 'integer',
|
||||||
|
'readonly' => true,
|
||||||
|
'context' => array( 'view', 'edit' ),
|
||||||
|
'description' => __( 'Number of items sold.', 'woocommerce' ),
|
||||||
|
),
|
||||||
|
'gross_revenue' => array(
|
||||||
|
'type' => 'number',
|
||||||
|
'readonly' => true,
|
||||||
|
'context' => array( 'view', 'edit' ),
|
||||||
|
'description' => __( 'Total gross revenue of all items sold.', 'woocommerce' ),
|
||||||
|
),
|
||||||
|
'orders_count' => array(
|
||||||
|
'type' => 'integer',
|
||||||
|
'readonly' => true,
|
||||||
|
'context' => array( 'view', 'edit' ),
|
||||||
|
'description' => __( 'Number of orders product appeared in.', 'woocommerce' ),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
return $this->add_additional_fields_schema( $schema );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the query params for collections.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function get_collection_params() {
|
||||||
|
$params = array();
|
||||||
|
$params['context'] = $this->get_context_param( array( 'default' => 'view' ) );
|
||||||
|
$params['page'] = array(
|
||||||
|
'description' => __( 'Current page of the collection.', 'woocommerce' ),
|
||||||
|
'type' => 'integer',
|
||||||
|
'default' => 1,
|
||||||
|
'sanitize_callback' => 'absint',
|
||||||
|
'validate_callback' => 'rest_validate_request_arg',
|
||||||
|
'minimum' => 1,
|
||||||
|
);
|
||||||
|
$params['per_page'] = array(
|
||||||
|
'description' => __( 'Maximum number of items to be returned in result set.', 'woocommerce' ),
|
||||||
|
'type' => 'integer',
|
||||||
|
'default' => 10,
|
||||||
|
'minimum' => 1,
|
||||||
|
'maximum' => 100,
|
||||||
|
'sanitize_callback' => 'absint',
|
||||||
|
'validate_callback' => 'rest_validate_request_arg',
|
||||||
|
);
|
||||||
|
$params['after'] = array(
|
||||||
|
'description' => __( 'Limit response to resources published after a given ISO8601 compliant date.', 'woocommerce' ),
|
||||||
|
'type' => 'string',
|
||||||
|
'format' => 'date-time',
|
||||||
|
'validate_callback' => 'rest_validate_request_arg',
|
||||||
|
);
|
||||||
|
$params['before'] = array(
|
||||||
|
'description' => __( 'Limit response to resources published before a given ISO8601 compliant date.', 'woocommerce' ),
|
||||||
|
'type' => 'string',
|
||||||
|
'format' => 'date-time',
|
||||||
|
'validate_callback' => 'rest_validate_request_arg',
|
||||||
|
);
|
||||||
|
$params['order'] = array(
|
||||||
|
'description' => __( 'Order sort attribute ascending or descending.', 'woocommerce' ),
|
||||||
|
'type' => 'string',
|
||||||
|
'default' => 'desc',
|
||||||
|
'enum' => array( 'asc', 'desc' ),
|
||||||
|
'validate_callback' => 'rest_validate_request_arg',
|
||||||
|
);
|
||||||
|
$params['orderby'] = array(
|
||||||
|
'description' => __( 'Sort collection by object attribute.', 'woocommerce' ),
|
||||||
|
'type' => 'string',
|
||||||
|
'default' => 'date',
|
||||||
|
'enum' => array(
|
||||||
|
'date',
|
||||||
|
'gross_revenue',
|
||||||
|
'orders_count',
|
||||||
|
'items_sold',
|
||||||
|
),
|
||||||
|
'validate_callback' => 'rest_validate_request_arg',
|
||||||
|
);
|
||||||
|
$params['categories'] = array(
|
||||||
|
'description' => __( 'Limit result to items from the specified categories.', 'woocommerce' ),
|
||||||
|
'type' => 'array',
|
||||||
|
'sanitize_callback' => 'wp_parse_id_list',
|
||||||
|
'validate_callback' => 'rest_validate_request_arg',
|
||||||
|
'items' => array(
|
||||||
|
'type' => 'integer',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
$params['products'] = array(
|
||||||
|
'description' => __( 'Limit result to items with specified product ids.', 'woocommerce' ),
|
||||||
|
'type' => 'array',
|
||||||
|
'sanitize_callback' => 'wp_parse_id_list',
|
||||||
|
'validate_callback' => 'rest_validate_request_arg',
|
||||||
|
'items' => array(
|
||||||
|
'type' => 'integer',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
$params['extended_product_info'] = array(
|
||||||
|
'description' => __( 'Add additional piece of info about each product to the report.', 'woocommerce' ),
|
||||||
|
'type' => 'boolean',
|
||||||
|
'default' => false,
|
||||||
|
'sanitize_callback' => 'wc_string_to_bool',
|
||||||
|
'validate_callback' => 'rest_validate_request_arg',
|
||||||
|
);
|
||||||
|
|
||||||
|
return $params;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,313 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* REST API Reports products stats controller
|
||||||
|
*
|
||||||
|
* Handles requests to the /reports/products/stats endpoint.
|
||||||
|
*
|
||||||
|
* @package WooCommerce/API
|
||||||
|
* @since 3.5.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
defined( 'ABSPATH' ) || exit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* REST API Reports products stats controller class.
|
||||||
|
*
|
||||||
|
* @package WooCommerce/API
|
||||||
|
* @extends WC_REST_Reports_Controller
|
||||||
|
*/
|
||||||
|
class WC_REST_Reports_Products_Stats_Controller extends WC_REST_Reports_Controller {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Endpoint namespace.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $namespace = 'wc/v3';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Route base.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $rest_base = 'reports/products/stats';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all reports.
|
||||||
|
*
|
||||||
|
* @param WP_REST_Request $request Request data.
|
||||||
|
* @return array|WP_Error
|
||||||
|
*/
|
||||||
|
public function get_items( $request ) {
|
||||||
|
$query_args = array(
|
||||||
|
'fields' => array(
|
||||||
|
'items_sold',
|
||||||
|
'gross_revenue',
|
||||||
|
'orders_count',
|
||||||
|
'products_count',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
foreach ( array_keys( $this->get_collection_params() ) as $arg ) {
|
||||||
|
if ( isset( $request[ $arg ] ) ) {
|
||||||
|
$query_args[ $arg ] = $request[ $arg ];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$query = new WC_Reports_Products_Stats_Query( $query_args );
|
||||||
|
$report_data = $query->get_data();
|
||||||
|
|
||||||
|
$out_data = array(
|
||||||
|
'totals' => get_object_vars( $report_data->totals ),
|
||||||
|
'intervals' => array(),
|
||||||
|
);
|
||||||
|
|
||||||
|
foreach ( $report_data->intervals as $interval_data ) {
|
||||||
|
$item = $this->prepare_item_for_response( $interval_data, $request );
|
||||||
|
$out_data['intervals'][] = $this->prepare_response_for_collection( $item );
|
||||||
|
}
|
||||||
|
|
||||||
|
$response = rest_ensure_response( $out_data );
|
||||||
|
$response->header( 'X-WP-Total', (int) $report_data->total );
|
||||||
|
$response->header( 'X-WP-TotalPages', (int) $report_data->pages );
|
||||||
|
|
||||||
|
$page = $report_data->page_no;
|
||||||
|
$max_pages = $report_data->pages;
|
||||||
|
$base = add_query_arg( $request->get_query_params(), rest_url( sprintf( '/%s/%s', $this->namespace, $this->rest_base ) ) );
|
||||||
|
if ( $page > 1 ) {
|
||||||
|
$prev_page = $page - 1;
|
||||||
|
if ( $prev_page > $max_pages ) {
|
||||||
|
$prev_page = $max_pages;
|
||||||
|
}
|
||||||
|
$prev_link = add_query_arg( 'page', $prev_page, $base );
|
||||||
|
$response->link_header( 'prev', $prev_link );
|
||||||
|
}
|
||||||
|
if ( $max_pages > $page ) {
|
||||||
|
$next_page = $page + 1;
|
||||||
|
$next_link = add_query_arg( 'page', $next_page, $base );
|
||||||
|
$response->link_header( 'next', $next_link );
|
||||||
|
}
|
||||||
|
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepare a report object for serialization.
|
||||||
|
*
|
||||||
|
* @param Array $report Report data.
|
||||||
|
* @param WP_REST_Request $request Request object.
|
||||||
|
* @return WP_REST_Response
|
||||||
|
*/
|
||||||
|
public function prepare_item_for_response( $report, $request ) {
|
||||||
|
$data = $report;
|
||||||
|
|
||||||
|
$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
|
||||||
|
$data = $this->add_additional_fields_to_object( $data, $request );
|
||||||
|
$data = $this->filter_response_by_context( $data, $context );
|
||||||
|
|
||||||
|
// Wrap the data in a response object.
|
||||||
|
$response = rest_ensure_response( $data );
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter a report returned from the API.
|
||||||
|
*
|
||||||
|
* Allows modification of the report data right before it is returned.
|
||||||
|
*
|
||||||
|
* @param WP_REST_Response $response The response object.
|
||||||
|
* @param object $report The original report object.
|
||||||
|
* @param WP_REST_Request $request Request used to generate the response.
|
||||||
|
*/
|
||||||
|
return apply_filters( 'woocommerce_rest_prepare_report_products_stats', $response, $report, $request );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the Report's schema, conforming to JSON Schema.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function get_item_schema() {
|
||||||
|
$totals = array(
|
||||||
|
'items_sold' => array(
|
||||||
|
'description' => __( 'Number of items sold.', 'woocommerce' ),
|
||||||
|
'type' => 'integer',
|
||||||
|
'context' => array( 'view', 'edit' ),
|
||||||
|
'readonly' => true,
|
||||||
|
),
|
||||||
|
'gross_revenue' => array(
|
||||||
|
'description' => __( 'Gross revenue.', 'woocommerce' ),
|
||||||
|
'type' => 'number',
|
||||||
|
'context' => array( 'view', 'edit' ),
|
||||||
|
'readonly' => true,
|
||||||
|
),
|
||||||
|
'orders_count' => array(
|
||||||
|
'description' => __( 'Number of orders.', 'woocommerce' ),
|
||||||
|
'type' => 'integer',
|
||||||
|
'context' => array( 'view', 'edit' ),
|
||||||
|
'readonly' => true,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
$schema = array(
|
||||||
|
'$schema' => 'http://json-schema.org/draft-04/schema#',
|
||||||
|
'title' => 'report_products_stats',
|
||||||
|
'type' => 'object',
|
||||||
|
'properties' => array(
|
||||||
|
'totals' => array(
|
||||||
|
'description' => __( 'Totals data.', 'woocommerce' ),
|
||||||
|
'type' => 'object',
|
||||||
|
'context' => array( 'view', 'edit' ),
|
||||||
|
'readonly' => true,
|
||||||
|
'properties' => $totals,
|
||||||
|
),
|
||||||
|
'intervals' => array(
|
||||||
|
'description' => __( 'Reports data grouped by intervals.', 'woocommerce' ),
|
||||||
|
'type' => 'array',
|
||||||
|
'context' => array( 'view', 'edit' ),
|
||||||
|
'readonly' => true,
|
||||||
|
'items' => array(
|
||||||
|
'type' => 'object',
|
||||||
|
'properties' => array(
|
||||||
|
'interval' => array(
|
||||||
|
'description' => __( 'Type of interval.', 'woocommerce' ),
|
||||||
|
'type' => 'string',
|
||||||
|
'context' => array( 'view', 'edit' ),
|
||||||
|
'readonly' => true,
|
||||||
|
'enum' => array( 'day', 'week', 'month', 'year' ),
|
||||||
|
),
|
||||||
|
'date_start' => array(
|
||||||
|
'description' => __( "The date the report start, in the site's timezone.", 'woocommerce' ),
|
||||||
|
'type' => 'date-time',
|
||||||
|
'context' => array( 'view', 'edit' ),
|
||||||
|
'readonly' => true,
|
||||||
|
),
|
||||||
|
'date_start_gmt' => array(
|
||||||
|
'description' => __( 'The date the report start, as GMT.', 'woocommerce' ),
|
||||||
|
'type' => 'date-time',
|
||||||
|
'context' => array( 'view', 'edit' ),
|
||||||
|
'readonly' => true,
|
||||||
|
),
|
||||||
|
'date_end' => array(
|
||||||
|
'description' => __( "The date the report end, in the site's timezone.", 'woocommerce' ),
|
||||||
|
'type' => 'date-time',
|
||||||
|
'context' => array( 'view', 'edit' ),
|
||||||
|
'readonly' => true,
|
||||||
|
),
|
||||||
|
'date_end_gmt' => array(
|
||||||
|
'description' => __( 'The date the report end, as GMT.', 'woocommerce' ),
|
||||||
|
'type' => 'date-time',
|
||||||
|
'context' => array( 'view', 'edit' ),
|
||||||
|
'readonly' => true,
|
||||||
|
),
|
||||||
|
'subtotals' => array(
|
||||||
|
'description' => __( 'Interval subtotals.', 'woocommerce' ),
|
||||||
|
'type' => 'object',
|
||||||
|
'context' => array( 'view', 'edit' ),
|
||||||
|
'readonly' => true,
|
||||||
|
'properties' => $totals,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
return $this->add_additional_fields_schema( $schema );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the query params for collections.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function get_collection_params() {
|
||||||
|
$params = array();
|
||||||
|
$params['context'] = $this->get_context_param( array( 'default' => 'view' ) );
|
||||||
|
$params['page'] = array(
|
||||||
|
'description' => __( 'Current page of the collection.', 'woocommerce' ),
|
||||||
|
'type' => 'integer',
|
||||||
|
'default' => 1,
|
||||||
|
'sanitize_callback' => 'absint',
|
||||||
|
'validate_callback' => 'rest_validate_request_arg',
|
||||||
|
'minimum' => 1,
|
||||||
|
);
|
||||||
|
$params['per_page'] = array(
|
||||||
|
'description' => __( 'Maximum number of items to be returned in result set.', 'woocommerce' ),
|
||||||
|
'type' => 'integer',
|
||||||
|
'default' => 10,
|
||||||
|
'minimum' => 1,
|
||||||
|
'maximum' => 100,
|
||||||
|
'sanitize_callback' => 'absint',
|
||||||
|
'validate_callback' => 'rest_validate_request_arg',
|
||||||
|
);
|
||||||
|
$params['after'] = array(
|
||||||
|
'description' => __( 'Limit response to resources published after a given ISO8601 compliant date.', 'woocommerce' ),
|
||||||
|
'type' => 'string',
|
||||||
|
'format' => 'date-time',
|
||||||
|
'validate_callback' => 'rest_validate_request_arg',
|
||||||
|
);
|
||||||
|
$params['before'] = array(
|
||||||
|
'description' => __( 'Limit response to resources published before a given ISO8601 compliant date.', 'woocommerce' ),
|
||||||
|
'type' => 'string',
|
||||||
|
'format' => 'date-time',
|
||||||
|
'validate_callback' => 'rest_validate_request_arg',
|
||||||
|
);
|
||||||
|
$params['order'] = array(
|
||||||
|
'description' => __( 'Order sort attribute ascending or descending.', 'woocommerce' ),
|
||||||
|
'type' => 'string',
|
||||||
|
'default' => 'desc',
|
||||||
|
'enum' => array( 'asc', 'desc' ),
|
||||||
|
'validate_callback' => 'rest_validate_request_arg',
|
||||||
|
);
|
||||||
|
$params['orderby'] = array(
|
||||||
|
'description' => __( 'Sort collection by object attribute.', 'woocommerce' ),
|
||||||
|
'type' => 'string',
|
||||||
|
'default' => 'date',
|
||||||
|
'enum' => array(
|
||||||
|
'date',
|
||||||
|
'gross_revenue',
|
||||||
|
'coupons',
|
||||||
|
'refunds',
|
||||||
|
'shipping',
|
||||||
|
'taxes',
|
||||||
|
'net_revenue',
|
||||||
|
'orders_count',
|
||||||
|
'items_sold',
|
||||||
|
),
|
||||||
|
'validate_callback' => 'rest_validate_request_arg',
|
||||||
|
);
|
||||||
|
$params['interval'] = array(
|
||||||
|
'description' => __( 'Time interval to use for buckets in the returned data.', 'woocommerce' ),
|
||||||
|
'type' => 'string',
|
||||||
|
'default' => 'week',
|
||||||
|
'enum' => array(
|
||||||
|
'hour',
|
||||||
|
'day',
|
||||||
|
'week',
|
||||||
|
'month',
|
||||||
|
'quarter',
|
||||||
|
'year',
|
||||||
|
),
|
||||||
|
'validate_callback' => 'rest_validate_request_arg',
|
||||||
|
);
|
||||||
|
$params['categories'] = array(
|
||||||
|
'description' => __( 'Limit result to items from the specified categories.', 'woocommerce' ),
|
||||||
|
'type' => 'array',
|
||||||
|
'sanitize_callback' => 'wp_parse_id_list',
|
||||||
|
'validate_callback' => 'rest_validate_request_arg',
|
||||||
|
'items' => array(
|
||||||
|
'type' => 'integer',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
$params['products'] = array(
|
||||||
|
'description' => __( 'Limit result to items with specified product ids.', 'woocommerce' ),
|
||||||
|
'type' => 'array',
|
||||||
|
'sanitize_callback' => 'wp_parse_id_list',
|
||||||
|
'validate_callback' => 'rest_validate_request_arg',
|
||||||
|
'items' => array(
|
||||||
|
'type' => 'integer',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
return $params;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,324 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* REST API Reports revenue stats controller
|
||||||
|
*
|
||||||
|
* Handles requests to the /reports/revenue/stats endpoint.
|
||||||
|
*
|
||||||
|
* @package WooCommerce/API
|
||||||
|
* @since 3.5.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
defined( 'ABSPATH' ) || exit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* REST API Reports revenue stats controller class.
|
||||||
|
*
|
||||||
|
* @package WooCommerce/API
|
||||||
|
* @extends WC_REST_Reports_Controller
|
||||||
|
*/
|
||||||
|
class WC_REST_Reports_Revenue_Stats_Controller extends WC_REST_Reports_Controller {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Endpoint namespace.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $namespace = 'wc/v3';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Route base.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $rest_base = 'reports/revenue/stats';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps query arguments from the REST request.
|
||||||
|
*
|
||||||
|
* @param array $request Request array.
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
protected function prepare_reports_query( $request ) {
|
||||||
|
$args = array();
|
||||||
|
$args['before'] = $request['before'];
|
||||||
|
$args['after'] = $request['after'];
|
||||||
|
$args['interval'] = $request['interval'];
|
||||||
|
$args['page'] = $request['page'];
|
||||||
|
$args['per_page'] = $request['per_page'];
|
||||||
|
$args['orderby'] = $request['orderby'];
|
||||||
|
$args['order'] = $request['order'];
|
||||||
|
|
||||||
|
return $args;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all reports.
|
||||||
|
*
|
||||||
|
* @param WP_REST_Request $request Request data.
|
||||||
|
* @return array|WP_Error
|
||||||
|
*/
|
||||||
|
public function get_items( $request ) {
|
||||||
|
$query_args = $this->prepare_reports_query( $request );
|
||||||
|
$reports_revenue = new WC_Reports_Revenue_Query( $query_args );
|
||||||
|
$report_data = $reports_revenue->get_data();
|
||||||
|
|
||||||
|
$out_data = array(
|
||||||
|
'totals' => get_object_vars( $report_data->totals ),
|
||||||
|
'intervals' => array(),
|
||||||
|
);
|
||||||
|
|
||||||
|
foreach ( $report_data->intervals as $interval_data ) {
|
||||||
|
$item = $this->prepare_item_for_response( $interval_data, $request );
|
||||||
|
$out_data['intervals'][] = $this->prepare_response_for_collection( $item );
|
||||||
|
}
|
||||||
|
|
||||||
|
$response = rest_ensure_response( $out_data );
|
||||||
|
$response->header( 'X-WP-Total', (int) $report_data->total );
|
||||||
|
$response->header( 'X-WP-TotalPages', (int) $report_data->pages );
|
||||||
|
|
||||||
|
$page = $report_data->page_no;
|
||||||
|
$max_pages = $report_data->pages;
|
||||||
|
$base = add_query_arg( $request->get_query_params(), rest_url( sprintf( '/%s/%s', $this->namespace, $this->rest_base ) ) );
|
||||||
|
if ( $page > 1 ) {
|
||||||
|
$prev_page = $page - 1;
|
||||||
|
if ( $prev_page > $max_pages ) {
|
||||||
|
$prev_page = $max_pages;
|
||||||
|
}
|
||||||
|
$prev_link = add_query_arg( 'page', $prev_page, $base );
|
||||||
|
$response->link_header( 'prev', $prev_link );
|
||||||
|
}
|
||||||
|
if ( $max_pages > $page ) {
|
||||||
|
$next_page = $page + 1;
|
||||||
|
$next_link = add_query_arg( 'page', $next_page, $base );
|
||||||
|
$response->link_header( 'next', $next_link );
|
||||||
|
}
|
||||||
|
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepare a report object for serialization.
|
||||||
|
*
|
||||||
|
* @param Array $report Report data.
|
||||||
|
* @param WP_REST_Request $request Request object.
|
||||||
|
* @return WP_REST_Response
|
||||||
|
*/
|
||||||
|
public function prepare_item_for_response( $report, $request ) {
|
||||||
|
$data = $report;
|
||||||
|
|
||||||
|
$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
|
||||||
|
$data = $this->add_additional_fields_to_object( $data, $request );
|
||||||
|
$data = $this->filter_response_by_context( $data, $context );
|
||||||
|
|
||||||
|
// Wrap the data in a response object.
|
||||||
|
$response = rest_ensure_response( $data );
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter a report returned from the API.
|
||||||
|
*
|
||||||
|
* Allows modification of the report data right before it is returned.
|
||||||
|
*
|
||||||
|
* @param WP_REST_Response $response The response object.
|
||||||
|
* @param object $report The original report object.
|
||||||
|
* @param WP_REST_Request $request Request used to generate the response.
|
||||||
|
*/
|
||||||
|
return apply_filters( 'woocommerce_rest_prepare_report_revenue_stats', $response, $report, $request );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the Report's schema, conforming to JSON Schema.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function get_item_schema() {
|
||||||
|
$totals = array(
|
||||||
|
'gross_revenue' => array(
|
||||||
|
'description' => __( 'Gross revenue.', 'woocommerce' ),
|
||||||
|
'type' => 'number',
|
||||||
|
'context' => array( 'view', 'edit' ),
|
||||||
|
'readonly' => true,
|
||||||
|
),
|
||||||
|
'net_revenue' => array(
|
||||||
|
'description' => __( 'Net revenue.', 'woocommerce' ),
|
||||||
|
'type' => 'number',
|
||||||
|
'context' => array( 'view', 'edit' ),
|
||||||
|
'readonly' => true,
|
||||||
|
),
|
||||||
|
'coupons' => array(
|
||||||
|
'description' => __( 'Total of coupons.', 'woocommerce' ),
|
||||||
|
'type' => 'number',
|
||||||
|
'context' => array( 'view', 'edit' ),
|
||||||
|
'readonly' => true,
|
||||||
|
),
|
||||||
|
'shipping' => array(
|
||||||
|
'description' => __( 'Total of shipping.', 'woocommerce' ),
|
||||||
|
'type' => 'number',
|
||||||
|
'context' => array( 'view', 'edit' ),
|
||||||
|
'readonly' => true,
|
||||||
|
),
|
||||||
|
'taxes' => array(
|
||||||
|
'description' => __( 'Total of taxes.', 'woocommerce' ),
|
||||||
|
'type' => 'number',
|
||||||
|
'context' => array( 'view', 'edit' ),
|
||||||
|
'readonly' => true,
|
||||||
|
),
|
||||||
|
'refunds' => array(
|
||||||
|
'description' => __( 'Total of refunds.', 'woocommerce' ),
|
||||||
|
'type' => 'number',
|
||||||
|
'context' => array( 'view', 'edit' ),
|
||||||
|
'readonly' => true,
|
||||||
|
),
|
||||||
|
'orders_count' => array(
|
||||||
|
'description' => __( 'Amount of orders', 'woocommerce' ),
|
||||||
|
'type' => 'integer',
|
||||||
|
'context' => array( 'view', 'edit' ),
|
||||||
|
'readonly' => true,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
$schema = array(
|
||||||
|
'$schema' => 'http://json-schema.org/draft-04/schema#',
|
||||||
|
'title' => 'report_revenue_stats',
|
||||||
|
'type' => 'object',
|
||||||
|
'properties' => array(
|
||||||
|
'totals' => array(
|
||||||
|
'description' => __( 'Totals data.', 'woocommerce' ),
|
||||||
|
'type' => 'object',
|
||||||
|
'context' => array( 'view', 'edit' ),
|
||||||
|
'readonly' => true,
|
||||||
|
'properties' => $totals,
|
||||||
|
),
|
||||||
|
'intervals' => array(
|
||||||
|
'description' => __( 'Reports data grouped by intervals.', 'woocommerce' ),
|
||||||
|
'type' => 'array',
|
||||||
|
'context' => array( 'view', 'edit' ),
|
||||||
|
'readonly' => true,
|
||||||
|
'items' => array(
|
||||||
|
'type' => 'object',
|
||||||
|
'properties' => array(
|
||||||
|
'interval' => array(
|
||||||
|
'description' => __( 'Type of interval.', 'woocommerce' ),
|
||||||
|
'type' => 'string',
|
||||||
|
'context' => array( 'view', 'edit' ),
|
||||||
|
'readonly' => true,
|
||||||
|
'enum' => array( 'day', 'week', 'month', 'year' ),
|
||||||
|
),
|
||||||
|
'date_start' => array(
|
||||||
|
'description' => __( "The date the report start, in the site's timezone.", 'woocommerce' ),
|
||||||
|
'type' => 'date-time',
|
||||||
|
'context' => array( 'view', 'edit' ),
|
||||||
|
'readonly' => true,
|
||||||
|
),
|
||||||
|
'date_start_gmt' => array(
|
||||||
|
'description' => __( 'The date the report start, as GMT.', 'woocommerce' ),
|
||||||
|
'type' => 'date-time',
|
||||||
|
'context' => array( 'view', 'edit' ),
|
||||||
|
'readonly' => true,
|
||||||
|
),
|
||||||
|
'date_end' => array(
|
||||||
|
'description' => __( "The date the report end, in the site's timezone.", 'woocommerce' ),
|
||||||
|
'type' => 'date-time',
|
||||||
|
'context' => array( 'view', 'edit' ),
|
||||||
|
'readonly' => true,
|
||||||
|
),
|
||||||
|
'date_end_gmt' => array(
|
||||||
|
'description' => __( 'The date the report end, as GMT.', 'woocommerce' ),
|
||||||
|
'type' => 'date-time',
|
||||||
|
'context' => array( 'view', 'edit' ),
|
||||||
|
'readonly' => true,
|
||||||
|
),
|
||||||
|
'subtotals' => array(
|
||||||
|
'description' => __( 'Interval subtotals.', 'woocommerce' ),
|
||||||
|
'type' => 'object',
|
||||||
|
'context' => array( 'view', 'edit' ),
|
||||||
|
'readonly' => true,
|
||||||
|
'properties' => $totals,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
return $this->add_additional_fields_schema( $schema );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the query params for collections.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function get_collection_params() {
|
||||||
|
$params = array();
|
||||||
|
$params['context'] = $this->get_context_param( array( 'default' => 'view' ) );
|
||||||
|
$params['page'] = array(
|
||||||
|
'description' => __( 'Current page of the collection.', 'woocommerce' ),
|
||||||
|
'type' => 'integer',
|
||||||
|
'default' => 1,
|
||||||
|
'sanitize_callback' => 'absint',
|
||||||
|
'validate_callback' => 'rest_validate_request_arg',
|
||||||
|
'minimum' => 1,
|
||||||
|
);
|
||||||
|
$params['per_page'] = array(
|
||||||
|
'description' => __( 'Maximum number of items to be returned in result set.', 'woocommerce' ),
|
||||||
|
'type' => 'integer',
|
||||||
|
'default' => 10,
|
||||||
|
'minimum' => 1,
|
||||||
|
'maximum' => 100,
|
||||||
|
'sanitize_callback' => 'absint',
|
||||||
|
'validate_callback' => 'rest_validate_request_arg',
|
||||||
|
);
|
||||||
|
$params['after'] = array(
|
||||||
|
'description' => __( 'Limit response to resources published after a given ISO8601 compliant date.', 'woocommerce' ),
|
||||||
|
'type' => 'string',
|
||||||
|
'format' => 'date-time',
|
||||||
|
'validate_callback' => 'rest_validate_request_arg',
|
||||||
|
);
|
||||||
|
$params['before'] = array(
|
||||||
|
'description' => __( 'Limit response to resources published before a given ISO8601 compliant date.', 'woocommerce' ),
|
||||||
|
'type' => 'string',
|
||||||
|
'format' => 'date-time',
|
||||||
|
'validate_callback' => 'rest_validate_request_arg',
|
||||||
|
);
|
||||||
|
$params['order'] = array(
|
||||||
|
'description' => __( 'Order sort attribute ascending or descending.', 'woocommerce' ),
|
||||||
|
'type' => 'string',
|
||||||
|
'default' => 'desc',
|
||||||
|
'enum' => array( 'asc', 'desc' ),
|
||||||
|
'validate_callback' => 'rest_validate_request_arg',
|
||||||
|
);
|
||||||
|
$params['orderby'] = array(
|
||||||
|
'description' => __( 'Sort collection by object attribute.', 'woocommerce' ),
|
||||||
|
'type' => 'string',
|
||||||
|
'default' => 'date',
|
||||||
|
'enum' => array(
|
||||||
|
'date',
|
||||||
|
'gross_revenue',
|
||||||
|
'coupons',
|
||||||
|
'refunds',
|
||||||
|
'shipping',
|
||||||
|
'taxes',
|
||||||
|
'net_revenue',
|
||||||
|
'orders_count',
|
||||||
|
'items_sold',
|
||||||
|
),
|
||||||
|
'validate_callback' => 'rest_validate_request_arg',
|
||||||
|
);
|
||||||
|
$params['interval'] = array(
|
||||||
|
'description' => __( 'Time interval to use for buckets in the returned data.', 'woocommerce' ),
|
||||||
|
'type' => 'string',
|
||||||
|
'default' => 'week',
|
||||||
|
'enum' => array(
|
||||||
|
'hour',
|
||||||
|
'day',
|
||||||
|
'week',
|
||||||
|
'month',
|
||||||
|
'quarter',
|
||||||
|
'year',
|
||||||
|
),
|
||||||
|
'validate_callback' => 'rest_validate_request_arg',
|
||||||
|
);
|
||||||
|
|
||||||
|
return $params;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* REST API Reports taxes controller
|
||||||
|
*
|
||||||
|
* Handles requests to the /reports/taxes endpoint.
|
||||||
|
*
|
||||||
|
* @package WooCommerce/API
|
||||||
|
* @since 3.5.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
defined( 'ABSPATH' ) || exit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* REST API Reports taxes controller class.
|
||||||
|
*
|
||||||
|
* @package WooCommerce/API
|
||||||
|
* @extends WC_REST_Reports_Controller
|
||||||
|
*/
|
||||||
|
class WC_REST_Reports_Taxes_Controller extends WC_REST_Reports_Controller {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Endpoint namespace.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $namespace = 'wc/v3';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Route base.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $rest_base = 'reports/taxes';
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* REST API Reports taxes stats controller
|
||||||
|
*
|
||||||
|
* Handles requests to the /reports/taxes/stats endpoint.
|
||||||
|
*
|
||||||
|
* @package WooCommerce/API
|
||||||
|
* @since 3.5.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
defined( 'ABSPATH' ) || exit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* REST API Reports taxes stats controller class.
|
||||||
|
*
|
||||||
|
* @package WooCommerce/API
|
||||||
|
* @extends WC_REST_Reports_Controller
|
||||||
|
*/
|
||||||
|
class WC_REST_Reports_Taxes_Stats_Controller extends WC_REST_Reports_Controller {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Endpoint namespace.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $namespace = 'wc/v3';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Route base.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $rest_base = 'reports/taxes/stats';
|
||||||
|
}
|
|
@ -0,0 +1,71 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* REST API WC System Status Tools Controller
|
||||||
|
*
|
||||||
|
* Handles requests to the /system_status/tools/* endpoints.
|
||||||
|
*
|
||||||
|
* @package WooCommerce/API
|
||||||
|
* @since 3.0.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
defined( 'ABSPATH' ) || exit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* System status tools controller.
|
||||||
|
*
|
||||||
|
* @package WooCommerce/API
|
||||||
|
* @extends WC_REST_System_Status_Tools_V2_Controller
|
||||||
|
*/
|
||||||
|
class WC_REST_System_Status_Tools_Controller extends WC_REST_System_Status_Tools_V2_Controller {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Endpoint namespace.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $namespace = 'wc/v3';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A list of available tools for use in the system status section.
|
||||||
|
* 'button' becomes 'action' in the API.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function get_tools() {
|
||||||
|
return array_merge(
|
||||||
|
parent::get_tools(),
|
||||||
|
array(
|
||||||
|
'rebuild_stats' => array(
|
||||||
|
'name' => __( 'Rebuild reports data', 'woocommerce' ),
|
||||||
|
'button' => __( 'Rebuild reports', 'woocommerce' ),
|
||||||
|
'desc' => __( 'This tool will rebuild all of the information used by the reports.', 'woocommerce' ),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Actually executes a tool.
|
||||||
|
*
|
||||||
|
* @param string $tool Tool.
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function execute_tool( $tool ) {
|
||||||
|
$ran = true;
|
||||||
|
$message = '';
|
||||||
|
|
||||||
|
switch ( $tool ) {
|
||||||
|
case 'rebuild_stats':
|
||||||
|
WC_Reports_Orders_Data_Store::queue_order_stats_repopulate_database();
|
||||||
|
$message = __( 'Rebuilding reports data in the background . . .', 'woocommerce' );
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return parent::execute_tool( $tool );
|
||||||
|
}
|
||||||
|
|
||||||
|
return array(
|
||||||
|
'success' => $ran,
|
||||||
|
'message' => $message,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,199 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
|
||||||
|
class WC_Admin_Api_Init {
|
||||||
|
|
||||||
|
public function __construct() {
|
||||||
|
// Initialize classes.
|
||||||
|
add_action( 'plugins_loaded', array( $this, 'init_classes' ) );
|
||||||
|
// Hook in data stores.
|
||||||
|
add_filter( 'woocommerce_data_stores', array( 'WC_Admin_Api_Init', 'add_data_stores' ) );
|
||||||
|
// Add wc-admin report tables to list of WooCommerce tables.
|
||||||
|
add_filter( 'woocommerce_install_get_tables', array( 'WC_Admin_Api_Init', 'add_report_tables' ) );
|
||||||
|
// REST API extensions init.
|
||||||
|
add_action( 'rest_api_init', array( $this, 'rest_api_init' ) );
|
||||||
|
// Initialize report classes.
|
||||||
|
add_action( 'plugins_loaded', array( 'WC_Admin_Api_Init', 'orders_data_store_init' ), 20 );
|
||||||
|
add_action( 'plugins_loaded', array( 'WC_Admin_Api_Init', 'order_product_lookup_store_init' ), 20 );
|
||||||
|
|
||||||
|
// Create tables.
|
||||||
|
$this->create_db_tables();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function init_classes() {
|
||||||
|
// Interfaces.
|
||||||
|
require_once( dirname( __FILE__ ) . '/interfaces/class-wc-admin-reports-data-store-interface.php' );
|
||||||
|
|
||||||
|
// Query classes for reports.
|
||||||
|
require_once( dirname( __FILE__ ) . '/class-wc-admin-reports-revenue-query.php' );
|
||||||
|
require_once( dirname( __FILE__ ) . '/class-wc-admin-reports-orders-stats-query.php' );
|
||||||
|
require_once( dirname( __FILE__ ) . '/class-wc-admin-reports-products-query.php' );
|
||||||
|
require_once( dirname( __FILE__ ) . '/class-wc-admin-reports-products-stats-query.php' );
|
||||||
|
require_once( dirname( __FILE__ ) . '/class-wc-admin-reports-categories-query.php' );
|
||||||
|
|
||||||
|
// Reports data stores.
|
||||||
|
require_once( dirname( __FILE__ ) . '/data-stores/class-wc-admin-reports-data-store.php' );
|
||||||
|
require_once( dirname( __FILE__ ) . '/data-stores/class-wc-admin-reports-orders-data-store.php' );
|
||||||
|
require_once( dirname( __FILE__ ) . '/data-stores/class-wc-admin-reports-products-data-store.php' );
|
||||||
|
require_once( dirname( __FILE__ ) . '/data-stores/class-wc-admin-reports-products-stats-data-store.php' );
|
||||||
|
require_once( dirname( __FILE__ ) . '/data-stores/class-wc-admin-reports-categories-data-store.php' );
|
||||||
|
}
|
||||||
|
|
||||||
|
public function rest_api_init() {
|
||||||
|
require_once( dirname( __FILE__ ) . '/api/class-wc-admin-rest-reports-controller.php' );
|
||||||
|
require_once( dirname( __FILE__ ) . '/api/class-wc-admin-rest-system-status-tools-controller.php' );
|
||||||
|
require_once( dirname( __FILE__ ) . '/api/class-wc-admin-rest-reports-categories-controller.php' );
|
||||||
|
require_once( dirname( __FILE__ ) . '/api/class-wc-admin-rest-reports-coupons-controller.php' );
|
||||||
|
require_once( dirname( __FILE__ ) . '/api/class-wc-admin-rest-reports-coupons-stats-controller.php' );
|
||||||
|
require_once( dirname( __FILE__ ) . '/api/class-wc-admin-rest-reports-customers-controller.php' );
|
||||||
|
require_once( dirname( __FILE__ ) . '/api/class-wc-admin-rest-reports-downloads-controller.php' );
|
||||||
|
require_once( dirname( __FILE__ ) . '/api/class-wc-admin-rest-reports-downloads-files-controller.php' );
|
||||||
|
require_once( dirname( __FILE__ ) . '/api/class-wc-admin-rest-reports-downloads-stats-controller.php' );
|
||||||
|
require_once( dirname( __FILE__ ) . '/api/class-wc-admin-rest-reports-orders-stats-controller.php' );
|
||||||
|
require_once( dirname( __FILE__ ) . '/api/class-wc-admin-rest-reports-products-controller.php' );
|
||||||
|
require_once( dirname( __FILE__ ) . '/api/class-wc-admin-rest-reports-products-stats-controller.php' );
|
||||||
|
require_once( dirname( __FILE__ ) . '/api/class-wc-admin-rest-reports-revenue-stats-controller.php' );
|
||||||
|
require_once( dirname( __FILE__ ) . '/api/class-wc-admin-rest-reports-taxes-controller.php' );
|
||||||
|
require_once( dirname( __FILE__ ) . '/api/class-wc-admin-rest-reports-taxes-stats-controller.php' );
|
||||||
|
|
||||||
|
$controllers = array(
|
||||||
|
'WC_Admin_REST_Reports_Controller',
|
||||||
|
'WC_Admin_REST_System_Status_Tools_Controller',
|
||||||
|
'WC_Admin_REST_Reports_Products_Controller',
|
||||||
|
'WC_Admin_REST_Reports_Products_Stats_Controller',
|
||||||
|
'WC_Admin_REST_Reports_Revenue_Stats_Controller',
|
||||||
|
'WC_Admin_REST_Reports_Orders_Stats_Controller',
|
||||||
|
'WC_Admin_REST_Reports_Categories_Controller',
|
||||||
|
);
|
||||||
|
|
||||||
|
foreach ( $controllers as $controller ) {
|
||||||
|
$this->$controller = new $controller();
|
||||||
|
$this->$controller->register_routes();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function orders_data_store_init() {
|
||||||
|
WC_Admin_Reports_Orders_Data_Store::init();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function order_product_lookup_store_init() {
|
||||||
|
global $wpdb;
|
||||||
|
|
||||||
|
$orders = get_transient( 'wc_update_350_all_orders' );
|
||||||
|
if ( false === $orders ) {
|
||||||
|
$orders = wc_get_orders( array(
|
||||||
|
'limit' => -1,
|
||||||
|
'return' => 'ids',
|
||||||
|
) );
|
||||||
|
set_transient( 'wc_update_350_all_orders', $orders, DAY_IN_SECONDS );
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process orders untill close to running out of memory timeouts on large sites then requeue.
|
||||||
|
foreach ( $orders as $order_id ) {
|
||||||
|
$order = wc_get_order( $order_id );
|
||||||
|
if ( ! $order ) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
foreach ( $order->get_items() as $order_item ) {
|
||||||
|
$wpdb->replace(
|
||||||
|
$wpdb->prefix . 'wc_order_product_lookup',
|
||||||
|
array(
|
||||||
|
'order_item_id' => $order_item->get_id(),
|
||||||
|
'order_id' => $order->get_id(),
|
||||||
|
'product_id' => $order_item->get_product_id( 'edit' ),
|
||||||
|
'customer_id' => ( 0 < $order->get_customer_id( 'edit' ) ) ? $order->get_customer_id( 'edit' ) : null,
|
||||||
|
'product_qty' => $order_item->get_quantity( 'edit' ),
|
||||||
|
'product_gross_revenue' => $order_item->get_subtotal( 'edit' ),
|
||||||
|
'date_created' => date( 'Y-m-d H:i:s', $order->get_date_created( 'edit' )->getTimestamp() ),
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
'%d',
|
||||||
|
'%d',
|
||||||
|
'%d',
|
||||||
|
'%d',
|
||||||
|
'%d',
|
||||||
|
'%f',
|
||||||
|
'%s',
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// Pop the order ID from the array for updating the transient later should we near memory exhaustion.
|
||||||
|
unset( $orders[ $order_id ] );
|
||||||
|
if ( $updater instanceof WC_Background_Updater && $updater->is_memory_exceeded() ) {
|
||||||
|
// Update the transient for the next run to avoid processing the same orders again.
|
||||||
|
set_transient( 'wc_update_350_all_orders', $orders, DAY_IN_SECONDS );
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function add_data_stores( $data_stores ) {
|
||||||
|
return array_merge(
|
||||||
|
$data_stores,
|
||||||
|
array(
|
||||||
|
'report-revenue-stats' => 'WC_Reports_Orders_Data_Store',
|
||||||
|
'report-orders-stats' => 'WC_Reports_Orders_Data_Store',
|
||||||
|
'report-products' => 'WC_Reports_Products_Data_Store',
|
||||||
|
'report-products-stats' => 'WC_Reports_Products_Stats_Data_Store',
|
||||||
|
'report-categories' => 'WC_Reports_Categories_Data_Store',
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function add_report_tables( $wc_tables ) {
|
||||||
|
return array_merge(
|
||||||
|
$wc_tables,
|
||||||
|
array(
|
||||||
|
// TODO: will this work on multisite?
|
||||||
|
"{$wpdb->prefix}wc_admin_order_stats",
|
||||||
|
"{$wpdb->prefix}wc_admin_order_product_lookup",
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function get_schema() {
|
||||||
|
global $wpdb;
|
||||||
|
|
||||||
|
if ( $wpdb->has_cap( 'collation' ) ) {
|
||||||
|
$collate = $wpdb->get_charset_collate();
|
||||||
|
}
|
||||||
|
|
||||||
|
$tables = "
|
||||||
|
CREATE TABLE {$wpdb->prefix}wc_admin_order_stats (
|
||||||
|
order_id bigint(20) unsigned NOT NULL,
|
||||||
|
date_created timestamp DEFAULT '0000-00-00 00:00:00' NOT NULL,
|
||||||
|
num_items_sold int(11) UNSIGNED DEFAULT 0 NOT NULL,
|
||||||
|
gross_total double DEFAULT 0 NOT NULL,
|
||||||
|
coupon_total double DEFAULT 0 NOT NULL,
|
||||||
|
refund_total double DEFAULT 0 NOT NULL,
|
||||||
|
tax_total double DEFAULT 0 NOT NULL,
|
||||||
|
shipping_total double DEFAULT 0 NOT NULL,
|
||||||
|
net_total double DEFAULT 0 NOT NULL,
|
||||||
|
PRIMARY KEY (order_id),
|
||||||
|
KEY date_created (date_created)
|
||||||
|
) $collate;
|
||||||
|
CREATE TABLE {$wpdb->prefix}wc_admin_order_product_lookup (
|
||||||
|
order_item_id BIGINT UNSIGNED NOT NULL,
|
||||||
|
order_id BIGINT UNSIGNED NOT NULL,
|
||||||
|
product_id BIGINT UNSIGNED NOT NULL,
|
||||||
|
customer_id BIGINT UNSIGNED NULL,
|
||||||
|
date_created timestamp DEFAULT '0000-00-00 00:00:00' NOT NULL,
|
||||||
|
product_qty INT UNSIGNED NOT NULL,
|
||||||
|
product_gross_revenue double DEFAULT 0 NOT NULL,
|
||||||
|
PRIMARY KEY (order_item_id),
|
||||||
|
KEY order_id (order_id),
|
||||||
|
KEY product_id (product_id),
|
||||||
|
KEY customer_id (customer_id),
|
||||||
|
KEY date_created (date_created)
|
||||||
|
) $collate;";
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function create_db_tables() {
|
||||||
|
require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
|
||||||
|
|
||||||
|
dbDelta( self::get_schema() );
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
new WC_Admin_Api_Init();
|
|
@ -0,0 +1,77 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Order stats background process.
|
||||||
|
*
|
||||||
|
* @package WooCommerce Admin/Classes
|
||||||
|
*/
|
||||||
|
|
||||||
|
defined( 'ABSPATH' ) || exit;
|
||||||
|
|
||||||
|
if ( ! class_exists( 'WC_Background_Process', false ) ) {
|
||||||
|
include_once WC_ABSPATH . '/includes/abstracts/class-wc-background-process.php';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* WC_Admin_Order_Stats_Background_Process class.
|
||||||
|
*
|
||||||
|
* @todo use Action Scheduler instead of this.
|
||||||
|
*/
|
||||||
|
class WC_Admin_Order_Stats_Background_Process extends WC_Background_Process {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initiate new background process.
|
||||||
|
*/
|
||||||
|
public function __construct() {
|
||||||
|
// Uses unique prefix per blog so each blog has separate queue.
|
||||||
|
$this->prefix = 'wp_' . get_current_blog_id();
|
||||||
|
$this->action = 'wc_order_stats';
|
||||||
|
parent::__construct();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Push to queue without scheduling duplicate recalculation events.
|
||||||
|
* Overrides WC_Background_Process::push_to_queue.
|
||||||
|
*
|
||||||
|
* @param integer $data Timestamp of hour to generate stats.
|
||||||
|
*/
|
||||||
|
public function push_to_queue( $data ) {
|
||||||
|
$data = absint( $data );
|
||||||
|
if ( ! in_array( $data, $this->data, true ) ) {
|
||||||
|
$this->data[] = $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dispatch but only if there is data to update.
|
||||||
|
* Overrides WC_Background_Process::dispatch.
|
||||||
|
*/
|
||||||
|
public function dispatch() {
|
||||||
|
if ( ! $this->data ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return parent::dispatch();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Code to execute for each item in the queue
|
||||||
|
*
|
||||||
|
* @param string $item Queue item to iterate over.
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
protected function task( $item ) {
|
||||||
|
if ( ! $item ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$order = wc_get_order( $item );
|
||||||
|
if ( ! $order ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
WC_Admin_Reports_Orders_Data_Store::update( $order );
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,49 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Class for parameter-based Categories Report querying
|
||||||
|
*
|
||||||
|
* Example usage:
|
||||||
|
* $args = array(
|
||||||
|
* 'before' => '2018-07-19 00:00:00',
|
||||||
|
* 'after' => '2018-07-05 00:00:00',
|
||||||
|
* 'page' => 2,
|
||||||
|
* 'order' => 'desc',
|
||||||
|
* 'orderby' => 'items_sold',
|
||||||
|
* );
|
||||||
|
* $report = new WC_Admin_Reports_Categories_Query( $args );
|
||||||
|
* $mydata = $report->get_data();
|
||||||
|
*
|
||||||
|
* @package WooCommerce Admin/Classes
|
||||||
|
*/
|
||||||
|
|
||||||
|
defined( 'ABSPATH' ) || exit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* WC_Admin_Reports_Categories_Query
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
class WC_Admin_Reports_Categories_Query extends WC_Admin_Reports_Query {
|
||||||
|
|
||||||
|
const REPORT_NAME = 'report-categories';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Valid fields for Categories report.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
protected function get_default_query_vars() {
|
||||||
|
return array();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get categories data based on the current query vars.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function get_data() {
|
||||||
|
$args = apply_filters( 'woocommerce_reports_categories_query_args', $this->get_query_vars() );
|
||||||
|
$results = WC_Data_Store::load( self::REPORT_NAME )->get_data( $args );
|
||||||
|
return apply_filters( 'woocommerce_reports_categories_select_query', $results, $args );
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,413 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Class for time interval handling for reports
|
||||||
|
*
|
||||||
|
* @package WooCommerce/Classes
|
||||||
|
* @version 3.5.0
|
||||||
|
* @since 3.5.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
defined( 'ABSPATH' ) || exit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Date & time interval handling class for Reporting API.
|
||||||
|
*/
|
||||||
|
class WC_Admin_Reports_Interval {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format string for ISO DateTime formatter.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
public static $iso_datetime_format = 'Y-m-d\TH:i:s\Z';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format string for use in SQL queries.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
public static $sql_datetime_format = 'Y-m-d H:i:s';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns date format to be used as grouping clause in SQL.
|
||||||
|
*
|
||||||
|
* @param string $time_interval Time interval.
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public static function mysql_datetime_format( $time_interval ) {
|
||||||
|
$first_day_of_week = absint( get_option( 'start_of_week' ) );
|
||||||
|
|
||||||
|
if ( 1 === $first_day_of_week ) {
|
||||||
|
// Week begins on Monday, ISO 8601.
|
||||||
|
$week_format = "DATE_FORMAT(date_created, '%x-%v')";
|
||||||
|
} else {
|
||||||
|
// Week begins on day other than specified by ISO 8601, needs to be in sync with function simple_week_number.
|
||||||
|
$week_format = "CONCAT(YEAR(date_created), '-', LPAD( FLOOR( ( DAYOFYEAR(date_created) + ( ( DATE_FORMAT(MAKEDATE(YEAR(date_created),1), '%w') - $first_day_of_week + 7 ) % 7 ) - 1 ) / 7 ) + 1 , 2, '0'))";
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
$mysql_date_format_mapping = array(
|
||||||
|
'hour' => "DATE_FORMAT(date_created, '%Y-%m-%d %k')",
|
||||||
|
'day' => "DATE_FORMAT(date_created, '%Y-%m-%d')",
|
||||||
|
'week' => $week_format,
|
||||||
|
'month' => "DATE_FORMAT(date_created, '%Y-%m')",
|
||||||
|
'quarter' => "CONCAT(YEAR(date_created), '-', QUARTER(date_created))",
|
||||||
|
'year' => 'YEAR(date_created)',
|
||||||
|
|
||||||
|
);
|
||||||
|
|
||||||
|
return $mysql_date_format_mapping[ $time_interval ];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns quarter for the DateTime.
|
||||||
|
*
|
||||||
|
* @param DateTime $datetime Date & time.
|
||||||
|
* @return int|null
|
||||||
|
*/
|
||||||
|
public static function quarter( $datetime ) {
|
||||||
|
// TODO: is there a smarter way? Is floor((m - 1)/3) + 1 better?
|
||||||
|
switch ( (int) $datetime->format( 'm' ) ) {
|
||||||
|
case 1:
|
||||||
|
case 2:
|
||||||
|
case 3:
|
||||||
|
return 1;
|
||||||
|
case 4:
|
||||||
|
case 5:
|
||||||
|
case 6:
|
||||||
|
return 2;
|
||||||
|
case 7:
|
||||||
|
case 8:
|
||||||
|
case 9:
|
||||||
|
return 3;
|
||||||
|
case 10:
|
||||||
|
case 11:
|
||||||
|
case 12:
|
||||||
|
return 4;
|
||||||
|
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns simple week number for the DateTime, for week starting on $first_day_of_week.
|
||||||
|
*
|
||||||
|
* The first week of the year is considered to be the week containing January 1.
|
||||||
|
* The second week starts on the next $first_day_of_week.
|
||||||
|
*
|
||||||
|
* @param DateTime $datetime Date for which the week number is to be calculated.
|
||||||
|
* @param int $first_day_of_week 0 for Sunday to 6 for Saturday.
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public static function simple_week_number( $datetime, $first_day_of_week ) {
|
||||||
|
$beg_of_year_day = new DateTime( "{$datetime->format('Y')}-01-01" );
|
||||||
|
$adj_day_beg_of_year = ( (int) $beg_of_year_day->format( 'w' ) - $first_day_of_week + 7 ) % 7;
|
||||||
|
$days_since_start_of_year = (int) $datetime->format( 'z' ) + 1;
|
||||||
|
|
||||||
|
return (int) floor( ( ( $days_since_start_of_year + $adj_day_beg_of_year - 1 ) / 7 ) ) + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns ISO 8601 week number for the DateTime, if week starts on Monday,
|
||||||
|
* otherwise returns simple week number.
|
||||||
|
*
|
||||||
|
* @see WC_Admin_Reports_Interval::simple_week_number()
|
||||||
|
*
|
||||||
|
* @param DateTime $datetime Date for which the week number is to be calculated.
|
||||||
|
* @param int $first_day_of_week 0 for Sunday to 6 for Saturday.
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public static function week_number( $datetime, $first_day_of_week ) {
|
||||||
|
if ( 1 === $first_day_of_week ) {
|
||||||
|
$week_number = (int) $datetime->format( 'W' );
|
||||||
|
} else {
|
||||||
|
$week_number = WC_Admin_Reports_Interval::simple_week_number( $datetime, $first_day_of_week );
|
||||||
|
}
|
||||||
|
return $week_number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns time interval id for the DateTime.
|
||||||
|
*
|
||||||
|
* @param string $time_interval Time interval type (week, day, etc).
|
||||||
|
* @param DateTime $datetime Date & time.
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function time_interval_id( $time_interval, $datetime ) {
|
||||||
|
$php_time_format_for = array(
|
||||||
|
'hour' => 'Y-m-d H',
|
||||||
|
'day' => 'Y-m-d',
|
||||||
|
'week' => 'o-W',
|
||||||
|
'month' => 'Y-m',
|
||||||
|
'quarter' => 'Y-' . WC_Admin_Reports_Interval::quarter( $datetime ),
|
||||||
|
'year' => 'Y',
|
||||||
|
);
|
||||||
|
|
||||||
|
// If the week does not begin on Monday.
|
||||||
|
$first_day_of_week = absint( get_option( 'start_of_week' ) );
|
||||||
|
|
||||||
|
if ( 'week' === $time_interval && 1 !== $first_day_of_week ) {
|
||||||
|
$week_no = WC_Admin_Reports_Interval::simple_week_number( $datetime, $first_day_of_week );
|
||||||
|
$week_no = str_pad( $week_no, 2, '0', STR_PAD_LEFT );
|
||||||
|
$year_no = $datetime->format( 'Y' );
|
||||||
|
return "$year_no-$week_no";
|
||||||
|
}
|
||||||
|
|
||||||
|
return $datetime->format( $php_time_format_for[ $time_interval ] );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates number of time intervals between two dates, closed interval on both sides.
|
||||||
|
*
|
||||||
|
* @param string $start Start date & time.
|
||||||
|
* @param string $end End date & time.
|
||||||
|
* @param string $interval Time interval increment, e.g. hour, day, week.
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public static function intervals_between( $start, $end, $interval ) {
|
||||||
|
$start_datetime = new DateTime( $start );
|
||||||
|
$end_datetime = new DateTime( $end );
|
||||||
|
|
||||||
|
switch ( $interval ) {
|
||||||
|
case 'hour':
|
||||||
|
$diff_timestamp = (int) $end_datetime->format( 'U' ) - (int) $start_datetime->format( 'U' );
|
||||||
|
return (int) floor( ( (int) $diff_timestamp ) / HOUR_IN_SECONDS ) + 1;
|
||||||
|
case 'day':
|
||||||
|
$diff_timestamp = (int) $end_datetime->format( 'U' ) - (int) $start_datetime->format( 'U' );
|
||||||
|
return (int) floor( ( (int) $diff_timestamp ) / DAY_IN_SECONDS ) + 1;
|
||||||
|
case 'week':
|
||||||
|
// TODO: optimize? approximately day count / 7, but year end is tricky, a week can have fewer days.
|
||||||
|
$week_count = 0;
|
||||||
|
do {
|
||||||
|
$start_datetime = WC_Admin_Reports_Interval::next_week_start( $start_datetime );
|
||||||
|
$week_count++;
|
||||||
|
} while ( $start_datetime <= $end_datetime );
|
||||||
|
return $week_count;
|
||||||
|
case 'month':
|
||||||
|
// Year diff in months: (end_year - start_year - 1) * 12.
|
||||||
|
$year_diff_in_months = ( (int) $end_datetime->format( 'Y' ) - (int) $start_datetime->format( 'Y' ) - 1 ) * 12;
|
||||||
|
// All the months in end_date year plus months from X to 12 in the start_date year.
|
||||||
|
$month_diff = (int) $end_datetime->format( 'n' ) + ( 12 - (int) $start_datetime->format( 'n' ) );
|
||||||
|
// Add months for number of years between end_date and start_date.
|
||||||
|
$month_diff += $year_diff_in_months + 1;
|
||||||
|
return $month_diff;
|
||||||
|
case 'quarter':
|
||||||
|
// Year diff in quarters: (end_year - start_year - 1) * 4.
|
||||||
|
$year_diff_in_quarters = ( (int) $end_datetime->format( 'Y' ) - (int) $start_datetime->format( 'Y' ) - 1 ) * 4;
|
||||||
|
// All the quarters in end_date year plus quarters from X to 4 in the start_date year.
|
||||||
|
$quarter_diff = WC_Admin_Reports_Interval::quarter( $end_datetime ) + ( 4 - WC_Admin_Reports_Interval::quarter( $start_datetime ) );
|
||||||
|
// Add quarters for number of years between end_date and start_date.
|
||||||
|
$quarter_diff += $year_diff_in_quarters + 1;
|
||||||
|
return $quarter_diff;
|
||||||
|
case 'year':
|
||||||
|
$year_diff = (int) $end_datetime->format( 'Y' ) - (int) $start_datetime->format( 'Y' );
|
||||||
|
return $year_diff + 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a new DateTime object representing the next hour start/previous hour end if reversed.
|
||||||
|
*
|
||||||
|
* @param DateTime $datetime Date and time.
|
||||||
|
* @param bool $reversed Going backwards in time instead of forward.
|
||||||
|
* @return DateTime
|
||||||
|
*/
|
||||||
|
public static function next_hour_start( $datetime, $reversed = false ) {
|
||||||
|
$hour_increment = $reversed ? 0 : 1;
|
||||||
|
$timestamp = (int) $datetime->format( 'U' );
|
||||||
|
$hours_offset_timestamp = ( floor( $timestamp / HOUR_IN_SECONDS ) + $hour_increment ) * HOUR_IN_SECONDS;
|
||||||
|
|
||||||
|
if ( $reversed ) {
|
||||||
|
$hours_offset_timestamp --;
|
||||||
|
}
|
||||||
|
|
||||||
|
$hours_offset_time = new DateTime();
|
||||||
|
$hours_offset_time->setTimestamp( $hours_offset_timestamp );
|
||||||
|
return $hours_offset_time;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a new DateTime object representing the next day start, or previous day end if reversed.
|
||||||
|
*
|
||||||
|
* @param DateTime $datetime Date and time.
|
||||||
|
* @param bool $reversed Going backwards in time instead of forward.
|
||||||
|
* @return DateTime
|
||||||
|
*/
|
||||||
|
public static function next_day_start( $datetime, $reversed = false ) {
|
||||||
|
$day_increment = $reversed ? -1 : 1;
|
||||||
|
$timestamp = (int) $datetime->format( 'U' );
|
||||||
|
$next_day_timestamp = ( floor( $timestamp / DAY_IN_SECONDS ) + $day_increment ) * DAY_IN_SECONDS;
|
||||||
|
$next_day = new DateTime();
|
||||||
|
$next_day->setTimestamp( $next_day_timestamp );
|
||||||
|
|
||||||
|
// The day boundary is actually next midnight when going in reverse, so set it to day -1 at 23:59:59.
|
||||||
|
if ( $reversed ) {
|
||||||
|
$timestamp = (int) $next_day->format( 'U' );
|
||||||
|
$end_of_day_timestamp = floor( $timestamp / DAY_IN_SECONDS ) * DAY_IN_SECONDS + DAY_IN_SECONDS - 1;
|
||||||
|
$next_day->setTimestamp( $end_of_day_timestamp );
|
||||||
|
}
|
||||||
|
|
||||||
|
return $next_day;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns DateTime object representing the next week start, or previous week end if reversed.
|
||||||
|
*
|
||||||
|
* @param DateTime $datetime Date and time.
|
||||||
|
* @param bool $reversed Going backwards in time instead of forward.
|
||||||
|
* @return DateTime
|
||||||
|
*/
|
||||||
|
public static function next_week_start( $datetime, $reversed = false ) {
|
||||||
|
$first_day_of_week = absint( get_option( 'start_of_week' ) );
|
||||||
|
$initial_week_no = WC_Admin_Reports_Interval::week_number( $datetime, $first_day_of_week );
|
||||||
|
|
||||||
|
do {
|
||||||
|
$datetime = WC_Admin_Reports_Interval::next_day_start( $datetime, $reversed );
|
||||||
|
$current_week_no = WC_Admin_Reports_Interval::week_number( $datetime, $first_day_of_week );
|
||||||
|
} while ( $current_week_no === $initial_week_no );
|
||||||
|
|
||||||
|
// The week boundary is actually next midnight when going in reverse, so set it to day -1 at 23:59:59.
|
||||||
|
if ( $reversed ) {
|
||||||
|
$timestamp = (int) $datetime->format( 'U' );
|
||||||
|
$end_of_day_timestamp = floor( $timestamp / DAY_IN_SECONDS ) * DAY_IN_SECONDS + DAY_IN_SECONDS - 1;
|
||||||
|
$datetime->setTimestamp( $end_of_day_timestamp );
|
||||||
|
}
|
||||||
|
|
||||||
|
return $datetime;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a new DateTime object representing the next month start, or previous month end if reversed.
|
||||||
|
*
|
||||||
|
* @param DateTime $datetime Date and time.
|
||||||
|
* @param bool $reversed Going backwards in time instead of forward.
|
||||||
|
* @return DateTime
|
||||||
|
*/
|
||||||
|
public static function next_month_start( $datetime, $reversed = false ) {
|
||||||
|
$month_increment = 1;
|
||||||
|
$year = $datetime->format( 'Y' );
|
||||||
|
$month = (int) $datetime->format( 'm' );
|
||||||
|
|
||||||
|
if ( $reversed ) {
|
||||||
|
$beg_of_month_datetime = new DateTime( "$year-$month-01 00:00:00" );
|
||||||
|
$timestamp = (int) $beg_of_month_datetime->format( 'U' );
|
||||||
|
$end_of_prev_month_timestamp = $timestamp - 1;
|
||||||
|
$datetime->setTimestamp( $end_of_prev_month_timestamp );
|
||||||
|
} else {
|
||||||
|
$month += $month_increment;
|
||||||
|
if ( $month > 12 ) {
|
||||||
|
$month = 1;
|
||||||
|
$year ++;
|
||||||
|
}
|
||||||
|
$day = '01';
|
||||||
|
$datetime = new DateTime( "$year-$month-$day 00:00:00" );
|
||||||
|
}
|
||||||
|
|
||||||
|
return $datetime;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a new DateTime object representing the next quarter start, or previous quarter end if reversed.
|
||||||
|
*
|
||||||
|
* @param DateTime $datetime Date and time.
|
||||||
|
* @param bool $reversed Going backwards in time instead of forward.
|
||||||
|
* @return DateTime
|
||||||
|
*/
|
||||||
|
public static function next_quarter_start( $datetime, $reversed = false ) {
|
||||||
|
$year = $datetime->format( 'Y' );
|
||||||
|
$month = (int) $datetime->format( 'n' );
|
||||||
|
|
||||||
|
switch ( $month ) {
|
||||||
|
case 1:
|
||||||
|
case 2:
|
||||||
|
case 3:
|
||||||
|
if ( $reversed ) {
|
||||||
|
$month = 1;
|
||||||
|
} else {
|
||||||
|
$month = 4;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
case 5:
|
||||||
|
case 6:
|
||||||
|
if ( $reversed ) {
|
||||||
|
$month = 4;
|
||||||
|
} else {
|
||||||
|
$month = 7;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 7:
|
||||||
|
case 8:
|
||||||
|
case 9:
|
||||||
|
if ( $reversed ) {
|
||||||
|
$month = 7;
|
||||||
|
} else {
|
||||||
|
$month = 10;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 10:
|
||||||
|
case 11:
|
||||||
|
case 12:
|
||||||
|
if ( $reversed ) {
|
||||||
|
$month = 10;
|
||||||
|
} else {
|
||||||
|
$month = 1;
|
||||||
|
$year ++;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
$datetime = new DateTime( "$year-$month-01 00:00:00" );
|
||||||
|
if ( $reversed ) {
|
||||||
|
$timestamp = (int) $datetime->format( 'U' );
|
||||||
|
$end_of_prev_month_timestamp = $timestamp - 1;
|
||||||
|
$datetime->setTimestamp( $end_of_prev_month_timestamp );
|
||||||
|
}
|
||||||
|
|
||||||
|
return $datetime;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a new DateTime object representing the next year start, or previous year end if reversed.
|
||||||
|
*
|
||||||
|
* @param DateTime $datetime Date and time.
|
||||||
|
* @param bool $reversed Going backwards in time instead of forward.
|
||||||
|
* @return DateTime
|
||||||
|
*/
|
||||||
|
public static function next_year_start( $datetime, $reversed = false ) {
|
||||||
|
$year_increment = 1;
|
||||||
|
$year = (int) $datetime->format( 'Y' );
|
||||||
|
$month = '01';
|
||||||
|
$day = '01';
|
||||||
|
|
||||||
|
if ( $reversed ) {
|
||||||
|
$datetime = new DateTime( "$year-$month-$day 00:00:00" );
|
||||||
|
$timestamp = (int) $datetime->format( 'U' );
|
||||||
|
$end_of_prev_year_timestamp = $timestamp - 1;
|
||||||
|
$datetime->setTimestamp( $end_of_prev_year_timestamp );
|
||||||
|
} else {
|
||||||
|
$year += $year_increment;
|
||||||
|
$datetime = new DateTime( "$year-$month-$day 00:00:00" );
|
||||||
|
}
|
||||||
|
|
||||||
|
return $datetime;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns beginning of next time interval for provided DateTime.
|
||||||
|
*
|
||||||
|
* E.g. for current DateTime, beginning of next day, week, quarter, etc.
|
||||||
|
*
|
||||||
|
* @param DateTime $datetime Date and time.
|
||||||
|
* @param string $time_interval Time interval, e.g. week, day, hour.
|
||||||
|
* @param bool $reversed Going backwards in time instead of forward.
|
||||||
|
* @return DateTime
|
||||||
|
*/
|
||||||
|
public static function iterate( $datetime, $time_interval, $reversed = false ) {
|
||||||
|
// $result_datetime =
|
||||||
|
// $result_timestamp_adjusted = $result_datetime->format( 'U' ) - 1;
|
||||||
|
// $result_datetime->setTimestamp( $result_timestamp_adjusted );
|
||||||
|
return call_user_func( array( __CLASS__, "next_{$time_interval}_start" ), $datetime, $reversed );
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,60 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Class for parameter-based Orders Reports querying
|
||||||
|
*
|
||||||
|
* Example usage:
|
||||||
|
* $args = array(
|
||||||
|
* 'before' => '2018-07-19 00:00:00',
|
||||||
|
* 'after' => '2018-07-05 00:00:00',
|
||||||
|
* 'interval' => 'week',
|
||||||
|
* 'categories' => array(15, 18),
|
||||||
|
* 'coupons' => array(138),
|
||||||
|
* 'order_status' => array('completed'),
|
||||||
|
* );
|
||||||
|
* $report = new WC_Reports_Orders_Stats_Query( $args );
|
||||||
|
* $mydata = $report->get_data();
|
||||||
|
*
|
||||||
|
* @package WooCommerce/Classes
|
||||||
|
* @version 3.5.0
|
||||||
|
* @since 3.5.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
defined( 'ABSPATH' ) || exit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* WC_Reports_Orders_Stats_Query
|
||||||
|
*
|
||||||
|
* @version 3.5.0
|
||||||
|
*/
|
||||||
|
class WC_Reports_Orders_Stats_Query extends WC_Reports_Query {
|
||||||
|
|
||||||
|
const REPORT_NAME = 'report-orders-stats';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Valid fields for Orders report.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
protected function get_default_query_vars() {
|
||||||
|
return array(
|
||||||
|
'fields' => array(
|
||||||
|
'net_revenue',
|
||||||
|
'avg_order_value',
|
||||||
|
'orders_count',
|
||||||
|
'avg_items_per_order',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get revenue data based on the current query vars.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function get_data() {
|
||||||
|
$args = apply_filters( 'woocommerce_reports_orders_stats_query_args', $this->get_query_vars() );
|
||||||
|
$results = WC_Data_Store::load( $this::REPORT_NAME )->get_data( $args );
|
||||||
|
return apply_filters( 'woocommerce_reports_orders_stats_select_query', $results, $args );
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,52 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Class for parameter-based Products Report querying
|
||||||
|
*
|
||||||
|
* Example usage:
|
||||||
|
* $args = array(
|
||||||
|
* 'before' => '2018-07-19 00:00:00',
|
||||||
|
* 'after' => '2018-07-05 00:00:00',
|
||||||
|
* 'page' => 2,
|
||||||
|
* 'categories' => array(15, 18),
|
||||||
|
* 'products' => array(1,2,3)
|
||||||
|
* );
|
||||||
|
* $report = new WC_Reports_Products_Query( $args );
|
||||||
|
* $mydata = $report->get_data();
|
||||||
|
*
|
||||||
|
* @package WooCommerce/Classes
|
||||||
|
* @version 3.5.0
|
||||||
|
* @since 3.5.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
defined( 'ABSPATH' ) || exit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* WC_Reports_Products_Query
|
||||||
|
*
|
||||||
|
* @version 3.5.0
|
||||||
|
*/
|
||||||
|
class WC_Reports_Products_Query extends WC_Reports_Query {
|
||||||
|
|
||||||
|
const REPORT_NAME = 'report-products';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Valid fields for Products report.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
protected function get_default_query_vars() {
|
||||||
|
return array();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get product data based on the current query vars.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function get_data() {
|
||||||
|
$args = apply_filters( 'woocommerce_reports_products_query_args', $this->get_query_vars() );
|
||||||
|
$results = WC_Data_Store::load( $this::REPORT_NAME )->get_data( $args );
|
||||||
|
return apply_filters( 'woocommerce_reports_products_select_query', $results, $args );
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,52 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Class for parameter-based Products Stats Report querying
|
||||||
|
*
|
||||||
|
* Example usage:
|
||||||
|
* $args = array(
|
||||||
|
* 'before' => '2018-07-19 00:00:00',
|
||||||
|
* 'after' => '2018-07-05 00:00:00',
|
||||||
|
* 'page' => 2,
|
||||||
|
* 'categories' => array(15, 18),
|
||||||
|
* 'product_ids' => array(1,2,3)
|
||||||
|
* );
|
||||||
|
* $report = new WC_Admin_Reports_Products_Stats_Query( $args );
|
||||||
|
* $mydata = $report->get_data();
|
||||||
|
*
|
||||||
|
* @package WooCommerce Admin/Classes
|
||||||
|
* @version 3.5.0
|
||||||
|
* @since 3.5.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
defined( 'ABSPATH' ) || exit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* WC_Admin_Reports_Products_Query
|
||||||
|
*
|
||||||
|
* @version 3.5.0
|
||||||
|
*/
|
||||||
|
class WC_Admin_Reports_Products_Stats_Query extends WC_Admin_Reports_Query {
|
||||||
|
|
||||||
|
const REPORT_NAME = 'report-products-stats';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Valid fields for Products report.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
protected function get_default_query_vars() {
|
||||||
|
return array();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get product data based on the current query vars.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function get_data() {
|
||||||
|
$args = apply_filters( 'woocommerce_reports_products_stats_query_args', $this->get_query_vars() );
|
||||||
|
$results = WC_Data_Store::load( $this::REPORT_NAME )->get_data( $args );
|
||||||
|
return apply_filters( 'woocommerce_reports_products_stats_select_query', $results, $args );
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Class for parameter-based Reports querying
|
||||||
|
*
|
||||||
|
* @package WooCommerce/Classes
|
||||||
|
* @version 3.5.0
|
||||||
|
* @since 3.5.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
defined( 'ABSPATH' ) || exit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* WC_Reports_Query
|
||||||
|
*
|
||||||
|
* @version 3.5.0
|
||||||
|
*/
|
||||||
|
abstract class WC_Reports_Query extends WC_Object_Query {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get report data matching the current query vars.
|
||||||
|
*
|
||||||
|
* @return array|object of WC_Product objects
|
||||||
|
*/
|
||||||
|
public function get_data() {
|
||||||
|
/* translators: %s: Method name */
|
||||||
|
return new WP_Error( 'invalid-method', sprintf( __( "Method '%s' not implemented. Must be overridden in subclass.", 'woocommerce' ), __METHOD__ ), array( 'status' => 405 ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,68 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Class for parameter-based Revenue Reports querying
|
||||||
|
*
|
||||||
|
* Example usage:
|
||||||
|
* $args = array(
|
||||||
|
* 'before' => '2018-07-19 00:00:00',
|
||||||
|
* 'after' => '2018-07-05 00:00:00',
|
||||||
|
* 'interval' => 'week',
|
||||||
|
* );
|
||||||
|
* $report = new WC_Reports_Revenue_Query( $args );
|
||||||
|
* $mydata = $report->get_data();
|
||||||
|
*
|
||||||
|
* @package WooCommerce/Classes
|
||||||
|
* @version 3.5.0
|
||||||
|
* @since 3.5.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
defined( 'ABSPATH' ) || exit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* WC_Reports_Revenue_Query
|
||||||
|
*
|
||||||
|
* @version 3.5.0
|
||||||
|
*/
|
||||||
|
class WC_Reports_Revenue_Query extends WC_Reports_Query {
|
||||||
|
|
||||||
|
const REPORT_NAME = 'report-revenue-stats';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Valid fields for Revenue report.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
protected function get_default_query_vars() {
|
||||||
|
return array(
|
||||||
|
'per_page' => get_option( 'posts_per_page' ), // not sure if this should be the default.
|
||||||
|
'page' => 1,
|
||||||
|
'order' => 'DESC',
|
||||||
|
'orderby' => 'date',
|
||||||
|
'before' => '',
|
||||||
|
'after' => '',
|
||||||
|
'interval' => 'week',
|
||||||
|
'fields' => array(
|
||||||
|
'orders_count',
|
||||||
|
'num_items_sold',
|
||||||
|
'gross_revenue',
|
||||||
|
'coupons',
|
||||||
|
'refunds',
|
||||||
|
'taxes',
|
||||||
|
'shipping',
|
||||||
|
'net_revenue',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get revenue data based on the current query vars.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function get_data() {
|
||||||
|
$args = apply_filters( 'woocommerce_reports_revenue_query_args', $this->get_query_vars() );
|
||||||
|
$results = WC_Data_Store::load( $this::REPORT_NAME )->get_data( $args );
|
||||||
|
return apply_filters( 'woocommerce_reports_revenue_select_query', $results, $args );
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,265 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* WC_Admin_Reports_Categories_Data_Store class file.
|
||||||
|
*
|
||||||
|
* @package WooCommerce Admin/Classes
|
||||||
|
*/
|
||||||
|
|
||||||
|
defined( 'ABSPATH' ) || exit;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* WC_Admin_Reports_Categories_Data_Store.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
class WC_Admin_Reports_Categories_Data_Store extends WC_Admin_Reports_Data_Store implements WC_Admin_Reports_Data_Store_Interface {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Table used to get the data.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
const TABLE_NAME = 'wc_admin_order_product_lookup';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Order by setting used for sorting categories data.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $order_by = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Order setting used for sorting categories data.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $order = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mapping columns to data type to return correct response types.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $column_types = array(
|
||||||
|
'category_id' => 'intval',
|
||||||
|
'items_sold' => 'intval',
|
||||||
|
'gross_revenue' => 'floatval',
|
||||||
|
'orders_count' => 'intval',
|
||||||
|
'products_count' => 'intval',
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SQL columns to select in the db query.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $report_columns = array(
|
||||||
|
'items_sold' => 'SUM(product_qty) as items_sold',
|
||||||
|
'gross_revenue' => 'SUM(product_gross_revenue) AS gross_revenue',
|
||||||
|
'orders_count' => 'COUNT(DISTINCT order_id) as orders_count',
|
||||||
|
// 'products_count' is not a SQL column at the moment, see below.
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the database query with parameters used for Categories report: time span and order status.
|
||||||
|
*
|
||||||
|
* @param array $query_args Query arguments supplied by the user.
|
||||||
|
* @return array Array of parameters used for SQL query.
|
||||||
|
*/
|
||||||
|
protected function get_sql_query_params( $query_args ) {
|
||||||
|
global $wpdb;
|
||||||
|
|
||||||
|
$sql_query_params = $this->get_time_period_sql_params( $query_args );
|
||||||
|
// Limit is left out here so that the grouping in code by PHP can be applied correctly.
|
||||||
|
$sql_query_params = array_merge( $sql_query_params, $this->get_order_by_sql_params( $query_args ) );
|
||||||
|
|
||||||
|
$order_product_lookup_table = $wpdb->prefix . self::TABLE_NAME;
|
||||||
|
|
||||||
|
$allowed_products = $this->get_allowed_products( $query_args );
|
||||||
|
|
||||||
|
if ( count( $allowed_products ) > 0 ) {
|
||||||
|
$allowed_products_str = implode( ',', $allowed_products );
|
||||||
|
$sql_query_params['where_clause'] .= " AND {$order_product_lookup_table}.product_id IN ({$allowed_products_str})";
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( is_array( $query_args['order_status'] ) && count( $query_args['order_status'] ) > 0 ) {
|
||||||
|
$statuses = array_map( array( $this, 'normalize_order_status' ), $query_args['order_status'] );
|
||||||
|
|
||||||
|
$sql_query_params['from_clause'] .= " JOIN {$wpdb->prefix}posts ON {$order_product_lookup_table}.order_id = {$wpdb->prefix}posts.ID";
|
||||||
|
$sql_query_params['where_clause'] .= " AND {$wpdb->prefix}posts.post_status IN ( '" . implode( "','", $statuses ) . "' ) ";
|
||||||
|
}
|
||||||
|
|
||||||
|
return $sql_query_params;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps ordering specified by the user to columns in the database/fields in the data.
|
||||||
|
*
|
||||||
|
* @param string $order_by Sorting criterion.
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected function normalize_order_by( $order_by ) {
|
||||||
|
if ( 'date' === $order_by ) {
|
||||||
|
return 'date_created';
|
||||||
|
}
|
||||||
|
|
||||||
|
return $order_by;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compares values in 2 arrays based on criterion specified when called via sort_records.
|
||||||
|
*
|
||||||
|
* @param array $a Array 1 to compare.
|
||||||
|
* @param array $b Array 2 to compare.
|
||||||
|
* @return integer|WP_Error
|
||||||
|
*/
|
||||||
|
private function sort_callback( $a, $b ) {
|
||||||
|
if ( '' === $this->order_by || '' === $this->order ) {
|
||||||
|
return new WP_Error( 'woocommerce_reports_categories_sort_failed', __( 'Sorry, fetching categories data failed.', 'woocommerce' ) );
|
||||||
|
}
|
||||||
|
if ( $a[ $this->order_by ] === $b[ $this->order_by ] ) {
|
||||||
|
return 0;
|
||||||
|
} elseif ( $a[ $this->order_by ] > $b[ $this->order_by ] ) {
|
||||||
|
return strtolower( $this->order ) === 'desc' ? -1 : 1;
|
||||||
|
} elseif ( $a[ $this->order_by ] < $b[ $this->order_by ] ) {
|
||||||
|
return strtolower( $this->order ) === 'desc' ? 1 : -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sorts the data based on given sorting criterion and order.
|
||||||
|
*
|
||||||
|
* @param array $data Data to sort.
|
||||||
|
* @param string $sort_by Sorting criterion, any of the column_types keys.
|
||||||
|
* @param string $direction Sorting direction: asc/desc.
|
||||||
|
*/
|
||||||
|
protected function sort_records( &$data, $sort_by, $direction ) {
|
||||||
|
$this->order_by = $this->normalize_order_by( $sort_by );
|
||||||
|
$this->order = $direction;
|
||||||
|
usort( $data, array( $this, 'sort_callback' ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the page of data according to page number and items per page.
|
||||||
|
*
|
||||||
|
* @param array $data Data to paginate.
|
||||||
|
* @param integer $page_no Page number.
|
||||||
|
* @param integer $items_per_page Number of items per page.
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
protected function page_records( $data, $page_no, $items_per_page ) {
|
||||||
|
$offset = ( $page_no - 1 ) * $items_per_page;
|
||||||
|
return array_slice( $data, $offset, $items_per_page );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the report data based on parameters supplied by the user.
|
||||||
|
*
|
||||||
|
* @param array $query_args Query parameters.
|
||||||
|
* @return stdClass|WP_Error Data.
|
||||||
|
*/
|
||||||
|
public function get_data( $query_args ) {
|
||||||
|
global $wpdb;
|
||||||
|
|
||||||
|
$table_name = $wpdb->prefix . self::TABLE_NAME;
|
||||||
|
$now = time();
|
||||||
|
$week_back = $now - WEEK_IN_SECONDS;
|
||||||
|
|
||||||
|
// These defaults are only partially applied when used via REST API, as that has its own defaults.
|
||||||
|
$defaults = array(
|
||||||
|
'per_page' => get_option( 'posts_per_page' ),
|
||||||
|
'page' => 1,
|
||||||
|
'order' => 'DESC',
|
||||||
|
'orderby' => 'date',
|
||||||
|
'before' => date( WC_Admin_Reports_Interval::$iso_datetime_format, $now ),
|
||||||
|
'after' => date( WC_Admin_Reports_Interval::$iso_datetime_format, $week_back ),
|
||||||
|
'fields' => '*',
|
||||||
|
'categories' => array(),
|
||||||
|
// This is not a parameter for products reports per se, but maybe we should restricts order statuses here, too?
|
||||||
|
'order_status' => parent::get_report_order_statuses(),
|
||||||
|
|
||||||
|
);
|
||||||
|
$query_args = wp_parse_args( $query_args, $defaults );
|
||||||
|
|
||||||
|
$cache_key = $this->get_cache_key( $query_args );
|
||||||
|
$data = wp_cache_get( $cache_key, $this->cache_group );
|
||||||
|
|
||||||
|
if ( false === $data ) {
|
||||||
|
$data = (object) array(
|
||||||
|
'data' => array(),
|
||||||
|
'total' => 0,
|
||||||
|
'pages' => 0,
|
||||||
|
'page_no' => 0,
|
||||||
|
);
|
||||||
|
|
||||||
|
$selections = $this->selected_columns( $query_args );
|
||||||
|
$sql_query_params = $this->get_sql_query_params( $query_args );
|
||||||
|
|
||||||
|
$products_data = $wpdb->get_results(
|
||||||
|
"SELECT
|
||||||
|
product_id,
|
||||||
|
date_created,
|
||||||
|
{$selections}
|
||||||
|
FROM
|
||||||
|
{$table_name}
|
||||||
|
{$sql_query_params['from_clause']}
|
||||||
|
WHERE
|
||||||
|
1=1
|
||||||
|
{$sql_query_params['where_clause']}
|
||||||
|
GROUP BY
|
||||||
|
product_id
|
||||||
|
", ARRAY_A
|
||||||
|
); // WPCS: cache ok, DB call ok, unprepared SQL ok.
|
||||||
|
|
||||||
|
if ( null === $products_data ) {
|
||||||
|
return new WP_Error( 'woocommerce_reports_categories_result_failed', __( 'Sorry, fetching revenue data failed.', 'woocommerce' ), array( 'status' => 500 ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
// Group by category without a helper table, worst case we add it and change the SQL afterwards.
|
||||||
|
// Other option would be a join with wp_post and taxonomies, but a) performance might be bad, b) how to handle custom product tables?
|
||||||
|
$categories_data = array();
|
||||||
|
foreach ( $products_data as $product_data ) {
|
||||||
|
$categories = get_the_terms( $product_data['product_id'], 'product_cat' );
|
||||||
|
foreach ( $categories as $category ) {
|
||||||
|
$cat_id = $category->term_id;
|
||||||
|
if ( ! key_exists( $cat_id, $categories_data ) ) {
|
||||||
|
$categories_data[ $cat_id ] = array(
|
||||||
|
'category_id' => 0,
|
||||||
|
'items_sold' => 0,
|
||||||
|
'gross_revenue' => 0.0,
|
||||||
|
'orders_count' => 0,
|
||||||
|
'products_count' => 0,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$categories_data[ $cat_id ]['category_id'] = $cat_id;
|
||||||
|
$categories_data[ $cat_id ]['items_sold'] += $product_data['items_sold'];
|
||||||
|
$categories_data[ $cat_id ]['gross_revenue'] += $product_data['gross_revenue'];
|
||||||
|
$categories_data[ $cat_id ]['orders_count'] += $product_data['orders_count'];
|
||||||
|
$categories_data[ $cat_id ]['products_count'] ++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$record_count = count( $categories_data );
|
||||||
|
$total_pages = (int) ceil( $record_count / $query_args['per_page'] );
|
||||||
|
if ( $query_args['page'] < 1 || $query_args['page'] > $total_pages ) {
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->sort_records( $categories_data, $query_args['orderby'], $query_args['order'] );
|
||||||
|
$categories_data = $this->page_records( $categories_data, $query_args['page'], $query_args['per_page'] );
|
||||||
|
|
||||||
|
$categories_data = array_map( array( $this, 'cast_numbers' ), $categories_data );
|
||||||
|
$data = (object) array(
|
||||||
|
'data' => $categories_data,
|
||||||
|
'total' => $record_count,
|
||||||
|
'pages' => $total_pages,
|
||||||
|
'page_no' => (int) $query_args['page'],
|
||||||
|
);
|
||||||
|
|
||||||
|
wp_cache_set( $cache_key, $data, $this->cache_group );
|
||||||
|
}
|
||||||
|
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,579 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* WC_Admin_Reports_Data_Store class file.
|
||||||
|
*
|
||||||
|
* @package WooCommerce Admin/Classes
|
||||||
|
*/
|
||||||
|
|
||||||
|
if ( ! defined( 'ABSPATH' ) ) {
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* WC_Admin_Reports_Data_Store: Common parent for custom report data stores.
|
||||||
|
*/
|
||||||
|
class WC_Admin_Reports_Data_Store {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cache group for the reports.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $cache_group = 'reports';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Time out for the cache.
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
protected $cache_timeout = 3600;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Table used as a data store for this report.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
const TABLE_NAME = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mapping columns to data type to return correct response types.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $column_types = array();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SQL columns to select in the db query.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $report_columns = array();
|
||||||
|
|
||||||
|
// TODO: this does not really belong here, maybe factor out the comparison as separate class?
|
||||||
|
/**
|
||||||
|
* Order by property, used in the cmp function.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $order_by = '';
|
||||||
|
/**
|
||||||
|
* Order property, used in the cmp function.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $order = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compares two report data objects by pre-defined object property and ASC/DESC ordering.
|
||||||
|
*
|
||||||
|
* @param stdClass $a Object a.
|
||||||
|
* @param stdClass $b Object b.
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
private function interval_cmp( $a, $b ) {
|
||||||
|
if ( '' === $this->order_by || '' === $this->order ) {
|
||||||
|
return 0;
|
||||||
|
// TODO: should return WP_Error here perhaps?
|
||||||
|
}
|
||||||
|
if ( $a[ $this->order_by ] === $b[ $this->order_by ] ) {
|
||||||
|
return 0;
|
||||||
|
} elseif ( $a[ $this->order_by ] > $b[ $this->order_by ] ) {
|
||||||
|
return strtolower( $this->order ) === 'desc' ? -1 : 1;
|
||||||
|
} elseif ( $a[ $this->order_by ] < $b[ $this->order_by ] ) {
|
||||||
|
return strtolower( $this->order ) === 'desc' ? 1 : -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sorts intervals according to user's request.
|
||||||
|
*
|
||||||
|
* They are pre-sorted in SQL, but after adding gaps, they need to be sorted including the added ones.
|
||||||
|
*
|
||||||
|
* @param stdClass $data Data object, must contain an array under $data->intervals.
|
||||||
|
* @param string $sort_by Ordering property.
|
||||||
|
* @param string $direction DESC/ASC.
|
||||||
|
*/
|
||||||
|
protected function sort_intervals( &$data, $sort_by, $direction ) {
|
||||||
|
$this->order_by = $this->normalize_order_by( $sort_by );
|
||||||
|
$this->order = $direction;
|
||||||
|
usort( $data->intervals, array( $this, 'interval_cmp' ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fills in interval gaps from DB with 0-filled objects.
|
||||||
|
*
|
||||||
|
* @param array $db_intervals Array of all intervals present in the db.
|
||||||
|
* @param DateTime $datetime_start Start date.
|
||||||
|
* @param DateTime $datetime_end End date.
|
||||||
|
* @param string $time_interval Time interval, e.g. day, week, month.
|
||||||
|
* @param stdClass $data Data with SQL extracted intervals.
|
||||||
|
* @return stdClass
|
||||||
|
*/
|
||||||
|
protected function fill_in_missing_intervals( $db_intervals, $datetime_start, $datetime_end, $time_interval, &$data ) {
|
||||||
|
// TODO: this is ugly and messy.
|
||||||
|
// At this point, we don't know when we can stop iterating, as the ordering can be based on any value.
|
||||||
|
$end_datetime = new DateTime( $datetime_end );
|
||||||
|
$time_ids = array_flip( wp_list_pluck( $data->intervals, 'time_interval' ) );
|
||||||
|
$db_intervals = array_flip( $db_intervals );
|
||||||
|
$datetime = new DateTime( $datetime_start );
|
||||||
|
// Totals object used to get all needed properties.
|
||||||
|
$totals_arr = get_object_vars( $data->totals );
|
||||||
|
foreach ( $totals_arr as $key => $val ) {
|
||||||
|
$totals_arr[ $key ] = 0;
|
||||||
|
}
|
||||||
|
while ( $datetime <= $end_datetime ) {
|
||||||
|
$next_start = WC_Admin_Reports_Interval::iterate( $datetime, $time_interval );
|
||||||
|
$time_id = WC_Admin_Reports_Interval::time_interval_id( $time_interval, $datetime );
|
||||||
|
// Either create fill-zero interval or use data from db.
|
||||||
|
if ( $next_start > $end_datetime ) {
|
||||||
|
$interval_end = $end_datetime->format( 'Y-m-d H:i:s' );
|
||||||
|
} else {
|
||||||
|
$prev_end_timestamp = (int) $next_start->format( 'U' ) - 1;
|
||||||
|
$prev_end = new DateTime();
|
||||||
|
$prev_end->setTimestamp( $prev_end_timestamp );
|
||||||
|
$interval_end = $prev_end->format( 'Y-m-d H:i:s' );
|
||||||
|
}
|
||||||
|
if ( array_key_exists( $time_id, $time_ids ) ) {
|
||||||
|
// For interval present in the db for this time frame, just fill in dates.
|
||||||
|
$record = &$data->intervals[ $time_ids[ $time_id ] ];
|
||||||
|
$record['date_start'] = $datetime->format( 'Y-m-d H:i:s' );
|
||||||
|
$record['date_end'] = $interval_end;
|
||||||
|
} elseif ( array_key_exists( $time_id, $db_intervals ) ) {
|
||||||
|
// For intervals present in the db outside of this time frame, do nothing.
|
||||||
|
} else {
|
||||||
|
// For intervals not present in the db, fabricate it.
|
||||||
|
$record_arr = array();
|
||||||
|
$record_arr['time_interval'] = $time_id;
|
||||||
|
$record_arr['date_start'] = $datetime->format( 'Y-m-d H:i:s' );
|
||||||
|
$record_arr['date_end'] = $interval_end;
|
||||||
|
$data->intervals[] = array_merge( $record_arr, $totals_arr );
|
||||||
|
}
|
||||||
|
$datetime = $next_start;
|
||||||
|
}
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes extra records from intervals so that only requested number of records get returned.
|
||||||
|
*
|
||||||
|
* @param stdClass $data Data from whose intervals the records get removed.
|
||||||
|
* @param int $page_no Offset requested by the user.
|
||||||
|
* @param int $items_per_page Number of records requested by the user.
|
||||||
|
* @param int $db_interval_count
|
||||||
|
* @param int $expected_interval_count
|
||||||
|
* @param string $order_by
|
||||||
|
*/
|
||||||
|
protected function remove_extra_records( &$data, $page_no, $items_per_page, $db_interval_count, $expected_interval_count, $order_by ) {
|
||||||
|
if ( 'date' === strtolower( $order_by ) ) {
|
||||||
|
$offset = 0;
|
||||||
|
} else {
|
||||||
|
$offset = ( $page_no - 1 ) * $items_per_page - $db_interval_count;
|
||||||
|
$offset = $offset < 0 ? 0 : $offset;
|
||||||
|
}
|
||||||
|
$count = $expected_interval_count - ( $page_no - 1 ) * $items_per_page;
|
||||||
|
if ( $count < 0 ) {
|
||||||
|
$count = 0;
|
||||||
|
} elseif ( $count > $items_per_page ) {
|
||||||
|
$count = $items_per_page;
|
||||||
|
}
|
||||||
|
$data->intervals = array_slice( $data->intervals, $offset, $count );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the LIMIT query part for Intervals query of the report.
|
||||||
|
*
|
||||||
|
* If there are less records in the database than time intervals, then we need to remap offset in SQL query
|
||||||
|
* to fetch correct records.
|
||||||
|
*
|
||||||
|
* @param array $intervals_query Array with clauses for the Intervals SQL query.
|
||||||
|
* @param int $db_records Number of records in the db for requested time period.
|
||||||
|
*/
|
||||||
|
protected function update_intervals_sql_params( &$intervals_query, &$query_args, $db_interval_count, $expected_interval_count ) {
|
||||||
|
if ( $db_interval_count === $expected_interval_count ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ( 'date' === strtolower( $query_args['orderby'] ) ) {
|
||||||
|
// page X in request translates to slightly different dates in the db, in case some
|
||||||
|
// records are missing from the db.
|
||||||
|
$start_iteration = 0;
|
||||||
|
$end_iteration = 0;
|
||||||
|
if ( 'asc' === strtolower( $query_args['order'] ) ) {
|
||||||
|
// ORDER BY date ASC.
|
||||||
|
$new_start_date = new DateTime( $query_args['after'] );
|
||||||
|
$intervals_to_skip = ( $query_args['page'] - 1 ) * $intervals_query['per_page'];
|
||||||
|
$latest_end_date = new DateTime( $query_args['before'] );
|
||||||
|
for ( $i = 0; $i < $intervals_to_skip; $i++ ) {
|
||||||
|
if ( $new_start_date > $latest_end_date ) {
|
||||||
|
$new_start_date = $latest_end_date;
|
||||||
|
$start_iteration = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
$new_start_date = WC_Admin_Reports_Interval::iterate( $new_start_date, $query_args['interval'] );
|
||||||
|
$start_iteration ++;
|
||||||
|
}
|
||||||
|
|
||||||
|
$new_end_date = clone $new_start_date;
|
||||||
|
for ( $i = 0; $i < $intervals_query['per_page']; $i++ ) {
|
||||||
|
if ( $new_end_date > $latest_end_date ) {
|
||||||
|
$new_end_date = $latest_end_date;
|
||||||
|
$end_iteration = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
$new_end_date = WC_Admin_Reports_Interval::iterate( $new_end_date, $query_args['interval'] );
|
||||||
|
$end_iteration ++;
|
||||||
|
}
|
||||||
|
if ( $end_iteration ) {
|
||||||
|
$new_end_date_timestamp = (int) $new_end_date->format( 'U' ) - 1;
|
||||||
|
$new_end_date->setTimestamp( $new_end_date_timestamp );
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// ORDER BY date DESC.
|
||||||
|
$new_end_date = new DateTime( $query_args['before'] );
|
||||||
|
$intervals_to_skip = ( $query_args['page'] - 1 ) * $intervals_query['per_page'];
|
||||||
|
$earliest_start_date = new DateTime( $query_args['after'] );
|
||||||
|
for ( $i = 0; $i < $intervals_to_skip; $i++ ) {
|
||||||
|
if ( $new_end_date < $earliest_start_date ) {
|
||||||
|
$new_end_date = $earliest_start_date;
|
||||||
|
$end_iteration = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
$new_end_date = WC_Admin_Reports_Interval::iterate( $new_end_date, $query_args['interval'], true );
|
||||||
|
$end_iteration ++;
|
||||||
|
}
|
||||||
|
|
||||||
|
$new_start_date = clone $new_end_date;
|
||||||
|
for ( $i = 0; $i < $intervals_query['per_page']; $i++ ) {
|
||||||
|
if ( $new_start_date < $earliest_start_date ) {
|
||||||
|
$new_start_date = $earliest_start_date;
|
||||||
|
$start_iteration = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
$new_start_date = WC_Admin_Reports_Interval::iterate( $new_start_date, $query_args['interval'], true );
|
||||||
|
$start_iteration ++;
|
||||||
|
}
|
||||||
|
if ( $start_iteration ) {
|
||||||
|
// TODO: is this correct? should it only be added if iterate runs? other two iterate instances, too?
|
||||||
|
$new_start_date_timestamp = (int) $new_start_date->format( 'U' ) + 1;
|
||||||
|
$new_start_date->setTimestamp( $new_start_date_timestamp );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$query_args['adj_after'] = $new_start_date->format( WC_Admin_Reports_Interval::$iso_datetime_format );
|
||||||
|
$query_args['adj_before'] = $new_end_date->format( WC_Admin_Reports_Interval::$iso_datetime_format );
|
||||||
|
$intervals_query['where_clause'] = '';
|
||||||
|
$intervals_query['where_clause'] .= " AND date_created <= '{$query_args['adj_before']}'";
|
||||||
|
$intervals_query['where_clause'] .= " AND date_created >= '{$query_args['adj_after']}'";
|
||||||
|
$intervals_query['limit'] = 'LIMIT 0,' . $intervals_query['per_page'];
|
||||||
|
} else {
|
||||||
|
if ( 'asc' === $query_args['order'] ) {
|
||||||
|
$offset = ( ( $query_args['page'] - 1 ) * $intervals_query['per_page'] ) - ( $expected_interval_count - $db_interval_count );
|
||||||
|
$offset = $offset < 0 ? 0 : $offset;
|
||||||
|
$count = $query_args['page'] * $intervals_query['per_page'] - ( $expected_interval_count - $db_interval_count );
|
||||||
|
if ( $count < 0 ) {
|
||||||
|
$count = 0;
|
||||||
|
} elseif ( $count > $intervals_query['per_page'] ) {
|
||||||
|
$count = $intervals_query['per_page'];
|
||||||
|
}
|
||||||
|
$intervals_query['limit'] = 'LIMIT ' . $offset . ',' . $count;
|
||||||
|
}
|
||||||
|
// Otherwise no change in limit clause.
|
||||||
|
$query_args['adj_after'] = $query_args['after'];
|
||||||
|
$query_args['adj_before'] = $query_args['before'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns string to be used as cache key for the data.
|
||||||
|
*
|
||||||
|
* @param array $params Query parameters.
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected function get_cache_key( $params ) {
|
||||||
|
// TODO: this is not working in PHP 5.2 (but revenue class has static methods, so it cannot use object property).
|
||||||
|
return 'woocommerce_' . $this::TABLE_NAME . '_' . md5( wp_json_encode( $params ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Casts strings returned from the database to appropriate data types for output.
|
||||||
|
*
|
||||||
|
* @param array $array Associative array of values extracted from the database.
|
||||||
|
* @return array|WP_Error
|
||||||
|
*/
|
||||||
|
protected function cast_numbers( $array ) {
|
||||||
|
$retyped_array = array();
|
||||||
|
$column_types = apply_filters( 'woocommerce_rest_reports_column_types', $this->column_types, $array );
|
||||||
|
foreach ( $array as $column_name => $value ) {
|
||||||
|
if ( isset( $column_types[ $column_name ] ) ) {
|
||||||
|
$retyped_array[ $column_name ] = $column_types[ $column_name ]( $value );
|
||||||
|
} else {
|
||||||
|
$retyped_array[ $column_name ] = $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $retyped_array;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a list of columns selected by the query_args formatted as a comma separated string.
|
||||||
|
*
|
||||||
|
* @param array $query_args User-supplied options.
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected function selected_columns( $query_args ) {
|
||||||
|
$selections = $this->report_columns;
|
||||||
|
|
||||||
|
if ( isset( $query_args['fields'] ) && is_array( $query_args['fields'] ) ) {
|
||||||
|
$keep = array();
|
||||||
|
foreach ( $query_args['fields'] as $field ) {
|
||||||
|
if ( isset( $selections[ $field ] ) ) {
|
||||||
|
$keep[ $field ] = $selections[ $field ];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$selections = implode( ', ', $keep );
|
||||||
|
} else {
|
||||||
|
$selections = implode( ', ', $selections );
|
||||||
|
}
|
||||||
|
return $selections;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the order statuses used when calculating reports.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
protected static function get_report_order_statuses() {
|
||||||
|
return apply_filters( 'woocommerce_reports_order_statuses', array( 'completed', 'processing', 'on-hold' ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps order status provided by the user to the one used in the database.
|
||||||
|
*
|
||||||
|
* @param string $status Order status.
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected function normalize_order_status( $status ) {
|
||||||
|
$status = trim( $status );
|
||||||
|
return 'wc-' . $status;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Normalizes order_by clause to match to SQL query.
|
||||||
|
*
|
||||||
|
* @param string $order_by Order by option requeste by user.
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected function normalize_order_by( $order_by ) {
|
||||||
|
if ( 'date' === $order_by ) {
|
||||||
|
return 'time_interval';
|
||||||
|
}
|
||||||
|
|
||||||
|
return $order_by;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates start and end dates for intervals so that they represent intervals' borders, not times when data in db were recorded.
|
||||||
|
*
|
||||||
|
* E.g. if there are db records for only Tuesday and Thursday this week, the actual week interval is [Mon, Sun], not [Tue, Thu].
|
||||||
|
*
|
||||||
|
* @param DateTime $datetime_start Start date.
|
||||||
|
* @param DateTime $datetime_end End date.
|
||||||
|
* @param string $time_interval Time interval, e.g. day, week, month.
|
||||||
|
* @param array $intervals Array of intervals extracted from SQL db.
|
||||||
|
*/
|
||||||
|
protected function update_interval_boundary_dates( $datetime_start, $datetime_end, $time_interval, &$intervals ) {
|
||||||
|
foreach ( $intervals as $key => $interval ) {
|
||||||
|
$datetime = new DateTime( $interval['datetime_anchor'] );
|
||||||
|
|
||||||
|
$prev_start = WC_Admin_Reports_Interval::iterate( $datetime, $time_interval, true );
|
||||||
|
// TODO: not sure if the +1/-1 here are correct, especially as they are applied before the ?: below.
|
||||||
|
$prev_start_timestamp = (int) $prev_start->format( 'U' ) + 1;
|
||||||
|
$prev_start->setTimestamp( $prev_start_timestamp );
|
||||||
|
if ( $datetime_start ) {
|
||||||
|
$start_datetime = new DateTime( $datetime_start );
|
||||||
|
$intervals[ $key ]['date_start'] = ( $prev_start < $start_datetime ? $start_datetime : $prev_start )->format( 'Y-m-d H:i:s' );
|
||||||
|
} else {
|
||||||
|
$intervals[ $key ]['date_start'] = $prev_start->format( 'Y-m-d H:i:s' );
|
||||||
|
}
|
||||||
|
|
||||||
|
$next_end = WC_Admin_Reports_Interval::iterate( $datetime, $time_interval );
|
||||||
|
$next_end_timestamp = (int) $next_end->format( 'U' ) - 1;
|
||||||
|
$next_end->setTimestamp( $next_end_timestamp );
|
||||||
|
if ( $datetime_end ) {
|
||||||
|
$end_datetime = new DateTime( $datetime_end );
|
||||||
|
$intervals[ $key ]['date_end'] = ( $next_end > $end_datetime ? $end_datetime : $next_end )->format( 'Y-m-d H:i:s' );
|
||||||
|
} else {
|
||||||
|
$intervals[ $key ]['date_end'] = $next_end->format( 'Y-m-d H:i:s' );
|
||||||
|
}
|
||||||
|
|
||||||
|
$intervals[ $key ]['interval'] = $time_interval;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Change structure of intervals to form a correct response.
|
||||||
|
*
|
||||||
|
* @param array $intervals Time interval, e.g. day, week, month.
|
||||||
|
*/
|
||||||
|
protected function create_interval_subtotals( &$intervals ) {
|
||||||
|
foreach ( $intervals as $key => $interval ) {
|
||||||
|
// Move intervals result to subtotals object.
|
||||||
|
$intervals[ $key ] = array(
|
||||||
|
'interval' => $interval['time_interval'],
|
||||||
|
'date_start' => $interval['date_start'],
|
||||||
|
'date_start_gmt' => $interval['date_start'],
|
||||||
|
'date_end' => $interval['date_end'],
|
||||||
|
'date_end_gmt' => $interval['date_end'],
|
||||||
|
);
|
||||||
|
|
||||||
|
unset( $interval['interval'] );
|
||||||
|
unset( $interval['date_start'] );
|
||||||
|
unset( $interval['date_end'] );
|
||||||
|
unset( $interval['datetime_anchor'] );
|
||||||
|
unset( $interval['time_interval'] );
|
||||||
|
$intervals[ $key ]['subtotals'] = (object) $this->cast_numbers( $interval );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fills WHERE clause of SQL request for 'Totals' section of data response based on user supplied parameters.
|
||||||
|
*
|
||||||
|
* @param array $query_args Parameters supplied by the user.
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
protected function get_time_period_sql_params( $query_args ) {
|
||||||
|
$sql_query = array(
|
||||||
|
'from_clause' => '',
|
||||||
|
'where_clause' => '',
|
||||||
|
);
|
||||||
|
|
||||||
|
if ( isset( $query_args['before'] ) && '' !== $query_args['before'] ) {
|
||||||
|
$datetime = new DateTime( $query_args['before'] );
|
||||||
|
$datetime_str = $datetime->format( WC_Admin_Reports_Interval::$sql_datetime_format );
|
||||||
|
$sql_query['where_clause'] .= " AND date_created <= '$datetime_str'";
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( isset( $query_args['after'] ) && '' !== $query_args['after'] ) {
|
||||||
|
$datetime = new DateTime( $query_args['after'] );
|
||||||
|
$datetime_str = $datetime->format( WC_Admin_Reports_Interval::$sql_datetime_format );
|
||||||
|
$sql_query['where_clause'] .= " AND date_created >= '$datetime_str'";
|
||||||
|
}
|
||||||
|
|
||||||
|
return $sql_query;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fills LIMIT clause of SQL request based on user supplied parameters.
|
||||||
|
*
|
||||||
|
* @param array $query_args Parameters supplied by the user.
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
protected function get_limit_sql_params( $query_args ) {
|
||||||
|
$sql_query['per_page'] = get_option( 'posts_per_page' );
|
||||||
|
if ( isset( $query_args['per_page'] ) && is_numeric( $query_args['per_page'] ) ) {
|
||||||
|
$sql_query['per_page'] = (int) $query_args['per_page'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$sql_query['offset'] = 0;
|
||||||
|
if ( isset( $query_args['page'] ) ) {
|
||||||
|
$sql_query['offset'] = ( (int) $query_args['page'] - 1 ) * $sql_query['per_page'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$sql_query['limit'] = "LIMIT {$sql_query['offset']}, {$sql_query['per_page']}";
|
||||||
|
return $sql_query;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fills ORDER BY clause of SQL request based on user supplied parameters.
|
||||||
|
*
|
||||||
|
* @param array $query_args Parameters supplied by the user.
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
protected function get_order_by_sql_params( $query_args ) {
|
||||||
|
$sql_query['order_by_clause'] = '';
|
||||||
|
if ( isset( $query_args['orderby'] ) ) {
|
||||||
|
$sql_query['order_by_clause'] = $this->normalize_order_by( $query_args['orderby'] );
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( isset( $query_args['order'] ) ) {
|
||||||
|
$sql_query['order_by_clause'] .= ' ' . $query_args['order'];
|
||||||
|
} else {
|
||||||
|
$sql_query['order_by_clause'] .= ' DESC';
|
||||||
|
}
|
||||||
|
|
||||||
|
return $sql_query;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fills FROM and WHERE clauses of SQL request for 'Intervals' section of data response based on user supplied parameters.
|
||||||
|
*
|
||||||
|
* @param array $query_args Parameters supplied by the user.
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
protected function get_intervals_sql_params( $query_args ) {
|
||||||
|
$intervals_query = array(
|
||||||
|
'from_clause' => '',
|
||||||
|
'where_clause' => '',
|
||||||
|
);
|
||||||
|
|
||||||
|
$intervals_query = array_merge( $intervals_query, $this->get_time_period_sql_params( $query_args ) );
|
||||||
|
|
||||||
|
if ( isset( $query_args['interval'] ) && '' !== $query_args['interval'] ) {
|
||||||
|
$interval = $query_args['interval'];
|
||||||
|
$intervals_query['select_clause'] = WC_Admin_Reports_Interval::mysql_datetime_format( $interval );
|
||||||
|
}
|
||||||
|
|
||||||
|
$intervals_query = array_merge( $intervals_query, $this->get_limit_sql_params( $query_args ) );
|
||||||
|
|
||||||
|
$intervals_query = array_merge( $intervals_query, $this->get_order_by_sql_params( $query_args ) );
|
||||||
|
|
||||||
|
return $intervals_query;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an array of products belonging to given categories.
|
||||||
|
*
|
||||||
|
* @param array $categories List of categories IDs.
|
||||||
|
* @return array|stdClass
|
||||||
|
*/
|
||||||
|
protected function get_products_by_cat_ids( $categories ) {
|
||||||
|
$product_categories = get_categories( array(
|
||||||
|
'hide_empty' => 0,
|
||||||
|
'taxonomy' => 'product_cat',
|
||||||
|
) );
|
||||||
|
$cat_slugs = array();
|
||||||
|
$categories = array_flip( $categories );
|
||||||
|
foreach ( $product_categories as $product_cat ) {
|
||||||
|
if ( key_exists( $product_cat->cat_ID, $categories ) ) {
|
||||||
|
$cat_slugs[] = $product_cat->slug;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$args = array(
|
||||||
|
'category' => $cat_slugs,
|
||||||
|
'limit' => -1,
|
||||||
|
);
|
||||||
|
return wc_get_products( $args );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns ids of allowed products, based on query arguments from the user.
|
||||||
|
*
|
||||||
|
* @param array $query_args Parameters supplied by the user.
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
protected function get_allowed_products( $query_args ) {
|
||||||
|
$allowed_products = array();
|
||||||
|
if ( isset( $query_args['categories'] ) && is_array( $query_args['categories'] ) && count( $query_args['categories'] ) > 0 ) {
|
||||||
|
$allowed_products = $this->get_products_by_cat_ids( $query_args['categories'] );
|
||||||
|
$allowed_products = wc_list_pluck( $allowed_products, 'get_id' );
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( isset( $query_args['products'] ) && is_array( $query_args['products'] ) && count( $query_args['products'] ) > 0 ) {
|
||||||
|
if ( count( $allowed_products ) > 0 ) {
|
||||||
|
$allowed_products = array_intersect( $allowed_products, $query_args['products'] );
|
||||||
|
} else {
|
||||||
|
$allowed_products = $query_args['products'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $allowed_products;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,465 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* WC_Admin_Reports_Orders_Data_Store class file.
|
||||||
|
*
|
||||||
|
* @package WooCommerce Admin/Classes
|
||||||
|
*/
|
||||||
|
|
||||||
|
defined( 'ABSPATH' ) || exit;
|
||||||
|
|
||||||
|
if ( ! class_exists( 'WC_Admin_Order_Stats_Background_Process', false ) ) {
|
||||||
|
include_once WC_ABSPATH . 'includes/class-wc-admin-order-stats-background-process.php';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* WC_Admin_Reports_Orders_Data_Store.
|
||||||
|
*
|
||||||
|
* @version 3.5.0
|
||||||
|
*/
|
||||||
|
class WC_Admin_Reports_Orders_Data_Store extends WC_Admin_Reports_Data_Store implements WC_Admin_Reports_Data_Store_Interface {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Table used to get the data.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
const TABLE_NAME = 'wc_admin_order_stats';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cron event name.
|
||||||
|
*/
|
||||||
|
const CRON_EVENT = 'wc_admin_order_stats_update';
|
||||||
|
|
||||||
|
protected $column_types = array(
|
||||||
|
'orders_count' => 'intval',
|
||||||
|
'num_items_sold' => 'intval',
|
||||||
|
'gross_revenue' => 'floatval',
|
||||||
|
'coupons' => 'floatval',
|
||||||
|
'refunds' => 'floatval',
|
||||||
|
'taxes' => 'floatval',
|
||||||
|
'shipping' => 'floatval',
|
||||||
|
'net_revenue' => 'floatval',
|
||||||
|
'avg_items_per_order' => 'floatval',
|
||||||
|
'avg_order_value' => 'floatval',
|
||||||
|
);
|
||||||
|
|
||||||
|
protected $report_columns = array(
|
||||||
|
'orders_count' => 'COUNT(*) as orders_count',
|
||||||
|
'num_items_sold' => 'SUM(num_items_sold) as num_items_sold',
|
||||||
|
'gross_revenue' => 'SUM(gross_total) AS gross_revenue',
|
||||||
|
'coupons' => 'SUM(coupon_total) AS coupons',
|
||||||
|
'refunds' => 'SUM(refund_total) AS refunds',
|
||||||
|
'taxes' => 'SUM(tax_total) AS taxes',
|
||||||
|
'shipping' => 'SUM(shipping_total) AS shipping',
|
||||||
|
'net_revenue' => 'SUM(net_total) AS net_revenue',
|
||||||
|
'avg_items_per_order' => 'AVG(num_items_sold) AS avg_items_per_order',
|
||||||
|
'avg_order_value' => 'AVG(gross_total) AS avg_order_value',
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Background process to populate order stats.
|
||||||
|
*
|
||||||
|
* @var WC_Admin_Order_Stats_Background_Process
|
||||||
|
*/
|
||||||
|
protected static $background_process;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*/
|
||||||
|
public function __construct() {
|
||||||
|
if ( ! self::$background_process ) {
|
||||||
|
self::$background_process = new WC_Admin_Order_Stats_Background_Process();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set up all the hooks for maintaining and populating table data.
|
||||||
|
*/
|
||||||
|
public static function init() {
|
||||||
|
add_action( 'save_post', array( __CLASS__, 'sync_order' ) );
|
||||||
|
|
||||||
|
if ( ! self::$background_process ) {
|
||||||
|
self::$background_process = new WC_Admin_Order_Stats_Background_Process();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns expected number of items on the page in case of date ordering.
|
||||||
|
*
|
||||||
|
* @param $expected_interval_count
|
||||||
|
* @param $items_per_page
|
||||||
|
* @param $page_no
|
||||||
|
*
|
||||||
|
* @return float|int
|
||||||
|
*/
|
||||||
|
protected function expected_intervals_on_page( $expected_interval_count, $items_per_page, $page_no ) {
|
||||||
|
$total_pages = (int) ceil( $expected_interval_count / $items_per_page );
|
||||||
|
if ( $page_no < $total_pages ) {
|
||||||
|
return $items_per_page;
|
||||||
|
} elseif ( $page_no === $total_pages ) {
|
||||||
|
return $expected_interval_count - ( $page_no - 1 ) * $items_per_page;
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if there are any intervals that need to be filled in the response.
|
||||||
|
*
|
||||||
|
* @param $expected_interval_count
|
||||||
|
* @param $db_records
|
||||||
|
* @param $items_per_page
|
||||||
|
* @param $page_no
|
||||||
|
* @param $order
|
||||||
|
* @param $order_by
|
||||||
|
* @param $intervals_count
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
protected function intervals_missing( $expected_interval_count, $db_records, $items_per_page, $page_no, $order, $order_by, $intervals_count ) {
|
||||||
|
if ( $expected_interval_count > $db_records ) {
|
||||||
|
if ( 'date' === $order_by ) {
|
||||||
|
$expected_intervals_on_page = $this->expected_intervals_on_page( $expected_interval_count, $items_per_page, $page_no );
|
||||||
|
if ( $intervals_count < $expected_intervals_on_page ) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if ( 'desc' === $order ) {
|
||||||
|
if ( $page_no > floor( $db_records / $items_per_page ) ) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} elseif ( 'asc' === $order ) {
|
||||||
|
if ( $page_no <= ceil( ( $expected_interval_count - $db_records ) / $items_per_page ) ) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Invalid ordering.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the totals and intervals database queries with parameters used for Orders report: categories, coupons and order status.
|
||||||
|
*
|
||||||
|
* @param array $query_args Query arguments supplied by the user.
|
||||||
|
* @param array $totals_query Array of options for totals db query.
|
||||||
|
* @param array $intervals_query Array of options for intervals db query.
|
||||||
|
*/
|
||||||
|
protected function orders_stats_sql_filter( $query_args, &$totals_query, &$intervals_query ) {
|
||||||
|
// TODO: performance of all of this?
|
||||||
|
global $wpdb;
|
||||||
|
|
||||||
|
$where_clause = '';
|
||||||
|
$from_clause = '';
|
||||||
|
|
||||||
|
$orders_stats_table = $wpdb->prefix . self::TABLE_NAME;
|
||||||
|
$allowed_products = $this->get_allowed_products( $query_args );
|
||||||
|
|
||||||
|
if ( count( $allowed_products ) > 0 ) {
|
||||||
|
$allowed_products_str = implode( ',', $allowed_products );
|
||||||
|
|
||||||
|
$where_clause .= " AND {$orders_stats_table}.order_id IN (
|
||||||
|
SELECT
|
||||||
|
DISTINCT {$wpdb->prefix}wc_admin_order_product_lookup.order_id
|
||||||
|
FROM
|
||||||
|
{$wpdb->prefix}wc_admin_order_product_lookup
|
||||||
|
WHERE
|
||||||
|
{$wpdb->prefix}wc_admin_order_product_lookup.product_id IN ({$allowed_products_str})
|
||||||
|
)";
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( is_array( $query_args['coupons'] ) && count( $query_args['coupons'] ) > 0 ) {
|
||||||
|
$allowed_coupons_str = implode( ', ', $query_args['coupons'] );
|
||||||
|
|
||||||
|
$where_clause .= " AND {$orders_stats_table}.order_id IN (
|
||||||
|
SELECT
|
||||||
|
DISTINCT {$wpdb->prefix}wc_order_coupon_lookup.order_id
|
||||||
|
FROM
|
||||||
|
{$wpdb->prefix}wc_order_coupon_lookup
|
||||||
|
WHERE
|
||||||
|
{$wpdb->prefix}wc_order_coupon_lookup.coupon_id IN ({$allowed_coupons_str})
|
||||||
|
)";
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( is_array( $query_args['order_status'] ) && count( $query_args['order_status'] ) > 0 ) {
|
||||||
|
$statuses = array_map( array( $this, 'normalize_order_status' ), $query_args['order_status'] );
|
||||||
|
|
||||||
|
$from_clause .= " JOIN {$wpdb->prefix}posts ON {$orders_stats_table}.order_id = {$wpdb->prefix}posts.ID";
|
||||||
|
$where_clause .= " AND {$wpdb->prefix}posts.post_status IN ( '" . implode( "','", $statuses ) . "' ) ";
|
||||||
|
}
|
||||||
|
|
||||||
|
// To avoid requesting the subqueries twice, the result is applied to all queries passed to the method.
|
||||||
|
$totals_query['where_clause'] .= $where_clause;
|
||||||
|
$totals_query['from_clause'] .= $from_clause;
|
||||||
|
$intervals_query['where_clause'] .= $where_clause;
|
||||||
|
$intervals_query['from_clause'] .= $from_clause;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the report data based on parameters supplied by the user.
|
||||||
|
*
|
||||||
|
* @since 3.5.0
|
||||||
|
* @param array $query_args Query parameters.
|
||||||
|
* @return stdClass|WP_Error Data.
|
||||||
|
*/
|
||||||
|
public function get_data( $query_args ) {
|
||||||
|
global $wpdb;
|
||||||
|
|
||||||
|
$table_name = $wpdb->prefix . self::TABLE_NAME;
|
||||||
|
$now = time();
|
||||||
|
$week_back = $now - WEEK_IN_SECONDS;
|
||||||
|
|
||||||
|
// These defaults are only applied when not using REST API, as the API has its own defaults that overwrite these for most values (except before, after, etc).
|
||||||
|
$defaults = array(
|
||||||
|
'per_page' => get_option( 'posts_per_page' ),
|
||||||
|
'page' => 1,
|
||||||
|
'order' => 'DESC',
|
||||||
|
'orderby' => 'date',
|
||||||
|
'before' => date( WC_Admin_Reports_Interval::$iso_datetime_format, $now ),
|
||||||
|
'after' => date( WC_Admin_Reports_Interval::$iso_datetime_format, $week_back ),
|
||||||
|
'interval' => 'week',
|
||||||
|
'fields' => '*',
|
||||||
|
'categories' => array(),
|
||||||
|
'coupons' => array(),
|
||||||
|
'order_status' => parent::get_report_order_statuses(),
|
||||||
|
'products' => array(),
|
||||||
|
);
|
||||||
|
$query_args = wp_parse_args( $query_args, $defaults );
|
||||||
|
|
||||||
|
$cache_key = $this->get_cache_key( $query_args );
|
||||||
|
$data = wp_cache_get( $cache_key, $this->cache_group );
|
||||||
|
|
||||||
|
if ( false === $data ) {
|
||||||
|
$data = (object) array(
|
||||||
|
'totals' => (object) array(),
|
||||||
|
'intervals' => (object) array(),
|
||||||
|
'total' => 0,
|
||||||
|
'pages' => 0,
|
||||||
|
'page_no' => 0,
|
||||||
|
);
|
||||||
|
|
||||||
|
$selections = $this->selected_columns( $query_args );
|
||||||
|
$totals_query = $this->get_time_period_sql_params( $query_args );
|
||||||
|
$intervals_query = $this->get_intervals_sql_params( $query_args );
|
||||||
|
|
||||||
|
// Additional filtering for Orders report.
|
||||||
|
$this->orders_stats_sql_filter( $query_args, $totals_query, $intervals_query );
|
||||||
|
|
||||||
|
$totals = $wpdb->get_results(
|
||||||
|
"SELECT
|
||||||
|
{$selections}
|
||||||
|
FROM
|
||||||
|
{$table_name}
|
||||||
|
{$totals_query['from_clause']}
|
||||||
|
WHERE
|
||||||
|
1=1
|
||||||
|
{$totals_query['where_clause']}", ARRAY_A
|
||||||
|
); // WPCS: cache ok, DB call ok, unprepared SQL ok.
|
||||||
|
if ( null === $totals ) {
|
||||||
|
return new WP_Error( 'woocommerce_reports_revenue_result_failed', __( 'Sorry, fetching revenue data failed.', 'woocommerce' ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
// Specification says these are not included in totals.
|
||||||
|
unset( $totals[0]['date_start'] );
|
||||||
|
unset( $totals[0]['date_end'] );
|
||||||
|
|
||||||
|
$totals = (object) $this->cast_numbers( $totals[0] );
|
||||||
|
|
||||||
|
$db_intervals = $wpdb->get_col(
|
||||||
|
"SELECT
|
||||||
|
{$intervals_query['select_clause']} AS time_interval
|
||||||
|
FROM
|
||||||
|
{$table_name}
|
||||||
|
{$intervals_query['from_clause']}
|
||||||
|
WHERE
|
||||||
|
1=1
|
||||||
|
{$intervals_query['where_clause']}
|
||||||
|
GROUP BY
|
||||||
|
time_interval"
|
||||||
|
); // WPCS: cache ok, DB call ok, , unprepared SQL ok.
|
||||||
|
|
||||||
|
$db_interval_count = count( $db_intervals );
|
||||||
|
$expected_interval_count = WC_Admin_Reports_Interval::intervals_between( $query_args['after'], $query_args['before'], $query_args['interval'] );
|
||||||
|
$total_pages = (int) ceil( $expected_interval_count / $intervals_query['per_page'] );
|
||||||
|
|
||||||
|
if ( $query_args['page'] < 1 || $query_args['page'] > $total_pages ) {
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->update_intervals_sql_params( $intervals_query, $query_args, $db_interval_count, $expected_interval_count );
|
||||||
|
|
||||||
|
if ( '' !== $selections ) {
|
||||||
|
$selections = ', ' . $selections;
|
||||||
|
}
|
||||||
|
|
||||||
|
$intervals = $wpdb->get_results(
|
||||||
|
"SELECT
|
||||||
|
MAX(date_created) AS datetime_anchor,
|
||||||
|
{$intervals_query['select_clause']} AS time_interval
|
||||||
|
{$selections}
|
||||||
|
FROM
|
||||||
|
{$table_name}
|
||||||
|
{$intervals_query['from_clause']}
|
||||||
|
WHERE
|
||||||
|
1=1
|
||||||
|
{$intervals_query['where_clause']}
|
||||||
|
GROUP BY
|
||||||
|
time_interval
|
||||||
|
ORDER BY
|
||||||
|
{$intervals_query['order_by_clause']}
|
||||||
|
{$intervals_query['limit']}", ARRAY_A
|
||||||
|
); // WPCS: cache ok, DB call ok, unprepared SQL ok.
|
||||||
|
|
||||||
|
echo $wpdb->last_query;
|
||||||
|
|
||||||
|
if ( null === $intervals ) {
|
||||||
|
return new WP_Error( 'woocommerce_reports_revenue_result_failed', __( 'Sorry, fetching revenue data failed.', 'woocommerce' ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = (object) array(
|
||||||
|
'totals' => $totals,
|
||||||
|
'intervals' => $intervals,
|
||||||
|
'total' => $expected_interval_count,
|
||||||
|
'pages' => $total_pages,
|
||||||
|
'page_no' => (int) $query_args['page'],
|
||||||
|
);
|
||||||
|
|
||||||
|
if ( $this->intervals_missing( $expected_interval_count, $db_interval_count, $intervals_query['per_page'], $query_args['page'], $query_args['order'], $query_args['orderby'], count( $intervals ) ) ) {
|
||||||
|
$this->fill_in_missing_intervals( $db_intervals, $query_args['adj_after'], $query_args['adj_before'], $query_args['interval'], $data );
|
||||||
|
$this->sort_intervals( $data, $query_args['orderby'], $query_args['order'] );
|
||||||
|
$this->remove_extra_records( $data, $query_args['page'], $intervals_query['per_page'], $db_interval_count, $expected_interval_count, $query_args['orderby'] );
|
||||||
|
} else {
|
||||||
|
$this->update_interval_boundary_dates( $query_args['adj_after'], $query_args['adj_before'], $query_args['interval'], $data->intervals );
|
||||||
|
}
|
||||||
|
$this->create_interval_subtotals( $data->intervals );
|
||||||
|
|
||||||
|
wp_cache_set( $cache_key, $data, $this->cache_group );
|
||||||
|
}
|
||||||
|
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Queue a background process that will repopulate the entire orders stats database.
|
||||||
|
*
|
||||||
|
* @todo Make this work on large DBs.
|
||||||
|
*/
|
||||||
|
public static function queue_order_stats_repopulate_database() {
|
||||||
|
|
||||||
|
// This needs to be updated to work in batches instead of getting all orders, as
|
||||||
|
// that will not work well on DBs with more than a few hundred orders.
|
||||||
|
$order_ids = wc_get_orders( array(
|
||||||
|
'limit' => -1,
|
||||||
|
'status' => parent::get_report_order_statuses(),
|
||||||
|
'type' => 'shop_order',
|
||||||
|
'return' => 'ids',
|
||||||
|
) );
|
||||||
|
|
||||||
|
foreach ( $order_ids as $id ) {
|
||||||
|
self::$background_process->push_to_queue( $id );
|
||||||
|
}
|
||||||
|
|
||||||
|
self::$background_process->save();
|
||||||
|
self::$background_process->dispatch();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add order information to the lookup table when orders are created or modified.
|
||||||
|
*
|
||||||
|
* @param int $post_id Post ID.
|
||||||
|
*/
|
||||||
|
public static function sync_order( $post_id ) {
|
||||||
|
if ( 'shop_order' !== get_post_type( $post_id ) ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$order = wc_get_order( $post_id );
|
||||||
|
if ( ! $order ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self::update( $order );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the database with stats data.
|
||||||
|
*
|
||||||
|
* @param WC_Order $order Order to update row for.
|
||||||
|
* @return int|bool Number or rows modified or false on failure.
|
||||||
|
*/
|
||||||
|
public static function update( $order ) {
|
||||||
|
global $wpdb;
|
||||||
|
$table_name = $wpdb->prefix . self::TABLE_NAME;
|
||||||
|
|
||||||
|
if ( ! $order->get_id() || ! $order->get_date_created() ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( ! in_array( $order->get_status(), parent::get_report_order_statuses(), true ) ) {
|
||||||
|
$wpdb->delete( $table_name, array(
|
||||||
|
'order_id' => $order->get_id(),
|
||||||
|
) );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = array(
|
||||||
|
'order_id' => $order->get_id(),
|
||||||
|
'date_created' => $order->get_date_created()->date( 'Y-m-d H:i:s' ),
|
||||||
|
'num_items_sold' => self::get_num_items_sold( $order ),
|
||||||
|
'gross_total' => $order->get_total(),
|
||||||
|
'coupon_total' => $order->get_total_discount(),
|
||||||
|
'refund_total' => $order->get_total_refunded(),
|
||||||
|
'tax_total' => $order->get_total_tax(),
|
||||||
|
'shipping_total' => $order->get_shipping_total(),
|
||||||
|
'net_total' => $order->get_total() - $order->get_total_tax() - $order->get_shipping_total(),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Update or add the information to the DB.
|
||||||
|
return $wpdb->replace(
|
||||||
|
$table_name,
|
||||||
|
$data,
|
||||||
|
array(
|
||||||
|
'%d',
|
||||||
|
'%s',
|
||||||
|
'%d',
|
||||||
|
'%f',
|
||||||
|
'%f',
|
||||||
|
'%f',
|
||||||
|
'%f',
|
||||||
|
'%f',
|
||||||
|
'%f',
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculation methods.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get number of items sold among all orders.
|
||||||
|
*
|
||||||
|
* @param array $order WC_Order object.
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
protected static function get_num_items_sold( $order ) {
|
||||||
|
$num_items = 0;
|
||||||
|
|
||||||
|
$line_items = $order->get_items( 'line_item' );
|
||||||
|
foreach ( $line_items as $line_item ) {
|
||||||
|
$num_items += $line_item->get_quantity();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $num_items;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,228 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* WC_Admin_Reports_Products_Data_Store class file.
|
||||||
|
*
|
||||||
|
* @package WooCommerce Admin/Classes
|
||||||
|
*/
|
||||||
|
|
||||||
|
defined( 'ABSPATH' ) || exit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* WC_Admin_Reports_Products_Data_Store.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
class WC_Admin_Reports_Products_Data_Store extends WC_Admin_Reports_Data_Store implements WC_Admin_Reports_Data_Store_Interface {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Table used to get the data.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
const TABLE_NAME = 'wc_admin_order_product_lookup';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mapping columns to data type to return correct response types.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $column_types = array(
|
||||||
|
'date_start' => 'strval',
|
||||||
|
'date_end' => 'strval',
|
||||||
|
'product_id' => 'intval',
|
||||||
|
'items_sold' => 'intval',
|
||||||
|
'gross_revenue' => 'floatval',
|
||||||
|
'orders_count' => 'intval',
|
||||||
|
// Extended attributes.
|
||||||
|
'name' => 'strval',
|
||||||
|
'price' => 'floatval',
|
||||||
|
'image' => 'strval',
|
||||||
|
'permalink' => 'strval',
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SQL columns to select in the db query and their mapping to SQL code.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $report_columns = array(
|
||||||
|
'product_id' => 'product_id',
|
||||||
|
'items_sold' => 'SUM(product_qty) as items_sold',
|
||||||
|
'gross_revenue' => 'SUM(product_gross_revenue) AS gross_revenue',
|
||||||
|
'orders_count' => 'COUNT(DISTINCT order_id) as orders_count',
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extended product attributes to include in the data.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $extended_attributes = array(
|
||||||
|
'name',
|
||||||
|
'price',
|
||||||
|
'image',
|
||||||
|
'permalink',
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the database query with parameters used for Products report: categories and order status.
|
||||||
|
*
|
||||||
|
* @param array $query_args Query arguments supplied by the user.
|
||||||
|
* @return array Array of parameters used for SQL query.
|
||||||
|
*/
|
||||||
|
protected function get_sql_query_params( $query_args ) {
|
||||||
|
global $wpdb;
|
||||||
|
|
||||||
|
$sql_query_params = $this->get_time_period_sql_params( $query_args );
|
||||||
|
$sql_query_params = array_merge( $sql_query_params, $this->get_limit_sql_params( $query_args ) );
|
||||||
|
$sql_query_params = array_merge( $sql_query_params, $this->get_order_by_sql_params( $query_args ) );
|
||||||
|
|
||||||
|
$order_product_lookup_table = $wpdb->prefix . self::TABLE_NAME;
|
||||||
|
$allowed_products = $this->get_allowed_products( $query_args );
|
||||||
|
|
||||||
|
if ( count( $allowed_products ) > 0 ) {
|
||||||
|
$allowed_products_str = implode( ',', $allowed_products );
|
||||||
|
$sql_query_params['where_clause'] .= " AND {$order_product_lookup_table}.product_id IN ({$allowed_products_str})";
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( is_array( $query_args['order_status'] ) && count( $query_args['order_status'] ) > 0 ) {
|
||||||
|
$statuses = array_map( array( $this, 'normalize_order_status' ), $query_args['order_status'] );
|
||||||
|
|
||||||
|
$sql_query_params['from_clause'] .= " JOIN {$wpdb->prefix}posts ON {$order_product_lookup_table}.order_id = {$wpdb->prefix}posts.ID";
|
||||||
|
$sql_query_params['where_clause'] .= " AND {$wpdb->prefix}posts.post_status IN ( '" . implode( "','", $statuses ) . "' ) ";
|
||||||
|
}
|
||||||
|
|
||||||
|
return $sql_query_params;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps ordering specified by the user to columns in the database/fields in the data.
|
||||||
|
*
|
||||||
|
* @param string $order_by Sorting criterion.
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected function normalize_order_by( $order_by ) {
|
||||||
|
if ( 'date' === $order_by ) {
|
||||||
|
return 'date_created';
|
||||||
|
}
|
||||||
|
|
||||||
|
return $order_by;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enriches the product data with attributes specified by the extended_attributes.
|
||||||
|
*
|
||||||
|
* @param array $products_data Product data.
|
||||||
|
*/
|
||||||
|
protected function include_extended_product_info( &$products_data ) {
|
||||||
|
foreach ( $products_data as $key => $product_data ) {
|
||||||
|
$product = wc_get_product( $product_data['product_id'] );
|
||||||
|
$extended_attributes = apply_filters( 'woocommerce_rest_reports_products_extended_attributes', $this->extended_attributes, $product_data );
|
||||||
|
foreach ( $extended_attributes as $extended_attribute ) {
|
||||||
|
$function = 'get_' . $extended_attribute;
|
||||||
|
if ( is_callable( array( $product, $function ) ) ) {
|
||||||
|
$value = $product->{$function}();
|
||||||
|
$products_data[ $key ][ $extended_attribute ] = $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the report data based on parameters supplied by the user.
|
||||||
|
*
|
||||||
|
* @param array $query_args Query parameters.
|
||||||
|
* @return stdClass|WP_Error Data.
|
||||||
|
*/
|
||||||
|
public function get_data( $query_args ) {
|
||||||
|
global $wpdb;
|
||||||
|
|
||||||
|
$table_name = $wpdb->prefix . self::TABLE_NAME;
|
||||||
|
$now = time();
|
||||||
|
$week_back = $now - WEEK_IN_SECONDS;
|
||||||
|
|
||||||
|
// These defaults are only partially applied when used via REST API, as that has its own defaults.
|
||||||
|
$defaults = array(
|
||||||
|
'per_page' => get_option( 'posts_per_page' ),
|
||||||
|
'page' => 1,
|
||||||
|
'order' => 'DESC',
|
||||||
|
'orderby' => 'date',
|
||||||
|
'before' => date( WC_Admin_Reports_Interval::$iso_datetime_format, $now ),
|
||||||
|
'after' => date( WC_Admin_Reports_Interval::$iso_datetime_format, $week_back ),
|
||||||
|
'fields' => '*',
|
||||||
|
'categories' => array(),
|
||||||
|
'products' => array(),
|
||||||
|
'extended_product_info' => false,
|
||||||
|
// This is not a parameter for products reports per se, but we want to only take into account selected order types.
|
||||||
|
'order_status' => parent::get_report_order_statuses(),
|
||||||
|
|
||||||
|
);
|
||||||
|
$query_args = wp_parse_args( $query_args, $defaults );
|
||||||
|
|
||||||
|
$cache_key = $this->get_cache_key( $query_args );
|
||||||
|
$data = wp_cache_get( $cache_key, $this->cache_group );
|
||||||
|
|
||||||
|
if ( false === $data ) {
|
||||||
|
|
||||||
|
$selections = $this->selected_columns( $query_args );
|
||||||
|
$sql_query_params = $this->get_sql_query_params( $query_args );
|
||||||
|
|
||||||
|
$db_records_count = (int) $wpdb->get_var(
|
||||||
|
"SELECT COUNT(*) FROM (
|
||||||
|
SELECT
|
||||||
|
product_id
|
||||||
|
FROM
|
||||||
|
{$table_name}
|
||||||
|
{$sql_query_params['from_clause']}
|
||||||
|
WHERE
|
||||||
|
1=1
|
||||||
|
{$sql_query_params['where_clause']}
|
||||||
|
GROUP BY
|
||||||
|
product_id
|
||||||
|
) AS tt"
|
||||||
|
); // WPCS: cache ok, DB call ok, unprepared SQL ok.
|
||||||
|
|
||||||
|
$total_pages = (int) ceil( $db_records_count / $sql_query_params['per_page'] );
|
||||||
|
if ( $query_args['page'] < 1 || $query_args['page'] > $total_pages ) {
|
||||||
|
return array();
|
||||||
|
}
|
||||||
|
|
||||||
|
$product_data = $wpdb->get_results(
|
||||||
|
"SELECT
|
||||||
|
{$selections}
|
||||||
|
FROM
|
||||||
|
{$table_name}
|
||||||
|
{$sql_query_params['from_clause']}
|
||||||
|
WHERE
|
||||||
|
1=1
|
||||||
|
{$sql_query_params['where_clause']}
|
||||||
|
GROUP BY
|
||||||
|
product_id
|
||||||
|
ORDER BY
|
||||||
|
{$sql_query_params['order_by_clause']}
|
||||||
|
{$sql_query_params['limit']}
|
||||||
|
", ARRAY_A
|
||||||
|
); // WPCS: cache ok, DB call ok, unprepared SQL ok.
|
||||||
|
|
||||||
|
if ( null === $product_data ) {
|
||||||
|
return new WP_Error( 'woocommerce_reports_products_result_failed', __( 'Sorry, fetching revenue data failed.', 'woocommerce' ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( $query_args['extended_product_info'] ) {
|
||||||
|
$this->include_extended_product_info( $product_data );
|
||||||
|
}
|
||||||
|
$product_data = array_map( array( $this, 'cast_numbers' ), $product_data );
|
||||||
|
$data = (object) array(
|
||||||
|
'data' => $product_data,
|
||||||
|
'total' => $db_records_count,
|
||||||
|
'pages' => $total_pages,
|
||||||
|
'page_no' => (int) $query_args['page'],
|
||||||
|
);
|
||||||
|
|
||||||
|
wp_cache_set( $cache_key, $data, $this->cache_group );
|
||||||
|
}
|
||||||
|
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,200 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* WC_Admin_Reports_Products_Stats_Data_Store class file.
|
||||||
|
*
|
||||||
|
* @package WooCommerce Admin/Classes
|
||||||
|
*/
|
||||||
|
|
||||||
|
defined( 'ABSPATH' ) || exit;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* WC_Reports_Products_Stats_Data_Store.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
class WC_Admin_Reports_Products_Stats_Data_Store extends WC_Admin_Reports_Products_Data_Store implements WC_Admin_Reports_Data_Store_Interface {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mapping columns to data type to return correct response types.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $column_types = array(
|
||||||
|
'date_start' => 'strval',
|
||||||
|
'date_end' => 'strval',
|
||||||
|
'product_id' => 'intval',
|
||||||
|
'items_sold' => 'intval',
|
||||||
|
'gross_revenue' => 'floatval',
|
||||||
|
'orders_count' => 'intval',
|
||||||
|
'products_count' => 'intval',
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SQL columns to select in the db query.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $report_columns = array(
|
||||||
|
'items_sold' => 'SUM(product_qty) as items_sold',
|
||||||
|
'gross_revenue' => 'SUM(product_gross_revenue) AS gross_revenue',
|
||||||
|
'orders_count' => 'COUNT(DISTINCT order_id) as orders_count',
|
||||||
|
'products_count' => 'COUNT(DISTINCT product_id) as products_count',
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the database query with parameters used for Products Stats report: categories and order status.
|
||||||
|
*
|
||||||
|
* @param array $query_args Query arguments supplied by the user.
|
||||||
|
* @param array $totals_params SQL parameters for the totals query.
|
||||||
|
* @param array $intervals_params SQL parameters for the intervals query.
|
||||||
|
*/
|
||||||
|
protected function update_sql_query_params( $query_args, &$totals_params, &$intervals_params ) {
|
||||||
|
global $wpdb;
|
||||||
|
|
||||||
|
$allowed_products = $this->get_allowed_products( $query_args );
|
||||||
|
$products_where_clause = '';
|
||||||
|
$products_from_clause = '';
|
||||||
|
|
||||||
|
$order_product_lookup_table = $wpdb->prefix . self::TABLE_NAME;
|
||||||
|
if ( count( $allowed_products ) > 0 ) {
|
||||||
|
$allowed_products_str = implode( ',', $allowed_products );
|
||||||
|
$products_where_clause .= " AND {$order_product_lookup_table}.product_id IN ({$allowed_products_str})";
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( is_array( $query_args['order_status'] ) && count( $query_args['order_status'] ) > 0 ) {
|
||||||
|
$statuses = array_map( array( $this, 'normalize_order_status' ), $query_args['order_status'] );
|
||||||
|
|
||||||
|
$products_from_clause .= " JOIN {$wpdb->prefix}posts ON {$order_product_lookup_table}.order_id = {$wpdb->prefix}posts.ID";
|
||||||
|
$products_where_clause .= " AND {$wpdb->prefix}posts.post_status IN ( '" . implode( "','", $statuses ) . "' ) ";
|
||||||
|
}
|
||||||
|
|
||||||
|
$totals_params = array_merge( $totals_params, $this->get_time_period_sql_params( $query_args ) );
|
||||||
|
$totals_params['where_clause'] .= $products_where_clause;
|
||||||
|
$totals_params['from_clause'] .= $products_from_clause;
|
||||||
|
|
||||||
|
$intervals_params = array_merge( $intervals_params, $this->get_intervals_sql_params( $query_args ) );
|
||||||
|
$intervals_params['where_clause'] .= $products_where_clause;
|
||||||
|
$intervals_params['from_clause'] .= $products_from_clause;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the report data based on parameters supplied by the user.
|
||||||
|
*
|
||||||
|
* @since 3.5.0
|
||||||
|
* @param array $query_args Query parameters.
|
||||||
|
* @return stdClass|WP_Error Data.
|
||||||
|
*/
|
||||||
|
public function get_data( $query_args ) {
|
||||||
|
global $wpdb;
|
||||||
|
|
||||||
|
$table_name = $wpdb->prefix . self::TABLE_NAME;
|
||||||
|
$now = time();
|
||||||
|
$week_back = $now - WEEK_IN_SECONDS;
|
||||||
|
|
||||||
|
// These defaults are only partially applied when used via REST API, as that has its own defaults.
|
||||||
|
$defaults = array(
|
||||||
|
'per_page' => get_option( 'posts_per_page' ),
|
||||||
|
'page' => 1,
|
||||||
|
'order' => 'DESC',
|
||||||
|
'orderby' => 'date',
|
||||||
|
'before' => date( WC_Admin_Reports_Interval::$iso_datetime_format, $now ),
|
||||||
|
'after' => date( WC_Admin_Reports_Interval::$iso_datetime_format, $week_back ),
|
||||||
|
'fields' => '*',
|
||||||
|
'categories' => array(),
|
||||||
|
'interval' => 'week',
|
||||||
|
'products' => array(),
|
||||||
|
// This is not a parameter for products reports per se, but we should probably restricts order statuses here, too.
|
||||||
|
'order_status' => parent::get_report_order_statuses(),
|
||||||
|
);
|
||||||
|
$query_args = wp_parse_args( $query_args, $defaults );
|
||||||
|
|
||||||
|
$cache_key = $this->get_cache_key( $query_args );
|
||||||
|
$product_data = wp_cache_get( $cache_key, $this->cache_group );
|
||||||
|
|
||||||
|
if ( false === $product_data ) {
|
||||||
|
$selections = $this->selected_columns( $query_args );
|
||||||
|
$totals_query = array();
|
||||||
|
$intervals_query = array();
|
||||||
|
$this->update_sql_query_params( $query_args, $totals_query, $intervals_query );
|
||||||
|
|
||||||
|
$db_records_count = (int) $wpdb->get_var(
|
||||||
|
"SELECT COUNT(*) FROM (
|
||||||
|
SELECT
|
||||||
|
{$intervals_query['select_clause']} AS time_interval
|
||||||
|
FROM
|
||||||
|
{$table_name}
|
||||||
|
{$intervals_query['from_clause']}
|
||||||
|
WHERE
|
||||||
|
1=1
|
||||||
|
{$intervals_query['where_clause']}
|
||||||
|
GROUP BY
|
||||||
|
time_interval
|
||||||
|
) AS t"
|
||||||
|
); // WPCS: cache ok, DB call ok, unprepared SQL ok.
|
||||||
|
|
||||||
|
$total_pages = (int) ceil( $db_records_count / $intervals_query['per_page'] );
|
||||||
|
if ( $query_args['page'] < 1 || $query_args['page'] > $total_pages ) {
|
||||||
|
return array();
|
||||||
|
}
|
||||||
|
|
||||||
|
$totals = $wpdb->get_results(
|
||||||
|
"SELECT
|
||||||
|
{$selections}
|
||||||
|
FROM
|
||||||
|
{$table_name}
|
||||||
|
{$totals_query['from_clause']}
|
||||||
|
WHERE
|
||||||
|
1=1
|
||||||
|
{$totals_query['where_clause']}", ARRAY_A
|
||||||
|
); // WPCS: cache ok, DB call ok, unprepared SQL ok.
|
||||||
|
|
||||||
|
if ( null === $totals ) {
|
||||||
|
return new WP_Error( 'woocommerce_reports_products_stats_result_failed', __( 'Sorry, fetching revenue data failed.', 'woocommerce' ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( '' !== $selections ) {
|
||||||
|
$selections = ', ' . $selections;
|
||||||
|
}
|
||||||
|
|
||||||
|
$intervals = $wpdb->get_results(
|
||||||
|
"SELECT
|
||||||
|
MAX(date_created) AS datetime_anchor,
|
||||||
|
{$intervals_query['select_clause']} AS time_interval
|
||||||
|
{$selections}
|
||||||
|
FROM
|
||||||
|
{$table_name}
|
||||||
|
{$intervals_query['from_clause']}
|
||||||
|
WHERE
|
||||||
|
1=1
|
||||||
|
{$intervals_query['where_clause']}
|
||||||
|
GROUP BY
|
||||||
|
time_interval
|
||||||
|
ORDER BY
|
||||||
|
{$intervals_query['order_by_clause']}
|
||||||
|
{$intervals_query['limit']}", ARRAY_A
|
||||||
|
); // WPCS: cache ok, DB call ok, unprepared SQL ok.
|
||||||
|
|
||||||
|
if ( null === $intervals ) {
|
||||||
|
return new WP_Error( 'woocommerce_reports_products_stats_result_failed', __( 'Sorry, fetching revenue data failed.', 'woocommerce' ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
$totals = (object) $this->cast_numbers( $totals[0] );
|
||||||
|
|
||||||
|
$this->update_interval_boundary_dates( $query_args['after'], $query_args['before'], $query_args['interval'], $intervals );
|
||||||
|
$this->create_interval_subtotals( $intervals );
|
||||||
|
|
||||||
|
$data = (object) array(
|
||||||
|
'totals' => $totals,
|
||||||
|
'intervals' => $intervals,
|
||||||
|
'total' => $db_records_count,
|
||||||
|
'pages' => $total_pages,
|
||||||
|
'page_no' => (int) $query_args['page'],
|
||||||
|
);
|
||||||
|
|
||||||
|
wp_cache_set( $cache_key, $data, $this->cache_group );
|
||||||
|
}
|
||||||
|
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Reports Data Store Interface
|
||||||
|
*
|
||||||
|
* @version 3.5.0
|
||||||
|
* @package WooCommerce/Interface
|
||||||
|
*/
|
||||||
|
|
||||||
|
if ( ! defined( 'ABSPATH' ) ) {
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* WooCommerce Reports data store interface.
|
||||||
|
*
|
||||||
|
* @since 3.5.0
|
||||||
|
*/
|
||||||
|
interface WC_Reports_Data_Store_Interface {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the data based on args.
|
||||||
|
*
|
||||||
|
* @param array $args Query parameters.
|
||||||
|
* @return stdClass|WP_Error
|
||||||
|
*/
|
||||||
|
public function get_data( $args );
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make an entry in the wc_admin_order_product_lookup table for an order.
|
||||||
|
*
|
||||||
|
* @since 3.5.0
|
||||||
|
* @param int $order_id Order ID.
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
function wc_admin_order_product_lookup_entry( $order_id ) {
|
||||||
|
global $wpdb;
|
||||||
|
|
||||||
|
$order = wc_get_order( $order_id );
|
||||||
|
if ( ! $order ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ( $order->get_items() as $order_item ) {
|
||||||
|
$wpdb->replace(
|
||||||
|
$wpdb->prefix . 'wc_admin_order_product_lookup',
|
||||||
|
array(
|
||||||
|
'order_item_id' => $order_item->get_id(),
|
||||||
|
'order_id' => $order->get_id(),
|
||||||
|
'product_id' => $order_item->get_product_id( 'edit' ),
|
||||||
|
'customer_id' => ( 0 < $order->get_customer_id( 'edit' ) ) ? $order->get_customer_id( 'edit' ) : null,
|
||||||
|
'product_qty' => $order_item->get_quantity( 'edit' ),
|
||||||
|
'product_gross_revenue' => $order_item->get_subtotal( 'edit' ),
|
||||||
|
'date_created' => date( 'Y-m-d H:i:s', $order->get_date_created( 'edit' )->getTimestamp() ),
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
'%d',
|
||||||
|
'%d',
|
||||||
|
'%d',
|
||||||
|
'%d',
|
||||||
|
'%d',
|
||||||
|
'%f',
|
||||||
|
'%s',
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
add_action( 'save_post', 'wc_admin_order_product_lookup_entry', 10, 1 );
|
|
@ -40,6 +40,9 @@ function wc_admin_plugins_loaded() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Initialize the WC API extensions.
|
||||||
|
require_once dirname( __FILE__ ) . 'includes/class-wc-admin-api-init.php';
|
||||||
|
|
||||||
// Some common utilities
|
// Some common utilities
|
||||||
require_once dirname( __FILE__ ) . '/lib/common.php';
|
require_once dirname( __FILE__ ) . '/lib/common.php';
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue