Reduce duplicated code in Analytics classes (#49425)
- Add code docs to the Analytics classes and make get_order_statuses non-internal.
Document analytics classes and some methods. Add simple examples of how to use it. Give a brief "why?" and "when?" answers. Add links between related classes.
- Replace `Automattic\WooCommerce\Admin\API\Reports\*\Query` classes with a single `GenericQuery` class.
- Reduce the amount of duplicated code in Analytics `DataStore`s.
- Reduce duplicated code in Analytics controllers
- Add `FilteredGetDataTrait`, `OrderAwareControllerTrait`, and `StatsDataStoreTrait` for extension developers to reuse while creating custom Analytics
- Add a `GenericQuery` to reduce duplicated code in Query classes.
Also, to expose it to community extensions for reuse.
- Use `GenericQuery` instead of duplicated `Query` classes
- Move caching code to shared `DataStore::get_data`
- Move intervals specific `initialize_queries` to shared Trait
- Move intervals checking logic to shared `StatsDataStoreTrait`
- Reuse `GenericController::prepare_item_for_response` for `Reports\Controller` subclasses
- Reuse `GenericController::get_collection_params` for `Reports\Controller` subclasses.
- Move `get_items` code to shared `GenericStatsController`
- Move shared paginable controllers `get_items` code to the generic class
- Move fields param to `GenericStatsController`
- Separate `OrderAwareControllerTrait` from `ReportController`
to allow specific Analytics Controllers to extend the `Generic(Stats)Controller` directly, without extending the `ReportController`, which is used to list reports.
It's meant not to change any behavior. However, due to unification, it did tweak a few things mostly from the developer perspective:
- Previously, only some controllers threw an error from `get_items` when the data was missing; some did not. Now controllers the following Controllers changed behavior to also for such an error:
- Coupons
- Customers
- Downloads
- Orders
- Products
- Taxes
- the error has one error code from all controllers:`woocommerce_rest_reports_invalid_response` (previously there were report-specific)
- `woocommerce_rest_reports_categories_invalid_response` -> `woocommerce_rest_reports_invalid_response`
- In Orders Controller, the `$orders_data['order_number'], $orders_data['total_formatted']` are set in the `prepare_item_for_response` method, not in the `get_items`So `prepare_item_for_response` function is expected to be called with bare data from Query
- In Products Controller extended-info is now sanitized in the `prepare_item_for_response` method, not in the `get_items`
- ⚠️ (breaking) Previosly some Controlers' `prepare_item_for_response` function was getting and expecting first argument to be casted to `object`, while the rest of controllers used data as returned from the Store. This PR unifies this behavior for
- `Coupons\Stats\Controller`
- `Taxes\Controller`
- `Taxes\Stats\Controller`
So, if you extend those classes and override `prepare_item_for_response` in a way that expects `object`, you will get an error. (I have not found such cases in the `all-plugins` repo.)
All `woocommerce_rest_prepare_report_*` filters remain intact and inconsistent for backward compatibility.
Co-authored-by: github-actions <github-actions@github.com>
Co-authored-by: Mik <mikkamp@users.noreply.github.com>
This commit is contained in:
parent
c738aeed17
commit
0322426dce
|
@ -22,7 +22,7 @@ The `SqlQuery` class is a SQL Query statement object. Its properties consist of
|
||||||
|
|
||||||
## Reports Data Stores
|
## Reports Data Stores
|
||||||
|
|
||||||
The base DataStore `Automattic\WooCommerce\Admin\API\Reports\DataStore` extends the `SqlQuery` class. The implementation data store classes use the following `SqlQuery` instances:
|
The base DataStore `Automattic\WooCommerce\Admin\API\Reports\DataStore` extends the `SqlQuery` class. There is `StatsDataStoreTrait` that adds Interval & Total Queries. The implementation data store classes use the following `SqlQuery` instances:
|
||||||
|
|
||||||
| Data Store | Context | Class Query | Sub Query | Interval Query | Total Query |
|
| Data Store | Context | Class Query | Sub Query | Interval Query | Total Query |
|
||||||
| ---------- | ------- | ----------- | --------- | -------------- | ----------- |
|
| ---------- | ------- | ----------- | --------- | -------------- | ----------- |
|
||||||
|
@ -40,6 +40,7 @@ The base DataStore `Automattic\WooCommerce\Admin\API\Reports\DataStore` extends
|
||||||
| Taxes | taxes | Yes | Yes | - | - |
|
| Taxes | taxes | Yes | Yes | - | - |
|
||||||
| Tax Stats | tax_stats | Yes | - | Yes | Yes |
|
| Tax Stats | tax_stats | Yes | - | Yes | Yes |
|
||||||
| Variations | variations | Yes | Yes | - | - |
|
| Variations | variations | Yes | Yes | - | - |
|
||||||
|
| StatsDataStoreTrait | n/a | n/a | - | Yes | Yes |
|
||||||
|
|
||||||
Query contexts are named as follows:
|
Query contexts are named as follows:
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
Significance: patch
|
||||||
|
Type: dev
|
||||||
|
|
||||||
|
Replace `Automattic\WooCommerce\Admin\API\Reports\*\Query` classes with a single `GenericQuery` class.
|
|
@ -0,0 +1,4 @@
|
||||||
|
Significance: patch
|
||||||
|
Type: dev
|
||||||
|
|
||||||
|
Reduce the amount of duplicated code in Analytics `DataStore`s.
|
|
@ -0,0 +1,4 @@
|
||||||
|
Significance: minor
|
||||||
|
Type: add
|
||||||
|
|
||||||
|
Add `FilteredGetDataTrait`, `OrderAwareControllerTrait`, and `StatsDataStoreTrait` for extension developers to reuse while creating custom Analytics
|
|
@ -0,0 +1,4 @@
|
||||||
|
Significance: patch
|
||||||
|
Type: dev
|
||||||
|
|
||||||
|
Added code docs with examples to the Analytics classes
|
|
@ -0,0 +1,4 @@
|
||||||
|
Significance: patch
|
||||||
|
Type: dev
|
||||||
|
|
||||||
|
Reduce duplicated code in Analytics controllers, unify their behavior and API.
|
|
@ -9,16 +9,20 @@ namespace Automattic\WooCommerce\Admin\API\Reports\Categories;
|
||||||
|
|
||||||
defined( 'ABSPATH' ) || exit;
|
defined( 'ABSPATH' ) || exit;
|
||||||
|
|
||||||
use Automattic\WooCommerce\Admin\API\Reports\Controller as ReportsController;
|
|
||||||
use Automattic\WooCommerce\Admin\API\Reports\ExportableInterface;
|
use Automattic\WooCommerce\Admin\API\Reports\ExportableInterface;
|
||||||
|
use Automattic\WooCommerce\Admin\API\Reports\GenericController;
|
||||||
|
use Automattic\WooCommerce\Admin\API\Reports\GenericQuery;
|
||||||
|
use Automattic\WooCommerce\Admin\API\Reports\OrderAwareControllerTrait;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* REST API Reports categories controller class.
|
* REST API Reports categories controller class.
|
||||||
*
|
*
|
||||||
* @internal
|
* @internal
|
||||||
* @extends \Automattic\WooCommerce\Admin\API\Reports\Controller
|
* @extends \Automattic\WooCommerce\Admin\API\Reports\GenericController
|
||||||
*/
|
*/
|
||||||
class Controller extends ReportsController implements ExportableInterface {
|
class Controller extends GenericController implements ExportableInterface {
|
||||||
|
|
||||||
|
use OrderAwareControllerTrait;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Route base.
|
* Route base.
|
||||||
|
@ -27,6 +31,19 @@ class Controller extends ReportsController implements ExportableInterface {
|
||||||
*/
|
*/
|
||||||
protected $rest_base = 'reports/categories';
|
protected $rest_base = 'reports/categories';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get data from `'categories'` Query.
|
||||||
|
*
|
||||||
|
* @override GenericController::get_datastore_data()
|
||||||
|
*
|
||||||
|
* @param array $query_args Query arguments.
|
||||||
|
* @return mixed Results from the data store.
|
||||||
|
*/
|
||||||
|
protected function get_datastore_data( $query_args = array() ) {
|
||||||
|
$query = new GenericQuery( $query_args, 'categories' );
|
||||||
|
return $query->get_data();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Maps query arguments from the REST request.
|
* Maps query arguments from the REST request.
|
||||||
*
|
*
|
||||||
|
@ -52,56 +69,15 @@ class Controller extends ReportsController implements ExportableInterface {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all reports.
|
* Prepare a report data item for serialization.
|
||||||
*
|
*
|
||||||
* @param WP_REST_Request $request Request data.
|
* @param mixed $report Report data item as returned from Data Store.
|
||||||
* @return array|WP_Error
|
* @param \WP_REST_Request $request Request object.
|
||||||
*/
|
* @return \WP_REST_Response
|
||||||
public function get_items( $request ) {
|
|
||||||
$query_args = $this->prepare_reports_query( $request );
|
|
||||||
$categories_query = new 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 );
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->add_pagination_headers(
|
|
||||||
$request,
|
|
||||||
$out_data,
|
|
||||||
(int) $report_data->total,
|
|
||||||
(int) $report_data->page_no,
|
|
||||||
(int) $report_data->pages
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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 ) {
|
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.
|
// Wrap the data in a response object.
|
||||||
$response = rest_ensure_response( $data );
|
$response = parent::prepare_item_for_response( $report, $request );
|
||||||
$response->add_links( $this->prepare_links( $report ) );
|
$response->add_links( $this->prepare_links( $report ) );
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -119,7 +95,7 @@ class Controller extends ReportsController implements ExportableInterface {
|
||||||
/**
|
/**
|
||||||
* Prepare links for the request.
|
* Prepare links for the request.
|
||||||
*
|
*
|
||||||
* @param \Automattic\WooCommerce\Admin\API\Reports\Query $object Object data.
|
* @param \Automattic\WooCommerce\Admin\API\Reports\GenericQuery $object Object data.
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
protected function prepare_links( $object ) {
|
protected function prepare_links( $object ) {
|
||||||
|
@ -193,59 +169,17 @@ class Controller extends ReportsController implements ExportableInterface {
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
public function get_collection_params() {
|
public function get_collection_params() {
|
||||||
$params = array();
|
$params = parent::get_collection_params();
|
||||||
$params['context'] = $this->get_context_param( array( 'default' => 'view' ) );
|
$params['orderby']['default'] = 'category_id';
|
||||||
$params['page'] = array(
|
$params['orderby']['enum'] = array(
|
||||||
'description' => __( 'Current page of the collection.', 'woocommerce' ),
|
'category_id',
|
||||||
'type' => 'integer',
|
'items_sold',
|
||||||
'default' => 1,
|
'net_revenue',
|
||||||
'sanitize_callback' => 'absint',
|
'orders_count',
|
||||||
'validate_callback' => 'rest_validate_request_arg',
|
'products_count',
|
||||||
'minimum' => 1,
|
'category',
|
||||||
);
|
);
|
||||||
$params['per_page'] = array(
|
$params['interval'] = 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' => 'category_id',
|
|
||||||
'enum' => array(
|
|
||||||
'category_id',
|
|
||||||
'items_sold',
|
|
||||||
'net_revenue',
|
|
||||||
'orders_count',
|
|
||||||
'products_count',
|
|
||||||
'category',
|
|
||||||
),
|
|
||||||
'validate_callback' => 'rest_validate_request_arg',
|
|
||||||
);
|
|
||||||
$params['interval'] = array(
|
|
||||||
'description' => __( 'Time interval to use for buckets in the returned data.', 'woocommerce' ),
|
'description' => __( 'Time interval to use for buckets in the returned data.', 'woocommerce' ),
|
||||||
'type' => 'string',
|
'type' => 'string',
|
||||||
'default' => 'week',
|
'default' => 'week',
|
||||||
|
@ -259,7 +193,7 @@ class Controller extends ReportsController implements ExportableInterface {
|
||||||
),
|
),
|
||||||
'validate_callback' => 'rest_validate_request_arg',
|
'validate_callback' => 'rest_validate_request_arg',
|
||||||
);
|
);
|
||||||
$params['status_is'] = array(
|
$params['status_is'] = array(
|
||||||
'description' => __( 'Limit result set to items that have the specified order status.', 'woocommerce' ),
|
'description' => __( 'Limit result set to items that have the specified order status.', 'woocommerce' ),
|
||||||
'type' => 'array',
|
'type' => 'array',
|
||||||
'sanitize_callback' => 'wp_parse_slug_list',
|
'sanitize_callback' => 'wp_parse_slug_list',
|
||||||
|
@ -269,7 +203,7 @@ class Controller extends ReportsController implements ExportableInterface {
|
||||||
'type' => 'string',
|
'type' => 'string',
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
$params['status_is_not'] = array(
|
$params['status_is_not'] = array(
|
||||||
'description' => __( 'Limit result set to items that don\'t have the specified order status.', 'woocommerce' ),
|
'description' => __( 'Limit result set to items that don\'t have the specified order status.', 'woocommerce' ),
|
||||||
'type' => 'array',
|
'type' => 'array',
|
||||||
'sanitize_callback' => 'wp_parse_slug_list',
|
'sanitize_callback' => 'wp_parse_slug_list',
|
||||||
|
@ -279,7 +213,7 @@ class Controller extends ReportsController implements ExportableInterface {
|
||||||
'type' => 'string',
|
'type' => 'string',
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
$params['categories'] = array(
|
$params['categories'] = array(
|
||||||
'description' => __( 'Limit result set to all items that have the specified term assigned in the categories taxonomy.', 'woocommerce' ),
|
'description' => __( 'Limit result set to all items that have the specified term assigned in the categories taxonomy.', 'woocommerce' ),
|
||||||
'type' => 'array',
|
'type' => 'array',
|
||||||
'sanitize_callback' => 'wp_parse_id_list',
|
'sanitize_callback' => 'wp_parse_id_list',
|
||||||
|
@ -288,19 +222,13 @@ class Controller extends ReportsController implements ExportableInterface {
|
||||||
'type' => 'integer',
|
'type' => 'integer',
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
$params['extended_info'] = array(
|
$params['extended_info'] = array(
|
||||||
'description' => __( 'Add additional piece of info about each category to the report.', 'woocommerce' ),
|
'description' => __( 'Add additional piece of info about each category to the report.', 'woocommerce' ),
|
||||||
'type' => 'boolean',
|
'type' => 'boolean',
|
||||||
'default' => false,
|
'default' => false,
|
||||||
'sanitize_callback' => 'wc_string_to_bool',
|
'sanitize_callback' => 'wc_string_to_bool',
|
||||||
'validate_callback' => 'rest_validate_request_arg',
|
'validate_callback' => 'rest_validate_request_arg',
|
||||||
);
|
);
|
||||||
$params['force_cache_refresh'] = array(
|
|
||||||
'description' => __( 'Force retrieval of fresh data instead of from the cache.', 'woocommerce' ),
|
|
||||||
'type' => 'boolean',
|
|
||||||
'sanitize_callback' => 'wp_validate_boolean',
|
|
||||||
'validate_callback' => 'rest_validate_request_arg',
|
|
||||||
);
|
|
||||||
|
|
||||||
return $params;
|
return $params;
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,6 @@ defined( 'ABSPATH' ) || exit;
|
||||||
|
|
||||||
use Automattic\WooCommerce\Admin\API\Reports\DataStore as ReportsDataStore;
|
use Automattic\WooCommerce\Admin\API\Reports\DataStore as ReportsDataStore;
|
||||||
use Automattic\WooCommerce\Admin\API\Reports\DataStoreInterface;
|
use Automattic\WooCommerce\Admin\API\Reports\DataStoreInterface;
|
||||||
use Automattic\WooCommerce\Admin\API\Reports\TimeInterval;
|
|
||||||
use Automattic\WooCommerce\Admin\API\Reports\SqlQuery;
|
use Automattic\WooCommerce\Admin\API\Reports\SqlQuery;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -20,6 +19,8 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||||
/**
|
/**
|
||||||
* Table used to get the data.
|
* Table used to get the data.
|
||||||
*
|
*
|
||||||
|
* @override ReportsDataStore::$table_name
|
||||||
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
protected static $table_name = 'wc_order_product_lookup';
|
protected static $table_name = 'wc_order_product_lookup';
|
||||||
|
@ -27,6 +28,8 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||||
/**
|
/**
|
||||||
* Cache identifier.
|
* Cache identifier.
|
||||||
*
|
*
|
||||||
|
* @override ReportsDataStore::$cache_key
|
||||||
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
protected $cache_key = 'categories';
|
protected $cache_key = 'categories';
|
||||||
|
@ -48,6 +51,8 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||||
/**
|
/**
|
||||||
* Mapping columns to data type to return correct response types.
|
* Mapping columns to data type to return correct response types.
|
||||||
*
|
*
|
||||||
|
* @override ReportsDataStore::$column_types
|
||||||
|
*
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
protected $column_types = array(
|
protected $column_types = array(
|
||||||
|
@ -61,12 +66,16 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||||
/**
|
/**
|
||||||
* Data store context used to pass to filters.
|
* Data store context used to pass to filters.
|
||||||
*
|
*
|
||||||
|
* @override ReportsDataStore::$context
|
||||||
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
protected $context = 'categories';
|
protected $context = 'categories';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Assign report columns once full table name has been assigned.
|
* Assign report columns once full table name has been assigned.
|
||||||
|
*
|
||||||
|
* @override ReportsDataStore::assign_report_columns()
|
||||||
*/
|
*/
|
||||||
protected function assign_report_columns() {
|
protected function assign_report_columns() {
|
||||||
$table_name = self::get_db_table_name();
|
$table_name = self::get_db_table_name();
|
||||||
|
@ -145,6 +154,8 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||||
/**
|
/**
|
||||||
* Maps ordering specified by the user to columns in the database/fields in the data.
|
* Maps ordering specified by the user to columns in the database/fields in the data.
|
||||||
*
|
*
|
||||||
|
* @override ReportsDataStore::normalize_order_by()
|
||||||
|
*
|
||||||
* @param string $order_by Sorting criterion.
|
* @param string $order_by Sorting criterion.
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
|
@ -201,104 +212,99 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the report data based on parameters supplied by the user.
|
* Get the default query arguments to be used by get_data().
|
||||||
|
* These defaults are only partially applied when used via REST API, as that has its own defaults.
|
||||||
*
|
*
|
||||||
* @param array $query_args Query parameters.
|
* @override ReportsDataStore::get_default_query_vars()
|
||||||
* @return stdClass|WP_Error Data.
|
*
|
||||||
|
* @return array Query parameters.
|
||||||
*/
|
*/
|
||||||
public function get_data( $query_args ) {
|
public function get_default_query_vars() {
|
||||||
|
$defaults = parent::get_default_query_vars();
|
||||||
|
$defaults['category_includes'] = array();
|
||||||
|
$defaults['extended_info'] = false;
|
||||||
|
|
||||||
|
return $defaults;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the report data based on normalized parameters.
|
||||||
|
* Will be called by `get_data` if there is no data in cache.
|
||||||
|
*
|
||||||
|
* @see get_data
|
||||||
|
* @override ReportsDataStore::get_noncached_data()
|
||||||
|
*
|
||||||
|
* @param array $query_args Query parameters.
|
||||||
|
* @return stdClass|WP_Error Data object `{ totals: *, intervals: array, total: int, pages: int, page_no: int }`, or error.
|
||||||
|
*/
|
||||||
|
public function get_noncached_data( $query_args ) {
|
||||||
global $wpdb;
|
global $wpdb;
|
||||||
|
|
||||||
$table_name = self::get_db_table_name();
|
$table_name = self::get_db_table_name();
|
||||||
|
$this->initialize_queries();
|
||||||
|
|
||||||
// These defaults are only partially applied when used via REST API, as that has its own defaults.
|
$data = (object) array(
|
||||||
$defaults = array(
|
'data' => array(),
|
||||||
'per_page' => get_option( 'posts_per_page' ),
|
'total' => 0,
|
||||||
'page' => 1,
|
'pages' => 0,
|
||||||
'order' => 'DESC',
|
'page_no' => 0,
|
||||||
'orderby' => 'date',
|
|
||||||
'before' => TimeInterval::default_before(),
|
|
||||||
'after' => TimeInterval::default_after(),
|
|
||||||
'fields' => '*',
|
|
||||||
'category_includes' => array(),
|
|
||||||
'extended_info' => false,
|
|
||||||
);
|
);
|
||||||
$query_args = wp_parse_args( $query_args, $defaults );
|
|
||||||
$this->normalize_timezones( $query_args, $defaults );
|
|
||||||
|
|
||||||
/*
|
$this->subquery->add_sql_clause( 'select', $this->selected_columns( $query_args ) );
|
||||||
* We need to get the cache key here because
|
$included_categories = $this->get_included_categories_array( $query_args );
|
||||||
* parent::update_intervals_sql_params() modifies $query_args.
|
$this->add_sql_query_params( $query_args );
|
||||||
*/
|
|
||||||
$cache_key = $this->get_cache_key( $query_args );
|
|
||||||
$data = $this->get_cached_data( $cache_key );
|
|
||||||
|
|
||||||
if ( false === $data ) {
|
if ( count( $included_categories ) > 0 ) {
|
||||||
$this->initialize_queries();
|
$fields = $this->get_fields( $query_args );
|
||||||
|
$ids_table = $this->get_ids_table( $included_categories, 'category_id' );
|
||||||
|
|
||||||
$data = (object) array(
|
$this->add_sql_clause( 'select', $this->format_join_selections( array_merge( array( 'category_id' ), $fields ), array( 'category_id' ) ) );
|
||||||
'data' => array(),
|
$this->add_sql_clause( 'from', '(' );
|
||||||
'total' => 0,
|
$this->add_sql_clause( 'from', $this->subquery->get_query_statement() );
|
||||||
'pages' => 0,
|
$this->add_sql_clause( 'from', ") AS {$table_name}" );
|
||||||
'page_no' => 0,
|
$this->add_sql_clause(
|
||||||
|
'right_join',
|
||||||
|
"RIGHT JOIN ( {$ids_table} ) AS default_results
|
||||||
|
ON default_results.category_id = {$table_name}.category_id"
|
||||||
);
|
);
|
||||||
|
|
||||||
$this->subquery->add_sql_clause( 'select', $this->selected_columns( $query_args ) );
|
$categories_query = $this->get_query_statement();
|
||||||
$included_categories = $this->get_included_categories_array( $query_args );
|
} else {
|
||||||
$this->add_sql_query_params( $query_args );
|
$this->subquery->add_sql_clause( 'order_by', $this->get_sql_clause( 'order_by' ) );
|
||||||
|
$categories_query = $this->subquery->get_query_statement();
|
||||||
if ( count( $included_categories ) > 0 ) {
|
|
||||||
$fields = $this->get_fields( $query_args );
|
|
||||||
$ids_table = $this->get_ids_table( $included_categories, 'category_id' );
|
|
||||||
|
|
||||||
$this->add_sql_clause( 'select', $this->format_join_selections( array_merge( array( 'category_id' ), $fields ), array( 'category_id' ) ) );
|
|
||||||
$this->add_sql_clause( 'from', '(' );
|
|
||||||
$this->add_sql_clause( 'from', $this->subquery->get_query_statement() );
|
|
||||||
$this->add_sql_clause( 'from', ") AS {$table_name}" );
|
|
||||||
$this->add_sql_clause(
|
|
||||||
'right_join',
|
|
||||||
"RIGHT JOIN ( {$ids_table} ) AS default_results
|
|
||||||
ON default_results.category_id = {$table_name}.category_id"
|
|
||||||
);
|
|
||||||
|
|
||||||
$categories_query = $this->get_query_statement();
|
|
||||||
} else {
|
|
||||||
$this->subquery->add_sql_clause( 'order_by', $this->get_sql_clause( 'order_by' ) );
|
|
||||||
$categories_query = $this->subquery->get_query_statement();
|
|
||||||
}
|
|
||||||
$categories_data = $wpdb->get_results(
|
|
||||||
$categories_query, // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
|
||||||
ARRAY_A
|
|
||||||
);
|
|
||||||
|
|
||||||
if ( null === $categories_data ) {
|
|
||||||
return new \WP_Error( 'woocommerce_analytics_categories_result_failed', __( 'Sorry, fetching revenue data failed.', 'woocommerce' ), array( 'status' => 500 ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
$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;
|
|
||||||
}
|
|
||||||
|
|
||||||
$categories_data = $this->page_records( $categories_data, $query_args['page'], $query_args['per_page'] );
|
|
||||||
$this->include_extended_info( $categories_data, $query_args );
|
|
||||||
$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'],
|
|
||||||
);
|
|
||||||
|
|
||||||
$this->set_cached_data( $cache_key, $data );
|
|
||||||
}
|
}
|
||||||
|
$categories_data = $wpdb->get_results(
|
||||||
|
$categories_query, // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||||
|
ARRAY_A
|
||||||
|
);
|
||||||
|
|
||||||
|
if ( null === $categories_data ) {
|
||||||
|
return new \WP_Error( 'woocommerce_analytics_categories_result_failed', __( 'Sorry, fetching revenue data failed.', 'woocommerce' ), array( 'status' => 500 ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
$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;
|
||||||
|
}
|
||||||
|
|
||||||
|
$categories_data = $this->page_records( $categories_data, $query_args['page'], $query_args['per_page'] );
|
||||||
|
$this->include_extended_info( $categories_data, $query_args );
|
||||||
|
$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'],
|
||||||
|
);
|
||||||
|
|
||||||
return $data;
|
return $data;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize query objects.
|
* Initialize query objects.
|
||||||
|
*
|
||||||
|
* @override ReportsDataStore::initialize_queries()
|
||||||
*/
|
*/
|
||||||
protected function initialize_queries() {
|
protected function initialize_queries() {
|
||||||
global $wpdb;
|
global $wpdb;
|
||||||
|
|
|
@ -21,7 +21,9 @@ defined( 'ABSPATH' ) || exit;
|
||||||
use Automattic\WooCommerce\Admin\API\Reports\Query as ReportsQuery;
|
use Automattic\WooCommerce\Admin\API\Reports\Query as ReportsQuery;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* API\Reports\Query
|
* API\Reports\Categories\Query
|
||||||
|
*
|
||||||
|
* @deprecated 9.3.0 Categories\Query class is deprecated, please use GenericQuery or \WC_Object_Query instead.
|
||||||
*/
|
*/
|
||||||
class Query extends ReportsQuery {
|
class Query extends ReportsQuery {
|
||||||
|
|
||||||
|
@ -30,6 +32,8 @@ class Query extends ReportsQuery {
|
||||||
/**
|
/**
|
||||||
* Valid fields for Categories report.
|
* Valid fields for Categories report.
|
||||||
*
|
*
|
||||||
|
* @deprecated 9.3.0 Categories\Query class is deprecated, please use GenericQuery or \WC_Object_Query instead.
|
||||||
|
*
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
protected function get_default_query_vars() {
|
protected function get_default_query_vars() {
|
||||||
|
@ -39,6 +43,8 @@ class Query extends ReportsQuery {
|
||||||
/**
|
/**
|
||||||
* Get categories data based on the current query vars.
|
* Get categories data based on the current query vars.
|
||||||
*
|
*
|
||||||
|
* @deprecated 9.3.0 Categories\Query class is deprecated, please use GenericQuery or \WC_Object_Query instead.
|
||||||
|
*
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
public function get_data() {
|
public function get_data() {
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
<?php
|
<?php
|
||||||
/**
|
/**
|
||||||
* REST API Reports controller extended by WC Admin plugin.
|
* REST API Reports controller extended to handle requests to the reports endpoint.
|
||||||
*
|
|
||||||
* Handles requests to the reports endpoint.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
namespace Automattic\WooCommerce\Admin\API\Reports;
|
namespace Automattic\WooCommerce\Admin\API\Reports;
|
||||||
|
@ -10,15 +8,20 @@ namespace Automattic\WooCommerce\Admin\API\Reports;
|
||||||
defined( 'ABSPATH' ) || exit;
|
defined( 'ABSPATH' ) || exit;
|
||||||
|
|
||||||
use Automattic\WooCommerce\Admin\API\Reports\GenericController;
|
use Automattic\WooCommerce\Admin\API\Reports\GenericController;
|
||||||
|
use Automattic\WooCommerce\Admin\API\Reports\OrderAwareControllerTrait;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* REST API Reports controller class.
|
* Reports controller class.
|
||||||
|
*
|
||||||
|
* Controller that handles the endpoint that returns all available analytics endpoints.
|
||||||
*
|
*
|
||||||
* @internal
|
* @internal
|
||||||
* @extends GenericController
|
* @extends GenericController
|
||||||
*/
|
*/
|
||||||
class Controller extends GenericController {
|
class Controller extends GenericController {
|
||||||
|
|
||||||
|
use OrderAwareControllerTrait;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all reports.
|
* Get all reports.
|
||||||
*
|
*
|
||||||
|
@ -135,71 +138,6 @@ class Controller extends GenericController {
|
||||||
return rest_ensure_response( $data );
|
return rest_ensure_response( $data );
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the order number for an order. If no filter is present for `woocommerce_order_number`, we can just return the ID.
|
|
||||||
* Returns the parent order number if the order is actually a refund.
|
|
||||||
*
|
|
||||||
* @param int $order_id Order ID.
|
|
||||||
* @return string|null The Order Number or null if the order doesn't exist.
|
|
||||||
*/
|
|
||||||
protected function get_order_number( $order_id ) {
|
|
||||||
$order = wc_get_order( $order_id );
|
|
||||||
|
|
||||||
if ( ! $this->is_valid_order( $order ) ) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( 'shop_order_refund' === $order->get_type() ) {
|
|
||||||
$order = wc_get_order( $order->get_parent_id() );
|
|
||||||
|
|
||||||
// If the parent order doesn't exist, return null.
|
|
||||||
if ( ! $this->is_valid_order( $order ) ) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( ! has_filter( 'woocommerce_order_number' ) ) {
|
|
||||||
return $order->get_id();
|
|
||||||
}
|
|
||||||
|
|
||||||
return $order->get_order_number();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether the order is valid.
|
|
||||||
*
|
|
||||||
* @param bool|WC_Order|WC_Order_Refund $order Order object.
|
|
||||||
* @return bool True if the order is valid, false otherwise.
|
|
||||||
*/
|
|
||||||
protected function is_valid_order( $order ) {
|
|
||||||
return $order instanceof \WC_Order || $order instanceof \WC_Order_Refund;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the order total with the related currency formatting.
|
|
||||||
* Returns the parent order total if the order is actually a refund.
|
|
||||||
*
|
|
||||||
* @param int $order_id Order ID.
|
|
||||||
* @return string|null The Order Number or null if the order doesn't exist.
|
|
||||||
*/
|
|
||||||
protected function get_total_formatted( $order_id ) {
|
|
||||||
$order = wc_get_order( $order_id );
|
|
||||||
|
|
||||||
if ( ! $this->is_valid_order( $order ) ) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( 'shop_order_refund' === $order->get_type() ) {
|
|
||||||
$order = wc_get_order( $order->get_parent_id() );
|
|
||||||
|
|
||||||
if ( ! $this->is_valid_order( $order ) ) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return wp_strip_all_tags( html_entity_decode( $order->get_formatted_order_total() ), true );
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prepare a report object for serialization.
|
* Prepare a report object for serialization.
|
||||||
*
|
*
|
||||||
|
@ -214,12 +152,8 @@ class Controller extends GenericController {
|
||||||
'path' => $report->path,
|
'path' => $report->path,
|
||||||
);
|
);
|
||||||
|
|
||||||
$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.
|
// Wrap the data in a response object.
|
||||||
$response = rest_ensure_response( $data );
|
$response = parent::prepare_item_for_response( $data, $request );
|
||||||
$response->add_links(
|
$response->add_links(
|
||||||
array(
|
array(
|
||||||
'self' => array(
|
'self' => array(
|
||||||
|
@ -249,6 +183,8 @@ class Controller extends GenericController {
|
||||||
/**
|
/**
|
||||||
* Get the Report's schema, conforming to JSON Schema.
|
* Get the Report's schema, conforming to JSON Schema.
|
||||||
*
|
*
|
||||||
|
* @override WP_REST_Controller::get_item_schema()
|
||||||
|
*
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
public function get_item_schema() {
|
public function get_item_schema() {
|
||||||
|
@ -291,42 +227,4 @@ class Controller extends GenericController {
|
||||||
'context' => $this->get_context_param( array( 'default' => 'view' ) ),
|
'context' => $this->get_context_param( array( 'default' => 'view' ) ),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get order statuses without prefixes.
|
|
||||||
* Includes unregistered statuses that have been marked "actionable".
|
|
||||||
*
|
|
||||||
* @internal
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public static function get_order_statuses() {
|
|
||||||
// Allow all statuses selected as "actionable" - this may include unregistered statuses.
|
|
||||||
// See: https://github.com/woocommerce/woocommerce-admin/issues/5592.
|
|
||||||
$actionable_statuses = get_option( 'woocommerce_actionable_order_statuses', array() );
|
|
||||||
|
|
||||||
// See WC_REST_Orders_V2_Controller::get_collection_params() re: any/trash statuses.
|
|
||||||
$registered_statuses = array_merge( array( 'any', 'trash' ), array_keys( self::get_order_status_labels() ) );
|
|
||||||
|
|
||||||
// Merge the status arrays (using flip to avoid array_unique()).
|
|
||||||
$allowed_statuses = array_keys( array_merge( array_flip( $registered_statuses ), array_flip( $actionable_statuses ) ) );
|
|
||||||
|
|
||||||
return $allowed_statuses;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get order statuses (and labels) without prefixes.
|
|
||||||
*
|
|
||||||
* @internal
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public static function get_order_status_labels() {
|
|
||||||
$order_statuses = array();
|
|
||||||
|
|
||||||
foreach ( wc_get_order_statuses() as $key => $label ) {
|
|
||||||
$new_key = str_replace( 'wc-', '', $key );
|
|
||||||
$order_statuses[ $new_key ] = $label;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $order_statuses;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ defined( 'ABSPATH' ) || exit;
|
||||||
|
|
||||||
use Automattic\WooCommerce\Admin\API\Reports\GenericController;
|
use Automattic\WooCommerce\Admin\API\Reports\GenericController;
|
||||||
use Automattic\WooCommerce\Admin\API\Reports\ExportableInterface;
|
use Automattic\WooCommerce\Admin\API\Reports\ExportableInterface;
|
||||||
|
use Automattic\WooCommerce\Admin\API\Reports\GenericQuery;
|
||||||
use WP_REST_Request;
|
use WP_REST_Request;
|
||||||
use WP_REST_Response;
|
use WP_REST_Response;
|
||||||
|
|
||||||
|
@ -29,6 +30,19 @@ class Controller extends GenericController implements ExportableInterface {
|
||||||
*/
|
*/
|
||||||
protected $rest_base = 'reports/coupons';
|
protected $rest_base = 'reports/coupons';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get data from `'coupons'` Query.
|
||||||
|
*
|
||||||
|
* @override GenericController::get_datastore_data()
|
||||||
|
*
|
||||||
|
* @param array $query_args Query arguments.
|
||||||
|
* @return mixed Results from the data store.
|
||||||
|
*/
|
||||||
|
protected function get_datastore_data( $query_args = array() ) {
|
||||||
|
$query = new GenericQuery( $query_args, 'coupons' );
|
||||||
|
return $query->get_data();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Maps query arguments from the REST request.
|
* Maps query arguments from the REST request.
|
||||||
*
|
*
|
||||||
|
@ -50,38 +64,11 @@ class Controller extends GenericController implements ExportableInterface {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all reports.
|
* Prepare a report data item for serialization.
|
||||||
*
|
*
|
||||||
* @param WP_REST_Request $request Request data.
|
* @param array $report Report data item as returned from Data Store.
|
||||||
* @return array|WP_Error
|
* @param \WP_REST_Request $request Request object.
|
||||||
*/
|
* @return \WP_REST_Response
|
||||||
public function get_items( $request ) {
|
|
||||||
$query_args = $this->prepare_reports_query( $request );
|
|
||||||
$coupons_query = new Query( $query_args );
|
|
||||||
$report_data = $coupons_query->get_data();
|
|
||||||
|
|
||||||
$data = array();
|
|
||||||
|
|
||||||
foreach ( $report_data->data as $coupons_data ) {
|
|
||||||
$item = $this->prepare_item_for_response( $coupons_data, $request );
|
|
||||||
$data[] = $this->prepare_response_for_collection( $item );
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->add_pagination_headers(
|
|
||||||
$request,
|
|
||||||
$data,
|
|
||||||
(int) $report_data->total,
|
|
||||||
(int) $report_data->page_no,
|
|
||||||
(int) $report_data->pages
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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 ) {
|
public function prepare_item_for_response( $report, $request ) {
|
||||||
$response = parent::prepare_item_for_response( $report, $request );
|
$response = parent::prepare_item_for_response( $report, $request );
|
||||||
|
|
|
@ -21,6 +21,8 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||||
/**
|
/**
|
||||||
* Table used to get the data.
|
* Table used to get the data.
|
||||||
*
|
*
|
||||||
|
* @override ReportsDataStore::$table_name
|
||||||
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
protected static $table_name = 'wc_order_coupon_lookup';
|
protected static $table_name = 'wc_order_coupon_lookup';
|
||||||
|
@ -28,6 +30,8 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||||
/**
|
/**
|
||||||
* Cache identifier.
|
* Cache identifier.
|
||||||
*
|
*
|
||||||
|
* @override ReportsDataStore::$cache_key
|
||||||
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
protected $cache_key = 'coupons';
|
protected $cache_key = 'coupons';
|
||||||
|
@ -35,6 +39,8 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||||
/**
|
/**
|
||||||
* Mapping columns to data type to return correct response types.
|
* Mapping columns to data type to return correct response types.
|
||||||
*
|
*
|
||||||
|
* @override ReportsDataStore::$column_types
|
||||||
|
*
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
protected $column_types = array(
|
protected $column_types = array(
|
||||||
|
@ -46,12 +52,16 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||||
/**
|
/**
|
||||||
* Data store context used to pass to filters.
|
* Data store context used to pass to filters.
|
||||||
*
|
*
|
||||||
|
* @override ReportsDataStore::$context
|
||||||
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
protected $context = 'coupons';
|
protected $context = 'coupons';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Assign report columns once full table name has been assigned.
|
* Assign report columns once full table name has been assigned.
|
||||||
|
*
|
||||||
|
* @override ReportsDataStore::assign_report_columns()
|
||||||
*/
|
*/
|
||||||
protected function assign_report_columns() {
|
protected function assign_report_columns() {
|
||||||
$table_name = self::get_db_table_name();
|
$table_name = self::get_db_table_name();
|
||||||
|
@ -148,6 +158,8 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||||
/**
|
/**
|
||||||
* Maps ordering specified by the user to columns in the database/fields in the data.
|
* Maps ordering specified by the user to columns in the database/fields in the data.
|
||||||
*
|
*
|
||||||
|
* @override ReportsDataStore::normalize_order_by()
|
||||||
|
*
|
||||||
* @param string $order_by Sorting criterion.
|
* @param string $order_by Sorting criterion.
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
|
@ -223,119 +235,6 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 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 = self::get_db_table_name();
|
|
||||||
|
|
||||||
// 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' => 'coupon_id',
|
|
||||||
'before' => TimeInterval::default_before(),
|
|
||||||
'after' => TimeInterval::default_after(),
|
|
||||||
'fields' => '*',
|
|
||||||
'coupons' => array(),
|
|
||||||
'extended_info' => false,
|
|
||||||
);
|
|
||||||
$query_args = wp_parse_args( $query_args, $defaults );
|
|
||||||
$this->normalize_timezones( $query_args, $defaults );
|
|
||||||
|
|
||||||
/*
|
|
||||||
* We need to get the cache key here because
|
|
||||||
* parent::update_intervals_sql_params() modifies $query_args.
|
|
||||||
*/
|
|
||||||
$cache_key = $this->get_cache_key( $query_args );
|
|
||||||
$data = $this->get_cached_data( $cache_key );
|
|
||||||
|
|
||||||
if ( false === $data ) {
|
|
||||||
$this->initialize_queries();
|
|
||||||
|
|
||||||
$data = (object) array(
|
|
||||||
'data' => array(),
|
|
||||||
'total' => 0,
|
|
||||||
'pages' => 0,
|
|
||||||
'page_no' => 0,
|
|
||||||
);
|
|
||||||
|
|
||||||
$selections = $this->selected_columns( $query_args );
|
|
||||||
$included_coupons = $this->get_included_coupons_array( $query_args );
|
|
||||||
$limit_params = $this->get_limit_params( $query_args );
|
|
||||||
$this->subquery->add_sql_clause( 'select', $selections );
|
|
||||||
$this->add_sql_query_params( $query_args );
|
|
||||||
|
|
||||||
if ( count( $included_coupons ) > 0 ) {
|
|
||||||
$total_results = count( $included_coupons );
|
|
||||||
$total_pages = (int) ceil( $total_results / $limit_params['per_page'] );
|
|
||||||
|
|
||||||
$fields = $this->get_fields( $query_args );
|
|
||||||
$ids_table = $this->get_ids_table( $included_coupons, 'coupon_id' );
|
|
||||||
|
|
||||||
$this->add_sql_clause( 'select', $this->format_join_selections( $fields, array( 'coupon_id' ) ) );
|
|
||||||
$this->add_sql_clause( 'from', '(' );
|
|
||||||
$this->add_sql_clause( 'from', $this->subquery->get_query_statement() );
|
|
||||||
$this->add_sql_clause( 'from', ") AS {$table_name}" );
|
|
||||||
$this->add_sql_clause(
|
|
||||||
'right_join',
|
|
||||||
"RIGHT JOIN ( {$ids_table} ) AS default_results
|
|
||||||
ON default_results.coupon_id = {$table_name}.coupon_id"
|
|
||||||
);
|
|
||||||
|
|
||||||
$coupons_query = $this->get_query_statement();
|
|
||||||
} else {
|
|
||||||
$this->subquery->add_sql_clause( 'order_by', $this->get_sql_clause( 'order_by' ) );
|
|
||||||
$this->subquery->add_sql_clause( 'limit', $this->get_sql_clause( 'limit' ) );
|
|
||||||
$coupons_query = $this->subquery->get_query_statement();
|
|
||||||
|
|
||||||
$this->subquery->clear_sql_clause( array( 'select', 'order_by', 'limit' ) );
|
|
||||||
$this->subquery->add_sql_clause( 'select', 'coupon_id' );
|
|
||||||
$coupon_subquery = "SELECT COUNT(*) FROM (
|
|
||||||
{$this->subquery->get_query_statement()}
|
|
||||||
) AS tt";
|
|
||||||
|
|
||||||
$db_records_count = (int) $wpdb->get_var(
|
|
||||||
$coupon_subquery // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
|
||||||
);
|
|
||||||
|
|
||||||
$total_results = $db_records_count;
|
|
||||||
$total_pages = (int) ceil( $db_records_count / $limit_params['per_page'] );
|
|
||||||
if ( $query_args['page'] < 1 || $query_args['page'] > $total_pages ) {
|
|
||||||
return $data;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$coupon_data = $wpdb->get_results(
|
|
||||||
$coupons_query, // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
|
||||||
ARRAY_A
|
|
||||||
);
|
|
||||||
if ( null === $coupon_data ) {
|
|
||||||
return $data;
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->include_extended_info( $coupon_data, $query_args );
|
|
||||||
|
|
||||||
$coupon_data = array_map( array( $this, 'cast_numbers' ), $coupon_data );
|
|
||||||
$data = (object) array(
|
|
||||||
'data' => $coupon_data,
|
|
||||||
'total' => $total_results,
|
|
||||||
'pages' => $total_pages,
|
|
||||||
'page_no' => (int) $query_args['page'],
|
|
||||||
);
|
|
||||||
|
|
||||||
$this->set_cached_data( $cache_key, $data );
|
|
||||||
}
|
|
||||||
|
|
||||||
return $data;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get coupon ID for an order.
|
* Get coupon ID for an order.
|
||||||
*
|
*
|
||||||
|
@ -363,6 +262,115 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||||
return wc_get_coupon_id_by_code( $coupon_item->get_code() );
|
return wc_get_coupon_id_by_code( $coupon_item->get_code() );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the default query arguments to be used by get_data().
|
||||||
|
* These defaults are only partially applied when used via REST API, as that has its own defaults.
|
||||||
|
*
|
||||||
|
* @override ReportsDataStore::get_default_query_vars()
|
||||||
|
*
|
||||||
|
* @return array Query parameters.
|
||||||
|
*/
|
||||||
|
public function get_default_query_vars() {
|
||||||
|
$defaults = parent::get_default_query_vars();
|
||||||
|
$defaults['orderby'] = 'coupon_id';
|
||||||
|
$defaults['coupons'] = array();
|
||||||
|
$defaults['extended_info'] = false;
|
||||||
|
|
||||||
|
return $defaults;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the report data based on normalized parameters.
|
||||||
|
* Will be called by `get_data` if there is no data in cache.
|
||||||
|
*
|
||||||
|
* @override ReportsDataStore::get_noncached_data()
|
||||||
|
*
|
||||||
|
* @see get_data
|
||||||
|
* @param array $query_args Query parameters.
|
||||||
|
* @return stdClass|WP_Error Data object `{ totals: *, intervals: array, total: int, pages: int, page_no: int }`, or error.
|
||||||
|
*/
|
||||||
|
public function get_noncached_data( $query_args ) {
|
||||||
|
global $wpdb;
|
||||||
|
|
||||||
|
$table_name = self::get_db_table_name();
|
||||||
|
|
||||||
|
$this->initialize_queries();
|
||||||
|
|
||||||
|
$data = (object) array(
|
||||||
|
'data' => array(),
|
||||||
|
'total' => 0,
|
||||||
|
'pages' => 0,
|
||||||
|
'page_no' => 0,
|
||||||
|
);
|
||||||
|
|
||||||
|
$selections = $this->selected_columns( $query_args );
|
||||||
|
$included_coupons = $this->get_included_coupons_array( $query_args );
|
||||||
|
$limit_params = $this->get_limit_params( $query_args );
|
||||||
|
$this->subquery->add_sql_clause( 'select', $selections );
|
||||||
|
$this->add_sql_query_params( $query_args );
|
||||||
|
|
||||||
|
if ( count( $included_coupons ) > 0 ) {
|
||||||
|
$total_results = count( $included_coupons );
|
||||||
|
$total_pages = (int) ceil( $total_results / $limit_params['per_page'] );
|
||||||
|
|
||||||
|
$fields = $this->get_fields( $query_args );
|
||||||
|
$ids_table = $this->get_ids_table( $included_coupons, 'coupon_id' );
|
||||||
|
|
||||||
|
$this->add_sql_clause( 'select', $this->format_join_selections( $fields, array( 'coupon_id' ) ) );
|
||||||
|
$this->add_sql_clause( 'from', '(' );
|
||||||
|
$this->add_sql_clause( 'from', $this->subquery->get_query_statement() );
|
||||||
|
$this->add_sql_clause( 'from', ") AS {$table_name}" );
|
||||||
|
$this->add_sql_clause(
|
||||||
|
'right_join',
|
||||||
|
"RIGHT JOIN ( {$ids_table} ) AS default_results
|
||||||
|
ON default_results.coupon_id = {$table_name}.coupon_id"
|
||||||
|
);
|
||||||
|
|
||||||
|
$coupons_query = $this->get_query_statement();
|
||||||
|
} else {
|
||||||
|
$this->subquery->add_sql_clause( 'order_by', $this->get_sql_clause( 'order_by' ) );
|
||||||
|
$this->subquery->add_sql_clause( 'limit', $this->get_sql_clause( 'limit' ) );
|
||||||
|
$coupons_query = $this->subquery->get_query_statement();
|
||||||
|
|
||||||
|
$this->subquery->clear_sql_clause( array( 'select', 'order_by', 'limit' ) );
|
||||||
|
$this->subquery->add_sql_clause( 'select', 'coupon_id' );
|
||||||
|
$coupon_subquery = "SELECT COUNT(*) FROM (
|
||||||
|
{$this->subquery->get_query_statement()}
|
||||||
|
) AS tt";
|
||||||
|
|
||||||
|
$db_records_count = (int) $wpdb->get_var(
|
||||||
|
$coupon_subquery // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||||
|
);
|
||||||
|
|
||||||
|
$total_results = $db_records_count;
|
||||||
|
$total_pages = (int) ceil( $db_records_count / $limit_params['per_page'] );
|
||||||
|
if ( $query_args['page'] < 1 || $query_args['page'] > $total_pages ) {
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$coupon_data = $wpdb->get_results(
|
||||||
|
$coupons_query, // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||||
|
ARRAY_A
|
||||||
|
);
|
||||||
|
if ( null === $coupon_data ) {
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->include_extended_info( $coupon_data, $query_args );
|
||||||
|
|
||||||
|
$coupon_data = array_map( array( $this, 'cast_numbers' ), $coupon_data );
|
||||||
|
$data = (object) array(
|
||||||
|
'data' => $coupon_data,
|
||||||
|
'total' => $total_results,
|
||||||
|
'pages' => $total_pages,
|
||||||
|
'page_no' => (int) $query_args['page'],
|
||||||
|
);
|
||||||
|
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create or update an an entry in the wc_order_coupon_lookup table for an order.
|
* Create or update an an entry in the wc_order_coupon_lookup table for an order.
|
||||||
*
|
*
|
||||||
|
|
|
@ -21,12 +21,16 @@ use Automattic\WooCommerce\Admin\API\Reports\Query as ReportsQuery;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* API\Reports\Coupons\Query
|
* API\Reports\Coupons\Query
|
||||||
|
*
|
||||||
|
* @deprecated 9.3.0 Coupons\Query class is deprecated, please use GenericQuery or \WC_Object_Query instead.
|
||||||
*/
|
*/
|
||||||
class Query extends ReportsQuery {
|
class Query extends ReportsQuery {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Valid fields for Products report.
|
* Valid fields for Products report.
|
||||||
*
|
*
|
||||||
|
* @deprecated 9.3.0 Coupons\Query class is deprecated, please use GenericQuery or \WC_Object_Query instead.
|
||||||
|
*
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
protected function get_default_query_vars() {
|
protected function get_default_query_vars() {
|
||||||
|
@ -36,6 +40,8 @@ class Query extends ReportsQuery {
|
||||||
/**
|
/**
|
||||||
* Get product data based on the current query vars.
|
* Get product data based on the current query vars.
|
||||||
*
|
*
|
||||||
|
* @deprecated 9.3.0 Coupons\Query class is deprecated, please use GenericQuery or \WC_Object_Query instead.
|
||||||
|
*
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
public function get_data() {
|
public function get_data() {
|
||||||
|
|
|
@ -10,7 +10,7 @@ namespace Automattic\WooCommerce\Admin\API\Reports\Coupons\Stats;
|
||||||
defined( 'ABSPATH' ) || exit;
|
defined( 'ABSPATH' ) || exit;
|
||||||
|
|
||||||
use Automattic\WooCommerce\Admin\API\Reports\GenericStatsController;
|
use Automattic\WooCommerce\Admin\API\Reports\GenericStatsController;
|
||||||
use Automattic\WooCommerce\Admin\API\Reports\ParameterException;
|
use Automattic\WooCommerce\Admin\API\Reports\GenericQuery;
|
||||||
use WP_REST_Request;
|
use WP_REST_Request;
|
||||||
use WP_REST_Response;
|
use WP_REST_Response;
|
||||||
|
|
||||||
|
@ -54,51 +54,30 @@ class Controller extends GenericStatsController {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all reports.
|
* Get data from `'coupons-stats'` Query.
|
||||||
*
|
*
|
||||||
* @param WP_REST_Request $request Request data.
|
* @override GenericController::get_datastore_data()
|
||||||
* @return array|WP_Error
|
*
|
||||||
|
* @param array $query_args Query arguments.
|
||||||
|
* @return mixed Results from the data store.
|
||||||
*/
|
*/
|
||||||
public function get_items( $request ) {
|
protected function get_datastore_data( $query_args = array() ) {
|
||||||
$query_args = $this->prepare_reports_query( $request );
|
$query = new GenericQuery( $query_args, 'coupons-stats' );
|
||||||
$coupons_query = new Query( $query_args );
|
return $query->get_data();
|
||||||
try {
|
|
||||||
$report_data = $coupons_query->get_data();
|
|
||||||
} catch ( ParameterException $e ) {
|
|
||||||
return new \WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
$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( (object) $interval_data, $request );
|
|
||||||
$out_data['intervals'][] = $this->prepare_response_for_collection( $item );
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->add_pagination_headers(
|
|
||||||
$request,
|
|
||||||
$out_data,
|
|
||||||
(int) $report_data->total,
|
|
||||||
(int) $report_data->page_no,
|
|
||||||
(int) $report_data->pages
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prepare a report object for serialization.
|
* Prepare a report data item for serialization.
|
||||||
*
|
*
|
||||||
* @param stdClass $report Report data.
|
* @param mixed $report Report data item as returned from Data Store.
|
||||||
* @param WP_REST_Request $request Request object.
|
* @param WP_REST_Request $request Request object.
|
||||||
* @return WP_REST_Response
|
* @return WP_REST_Response
|
||||||
*/
|
*/
|
||||||
public function prepare_item_for_response( $report, $request ) {
|
public function prepare_item_for_response( $report, $request ) {
|
||||||
$data = get_object_vars( $report );
|
$response = parent::prepare_item_for_response( $report, $request );
|
||||||
|
|
||||||
$response = parent::prepare_item_for_response( $data, $request );
|
|
||||||
|
|
||||||
|
// Map to `object` for backwards compatibility.
|
||||||
|
$report = (object) $report;
|
||||||
/**
|
/**
|
||||||
* Filter a report returned from the API.
|
* Filter a report returned from the API.
|
||||||
*
|
*
|
||||||
|
@ -189,15 +168,6 @@ class Controller extends GenericStatsController {
|
||||||
),
|
),
|
||||||
'validate_callback' => 'rest_validate_request_arg',
|
'validate_callback' => 'rest_validate_request_arg',
|
||||||
);
|
);
|
||||||
$params['fields'] = array(
|
|
||||||
'description' => __( 'Limit stats fields to the specified items.', 'woocommerce' ),
|
|
||||||
'type' => 'array',
|
|
||||||
'sanitize_callback' => 'wp_parse_slug_list',
|
|
||||||
'validate_callback' => 'rest_validate_request_arg',
|
|
||||||
'items' => array(
|
|
||||||
'type' => 'string',
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
return $params;
|
return $params;
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,15 +9,19 @@ defined( 'ABSPATH' ) || exit;
|
||||||
use Automattic\WooCommerce\Admin\API\Reports\Coupons\DataStore as CouponsDataStore;
|
use Automattic\WooCommerce\Admin\API\Reports\Coupons\DataStore as CouponsDataStore;
|
||||||
use Automattic\WooCommerce\Admin\API\Reports\DataStoreInterface;
|
use Automattic\WooCommerce\Admin\API\Reports\DataStoreInterface;
|
||||||
use Automattic\WooCommerce\Admin\API\Reports\TimeInterval;
|
use Automattic\WooCommerce\Admin\API\Reports\TimeInterval;
|
||||||
use Automattic\WooCommerce\Admin\API\Reports\SqlQuery;
|
use Automattic\WooCommerce\Admin\API\Reports\StatsDataStoreTrait;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* API\Reports\Coupons\Stats\DataStore.
|
* API\Reports\Coupons\Stats\DataStore.
|
||||||
*/
|
*/
|
||||||
class DataStore extends CouponsDataStore implements DataStoreInterface {
|
class DataStore extends CouponsDataStore implements DataStoreInterface {
|
||||||
|
use StatsDataStoreTrait;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mapping columns to data type to return correct response types.
|
* Mapping columns to data type to return correct response types.
|
||||||
*
|
*
|
||||||
|
* @override CouponsDataStore::$column_types
|
||||||
|
*
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
protected $column_types = array(
|
protected $column_types = array(
|
||||||
|
@ -33,6 +37,8 @@ class DataStore extends CouponsDataStore implements DataStoreInterface {
|
||||||
/**
|
/**
|
||||||
* SQL columns to select in the db query.
|
* SQL columns to select in the db query.
|
||||||
*
|
*
|
||||||
|
* @override CouponsDataStore::$report_columns
|
||||||
|
*
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
protected $report_columns;
|
protected $report_columns;
|
||||||
|
@ -40,6 +46,8 @@ class DataStore extends CouponsDataStore implements DataStoreInterface {
|
||||||
/**
|
/**
|
||||||
* Data store context used to pass to filters.
|
* Data store context used to pass to filters.
|
||||||
*
|
*
|
||||||
|
* @override CouponsDataStore::$context
|
||||||
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
protected $context = 'coupons_stats';
|
protected $context = 'coupons_stats';
|
||||||
|
@ -47,12 +55,16 @@ class DataStore extends CouponsDataStore implements DataStoreInterface {
|
||||||
/**
|
/**
|
||||||
* Cache identifier.
|
* Cache identifier.
|
||||||
*
|
*
|
||||||
|
* @override CouponsDataStore::get_default_query_vars()
|
||||||
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
protected $cache_key = 'coupons_stats';
|
protected $cache_key = 'coupons_stats';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Assign report columns once full table name has been assigned.
|
* Assign report columns once full table name has been assigned.
|
||||||
|
*
|
||||||
|
* @override CouponsDataStore::assign_report_columns()
|
||||||
*/
|
*/
|
||||||
protected function assign_report_columns() {
|
protected function assign_report_columns() {
|
||||||
$table_name = self::get_db_table_name();
|
$table_name = self::get_db_table_name();
|
||||||
|
@ -105,145 +117,114 @@ class DataStore extends CouponsDataStore implements DataStoreInterface {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the report data based on parameters supplied by the user.
|
* Get the default query arguments to be used by get_data().
|
||||||
|
* These defaults are only partially applied when used via REST API, as that has its own defaults.
|
||||||
*
|
*
|
||||||
* @since 3.5.0
|
* @override CouponsDataStore::get_default_query_vars()
|
||||||
* @param array $query_args Query parameters.
|
*
|
||||||
* @return stdClass|WP_Error Data.
|
* @return array Query parameters.
|
||||||
*/
|
*/
|
||||||
public function get_data( $query_args ) {
|
public function get_default_query_vars() {
|
||||||
|
$defaults = parent::get_default_query_vars();
|
||||||
|
$defaults['coupons'] = array();
|
||||||
|
$defaults['interval'] = 'week';
|
||||||
|
|
||||||
|
return $defaults;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the report data based on normalized parameters.
|
||||||
|
* Will be called by `get_data` if there is no data in cache.
|
||||||
|
*
|
||||||
|
* @override CouponsDataStore::get_noncached_stats_data()
|
||||||
|
*
|
||||||
|
* @see get_data
|
||||||
|
* @see get_noncached_stats_data
|
||||||
|
* @param array $query_args Query parameters.
|
||||||
|
* @param array $params Query limit parameters.
|
||||||
|
* @param stdClass $data Reference to the data object to fill.
|
||||||
|
* @param int $expected_interval_count Number of expected intervals.
|
||||||
|
* @return stdClass|WP_Error Data object `{ totals: *, intervals: array, total: int, pages: int, page_no: int }`, or error.
|
||||||
|
*/
|
||||||
|
public function get_noncached_stats_data( $query_args, $params, &$data, $expected_interval_count ) {
|
||||||
global $wpdb;
|
global $wpdb;
|
||||||
|
|
||||||
$table_name = self::get_db_table_name();
|
$table_name = self::get_db_table_name();
|
||||||
|
|
||||||
// These defaults are only partially applied when used via REST API, as that has its own defaults.
|
$this->initialize_queries();
|
||||||
$defaults = array(
|
|
||||||
'per_page' => get_option( 'posts_per_page' ),
|
$selections = $this->selected_columns( $query_args );
|
||||||
'page' => 1,
|
$totals_query = array();
|
||||||
'order' => 'DESC',
|
$intervals_query = array();
|
||||||
'orderby' => 'date',
|
$limit_params = $this->get_limit_sql_params( $query_args );
|
||||||
'before' => TimeInterval::default_before(),
|
$this->update_sql_query_params( $query_args, $totals_query, $intervals_query );
|
||||||
'after' => TimeInterval::default_after(),
|
|
||||||
'fields' => '*',
|
$db_intervals = $wpdb->get_col(
|
||||||
'interval' => 'week',
|
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- cache ok, DB call ok, unprepared SQL ok.
|
||||||
'coupons' => array(),
|
$this->interval_query->get_query_statement()
|
||||||
);
|
);
|
||||||
$query_args = wp_parse_args( $query_args, $defaults );
|
|
||||||
$this->normalize_timezones( $query_args, $defaults );
|
|
||||||
|
|
||||||
/*
|
$db_interval_count = count( $db_intervals );
|
||||||
* We need to get the cache key here because
|
|
||||||
* parent::update_intervals_sql_params() modifies $query_args.
|
|
||||||
*/
|
|
||||||
$cache_key = $this->get_cache_key( $query_args );
|
|
||||||
$data = $this->get_cached_data( $cache_key );
|
|
||||||
|
|
||||||
if ( false === $data ) {
|
$this->total_query->add_sql_clause( 'select', $selections );
|
||||||
$this->initialize_queries();
|
$totals = $wpdb->get_results(
|
||||||
|
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- cache ok, DB call ok, unprepared SQL ok.
|
||||||
|
$this->total_query->get_query_statement(),
|
||||||
|
ARRAY_A
|
||||||
|
);
|
||||||
|
|
||||||
$data = (object) array(
|
if ( null === $totals ) {
|
||||||
'data' => array(),
|
return $data;
|
||||||
'total' => 0,
|
|
||||||
'pages' => 0,
|
|
||||||
'page_no' => 0,
|
|
||||||
);
|
|
||||||
|
|
||||||
$selections = $this->selected_columns( $query_args );
|
|
||||||
$totals_query = array();
|
|
||||||
$intervals_query = array();
|
|
||||||
$limit_params = $this->get_limit_sql_params( $query_args );
|
|
||||||
$this->update_sql_query_params( $query_args, $totals_query, $intervals_query );
|
|
||||||
|
|
||||||
$db_intervals = $wpdb->get_col(
|
|
||||||
$this->interval_query->get_query_statement()
|
|
||||||
); // WPCS: cache ok, DB call ok, unprepared SQL ok.
|
|
||||||
|
|
||||||
$db_interval_count = count( $db_intervals );
|
|
||||||
$expected_interval_count = TimeInterval::intervals_between( $query_args['after'], $query_args['before'], $query_args['interval'] );
|
|
||||||
$total_pages = (int) ceil( $expected_interval_count / $limit_params['per_page'] );
|
|
||||||
if ( $query_args['page'] < 1 || $query_args['page'] > $total_pages ) {
|
|
||||||
return $data;
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->total_query->add_sql_clause( 'select', $selections );
|
|
||||||
$totals = $wpdb->get_results(
|
|
||||||
$this->total_query->get_query_statement(),
|
|
||||||
ARRAY_A
|
|
||||||
); // WPCS: cache ok, DB call ok, unprepared SQL ok.
|
|
||||||
|
|
||||||
if ( null === $totals ) {
|
|
||||||
return $data;
|
|
||||||
}
|
|
||||||
|
|
||||||
// @todo remove these assignements when refactoring segmenter classes to use query objects.
|
|
||||||
$totals_query = array(
|
|
||||||
'from_clause' => $this->total_query->get_sql_clause( 'join' ),
|
|
||||||
'where_time_clause' => $this->total_query->get_sql_clause( 'where_time' ),
|
|
||||||
'where_clause' => $this->total_query->get_sql_clause( 'where' ),
|
|
||||||
);
|
|
||||||
$intervals_query = array(
|
|
||||||
'select_clause' => $this->get_sql_clause( 'select' ),
|
|
||||||
'from_clause' => $this->interval_query->get_sql_clause( 'join' ),
|
|
||||||
'where_time_clause' => $this->interval_query->get_sql_clause( 'where_time' ),
|
|
||||||
'where_clause' => $this->interval_query->get_sql_clause( 'where' ),
|
|
||||||
'limit' => $this->get_sql_clause( 'limit' ),
|
|
||||||
);
|
|
||||||
$segmenter = new Segmenter( $query_args, $this->report_columns );
|
|
||||||
$totals[0]['segments'] = $segmenter->get_totals_segments( $totals_query, $table_name );
|
|
||||||
$totals = (object) $this->cast_numbers( $totals[0] );
|
|
||||||
|
|
||||||
// Intervals.
|
|
||||||
$this->update_intervals_sql_params( $query_args, $db_interval_count, $expected_interval_count, $table_name );
|
|
||||||
$this->interval_query->add_sql_clause( 'select', ", MAX({$table_name}.date_created) AS datetime_anchor" );
|
|
||||||
|
|
||||||
if ( '' !== $selections ) {
|
|
||||||
$this->interval_query->add_sql_clause( 'select', ', ' . $selections );
|
|
||||||
}
|
|
||||||
|
|
||||||
$intervals = $wpdb->get_results(
|
|
||||||
$this->interval_query->get_query_statement(),
|
|
||||||
ARRAY_A
|
|
||||||
); // WPCS: cache ok, DB call ok, unprepared SQL ok.
|
|
||||||
|
|
||||||
if ( null === $intervals ) {
|
|
||||||
return $data;
|
|
||||||
}
|
|
||||||
|
|
||||||
$data = (object) array(
|
|
||||||
'totals' => $totals,
|
|
||||||
'intervals' => $intervals,
|
|
||||||
'total' => $expected_interval_count,
|
|
||||||
'pages' => $total_pages,
|
|
||||||
'page_no' => (int) $query_args['page'],
|
|
||||||
);
|
|
||||||
|
|
||||||
if ( TimeInterval::intervals_missing( $expected_interval_count, $db_interval_count, $limit_params['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'], $limit_params['per_page'], $db_interval_count, $expected_interval_count, $query_args['orderby'], $query_args['order'] );
|
|
||||||
} else {
|
|
||||||
$this->update_interval_boundary_dates( $query_args['after'], $query_args['before'], $query_args['interval'], $data->intervals );
|
|
||||||
}
|
|
||||||
$segmenter->add_intervals_segments( $data, $intervals_query, $table_name );
|
|
||||||
$this->create_interval_subtotals( $data->intervals );
|
|
||||||
|
|
||||||
$this->set_cached_data( $cache_key, $data );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// phpcs:ignore Generic.Commenting.Todo.TaskFound
|
||||||
|
// @todo remove these assignements when refactoring segmenter classes to use query objects.
|
||||||
|
$totals_query = array(
|
||||||
|
'from_clause' => $this->total_query->get_sql_clause( 'join' ),
|
||||||
|
'where_time_clause' => $this->total_query->get_sql_clause( 'where_time' ),
|
||||||
|
'where_clause' => $this->total_query->get_sql_clause( 'where' ),
|
||||||
|
);
|
||||||
|
$intervals_query = array(
|
||||||
|
'select_clause' => $this->get_sql_clause( 'select' ),
|
||||||
|
'from_clause' => $this->interval_query->get_sql_clause( 'join' ),
|
||||||
|
'where_time_clause' => $this->interval_query->get_sql_clause( 'where_time' ),
|
||||||
|
'where_clause' => $this->interval_query->get_sql_clause( 'where' ),
|
||||||
|
'limit' => $this->get_sql_clause( 'limit' ),
|
||||||
|
);
|
||||||
|
$segmenter = new Segmenter( $query_args, $this->report_columns );
|
||||||
|
$totals[0]['segments'] = $segmenter->get_totals_segments( $totals_query, $table_name );
|
||||||
|
$totals = (object) $this->cast_numbers( $totals[0] );
|
||||||
|
|
||||||
|
// Intervals.
|
||||||
|
$this->update_intervals_sql_params( $query_args, $db_interval_count, $expected_interval_count, $table_name );
|
||||||
|
$this->interval_query->add_sql_clause( 'select', ", MAX({$table_name}.date_created) AS datetime_anchor" );
|
||||||
|
|
||||||
|
if ( '' !== $selections ) {
|
||||||
|
$this->interval_query->add_sql_clause( 'select', ', ' . $selections );
|
||||||
|
}
|
||||||
|
|
||||||
|
$intervals = $wpdb->get_results(
|
||||||
|
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- cache ok, DB call ok, unprepared SQL ok.
|
||||||
|
$this->interval_query->get_query_statement(),
|
||||||
|
ARRAY_A
|
||||||
|
);
|
||||||
|
|
||||||
|
if ( null === $intervals ) {
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
$data->totals = $totals;
|
||||||
|
$data->intervals = $intervals;
|
||||||
|
|
||||||
|
if ( TimeInterval::intervals_missing( $expected_interval_count, $db_interval_count, $limit_params['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'], $limit_params['per_page'], $db_interval_count, $expected_interval_count, $query_args['orderby'], $query_args['order'] );
|
||||||
|
} else {
|
||||||
|
$this->update_interval_boundary_dates( $query_args['after'], $query_args['before'], $query_args['interval'], $data->intervals );
|
||||||
|
}
|
||||||
|
$segmenter->add_intervals_segments( $data, $intervals_query, $table_name );
|
||||||
|
|
||||||
return $data;
|
return $data;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize query objects.
|
|
||||||
*/
|
|
||||||
protected function initialize_queries() {
|
|
||||||
$this->clear_all_clauses();
|
|
||||||
unset( $this->subquery );
|
|
||||||
$this->total_query = new SqlQuery( $this->context . '_total' );
|
|
||||||
$this->total_query->add_sql_clause( 'from', self::get_db_table_name() );
|
|
||||||
|
|
||||||
$this->interval_query = new SqlQuery( $this->context . '_interval' );
|
|
||||||
$this->interval_query->add_sql_clause( 'from', self::get_db_table_name() );
|
|
||||||
$this->interval_query->add_sql_clause( 'group_by', 'time_interval' );
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,12 +21,16 @@ use Automattic\WooCommerce\Admin\API\Reports\Query as ReportsQuery;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* API\Reports\Coupons\Stats\Query
|
* API\Reports\Coupons\Stats\Query
|
||||||
|
*
|
||||||
|
* @deprecated 9.3.0 Coupons\Stats\Query class is deprecated, please use GenericQuery or \WC_Object_Query instead.
|
||||||
*/
|
*/
|
||||||
class Query extends ReportsQuery {
|
class Query extends ReportsQuery {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Valid fields for Products report.
|
* Valid fields for Products report.
|
||||||
*
|
*
|
||||||
|
* @deprecated 9.3.0 Coupons\Stats\Query class is deprecated, please use GenericQuery or \WC_Object_Query instead.
|
||||||
|
*
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
protected function get_default_query_vars() {
|
protected function get_default_query_vars() {
|
||||||
|
@ -36,6 +40,8 @@ class Query extends ReportsQuery {
|
||||||
/**
|
/**
|
||||||
* Get product data based on the current query vars.
|
* Get product data based on the current query vars.
|
||||||
*
|
*
|
||||||
|
* @deprecated 9.3.0 Coupons\Stats\Query class is deprecated, please use GenericQuery or \WC_Object_Query instead.
|
||||||
|
*
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
public function get_data() {
|
public function get_data() {
|
||||||
|
|
|
@ -33,6 +33,19 @@ class Controller extends GenericController implements ExportableInterface {
|
||||||
*/
|
*/
|
||||||
protected $rest_base = 'reports/customers';
|
protected $rest_base = 'reports/customers';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get data from Query.
|
||||||
|
*
|
||||||
|
* @override GenericController::get_datastore_data()
|
||||||
|
*
|
||||||
|
* @param array $query_args Query arguments.
|
||||||
|
* @return mixed Results from the data store.
|
||||||
|
*/
|
||||||
|
protected function get_datastore_data( $query_args = array() ) {
|
||||||
|
$query = new Query( $query_args );
|
||||||
|
return $query->get_data();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Maps query arguments from the REST request.
|
* Maps query arguments from the REST request.
|
||||||
*
|
*
|
||||||
|
@ -84,34 +97,6 @@ class Controller extends GenericController implements ExportableInterface {
|
||||||
return $args;
|
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 );
|
|
||||||
$customers_query = new Query( $query_args );
|
|
||||||
$report_data = $customers_query->get_data();
|
|
||||||
|
|
||||||
$data = array();
|
|
||||||
|
|
||||||
foreach ( $report_data->data as $customer_data ) {
|
|
||||||
$item = $this->prepare_item_for_response( $customer_data, $request );
|
|
||||||
$data[] = $this->prepare_response_for_collection( $item );
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->add_pagination_headers(
|
|
||||||
$request,
|
|
||||||
$data,
|
|
||||||
(int) $report_data->total,
|
|
||||||
(int) $report_data->page_no,
|
|
||||||
(int) $report_data->pages
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get one report.
|
* Get one report.
|
||||||
*
|
*
|
||||||
|
@ -139,11 +124,11 @@ class Controller extends GenericController implements ExportableInterface {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prepare a report object for serialization.
|
* Prepare a report data item for serialization.
|
||||||
*
|
*
|
||||||
* @param array $report Report data.
|
* @param array $report Report data item as returned from Data Store.
|
||||||
* @param WP_REST_Request $request Request object.
|
* @param \WP_REST_Request $request Request object.
|
||||||
* @return WP_REST_Response
|
* @return \WP_REST_Response
|
||||||
*/
|
*/
|
||||||
public function prepare_item_for_response( $report, $request ) {
|
public function prepare_item_for_response( $report, $request ) {
|
||||||
$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
|
$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
|
||||||
|
|
|
@ -22,6 +22,8 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||||
/**
|
/**
|
||||||
* Table used to get the data.
|
* Table used to get the data.
|
||||||
*
|
*
|
||||||
|
* @override ReportsDataStore::$table_name
|
||||||
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
protected static $table_name = 'wc_customer_lookup';
|
protected static $table_name = 'wc_customer_lookup';
|
||||||
|
@ -29,6 +31,8 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||||
/**
|
/**
|
||||||
* Cache identifier.
|
* Cache identifier.
|
||||||
*
|
*
|
||||||
|
* @override ReportsDataStore::$cache_key
|
||||||
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
protected $cache_key = 'customers';
|
protected $cache_key = 'customers';
|
||||||
|
@ -36,6 +40,8 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||||
/**
|
/**
|
||||||
* Mapping columns to data type to return correct response types.
|
* Mapping columns to data type to return correct response types.
|
||||||
*
|
*
|
||||||
|
* @override ReportsDataStore::$column_types
|
||||||
|
*
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
protected $column_types = array(
|
protected $column_types = array(
|
||||||
|
@ -49,12 +55,16 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||||
/**
|
/**
|
||||||
* Data store context used to pass to filters.
|
* Data store context used to pass to filters.
|
||||||
*
|
*
|
||||||
|
* @override ReportsDataStore::$context
|
||||||
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
protected $context = 'customers';
|
protected $context = 'customers';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Assign report columns once full table name has been assigned.
|
* Assign report columns once full table name has been assigned.
|
||||||
|
*
|
||||||
|
* @override ReportsDataStore::assign_report_columns()
|
||||||
*/
|
*/
|
||||||
protected function assign_report_columns() {
|
protected function assign_report_columns() {
|
||||||
global $wpdb;
|
global $wpdb;
|
||||||
|
@ -168,6 +178,8 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||||
/**
|
/**
|
||||||
* Maps ordering specified by the user to columns in the database/fields in the data.
|
* Maps ordering specified by the user to columns in the database/fields in the data.
|
||||||
*
|
*
|
||||||
|
* @override ReportsDataStore::normalize_order_by()
|
||||||
|
*
|
||||||
* @param string $order_by Sorting criterion.
|
* @param string $order_by Sorting criterion.
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
|
@ -182,6 +194,8 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||||
/**
|
/**
|
||||||
* Fills WHERE clause of SQL request with date-related constraints.
|
* Fills WHERE clause of SQL request with date-related constraints.
|
||||||
*
|
*
|
||||||
|
* @override ReportsDataStore::add_time_period_sql_params()
|
||||||
|
*
|
||||||
* @param array $query_args Parameters supplied by the user.
|
* @param array $query_args Parameters supplied by the user.
|
||||||
* @param string $table_name Name of the db table relevant for the date constraint.
|
* @param string $table_name Name of the db table relevant for the date constraint.
|
||||||
*/
|
*/
|
||||||
|
@ -409,89 +423,20 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the report data based on parameters supplied by the user.
|
* Get the default query arguments to be used by get_data().
|
||||||
|
* These defaults are only partially applied when used via REST API, as that has its own defaults.
|
||||||
*
|
*
|
||||||
* @param array $query_args Query parameters.
|
* @override ReportsDataStore::get_default_query_vars()
|
||||||
* @return stdClass|WP_Error Data.
|
*
|
||||||
|
* @return array Query parameters.
|
||||||
*/
|
*/
|
||||||
public function get_data( $query_args ) {
|
public function get_default_query_vars() {
|
||||||
global $wpdb;
|
$defaults = parent::get_default_query_vars();
|
||||||
|
$defaults['orderby'] = 'date_registered';
|
||||||
|
$defaults['order_before'] = TimeInterval::default_before();
|
||||||
|
$defaults['order_after'] = TimeInterval::default_after();
|
||||||
|
|
||||||
$customers_table_name = self::get_db_table_name();
|
return $defaults;
|
||||||
$order_stats_table_name = $wpdb->prefix . 'wc_order_stats';
|
|
||||||
|
|
||||||
// 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_registered',
|
|
||||||
'order_before' => TimeInterval::default_before(),
|
|
||||||
'order_after' => TimeInterval::default_after(),
|
|
||||||
'fields' => '*',
|
|
||||||
);
|
|
||||||
$query_args = wp_parse_args( $query_args, $defaults );
|
|
||||||
$this->normalize_timezones( $query_args, $defaults );
|
|
||||||
|
|
||||||
/*
|
|
||||||
* We need to get the cache key here because
|
|
||||||
* parent::update_intervals_sql_params() modifies $query_args.
|
|
||||||
*/
|
|
||||||
$cache_key = $this->get_cache_key( $query_args );
|
|
||||||
$data = $this->get_cached_data( $cache_key );
|
|
||||||
|
|
||||||
if ( false === $data ) {
|
|
||||||
$this->initialize_queries();
|
|
||||||
|
|
||||||
$data = (object) array(
|
|
||||||
'data' => array(),
|
|
||||||
'total' => 0,
|
|
||||||
'pages' => 0,
|
|
||||||
'page_no' => 0,
|
|
||||||
);
|
|
||||||
|
|
||||||
$selections = $this->selected_columns( $query_args );
|
|
||||||
$sql_query_params = $this->add_sql_query_params( $query_args );
|
|
||||||
$count_query = "SELECT COUNT(*) FROM (
|
|
||||||
{$this->subquery->get_query_statement()}
|
|
||||||
) as tt
|
|
||||||
";
|
|
||||||
$db_records_count = (int) $wpdb->get_var(
|
|
||||||
$count_query // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
|
||||||
);
|
|
||||||
|
|
||||||
$params = $this->get_limit_params( $query_args );
|
|
||||||
$total_pages = (int) ceil( $db_records_count / $params['per_page'] );
|
|
||||||
if ( $query_args['page'] < 1 || $query_args['page'] > $total_pages ) {
|
|
||||||
return $data;
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->subquery->clear_sql_clause( 'select' );
|
|
||||||
$this->subquery->add_sql_clause( 'select', $selections );
|
|
||||||
$this->subquery->add_sql_clause( 'order_by', $this->get_sql_clause( 'order_by' ) );
|
|
||||||
$this->subquery->add_sql_clause( 'limit', $this->get_sql_clause( 'limit' ) );
|
|
||||||
|
|
||||||
$customer_data = $wpdb->get_results(
|
|
||||||
$this->subquery->get_query_statement(), // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
|
||||||
ARRAY_A
|
|
||||||
);
|
|
||||||
|
|
||||||
if ( null === $customer_data ) {
|
|
||||||
return $data;
|
|
||||||
}
|
|
||||||
|
|
||||||
$customer_data = array_map( array( $this, 'cast_numbers' ), $customer_data );
|
|
||||||
$data = (object) array(
|
|
||||||
'data' => $customer_data,
|
|
||||||
'total' => $db_records_count,
|
|
||||||
'pages' => $total_pages,
|
|
||||||
'page_no' => (int) $query_args['page'],
|
|
||||||
);
|
|
||||||
|
|
||||||
$this->set_cached_data( $cache_key, $data );
|
|
||||||
}
|
|
||||||
|
|
||||||
return $data;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -533,6 +478,69 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the report data based on normalized parameters.
|
||||||
|
* Will be called by `get_data` if there is no data in cache.
|
||||||
|
*
|
||||||
|
* @override ReportsDataStore::get_noncached_data()
|
||||||
|
*
|
||||||
|
* @see get_data
|
||||||
|
* @param array $query_args Query parameters.
|
||||||
|
* @return stdClass|WP_Error Data object `{ totals: *, intervals: array, total: int, pages: int, page_no: int }`, or error.
|
||||||
|
*/
|
||||||
|
public function get_noncached_data( $query_args ) {
|
||||||
|
global $wpdb;
|
||||||
|
|
||||||
|
$this->initialize_queries();
|
||||||
|
|
||||||
|
$data = (object) array(
|
||||||
|
'data' => array(),
|
||||||
|
'total' => 0,
|
||||||
|
'pages' => 0,
|
||||||
|
'page_no' => 0,
|
||||||
|
);
|
||||||
|
|
||||||
|
$selections = $this->selected_columns( $query_args );
|
||||||
|
$sql_query_params = $this->add_sql_query_params( $query_args );
|
||||||
|
$count_query = "SELECT COUNT(*) FROM (
|
||||||
|
{$this->subquery->get_query_statement()}
|
||||||
|
) as tt
|
||||||
|
";
|
||||||
|
$db_records_count = (int) $wpdb->get_var(
|
||||||
|
$count_query // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||||
|
);
|
||||||
|
|
||||||
|
$params = $this->get_limit_params( $query_args );
|
||||||
|
$total_pages = (int) ceil( $db_records_count / $params['per_page'] );
|
||||||
|
if ( $query_args['page'] < 1 || $query_args['page'] > $total_pages ) {
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->subquery->clear_sql_clause( 'select' );
|
||||||
|
$this->subquery->add_sql_clause( 'select', $selections );
|
||||||
|
$this->subquery->add_sql_clause( 'order_by', $this->get_sql_clause( 'order_by' ) );
|
||||||
|
$this->subquery->add_sql_clause( 'limit', $this->get_sql_clause( 'limit' ) );
|
||||||
|
|
||||||
|
$customer_data = $wpdb->get_results(
|
||||||
|
$this->subquery->get_query_statement(), // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||||
|
ARRAY_A
|
||||||
|
);
|
||||||
|
|
||||||
|
if ( null === $customer_data ) {
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
$customer_data = array_map( array( $this, 'cast_numbers' ), $customer_data );
|
||||||
|
$data = (object) array(
|
||||||
|
'data' => $customer_data,
|
||||||
|
'total' => $db_records_count,
|
||||||
|
'pages' => $total_pages,
|
||||||
|
'page_no' => (int) $query_args['page'],
|
||||||
|
);
|
||||||
|
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get or create a customer from a given order.
|
* Get or create a customer from a given order.
|
||||||
*
|
*
|
||||||
|
|
|
@ -16,14 +16,23 @@
|
||||||
|
|
||||||
namespace Automattic\WooCommerce\Admin\API\Reports\Customers;
|
namespace Automattic\WooCommerce\Admin\API\Reports\Customers;
|
||||||
|
|
||||||
defined( 'ABSPATH' ) || exit;
|
use Automattic\WooCommerce\Admin\API\Reports\GenericQuery;
|
||||||
|
|
||||||
use Automattic\WooCommerce\Admin\API\Reports\Query as ReportsQuery;
|
defined( 'ABSPATH' ) || exit;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* API\Reports\Customers\Query
|
* API\Reports\Customers\Query
|
||||||
*/
|
*/
|
||||||
class Query extends ReportsQuery {
|
class Query extends GenericQuery {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specific query name.
|
||||||
|
* Will be used to load the `report-{name}` data store,
|
||||||
|
* and to call `woocommerce_analytics_{snake_case(name)}_*` filters.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $name = 'customers';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Valid fields for Customers report.
|
* Valid fields for Customers report.
|
||||||
|
@ -39,17 +48,4 @@ class Query extends ReportsQuery {
|
||||||
'fields' => '*',
|
'fields' => '*',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get product data based on the current query vars.
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function get_data() {
|
|
||||||
$args = apply_filters( 'woocommerce_analytics_customers_query_args', $this->get_query_vars() );
|
|
||||||
|
|
||||||
$data_store = \WC_Data_Store::load( 'report-customers' );
|
|
||||||
$results = $data_store->get_data( $args );
|
|
||||||
return apply_filters( 'woocommerce_analytics_customers_select_query', $results, $args );
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,8 @@
|
||||||
|
|
||||||
namespace Automattic\WooCommerce\Admin\API\Reports\Customers\Stats;
|
namespace Automattic\WooCommerce\Admin\API\Reports\Customers\Stats;
|
||||||
|
|
||||||
|
use Automattic\WooCommerce\Admin\API\Reports\Customers\Query;
|
||||||
|
|
||||||
defined( 'ABSPATH' ) || exit;
|
defined( 'ABSPATH' ) || exit;
|
||||||
|
|
||||||
use Automattic\WooCommerce\Admin\API\Reports\TimeInterval;
|
use Automattic\WooCommerce\Admin\API\Reports\TimeInterval;
|
||||||
|
@ -83,7 +85,7 @@ class Controller extends \WC_REST_Reports_Controller {
|
||||||
*/
|
*/
|
||||||
public function get_items( $request ) {
|
public function get_items( $request ) {
|
||||||
$query_args = $this->prepare_reports_query( $request );
|
$query_args = $this->prepare_reports_query( $request );
|
||||||
$customers_query = new Query( $query_args );
|
$customers_query = new Query( $query_args, 'customers-stats' );
|
||||||
$report_data = $customers_query->get_data();
|
$report_data = $customers_query->get_data();
|
||||||
$out_data = array(
|
$out_data = array(
|
||||||
'totals' => $report_data,
|
'totals' => $report_data,
|
||||||
|
@ -93,11 +95,11 @@ class Controller extends \WC_REST_Reports_Controller {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prepare a report object for serialization.
|
* Prepare a report data item for serialization.
|
||||||
*
|
*
|
||||||
* @param Array $report Report data.
|
* @param array $report Report data item as returned from Data Store.
|
||||||
* @param WP_REST_Request $request Request object.
|
* @param \WP_REST_Request $request Request object.
|
||||||
* @return WP_REST_Response
|
* @return \WP_REST_Response
|
||||||
*/
|
*/
|
||||||
public function prepare_item_for_response( $report, $request ) {
|
public function prepare_item_for_response( $report, $request ) {
|
||||||
$data = $report;
|
$data = $report;
|
||||||
|
|
|
@ -8,6 +8,7 @@ namespace Automattic\WooCommerce\Admin\API\Reports\Customers\Stats;
|
||||||
defined( 'ABSPATH' ) || exit;
|
defined( 'ABSPATH' ) || exit;
|
||||||
|
|
||||||
use Automattic\WooCommerce\Admin\API\Reports\Customers\DataStore as CustomersDataStore;
|
use Automattic\WooCommerce\Admin\API\Reports\Customers\DataStore as CustomersDataStore;
|
||||||
|
use Automattic\WooCommerce\Admin\API\Reports\DataStore as ReportsDataStore;
|
||||||
use Automattic\WooCommerce\Admin\API\Reports\DataStoreInterface;
|
use Automattic\WooCommerce\Admin\API\Reports\DataStoreInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -17,6 +18,8 @@ class DataStore extends CustomersDataStore implements DataStoreInterface {
|
||||||
/**
|
/**
|
||||||
* Mapping columns to data type to return correct response types.
|
* Mapping columns to data type to return correct response types.
|
||||||
*
|
*
|
||||||
|
* @override CustomersDataStore::$column_types
|
||||||
|
*
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
protected $column_types = array(
|
protected $column_types = array(
|
||||||
|
@ -29,6 +32,8 @@ class DataStore extends CustomersDataStore implements DataStoreInterface {
|
||||||
/**
|
/**
|
||||||
* Cache identifier.
|
* Cache identifier.
|
||||||
*
|
*
|
||||||
|
* @override CustomersDataStore::$cache_key
|
||||||
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
protected $cache_key = 'customers_stats';
|
protected $cache_key = 'customers_stats';
|
||||||
|
@ -36,12 +41,16 @@ class DataStore extends CustomersDataStore implements DataStoreInterface {
|
||||||
/**
|
/**
|
||||||
* Data store context used to pass to filters.
|
* Data store context used to pass to filters.
|
||||||
*
|
*
|
||||||
|
* @override CustomersDataStore::$context
|
||||||
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
protected $context = 'customers_stats';
|
protected $context = 'customers_stats';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Assign report columns once full table name has been assigned.
|
* Assign report columns once full table name has been assigned.
|
||||||
|
*
|
||||||
|
* @override CustomersDataStore::assign_report_columns()
|
||||||
*/
|
*/
|
||||||
protected function assign_report_columns() {
|
protected function assign_report_columns() {
|
||||||
$this->report_columns = array(
|
$this->report_columns = array(
|
||||||
|
@ -53,76 +62,70 @@ class DataStore extends CustomersDataStore implements DataStoreInterface {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the report data based on parameters supplied by the user.
|
* Get the default query arguments to be used by get_data().
|
||||||
|
* These defaults are only partially applied when used via REST API, as that has its own defaults.
|
||||||
*
|
*
|
||||||
* @param array $query_args Query parameters.
|
* @override CustomersDataStore::get_default_query_vars()
|
||||||
* @return stdClass|WP_Error Data.
|
*
|
||||||
|
* @return array Query parameters.
|
||||||
*/
|
*/
|
||||||
public function get_data( $query_args ) {
|
public function get_default_query_vars() {
|
||||||
|
$defaults = ReportsDataStore::get_default_query_vars();
|
||||||
|
$defaults['orderby'] = 'date_registered';
|
||||||
|
// Do not set `order_before` and `order_after` here, like in the parent class.
|
||||||
|
return $defaults;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the report data based on normalized parameters.
|
||||||
|
* Will be called by `get_data` if there is no data in cache.
|
||||||
|
*
|
||||||
|
* @override CustomersDataStore::get_noncached_data()
|
||||||
|
*
|
||||||
|
* @see get_data
|
||||||
|
* @param array $query_args Query parameters.
|
||||||
|
* @return stdClass|WP_Error Data object `{ totals: *, intervals: array, total: int, pages: int, page_no: int }`, or error.
|
||||||
|
*/
|
||||||
|
public function get_noncached_data( $query_args ) {
|
||||||
global $wpdb;
|
global $wpdb;
|
||||||
|
$this->initialize_queries();
|
||||||
|
|
||||||
$customers_table_name = self::get_db_table_name();
|
$data = (object) array(
|
||||||
|
'customers_count' => 0,
|
||||||
// These defaults are only partially applied when used via REST API, as that has its own defaults.
|
'avg_orders_count' => 0,
|
||||||
$defaults = array(
|
'avg_total_spend' => 0.0,
|
||||||
'per_page' => get_option( 'posts_per_page' ),
|
'avg_avg_order_value' => 0.0,
|
||||||
'page' => 1,
|
|
||||||
'order' => 'DESC',
|
|
||||||
'orderby' => 'date_registered',
|
|
||||||
'fields' => '*',
|
|
||||||
);
|
);
|
||||||
$query_args = wp_parse_args( $query_args, $defaults );
|
|
||||||
$this->normalize_timezones( $query_args, $defaults );
|
|
||||||
|
|
||||||
/*
|
$selections = $this->selected_columns( $query_args );
|
||||||
* We need to get the cache key here because
|
$this->add_sql_query_params( $query_args );
|
||||||
* parent::update_intervals_sql_params() modifies $query_args.
|
// Clear SQL clauses set for parent class queries that are different here.
|
||||||
*/
|
$this->subquery->clear_sql_clause( 'select' );
|
||||||
$cache_key = $this->get_cache_key( $query_args );
|
$this->subquery->add_sql_clause( 'select', 'SUM( total_sales ) AS total_spend,' );
|
||||||
$data = $this->get_cached_data( $cache_key );
|
$this->subquery->add_sql_clause(
|
||||||
|
'select',
|
||||||
|
'SUM( CASE WHEN parent_id = 0 THEN 1 END ) as orders_count,'
|
||||||
|
);
|
||||||
|
$this->subquery->add_sql_clause(
|
||||||
|
'select',
|
||||||
|
'CASE WHEN SUM( CASE WHEN parent_id = 0 THEN 1 ELSE 0 END ) = 0 THEN NULL ELSE SUM( total_sales ) / SUM( CASE WHEN parent_id = 0 THEN 1 ELSE 0 END ) END AS avg_order_value'
|
||||||
|
);
|
||||||
|
|
||||||
if ( false === $data ) {
|
$this->clear_sql_clause( array( 'order_by', 'limit' ) );
|
||||||
$this->initialize_queries();
|
$this->add_sql_clause( 'select', $selections );
|
||||||
|
$this->add_sql_clause( 'from', "({$this->subquery->get_query_statement()}) AS tt" );
|
||||||
|
|
||||||
$data = (object) array(
|
$report_data = $wpdb->get_results(
|
||||||
'customers_count' => 0,
|
$this->get_query_statement(), // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||||
'avg_orders_count' => 0,
|
ARRAY_A
|
||||||
'avg_total_spend' => 0.0,
|
);
|
||||||
'avg_avg_order_value' => 0.0,
|
|
||||||
);
|
|
||||||
|
|
||||||
$selections = $this->selected_columns( $query_args );
|
if ( null === $report_data ) {
|
||||||
$this->add_sql_query_params( $query_args );
|
return $data;
|
||||||
// Clear SQL clauses set for parent class queries that are different here.
|
|
||||||
$this->subquery->clear_sql_clause( 'select' );
|
|
||||||
$this->subquery->add_sql_clause( 'select', 'SUM( total_sales ) AS total_spend,' );
|
|
||||||
$this->subquery->add_sql_clause(
|
|
||||||
'select',
|
|
||||||
'SUM( CASE WHEN parent_id = 0 THEN 1 END ) as orders_count,'
|
|
||||||
);
|
|
||||||
$this->subquery->add_sql_clause(
|
|
||||||
'select',
|
|
||||||
'CASE WHEN SUM( CASE WHEN parent_id = 0 THEN 1 ELSE 0 END ) = 0 THEN NULL ELSE SUM( total_sales ) / SUM( CASE WHEN parent_id = 0 THEN 1 ELSE 0 END ) END AS avg_order_value'
|
|
||||||
);
|
|
||||||
|
|
||||||
$this->clear_sql_clause( array( 'order_by', 'limit' ) );
|
|
||||||
$this->add_sql_clause( 'select', $selections );
|
|
||||||
$this->add_sql_clause( 'from', "({$this->subquery->get_query_statement()}) AS tt" );
|
|
||||||
|
|
||||||
$report_data = $wpdb->get_results(
|
|
||||||
$this->get_query_statement(), // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
|
||||||
ARRAY_A
|
|
||||||
);
|
|
||||||
|
|
||||||
if ( null === $report_data ) {
|
|
||||||
return $data;
|
|
||||||
}
|
|
||||||
|
|
||||||
$data = (object) $this->cast_numbers( $report_data[0] );
|
|
||||||
|
|
||||||
$this->set_cached_data( $cache_key, $data );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$data = (object) $this->cast_numbers( $report_data[0] );
|
||||||
|
|
||||||
return $data;
|
return $data;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,12 +22,16 @@ use Automattic\WooCommerce\Admin\API\Reports\Query as ReportsQuery;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* API\Reports\Customers\Stats\Query
|
* API\Reports\Customers\Stats\Query
|
||||||
|
*
|
||||||
|
* @deprecated 9.3.0 Customers\Stats\Query class is deprecated, please use Reports\Customers\Query with a custom name, GenericQuery or \WC_Object_Query instead.
|
||||||
*/
|
*/
|
||||||
class Query extends ReportsQuery {
|
class Query extends ReportsQuery {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Valid fields for Customers report.
|
* Valid fields for Customers report.
|
||||||
*
|
*
|
||||||
|
* @deprecated 9.3.0 Customers\Stats\Query class is deprecated, please use Reports\Customers\Query with a custom name, GenericQuery or \WC_Object_Query instead.
|
||||||
|
*
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
protected function get_default_query_vars() {
|
protected function get_default_query_vars() {
|
||||||
|
@ -43,6 +47,8 @@ class Query extends ReportsQuery {
|
||||||
/**
|
/**
|
||||||
* Get product data based on the current query vars.
|
* Get product data based on the current query vars.
|
||||||
*
|
*
|
||||||
|
* @deprecated 9.3.0 Customers\Stats\Query class is deprecated, please use Reports\Customers\Query with a custom name, GenericQuery or \WC_Object_Query instead.
|
||||||
|
*
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
public function get_data() {
|
public function get_data() {
|
||||||
|
|
|
@ -9,12 +9,59 @@ if ( ! defined( 'ABSPATH' ) ) {
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
use Automattic\WooCommerce\Admin\API\Reports\DataStoreInterface;
|
||||||
use Automattic\WooCommerce\Admin\API\Reports\TimeInterval;
|
use Automattic\WooCommerce\Admin\API\Reports\TimeInterval;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Admin\API\Reports\DataStore: Common parent for custom report data stores.
|
* Common parent for custom report data stores.
|
||||||
|
*
|
||||||
|
* We use Report DataStores to separate DB data retrieval logic from the REST API controllers.
|
||||||
|
*
|
||||||
|
* Handles caching, data normalization, intervals-related methods, and other common functionality.
|
||||||
|
* So, in your custom report DataStore class that extends this class
|
||||||
|
* you can focus on specifics by overriding the `get_noncached_data` method.
|
||||||
|
*
|
||||||
|
* Minimalistic example:
|
||||||
|
* <pre><code class="language-php">class MyDataStore extends DataStore implements DataStoreInterface {
|
||||||
|
* /** Cache identifier, used by the `DataStore` class to handle caching for you. */
|
||||||
|
* protected $cache_key = 'my_thing';
|
||||||
|
* /** Data store context used to pass to filters. */
|
||||||
|
* protected $context = 'my_thing';
|
||||||
|
* /** Table used to get the data. */
|
||||||
|
* protected static $table_name = 'my_table';
|
||||||
|
* /**
|
||||||
|
* * Method that overrides the `DataStore::get_noncached_data()` to return the report data.
|
||||||
|
* * Will be called by `get_data` if there is no data in cache.
|
||||||
|
* */
|
||||||
|
* public function get_noncached_data( $query ) {
|
||||||
|
* // Do your magic.
|
||||||
|
*
|
||||||
|
* // Then return your data in conforming object structure.
|
||||||
|
* return (object) array(
|
||||||
|
* 'data' => $product_data,
|
||||||
|
* 'total' => 1,
|
||||||
|
* 'page_no' => 1,
|
||||||
|
* 'pages' => 1,
|
||||||
|
* );
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* </code></pre>
|
||||||
|
*
|
||||||
|
* Please use the `woocommerce_data_stores` filter to add your custom data store to the list of available ones.
|
||||||
|
* Then, your store could be accessed by Controller classes ({@see GenericController::get_datastore_data() GenericController::get_datastore_data()})
|
||||||
|
* or using {@link \WC_Data_Store::load() \WC_Data_Store::load()}.
|
||||||
|
*
|
||||||
|
* We recommend registering using the REST base name of your Controller as the key, e.g.:
|
||||||
|
* <pre><code class="language-php">add_filter( 'woocommerce_data_stores', function( $stores ) {
|
||||||
|
* $stores['reports/my-thing'] = 'MyExtension\Admin\Analytics\Rest_API\MyDataStore';
|
||||||
|
* } );
|
||||||
|
* </code></pre>
|
||||||
|
* This way, `GenericController` will pick it up automatically.
|
||||||
|
*
|
||||||
|
* Note that this class is NOT {@link https://developer.woocommerce.com/docs/how-to-manage-woocommerce-data-stores/ a CRUD data store}.
|
||||||
|
* It does not implement the {@see WC_Object_Data_Store_Interface WC_Object_Data_Store_Interface} nor extend WC_Data & WC_Data_Store_WP classes.
|
||||||
*/
|
*/
|
||||||
class DataStore extends SqlQuery {
|
class DataStore extends SqlQuery implements DataStoreInterface {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cache group for the reports.
|
* Cache group for the reports.
|
||||||
|
@ -90,6 +137,8 @@ class DataStore extends SqlQuery {
|
||||||
/**
|
/**
|
||||||
* Data store context used to pass to filters.
|
* Data store context used to pass to filters.
|
||||||
*
|
*
|
||||||
|
* @override SqlQuery
|
||||||
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
protected $context = 'reports';
|
protected $context = 'reports';
|
||||||
|
@ -138,6 +187,8 @@ class DataStore extends SqlQuery {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class constructor.
|
* Class constructor.
|
||||||
|
*
|
||||||
|
* @override SqlQuery::__construct()
|
||||||
*/
|
*/
|
||||||
public function __construct() {
|
public function __construct() {
|
||||||
self::set_db_table_name();
|
self::set_db_table_name();
|
||||||
|
@ -160,6 +211,54 @@ class DataStore extends SqlQuery {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the data based on args.
|
||||||
|
*
|
||||||
|
* Returns the report data based on parameters supplied by the user.
|
||||||
|
* Fetches it from cache or returns `get_noncached_data` result.
|
||||||
|
*
|
||||||
|
* @param array $query_args Query parameters.
|
||||||
|
* @return stdClass|WP_Error
|
||||||
|
*/
|
||||||
|
public function get_data( $query_args ) {
|
||||||
|
$defaults = $this->get_default_query_vars();
|
||||||
|
$query_args = wp_parse_args( $query_args, $defaults );
|
||||||
|
$this->normalize_timezones( $query_args, $defaults );
|
||||||
|
|
||||||
|
/*
|
||||||
|
* We need to get the cache key here because
|
||||||
|
* parent::update_intervals_sql_params() modifies $query_args.
|
||||||
|
*/
|
||||||
|
$cache_key = $this->get_cache_key( $query_args );
|
||||||
|
$data = $this->get_cached_data( $cache_key );
|
||||||
|
|
||||||
|
if ( false === $data ) {
|
||||||
|
$data = $this->get_noncached_data( $query_args );
|
||||||
|
$this->set_cached_data( $cache_key, $data );
|
||||||
|
}
|
||||||
|
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the default query arguments to be used by get_data().
|
||||||
|
* These defaults are only partially applied when used via REST API, as that has its own defaults.
|
||||||
|
*
|
||||||
|
* @return array Query parameters.
|
||||||
|
*/
|
||||||
|
public function get_default_query_vars() {
|
||||||
|
return array(
|
||||||
|
'per_page' => get_option( 'posts_per_page' ),
|
||||||
|
'page' => 1,
|
||||||
|
'order' => 'DESC',
|
||||||
|
'orderby' => 'date',
|
||||||
|
'before' => TimeInterval::default_before(),
|
||||||
|
'after' => TimeInterval::default_after(),
|
||||||
|
'fields' => '*',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get table name from database class.
|
* Get table name from database class.
|
||||||
*/
|
*/
|
||||||
|
@ -168,6 +267,19 @@ class DataStore extends SqlQuery {
|
||||||
return isset( $wpdb->{static::$table_name} ) ? $wpdb->{static::$table_name} : $wpdb->prefix . static::$table_name;
|
return isset( $wpdb->{static::$table_name} ) ? $wpdb->{static::$table_name} : $wpdb->prefix . static::$table_name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the report data based on normalized parameters.
|
||||||
|
* Will be called by `get_data` if there is no data in cache.
|
||||||
|
*
|
||||||
|
* @see get_data
|
||||||
|
* @param array $query_args Query parameters.
|
||||||
|
* @return stdClass|WP_Error Data object `{ totals: *, intervals: array, total: int, pages: int, page_no: int }`, or error.
|
||||||
|
*/
|
||||||
|
public function get_noncached_data( $query_args ) {
|
||||||
|
/* 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 ) );
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set table name from database class.
|
* Set table name from database class.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -9,16 +9,20 @@ namespace Automattic\WooCommerce\Admin\API\Reports\Downloads;
|
||||||
|
|
||||||
defined( 'ABSPATH' ) || exit;
|
defined( 'ABSPATH' ) || exit;
|
||||||
|
|
||||||
use Automattic\WooCommerce\Admin\API\Reports\Controller as ReportsController;
|
|
||||||
use Automattic\WooCommerce\Admin\API\Reports\ExportableInterface;
|
use Automattic\WooCommerce\Admin\API\Reports\ExportableInterface;
|
||||||
|
use Automattic\WooCommerce\Admin\API\Reports\GenericController;
|
||||||
|
use Automattic\WooCommerce\Admin\API\Reports\GenericQuery;
|
||||||
|
use Automattic\WooCommerce\Admin\API\Reports\OrderAwareControllerTrait;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* REST API Reports downloads controller class.
|
* REST API Reports downloads controller class.
|
||||||
*
|
*
|
||||||
* @internal
|
* @internal
|
||||||
* @extends Automattic\WooCommerce\Admin\API\Reports\Controller
|
* @extends Automattic\WooCommerce\Admin\API\Reports\GenericController
|
||||||
*/
|
*/
|
||||||
class Controller extends ReportsController implements ExportableInterface {
|
class Controller extends GenericController implements ExportableInterface {
|
||||||
|
|
||||||
|
use OrderAwareControllerTrait;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Route base.
|
* Route base.
|
||||||
|
@ -28,67 +32,40 @@ class Controller extends ReportsController implements ExportableInterface {
|
||||||
protected $rest_base = 'reports/downloads';
|
protected $rest_base = 'reports/downloads';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get items.
|
* Get data from `'downloads'` Query.
|
||||||
*
|
*
|
||||||
* @param WP_REST_Request $request Request data.
|
* @override GenericController::get_datastore_data()
|
||||||
* @return array|WP_Error
|
*
|
||||||
|
* @param array $query_args Query arguments.
|
||||||
|
* @return mixed Results from the data store.
|
||||||
*/
|
*/
|
||||||
public function get_items( $request ) {
|
protected function get_datastore_data( $query_args = array() ) {
|
||||||
$args = array();
|
$query = new GenericQuery( $query_args, 'downloads' );
|
||||||
$registered = array_keys( $this->get_collection_params() );
|
return $query->get_data();
|
||||||
foreach ( $registered as $param_name ) {
|
|
||||||
if ( isset( $request[ $param_name ] ) ) {
|
|
||||||
$args[ $param_name ] = $request[ $param_name ];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$reports = new Query( $args );
|
|
||||||
$downloads_data = $reports->get_data();
|
|
||||||
|
|
||||||
$data = array();
|
|
||||||
|
|
||||||
foreach ( $downloads_data->data as $download_data ) {
|
|
||||||
$item = $this->prepare_item_for_response( $download_data, $request );
|
|
||||||
$data[] = $this->prepare_response_for_collection( $item );
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->add_pagination_headers(
|
|
||||||
$request,
|
|
||||||
$data,
|
|
||||||
(int) $downloads_data->total,
|
|
||||||
(int) $downloads_data->page_no,
|
|
||||||
(int) $downloads_data->pages
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prepare a report object for serialization.
|
* Prepare a report data item for serialization.
|
||||||
*
|
*
|
||||||
* @param Array $report Report data.
|
* @param Array $report Report data item as returned from Data Store.
|
||||||
* @param WP_REST_Request $request Request object.
|
* @param WP_REST_Request $request Request object.
|
||||||
* @return WP_REST_Response
|
* @return WP_REST_Response
|
||||||
*/
|
*/
|
||||||
public function prepare_item_for_response( $report, $request ) {
|
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.
|
// Wrap the data in a response object.
|
||||||
$response = rest_ensure_response( $data );
|
$response = parent::prepare_item_for_response( $report, $request );
|
||||||
$response->add_links( $this->prepare_links( $report ) );
|
$response->add_links( $this->prepare_links( $report ) );
|
||||||
|
|
||||||
$response->data['date'] = get_date_from_gmt( $data['date_gmt'], 'Y-m-d H:i:s' );
|
$response->data['date'] = get_date_from_gmt( $report['date_gmt'], 'Y-m-d H:i:s' );
|
||||||
|
|
||||||
// Figure out file name.
|
// Figure out file name.
|
||||||
// Matches https://github.com/woocommerce/woocommerce/blob/4be0018c092e617c5d2b8c46b800eb71ece9ddef/includes/class-wc-download-handler.php#L197.
|
// Matches https://github.com/woocommerce/woocommerce/blob/4be0018c092e617c5d2b8c46b800eb71ece9ddef/includes/class-wc-download-handler.php#L197.
|
||||||
$product_id = intval( $data['product_id'] );
|
$product_id = intval( $report['product_id'] );
|
||||||
$_product = wc_get_product( $product_id );
|
$_product = wc_get_product( $product_id );
|
||||||
|
|
||||||
// Make sure the product hasn't been deleted.
|
// Make sure the product hasn't been deleted.
|
||||||
if ( $_product ) {
|
if ( $_product ) {
|
||||||
$file_path = $_product->get_file_download_path( $data['download_id'] );
|
$file_path = $_product->get_file_download_path( $report['download_id'] );
|
||||||
$filename = basename( $file_path );
|
$filename = basename( $file_path );
|
||||||
$response->data['file_name'] = apply_filters( 'woocommerce_file_download_filename', $filename, $product_id );
|
$response->data['file_name'] = apply_filters( 'woocommerce_file_download_filename', $filename, $product_id );
|
||||||
$response->data['file_path'] = $file_path;
|
$response->data['file_path'] = $file_path;
|
||||||
|
@ -97,9 +74,9 @@ class Controller extends ReportsController implements ExportableInterface {
|
||||||
$response->data['file_path'] = '';
|
$response->data['file_path'] = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
$customer = new \WC_Customer( $data['user_id'] );
|
$customer = new \WC_Customer( $report['user_id'] );
|
||||||
$response->data['username'] = $customer->get_username();
|
$response->data['username'] = $customer->get_username();
|
||||||
$response->data['order_number'] = $this->get_order_number( $data['order_id'] );
|
$response->data['order_number'] = $this->get_order_number( $report['order_id'] );
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Filter a report returned from the API.
|
* Filter a report returned from the API.
|
||||||
|
@ -130,6 +107,22 @@ class Controller extends ReportsController implements ExportableInterface {
|
||||||
return $links;
|
return $links;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps query arguments from the REST request.
|
||||||
|
*
|
||||||
|
* @param array $request Request array.
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
protected function prepare_reports_query( $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 ];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $args;
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Get the Report's schema, conforming to JSON Schema.
|
* Get the Report's schema, conforming to JSON Schema.
|
||||||
*
|
*
|
||||||
|
@ -225,53 +218,10 @@ class Controller extends ReportsController implements ExportableInterface {
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
public function get_collection_params() {
|
public function get_collection_params() {
|
||||||
$params = array();
|
$params = parent::get_collection_params();
|
||||||
$params['context'] = $this->get_context_param( array( 'default' => 'view' ) );
|
$params['orderby']['enum'] = array(
|
||||||
$params['page'] = array(
|
'date',
|
||||||
'description' => __( 'Current page of the collection.', 'woocommerce' ),
|
'product',
|
||||||
'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',
|
|
||||||
'product',
|
|
||||||
),
|
|
||||||
'validate_callback' => 'rest_validate_request_arg',
|
|
||||||
);
|
);
|
||||||
$params['match'] = array(
|
$params['match'] = array(
|
||||||
'description' => __( 'Indicates whether all the conditions should be true for the resulting set, or if any one of them is sufficient. Match affects the following parameters: products, orders, username, ip_address.', 'woocommerce' ),
|
'description' => __( 'Indicates whether all the conditions should be true for the resulting set, or if any one of them is sufficient. Match affects the following parameters: products, orders, username, ip_address.', 'woocommerce' ),
|
||||||
|
@ -355,12 +305,6 @@ class Controller extends ReportsController implements ExportableInterface {
|
||||||
'type' => 'string',
|
'type' => 'string',
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
$params['force_cache_refresh'] = array(
|
|
||||||
'description' => __( 'Force retrieval of fresh data instead of from the cache.', 'woocommerce' ),
|
|
||||||
'type' => 'boolean',
|
|
||||||
'sanitize_callback' => 'wp_validate_boolean',
|
|
||||||
'validate_callback' => 'rest_validate_request_arg',
|
|
||||||
);
|
|
||||||
|
|
||||||
return $params;
|
return $params;
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,8 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||||
/**
|
/**
|
||||||
* Table used to get the data.
|
* Table used to get the data.
|
||||||
*
|
*
|
||||||
|
* @override ReportsDataStore::$table_name
|
||||||
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
protected static $table_name = 'wc_download_log';
|
protected static $table_name = 'wc_download_log';
|
||||||
|
@ -27,6 +29,8 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||||
/**
|
/**
|
||||||
* Cache identifier.
|
* Cache identifier.
|
||||||
*
|
*
|
||||||
|
* @override ReportsDataStore::$cache_key
|
||||||
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
protected $cache_key = 'downloads';
|
protected $cache_key = 'downloads';
|
||||||
|
@ -34,6 +38,8 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||||
/**
|
/**
|
||||||
* Mapping columns to data type to return correct response types.
|
* Mapping columns to data type to return correct response types.
|
||||||
*
|
*
|
||||||
|
* @override ReportsDataStore::$column_types
|
||||||
|
*
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
protected $column_types = array(
|
protected $column_types = array(
|
||||||
|
@ -51,12 +57,16 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||||
/**
|
/**
|
||||||
* Data store context used to pass to filters.
|
* Data store context used to pass to filters.
|
||||||
*
|
*
|
||||||
|
* @override ReportsDataStore::$context
|
||||||
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
protected $context = 'downloads';
|
protected $context = 'downloads';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Assign report columns once full table name has been assigned.
|
* Assign report columns once full table name has been assigned.
|
||||||
|
*
|
||||||
|
* @override ReportsDataStore::assign_report_columns()
|
||||||
*/
|
*/
|
||||||
protected function assign_report_columns() {
|
protected function assign_report_columns() {
|
||||||
$this->report_columns = array(
|
$this->report_columns = array(
|
||||||
|
@ -252,6 +262,8 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||||
/**
|
/**
|
||||||
* Gets WHERE time clause of SQL request with date-related constraints.
|
* Gets WHERE time clause of SQL request with date-related constraints.
|
||||||
*
|
*
|
||||||
|
* @override ReportsDataStore::add_time_period_sql_params()
|
||||||
|
*
|
||||||
* @param array $query_args Parameters supplied by the user.
|
* @param array $query_args Parameters supplied by the user.
|
||||||
* @param string $table_name Name of the db table relevant for the date constraint.
|
* @param string $table_name Name of the db table relevant for the date constraint.
|
||||||
* @return string
|
* @return string
|
||||||
|
@ -294,94 +306,89 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the report data based on parameters supplied by the user.
|
* Get the default query arguments to be used by get_data().
|
||||||
|
* These defaults are only partially applied when used via REST API, as that has its own defaults.
|
||||||
*
|
*
|
||||||
* @param array $query_args Query parameters.
|
* @override ReportsDataStore::get_default_query_vars()
|
||||||
* @return stdClass|WP_Error Data.
|
*
|
||||||
|
* @return array Query parameters.
|
||||||
*/
|
*/
|
||||||
public function get_data( $query_args ) {
|
public function get_default_query_vars() {
|
||||||
|
$defaults = parent::get_default_query_vars();
|
||||||
|
$defaults['orderby'] = 'timestamp';
|
||||||
|
|
||||||
|
return $defaults;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the report data based on normalized parameters.
|
||||||
|
* Will be called by `get_data` if there is no data in cache.
|
||||||
|
*
|
||||||
|
* @override ReportsDataStore::get_noncached_data()
|
||||||
|
*
|
||||||
|
* @see get_data
|
||||||
|
* @param array $query_args Query parameters.
|
||||||
|
* @return stdClass|WP_Error Data object `{ totals: *, intervals: array, total: int, pages: int, page_no: int }`, or error.
|
||||||
|
*/
|
||||||
|
public function get_noncached_data( $query_args ) {
|
||||||
global $wpdb;
|
global $wpdb;
|
||||||
|
|
||||||
$table_name = self::get_db_table_name();
|
$this->initialize_queries();
|
||||||
|
|
||||||
// These defaults are only partially applied when used via REST API, as that has its own defaults.
|
$data = (object) array(
|
||||||
$defaults = array(
|
'data' => array(),
|
||||||
'per_page' => get_option( 'posts_per_page' ),
|
'total' => 0,
|
||||||
'page' => 1,
|
'pages' => 0,
|
||||||
'order' => 'DESC',
|
'page_no' => 0,
|
||||||
'orderby' => 'timestamp',
|
|
||||||
'before' => TimeInterval::default_before(),
|
|
||||||
'after' => TimeInterval::default_after(),
|
|
||||||
'fields' => '*',
|
|
||||||
);
|
);
|
||||||
$query_args = wp_parse_args( $query_args, $defaults );
|
|
||||||
$this->normalize_timezones( $query_args, $defaults );
|
|
||||||
|
|
||||||
/*
|
$selections = $this->selected_columns( $query_args );
|
||||||
* We need to get the cache key here because
|
$this->add_sql_query_params( $query_args );
|
||||||
* parent::update_intervals_sql_params() modifies $query_args.
|
|
||||||
*/
|
|
||||||
$cache_key = $this->get_cache_key( $query_args );
|
|
||||||
$data = $this->get_cached_data( $cache_key );
|
|
||||||
|
|
||||||
if ( false === $data ) {
|
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||||
$this->initialize_queries();
|
$db_records_count = (int) $wpdb->get_var(
|
||||||
|
"SELECT COUNT(*) FROM (
|
||||||
|
{$this->subquery->get_query_statement()}
|
||||||
|
) AS tt"
|
||||||
|
);
|
||||||
|
// phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||||
|
|
||||||
$data = (object) array(
|
$params = $this->get_limit_params( $query_args );
|
||||||
'data' => array(),
|
$total_pages = (int) ceil( $db_records_count / $params['per_page'] );
|
||||||
'total' => 0,
|
if ( $query_args['page'] < 1 || $query_args['page'] > $total_pages ) {
|
||||||
'pages' => 0,
|
return $data;
|
||||||
'page_no' => 0,
|
|
||||||
);
|
|
||||||
|
|
||||||
$selections = $this->selected_columns( $query_args );
|
|
||||||
$this->add_sql_query_params( $query_args );
|
|
||||||
|
|
||||||
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
|
||||||
$db_records_count = (int) $wpdb->get_var(
|
|
||||||
"SELECT COUNT(*) FROM (
|
|
||||||
{$this->subquery->get_query_statement()}
|
|
||||||
) AS tt"
|
|
||||||
);
|
|
||||||
// phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
|
||||||
|
|
||||||
$params = $this->get_limit_params( $query_args );
|
|
||||||
$total_pages = (int) ceil( $db_records_count / $params['per_page'] );
|
|
||||||
if ( $query_args['page'] < 1 || $query_args['page'] > $total_pages ) {
|
|
||||||
return $data;
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->subquery->clear_sql_clause( 'select' );
|
|
||||||
$this->subquery->add_sql_clause( 'select', $selections );
|
|
||||||
$this->subquery->add_sql_clause( 'order_by', $this->get_sql_clause( 'order_by' ) );
|
|
||||||
$this->subquery->add_sql_clause( 'limit', $this->get_sql_clause( 'limit' ) );
|
|
||||||
|
|
||||||
$download_data = $wpdb->get_results(
|
|
||||||
$this->subquery->get_query_statement(), // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
|
||||||
ARRAY_A
|
|
||||||
);
|
|
||||||
|
|
||||||
if ( null === $download_data ) {
|
|
||||||
return $data;
|
|
||||||
}
|
|
||||||
|
|
||||||
$download_data = array_map( array( $this, 'cast_numbers' ), $download_data );
|
|
||||||
$data = (object) array(
|
|
||||||
'data' => $download_data,
|
|
||||||
'total' => $db_records_count,
|
|
||||||
'pages' => $total_pages,
|
|
||||||
'page_no' => (int) $query_args['page'],
|
|
||||||
);
|
|
||||||
|
|
||||||
$this->set_cached_data( $cache_key, $data );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$this->subquery->clear_sql_clause( 'select' );
|
||||||
|
$this->subquery->add_sql_clause( 'select', $selections );
|
||||||
|
$this->subquery->add_sql_clause( 'order_by', $this->get_sql_clause( 'order_by' ) );
|
||||||
|
$this->subquery->add_sql_clause( 'limit', $this->get_sql_clause( 'limit' ) );
|
||||||
|
|
||||||
|
$download_data = $wpdb->get_results(
|
||||||
|
$this->subquery->get_query_statement(), // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||||
|
ARRAY_A
|
||||||
|
);
|
||||||
|
|
||||||
|
if ( null === $download_data ) {
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
$download_data = array_map( array( $this, 'cast_numbers' ), $download_data );
|
||||||
|
$data = (object) array(
|
||||||
|
'data' => $download_data,
|
||||||
|
'total' => $db_records_count,
|
||||||
|
'pages' => $total_pages,
|
||||||
|
'page_no' => (int) $query_args['page'],
|
||||||
|
);
|
||||||
|
|
||||||
return $data;
|
return $data;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Maps ordering specified by the user to columns in the database/fields in the data.
|
* Maps ordering specified by the user to columns in the database/fields in the data.
|
||||||
*
|
*
|
||||||
|
* @override ReportsDataStore::normalize_order_by()
|
||||||
|
*
|
||||||
* @param string $order_by Sorting criterion.
|
* @param string $order_by Sorting criterion.
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -21,12 +21,16 @@ use Automattic\WooCommerce\Admin\API\Reports\Query as ReportsQuery;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* API\Reports\Downloads\Query
|
* API\Reports\Downloads\Query
|
||||||
|
*
|
||||||
|
* @deprecated 9.3.0 Downloads\Query class is deprecated, please use GenericQuery or \WC_Object_Query instead.
|
||||||
*/
|
*/
|
||||||
class Query extends ReportsQuery {
|
class Query extends ReportsQuery {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Valid fields for downloads report.
|
* Valid fields for downloads report.
|
||||||
*
|
*
|
||||||
|
* @deprecated 9.3.0 Downloads\Query class is deprecated, please use GenericQuery or \WC_Object_Query instead.
|
||||||
|
*
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
protected function get_default_query_vars() {
|
protected function get_default_query_vars() {
|
||||||
|
@ -36,6 +40,8 @@ class Query extends ReportsQuery {
|
||||||
/**
|
/**
|
||||||
* Get downloads data based on the current query vars.
|
* Get downloads data based on the current query vars.
|
||||||
*
|
*
|
||||||
|
* @deprecated 9.3.0 Downloads\Query class is deprecated, please use GenericQuery or \WC_Object_Query instead.
|
||||||
|
*
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
public function get_data() {
|
public function get_data() {
|
||||||
|
|
|
@ -9,6 +9,7 @@ namespace Automattic\WooCommerce\Admin\API\Reports\Downloads\Stats;
|
||||||
|
|
||||||
defined( 'ABSPATH' ) || exit;
|
defined( 'ABSPATH' ) || exit;
|
||||||
|
|
||||||
|
use Automattic\WooCommerce\Admin\API\Reports\GenericQuery;
|
||||||
use Automattic\WooCommerce\Admin\API\Reports\GenericStatsController;
|
use Automattic\WooCommerce\Admin\API\Reports\GenericStatsController;
|
||||||
use WP_REST_Request;
|
use WP_REST_Request;
|
||||||
use WP_REST_Response;
|
use WP_REST_Response;
|
||||||
|
@ -59,39 +60,22 @@ class Controller extends GenericStatsController {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all reports.
|
* Get data from `'downloads-stats'` Query.
|
||||||
*
|
*
|
||||||
* @param WP_REST_Request $request Request data.
|
* @override GenericController::get_datastore_data()
|
||||||
* @return array|WP_Error
|
*
|
||||||
|
* @param array $query_args Query arguments.
|
||||||
|
* @return mixed Results from the data store.
|
||||||
*/
|
*/
|
||||||
public function get_items( $request ) {
|
protected function get_datastore_data( $query_args = array() ) {
|
||||||
$query_args = $this->prepare_reports_query( $request );
|
$query = new GenericQuery( $query_args, 'downloads-stats' );
|
||||||
$downloads_query = new Query( $query_args );
|
return $query->get_data();
|
||||||
$report_data = $downloads_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 );
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->add_pagination_headers(
|
|
||||||
$request,
|
|
||||||
$out_data,
|
|
||||||
(int) $report_data->total,
|
|
||||||
(int) $report_data->page_no,
|
|
||||||
(int) $report_data->pages
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prepare a report object for serialization.
|
* Prepare a report data item for serialization.
|
||||||
*
|
*
|
||||||
* @param array $report Report data.
|
* @param array $report Report data item as returned from Data Store.
|
||||||
* @param WP_REST_Request $request Request object.
|
* @param WP_REST_Request $request Request object.
|
||||||
* @return WP_REST_Response
|
* @return WP_REST_Response
|
||||||
*/
|
*/
|
||||||
|
@ -110,7 +94,6 @@ class Controller extends GenericStatsController {
|
||||||
return apply_filters( 'woocommerce_rest_prepare_report_downloads_stats', $response, $report, $request );
|
return apply_filters( 'woocommerce_rest_prepare_report_downloads_stats', $response, $report, $request );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the Report's item properties schema.
|
* Get the Report's item properties schema.
|
||||||
* Will be used by `get_item_schema` as `totals` and `subtotals`.
|
* Will be used by `get_item_schema` as `totals` and `subtotals`.
|
||||||
|
@ -129,6 +112,7 @@ class Controller extends GenericStatsController {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the Report's schema, conforming to JSON Schema.
|
* Get the Report's schema, conforming to JSON Schema.
|
||||||
* It does not have the segments as in GenericStatsController.
|
* It does not have the segments as in GenericStatsController.
|
||||||
|
@ -298,15 +282,6 @@ class Controller extends GenericStatsController {
|
||||||
'type' => 'string',
|
'type' => 'string',
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
$params['fields'] = array(
|
|
||||||
'description' => __( 'Limit stats fields to the specified items.', 'woocommerce' ),
|
|
||||||
'type' => 'array',
|
|
||||||
'sanitize_callback' => 'wp_parse_slug_list',
|
|
||||||
'validate_callback' => 'rest_validate_request_arg',
|
|
||||||
'items' => array(
|
|
||||||
'type' => 'string',
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
return $params;
|
return $params;
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,16 +10,19 @@ defined( 'ABSPATH' ) || exit;
|
||||||
use Automattic\WooCommerce\Admin\API\Reports\Downloads\DataStore as DownloadsDataStore;
|
use Automattic\WooCommerce\Admin\API\Reports\Downloads\DataStore as DownloadsDataStore;
|
||||||
use Automattic\WooCommerce\Admin\API\Reports\DataStoreInterface;
|
use Automattic\WooCommerce\Admin\API\Reports\DataStoreInterface;
|
||||||
use Automattic\WooCommerce\Admin\API\Reports\TimeInterval;
|
use Automattic\WooCommerce\Admin\API\Reports\TimeInterval;
|
||||||
use Automattic\WooCommerce\Admin\API\Reports\SqlQuery;
|
use Automattic\WooCommerce\Admin\API\Reports\StatsDataStoreTrait;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* API\Reports\Downloads\Stats\DataStore.
|
* API\Reports\Downloads\Stats\DataStore.
|
||||||
*/
|
*/
|
||||||
class DataStore extends DownloadsDataStore implements DataStoreInterface {
|
class DataStore extends DownloadsDataStore implements DataStoreInterface {
|
||||||
|
use StatsDataStoreTrait;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mapping columns to data type to return correct response types.
|
* Mapping columns to data type to return correct response types.
|
||||||
*
|
*
|
||||||
|
* @override DownloadsDataStore::$column_types
|
||||||
|
*
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
protected $column_types = array(
|
protected $column_types = array(
|
||||||
|
@ -29,6 +32,8 @@ class DataStore extends DownloadsDataStore implements DataStoreInterface {
|
||||||
/**
|
/**
|
||||||
* Cache identifier.
|
* Cache identifier.
|
||||||
*
|
*
|
||||||
|
* @override DownloadsDataStore::$cache_key
|
||||||
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
protected $cache_key = 'downloads_stats';
|
protected $cache_key = 'downloads_stats';
|
||||||
|
@ -36,12 +41,16 @@ class DataStore extends DownloadsDataStore implements DataStoreInterface {
|
||||||
/**
|
/**
|
||||||
* Data store context used to pass to filters.
|
* Data store context used to pass to filters.
|
||||||
*
|
*
|
||||||
|
* @override DownloadsDataStore::$context
|
||||||
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
protected $context = 'downloads_stats';
|
protected $context = 'downloads_stats';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Assign report columns once full table name has been assigned.
|
* Assign report columns once full table name has been assigned.
|
||||||
|
*
|
||||||
|
* @override DownloadsDataStore::assign_report_columns()
|
||||||
*/
|
*/
|
||||||
protected function assign_report_columns() {
|
protected function assign_report_columns() {
|
||||||
$this->report_columns = array(
|
$this->report_columns = array(
|
||||||
|
@ -50,111 +59,100 @@ class DataStore extends DownloadsDataStore implements DataStoreInterface {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the report data based on parameters supplied by the user.
|
* Get the default query arguments to be used by get_data().
|
||||||
|
* These defaults are only partially applied when used via REST API, as that has its own defaults.
|
||||||
*
|
*
|
||||||
* @param array $query_args Query parameters.
|
* @override DownloadsDataStore::default_query_args()
|
||||||
* @return stdClass|WP_Error Data.
|
*
|
||||||
|
* @return array Query parameters.
|
||||||
*/
|
*/
|
||||||
public function get_data( $query_args ) {
|
public function get_default_query_vars() {
|
||||||
|
$defaults = parent::get_default_query_vars();
|
||||||
|
$defaults['interval'] = 'week';
|
||||||
|
|
||||||
|
return $defaults;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the report data based on normalized parameters.
|
||||||
|
* Will be called by `get_data` if there is no data in cache.
|
||||||
|
*
|
||||||
|
* @override DownloadsDataStore::get_noncached_data()
|
||||||
|
*
|
||||||
|
* @see get_data
|
||||||
|
* @see get_noncached_stats_data
|
||||||
|
* @param array $query_args Query parameters.
|
||||||
|
* @param array $params Query limit parameters.
|
||||||
|
* @param stdClass $data Reference to the data object to fill.
|
||||||
|
* @param int $expected_interval_count Number of expected intervals.
|
||||||
|
* @return stdClass|WP_Error Data object `{ totals: *, intervals: array, total: int, pages: int, page_no: int }`, or error.
|
||||||
|
*/
|
||||||
|
public function get_noncached_stats_data( $query_args, $params, &$data, $expected_interval_count ) {
|
||||||
global $wpdb;
|
global $wpdb;
|
||||||
|
|
||||||
$table_name = self::get_db_table_name();
|
$table_name = self::get_db_table_name();
|
||||||
|
|
||||||
// These defaults are only partially applied when used via REST API, as that has its own defaults.
|
$this->initialize_queries();
|
||||||
$defaults = array(
|
$selections = $this->selected_columns( $query_args );
|
||||||
'per_page' => get_option( 'posts_per_page' ),
|
$this->add_sql_query_params( $query_args );
|
||||||
'page' => 1,
|
$where_time = $this->add_time_period_sql_params( $query_args, $table_name );
|
||||||
'order' => 'DESC',
|
$this->add_intervals_sql_params( $query_args, $table_name );
|
||||||
'orderby' => 'date',
|
|
||||||
'fields' => '*',
|
$this->interval_query->add_sql_clause( 'select', $this->get_sql_clause( 'select' ) . ' AS time_interval' );
|
||||||
'interval' => 'week',
|
$this->interval_query->str_replace_clause( 'select', 'date_created', 'timestamp' );
|
||||||
'before' => TimeInterval::default_before(),
|
$this->interval_query->str_replace_clause( 'where_time', 'date_created', 'timestamp' );
|
||||||
'after' => TimeInterval::default_after(),
|
|
||||||
|
$db_intervals = $wpdb->get_col(
|
||||||
|
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- cache ok, DB call ok, unprepared SQL ok.
|
||||||
|
$this->interval_query->get_query_statement()
|
||||||
);
|
);
|
||||||
$query_args = wp_parse_args( $query_args, $defaults );
|
|
||||||
$this->normalize_timezones( $query_args, $defaults );
|
|
||||||
|
|
||||||
/*
|
$db_records_count = count( $db_intervals );
|
||||||
* We need to get the cache key here because
|
|
||||||
* parent::update_intervals_sql_params() modifies $query_args.
|
|
||||||
*/
|
|
||||||
$cache_key = $this->get_cache_key( $query_args );
|
|
||||||
$data = $this->get_cached_data( $cache_key );
|
|
||||||
|
|
||||||
if ( false === $data ) {
|
$this->update_intervals_sql_params( $query_args, $db_records_count, $expected_interval_count, $table_name );
|
||||||
$this->initialize_queries();
|
$this->interval_query->str_replace_clause( 'where_time', 'date_created', 'timestamp' );
|
||||||
$selections = $this->selected_columns( $query_args );
|
$this->total_query->add_sql_clause( 'select', $selections );
|
||||||
$this->add_sql_query_params( $query_args );
|
$this->total_query->add_sql_clause( 'where', $this->interval_query->get_sql_clause( 'where' ) );
|
||||||
$where_time = $this->add_time_period_sql_params( $query_args, $table_name );
|
if ( $where_time ) {
|
||||||
$this->add_intervals_sql_params( $query_args, $table_name );
|
$this->total_query->add_sql_clause( 'where_time', $where_time );
|
||||||
|
}
|
||||||
|
$totals = $wpdb->get_results(
|
||||||
|
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- cache ok, DB call ok, unprepared SQL ok.
|
||||||
|
$this->total_query->get_query_statement(),
|
||||||
|
ARRAY_A
|
||||||
|
);
|
||||||
|
if ( null === $totals ) {
|
||||||
|
return new \WP_Error( 'woocommerce_analytics_downloads_stats_result_failed', __( 'Sorry, fetching downloads data failed.', 'woocommerce' ) );
|
||||||
|
}
|
||||||
|
|
||||||
$this->interval_query->add_sql_clause( 'select', $this->get_sql_clause( 'select' ) . ' AS time_interval' );
|
$this->interval_query->add_sql_clause( 'order_by', $this->get_sql_clause( 'order_by' ) );
|
||||||
$this->interval_query->str_replace_clause( 'select', 'date_created', 'timestamp' );
|
$this->interval_query->add_sql_clause( 'limit', $this->get_sql_clause( 'limit' ) );
|
||||||
$this->interval_query->str_replace_clause( 'where_time', 'date_created', 'timestamp' );
|
$this->interval_query->add_sql_clause( 'select', ', MAX(timestamp) AS datetime_anchor' );
|
||||||
|
if ( '' !== $selections ) {
|
||||||
|
$this->interval_query->add_sql_clause( 'select', ', ' . $selections );
|
||||||
|
}
|
||||||
|
|
||||||
$db_intervals = $wpdb->get_col(
|
$intervals = $wpdb->get_results(
|
||||||
$this->interval_query->get_query_statement()
|
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- cache ok, DB call ok, unprepared SQL ok.
|
||||||
); // phpcs:ignore cache ok, DB call ok, unprepared SQL ok.
|
$this->interval_query->get_query_statement(),
|
||||||
|
ARRAY_A
|
||||||
|
);
|
||||||
|
|
||||||
$db_records_count = count( $db_intervals );
|
if ( null === $intervals ) {
|
||||||
|
return new \WP_Error( 'woocommerce_analytics_downloads_stats_result_failed', __( 'Sorry, fetching downloads data failed.', 'woocommerce' ) );
|
||||||
|
}
|
||||||
|
|
||||||
$params = $this->get_limit_params( $query_args );
|
$totals = (object) $this->cast_numbers( $totals[0] );
|
||||||
$expected_interval_count = TimeInterval::intervals_between( $query_args['after'], $query_args['before'], $query_args['interval'] );
|
|
||||||
$total_pages = (int) ceil( $expected_interval_count / $params['per_page'] );
|
|
||||||
if ( $query_args['page'] < 1 || $query_args['page'] > $total_pages ) {
|
|
||||||
return array();
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->update_intervals_sql_params( $query_args, $db_records_count, $expected_interval_count, $table_name );
|
$data->totals = $totals;
|
||||||
$this->interval_query->str_replace_clause( 'where_time', 'date_created', 'timestamp' );
|
$data->intervals = $intervals;
|
||||||
$this->total_query->add_sql_clause( 'select', $selections );
|
|
||||||
$this->total_query->add_sql_clause( 'where', $this->interval_query->get_sql_clause( 'where' ) );
|
|
||||||
if ( $where_time ) {
|
|
||||||
$this->total_query->add_sql_clause( 'where_time', $where_time );
|
|
||||||
}
|
|
||||||
$totals = $wpdb->get_results(
|
|
||||||
$this->total_query->get_query_statement(),
|
|
||||||
ARRAY_A
|
|
||||||
); // phpcs:ignore cache ok, DB call ok, unprepared SQL ok.
|
|
||||||
if ( null === $totals ) {
|
|
||||||
return new \WP_Error( 'woocommerce_analytics_downloads_stats_result_failed', __( 'Sorry, fetching downloads data failed.', 'woocommerce' ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->interval_query->add_sql_clause( 'order_by', $this->get_sql_clause( 'order_by' ) );
|
if ( $this->intervals_missing( $expected_interval_count, $db_records_count, $params['per_page'], $query_args['page'], $query_args['order'], $query_args['orderby'], count( $intervals ) ) ) {
|
||||||
$this->interval_query->add_sql_clause( 'limit', $this->get_sql_clause( 'limit' ) );
|
$this->fill_in_missing_intervals( $db_intervals, $query_args['adj_after'], $query_args['adj_before'], $query_args['interval'], $data );
|
||||||
$this->interval_query->add_sql_clause( 'select', ', MAX(timestamp) AS datetime_anchor' );
|
$this->sort_intervals( $data, $query_args['orderby'], $query_args['order'] );
|
||||||
if ( '' !== $selections ) {
|
$this->remove_extra_records( $data, $query_args['page'], $params['per_page'], $db_records_count, $expected_interval_count, $query_args['orderby'], $query_args['order'] );
|
||||||
$this->interval_query->add_sql_clause( 'select', ', ' . $selections );
|
} else {
|
||||||
}
|
$this->update_interval_boundary_dates( $query_args['after'], $query_args['before'], $query_args['interval'], $data->intervals );
|
||||||
|
|
||||||
$intervals = $wpdb->get_results(
|
|
||||||
$this->interval_query->get_query_statement(),
|
|
||||||
ARRAY_A
|
|
||||||
); // phpcs:ignore cache ok, DB call ok, unprepared SQL ok.
|
|
||||||
|
|
||||||
if ( null === $intervals ) {
|
|
||||||
return new \WP_Error( 'woocommerce_analytics_downloads_stats_result_failed', __( 'Sorry, fetching downloads data failed.', 'woocommerce' ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
$totals = (object) $this->cast_numbers( $totals[0] );
|
|
||||||
$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_records_count, $params['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'], $params['per_page'], $db_records_count, $expected_interval_count, $query_args['orderby'], $query_args['order'] );
|
|
||||||
} else {
|
|
||||||
$this->update_interval_boundary_dates( $query_args['after'], $query_args['before'], $query_args['interval'], $data->intervals );
|
|
||||||
}
|
|
||||||
$this->create_interval_subtotals( $data->intervals );
|
|
||||||
|
|
||||||
$this->set_cached_data( $cache_key, $data );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $data;
|
return $data;
|
||||||
|
@ -163,6 +161,8 @@ class DataStore extends DownloadsDataStore implements DataStoreInterface {
|
||||||
/**
|
/**
|
||||||
* Normalizes order_by clause to match to SQL query.
|
* Normalizes order_by clause to match to SQL query.
|
||||||
*
|
*
|
||||||
|
* @override DownloadsDataStore::normalize_order_by()
|
||||||
|
*
|
||||||
* @param string $order_by Order by option requeste by user.
|
* @param string $order_by Order by option requeste by user.
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
|
@ -173,18 +173,4 @@ class DataStore extends DownloadsDataStore implements DataStoreInterface {
|
||||||
|
|
||||||
return $order_by;
|
return $order_by;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize query objects.
|
|
||||||
*/
|
|
||||||
protected function initialize_queries() {
|
|
||||||
$this->clear_all_clauses();
|
|
||||||
unset( $this->subquery );
|
|
||||||
$this->total_query = new SqlQuery( $this->context . '_total' );
|
|
||||||
$this->total_query->add_sql_clause( 'from', self::get_db_table_name() );
|
|
||||||
|
|
||||||
$this->interval_query = new SqlQuery( $this->context . '_interval' );
|
|
||||||
$this->interval_query->add_sql_clause( 'from', self::get_db_table_name() );
|
|
||||||
$this->interval_query->add_sql_clause( 'group_by', 'time_interval' );
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,12 +11,16 @@ use Automattic\WooCommerce\Admin\API\Reports\Query as ReportsQuery;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* API\Reports\Downloads\Stats\Query
|
* API\Reports\Downloads\Stats\Query
|
||||||
|
*
|
||||||
|
* @deprecated 9.3.0 Downloads\Stats\Query class is deprecated, please use GenericQuery or \WC_Object_Query instead.
|
||||||
*/
|
*/
|
||||||
class Query extends ReportsQuery {
|
class Query extends ReportsQuery {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Valid fields for Orders report.
|
* Valid fields for Orders report.
|
||||||
*
|
*
|
||||||
|
* @deprecated 9.3.0 Downloads\Stats\Query class is deprecated, please use GenericQuery or \WC_Object_Query instead.
|
||||||
|
*
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
protected function get_default_query_vars() {
|
protected function get_default_query_vars() {
|
||||||
|
@ -26,6 +30,8 @@ class Query extends ReportsQuery {
|
||||||
/**
|
/**
|
||||||
* Get revenue data based on the current query vars.
|
* Get revenue data based on the current query vars.
|
||||||
*
|
*
|
||||||
|
* @deprecated 9.3.0 Downloads\Stats\Query class is deprecated, please use GenericQuery or \WC_Object_Query instead.
|
||||||
|
*
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
public function get_data() {
|
public function get_data() {
|
||||||
|
|
|
@ -0,0 +1,58 @@
|
||||||
|
<?php
|
||||||
|
declare( strict_types = 1);
|
||||||
|
|
||||||
|
namespace Automattic\WooCommerce\Admin\API\Reports;
|
||||||
|
|
||||||
|
// Exit if accessed directly.
|
||||||
|
if ( ! defined( 'ABSPATH' ) ) {
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Trait to call filters on `get_data` methods for data stores.
|
||||||
|
*
|
||||||
|
* It calls the filters `woocommerce_analytics_{$this->context}_query_args` and
|
||||||
|
* `woocommerce_analytics_{$this->context}_select_query` on the `get_data` method.
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
* <pre><code class="language-php">class MyStatsDataStore extends DataStore implements DataStoreInterface {
|
||||||
|
* // Use the trait.
|
||||||
|
* use FilteredGetDataTrait;
|
||||||
|
* // Provide all the necessary properties and methods for a regular DataStore.
|
||||||
|
* // ...
|
||||||
|
* }
|
||||||
|
* </code></pre>
|
||||||
|
*
|
||||||
|
* @see DataStore
|
||||||
|
*/
|
||||||
|
trait FilteredGetDataTrait {
|
||||||
|
/**
|
||||||
|
* Get the data based on args.
|
||||||
|
*
|
||||||
|
* Filters query args, calls DataStore::get_data, and returns the filtered data.
|
||||||
|
*
|
||||||
|
* @override ReportsDataStore::get_data()
|
||||||
|
*
|
||||||
|
* @param array $query_args Query parameters.
|
||||||
|
* @return stdClass|WP_Error
|
||||||
|
*/
|
||||||
|
public function get_data( $query_args ) {
|
||||||
|
/**
|
||||||
|
* Called before the data is fetched.
|
||||||
|
*
|
||||||
|
* @since 9.3.0
|
||||||
|
* @param array $query_args Query parameters.
|
||||||
|
*/
|
||||||
|
$args = apply_filters( "woocommerce_analytics_{$this->context}_query_args", $query_args );
|
||||||
|
$results = parent::get_data( $args );
|
||||||
|
/**
|
||||||
|
* Called after the data is fetched.
|
||||||
|
* The results can be modified here.
|
||||||
|
*
|
||||||
|
* @since 9.3.0
|
||||||
|
* @param stdClass|WP_Error $results The results of the query.
|
||||||
|
*/
|
||||||
|
return apply_filters( "woocommerce_analytics_{$this->context}_select_query", $results, $args );
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,10 +7,45 @@ use WP_REST_Request;
|
||||||
use WP_REST_Response;
|
use WP_REST_Response;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* WC REST API Reports controller extended
|
* {@see WC_REST_Reports_Controller WC REST API Reports Controller} extended to be shared as a generic base for all Analytics reports controllers.
|
||||||
* to be shared as a generic base for all Analytics controllers.
|
*
|
||||||
|
* Handles pagination HTTP headers and links, basic, conventional params.
|
||||||
|
* Does all the REST API plumbing as `WC_REST_Controller`.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Minimalistic example:
|
||||||
|
* <pre><code class="language-php">class MyController extends GenericController {
|
||||||
|
* /** Route of your new REST endpoint. */
|
||||||
|
* protected $rest_base = 'reports/my-thing';
|
||||||
|
* /**
|
||||||
|
* * Provide JSON schema for the response item.
|
||||||
|
* * @override WC_REST_Reports_Controller::get_item_schema()
|
||||||
|
* */
|
||||||
|
* public function get_item_schema() {
|
||||||
|
* $schema = array(
|
||||||
|
* '$schema' => 'http://json-schema.org/draft-04/schema#',
|
||||||
|
* 'title' => 'report_my_thing',
|
||||||
|
* 'type' => 'object',
|
||||||
|
* 'properties' => array(
|
||||||
|
* 'product_id' => array(
|
||||||
|
* 'type' => 'integer',
|
||||||
|
* 'readonly' => true,
|
||||||
|
* 'context' => array( 'view', 'edit' ),
|
||||||
|
* 'description' => __( 'Product ID.', 'my_extension' ),
|
||||||
|
* ),
|
||||||
|
* ),
|
||||||
|
* );
|
||||||
|
* // Add additional fields from `get_additional_fields` method and apply `woocommerce_rest_' . $schema['title'] . '_schema` filter.
|
||||||
|
* return $this->add_additional_fields_schema( $schema );
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* </code></pre>
|
||||||
|
*
|
||||||
|
* The above Controller will get the data from a {@see DataStore data store} registered as `$rest_base` (`reports/my-thing`).
|
||||||
|
* (To change this behavior, override the `get_datastore_data()` method).
|
||||||
|
*
|
||||||
|
* To use the controller, please register it with the filter `woocommerce_admin_rest_controllers` filter.
|
||||||
*
|
*
|
||||||
* @internal
|
|
||||||
* @extends WC_REST_Reports_Controller
|
* @extends WC_REST_Reports_Controller
|
||||||
*/
|
*/
|
||||||
abstract class GenericController extends \WC_REST_Reports_Controller {
|
abstract class GenericController extends \WC_REST_Reports_Controller {
|
||||||
|
@ -26,12 +61,12 @@ abstract class GenericController extends \WC_REST_Reports_Controller {
|
||||||
/**
|
/**
|
||||||
* Add pagination headers and links.
|
* Add pagination headers and links.
|
||||||
*
|
*
|
||||||
* @param WP_REST_Request $request Request data.
|
* @param \WP_REST_Request $request Request data.
|
||||||
* @param WP_REST_Response|array $response Response data.
|
* @param \WP_REST_Response|array $response Response data.
|
||||||
* @param int $total Total results.
|
* @param int $total Total results.
|
||||||
* @param int $page Current page.
|
* @param int $page Current page.
|
||||||
* @param int $max_pages Total amount of pages.
|
* @param int $max_pages Total amount of pages.
|
||||||
* @return WP_REST_Response
|
* @return \WP_REST_Response
|
||||||
*/
|
*/
|
||||||
public function add_pagination_headers( $request, $response, int $total, int $page, int $max_pages ) {
|
public function add_pagination_headers( $request, $response, int $total, int $page, int $max_pages ) {
|
||||||
$response = rest_ensure_response( $response );
|
$response = rest_ensure_response( $response );
|
||||||
|
@ -62,7 +97,19 @@ abstract class GenericController extends \WC_REST_Reports_Controller {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the query params for collections.
|
* Get data from `{$this->rest_base}` store, based on the given query vars.
|
||||||
|
*
|
||||||
|
* @throws Exception When the data store is not found {@see WC_Data_Store WC_Data_Store}.
|
||||||
|
* @param array $query_args Query arguments.
|
||||||
|
* @return mixed Results from the data store.
|
||||||
|
*/
|
||||||
|
protected function get_datastore_data( $query_args = array() ) {
|
||||||
|
$data_store = \WC_Data_Store::load( $this->rest_base );
|
||||||
|
return $data_store->get_data( $query_args );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the query params definition for collections.
|
||||||
*
|
*
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
|
@ -124,15 +171,62 @@ abstract class GenericController extends \WC_REST_Reports_Controller {
|
||||||
return $params;
|
return $params;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prepare a report object for serialization.
|
* Get the report data.
|
||||||
*
|
*
|
||||||
* @param array $report Report data.
|
* Prepares query params, fetches the report data from the Query object,
|
||||||
* @param WP_REST_Request $request Request object.
|
* prepares it for the response, and packs it into the convention-conforming response object.
|
||||||
|
*
|
||||||
|
* @throws \WP_Error When the queried data is invalid.
|
||||||
|
* @param \WP_REST_Request $request Request data.
|
||||||
|
* @return \WP_Error|\WP_REST_Response
|
||||||
|
*/
|
||||||
|
public function get_items( $request ) {
|
||||||
|
$query_args = $this->prepare_reports_query( $request );
|
||||||
|
$report_data = $this->get_datastore_data( $query_args );
|
||||||
|
|
||||||
|
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_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 );
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->add_pagination_headers(
|
||||||
|
$request,
|
||||||
|
$out_data,
|
||||||
|
(int) $report_data->total,
|
||||||
|
(int) $report_data->page_no,
|
||||||
|
(int) $report_data->pages
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepare a report data item for serialization.
|
||||||
|
*
|
||||||
|
* This method is called by `get_items` to prepare a single report data item for serialization.
|
||||||
|
* Calls `add_additional_fields_to_object` and `filter_response_by_context`,
|
||||||
|
* then wpraps the data with `rest_ensure_response`.
|
||||||
|
*
|
||||||
|
* You can extend it to add or filter some fields.
|
||||||
|
*
|
||||||
|
* @override WP_REST_Posts_Controller::prepare_item_for_response()
|
||||||
|
*
|
||||||
|
* @param mixed $report_item Report data item as returned from Data Store.
|
||||||
|
* @param WP_REST_Request $request Request object.
|
||||||
* @return WP_REST_Response
|
* @return WP_REST_Response
|
||||||
*/
|
*/
|
||||||
public function prepare_item_for_response( $report, $request ) {
|
public function prepare_item_for_response( $report_item, $request ) {
|
||||||
$data = $report;
|
$data = $report_item;
|
||||||
|
|
||||||
$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
|
$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
|
||||||
$data = $this->add_additional_fields_to_object( $data, $request );
|
$data = $this->add_additional_fields_to_object( $data, $request );
|
||||||
|
@ -141,4 +235,26 @@ abstract class GenericController extends \WC_REST_Reports_Controller {
|
||||||
// Wrap the data in a response object.
|
// Wrap the data in a response object.
|
||||||
return rest_ensure_response( $data );
|
return rest_ensure_response( $data );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps query arguments from the REST request, to be fed to Query.
|
||||||
|
*
|
||||||
|
* `WP_REST_Request` does not expose a method to return all params covering defaults,
|
||||||
|
* as it does for `$request['param']` accessor.
|
||||||
|
* Therefore, we re-implement defaults resolution.
|
||||||
|
*
|
||||||
|
* @param \WP_REST_Request $request Full request object.
|
||||||
|
* @return array Simplified array of params.
|
||||||
|
*/
|
||||||
|
protected function prepare_reports_query( $request ) {
|
||||||
|
$args = wp_parse_args(
|
||||||
|
array_intersect_key(
|
||||||
|
$request->get_query_params(),
|
||||||
|
$this->get_collection_params()
|
||||||
|
),
|
||||||
|
$request->get_default_params()
|
||||||
|
);
|
||||||
|
|
||||||
|
return $args;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,91 @@
|
||||||
|
<?php
|
||||||
|
declare( strict_types = 1);
|
||||||
|
|
||||||
|
namespace Automattic\WooCommerce\Admin\API\Reports;
|
||||||
|
|
||||||
|
defined( 'ABSPATH' ) || exit;
|
||||||
|
|
||||||
|
use WC_Data_Store;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A generic class for a report-specific query to be used in Analytics.
|
||||||
|
*
|
||||||
|
* Example usage:
|
||||||
|
* <pre><code class="language-php">$args = array(
|
||||||
|
* 'before' => '2018-07-19 00:00:00',
|
||||||
|
* 'after' => '2018-07-05 00:00:00',
|
||||||
|
* 'page' => 2,
|
||||||
|
* );
|
||||||
|
* $report = new GenericQuery( $args, 'coupons' );
|
||||||
|
* $mydata = $report->get_data();
|
||||||
|
* </code></pre>
|
||||||
|
*
|
||||||
|
* It uses the name provided in the class property or in the constructor call to load the `report-{name}` data store.
|
||||||
|
*
|
||||||
|
* It's used by the {@see GenericController GenericController}.
|
||||||
|
*
|
||||||
|
* @since 9.3.0
|
||||||
|
*/
|
||||||
|
class GenericQuery extends \WC_Object_Query {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specific query name.
|
||||||
|
* Will be used to load the `report-{name}` data store,
|
||||||
|
* and to call `woocommerce_analytics_{snake_case(name)}_*` filters.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $name;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new query.
|
||||||
|
*
|
||||||
|
* @param array $args Criteria to query on in a format similar to WP_Query.
|
||||||
|
* @param string $name Query name.
|
||||||
|
* @extends WC_Object_Query::_construct
|
||||||
|
*/
|
||||||
|
public function __construct( $args, $name = null ) {
|
||||||
|
$this->name = $name ?? $this->name;
|
||||||
|
|
||||||
|
return parent::__construct( $args ); // phpcs:ignore Universal.CodeAnalysis.ConstructorDestructorReturn.ReturnValueFound
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Valid fields for Products report.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
protected function get_default_query_vars() {
|
||||||
|
return array();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get data from `report-{$name}` store, based on the current query vars.
|
||||||
|
* Filters query vars through `woocommerce_analytics_{snake_case(name)}_query_args` filter.
|
||||||
|
* Filters results through `woocommerce_analytics_{snake_case(name)}_select_query` filter.
|
||||||
|
*
|
||||||
|
* @return mixed filtered results from the data store.
|
||||||
|
*/
|
||||||
|
public function get_data() {
|
||||||
|
$snake_name = str_replace( '-', '_', $this->name );
|
||||||
|
/**
|
||||||
|
* Filter query args given for the report.
|
||||||
|
*
|
||||||
|
* @since 9.3.0
|
||||||
|
*
|
||||||
|
* @param array $query_args Query args.
|
||||||
|
*/
|
||||||
|
$args = apply_filters( "woocommerce_analytics_{$snake_name}_query_args", $this->get_query_vars() );
|
||||||
|
|
||||||
|
$data_store = \WC_Data_Store::load( "report-{$this->name}" );
|
||||||
|
$results = $data_store->get_data( $args );
|
||||||
|
/**
|
||||||
|
* Filter report query results.
|
||||||
|
*
|
||||||
|
* @since 9.3.0
|
||||||
|
*
|
||||||
|
* @param stdClass|WP_Error $results Results from the data store.
|
||||||
|
* @param array $args Query args used to get the data (potentially filtered).
|
||||||
|
*/
|
||||||
|
return apply_filters( "woocommerce_analytics_{$snake_name}_select_query", $results, $args );
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,21 +6,69 @@ defined( 'ABSPATH' ) || exit;
|
||||||
use Automattic\WooCommerce\Admin\API\Reports\GenericController;
|
use Automattic\WooCommerce\Admin\API\Reports\GenericController;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generic base for all Stats controllers.
|
* Generic base for all stats controllers.
|
||||||
|
*
|
||||||
|
* {@see GenericController Generic Controller} extended to be shared as a generic base for all Analytics stats controllers.
|
||||||
|
*
|
||||||
|
* Besides the `GenericController` functionality, it adds conventional stats-specific collection params and item schema.
|
||||||
|
* So, you may want to extend only your report-specific {@see get_item_properties_schema() get_item_properties_schema()}`.
|
||||||
|
* It also uses the stats-specific {@see get_items() get_items()} method,
|
||||||
|
* which packs report data into `totals` and `intervals`.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Minimalistic example:
|
||||||
|
* <pre><code class="language-php">class StatsController extends GenericStatsController {
|
||||||
|
* /** Route of your new REST endpoint. */
|
||||||
|
* protected $rest_base = 'reports/my-thing/stats';
|
||||||
|
* /** Define your proeprties schema. */
|
||||||
|
* protected function get_item_properties_schema() {
|
||||||
|
* return array(
|
||||||
|
* 'my_property' => array(
|
||||||
|
* 'title' => __( 'My property', 'my-extension' ),
|
||||||
|
* 'type' => 'integer',
|
||||||
|
* 'readonly' => true,
|
||||||
|
* 'context' => array( 'view', 'edit' ),
|
||||||
|
* 'description' => __( 'Amazing thing.', 'my-extension' ),
|
||||||
|
* 'indicator' => true,
|
||||||
|
* ),
|
||||||
|
* );
|
||||||
|
* }
|
||||||
|
* /** Define overall schema. You can use the defaults,
|
||||||
|
* * just remember to provide your title and call `add_additional_fields_schema`
|
||||||
|
* * to run the filters
|
||||||
|
* */
|
||||||
|
* public function get_item_schema() {
|
||||||
|
* $schema = parent::get_item_schema();
|
||||||
|
* $schema['title'] = 'report_my_thing_stats';
|
||||||
|
*
|
||||||
|
* return $this->add_additional_fields_schema( $schema );
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* </code></pre>
|
||||||
*
|
*
|
||||||
* @internal
|
|
||||||
* @extends GenericController
|
* @extends GenericController
|
||||||
*/
|
*/
|
||||||
abstract class GenericStatsController extends GenericController {
|
abstract class GenericStatsController extends GenericController {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the query params for collections.
|
* Get the query params definition for collections.
|
||||||
* Adds intervals to the generic list.
|
* Adds `fields` & `intervals` to the generic list.
|
||||||
|
*
|
||||||
|
* @override GenericController::get_collection_params()
|
||||||
*
|
*
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
public function get_collection_params() {
|
public function get_collection_params() {
|
||||||
$params = parent::get_collection_params();
|
$params = parent::get_collection_params();
|
||||||
|
$params['fields'] = array(
|
||||||
|
'description' => __( 'Limit stats fields to the specified items.', 'woocommerce' ),
|
||||||
|
'type' => 'array',
|
||||||
|
'sanitize_callback' => 'wp_parse_slug_list',
|
||||||
|
'validate_callback' => 'rest_validate_request_arg',
|
||||||
|
'items' => array(
|
||||||
|
'type' => 'string',
|
||||||
|
),
|
||||||
|
);
|
||||||
$params['interval'] = array(
|
$params['interval'] = array(
|
||||||
'description' => __( 'Time interval to use for buckets in the returned data.', 'woocommerce' ),
|
'description' => __( 'Time interval to use for buckets in the returned data.', 'woocommerce' ),
|
||||||
'type' => 'string',
|
'type' => 'string',
|
||||||
|
@ -40,7 +88,7 @@ abstract class GenericStatsController extends GenericController {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the Report's item properties schema.
|
* Get the report's item properties schema.
|
||||||
* Will be used by `get_item_schema` as `totals` and `subtotals`.
|
* Will be used by `get_item_schema` as `totals` and `subtotals`.
|
||||||
*
|
*
|
||||||
* @return array
|
* @return array
|
||||||
|
@ -50,7 +98,7 @@ abstract class GenericStatsController extends GenericController {
|
||||||
/**
|
/**
|
||||||
* Get the Report's schema, conforming to JSON Schema.
|
* Get the Report's schema, conforming to JSON Schema.
|
||||||
*
|
*
|
||||||
* Please note, it does not call add_additional_fields_schema,
|
* Please note that it does not call add_additional_fields_schema,
|
||||||
* as you may want to update the `title` first.
|
* as you may want to update the `title` first.
|
||||||
*
|
*
|
||||||
* @return array
|
* @return array
|
||||||
|
@ -155,4 +203,43 @@ abstract class GenericStatsController extends GenericController {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the report data.
|
||||||
|
*
|
||||||
|
* Prepares query params, fetches the report data from the Query object,
|
||||||
|
* prepares it for the response, and packs it into the convention-conforming response object.
|
||||||
|
*
|
||||||
|
* @override GenericController::get_items()
|
||||||
|
*
|
||||||
|
* @throws \WP_Error When the queried data is invalid.
|
||||||
|
* @param \WP_REST_Request $request Request data.
|
||||||
|
* @return \WP_REST_Response|\WP_Error
|
||||||
|
*/
|
||||||
|
public function get_items( $request ) {
|
||||||
|
$query_args = $this->prepare_reports_query( $request );
|
||||||
|
try {
|
||||||
|
$report_data = $this->get_datastore_data( $query_args );
|
||||||
|
} catch ( ParameterException $e ) {
|
||||||
|
return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
$out_data = array(
|
||||||
|
'totals' => $report_data->totals ? get_object_vars( $report_data->totals ) : null,
|
||||||
|
'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 );
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->add_pagination_headers(
|
||||||
|
$request,
|
||||||
|
$out_data,
|
||||||
|
(int) $report_data->total,
|
||||||
|
(int) $report_data->page_no,
|
||||||
|
(int) $report_data->pages
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,123 @@
|
||||||
|
<?php
|
||||||
|
declare( strict_types = 1);
|
||||||
|
|
||||||
|
namespace Automattic\WooCommerce\Admin\API\Reports;
|
||||||
|
|
||||||
|
// Exit if accessed directly.
|
||||||
|
if ( ! defined( 'ABSPATH' ) ) {
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Trait to contain shared methods for reports Controllers that use order and orders statuses.
|
||||||
|
*
|
||||||
|
* If your analytics controller needs to work with orders,
|
||||||
|
* you will most probably need to use at least {@see get_order_statuses() get_order_statuses()}
|
||||||
|
* to filter only "actionable" statuses to produce consistent results among other analytics.
|
||||||
|
*
|
||||||
|
* @see GenericController
|
||||||
|
*/
|
||||||
|
trait OrderAwareControllerTrait {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the order number for an order. If no filter is present for `woocommerce_order_number`, we can just return the ID.
|
||||||
|
* Returns the parent order number if the order is actually a refund.
|
||||||
|
*
|
||||||
|
* @param int $order_id Order ID.
|
||||||
|
* @return string|null The Order Number or null if the order doesn't exist.
|
||||||
|
*/
|
||||||
|
protected function get_order_number( $order_id ) {
|
||||||
|
$order = wc_get_order( $order_id );
|
||||||
|
|
||||||
|
if ( ! $this->is_valid_order( $order ) ) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( 'shop_order_refund' === $order->get_type() ) {
|
||||||
|
$order = wc_get_order( $order->get_parent_id() );
|
||||||
|
|
||||||
|
// If the parent order doesn't exist, return null.
|
||||||
|
if ( ! $this->is_valid_order( $order ) ) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( ! has_filter( 'woocommerce_order_number' ) ) {
|
||||||
|
return $order->get_id();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $order->get_order_number();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the order is valid.
|
||||||
|
*
|
||||||
|
* @param bool|WC_Order|WC_Order_Refund $order Order object.
|
||||||
|
* @return bool True if the order is valid, false otherwise.
|
||||||
|
*/
|
||||||
|
protected function is_valid_order( $order ) {
|
||||||
|
return $order instanceof \WC_Order || $order instanceof \WC_Order_Refund;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the order total with the related currency formatting.
|
||||||
|
* Returns the parent order total if the order is actually a refund.
|
||||||
|
*
|
||||||
|
* @param int $order_id Order ID.
|
||||||
|
* @return string|null The Order Number or null if the order doesn't exist.
|
||||||
|
*/
|
||||||
|
protected function get_total_formatted( $order_id ) {
|
||||||
|
$order = wc_get_order( $order_id );
|
||||||
|
|
||||||
|
if ( ! $this->is_valid_order( $order ) ) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( 'shop_order_refund' === $order->get_type() ) {
|
||||||
|
$order = wc_get_order( $order->get_parent_id() );
|
||||||
|
|
||||||
|
if ( ! $this->is_valid_order( $order ) ) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return wp_strip_all_tags( html_entity_decode( $order->get_formatted_order_total() ), true );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get order statuses without prefixes.
|
||||||
|
* Includes unregistered statuses that have been marked "actionable".
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public static function get_order_statuses() {
|
||||||
|
// Allow all statuses selected as "actionable" - this may include unregistered statuses.
|
||||||
|
// See: https://github.com/woocommerce/woocommerce-admin/issues/5592.
|
||||||
|
$actionable_statuses = get_option( 'woocommerce_actionable_order_statuses', array() );
|
||||||
|
|
||||||
|
// See WC_REST_Orders_V2_Controller::get_collection_params() re: any/trash statuses.
|
||||||
|
$registered_statuses = array_merge( array( 'any', 'trash' ), array_keys( self::get_order_status_labels() ) );
|
||||||
|
|
||||||
|
// Merge the status arrays (using flip to avoid array_unique()).
|
||||||
|
$allowed_statuses = array_keys( array_merge( array_flip( $registered_statuses ), array_flip( $actionable_statuses ) ) );
|
||||||
|
|
||||||
|
return $allowed_statuses;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get order statuses (and labels) without prefixes.
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public static function get_order_status_labels() {
|
||||||
|
$order_statuses = array();
|
||||||
|
|
||||||
|
foreach ( wc_get_order_statuses() as $key => $label ) {
|
||||||
|
$new_key = str_replace( 'wc-', '', $key );
|
||||||
|
$order_statuses[ $new_key ] = $label;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $order_statuses;
|
||||||
|
}
|
||||||
|
}
|
|
@ -9,16 +9,19 @@ namespace Automattic\WooCommerce\Admin\API\Reports\Orders;
|
||||||
|
|
||||||
defined( 'ABSPATH' ) || exit;
|
defined( 'ABSPATH' ) || exit;
|
||||||
|
|
||||||
use Automattic\WooCommerce\Admin\API\Reports\Controller as ReportsController;
|
|
||||||
use Automattic\WooCommerce\Admin\API\Reports\ExportableInterface;
|
use Automattic\WooCommerce\Admin\API\Reports\ExportableInterface;
|
||||||
|
use Automattic\WooCommerce\Admin\API\Reports\GenericController;
|
||||||
|
use Automattic\WooCommerce\Admin\API\Reports\OrderAwareControllerTrait;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* REST API Reports orders controller class.
|
* REST API Reports orders controller class.
|
||||||
*
|
*
|
||||||
* @internal
|
* @internal
|
||||||
* @extends \Automattic\WooCommerce\Admin\API\Reports\Controller
|
* @extends \Automattic\WooCommerce\Admin\API\Reports\GenericController
|
||||||
*/
|
*/
|
||||||
class Controller extends ReportsController implements ExportableInterface {
|
class Controller extends GenericController implements ExportableInterface {
|
||||||
|
|
||||||
|
use OrderAwareControllerTrait;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Route base.
|
* Route base.
|
||||||
|
@ -27,6 +30,19 @@ class Controller extends ReportsController implements ExportableInterface {
|
||||||
*/
|
*/
|
||||||
protected $rest_base = 'reports/orders';
|
protected $rest_base = 'reports/orders';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get data from Query.
|
||||||
|
*
|
||||||
|
* @override GenericController::get_datastore_data()
|
||||||
|
*
|
||||||
|
* @param array $query_args Query arguments.
|
||||||
|
* @return mixed Results from the data store.
|
||||||
|
*/
|
||||||
|
protected function get_datastore_data( $query_args = array() ) {
|
||||||
|
$query = new Query( $query_args );
|
||||||
|
return $query->get_data();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Maps query arguments from the REST request.
|
* Maps query arguments from the REST request.
|
||||||
*
|
*
|
||||||
|
@ -65,50 +81,17 @@ class Controller extends ReportsController implements ExportableInterface {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all reports.
|
* Prepare a report data item for serialization.
|
||||||
*
|
*
|
||||||
* @param WP_REST_Request $request Request data.
|
* @param array $report Report data item as returned from Data Store.
|
||||||
* @return array|WP_Error
|
* @param \WP_REST_Request $request Request object.
|
||||||
*/
|
* @return \WP_REST_Response
|
||||||
public function get_items( $request ) {
|
|
||||||
$query_args = $this->prepare_reports_query( $request );
|
|
||||||
$orders_query = new Query( $query_args );
|
|
||||||
$report_data = $orders_query->get_data();
|
|
||||||
|
|
||||||
$data = array();
|
|
||||||
|
|
||||||
foreach ( $report_data->data as $orders_data ) {
|
|
||||||
$orders_data['order_number'] = $this->get_order_number( $orders_data['order_id'] );
|
|
||||||
$orders_data['total_formatted'] = $this->get_total_formatted( $orders_data['order_id'] );
|
|
||||||
$item = $this->prepare_item_for_response( $orders_data, $request );
|
|
||||||
$data[] = $this->prepare_response_for_collection( $item );
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->add_pagination_headers(
|
|
||||||
$request,
|
|
||||||
$data,
|
|
||||||
(int) $report_data->total,
|
|
||||||
(int) $report_data->page_no,
|
|
||||||
(int) $report_data->pages
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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 ) {
|
public function prepare_item_for_response( $report, $request ) {
|
||||||
$data = $report;
|
$report['order_number'] = $this->get_order_number( $report['order_id'] );
|
||||||
|
$report['total_formatted'] = $this->get_total_formatted( $report['order_id'] );
|
||||||
$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.
|
// Wrap the data in a response object.
|
||||||
$response = rest_ensure_response( $data );
|
$response = parent::prepare_item_for_response( $report, $request );
|
||||||
$response->add_links( $this->prepare_links( $report ) );
|
$response->add_links( $this->prepare_links( $report ) );
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -248,54 +231,12 @@ class Controller extends ReportsController implements ExportableInterface {
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
public function get_collection_params() {
|
public function get_collection_params() {
|
||||||
$params = array();
|
$params = parent::get_collection_params();
|
||||||
$params['context'] = $this->get_context_param( array( 'default' => 'view' ) );
|
$params['per_page']['minimum'] = 0;
|
||||||
$params['page'] = array(
|
$params['orderby']['enum'] = array(
|
||||||
'description' => __( 'Current page of the collection.', 'woocommerce' ),
|
'date',
|
||||||
'type' => 'integer',
|
'num_items_sold',
|
||||||
'default' => 1,
|
'net_total',
|
||||||
'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' => 0,
|
|
||||||
'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',
|
|
||||||
'num_items_sold',
|
|
||||||
'net_total',
|
|
||||||
),
|
|
||||||
'validate_callback' => 'rest_validate_request_arg',
|
|
||||||
);
|
);
|
||||||
$params['product_includes'] = array(
|
$params['product_includes'] = array(
|
||||||
'description' => __( 'Limit result set to items that have the specified product(s) assigned.', 'woocommerce' ),
|
'description' => __( 'Limit result set to items that have the specified product(s) assigned.', 'woocommerce' ),
|
||||||
|
@ -464,12 +405,6 @@ class Controller extends ReportsController implements ExportableInterface {
|
||||||
'default' => array(),
|
'default' => array(),
|
||||||
'validate_callback' => 'rest_validate_request_arg',
|
'validate_callback' => 'rest_validate_request_arg',
|
||||||
);
|
);
|
||||||
$params['force_cache_refresh'] = array(
|
|
||||||
'description' => __( 'Force retrieval of fresh data instead of from the cache.', 'woocommerce' ),
|
|
||||||
'type' => 'boolean',
|
|
||||||
'sanitize_callback' => 'wp_validate_boolean',
|
|
||||||
'validate_callback' => 'rest_validate_request_arg',
|
|
||||||
);
|
|
||||||
|
|
||||||
return $params;
|
return $params;
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,6 @@ use Automattic\WooCommerce\Admin\API\Reports\DataStore as ReportsDataStore;
|
||||||
use Automattic\WooCommerce\Admin\API\Reports\DataStoreInterface;
|
use Automattic\WooCommerce\Admin\API\Reports\DataStoreInterface;
|
||||||
use Automattic\WooCommerce\Admin\API\Reports\SqlQuery;
|
use Automattic\WooCommerce\Admin\API\Reports\SqlQuery;
|
||||||
use Automattic\WooCommerce\Admin\API\Reports\Cache;
|
use Automattic\WooCommerce\Admin\API\Reports\Cache;
|
||||||
use Automattic\WooCommerce\Admin\API\Reports\TimeInterval;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -25,6 +24,8 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dynamically sets the date column name based on configuration
|
* Dynamically sets the date column name based on configuration
|
||||||
|
*
|
||||||
|
* @override ReportsDataStore::__construct()
|
||||||
*/
|
*/
|
||||||
public function __construct() {
|
public function __construct() {
|
||||||
$this->date_column_name = get_option( 'woocommerce_date_type', 'date_paid' );
|
$this->date_column_name = get_option( 'woocommerce_date_type', 'date_paid' );
|
||||||
|
@ -34,6 +35,8 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||||
/**
|
/**
|
||||||
* Table used to get the data.
|
* Table used to get the data.
|
||||||
*
|
*
|
||||||
|
* @override ReportsDataStore::$table_name
|
||||||
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
protected static $table_name = 'wc_order_stats';
|
protected static $table_name = 'wc_order_stats';
|
||||||
|
@ -41,6 +44,8 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||||
/**
|
/**
|
||||||
* Cache identifier.
|
* Cache identifier.
|
||||||
*
|
*
|
||||||
|
* @override ReportsDataStore::$cache_key
|
||||||
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
protected $cache_key = 'orders';
|
protected $cache_key = 'orders';
|
||||||
|
@ -48,6 +53,8 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||||
/**
|
/**
|
||||||
* Mapping columns to data type to return correct response types.
|
* Mapping columns to data type to return correct response types.
|
||||||
*
|
*
|
||||||
|
* @override ReportsDataStore::$column_types
|
||||||
|
*
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
protected $column_types = array(
|
protected $column_types = array(
|
||||||
|
@ -66,12 +73,16 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||||
/**
|
/**
|
||||||
* Data store context used to pass to filters.
|
* Data store context used to pass to filters.
|
||||||
*
|
*
|
||||||
|
* @override ReportsDataStore::$context
|
||||||
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
protected $context = 'orders';
|
protected $context = 'orders';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Assign report columns once full table name has been assigned.
|
* Assign report columns once full table name has been assigned.
|
||||||
|
*
|
||||||
|
* @override ReportsDataStore::assign_report_columns()
|
||||||
*/
|
*/
|
||||||
protected function assign_report_columns() {
|
protected function assign_report_columns() {
|
||||||
$table_name = self::get_db_table_name();
|
$table_name = self::get_db_table_name();
|
||||||
|
@ -213,117 +224,118 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the report data based on parameters supplied by the user.
|
* Get the default query arguments to be used by get_data().
|
||||||
|
* These defaults are only partially applied when used via REST API, as that has its own defaults.
|
||||||
*
|
*
|
||||||
* @param array $query_args Query parameters.
|
* @override ReportsDataStore::get_default_query_vars()
|
||||||
* @return stdClass|WP_Error Data.
|
*
|
||||||
|
* @return array Query parameters.
|
||||||
*/
|
*/
|
||||||
public function get_data( $query_args ) {
|
public function get_default_query_vars() {
|
||||||
|
$defaults = array_merge(
|
||||||
|
parent::get_default_query_vars(),
|
||||||
|
array(
|
||||||
|
'orderby' => $this->date_column_name,
|
||||||
|
'product_includes' => array(),
|
||||||
|
'product_excludes' => array(),
|
||||||
|
'coupon_includes' => array(),
|
||||||
|
'coupon_excludes' => array(),
|
||||||
|
'tax_rate_includes' => array(),
|
||||||
|
'tax_rate_excludes' => array(),
|
||||||
|
'customer_type' => null,
|
||||||
|
'status_is' => array(),
|
||||||
|
'extended_info' => false,
|
||||||
|
'refunds' => null,
|
||||||
|
'order_includes' => array(),
|
||||||
|
'order_excludes' => array(),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
return $defaults;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the report data based on normalized parameters.
|
||||||
|
* Will be called by `get_data` if there is no data in cache.
|
||||||
|
*
|
||||||
|
* @override ReportsDataStore::get_noncached_data()
|
||||||
|
*
|
||||||
|
* @see get_data
|
||||||
|
* @param array $query_args Query parameters.
|
||||||
|
* @return stdClass|WP_Error Data object `{ totals: *, intervals: array, total: int, pages: int, page_no: int }`, or error.
|
||||||
|
*/
|
||||||
|
public function get_noncached_data( $query_args ) {
|
||||||
global $wpdb;
|
global $wpdb;
|
||||||
|
|
||||||
// These defaults are only partially applied when used via REST API, as that has its own defaults.
|
$this->initialize_queries();
|
||||||
$defaults = array(
|
|
||||||
'per_page' => get_option( 'posts_per_page' ),
|
$data = (object) array(
|
||||||
'page' => 1,
|
'data' => array(),
|
||||||
'order' => 'DESC',
|
'total' => 0,
|
||||||
'orderby' => $this->date_column_name,
|
'pages' => 0,
|
||||||
'before' => TimeInterval::default_before(),
|
'page_no' => 0,
|
||||||
'after' => TimeInterval::default_after(),
|
|
||||||
'fields' => '*',
|
|
||||||
'product_includes' => array(),
|
|
||||||
'product_excludes' => array(),
|
|
||||||
'coupon_includes' => array(),
|
|
||||||
'coupon_excludes' => array(),
|
|
||||||
'tax_rate_includes' => array(),
|
|
||||||
'tax_rate_excludes' => array(),
|
|
||||||
'customer_type' => null,
|
|
||||||
'status_is' => array(),
|
|
||||||
'extended_info' => false,
|
|
||||||
'refunds' => null,
|
|
||||||
'order_includes' => array(),
|
|
||||||
'order_excludes' => array(),
|
|
||||||
);
|
);
|
||||||
$query_args = wp_parse_args( $query_args, $defaults );
|
|
||||||
$this->normalize_timezones( $query_args, $defaults );
|
|
||||||
|
|
||||||
/*
|
$selections = $this->selected_columns( $query_args );
|
||||||
* We need to get the cache key here because
|
$params = $this->get_limit_params( $query_args );
|
||||||
* parent::update_intervals_sql_params() modifies $query_args.
|
$this->add_sql_query_params( $query_args );
|
||||||
*/
|
/* phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared */
|
||||||
$cache_key = $this->get_cache_key( $query_args );
|
$db_records_count = (int) $wpdb->get_var(
|
||||||
$data = $this->get_cached_data( $cache_key );
|
"SELECT COUNT( DISTINCT tt.order_id ) FROM (
|
||||||
|
{$this->subquery->get_query_statement()}
|
||||||
if ( false === $data ) {
|
) AS tt"
|
||||||
$this->initialize_queries();
|
);
|
||||||
|
/* phpcs:enable */
|
||||||
|
|
||||||
|
if ( 0 === $params['per_page'] ) {
|
||||||
|
$total_pages = 0;
|
||||||
|
} else {
|
||||||
|
$total_pages = (int) ceil( $db_records_count / $params['per_page'] );
|
||||||
|
}
|
||||||
|
if ( $query_args['page'] < 1 || $query_args['page'] > $total_pages ) {
|
||||||
$data = (object) array(
|
$data = (object) array(
|
||||||
'data' => array(),
|
'data' => array(),
|
||||||
'total' => 0,
|
'total' => $db_records_count,
|
||||||
'pages' => 0,
|
'pages' => 0,
|
||||||
'page_no' => 0,
|
'page_no' => 0,
|
||||||
);
|
);
|
||||||
|
return $data;
|
||||||
$selections = $this->selected_columns( $query_args );
|
|
||||||
$params = $this->get_limit_params( $query_args );
|
|
||||||
$this->add_sql_query_params( $query_args );
|
|
||||||
/* phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared */
|
|
||||||
$db_records_count = (int) $wpdb->get_var(
|
|
||||||
"SELECT COUNT( DISTINCT tt.order_id ) FROM (
|
|
||||||
{$this->subquery->get_query_statement()}
|
|
||||||
) AS tt"
|
|
||||||
);
|
|
||||||
/* phpcs:enable */
|
|
||||||
|
|
||||||
if ( 0 === $params['per_page'] ) {
|
|
||||||
$total_pages = 0;
|
|
||||||
} else {
|
|
||||||
$total_pages = (int) ceil( $db_records_count / $params['per_page'] );
|
|
||||||
}
|
|
||||||
if ( $query_args['page'] < 1 || $query_args['page'] > $total_pages ) {
|
|
||||||
$data = (object) array(
|
|
||||||
'data' => array(),
|
|
||||||
'total' => $db_records_count,
|
|
||||||
'pages' => 0,
|
|
||||||
'page_no' => 0,
|
|
||||||
);
|
|
||||||
return $data;
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->subquery->clear_sql_clause( 'select' );
|
|
||||||
$this->subquery->add_sql_clause( 'select', $selections );
|
|
||||||
$this->subquery->add_sql_clause( 'order_by', $this->get_sql_clause( 'order_by' ) );
|
|
||||||
$this->subquery->add_sql_clause( 'limit', $this->get_sql_clause( 'limit' ) );
|
|
||||||
/* phpcs:disable WordPress.DB.PreparedSQL.NotPrepared */
|
|
||||||
$orders_data = $wpdb->get_results(
|
|
||||||
$this->subquery->get_query_statement(),
|
|
||||||
ARRAY_A
|
|
||||||
);
|
|
||||||
/* phpcs:enable */
|
|
||||||
|
|
||||||
if ( null === $orders_data ) {
|
|
||||||
return $data;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( $query_args['extended_info'] ) {
|
|
||||||
$this->include_extended_info( $orders_data, $query_args );
|
|
||||||
}
|
|
||||||
|
|
||||||
$orders_data = array_map( array( $this, 'cast_numbers' ), $orders_data );
|
|
||||||
$data = (object) array(
|
|
||||||
'data' => $orders_data,
|
|
||||||
'total' => $db_records_count,
|
|
||||||
'pages' => $total_pages,
|
|
||||||
'page_no' => (int) $query_args['page'],
|
|
||||||
);
|
|
||||||
|
|
||||||
$this->set_cached_data( $cache_key, $data );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$this->subquery->clear_sql_clause( 'select' );
|
||||||
|
$this->subquery->add_sql_clause( 'select', $selections );
|
||||||
|
$this->subquery->add_sql_clause( 'order_by', $this->get_sql_clause( 'order_by' ) );
|
||||||
|
$this->subquery->add_sql_clause( 'limit', $this->get_sql_clause( 'limit' ) );
|
||||||
|
/* phpcs:disable WordPress.DB.PreparedSQL.NotPrepared */
|
||||||
|
$orders_data = $wpdb->get_results(
|
||||||
|
$this->subquery->get_query_statement(),
|
||||||
|
ARRAY_A
|
||||||
|
);
|
||||||
|
/* phpcs:enable */
|
||||||
|
|
||||||
|
if ( null === $orders_data ) {
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( $query_args['extended_info'] ) {
|
||||||
|
$this->include_extended_info( $orders_data, $query_args );
|
||||||
|
}
|
||||||
|
|
||||||
|
$orders_data = array_map( array( $this, 'cast_numbers' ), $orders_data );
|
||||||
|
$data = (object) array(
|
||||||
|
'data' => $orders_data,
|
||||||
|
'total' => $db_records_count,
|
||||||
|
'pages' => $total_pages,
|
||||||
|
'page_no' => (int) $query_args['page'],
|
||||||
|
);
|
||||||
return $data;
|
return $data;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Normalizes order_by clause to match to SQL query.
|
* Normalizes order_by clause to match to SQL query.
|
||||||
*
|
*
|
||||||
|
* @override ReportsDataStore::normalize_order_by()
|
||||||
|
*
|
||||||
* @param string $order_by Order by option requeste by user.
|
* @param string $order_by Order by option requeste by user.
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -19,24 +19,32 @@
|
||||||
|
|
||||||
namespace Automattic\WooCommerce\Admin\API\Reports\Orders;
|
namespace Automattic\WooCommerce\Admin\API\Reports\Orders;
|
||||||
|
|
||||||
|
use Automattic\WooCommerce\Admin\API\Reports\GenericQuery;
|
||||||
|
|
||||||
defined( 'ABSPATH' ) || exit;
|
defined( 'ABSPATH' ) || exit;
|
||||||
|
|
||||||
use Automattic\WooCommerce\Admin\API\Reports\Query as ReportsQuery;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* API\Reports\Orders\Query
|
* API\Reports\Orders\Query
|
||||||
*/
|
*/
|
||||||
class Query extends ReportsQuery {
|
class Query extends GenericQuery {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get order data based on the current query vars.
|
* Specific query name.
|
||||||
|
* Will be used to load the `report-{name}` data store,
|
||||||
|
* and to call `woocommerce_analytics_{snake_case(name)}_*` filters.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $name = 'orders';
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the default allowed query vars.
|
||||||
*
|
*
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
public function get_data() {
|
protected function get_default_query_vars() {
|
||||||
$args = apply_filters( 'woocommerce_analytics_orders_query_args', $this->get_query_vars() );
|
return \WC_Object_Query::get_default_query_vars();
|
||||||
$data_store = \WC_Data_Store::load( 'report-orders' );
|
|
||||||
$results = $data_store->get_data( $args );
|
|
||||||
return apply_filters( 'woocommerce_analytics_orders_select_query', $results, $args );
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,15 +9,19 @@ namespace Automattic\WooCommerce\Admin\API\Reports\Orders\Stats;
|
||||||
|
|
||||||
defined( 'ABSPATH' ) || exit;
|
defined( 'ABSPATH' ) || exit;
|
||||||
|
|
||||||
use Automattic\WooCommerce\Admin\API\Reports\ParameterException;
|
use Automattic\WooCommerce\Admin\API\Reports\GenericStatsController;
|
||||||
|
use Automattic\WooCommerce\Admin\API\Reports\OrderAwareControllerTrait;
|
||||||
|
use Automattic\WooCommerce\Admin\API\Reports\Orders\Stats\Query;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* REST API Reports orders stats controller class.
|
* REST API Reports orders stats controller class.
|
||||||
*
|
*
|
||||||
* @internal
|
* @internal
|
||||||
* @extends \Automattic\WooCommerce\Admin\API\Reports\Controller
|
* @extends \Automattic\WooCommerce\Admin\API\Reports\GenericStatsController
|
||||||
*/
|
*/
|
||||||
class Controller extends \Automattic\WooCommerce\Admin\API\Reports\Controller {
|
class Controller extends GenericStatsController {
|
||||||
|
|
||||||
|
use OrderAwareControllerTrait;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Route base.
|
* Route base.
|
||||||
|
@ -26,6 +30,19 @@ class Controller extends \Automattic\WooCommerce\Admin\API\Reports\Controller {
|
||||||
*/
|
*/
|
||||||
protected $rest_base = 'reports/orders/stats';
|
protected $rest_base = 'reports/orders/stats';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get data from Query.
|
||||||
|
*
|
||||||
|
* @override GenericController::get_datastore_data()
|
||||||
|
*
|
||||||
|
* @param array $query_args Query arguments.
|
||||||
|
* @return mixed Results from the data store.
|
||||||
|
*/
|
||||||
|
protected function get_datastore_data( $query_args = array() ) {
|
||||||
|
$query = new Query( $query_args );
|
||||||
|
return $query->get_data();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Maps query arguments from the REST request.
|
* Maps query arguments from the REST request.
|
||||||
*
|
*
|
||||||
|
@ -70,55 +87,15 @@ class Controller extends \Automattic\WooCommerce\Admin\API\Reports\Controller {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all reports.
|
* Prepare a report data item for serialization.
|
||||||
*
|
*
|
||||||
* @param WP_REST_Request $request Request data.
|
* @param Array $report Report data item as returned from Data Store.
|
||||||
* @return array|WP_Error
|
|
||||||
*/
|
|
||||||
public function get_items( $request ) {
|
|
||||||
$query_args = $this->prepare_reports_query( $request );
|
|
||||||
$orders_query = new Query( $query_args );
|
|
||||||
try {
|
|
||||||
$report_data = $orders_query->get_data();
|
|
||||||
} catch ( ParameterException $e ) {
|
|
||||||
return new \WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
$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 );
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->add_pagination_headers(
|
|
||||||
$request,
|
|
||||||
$out_data,
|
|
||||||
(int) $report_data->total,
|
|
||||||
(int) $report_data->page_no,
|
|
||||||
(int) $report_data->pages
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Prepare a report object for serialization.
|
|
||||||
*
|
|
||||||
* @param Array $report Report data.
|
|
||||||
* @param WP_REST_Request $request Request object.
|
* @param WP_REST_Request $request Request object.
|
||||||
* @return WP_REST_Response
|
* @return WP_REST_Response
|
||||||
*/
|
*/
|
||||||
public function prepare_item_for_response( $report, $request ) {
|
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.
|
// Wrap the data in a response object.
|
||||||
$response = rest_ensure_response( $data );
|
$response = parent::prepare_item_for_response( $report, $request );
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Filter a report returned from the API.
|
* Filter a report returned from the API.
|
||||||
|
@ -132,13 +109,15 @@ class Controller extends \Automattic\WooCommerce\Admin\API\Reports\Controller {
|
||||||
return apply_filters( 'woocommerce_rest_prepare_report_orders_stats', $response, $report, $request );
|
return apply_filters( 'woocommerce_rest_prepare_report_orders_stats', $response, $report, $request );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the Report's schema, conforming to JSON Schema.
|
* Get the Report's item properties schema.
|
||||||
|
* Will be used by `get_item_schema` as `totals` and `subtotals`.
|
||||||
*
|
*
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
public function get_item_schema() {
|
protected function get_item_properties_schema() {
|
||||||
$data_values = array(
|
return array(
|
||||||
'net_revenue' => array(
|
'net_revenue' => array(
|
||||||
'description' => __( 'Net sales.', 'woocommerce' ),
|
'description' => __( 'Net sales.', 'woocommerce' ),
|
||||||
'type' => 'number',
|
'type' => 'number',
|
||||||
|
@ -199,104 +178,19 @@ class Controller extends \Automattic\WooCommerce\Admin\API\Reports\Controller {
|
||||||
'readonly' => true,
|
'readonly' => true,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
$segments = array(
|
/**
|
||||||
'segments' => array(
|
* Get the Report's schema, conforming to JSON Schema.
|
||||||
'description' => __( 'Reports data grouped by segment condition.', 'woocommerce' ),
|
*
|
||||||
'type' => 'array',
|
* @return array
|
||||||
'context' => array( 'view', 'edit' ),
|
*/
|
||||||
'readonly' => true,
|
public function get_item_schema() {
|
||||||
'items' => array(
|
$schema = parent::get_item_schema();
|
||||||
'type' => 'object',
|
$schema['title'] = 'report_orders_stats';
|
||||||
'properties' => array(
|
|
||||||
'segment_id' => array(
|
|
||||||
'description' => __( 'Segment identificator.', 'woocommerce' ),
|
|
||||||
'type' => 'integer',
|
|
||||||
'context' => array( 'view', 'edit' ),
|
|
||||||
'readonly' => true,
|
|
||||||
),
|
|
||||||
'subtotals' => array(
|
|
||||||
'description' => __( 'Interval subtotals.', 'woocommerce' ),
|
|
||||||
'type' => 'object',
|
|
||||||
'context' => array( 'view', 'edit' ),
|
|
||||||
'readonly' => true,
|
|
||||||
'properties' => $data_values,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
$totals = array_merge( $data_values, $segments );
|
|
||||||
|
|
||||||
// Products is not shown in intervals.
|
// Products is not shown in intervals.
|
||||||
unset( $data_values['products'] );
|
unset( $schema['properties']['intervals']['items']['properties']['subtotals']['properties']['products'] );
|
||||||
|
|
||||||
$intervals = array_merge( $data_values, $segments );
|
|
||||||
|
|
||||||
$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' => $intervals,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
return $this->add_additional_fields_schema( $schema );
|
return $this->add_additional_fields_schema( $schema );
|
||||||
}
|
}
|
||||||
|
@ -307,69 +201,12 @@ class Controller extends \Automattic\WooCommerce\Admin\API\Reports\Controller {
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
public function get_collection_params() {
|
public function get_collection_params() {
|
||||||
$params = array();
|
$params = parent::get_collection_params();
|
||||||
$params['context'] = $this->get_context_param( array( 'default' => 'view' ) );
|
$params['orderby']['enum'] = array(
|
||||||
$params['page'] = array(
|
'date',
|
||||||
'description' => __( 'Current page of the collection.', 'woocommerce' ),
|
'net_revenue',
|
||||||
'type' => 'integer',
|
'orders_count',
|
||||||
'default' => 1,
|
'avg_order_value',
|
||||||
'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['match'] = array(
|
$params['match'] = array(
|
||||||
'description' => __( 'Indicates whether all the conditions should be true for the resulting set, or if any one of them is sufficient. Match affects the following parameters: status_is, status_is_not, product_includes, product_excludes, coupon_includes, coupon_excludes, customer, categories', 'woocommerce' ),
|
'description' => __( 'Indicates whether all the conditions should be true for the resulting set, or if any one of them is sufficient. Match affects the following parameters: status_is, status_is_not, product_includes, product_excludes, coupon_includes, coupon_excludes, customer, categories', 'woocommerce' ),
|
||||||
|
@ -412,7 +249,7 @@ class Controller extends \Automattic\WooCommerce\Admin\API\Reports\Controller {
|
||||||
'sanitize_callback' => 'wp_parse_id_list',
|
'sanitize_callback' => 'wp_parse_id_list',
|
||||||
|
|
||||||
);
|
);
|
||||||
$params['product_excludes'] = array(
|
$params['product_excludes'] = array(
|
||||||
'description' => __( 'Limit result set to items that don\'t have the specified product(s) assigned.', 'woocommerce' ),
|
'description' => __( 'Limit result set to items that don\'t have the specified product(s) assigned.', 'woocommerce' ),
|
||||||
'type' => 'array',
|
'type' => 'array',
|
||||||
'items' => array(
|
'items' => array(
|
||||||
|
@ -421,7 +258,8 @@ class Controller extends \Automattic\WooCommerce\Admin\API\Reports\Controller {
|
||||||
'default' => array(),
|
'default' => array(),
|
||||||
'sanitize_callback' => 'wp_parse_id_list',
|
'sanitize_callback' => 'wp_parse_id_list',
|
||||||
);
|
);
|
||||||
$params['variation_includes'] = array(
|
// Split assignments for PHPCS complaining on aligned.
|
||||||
|
$params['variation_includes'] = array(
|
||||||
'description' => __( 'Limit result set to items that have the specified variation(s) assigned.', 'woocommerce' ),
|
'description' => __( 'Limit result set to items that have the specified variation(s) assigned.', 'woocommerce' ),
|
||||||
'type' => 'array',
|
'type' => 'array',
|
||||||
'items' => array(
|
'items' => array(
|
||||||
|
@ -431,7 +269,7 @@ class Controller extends \Automattic\WooCommerce\Admin\API\Reports\Controller {
|
||||||
'sanitize_callback' => 'wp_parse_id_list',
|
'sanitize_callback' => 'wp_parse_id_list',
|
||||||
'validate_callback' => 'rest_validate_request_arg',
|
'validate_callback' => 'rest_validate_request_arg',
|
||||||
);
|
);
|
||||||
$params['variation_excludes'] = array(
|
$params['variation_excludes'] = array(
|
||||||
'description' => __( 'Limit result set to items that don\'t have the specified variation(s) assigned.', 'woocommerce' ),
|
'description' => __( 'Limit result set to items that don\'t have the specified variation(s) assigned.', 'woocommerce' ),
|
||||||
'type' => 'array',
|
'type' => 'array',
|
||||||
'items' => array(
|
'items' => array(
|
||||||
|
@ -441,7 +279,7 @@ class Controller extends \Automattic\WooCommerce\Admin\API\Reports\Controller {
|
||||||
'validate_callback' => 'rest_validate_request_arg',
|
'validate_callback' => 'rest_validate_request_arg',
|
||||||
'sanitize_callback' => 'wp_parse_id_list',
|
'sanitize_callback' => 'wp_parse_id_list',
|
||||||
);
|
);
|
||||||
$params['coupon_includes'] = array(
|
$params['coupon_includes'] = array(
|
||||||
'description' => __( 'Limit result set to items that have the specified coupon(s) assigned.', 'woocommerce' ),
|
'description' => __( 'Limit result set to items that have the specified coupon(s) assigned.', 'woocommerce' ),
|
||||||
'type' => 'array',
|
'type' => 'array',
|
||||||
'items' => array(
|
'items' => array(
|
||||||
|
@ -450,7 +288,7 @@ class Controller extends \Automattic\WooCommerce\Admin\API\Reports\Controller {
|
||||||
'default' => array(),
|
'default' => array(),
|
||||||
'sanitize_callback' => 'wp_parse_id_list',
|
'sanitize_callback' => 'wp_parse_id_list',
|
||||||
);
|
);
|
||||||
$params['coupon_excludes'] = array(
|
$params['coupon_excludes'] = array(
|
||||||
'description' => __( 'Limit result set to items that don\'t have the specified coupon(s) assigned.', 'woocommerce' ),
|
'description' => __( 'Limit result set to items that don\'t have the specified coupon(s) assigned.', 'woocommerce' ),
|
||||||
'type' => 'array',
|
'type' => 'array',
|
||||||
'items' => array(
|
'items' => array(
|
||||||
|
@ -459,7 +297,7 @@ class Controller extends \Automattic\WooCommerce\Admin\API\Reports\Controller {
|
||||||
'default' => array(),
|
'default' => array(),
|
||||||
'sanitize_callback' => 'wp_parse_id_list',
|
'sanitize_callback' => 'wp_parse_id_list',
|
||||||
);
|
);
|
||||||
$params['tax_rate_includes'] = array(
|
$params['tax_rate_includes'] = array(
|
||||||
'description' => __( 'Limit result set to items that have the specified tax rate(s) assigned.', 'woocommerce' ),
|
'description' => __( 'Limit result set to items that have the specified tax rate(s) assigned.', 'woocommerce' ),
|
||||||
'type' => 'array',
|
'type' => 'array',
|
||||||
'items' => array(
|
'items' => array(
|
||||||
|
@ -469,7 +307,7 @@ class Controller extends \Automattic\WooCommerce\Admin\API\Reports\Controller {
|
||||||
'sanitize_callback' => 'wp_parse_id_list',
|
'sanitize_callback' => 'wp_parse_id_list',
|
||||||
'validate_callback' => 'rest_validate_request_arg',
|
'validate_callback' => 'rest_validate_request_arg',
|
||||||
);
|
);
|
||||||
$params['tax_rate_excludes'] = array(
|
$params['tax_rate_excludes'] = array(
|
||||||
'description' => __( 'Limit result set to items that don\'t have the specified tax rate(s) assigned.', 'woocommerce' ),
|
'description' => __( 'Limit result set to items that don\'t have the specified tax rate(s) assigned.', 'woocommerce' ),
|
||||||
'type' => 'array',
|
'type' => 'array',
|
||||||
'items' => array(
|
'items' => array(
|
||||||
|
@ -479,7 +317,7 @@ class Controller extends \Automattic\WooCommerce\Admin\API\Reports\Controller {
|
||||||
'validate_callback' => 'rest_validate_request_arg',
|
'validate_callback' => 'rest_validate_request_arg',
|
||||||
'sanitize_callback' => 'wp_parse_id_list',
|
'sanitize_callback' => 'wp_parse_id_list',
|
||||||
);
|
);
|
||||||
$params['customer'] = array(
|
$params['customer'] = array(
|
||||||
'description' => __( 'Alias for customer_type (deprecated).', 'woocommerce' ),
|
'description' => __( 'Alias for customer_type (deprecated).', 'woocommerce' ),
|
||||||
'type' => 'string',
|
'type' => 'string',
|
||||||
'enum' => array(
|
'enum' => array(
|
||||||
|
@ -488,7 +326,7 @@ class Controller extends \Automattic\WooCommerce\Admin\API\Reports\Controller {
|
||||||
),
|
),
|
||||||
'validate_callback' => 'rest_validate_request_arg',
|
'validate_callback' => 'rest_validate_request_arg',
|
||||||
);
|
);
|
||||||
$params['customer_type'] = array(
|
$params['customer_type'] = array(
|
||||||
'description' => __( 'Limit result set to orders that have the specified customer_type', 'woocommerce' ),
|
'description' => __( 'Limit result set to orders that have the specified customer_type', 'woocommerce' ),
|
||||||
'type' => 'string',
|
'type' => 'string',
|
||||||
'enum' => array(
|
'enum' => array(
|
||||||
|
@ -497,7 +335,7 @@ class Controller extends \Automattic\WooCommerce\Admin\API\Reports\Controller {
|
||||||
),
|
),
|
||||||
'validate_callback' => 'rest_validate_request_arg',
|
'validate_callback' => 'rest_validate_request_arg',
|
||||||
);
|
);
|
||||||
$params['refunds'] = array(
|
$params['refunds'] = array(
|
||||||
'description' => __( 'Limit result set to specific types of refunds.', 'woocommerce' ),
|
'description' => __( 'Limit result set to specific types of refunds.', 'woocommerce' ),
|
||||||
'type' => 'string',
|
'type' => 'string',
|
||||||
'default' => '',
|
'default' => '',
|
||||||
|
@ -510,7 +348,7 @@ class Controller extends \Automattic\WooCommerce\Admin\API\Reports\Controller {
|
||||||
),
|
),
|
||||||
'validate_callback' => 'rest_validate_request_arg',
|
'validate_callback' => 'rest_validate_request_arg',
|
||||||
);
|
);
|
||||||
$params['attribute_is'] = array(
|
$params['attribute_is'] = array(
|
||||||
'description' => __( 'Limit result set to orders that include products with the specified attributes.', 'woocommerce' ),
|
'description' => __( 'Limit result set to orders that include products with the specified attributes.', 'woocommerce' ),
|
||||||
'type' => 'array',
|
'type' => 'array',
|
||||||
'items' => array(
|
'items' => array(
|
||||||
|
@ -519,7 +357,7 @@ class Controller extends \Automattic\WooCommerce\Admin\API\Reports\Controller {
|
||||||
'default' => array(),
|
'default' => array(),
|
||||||
'validate_callback' => 'rest_validate_request_arg',
|
'validate_callback' => 'rest_validate_request_arg',
|
||||||
);
|
);
|
||||||
$params['attribute_is_not'] = array(
|
$params['attribute_is_not'] = array(
|
||||||
'description' => __( 'Limit result set to orders that don\'t include products with the specified attributes.', 'woocommerce' ),
|
'description' => __( 'Limit result set to orders that don\'t include products with the specified attributes.', 'woocommerce' ),
|
||||||
'type' => 'array',
|
'type' => 'array',
|
||||||
'items' => array(
|
'items' => array(
|
||||||
|
@ -528,7 +366,7 @@ class Controller extends \Automattic\WooCommerce\Admin\API\Reports\Controller {
|
||||||
'default' => array(),
|
'default' => array(),
|
||||||
'validate_callback' => 'rest_validate_request_arg',
|
'validate_callback' => 'rest_validate_request_arg',
|
||||||
);
|
);
|
||||||
$params['segmentby'] = array(
|
$params['segmentby'] = array(
|
||||||
'description' => __( 'Segment the response by additional constraint.', 'woocommerce' ),
|
'description' => __( 'Segment the response by additional constraint.', 'woocommerce' ),
|
||||||
'type' => 'string',
|
'type' => 'string',
|
||||||
'enum' => array(
|
'enum' => array(
|
||||||
|
@ -540,21 +378,8 @@ class Controller extends \Automattic\WooCommerce\Admin\API\Reports\Controller {
|
||||||
),
|
),
|
||||||
'validate_callback' => 'rest_validate_request_arg',
|
'validate_callback' => 'rest_validate_request_arg',
|
||||||
);
|
);
|
||||||
$params['fields'] = array(
|
unset( $params['intervals'] );
|
||||||
'description' => __( 'Limit stats fields to the specified items.', 'woocommerce' ),
|
unset( $params['fields'] );
|
||||||
'type' => 'array',
|
|
||||||
'sanitize_callback' => 'wp_parse_slug_list',
|
|
||||||
'validate_callback' => 'rest_validate_request_arg',
|
|
||||||
'items' => array(
|
|
||||||
'type' => 'string',
|
|
||||||
),
|
|
||||||
);
|
|
||||||
$params['force_cache_refresh'] = array(
|
|
||||||
'description' => __( 'Force retrieval of fresh data instead of from the cache.', 'woocommerce' ),
|
|
||||||
'type' => 'boolean',
|
|
||||||
'sanitize_callback' => 'wp_validate_boolean',
|
|
||||||
'validate_callback' => 'rest_validate_request_arg',
|
|
||||||
);
|
|
||||||
|
|
||||||
return $params;
|
return $params;
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,15 +14,19 @@ use Automattic\WooCommerce\Admin\API\Reports\SqlQuery;
|
||||||
use Automattic\WooCommerce\Admin\API\Reports\Cache as ReportsCache;
|
use Automattic\WooCommerce\Admin\API\Reports\Cache as ReportsCache;
|
||||||
use Automattic\WooCommerce\Admin\API\Reports\Customers\DataStore as CustomersDataStore;
|
use Automattic\WooCommerce\Admin\API\Reports\Customers\DataStore as CustomersDataStore;
|
||||||
use Automattic\WooCommerce\Utilities\OrderUtil;
|
use Automattic\WooCommerce\Utilities\OrderUtil;
|
||||||
|
use Automattic\WooCommerce\Admin\API\Reports\StatsDataStoreTrait;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* API\Reports\Orders\Stats\DataStore.
|
* API\Reports\Orders\Stats\DataStore.
|
||||||
*/
|
*/
|
||||||
class DataStore extends ReportsDataStore implements DataStoreInterface {
|
class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||||
|
use StatsDataStoreTrait;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Table used to get the data.
|
* Table used to get the data.
|
||||||
*
|
*
|
||||||
|
* @override ReportsDataStore::$table_name
|
||||||
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
protected static $table_name = 'wc_order_stats';
|
protected static $table_name = 'wc_order_stats';
|
||||||
|
@ -35,6 +39,8 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||||
/**
|
/**
|
||||||
* Cache identifier.
|
* Cache identifier.
|
||||||
*
|
*
|
||||||
|
* @override ReportsDataStore::$cache_key
|
||||||
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
protected $cache_key = 'orders_stats';
|
protected $cache_key = 'orders_stats';
|
||||||
|
@ -42,6 +48,8 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||||
/**
|
/**
|
||||||
* Type for each column to cast values correctly later.
|
* Type for each column to cast values correctly later.
|
||||||
*
|
*
|
||||||
|
* @override ReportsDataStore::$column_types
|
||||||
|
*
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
protected $column_types = array(
|
protected $column_types = array(
|
||||||
|
@ -65,12 +73,16 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||||
/**
|
/**
|
||||||
* Data store context used to pass to filters.
|
* Data store context used to pass to filters.
|
||||||
*
|
*
|
||||||
|
* @override ReportsDataStore::$context
|
||||||
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
protected $context = 'orders_stats';
|
protected $context = 'orders_stats';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dynamically sets the date column name based on configuration
|
* Dynamically sets the date column name based on configuration
|
||||||
|
*
|
||||||
|
* @override ReportsDataStore::__construct()
|
||||||
*/
|
*/
|
||||||
public function __construct() {
|
public function __construct() {
|
||||||
$this->date_column_name = get_option( 'woocommerce_date_type', 'date_paid' );
|
$this->date_column_name = get_option( 'woocommerce_date_type', 'date_paid' );
|
||||||
|
@ -79,6 +91,8 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Assign report columns once full table name has been assigned.
|
* Assign report columns once full table name has been assigned.
|
||||||
|
*
|
||||||
|
* @override ReportsDataStore::assign_report_columns()
|
||||||
*/
|
*/
|
||||||
protected function assign_report_columns() {
|
protected function assign_report_columns() {
|
||||||
$table_name = self::get_db_table_name();
|
$table_name = self::get_db_table_name();
|
||||||
|
@ -260,176 +274,161 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the report data based on parameters supplied by the user.
|
* Get the default query arguments to be used by get_data().
|
||||||
|
* These defaults are only partially applied when used via REST API, as that has its own defaults.
|
||||||
*
|
*
|
||||||
* @param array $query_args Query parameters.
|
* @override ReportsDataStore::get_default_query_vars()
|
||||||
* @return stdClass|WP_Error Data.
|
*
|
||||||
|
* @return array Query parameters.
|
||||||
*/
|
*/
|
||||||
public function get_data( $query_args ) {
|
public function get_default_query_vars() {
|
||||||
|
$defaults = array_merge(
|
||||||
|
parent::get_default_query_vars(),
|
||||||
|
array(
|
||||||
|
'interval' => 'week',
|
||||||
|
'segmentby' => '',
|
||||||
|
|
||||||
|
'match' => 'all',
|
||||||
|
'status_is' => array(),
|
||||||
|
'status_is_not' => array(),
|
||||||
|
'product_includes' => array(),
|
||||||
|
'product_excludes' => array(),
|
||||||
|
'coupon_includes' => array(),
|
||||||
|
'coupon_excludes' => array(),
|
||||||
|
'tax_rate_includes' => array(),
|
||||||
|
'tax_rate_excludes' => array(),
|
||||||
|
'customer_type' => '',
|
||||||
|
'category_includes' => array(),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
return $defaults;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the report data based on normalized parameters.
|
||||||
|
* Will be called by `get_data` if there is no data in cache.
|
||||||
|
*
|
||||||
|
* @override ReportsDataStore::get_noncached_stats_data()
|
||||||
|
*
|
||||||
|
* @see get_data
|
||||||
|
* @see get_noncached_stats_data
|
||||||
|
* @param array $query_args Query parameters.
|
||||||
|
* @param array $params Query limit parameters.
|
||||||
|
* @param stdClass $data Reference to the data object to fill.
|
||||||
|
* @param int $expected_interval_count Number of expected intervals.
|
||||||
|
* @return stdClass|WP_Error Data object `{ totals: *, intervals: array, total: int, pages: int, page_no: int }`, or error.
|
||||||
|
*/
|
||||||
|
public function get_noncached_stats_data( $query_args, $params, &$data, $expected_interval_count ) {
|
||||||
global $wpdb;
|
global $wpdb;
|
||||||
|
|
||||||
$table_name = self::get_db_table_name();
|
$table_name = self::get_db_table_name();
|
||||||
|
|
||||||
// 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' => TimeInterval::default_before(),
|
|
||||||
'after' => TimeInterval::default_after(),
|
|
||||||
'interval' => 'week',
|
|
||||||
'fields' => '*',
|
|
||||||
'segmentby' => '',
|
|
||||||
|
|
||||||
'match' => 'all',
|
|
||||||
'status_is' => array(),
|
|
||||||
'status_is_not' => array(),
|
|
||||||
'product_includes' => array(),
|
|
||||||
'product_excludes' => array(),
|
|
||||||
'coupon_includes' => array(),
|
|
||||||
'coupon_excludes' => array(),
|
|
||||||
'tax_rate_includes' => array(),
|
|
||||||
'tax_rate_excludes' => array(),
|
|
||||||
'customer_type' => '',
|
|
||||||
'category_includes' => array(),
|
|
||||||
);
|
|
||||||
$query_args = wp_parse_args( $query_args, $defaults );
|
|
||||||
$this->normalize_timezones( $query_args, $defaults );
|
|
||||||
|
|
||||||
/*
|
|
||||||
* We need to get the cache key here because
|
|
||||||
* parent::update_intervals_sql_params() modifies $query_args.
|
|
||||||
*/
|
|
||||||
$cache_key = $this->get_cache_key( $query_args );
|
|
||||||
$data = $this->get_cached_data( $cache_key );
|
|
||||||
|
|
||||||
if ( isset( $query_args['date_type'] ) ) {
|
if ( isset( $query_args['date_type'] ) ) {
|
||||||
$this->date_column_name = $query_args['date_type'];
|
$this->date_column_name = $query_args['date_type'];
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( false === $data ) {
|
$this->initialize_queries();
|
||||||
$this->initialize_queries();
|
|
||||||
|
|
||||||
$data = (object) array(
|
$selections = $this->selected_columns( $query_args );
|
||||||
'totals' => (object) array(),
|
$this->add_time_period_sql_params( $query_args, $table_name );
|
||||||
'intervals' => (object) array(),
|
$this->add_intervals_sql_params( $query_args, $table_name );
|
||||||
'total' => 0,
|
$this->add_order_by_sql_params( $query_args );
|
||||||
'pages' => 0,
|
$where_time = $this->get_sql_clause( 'where_time' );
|
||||||
'page_no' => 0,
|
$params = $this->get_limit_sql_params( $query_args );
|
||||||
);
|
$coupon_join = "LEFT JOIN (
|
||||||
|
SELECT
|
||||||
|
order_id,
|
||||||
|
SUM(discount_amount) AS discount_amount,
|
||||||
|
COUNT(DISTINCT coupon_id) AS coupons_count
|
||||||
|
FROM
|
||||||
|
{$wpdb->prefix}wc_order_coupon_lookup
|
||||||
|
GROUP BY
|
||||||
|
order_id
|
||||||
|
) order_coupon_lookup
|
||||||
|
ON order_coupon_lookup.order_id = {$wpdb->prefix}wc_order_stats.order_id";
|
||||||
|
|
||||||
$selections = $this->selected_columns( $query_args );
|
// Additional filtering for Orders report.
|
||||||
$this->add_time_period_sql_params( $query_args, $table_name );
|
$this->orders_stats_sql_filter( $query_args );
|
||||||
$this->add_intervals_sql_params( $query_args, $table_name );
|
$this->total_query->add_sql_clause( 'select', $selections );
|
||||||
$this->add_order_by_sql_params( $query_args );
|
$this->total_query->add_sql_clause( 'left_join', $coupon_join );
|
||||||
$where_time = $this->get_sql_clause( 'where_time' );
|
$this->total_query->add_sql_clause( 'where_time', $where_time );
|
||||||
$params = $this->get_limit_sql_params( $query_args );
|
$totals = $wpdb->get_results(
|
||||||
$coupon_join = "LEFT JOIN (
|
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- cache ok, DB call ok, unprepared SQL ok.
|
||||||
SELECT
|
$this->total_query->get_query_statement(),
|
||||||
order_id,
|
ARRAY_A
|
||||||
SUM(discount_amount) AS discount_amount,
|
);
|
||||||
COUNT(DISTINCT coupon_id) AS coupons_count
|
if ( null === $totals ) {
|
||||||
FROM
|
return new \WP_Error( 'woocommerce_analytics_revenue_result_failed', __( 'Sorry, fetching revenue data failed.', 'woocommerce' ) );
|
||||||
{$wpdb->prefix}wc_order_coupon_lookup
|
|
||||||
GROUP BY
|
|
||||||
order_id
|
|
||||||
) order_coupon_lookup
|
|
||||||
ON order_coupon_lookup.order_id = {$wpdb->prefix}wc_order_stats.order_id";
|
|
||||||
|
|
||||||
// Additional filtering for Orders report.
|
|
||||||
$this->orders_stats_sql_filter( $query_args );
|
|
||||||
$this->total_query->add_sql_clause( 'select', $selections );
|
|
||||||
$this->total_query->add_sql_clause( 'left_join', $coupon_join );
|
|
||||||
$this->total_query->add_sql_clause( 'where_time', $where_time );
|
|
||||||
$totals = $wpdb->get_results(
|
|
||||||
$this->total_query->get_query_statement(),
|
|
||||||
ARRAY_A
|
|
||||||
); // phpcs:ignore cache ok, DB call ok, unprepared SQL ok.
|
|
||||||
if ( null === $totals ) {
|
|
||||||
return new \WP_Error( 'woocommerce_analytics_revenue_result_failed', __( 'Sorry, fetching revenue data failed.', 'woocommerce' ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
// phpcs:ignore Generic.Commenting.Todo.TaskFound
|
|
||||||
// @todo Remove these assignements when refactoring segmenter classes to use query objects.
|
|
||||||
$totals_query = array(
|
|
||||||
'from_clause' => $this->total_query->get_sql_clause( 'join' ),
|
|
||||||
'where_time_clause' => $where_time,
|
|
||||||
'where_clause' => $this->total_query->get_sql_clause( 'where' ),
|
|
||||||
);
|
|
||||||
$intervals_query = array(
|
|
||||||
'select_clause' => $this->get_sql_clause( 'select' ),
|
|
||||||
'from_clause' => $this->interval_query->get_sql_clause( 'join' ),
|
|
||||||
'where_time_clause' => $where_time,
|
|
||||||
'where_clause' => $this->interval_query->get_sql_clause( 'where' ),
|
|
||||||
'limit' => $this->get_sql_clause( 'limit' ),
|
|
||||||
);
|
|
||||||
|
|
||||||
$unique_products = $this->get_unique_product_count( $totals_query['from_clause'], $totals_query['where_time_clause'], $totals_query['where_clause'] );
|
|
||||||
$totals[0]['products'] = $unique_products;
|
|
||||||
$segmenter = new Segmenter( $query_args, $this->report_columns );
|
|
||||||
$unique_coupons = $this->get_unique_coupon_count( $totals_query['from_clause'], $totals_query['where_time_clause'], $totals_query['where_clause'] );
|
|
||||||
$totals[0]['coupons_count'] = $unique_coupons;
|
|
||||||
$totals[0]['segments'] = $segmenter->get_totals_segments( $totals_query, $table_name );
|
|
||||||
$totals = (object) $this->cast_numbers( $totals[0] );
|
|
||||||
|
|
||||||
$this->interval_query->add_sql_clause( 'select', $this->get_sql_clause( 'select' ) . ' AS time_interval' );
|
|
||||||
$this->interval_query->add_sql_clause( 'left_join', $coupon_join );
|
|
||||||
$this->interval_query->add_sql_clause( 'where_time', $where_time );
|
|
||||||
$db_intervals = $wpdb->get_col(
|
|
||||||
$this->interval_query->get_query_statement()
|
|
||||||
); // phpcs:ignore cache ok, DB call ok, , unprepared SQL ok.
|
|
||||||
|
|
||||||
$db_interval_count = count( $db_intervals );
|
|
||||||
$expected_interval_count = TimeInterval::intervals_between( $query_args['after'], $query_args['before'], $query_args['interval'] );
|
|
||||||
$total_pages = (int) ceil( $expected_interval_count / $params['per_page'] );
|
|
||||||
|
|
||||||
if ( $query_args['page'] < 1 || $query_args['page'] > $total_pages ) {
|
|
||||||
return $data;
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->update_intervals_sql_params( $query_args, $db_interval_count, $expected_interval_count, $table_name );
|
|
||||||
$this->interval_query->add_sql_clause( 'order_by', $this->get_sql_clause( 'order_by' ) );
|
|
||||||
$this->interval_query->add_sql_clause( 'limit', $this->get_sql_clause( 'limit' ) );
|
|
||||||
$this->interval_query->add_sql_clause( 'select', ", MAX({$table_name}.date_created) AS datetime_anchor" );
|
|
||||||
if ( '' !== $selections ) {
|
|
||||||
$this->interval_query->add_sql_clause( 'select', ', ' . $selections );
|
|
||||||
}
|
|
||||||
$intervals = $wpdb->get_results(
|
|
||||||
$this->interval_query->get_query_statement(),
|
|
||||||
ARRAY_A
|
|
||||||
); // phpcs:ignore cache ok, DB call ok, unprepared SQL ok.
|
|
||||||
|
|
||||||
if ( null === $intervals ) {
|
|
||||||
return new \WP_Error( 'woocommerce_analytics_revenue_result_failed', __( 'Sorry, fetching revenue data failed.', 'woocommerce' ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( isset( $intervals[0] ) ) {
|
|
||||||
$unique_coupons = $this->get_unique_coupon_count( $intervals_query['from_clause'], $intervals_query['where_time_clause'], $intervals_query['where_clause'], true );
|
|
||||||
$intervals[0]['coupons_count'] = $unique_coupons;
|
|
||||||
}
|
|
||||||
|
|
||||||
$data = (object) array(
|
|
||||||
'totals' => $totals,
|
|
||||||
'intervals' => $intervals,
|
|
||||||
'total' => $expected_interval_count,
|
|
||||||
'pages' => $total_pages,
|
|
||||||
'page_no' => (int) $query_args['page'],
|
|
||||||
);
|
|
||||||
|
|
||||||
if ( TimeInterval::intervals_missing( $expected_interval_count, $db_interval_count, $params['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'], $params['per_page'], $db_interval_count, $expected_interval_count, $query_args['orderby'], $query_args['order'] );
|
|
||||||
} else {
|
|
||||||
$this->update_interval_boundary_dates( $query_args['after'], $query_args['before'], $query_args['interval'], $data->intervals );
|
|
||||||
}
|
|
||||||
$segmenter->add_intervals_segments( $data, $intervals_query, $table_name );
|
|
||||||
$this->create_interval_subtotals( $data->intervals );
|
|
||||||
|
|
||||||
$this->set_cached_data( $cache_key, $data );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// phpcs:ignore Generic.Commenting.Todo.TaskFound
|
||||||
|
// @todo Remove these assignements when refactoring segmenter classes to use query objects.
|
||||||
|
$totals_query = array(
|
||||||
|
'from_clause' => $this->total_query->get_sql_clause( 'join' ),
|
||||||
|
'where_time_clause' => $where_time,
|
||||||
|
'where_clause' => $this->total_query->get_sql_clause( 'where' ),
|
||||||
|
);
|
||||||
|
$intervals_query = array(
|
||||||
|
'select_clause' => $this->get_sql_clause( 'select' ),
|
||||||
|
'from_clause' => $this->interval_query->get_sql_clause( 'join' ),
|
||||||
|
'where_time_clause' => $where_time,
|
||||||
|
'where_clause' => $this->interval_query->get_sql_clause( 'where' ),
|
||||||
|
'limit' => $this->get_sql_clause( 'limit' ),
|
||||||
|
);
|
||||||
|
|
||||||
|
$unique_products = $this->get_unique_product_count( $totals_query['from_clause'], $totals_query['where_time_clause'], $totals_query['where_clause'] );
|
||||||
|
$totals[0]['products'] = $unique_products;
|
||||||
|
$segmenter = new Segmenter( $query_args, $this->report_columns );
|
||||||
|
$unique_coupons = $this->get_unique_coupon_count( $totals_query['from_clause'], $totals_query['where_time_clause'], $totals_query['where_clause'] );
|
||||||
|
$totals[0]['coupons_count'] = $unique_coupons;
|
||||||
|
$totals[0]['segments'] = $segmenter->get_totals_segments( $totals_query, $table_name );
|
||||||
|
$totals = (object) $this->cast_numbers( $totals[0] );
|
||||||
|
|
||||||
|
$this->interval_query->add_sql_clause( 'select', $this->get_sql_clause( 'select' ) . ' AS time_interval' );
|
||||||
|
$this->interval_query->add_sql_clause( 'left_join', $coupon_join );
|
||||||
|
$this->interval_query->add_sql_clause( 'where_time', $where_time );
|
||||||
|
$db_intervals = $wpdb->get_col(
|
||||||
|
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- cache ok, DB call ok, , unprepared SQL ok.
|
||||||
|
$this->interval_query->get_query_statement()
|
||||||
|
);
|
||||||
|
|
||||||
|
$db_interval_count = count( $db_intervals );
|
||||||
|
|
||||||
|
$this->update_intervals_sql_params( $query_args, $db_interval_count, $expected_interval_count, $table_name );
|
||||||
|
$this->interval_query->add_sql_clause( 'order_by', $this->get_sql_clause( 'order_by' ) );
|
||||||
|
$this->interval_query->add_sql_clause( 'limit', $this->get_sql_clause( 'limit' ) );
|
||||||
|
$this->interval_query->add_sql_clause( 'select', ", MAX({$table_name}.date_created) AS datetime_anchor" );
|
||||||
|
if ( '' !== $selections ) {
|
||||||
|
$this->interval_query->add_sql_clause( 'select', ', ' . $selections );
|
||||||
|
}
|
||||||
|
$intervals = $wpdb->get_results(
|
||||||
|
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- cache ok, DB call ok, , unprepared SQL ok.
|
||||||
|
$this->interval_query->get_query_statement(),
|
||||||
|
ARRAY_A
|
||||||
|
);
|
||||||
|
|
||||||
|
if ( null === $intervals ) {
|
||||||
|
return new \WP_Error( 'woocommerce_analytics_revenue_result_failed', __( 'Sorry, fetching revenue data failed.', 'woocommerce' ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( isset( $intervals[0] ) ) {
|
||||||
|
$unique_coupons = $this->get_unique_coupon_count( $intervals_query['from_clause'], $intervals_query['where_time_clause'], $intervals_query['where_clause'], true );
|
||||||
|
$intervals[0]['coupons_count'] = $unique_coupons;
|
||||||
|
}
|
||||||
|
|
||||||
|
$data->totals = $totals;
|
||||||
|
$data->intervals = $intervals;
|
||||||
|
|
||||||
|
if ( TimeInterval::intervals_missing( $expected_interval_count, $db_interval_count, $params['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'], $params['per_page'], $db_interval_count, $expected_interval_count, $query_args['orderby'], $query_args['order'] );
|
||||||
|
} else {
|
||||||
|
$this->update_interval_boundary_dates( $query_args['after'], $query_args['before'], $query_args['interval'], $data->intervals );
|
||||||
|
}
|
||||||
|
$segmenter->add_intervals_segments( $data, $intervals_query, $table_name );
|
||||||
|
|
||||||
return $data;
|
return $data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -729,18 +728,4 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize query objects.
|
|
||||||
*/
|
|
||||||
protected function initialize_queries() {
|
|
||||||
$this->clear_all_clauses();
|
|
||||||
unset( $this->subquery );
|
|
||||||
$this->total_query = new SqlQuery( $this->context . '_total' );
|
|
||||||
$this->total_query->add_sql_clause( 'from', self::get_db_table_name() );
|
|
||||||
|
|
||||||
$this->interval_query = new SqlQuery( $this->context . '_interval' );
|
|
||||||
$this->interval_query->add_sql_clause( 'from', self::get_db_table_name() );
|
|
||||||
$this->interval_query->add_sql_clause( 'group_by', 'time_interval' );
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,14 +17,23 @@
|
||||||
|
|
||||||
namespace Automattic\WooCommerce\Admin\API\Reports\Orders\Stats;
|
namespace Automattic\WooCommerce\Admin\API\Reports\Orders\Stats;
|
||||||
|
|
||||||
defined( 'ABSPATH' ) || exit;
|
use Automattic\WooCommerce\Admin\API\Reports\GenericQuery;
|
||||||
|
|
||||||
use Automattic\WooCommerce\Admin\API\Reports\Query as ReportsQuery;
|
defined( 'ABSPATH' ) || exit;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* API\Reports\Orders\Stats\Query
|
* API\Reports\Orders\Stats\Query
|
||||||
*/
|
*/
|
||||||
class Query extends ReportsQuery {
|
class Query extends GenericQuery {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specific query name.
|
||||||
|
* Will be used to load the `report-{name}` data store,
|
||||||
|
* and to call `woocommerce_analytics_{snake_case(name)}_*` filters.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $name = 'orders-stats';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Valid fields for Orders report.
|
* Valid fields for Orders report.
|
||||||
|
@ -45,17 +54,4 @@ class Query extends ReportsQuery {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get revenue data based on the current query vars.
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function get_data() {
|
|
||||||
$args = apply_filters( 'woocommerce_analytics_orders_stats_query_args', $this->get_query_vars() );
|
|
||||||
|
|
||||||
$data_store = \WC_Data_Store::load( 'report-orders-stats' );
|
|
||||||
$results = $data_store->get_data( $args );
|
|
||||||
return apply_filters( 'woocommerce_analytics_orders_stats_select_query', $results, $args );
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -452,10 +452,10 @@ class Controller extends GenericController {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prepare a report object for serialization.
|
* Prepare a report data item for serialization.
|
||||||
*
|
*
|
||||||
* @param array $stat_data Report data.
|
* @param array $stat_data Report data item as returned from Data Store.
|
||||||
* @param WP_REST_Request $request Request object.
|
* @param WP_REST_Request $request Request object.
|
||||||
* @return WP_REST_Response
|
* @return WP_REST_Response
|
||||||
*/
|
*/
|
||||||
public function prepare_item_for_response( $stat_data, $request ) {
|
public function prepare_item_for_response( $stat_data, $request ) {
|
||||||
|
|
|
@ -9,8 +9,9 @@ namespace Automattic\WooCommerce\Admin\API\Reports\Products;
|
||||||
|
|
||||||
defined( 'ABSPATH' ) || exit;
|
defined( 'ABSPATH' ) || exit;
|
||||||
|
|
||||||
use Automattic\WooCommerce\Admin\API\Reports\GenericController;
|
|
||||||
use Automattic\WooCommerce\Admin\API\Reports\ExportableInterface;
|
use Automattic\WooCommerce\Admin\API\Reports\ExportableInterface;
|
||||||
|
use Automattic\WooCommerce\Admin\API\Reports\GenericController;
|
||||||
|
use Automattic\WooCommerce\Admin\API\Reports\GenericQuery;
|
||||||
use WP_REST_Request;
|
use WP_REST_Request;
|
||||||
use WP_REST_Response;
|
use WP_REST_Response;
|
||||||
|
|
||||||
|
@ -41,51 +42,22 @@ class Controller extends GenericController implements ExportableInterface {
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get items.
|
* Get data from `'products'` Query.
|
||||||
*
|
*
|
||||||
* @param WP_REST_Request $request Request data.
|
* @override GenericController::get_datastore_data()
|
||||||
*
|
*
|
||||||
* @return array|WP_Error
|
* @param array $query_args Query arguments.
|
||||||
|
* @return mixed Results from the data store.
|
||||||
*/
|
*/
|
||||||
public function get_items( $request ) {
|
protected function get_datastore_data( $query_args = array() ) {
|
||||||
$args = array();
|
$query = new GenericQuery( $query_args, 'products' );
|
||||||
$registered = array_keys( $this->get_collection_params() );
|
return $query->get_data();
|
||||||
foreach ( $registered as $param_name ) {
|
|
||||||
if ( isset( $request[ $param_name ] ) ) {
|
|
||||||
if ( isset( $this->param_mapping[ $param_name ] ) ) {
|
|
||||||
$args[ $this->param_mapping[ $param_name ] ] = $request[ $param_name ];
|
|
||||||
} else {
|
|
||||||
$args[ $param_name ] = $request[ $param_name ];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$reports = new 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 );
|
|
||||||
if ( isset( $item->data['extended_info']['name'] ) ) {
|
|
||||||
$item->data['extended_info']['name'] = wp_strip_all_tags( $item->data['extended_info']['name'] );
|
|
||||||
}
|
|
||||||
$data[] = $this->prepare_response_for_collection( $item );
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->add_pagination_headers(
|
|
||||||
$request,
|
|
||||||
$data,
|
|
||||||
(int) $products_data->total,
|
|
||||||
(int) $products_data->page_no,
|
|
||||||
(int) $products_data->pages
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prepare a report object for serialization.
|
* Prepare a report data item for serialization.
|
||||||
*
|
*
|
||||||
* @param Array $report Report data.
|
* @param Array $report Report data item as returned from Data Store.
|
||||||
* @param WP_REST_Request $request Request object.
|
* @param WP_REST_Request $request Request object.
|
||||||
* @return WP_REST_Response
|
* @return WP_REST_Response
|
||||||
*/
|
*/
|
||||||
|
@ -101,8 +73,36 @@ class Controller extends GenericController implements ExportableInterface {
|
||||||
* @param WP_REST_Response $response The response object.
|
* @param WP_REST_Response $response The response object.
|
||||||
* @param object $report The original report object.
|
* @param object $report The original report object.
|
||||||
* @param WP_REST_Request $request Request used to generate the response.
|
* @param WP_REST_Request $request Request used to generate the response.
|
||||||
|
*
|
||||||
|
* @since 6.5.0
|
||||||
*/
|
*/
|
||||||
return apply_filters( 'woocommerce_rest_prepare_report_products', $response, $report, $request );
|
$filtered_response = apply_filters( 'woocommerce_rest_prepare_report_products', $response, $report, $request );
|
||||||
|
if ( isset( $filtered_response->data['extended_info']['name'] ) ) {
|
||||||
|
$filtered_response->data['extended_info']['name'] = wp_strip_all_tags( $filtered_response->data['extended_info']['name'] );
|
||||||
|
}
|
||||||
|
return $filtered_response;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps query arguments from the REST request.
|
||||||
|
*
|
||||||
|
* @param array $request Request array.
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
protected function prepare_reports_query( $request ) {
|
||||||
|
$args = array();
|
||||||
|
$registered = array_keys( $this->get_collection_params() );
|
||||||
|
foreach ( $registered as $param_name ) {
|
||||||
|
if ( isset( $request[ $param_name ] ) ) {
|
||||||
|
if ( isset( $this->param_mapping[ $param_name ] ) ) {
|
||||||
|
$args[ $this->param_mapping[ $param_name ] ] = $request[ $param_name ];
|
||||||
|
} else {
|
||||||
|
$args[ $param_name ] = $request[ $param_name ];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $args;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -21,6 +21,8 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||||
/**
|
/**
|
||||||
* Table used to get the data.
|
* Table used to get the data.
|
||||||
*
|
*
|
||||||
|
* @override ReportsDataStore::$table_name
|
||||||
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
protected static $table_name = 'wc_order_product_lookup';
|
protected static $table_name = 'wc_order_product_lookup';
|
||||||
|
@ -28,6 +30,8 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||||
/**
|
/**
|
||||||
* Cache identifier.
|
* Cache identifier.
|
||||||
*
|
*
|
||||||
|
* @override ReportsDataStore::$cache_key
|
||||||
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
protected $cache_key = 'products';
|
protected $cache_key = 'products';
|
||||||
|
@ -35,6 +39,8 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||||
/**
|
/**
|
||||||
* Mapping columns to data type to return correct response types.
|
* Mapping columns to data type to return correct response types.
|
||||||
*
|
*
|
||||||
|
* @override ReportsDataStore::$column_types
|
||||||
|
*
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
protected $column_types = array(
|
protected $column_types = array(
|
||||||
|
@ -79,12 +85,16 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||||
/**
|
/**
|
||||||
* Data store context used to pass to filters.
|
* Data store context used to pass to filters.
|
||||||
*
|
*
|
||||||
|
* @override ReportsDataStore::$context
|
||||||
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
protected $context = 'products';
|
protected $context = 'products';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Assign report columns once full table name has been assigned.
|
* Assign report columns once full table name has been assigned.
|
||||||
|
*
|
||||||
|
* @override ReportsDataStore::assign_report_columns()
|
||||||
*/
|
*/
|
||||||
protected function assign_report_columns() {
|
protected function assign_report_columns() {
|
||||||
$table_name = self::get_db_table_name();
|
$table_name = self::get_db_table_name();
|
||||||
|
@ -175,6 +185,8 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||||
/**
|
/**
|
||||||
* Maps ordering specified by the user to columns in the database/fields in the data.
|
* Maps ordering specified by the user to columns in the database/fields in the data.
|
||||||
*
|
*
|
||||||
|
* @override ReportsDataStore::normalize_order_by()
|
||||||
|
*
|
||||||
* @param string $order_by Sorting criterion.
|
* @param string $order_by Sorting criterion.
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
|
@ -256,122 +268,137 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||||
/**
|
/**
|
||||||
* Returns the report data based on parameters supplied by the user.
|
* Returns the report data based on parameters supplied by the user.
|
||||||
*
|
*
|
||||||
|
* @override ReportsDataStore::get_data()
|
||||||
|
*
|
||||||
* @param array $query_args Query parameters.
|
* @param array $query_args Query parameters.
|
||||||
* @return stdClass|WP_Error Data.
|
* @return stdClass|WP_Error Data.
|
||||||
*/
|
*/
|
||||||
public function get_data( $query_args ) {
|
public function get_data( $query_args ) {
|
||||||
|
$data = parent::get_data( $query_args );
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Do not cache extended info -- this is required to get the latest stock data.
|
||||||
|
* `include_extended_info` checks only `extended_info` key,
|
||||||
|
* so we don't need to bother about normalizing timestamps.
|
||||||
|
*/
|
||||||
|
$defaults = $this->get_default_query_vars();
|
||||||
|
$query_args = wp_parse_args( $query_args, $defaults );
|
||||||
|
$this->include_extended_info( $data->data, $query_args );
|
||||||
|
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the default query arguments to be used by get_data().
|
||||||
|
* These defaults are only partially applied when used via REST API, as that has its own defaults.
|
||||||
|
*
|
||||||
|
* @override ReportsDataStore::get_default_query_vars()
|
||||||
|
*
|
||||||
|
* @return array Query parameters.
|
||||||
|
*/
|
||||||
|
public function get_default_query_vars() {
|
||||||
|
$defaults = parent::get_default_query_vars();
|
||||||
|
$defaults['category_includes'] = array();
|
||||||
|
$defaults['product_includes'] = array();
|
||||||
|
$defaults['extended_info'] = false;
|
||||||
|
|
||||||
|
return $defaults;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the report data based on normalized parameters.
|
||||||
|
* Will be called by `get_data` if there is no data in cache.
|
||||||
|
*
|
||||||
|
* @override ReportsDataStore::get_noncached_data()
|
||||||
|
*
|
||||||
|
* @see get_data
|
||||||
|
* @param array $query_args Query parameters.
|
||||||
|
* @return stdClass|WP_Error Data object `{ totals: *, intervals: array, total: int, pages: int, page_no: int }`, or error.
|
||||||
|
*/
|
||||||
|
public function get_noncached_data( $query_args ) {
|
||||||
global $wpdb;
|
global $wpdb;
|
||||||
|
|
||||||
$table_name = self::get_db_table_name();
|
$table_name = self::get_db_table_name();
|
||||||
|
|
||||||
// These defaults are only partially applied when used via REST API, as that has its own defaults.
|
$this->initialize_queries();
|
||||||
$defaults = array(
|
|
||||||
'per_page' => get_option( 'posts_per_page' ),
|
$data = (object) array(
|
||||||
'page' => 1,
|
'data' => array(),
|
||||||
'order' => 'DESC',
|
'total' => 0,
|
||||||
'orderby' => 'date',
|
'pages' => 0,
|
||||||
'before' => TimeInterval::default_before(),
|
'page_no' => 0,
|
||||||
'after' => TimeInterval::default_after(),
|
|
||||||
'fields' => '*',
|
|
||||||
'category_includes' => array(),
|
|
||||||
'product_includes' => array(),
|
|
||||||
'extended_info' => false,
|
|
||||||
);
|
);
|
||||||
$query_args = wp_parse_args( $query_args, $defaults );
|
|
||||||
$this->normalize_timezones( $query_args, $defaults );
|
|
||||||
|
|
||||||
/*
|
$selections = $this->selected_columns( $query_args );
|
||||||
* We need to get the cache key here because
|
$included_products = $this->get_included_products_array( $query_args );
|
||||||
* parent::update_intervals_sql_params() modifies $query_args.
|
$params = $this->get_limit_params( $query_args );
|
||||||
*/
|
$this->add_sql_query_params( $query_args );
|
||||||
$cache_key = $this->get_cache_key( $query_args );
|
|
||||||
$data = $this->get_cached_data( $cache_key );
|
|
||||||
|
|
||||||
if ( false === $data ) {
|
if ( count( $included_products ) > 0 ) {
|
||||||
$this->initialize_queries();
|
$filtered_products = array_diff( $included_products, array( '-1' ) );
|
||||||
|
$total_results = count( $filtered_products );
|
||||||
|
$total_pages = (int) ceil( $total_results / $params['per_page'] );
|
||||||
|
|
||||||
$data = (object) array(
|
if ( 'date' === $query_args['orderby'] ) {
|
||||||
'data' => array(),
|
$selections .= ", {$table_name}.date_created";
|
||||||
'total' => 0,
|
|
||||||
'pages' => 0,
|
|
||||||
'page_no' => 0,
|
|
||||||
);
|
|
||||||
|
|
||||||
$selections = $this->selected_columns( $query_args );
|
|
||||||
$included_products = $this->get_included_products_array( $query_args );
|
|
||||||
$params = $this->get_limit_params( $query_args );
|
|
||||||
$this->add_sql_query_params( $query_args );
|
|
||||||
|
|
||||||
if ( count( $included_products ) > 0 ) {
|
|
||||||
$filtered_products = array_diff( $included_products, array( '-1' ) );
|
|
||||||
$total_results = count( $filtered_products );
|
|
||||||
$total_pages = (int) ceil( $total_results / $params['per_page'] );
|
|
||||||
|
|
||||||
if ( 'date' === $query_args['orderby'] ) {
|
|
||||||
$selections .= ", {$table_name}.date_created";
|
|
||||||
}
|
|
||||||
|
|
||||||
$fields = $this->get_fields( $query_args );
|
|
||||||
$join_selections = $this->format_join_selections( $fields, array( 'product_id' ) );
|
|
||||||
$ids_table = $this->get_ids_table( $included_products, 'product_id' );
|
|
||||||
|
|
||||||
$this->subquery->clear_sql_clause( 'select' );
|
|
||||||
$this->subquery->add_sql_clause( 'select', $selections );
|
|
||||||
$this->add_sql_clause( 'select', $join_selections );
|
|
||||||
$this->add_sql_clause( 'from', '(' );
|
|
||||||
$this->add_sql_clause( 'from', $this->subquery->get_query_statement() );
|
|
||||||
$this->add_sql_clause( 'from', ") AS {$table_name}" );
|
|
||||||
$this->add_sql_clause(
|
|
||||||
'right_join',
|
|
||||||
"RIGHT JOIN ( {$ids_table} ) AS default_results
|
|
||||||
ON default_results.product_id = {$table_name}.product_id"
|
|
||||||
);
|
|
||||||
$this->add_sql_clause( 'where', 'AND default_results.product_id != -1' );
|
|
||||||
|
|
||||||
$products_query = $this->get_query_statement();
|
|
||||||
} else {
|
|
||||||
$count_query = "SELECT COUNT(*) FROM (
|
|
||||||
{$this->subquery->get_query_statement()}
|
|
||||||
) AS tt";
|
|
||||||
$db_records_count = (int) $wpdb->get_var(
|
|
||||||
$count_query // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
|
||||||
);
|
|
||||||
|
|
||||||
$total_results = $db_records_count;
|
|
||||||
$total_pages = (int) ceil( $db_records_count / $params['per_page'] );
|
|
||||||
|
|
||||||
if ( ( $query_args['page'] < 1 || $query_args['page'] > $total_pages ) ) {
|
|
||||||
return $data;
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->subquery->clear_sql_clause( 'select' );
|
|
||||||
$this->subquery->add_sql_clause( 'select', $selections );
|
|
||||||
$this->subquery->add_sql_clause( 'order_by', $this->get_sql_clause( 'order_by' ) );
|
|
||||||
$this->subquery->add_sql_clause( 'limit', $this->get_sql_clause( 'limit' ) );
|
|
||||||
$products_query = $this->subquery->get_query_statement();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$product_data = $wpdb->get_results(
|
$fields = $this->get_fields( $query_args );
|
||||||
$products_query, // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
$join_selections = $this->format_join_selections( $fields, array( 'product_id' ) );
|
||||||
ARRAY_A
|
$ids_table = $this->get_ids_table( $included_products, 'product_id' );
|
||||||
|
|
||||||
|
$this->subquery->clear_sql_clause( 'select' );
|
||||||
|
$this->subquery->add_sql_clause( 'select', $selections );
|
||||||
|
$this->add_sql_clause( 'select', $join_selections );
|
||||||
|
$this->add_sql_clause( 'from', '(' );
|
||||||
|
$this->add_sql_clause( 'from', $this->subquery->get_query_statement() );
|
||||||
|
$this->add_sql_clause( 'from', ") AS {$table_name}" );
|
||||||
|
$this->add_sql_clause(
|
||||||
|
'right_join',
|
||||||
|
"RIGHT JOIN ( {$ids_table} ) AS default_results
|
||||||
|
ON default_results.product_id = {$table_name}.product_id"
|
||||||
|
);
|
||||||
|
$this->add_sql_clause( 'where', 'AND default_results.product_id != -1' );
|
||||||
|
|
||||||
|
$products_query = $this->get_query_statement();
|
||||||
|
} else {
|
||||||
|
$count_query = "SELECT COUNT(*) FROM (
|
||||||
|
{$this->subquery->get_query_statement()}
|
||||||
|
) AS tt";
|
||||||
|
$db_records_count = (int) $wpdb->get_var(
|
||||||
|
$count_query // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||||
);
|
);
|
||||||
|
|
||||||
if ( null === $product_data ) {
|
$total_results = $db_records_count;
|
||||||
|
$total_pages = (int) ceil( $db_records_count / $params['per_page'] );
|
||||||
|
|
||||||
|
if ( ( $query_args['page'] < 1 || $query_args['page'] > $total_pages ) ) {
|
||||||
return $data;
|
return $data;
|
||||||
}
|
}
|
||||||
|
|
||||||
$product_data = array_map( array( $this, 'cast_numbers' ), $product_data );
|
$this->subquery->clear_sql_clause( 'select' );
|
||||||
$data = (object) array(
|
$this->subquery->add_sql_clause( 'select', $selections );
|
||||||
'data' => $product_data,
|
$this->subquery->add_sql_clause( 'order_by', $this->get_sql_clause( 'order_by' ) );
|
||||||
'total' => $total_results,
|
$this->subquery->add_sql_clause( 'limit', $this->get_sql_clause( 'limit' ) );
|
||||||
'pages' => $total_pages,
|
$products_query = $this->subquery->get_query_statement();
|
||||||
'page_no' => (int) $query_args['page'],
|
|
||||||
);
|
|
||||||
|
|
||||||
$this->set_cached_data( $cache_key, $data );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->include_extended_info( $data->data, $query_args );
|
$product_data = $wpdb->get_results(
|
||||||
|
$products_query, // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||||
|
ARRAY_A
|
||||||
|
);
|
||||||
|
|
||||||
|
if ( null === $product_data ) {
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
$product_data = array_map( array( $this, 'cast_numbers' ), $product_data );
|
||||||
|
$data = (object) array(
|
||||||
|
'data' => $product_data,
|
||||||
|
'total' => $total_results,
|
||||||
|
'pages' => $total_pages,
|
||||||
|
'page_no' => (int) $query_args['page'],
|
||||||
|
);
|
||||||
|
|
||||||
return $data;
|
return $data;
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,12 +22,16 @@ use Automattic\WooCommerce\Admin\API\Reports\Query as ReportsQuery;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* API\Reports\Products\Query
|
* API\Reports\Products\Query
|
||||||
|
*
|
||||||
|
* @deprecated 9.3.0 Products\Query class is deprecated, please use GenericQuery or \WC_Object_Query instead.
|
||||||
*/
|
*/
|
||||||
class Query extends ReportsQuery {
|
class Query extends ReportsQuery {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Valid fields for Products report.
|
* Valid fields for Products report.
|
||||||
*
|
*
|
||||||
|
* @deprecated 9.3.0 Products\Query class is deprecated, please use GenericQuery or \WC_Object_Query instead.
|
||||||
|
*
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
protected function get_default_query_vars() {
|
protected function get_default_query_vars() {
|
||||||
|
@ -37,6 +41,8 @@ class Query extends ReportsQuery {
|
||||||
/**
|
/**
|
||||||
* Get product data based on the current query vars.
|
* Get product data based on the current query vars.
|
||||||
*
|
*
|
||||||
|
* @deprecated 9.3.0 Products\Query class is deprecated, please use GenericQuery or \WC_Object_Query instead.
|
||||||
|
*
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
public function get_data() {
|
public function get_data() {
|
||||||
|
|
|
@ -9,8 +9,8 @@ namespace Automattic\WooCommerce\Admin\API\Reports\Products\Stats;
|
||||||
|
|
||||||
defined( 'ABSPATH' ) || exit;
|
defined( 'ABSPATH' ) || exit;
|
||||||
|
|
||||||
|
use Automattic\WooCommerce\Admin\API\Reports\GenericQuery;
|
||||||
use Automattic\WooCommerce\Admin\API\Reports\GenericStatsController;
|
use Automattic\WooCommerce\Admin\API\Reports\GenericStatsController;
|
||||||
use Automattic\WooCommerce\Admin\API\Reports\ParameterException;
|
|
||||||
use WP_REST_Request;
|
use WP_REST_Request;
|
||||||
use WP_REST_Response;
|
use WP_REST_Response;
|
||||||
|
|
||||||
|
@ -48,12 +48,25 @@ class Controller extends GenericStatsController {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all reports.
|
* Get data from `'products-stats'` Query.
|
||||||
*
|
*
|
||||||
* @param WP_REST_Request $request Request data.
|
* @override GenericController::get_datastore_data()
|
||||||
* @return array|WP_Error
|
*
|
||||||
|
* @param array $query_args Query arguments.
|
||||||
|
* @return mixed Results from the data store.
|
||||||
*/
|
*/
|
||||||
public function get_items( $request ) {
|
protected function get_datastore_data( $query_args = array() ) {
|
||||||
|
$query = new GenericQuery( $query_args, 'products-stats' );
|
||||||
|
return $query->get_data();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps query arguments from the REST request, to be fed to Query.
|
||||||
|
*
|
||||||
|
* @param \WP_REST_Request $request Full request object.
|
||||||
|
* @return array Simplified array of params.
|
||||||
|
*/
|
||||||
|
protected function prepare_reports_query( $request ) {
|
||||||
$query_args = array(
|
$query_args = array(
|
||||||
'fields' => array(
|
'fields' => array(
|
||||||
'items_sold',
|
'items_sold',
|
||||||
|
@ -75,36 +88,13 @@ class Controller extends GenericStatsController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$query = new Query( $query_args );
|
return $query_args;
|
||||||
try {
|
|
||||||
$report_data = $query->get_data();
|
|
||||||
} catch ( ParameterException $e ) {
|
|
||||||
return new \WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
$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 );
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->add_pagination_headers(
|
|
||||||
$request,
|
|
||||||
$out_data,
|
|
||||||
(int) $report_data->total,
|
|
||||||
(int) $report_data->page_no,
|
|
||||||
(int) $report_data->pages
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prepare a report object for serialization.
|
* Prepare a report data item for serialization.
|
||||||
*
|
*
|
||||||
* @param array $report Report data.
|
* @param array $report Report data item as returned from Data Store.
|
||||||
* @param WP_REST_Request $request Request object.
|
* @param WP_REST_Request $request Request object.
|
||||||
* @return WP_REST_Response
|
* @return WP_REST_Response
|
||||||
*/
|
*/
|
||||||
|
@ -255,15 +245,6 @@ class Controller extends GenericStatsController {
|
||||||
),
|
),
|
||||||
'validate_callback' => 'rest_validate_request_arg',
|
'validate_callback' => 'rest_validate_request_arg',
|
||||||
);
|
);
|
||||||
$params['fields'] = array(
|
|
||||||
'description' => __( 'Limit stats fields to the specified items.', 'woocommerce' ),
|
|
||||||
'type' => 'array',
|
|
||||||
'sanitize_callback' => 'wp_parse_slug_list',
|
|
||||||
'validate_callback' => 'rest_validate_request_arg',
|
|
||||||
'items' => array(
|
|
||||||
'type' => 'string',
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
return $params;
|
return $params;
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,18 +8,22 @@ namespace Automattic\WooCommerce\Admin\API\Reports\Products\Stats;
|
||||||
defined( 'ABSPATH' ) || exit;
|
defined( 'ABSPATH' ) || exit;
|
||||||
|
|
||||||
use Automattic\WooCommerce\Admin\API\Reports\Products\DataStore as ProductsDataStore;
|
use Automattic\WooCommerce\Admin\API\Reports\Products\DataStore as ProductsDataStore;
|
||||||
|
use Automattic\WooCommerce\Admin\API\Reports\DataStore as ReportsDataStore;
|
||||||
use Automattic\WooCommerce\Admin\API\Reports\DataStoreInterface;
|
use Automattic\WooCommerce\Admin\API\Reports\DataStoreInterface;
|
||||||
use Automattic\WooCommerce\Admin\API\Reports\TimeInterval;
|
use Automattic\WooCommerce\Admin\API\Reports\TimeInterval;
|
||||||
use Automattic\WooCommerce\Admin\API\Reports\SqlQuery;
|
use Automattic\WooCommerce\Admin\API\Reports\StatsDataStoreTrait;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* API\Reports\Products\Stats\DataStore.
|
* API\Reports\Products\Stats\DataStore.
|
||||||
*/
|
*/
|
||||||
class DataStore extends ProductsDataStore implements DataStoreInterface {
|
class DataStore extends ProductsDataStore implements DataStoreInterface {
|
||||||
|
use StatsDataStoreTrait;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mapping columns to data type to return correct response types.
|
* Mapping columns to data type to return correct response types.
|
||||||
*
|
*
|
||||||
|
* @override ProductsDataStore::$column_types
|
||||||
|
*
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
protected $column_types = array(
|
protected $column_types = array(
|
||||||
|
@ -36,6 +40,8 @@ class DataStore extends ProductsDataStore implements DataStoreInterface {
|
||||||
/**
|
/**
|
||||||
* Cache identifier.
|
* Cache identifier.
|
||||||
*
|
*
|
||||||
|
* @override ProductsDataStore::$cache_key
|
||||||
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
protected $cache_key = 'products_stats';
|
protected $cache_key = 'products_stats';
|
||||||
|
@ -43,12 +49,16 @@ class DataStore extends ProductsDataStore implements DataStoreInterface {
|
||||||
/**
|
/**
|
||||||
* Data store context used to pass to filters.
|
* Data store context used to pass to filters.
|
||||||
*
|
*
|
||||||
|
* @override ProductsDataStore::$context
|
||||||
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
protected $context = 'products_stats';
|
protected $context = 'products_stats';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Assign report columns once full table name has been assigned.
|
* Assign report columns once full table name has been assigned.
|
||||||
|
*
|
||||||
|
* @override ProductsDataStore::assign_report_columns()
|
||||||
*/
|
*/
|
||||||
protected function assign_report_columns() {
|
protected function assign_report_columns() {
|
||||||
$table_name = self::get_db_table_name();
|
$table_name = self::get_db_table_name();
|
||||||
|
@ -99,138 +109,141 @@ class DataStore extends ProductsDataStore implements DataStoreInterface {
|
||||||
$this->interval_query->add_sql_clause( 'select', $this->get_sql_clause( 'select' ) . ' AS time_interval' );
|
$this->interval_query->add_sql_clause( 'select', $this->get_sql_clause( 'select' ) . ' AS time_interval' );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the default query arguments to be used by get_data().
|
||||||
|
* These defaults are only partially applied when used via REST API, as that has its own defaults.
|
||||||
|
*
|
||||||
|
* @override ProductsDataStore::get_default_query_vars()
|
||||||
|
*
|
||||||
|
* @return array Query parameters.
|
||||||
|
*/
|
||||||
|
public function get_default_query_vars() {
|
||||||
|
$defaults = parent::get_default_query_vars();
|
||||||
|
$defaults['interval'] = 'week';
|
||||||
|
unset( $defaults['extended_info'] );
|
||||||
|
|
||||||
|
return $defaults;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the report data based on parameters supplied by the user.
|
* Returns the report data based on parameters supplied by the user.
|
||||||
*
|
*
|
||||||
* @since 3.5.0
|
* @override ProductsDataStore::get_data()
|
||||||
|
*
|
||||||
* @param array $query_args Query parameters.
|
* @param array $query_args Query parameters.
|
||||||
* @return stdClass|WP_Error Data.
|
* @return stdClass|WP_Error Data.
|
||||||
*/
|
*/
|
||||||
public function get_data( $query_args ) {
|
public function get_data( $query_args ) {
|
||||||
|
// Do not include extended info like `ProductsDataStore` does.
|
||||||
|
return ReportsDataStore::get_data( $query_args );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the report data based on normalized parameters.
|
||||||
|
* Will be called by `get_data` if there is no data in cache.
|
||||||
|
*
|
||||||
|
* @override ProductsDataStore::get_noncached_data()
|
||||||
|
*
|
||||||
|
* @see get_data
|
||||||
|
* @see get_noncached_stats_data
|
||||||
|
* @param array $query_args Query parameters.
|
||||||
|
* @param array $params Query limit parameters.
|
||||||
|
* @param stdClass $data Reference to the data object to fill.
|
||||||
|
* @param int $expected_interval_count Number of expected intervals.
|
||||||
|
* @return stdClass|WP_Error Data object `{ totals: *, intervals: array, total: int, pages: int, page_no: int }`, or error.
|
||||||
|
*/
|
||||||
|
public function get_noncached_stats_data( $query_args, $params, &$data, $expected_interval_count ) {
|
||||||
global $wpdb;
|
global $wpdb;
|
||||||
|
|
||||||
$table_name = self::get_db_table_name();
|
$table_name = self::get_db_table_name();
|
||||||
|
|
||||||
// These defaults are only partially applied when used via REST API, as that has its own defaults.
|
$this->initialize_queries();
|
||||||
$defaults = array(
|
|
||||||
'per_page' => get_option( 'posts_per_page' ),
|
$selections = $this->selected_columns( $query_args );
|
||||||
'page' => 1,
|
|
||||||
'order' => 'DESC',
|
$this->update_sql_query_params( $query_args );
|
||||||
'orderby' => 'date',
|
$this->get_limit_sql_params( $query_args );
|
||||||
'before' => TimeInterval::default_before(),
|
$this->interval_query->add_sql_clause( 'where_time', $this->get_sql_clause( 'where_time' ) );
|
||||||
'after' => TimeInterval::default_after(),
|
|
||||||
'fields' => '*',
|
$db_intervals = $wpdb->get_col(
|
||||||
'category_includes' => array(),
|
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- cache ok, DB call ok, unprepared SQL ok.
|
||||||
'interval' => 'week',
|
$this->interval_query->get_query_statement()
|
||||||
'product_includes' => array(),
|
|
||||||
);
|
);
|
||||||
$query_args = wp_parse_args( $query_args, $defaults );
|
|
||||||
$this->normalize_timezones( $query_args, $defaults );
|
|
||||||
|
|
||||||
/*
|
$db_interval_count = count( $db_intervals );
|
||||||
* We need to get the cache key here because
|
|
||||||
* parent::update_intervals_sql_params() modifies $query_args.
|
|
||||||
*/
|
|
||||||
$cache_key = $this->get_cache_key( $query_args );
|
|
||||||
$data = $this->get_cached_data( $cache_key );
|
|
||||||
|
|
||||||
if ( false === $data ) {
|
$intervals = array();
|
||||||
$this->initialize_queries();
|
$this->update_intervals_sql_params( $query_args, $db_interval_count, $expected_interval_count, $table_name );
|
||||||
|
$this->total_query->add_sql_clause( 'select', $selections );
|
||||||
|
$this->total_query->add_sql_clause( 'where_time', $this->get_sql_clause( 'where_time' ) );
|
||||||
|
|
||||||
$selections = $this->selected_columns( $query_args );
|
$totals = $wpdb->get_results(
|
||||||
$params = $this->get_limit_params( $query_args );
|
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- cache ok, DB call ok, unprepared SQL ok.
|
||||||
|
$this->total_query->get_query_statement(),
|
||||||
|
ARRAY_A
|
||||||
|
);
|
||||||
|
|
||||||
$this->update_sql_query_params( $query_args );
|
// phpcs:ignore Generic.Commenting.Todo.TaskFound
|
||||||
$this->get_limit_sql_params( $query_args );
|
// @todo remove these assignements when refactoring segmenter classes to use query objects.
|
||||||
$this->interval_query->add_sql_clause( 'where_time', $this->get_sql_clause( 'where_time' ) );
|
$totals_query = array(
|
||||||
|
'from_clause' => $this->total_query->get_sql_clause( 'join' ),
|
||||||
|
'where_time_clause' => $this->total_query->get_sql_clause( 'where_time' ),
|
||||||
|
'where_clause' => $this->total_query->get_sql_clause( 'where' ),
|
||||||
|
);
|
||||||
|
$intervals_query = array(
|
||||||
|
'select_clause' => $this->get_sql_clause( 'select' ),
|
||||||
|
'from_clause' => $this->interval_query->get_sql_clause( 'join' ),
|
||||||
|
'where_time_clause' => $this->interval_query->get_sql_clause( 'where_time' ),
|
||||||
|
'where_clause' => $this->interval_query->get_sql_clause( 'where' ),
|
||||||
|
'order_by' => $this->get_sql_clause( 'order_by' ),
|
||||||
|
'limit' => $this->get_sql_clause( 'limit' ),
|
||||||
|
);
|
||||||
|
$segmenter = new Segmenter( $query_args, $this->report_columns );
|
||||||
|
$totals[0]['segments'] = $segmenter->get_totals_segments( $totals_query, $table_name );
|
||||||
|
|
||||||
$db_intervals = $wpdb->get_col(
|
if ( null === $totals ) {
|
||||||
$this->interval_query->get_query_statement()
|
return new \WP_Error( 'woocommerce_analytics_products_stats_result_failed', __( 'Sorry, fetching revenue data failed.', 'woocommerce' ) );
|
||||||
); // WPCS: cache ok, DB call ok, unprepared SQL ok.
|
|
||||||
|
|
||||||
$db_interval_count = count( $db_intervals );
|
|
||||||
$expected_interval_count = TimeInterval::intervals_between( $query_args['after'], $query_args['before'], $query_args['interval'] );
|
|
||||||
$total_pages = (int) ceil( $expected_interval_count / $params['per_page'] );
|
|
||||||
if ( $query_args['page'] < 1 || $query_args['page'] > $total_pages ) {
|
|
||||||
return array();
|
|
||||||
}
|
|
||||||
|
|
||||||
$intervals = array();
|
|
||||||
$this->update_intervals_sql_params( $query_args, $db_interval_count, $expected_interval_count, $table_name );
|
|
||||||
$this->total_query->add_sql_clause( 'select', $selections );
|
|
||||||
$this->total_query->add_sql_clause( 'where_time', $this->get_sql_clause( 'where_time' ) );
|
|
||||||
|
|
||||||
$totals = $wpdb->get_results(
|
|
||||||
$this->total_query->get_query_statement(),
|
|
||||||
ARRAY_A
|
|
||||||
); // WPCS: cache ok, DB call ok, unprepared SQL ok.
|
|
||||||
|
|
||||||
// @todo remove these assignements when refactoring segmenter classes to use query objects.
|
|
||||||
$totals_query = array(
|
|
||||||
'from_clause' => $this->total_query->get_sql_clause( 'join' ),
|
|
||||||
'where_time_clause' => $this->total_query->get_sql_clause( 'where_time' ),
|
|
||||||
'where_clause' => $this->total_query->get_sql_clause( 'where' ),
|
|
||||||
);
|
|
||||||
$intervals_query = array(
|
|
||||||
'select_clause' => $this->get_sql_clause( 'select' ),
|
|
||||||
'from_clause' => $this->interval_query->get_sql_clause( 'join' ),
|
|
||||||
'where_time_clause' => $this->interval_query->get_sql_clause( 'where_time' ),
|
|
||||||
'where_clause' => $this->interval_query->get_sql_clause( 'where' ),
|
|
||||||
'order_by' => $this->get_sql_clause( 'order_by' ),
|
|
||||||
'limit' => $this->get_sql_clause( 'limit' ),
|
|
||||||
);
|
|
||||||
$segmenter = new Segmenter( $query_args, $this->report_columns );
|
|
||||||
$totals[0]['segments'] = $segmenter->get_totals_segments( $totals_query, $table_name );
|
|
||||||
|
|
||||||
if ( null === $totals ) {
|
|
||||||
return new \WP_Error( 'woocommerce_analytics_products_stats_result_failed', __( 'Sorry, fetching revenue data failed.', 'woocommerce' ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->interval_query->add_sql_clause( 'order_by', $this->get_sql_clause( 'order_by' ) );
|
|
||||||
$this->interval_query->add_sql_clause( 'limit', $this->get_sql_clause( 'limit' ) );
|
|
||||||
$this->interval_query->add_sql_clause( 'select', ", MAX({$table_name}.date_created) AS datetime_anchor" );
|
|
||||||
if ( '' !== $selections ) {
|
|
||||||
$this->interval_query->add_sql_clause( 'select', ', ' . $selections );
|
|
||||||
}
|
|
||||||
|
|
||||||
$intervals = $wpdb->get_results(
|
|
||||||
$this->interval_query->get_query_statement(),
|
|
||||||
ARRAY_A
|
|
||||||
); // WPCS: cache ok, DB call ok, unprepared SQL ok.
|
|
||||||
|
|
||||||
if ( null === $intervals ) {
|
|
||||||
return new \WP_Error( 'woocommerce_analytics_products_stats_result_failed', __( 'Sorry, fetching revenue data failed.', 'woocommerce' ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
$totals = (object) $this->cast_numbers( $totals[0] );
|
|
||||||
|
|
||||||
$data = (object) array(
|
|
||||||
'totals' => $totals,
|
|
||||||
'intervals' => $intervals,
|
|
||||||
'total' => $expected_interval_count,
|
|
||||||
'pages' => $total_pages,
|
|
||||||
'page_no' => (int) $query_args['page'],
|
|
||||||
);
|
|
||||||
|
|
||||||
if ( TimeInterval::intervals_missing( $expected_interval_count, $db_interval_count, $params['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'], $params['per_page'], $db_interval_count, $expected_interval_count, $query_args['orderby'], $query_args['order'] );
|
|
||||||
} else {
|
|
||||||
$this->update_interval_boundary_dates( $query_args['after'], $query_args['before'], $query_args['interval'], $data->intervals );
|
|
||||||
}
|
|
||||||
$segmenter->add_intervals_segments( $data, $intervals_query, $table_name );
|
|
||||||
$this->create_interval_subtotals( $data->intervals );
|
|
||||||
|
|
||||||
$this->set_cached_data( $cache_key, $data );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$this->interval_query->add_sql_clause( 'order_by', $this->get_sql_clause( 'order_by' ) );
|
||||||
|
$this->interval_query->add_sql_clause( 'limit', $this->get_sql_clause( 'limit' ) );
|
||||||
|
$this->interval_query->add_sql_clause( 'select', ", MAX({$table_name}.date_created) AS datetime_anchor" );
|
||||||
|
if ( '' !== $selections ) {
|
||||||
|
$this->interval_query->add_sql_clause( 'select', ', ' . $selections );
|
||||||
|
}
|
||||||
|
|
||||||
|
$intervals = $wpdb->get_results(
|
||||||
|
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- cache ok, DB call ok, unprepared SQL ok.
|
||||||
|
$this->interval_query->get_query_statement(),
|
||||||
|
ARRAY_A
|
||||||
|
);
|
||||||
|
|
||||||
|
if ( null === $intervals ) {
|
||||||
|
return new \WP_Error( 'woocommerce_analytics_products_stats_result_failed', __( 'Sorry, fetching revenue data failed.', 'woocommerce' ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
$totals = (object) $this->cast_numbers( $totals[0] );
|
||||||
|
|
||||||
|
$data->totals = $totals;
|
||||||
|
$data->intervals = $intervals;
|
||||||
|
|
||||||
|
if ( TimeInterval::intervals_missing( $expected_interval_count, $db_interval_count, $params['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'], $params['per_page'], $db_interval_count, $expected_interval_count, $query_args['orderby'], $query_args['order'] );
|
||||||
|
} else {
|
||||||
|
$this->update_interval_boundary_dates( $query_args['after'], $query_args['before'], $query_args['interval'], $data->intervals );
|
||||||
|
}
|
||||||
|
$segmenter->add_intervals_segments( $data, $intervals_query, $table_name );
|
||||||
|
|
||||||
return $data;
|
return $data;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Normalizes order_by clause to match to SQL query.
|
* Normalizes order_by clause to match to SQL query.
|
||||||
*
|
*
|
||||||
|
* @override ProductsDataStore::normalize_order_by()
|
||||||
|
*
|
||||||
* @param string $order_by Order by option requeste by user.
|
* @param string $order_by Order by option requeste by user.
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
|
@ -241,18 +254,4 @@ class DataStore extends ProductsDataStore implements DataStoreInterface {
|
||||||
|
|
||||||
return $order_by;
|
return $order_by;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize query objects.
|
|
||||||
*/
|
|
||||||
protected function initialize_queries() {
|
|
||||||
$this->clear_all_clauses();
|
|
||||||
unset( $this->subquery );
|
|
||||||
$this->total_query = new SqlQuery( $this->context . '_total' );
|
|
||||||
$this->total_query->add_sql_clause( 'from', self::get_db_table_name() );
|
|
||||||
|
|
||||||
$this->interval_query = new SqlQuery( $this->context . '_interval' );
|
|
||||||
$this->interval_query->add_sql_clause( 'from', self::get_db_table_name() );
|
|
||||||
$this->interval_query->add_sql_clause( 'group_by', 'time_interval' );
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,12 +22,16 @@ use Automattic\WooCommerce\Admin\API\Reports\Query as ReportsQuery;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* API\Reports\Products\Stats\Query
|
* API\Reports\Products\Stats\Query
|
||||||
|
*
|
||||||
|
* @deprecated 9.3.0 Products\Stats\Query class is deprecated, please use GenericQuery or \WC_Object_Query instead.
|
||||||
*/
|
*/
|
||||||
class Query extends ReportsQuery {
|
class Query extends ReportsQuery {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Valid fields for Products report.
|
* Valid fields for Products report.
|
||||||
*
|
*
|
||||||
|
* @deprecated 9.3.0 Products\Stats\Query class is deprecated, please use GenericQuery or \WC_Object_Query instead.
|
||||||
|
*
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
protected function get_default_query_vars() {
|
protected function get_default_query_vars() {
|
||||||
|
@ -37,6 +41,8 @@ class Query extends ReportsQuery {
|
||||||
/**
|
/**
|
||||||
* Get product data based on the current query vars.
|
* Get product data based on the current query vars.
|
||||||
*
|
*
|
||||||
|
* @deprecated 9.3.0 Products\Stats\Query class is deprecated, please use GenericQuery or \WC_Object_Query instead.
|
||||||
|
*
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
public function get_data() {
|
public function get_data() {
|
||||||
|
|
|
@ -9,12 +9,15 @@ defined( 'ABSPATH' ) || exit;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Admin\API\Reports\Query
|
* Admin\API\Reports\Query
|
||||||
|
*
|
||||||
|
* @deprecated 9.3.0 Query class is deprecated, please use GenericQuery or \WC_Object_Query instead.
|
||||||
*/
|
*/
|
||||||
abstract class Query extends \WC_Object_Query {
|
abstract class Query extends \WC_Object_Query {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get report data matching the current query vars.
|
* Get report data matching the current query vars.
|
||||||
*
|
*
|
||||||
|
* @deprecated 9.3.0 Query class is deprecated, please use GenericQuery or \WC_Object_Query instead.
|
||||||
|
*
|
||||||
* @return array|object of WC_Product objects
|
* @return array|object of WC_Product objects
|
||||||
*/
|
*/
|
||||||
public function get_data() {
|
public function get_data() {
|
||||||
|
|
|
@ -16,12 +16,15 @@ namespace Automattic\WooCommerce\Admin\API\Reports\Revenue;
|
||||||
|
|
||||||
defined( 'ABSPATH' ) || exit;
|
defined( 'ABSPATH' ) || exit;
|
||||||
|
|
||||||
use Automattic\WooCommerce\Admin\API\Reports\Query as ReportsQuery;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* API\Reports\Revenue\Query
|
* API\Reports\Revenue\Query
|
||||||
|
*
|
||||||
|
* This query uses inconsistent names:
|
||||||
|
* - `report-revenue-stats` data store
|
||||||
|
* - `woocommerce_analytics_revenue_*` filters
|
||||||
|
* So, for backward compatibility, we cannot use GenericQuery.
|
||||||
*/
|
*/
|
||||||
class Query extends ReportsQuery {
|
class Query extends \WC_Object_Query {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Valid fields for Revenue report.
|
* Valid fields for Revenue report.
|
||||||
|
|
|
@ -13,7 +13,6 @@ use Automattic\WooCommerce\Admin\API\Reports\GenericStatsController;
|
||||||
use Automattic\WooCommerce\Admin\API\Reports\Revenue\Query as RevenueQuery;
|
use Automattic\WooCommerce\Admin\API\Reports\Revenue\Query as RevenueQuery;
|
||||||
use Automattic\WooCommerce\Admin\API\Reports\ExportableInterface;
|
use Automattic\WooCommerce\Admin\API\Reports\ExportableInterface;
|
||||||
use Automattic\WooCommerce\Admin\API\Reports\ExportableTraits;
|
use Automattic\WooCommerce\Admin\API\Reports\ExportableTraits;
|
||||||
use Automattic\WooCommerce\Admin\API\Reports\ParameterException;
|
|
||||||
use WP_REST_Request;
|
use WP_REST_Request;
|
||||||
use WP_REST_Response;
|
use WP_REST_Response;
|
||||||
|
|
||||||
|
@ -60,37 +59,16 @@ class Controller extends GenericStatsController implements ExportableInterface {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all reports.
|
* Get data from RevenueQuery.
|
||||||
*
|
*
|
||||||
* @param WP_REST_Request $request Request data.
|
* @override GenericController::get_datastore_data()
|
||||||
* @return WP_REST_Response|WP_Error
|
*
|
||||||
|
* @param array $query_args Query arguments.
|
||||||
|
* @return mixed Results from the data store.
|
||||||
*/
|
*/
|
||||||
public function get_items( $request ) {
|
protected function get_datastore_data( $query_args = array() ) {
|
||||||
$query_args = $this->prepare_reports_query( $request );
|
$query = new RevenueQuery( $query_args );
|
||||||
$reports_revenue = new RevenueQuery( $query_args );
|
return $query->get_data();
|
||||||
try {
|
|
||||||
$report_data = $reports_revenue->get_data();
|
|
||||||
} catch ( ParameterException $e ) {
|
|
||||||
return new \WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
$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 );
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->add_pagination_headers(
|
|
||||||
$request,
|
|
||||||
$out_data,
|
|
||||||
(int) $report_data->total,
|
|
||||||
(int) $report_data->page_no,
|
|
||||||
(int) $report_data->pages
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -112,9 +90,9 @@ class Controller extends GenericStatsController implements ExportableInterface {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prepare a report object for serialization.
|
* Prepare a report data item for serialization.
|
||||||
*
|
*
|
||||||
* @param array $report Report data.
|
* @param array $report Report data item as returned from Data Store.
|
||||||
* @param WP_REST_Request $request Request object.
|
* @param WP_REST_Request $request Request object.
|
||||||
* @return WP_REST_Response
|
* @return WP_REST_Response
|
||||||
*/
|
*/
|
||||||
|
@ -279,6 +257,7 @@ class Controller extends GenericStatsController implements ExportableInterface {
|
||||||
),
|
),
|
||||||
'validate_callback' => 'rest_validate_request_arg',
|
'validate_callback' => 'rest_validate_request_arg',
|
||||||
);
|
);
|
||||||
|
unset( $params['fields'] );
|
||||||
|
|
||||||
return $params;
|
return $params;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,120 @@
|
||||||
|
<?php
|
||||||
|
declare( strict_types = 1);
|
||||||
|
|
||||||
|
namespace Automattic\WooCommerce\Admin\API\Reports;
|
||||||
|
|
||||||
|
// Exit if accessed directly.
|
||||||
|
if ( ! defined( 'ABSPATH' ) ) {
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
use Automattic\WooCommerce\Admin\API\Reports\SqlQuery;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Trait to contain *stats-specific methods for data stores.
|
||||||
|
*
|
||||||
|
* It does preliminary intervals & page calculations
|
||||||
|
* and prepares intervals & totals data structure by implementing the `get_noncached_data()` method.
|
||||||
|
* So, this time, you'll need to prepare `get_noncached_stats_data()` which will be called only if
|
||||||
|
* the requested page is within the date range.
|
||||||
|
*
|
||||||
|
* The trait also exposes the `initialize_queries()` method to initialize the interval and total queries.
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
* <pre><code class="language-php">class MyStatsDataStore extends DataStore implements DataStoreInterface {
|
||||||
|
* // Use the trait.
|
||||||
|
* use StatsDataStoreTrait;
|
||||||
|
* // Provide all the necessary properties and methods for a regular DataStore.
|
||||||
|
* // ...
|
||||||
|
* /**
|
||||||
|
* * Return your results with the help of the interval & total methods and queries.
|
||||||
|
* * @return stdClass|WP_Error $data filled with your results.
|
||||||
|
* */
|
||||||
|
* public function get_noncached_stats_data( $query_args, $params, &$data, $expected_interval_count ) {
|
||||||
|
* $this->initialize_queries();
|
||||||
|
* // Do your magic ...
|
||||||
|
* // ... with a help of things like:
|
||||||
|
* $this->update_intervals_sql_params( $query_args, $db_interval_count, $expected_interval_count, $table_name );
|
||||||
|
* $this->total_query->add_sql_clause( 'where_time', $this->get_sql_clause( 'where_time' ) );
|
||||||
|
*
|
||||||
|
* $totals = $wpdb->get_results(
|
||||||
|
* $this->total_query->get_query_statement(),
|
||||||
|
* ARRAY_A
|
||||||
|
* );
|
||||||
|
*
|
||||||
|
* $intervals = $wpdb->get_results(
|
||||||
|
* $this->interval_query->get_query_statement(),
|
||||||
|
* ARRAY_A
|
||||||
|
* );
|
||||||
|
*
|
||||||
|
* $data->totals = (object) $this->cast_numbers( $totals[0] );
|
||||||
|
* $data->intervals = $intervals;
|
||||||
|
*
|
||||||
|
* if ( TimeInterval::intervals_missing( $expected_interval_count, $db_interval_count, $params['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'], $params['per_page'], $db_interval_count, $expected_interval_count, $query_args['orderby'], $query_args['order'] );
|
||||||
|
* } else {
|
||||||
|
* $this->update_interval_boundary_dates( $query_args['after'], $query_args['before'], $query_args['interval'], $data->intervals );
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* return $data;
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* </code></pre>
|
||||||
|
*
|
||||||
|
* @see DataStore
|
||||||
|
*/
|
||||||
|
trait StatsDataStoreTrait {
|
||||||
|
/**
|
||||||
|
* Initialize query objects.
|
||||||
|
*/
|
||||||
|
protected function initialize_queries() {
|
||||||
|
$this->clear_all_clauses();
|
||||||
|
unset( $this->subquery );
|
||||||
|
$table_name = self::get_db_table_name();
|
||||||
|
|
||||||
|
$this->total_query = new SqlQuery( $this->context . '_total' );
|
||||||
|
$this->total_query->add_sql_clause( 'from', $table_name );
|
||||||
|
|
||||||
|
$this->interval_query = new SqlQuery( $this->context . '_interval' );
|
||||||
|
$this->interval_query->add_sql_clause( 'from', $table_name );
|
||||||
|
$this->interval_query->add_sql_clause( 'group_by', 'time_interval' );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the stats report data based on normalized parameters.
|
||||||
|
* Prepares the basic intervals and object structure
|
||||||
|
* Will be called by `get_data` if there is no data in cache.
|
||||||
|
* Will call `get_noncached_stats_data` to fetch the actual data.
|
||||||
|
*
|
||||||
|
* @see get_data
|
||||||
|
* @param array $query_args Query parameters.
|
||||||
|
* @return stdClass|WP_Error Data object, or error.
|
||||||
|
*/
|
||||||
|
public function get_noncached_data( $query_args ) {
|
||||||
|
$params = $this->get_limit_params( $query_args );
|
||||||
|
$expected_interval_count = TimeInterval::intervals_between( $query_args['after'], $query_args['before'], $query_args['interval'] );
|
||||||
|
$total_pages = (int) ceil( $expected_interval_count / $params['per_page'] );
|
||||||
|
|
||||||
|
// Default, empty data object.
|
||||||
|
$data = (object) array(
|
||||||
|
'totals' => null,
|
||||||
|
'intervals' => array(),
|
||||||
|
'total' => $expected_interval_count,
|
||||||
|
'pages' => $total_pages,
|
||||||
|
'page_no' => (int) $query_args['page'],
|
||||||
|
);
|
||||||
|
// If the requested page is out off range, return the deault empty object.
|
||||||
|
if ( $query_args['page'] >= 1 && $query_args['page'] <= $total_pages ) {
|
||||||
|
// Fetch the actual data.
|
||||||
|
$data = $this->get_noncached_stats_data( $query_args, $params, $data, $expected_interval_count );
|
||||||
|
|
||||||
|
if ( ! is_wp_error( $data ) && is_array( $data->intervals ) ) {
|
||||||
|
$this->create_interval_subtotals( $data->intervals );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
}
|
|
@ -276,9 +276,9 @@ class Controller extends GenericController implements ExportableInterface {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prepare a report object for serialization.
|
* Prepare a report data item for serialization.
|
||||||
*
|
*
|
||||||
* @param WC_Product $product Report data.
|
* @param WC_Product $product Report data item as returned from Data Store.
|
||||||
* @param WP_REST_Request $request Request object.
|
* @param WP_REST_Request $request Request object.
|
||||||
* @return WP_REST_Response
|
* @return WP_REST_Response
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -47,9 +47,9 @@ class Controller extends \WC_REST_Reports_Controller {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prepare a report object for serialization.
|
* Prepare a report data item for serialization.
|
||||||
*
|
*
|
||||||
* @param WC_Product $report Report data.
|
* @param WC_Product $report Report data item as returned from Data Store.
|
||||||
* @param WP_REST_Request $request Request object.
|
* @param WP_REST_Request $request Request object.
|
||||||
* @return WP_REST_Response
|
* @return WP_REST_Response
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -18,6 +18,8 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||||
/**
|
/**
|
||||||
* Get stock counts for the whole store.
|
* Get stock counts for the whole store.
|
||||||
*
|
*
|
||||||
|
* @override ReportsDataStore::get_data()
|
||||||
|
*
|
||||||
* @param array $query Not used for the stock stats data store, but needed for the interface.
|
* @param array $query Not used for the stock stats data store, but needed for the interface.
|
||||||
* @return array Array of counts.
|
* @return array Array of counts.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -10,12 +10,11 @@ namespace Automattic\WooCommerce\Admin\API\Reports\Stock\Stats;
|
||||||
|
|
||||||
defined( 'ABSPATH' ) || exit;
|
defined( 'ABSPATH' ) || exit;
|
||||||
|
|
||||||
use Automattic\WooCommerce\Admin\API\Reports\Query as ReportsQuery;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* API\Reports\Stock\Stats\Query
|
* API\Reports\Stock\Stats\Query
|
||||||
|
* This query takes no arguments, so we do not inherit from GenericQuery.
|
||||||
*/
|
*/
|
||||||
class Query extends ReportsQuery {
|
class Query extends \WC_Object_Query {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get product data based on the current query vars.
|
* Get product data based on the current query vars.
|
||||||
|
|
|
@ -9,9 +9,10 @@ namespace Automattic\WooCommerce\Admin\API\Reports\Taxes;
|
||||||
|
|
||||||
defined( 'ABSPATH' ) || exit;
|
defined( 'ABSPATH' ) || exit;
|
||||||
|
|
||||||
use Automattic\WooCommerce\Admin\API\Reports\GenericController;
|
|
||||||
use Automattic\WooCommerce\Admin\API\Reports\ExportableInterface;
|
use Automattic\WooCommerce\Admin\API\Reports\ExportableInterface;
|
||||||
use Automattic\WooCommerce\Admin\API\Reports\ExportableTraits;
|
use Automattic\WooCommerce\Admin\API\Reports\ExportableTraits;
|
||||||
|
use Automattic\WooCommerce\Admin\API\Reports\GenericController;
|
||||||
|
use Automattic\WooCommerce\Admin\API\Reports\GenericQuery;
|
||||||
use WP_REST_Request;
|
use WP_REST_Request;
|
||||||
use WP_REST_Response;
|
use WP_REST_Response;
|
||||||
|
|
||||||
|
@ -34,6 +35,19 @@ class Controller extends GenericController implements ExportableInterface {
|
||||||
*/
|
*/
|
||||||
protected $rest_base = 'reports/taxes';
|
protected $rest_base = 'reports/taxes';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get data from `'taxes'` Query.
|
||||||
|
*
|
||||||
|
* @override GenericController::get_datastore_data()
|
||||||
|
*
|
||||||
|
* @param array $query_args Query arguments.
|
||||||
|
* @return mixed Results from the data store.
|
||||||
|
*/
|
||||||
|
protected function get_datastore_data( $query_args = array() ) {
|
||||||
|
$query = new GenericQuery( $query_args, 'taxes' );
|
||||||
|
return $query->get_data();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Maps query arguments from the REST request.
|
* Maps query arguments from the REST request.
|
||||||
*
|
*
|
||||||
|
@ -55,41 +69,17 @@ class Controller extends GenericController implements ExportableInterface {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all reports.
|
* Prepare a report data item for serialization.
|
||||||
*
|
*
|
||||||
* @param WP_REST_Request $request Request data.
|
* @param mixed $report Report data item as returned from Data Store.
|
||||||
* @return array|WP_Error
|
|
||||||
*/
|
|
||||||
public function get_items( $request ) {
|
|
||||||
$query_args = $this->prepare_reports_query( $request );
|
|
||||||
$taxes_query = new Query( $query_args );
|
|
||||||
$report_data = $taxes_query->get_data();
|
|
||||||
|
|
||||||
$data = array();
|
|
||||||
|
|
||||||
foreach ( $report_data->data as $tax_data ) {
|
|
||||||
$item = $this->prepare_item_for_response( (object) $tax_data, $request );
|
|
||||||
$data[] = $this->prepare_response_for_collection( $item );
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->add_pagination_headers(
|
|
||||||
$request,
|
|
||||||
$data,
|
|
||||||
(int) $report_data->total,
|
|
||||||
(int) $report_data->page_no,
|
|
||||||
(int) $report_data->pages
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Prepare a report object for serialization.
|
|
||||||
*
|
|
||||||
* @param stdClass $report Report data.
|
|
||||||
* @param WP_REST_Request $request Request object.
|
* @param WP_REST_Request $request Request object.
|
||||||
* @return WP_REST_Response
|
* @return WP_REST_Response
|
||||||
*/
|
*/
|
||||||
public function prepare_item_for_response( $report, $request ) {
|
public function prepare_item_for_response( $report, $request ) {
|
||||||
$response = parent::prepare_item_for_response( $report, $request );
|
$response = parent::prepare_item_for_response( $report, $request );
|
||||||
|
|
||||||
|
// Map to `object` for backwards compatibility.
|
||||||
|
$report = (object) $report;
|
||||||
$response->add_links( $this->prepare_links( $report ) );
|
$response->add_links( $this->prepare_links( $report ) );
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -21,6 +21,8 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||||
/**
|
/**
|
||||||
* Table used to get the data.
|
* Table used to get the data.
|
||||||
*
|
*
|
||||||
|
* @override ReportsDataStore::$table_name
|
||||||
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
protected static $table_name = 'wc_order_tax_lookup';
|
protected static $table_name = 'wc_order_tax_lookup';
|
||||||
|
@ -28,6 +30,8 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||||
/**
|
/**
|
||||||
* Cache identifier.
|
* Cache identifier.
|
||||||
*
|
*
|
||||||
|
* @override ReportsDataStore::$cache_key
|
||||||
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
protected $cache_key = 'taxes';
|
protected $cache_key = 'taxes';
|
||||||
|
@ -35,6 +39,8 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||||
/**
|
/**
|
||||||
* Mapping columns to data type to return correct response types.
|
* Mapping columns to data type to return correct response types.
|
||||||
*
|
*
|
||||||
|
* @override ReportsDataStore::$column_types
|
||||||
|
*
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
protected $column_types = array(
|
protected $column_types = array(
|
||||||
|
@ -53,12 +59,16 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||||
/**
|
/**
|
||||||
* Data store context used to pass to filters.
|
* Data store context used to pass to filters.
|
||||||
*
|
*
|
||||||
|
* @override ReportsDataStore::$context
|
||||||
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
protected $context = 'taxes';
|
protected $context = 'taxes';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Assign report columns once full table name has been assigned.
|
* Assign report columns once full table name has been assigned.
|
||||||
|
*
|
||||||
|
* @override ReportsDataStore::assign_report_columns()
|
||||||
*/
|
*/
|
||||||
protected function assign_report_columns() {
|
protected function assign_report_columns() {
|
||||||
global $wpdb;
|
global $wpdb;
|
||||||
|
@ -138,100 +148,97 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the report data based on parameters supplied by the user.
|
* Get the default query arguments to be used by get_data().
|
||||||
|
* These defaults are only partially applied when used via REST API, as that has its own defaults.
|
||||||
*
|
*
|
||||||
* @param array $query_args Query parameters.
|
* @override ReportsDataStore::get_default_query_vars()
|
||||||
* @return stdClass|WP_Error Data.
|
*
|
||||||
|
* @return array Query parameters.
|
||||||
*/
|
*/
|
||||||
public function get_data( $query_args ) {
|
public function get_default_query_vars() {
|
||||||
|
$defaults = parent::get_default_query_vars();
|
||||||
|
$defaults['orderby'] = 'tax_rate_id';
|
||||||
|
$defaults['taxes'] = array();
|
||||||
|
|
||||||
|
return $defaults;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the report data based on normalized parameters.
|
||||||
|
* Will be called by `get_data` if there is no data in cache.
|
||||||
|
*
|
||||||
|
* @override ReportsDataStore::get_noncached_data()
|
||||||
|
*
|
||||||
|
* @see get_data
|
||||||
|
* @param array $query_args Query parameters.
|
||||||
|
* @return stdClass|WP_Error Data object `{ totals: *, intervals: array, total: int, pages: int, page_no: int }`, or error.
|
||||||
|
*/
|
||||||
|
public function get_noncached_data( $query_args ) {
|
||||||
global $wpdb;
|
global $wpdb;
|
||||||
|
|
||||||
// These defaults are only partially applied when used via REST API, as that has its own defaults.
|
$this->initialize_queries();
|
||||||
$defaults = array(
|
|
||||||
'per_page' => get_option( 'posts_per_page' ),
|
$data = (object) array(
|
||||||
'page' => 1,
|
'data' => array(),
|
||||||
'order' => 'DESC',
|
'total' => 0,
|
||||||
'orderby' => 'tax_rate_id',
|
'pages' => 0,
|
||||||
'before' => TimeInterval::default_before(),
|
'page_no' => 0,
|
||||||
'after' => TimeInterval::default_after(),
|
|
||||||
'fields' => '*',
|
|
||||||
'taxes' => array(),
|
|
||||||
);
|
);
|
||||||
$query_args = wp_parse_args( $query_args, $defaults );
|
|
||||||
$this->normalize_timezones( $query_args, $defaults );
|
|
||||||
|
|
||||||
/*
|
$this->add_sql_query_params( $query_args );
|
||||||
* We need to get the cache key here because
|
$params = $this->get_limit_params( $query_args );
|
||||||
* parent::update_intervals_sql_params() modifies $query_args.
|
|
||||||
*/
|
|
||||||
$cache_key = $this->get_cache_key( $query_args );
|
|
||||||
$data = $this->get_cached_data( $cache_key );
|
|
||||||
|
|
||||||
if ( false === $data ) {
|
if ( isset( $query_args['taxes'] ) && is_array( $query_args['taxes'] ) && ! empty( $query_args['taxes'] ) ) {
|
||||||
$this->initialize_queries();
|
$total_results = count( $query_args['taxes'] );
|
||||||
|
$total_pages = (int) ceil( $total_results / $params['per_page'] );
|
||||||
$data = (object) array(
|
} else {
|
||||||
'data' => array(),
|
$db_records_count = (int) $wpdb->get_var(
|
||||||
'total' => 0,
|
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- cache ok, DB call ok, unprepared SQL ok.
|
||||||
'pages' => 0,
|
"SELECT COUNT(*) FROM ( {$this->subquery->get_query_statement()} ) AS tt"
|
||||||
'page_no' => 0,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
$this->add_sql_query_params( $query_args );
|
$total_results = $db_records_count;
|
||||||
$params = $this->get_limit_params( $query_args );
|
$total_pages = (int) ceil( $db_records_count / $params['per_page'] );
|
||||||
|
|
||||||
if ( isset( $query_args['taxes'] ) && is_array( $query_args['taxes'] ) && ! empty( $query_args['taxes'] ) ) {
|
if ( $query_args['page'] < 1 || $query_args['page'] > $total_pages ) {
|
||||||
$total_results = count( $query_args['taxes'] );
|
|
||||||
$total_pages = (int) ceil( $total_results / $params['per_page'] );
|
|
||||||
} else {
|
|
||||||
$db_records_count = (int) $wpdb->get_var(
|
|
||||||
"SELECT COUNT(*) FROM (
|
|
||||||
{$this->subquery->get_query_statement()}
|
|
||||||
) AS tt"
|
|
||||||
); // WPCS: cache ok, DB call ok, unprepared SQL ok.
|
|
||||||
|
|
||||||
$total_results = $db_records_count;
|
|
||||||
$total_pages = (int) ceil( $db_records_count / $params['per_page'] );
|
|
||||||
|
|
||||||
if ( $query_args['page'] < 1 || $query_args['page'] > $total_pages ) {
|
|
||||||
return $data;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->subquery->clear_sql_clause( 'select' );
|
|
||||||
$this->subquery->add_sql_clause( 'select', $this->selected_columns( $query_args ) );
|
|
||||||
$this->subquery->add_sql_clause( 'group_by', ", {$wpdb->prefix}woocommerce_order_items.order_item_name, {$wpdb->prefix}woocommerce_order_itemmeta.meta_value" );
|
|
||||||
$this->subquery->add_sql_clause( 'order_by', $this->get_sql_clause( 'order_by' ) );
|
|
||||||
$this->subquery->add_sql_clause( 'limit', $this->get_sql_clause( 'limit' ) );
|
|
||||||
|
|
||||||
$taxes_query = $this->subquery->get_query_statement();
|
|
||||||
|
|
||||||
$tax_data = $wpdb->get_results(
|
|
||||||
$taxes_query,
|
|
||||||
ARRAY_A
|
|
||||||
); // WPCS: cache ok, DB call ok, unprepared SQL ok.
|
|
||||||
|
|
||||||
if ( null === $tax_data ) {
|
|
||||||
return $data;
|
return $data;
|
||||||
}
|
}
|
||||||
|
|
||||||
$tax_data = array_map( array( $this, 'cast_numbers' ), $tax_data );
|
|
||||||
$data = (object) array(
|
|
||||||
'data' => $tax_data,
|
|
||||||
'total' => $total_results,
|
|
||||||
'pages' => $total_pages,
|
|
||||||
'page_no' => (int) $query_args['page'],
|
|
||||||
);
|
|
||||||
|
|
||||||
$this->set_cached_data( $cache_key, $data );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$this->subquery->clear_sql_clause( 'select' );
|
||||||
|
$this->subquery->add_sql_clause( 'select', $this->selected_columns( $query_args ) );
|
||||||
|
$this->subquery->add_sql_clause( 'group_by', ", {$wpdb->prefix}woocommerce_order_items.order_item_name, {$wpdb->prefix}woocommerce_order_itemmeta.meta_value" );
|
||||||
|
$this->subquery->add_sql_clause( 'order_by', $this->get_sql_clause( 'order_by' ) );
|
||||||
|
$this->subquery->add_sql_clause( 'limit', $this->get_sql_clause( 'limit' ) );
|
||||||
|
|
||||||
|
$taxes_query = $this->subquery->get_query_statement();
|
||||||
|
|
||||||
|
$tax_data = $wpdb->get_results(
|
||||||
|
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- cache ok, DB call ok, unprepared SQL ok.
|
||||||
|
$taxes_query,
|
||||||
|
ARRAY_A
|
||||||
|
);
|
||||||
|
|
||||||
|
if ( null === $tax_data ) {
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
$tax_data = array_map( array( $this, 'cast_numbers' ), $tax_data );
|
||||||
|
$data = (object) array(
|
||||||
|
'data' => $tax_data,
|
||||||
|
'total' => $total_results,
|
||||||
|
'pages' => $total_pages,
|
||||||
|
'page_no' => (int) $query_args['page'],
|
||||||
|
);
|
||||||
|
|
||||||
return $data;
|
return $data;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Maps ordering specified by the user to columns in the database/fields in the data.
|
* Maps ordering specified by the user to columns in the database/fields in the data.
|
||||||
*
|
*
|
||||||
|
* @override ReportsDataStore::normalize_order_by()
|
||||||
|
*
|
||||||
* @param string $order_by Sorting criterion.
|
* @param string $order_by Sorting criterion.
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -21,12 +21,16 @@ use Automattic\WooCommerce\Admin\API\Reports\Query as ReportsQuery;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* API\Reports\Taxes\Query
|
* API\Reports\Taxes\Query
|
||||||
|
*
|
||||||
|
* @deprecated 9.3.0 Taxes\Query class is deprecated, please use GenericQuery or \WC_Object_Query instead.
|
||||||
*/
|
*/
|
||||||
class Query extends ReportsQuery {
|
class Query extends ReportsQuery {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Valid fields for Taxes report.
|
* Valid fields for Taxes report.
|
||||||
*
|
*
|
||||||
|
* @deprecated 9.3.0 Taxes\Query class is deprecated, please use GenericQuery or \WC_Object_Query instead.
|
||||||
|
*
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
protected function get_default_query_vars() {
|
protected function get_default_query_vars() {
|
||||||
|
@ -36,6 +40,8 @@ class Query extends ReportsQuery {
|
||||||
/**
|
/**
|
||||||
* Get product data based on the current query vars.
|
* Get product data based on the current query vars.
|
||||||
*
|
*
|
||||||
|
* @deprecated 9.3.0 Taxes\Query class is deprecated, please use GenericQuery or \WC_Object_Query instead.
|
||||||
|
*
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
public function get_data() {
|
public function get_data() {
|
||||||
|
|
|
@ -9,6 +9,7 @@ namespace Automattic\WooCommerce\Admin\API\Reports\Taxes\Stats;
|
||||||
|
|
||||||
defined( 'ABSPATH' ) || exit;
|
defined( 'ABSPATH' ) || exit;
|
||||||
|
|
||||||
|
use Automattic\WooCommerce\Admin\API\Reports\GenericQuery;
|
||||||
use Automattic\WooCommerce\Admin\API\Reports\GenericStatsController;
|
use Automattic\WooCommerce\Admin\API\Reports\GenericStatsController;
|
||||||
use WP_REST_Request;
|
use WP_REST_Request;
|
||||||
use WP_REST_Response;
|
use WP_REST_Response;
|
||||||
|
@ -83,47 +84,30 @@ class Controller extends GenericStatsController {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all reports.
|
* Get data from `'taxes-stats'` Query.
|
||||||
*
|
*
|
||||||
* @param WP_REST_Request $request Request data.
|
* @override GenericController::get_datastore_data()
|
||||||
* @return array|WP_Error
|
*
|
||||||
|
* @param array $query_args Query arguments.
|
||||||
|
* @return mixed Results from the data store.
|
||||||
*/
|
*/
|
||||||
public function get_items( $request ) {
|
protected function get_datastore_data( $query_args = array() ) {
|
||||||
$query_args = $this->prepare_reports_query( $request );
|
$query = new GenericQuery( $query_args, 'taxes-stats' );
|
||||||
$taxes_query = new Query( $query_args );
|
return $query->get_data();
|
||||||
$report_data = $taxes_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( (object) $interval_data, $request );
|
|
||||||
$out_data['intervals'][] = $this->prepare_response_for_collection( $item );
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->add_pagination_headers(
|
|
||||||
$request,
|
|
||||||
$out_data,
|
|
||||||
(int) $report_data->total,
|
|
||||||
(int) $report_data->page_no,
|
|
||||||
(int) $report_data->pages
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prepare a report object for serialization.
|
* Prepare a report data item for serialization.
|
||||||
*
|
*
|
||||||
* @param stdClass $report Report data.
|
* @param mixed $report Report data item as returned from Data Store.
|
||||||
* @param WP_REST_Request $request Request object.
|
* @param WP_REST_Request $request Request object.
|
||||||
* @return WP_REST_Response
|
* @return WP_REST_Response
|
||||||
*/
|
*/
|
||||||
public function prepare_item_for_response( $report, $request ) {
|
public function prepare_item_for_response( $report, $request ) {
|
||||||
$data = get_object_vars( $report );
|
$response = parent::prepare_item_for_response( $report, $request );
|
||||||
|
|
||||||
$response = parent::prepare_item_for_response( $data, $request );
|
|
||||||
|
|
||||||
|
// Map to `object` for backwards compatibility.
|
||||||
|
$report = (object) $report;
|
||||||
/**
|
/**
|
||||||
* Filter a report returned from the API.
|
* Filter a report returned from the API.
|
||||||
*
|
*
|
||||||
|
@ -226,15 +210,6 @@ class Controller extends GenericStatsController {
|
||||||
),
|
),
|
||||||
'validate_callback' => 'rest_validate_request_arg',
|
'validate_callback' => 'rest_validate_request_arg',
|
||||||
);
|
);
|
||||||
$params['fields'] = array(
|
|
||||||
'description' => __( 'Limit stats fields to the specified items.', 'woocommerce' ),
|
|
||||||
'type' => 'array',
|
|
||||||
'sanitize_callback' => 'wp_parse_slug_list',
|
|
||||||
'validate_callback' => 'rest_validate_request_arg',
|
|
||||||
'items' => array(
|
|
||||||
'type' => 'string',
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
return $params;
|
return $params;
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,16 +10,19 @@ defined( 'ABSPATH' ) || exit;
|
||||||
use Automattic\WooCommerce\Admin\API\Reports\DataStore as ReportsDataStore;
|
use Automattic\WooCommerce\Admin\API\Reports\DataStore as ReportsDataStore;
|
||||||
use Automattic\WooCommerce\Admin\API\Reports\DataStoreInterface;
|
use Automattic\WooCommerce\Admin\API\Reports\DataStoreInterface;
|
||||||
use Automattic\WooCommerce\Admin\API\Reports\TimeInterval;
|
use Automattic\WooCommerce\Admin\API\Reports\TimeInterval;
|
||||||
use Automattic\WooCommerce\Admin\API\Reports\SqlQuery;
|
use Automattic\WooCommerce\Admin\API\Reports\StatsDataStoreTrait;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* API\Reports\Taxes\Stats\DataStore.
|
* API\Reports\Taxes\Stats\DataStore.
|
||||||
*/
|
*/
|
||||||
class DataStore extends ReportsDataStore implements DataStoreInterface {
|
class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||||
|
use StatsDataStoreTrait;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Table used to get the data.
|
* Table used to get the data.
|
||||||
*
|
*
|
||||||
|
* @override ReportsDataStore::$table_name
|
||||||
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
protected static $table_name = 'wc_order_tax_lookup';
|
protected static $table_name = 'wc_order_tax_lookup';
|
||||||
|
@ -27,6 +30,8 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||||
/**
|
/**
|
||||||
* Cache identifier.
|
* Cache identifier.
|
||||||
*
|
*
|
||||||
|
* @override ReportsDataStore::$cache_key
|
||||||
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
protected $cache_key = 'taxes_stats';
|
protected $cache_key = 'taxes_stats';
|
||||||
|
@ -34,6 +39,8 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||||
/**
|
/**
|
||||||
* Mapping columns to data type to return correct response types.
|
* Mapping columns to data type to return correct response types.
|
||||||
*
|
*
|
||||||
|
* @override ReportsDataStore::$column_types
|
||||||
|
*
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
protected $column_types = array(
|
protected $column_types = array(
|
||||||
|
@ -47,12 +54,16 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||||
/**
|
/**
|
||||||
* Data store context used to pass to filters.
|
* Data store context used to pass to filters.
|
||||||
*
|
*
|
||||||
|
* @override ReportsDataStore::$context
|
||||||
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
protected $context = 'taxes_stats';
|
protected $context = 'taxes_stats';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Assign report columns once full table name has been assigned.
|
* Assign report columns once full table name has been assigned.
|
||||||
|
*
|
||||||
|
* @override ReportsDataStore::assign_report_columns()
|
||||||
*/
|
*/
|
||||||
protected function assign_report_columns() {
|
protected function assign_report_columns() {
|
||||||
$table_name = self::get_db_table_name();
|
$table_name = self::get_db_table_name();
|
||||||
|
@ -126,146 +137,116 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the report data based on parameters supplied by the user.
|
* Get the default query arguments to be used by get_data().
|
||||||
|
* These defaults are only partially applied when used via REST API, as that has its own defaults.
|
||||||
*
|
*
|
||||||
* @param array $query_args Query parameters.
|
* @override ReportsDataStore::get_default_query_vars()
|
||||||
* @return stdClass|WP_Error Data.
|
*
|
||||||
|
* @return array Query parameters.
|
||||||
*/
|
*/
|
||||||
public function get_data( $query_args ) {
|
public function get_default_query_vars() {
|
||||||
|
$defaults = parent::get_default_query_vars();
|
||||||
|
$defaults['orderby'] = 'tax_rate_id';
|
||||||
|
$defaults['taxes'] = array();
|
||||||
|
|
||||||
|
return $defaults;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the report data based on normalized parameters.
|
||||||
|
* Will be called by `get_data` if there is no data in cache.
|
||||||
|
*
|
||||||
|
* @override ReportsDataStore::get_noncached_data()
|
||||||
|
*
|
||||||
|
* @see get_data
|
||||||
|
* @see get_noncached_stats_data
|
||||||
|
* @param array $query_args Query parameters.
|
||||||
|
* @param array $params Query limit parameters.
|
||||||
|
* @param stdClass $data Reference to the data object to fill.
|
||||||
|
* @param int $expected_interval_count Number of expected intervals.
|
||||||
|
* @return stdClass|WP_Error Data object `{ totals: *, intervals: array, total: int, pages: int, page_no: int }`, or error.
|
||||||
|
*/
|
||||||
|
public function get_noncached_stats_data( $query_args, $params, &$data, $expected_interval_count ) {
|
||||||
global $wpdb;
|
global $wpdb;
|
||||||
|
|
||||||
$table_name = self::get_db_table_name();
|
$table_name = self::get_db_table_name();
|
||||||
|
|
||||||
// These defaults are only partially applied when used via REST API, as that has its own defaults.
|
$this->initialize_queries();
|
||||||
$defaults = array(
|
|
||||||
'per_page' => get_option( 'posts_per_page' ),
|
$selections = $this->selected_columns( $query_args );
|
||||||
'page' => 1,
|
$order_stats_join = "JOIN {$wpdb->prefix}wc_order_stats ON {$table_name}.order_id = {$wpdb->prefix}wc_order_stats.order_id";
|
||||||
'order' => 'DESC',
|
$this->update_sql_query_params( $query_args );
|
||||||
'orderby' => 'tax_rate_id',
|
$this->interval_query->add_sql_clause( 'join', $order_stats_join );
|
||||||
'before' => TimeInterval::default_before(),
|
|
||||||
'after' => TimeInterval::default_after(),
|
$db_intervals = $wpdb->get_col(
|
||||||
'fields' => '*',
|
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- cache ok, DB call ok, unprepared SQL ok.
|
||||||
'taxes' => array(),
|
$this->interval_query->get_query_statement()
|
||||||
);
|
);
|
||||||
$query_args = wp_parse_args( $query_args, $defaults );
|
$db_interval_count = count( $db_intervals );
|
||||||
$this->normalize_timezones( $query_args, $defaults );
|
|
||||||
|
|
||||||
/*
|
$this->total_query->add_sql_clause( 'select', $selections );
|
||||||
* We need to get the cache key here because
|
$this->total_query->add_sql_clause( 'join', $order_stats_join );
|
||||||
* parent::update_intervals_sql_params() modifies $query_args.
|
$this->total_query->add_sql_clause( 'where_time', $this->get_sql_clause( 'where_time' ) );
|
||||||
*/
|
|
||||||
$cache_key = $this->get_cache_key( $query_args );
|
|
||||||
$data = $this->get_cached_data( $cache_key );
|
|
||||||
|
|
||||||
if ( false === $data ) {
|
$totals = $wpdb->get_results(
|
||||||
$this->initialize_queries();
|
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- cache ok, DB call ok, unprepared SQL ok.
|
||||||
|
$this->total_query->get_query_statement(),
|
||||||
|
ARRAY_A
|
||||||
|
);
|
||||||
|
|
||||||
$data = (object) array(
|
if ( null === $totals ) {
|
||||||
'totals' => (object) array(),
|
return new \WP_Error( 'woocommerce_analytics_taxes_stats_result_failed', __( 'Sorry, fetching revenue data failed.', 'woocommerce' ) );
|
||||||
'intervals' => (object) array(),
|
|
||||||
'total' => 0,
|
|
||||||
'pages' => 0,
|
|
||||||
'page_no' => 0,
|
|
||||||
);
|
|
||||||
|
|
||||||
$selections = $this->selected_columns( $query_args );
|
|
||||||
$params = $this->get_limit_params( $query_args );
|
|
||||||
$order_stats_join = "JOIN {$wpdb->prefix}wc_order_stats ON {$table_name}.order_id = {$wpdb->prefix}wc_order_stats.order_id";
|
|
||||||
$this->update_sql_query_params( $query_args );
|
|
||||||
$this->interval_query->add_sql_clause( 'join', $order_stats_join );
|
|
||||||
|
|
||||||
$db_intervals = $wpdb->get_col(
|
|
||||||
$this->interval_query->get_query_statement()
|
|
||||||
); // WPCS: cache ok, DB call ok, unprepared SQL ok.
|
|
||||||
$db_interval_count = count( $db_intervals );
|
|
||||||
$expected_interval_count = TimeInterval::intervals_between( $query_args['after'], $query_args['before'], $query_args['interval'] );
|
|
||||||
$total_pages = (int) ceil( $expected_interval_count / $params['per_page'] );
|
|
||||||
|
|
||||||
if ( $query_args['page'] < 1 || $query_args['page'] > $total_pages ) {
|
|
||||||
return $data;
|
|
||||||
}
|
|
||||||
$this->total_query->add_sql_clause( 'select', $selections );
|
|
||||||
$this->total_query->add_sql_clause( 'join', $order_stats_join );
|
|
||||||
$this->total_query->add_sql_clause( 'where_time', $this->get_sql_clause( 'where_time' ) );
|
|
||||||
|
|
||||||
$totals = $wpdb->get_results(
|
|
||||||
$this->total_query->get_query_statement(),
|
|
||||||
ARRAY_A
|
|
||||||
); // WPCS: cache ok, DB call ok, unprepared SQL ok.
|
|
||||||
|
|
||||||
if ( null === $totals ) {
|
|
||||||
return new \WP_Error( 'woocommerce_analytics_taxes_stats_result_failed', __( 'Sorry, fetching revenue data failed.', 'woocommerce' ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
// @todo remove these assignements when refactoring segmenter classes to use query objects.
|
|
||||||
$totals_query = array(
|
|
||||||
'from_clause' => $this->total_query->get_sql_clause( 'join' ),
|
|
||||||
'where_time_clause' => $this->total_query->get_sql_clause( 'where_time' ),
|
|
||||||
'where_clause' => $this->total_query->get_sql_clause( 'where' ),
|
|
||||||
);
|
|
||||||
$intervals_query = array(
|
|
||||||
'select_clause' => $this->get_sql_clause( 'select' ),
|
|
||||||
'from_clause' => $this->interval_query->get_sql_clause( 'join' ),
|
|
||||||
'where_time_clause' => $this->interval_query->get_sql_clause( 'where_time' ),
|
|
||||||
'where_clause' => $this->interval_query->get_sql_clause( 'where' ),
|
|
||||||
);
|
|
||||||
$segmenter = new Segmenter( $query_args, $this->report_columns );
|
|
||||||
$totals[0]['segments'] = $segmenter->get_totals_segments( $totals_query, $table_name );
|
|
||||||
|
|
||||||
$this->update_intervals_sql_params( $query_args, $db_interval_count, $expected_interval_count, $table_name );
|
|
||||||
|
|
||||||
if ( '' !== $selections ) {
|
|
||||||
$this->interval_query->add_sql_clause( 'select', ', ' . $selections );
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->interval_query->add_sql_clause( 'select', ", MAX({$table_name}.date_created) AS datetime_anchor" );
|
|
||||||
$this->interval_query->add_sql_clause( 'order_by', $this->get_sql_clause( 'order_by' ) );
|
|
||||||
$this->interval_query->add_sql_clause( 'limit', $this->get_sql_clause( 'limit' ) );
|
|
||||||
|
|
||||||
$intervals = $wpdb->get_results(
|
|
||||||
$this->interval_query->get_query_statement(),
|
|
||||||
ARRAY_A
|
|
||||||
); // WPCS: cache ok, DB call ok, unprepared SQL ok.
|
|
||||||
|
|
||||||
if ( null === $intervals ) {
|
|
||||||
return new \WP_Error( 'woocommerce_analytics_taxes_stats_result_failed', __( 'Sorry, fetching tax data failed.', 'woocommerce' ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
$totals = (object) $this->cast_numbers( $totals[0] );
|
|
||||||
|
|
||||||
$data = (object) array(
|
|
||||||
'totals' => $totals,
|
|
||||||
'intervals' => $intervals,
|
|
||||||
'total' => $expected_interval_count,
|
|
||||||
'pages' => $total_pages,
|
|
||||||
'page_no' => (int) $query_args['page'],
|
|
||||||
);
|
|
||||||
|
|
||||||
if ( TimeInterval::intervals_missing( $expected_interval_count, $db_interval_count, $params['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'], $params['per_page'], $db_interval_count, $expected_interval_count, $query_args['orderby'], $query_args['order'] );
|
|
||||||
} else {
|
|
||||||
$this->update_interval_boundary_dates( $query_args['after'], $query_args['before'], $query_args['interval'], $data->intervals );
|
|
||||||
}
|
|
||||||
$segmenter->add_intervals_segments( $data, $intervals_query, $table_name );
|
|
||||||
$this->create_interval_subtotals( $data->intervals );
|
|
||||||
$this->set_cached_data( $cache_key, $data );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// phpcs:ignore Generic.Commenting.Todo.TaskFound
|
||||||
|
// @todo remove these assignements when refactoring segmenter classes to use query objects.
|
||||||
|
$totals_query = array(
|
||||||
|
'from_clause' => $this->total_query->get_sql_clause( 'join' ),
|
||||||
|
'where_time_clause' => $this->total_query->get_sql_clause( 'where_time' ),
|
||||||
|
'where_clause' => $this->total_query->get_sql_clause( 'where' ),
|
||||||
|
);
|
||||||
|
$intervals_query = array(
|
||||||
|
'select_clause' => $this->get_sql_clause( 'select' ),
|
||||||
|
'from_clause' => $this->interval_query->get_sql_clause( 'join' ),
|
||||||
|
'where_time_clause' => $this->interval_query->get_sql_clause( 'where_time' ),
|
||||||
|
'where_clause' => $this->interval_query->get_sql_clause( 'where' ),
|
||||||
|
);
|
||||||
|
$segmenter = new Segmenter( $query_args, $this->report_columns );
|
||||||
|
$totals[0]['segments'] = $segmenter->get_totals_segments( $totals_query, $table_name );
|
||||||
|
|
||||||
|
$this->update_intervals_sql_params( $query_args, $db_interval_count, $expected_interval_count, $table_name );
|
||||||
|
|
||||||
|
if ( '' !== $selections ) {
|
||||||
|
$this->interval_query->add_sql_clause( 'select', ', ' . $selections );
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->interval_query->add_sql_clause( 'select', ", MAX({$table_name}.date_created) AS datetime_anchor" );
|
||||||
|
$this->interval_query->add_sql_clause( 'order_by', $this->get_sql_clause( 'order_by' ) );
|
||||||
|
$this->interval_query->add_sql_clause( 'limit', $this->get_sql_clause( 'limit' ) );
|
||||||
|
|
||||||
|
$intervals = $wpdb->get_results(
|
||||||
|
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- cache ok, DB call ok, unprepared SQL ok.
|
||||||
|
$this->interval_query->get_query_statement(),
|
||||||
|
ARRAY_A
|
||||||
|
);
|
||||||
|
|
||||||
|
if ( null === $intervals ) {
|
||||||
|
return new \WP_Error( 'woocommerce_analytics_taxes_stats_result_failed', __( 'Sorry, fetching tax data failed.', 'woocommerce' ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
$totals = (object) $this->cast_numbers( $totals[0] );
|
||||||
|
|
||||||
|
$data->totals = $totals;
|
||||||
|
$data->intervals = $intervals;
|
||||||
|
|
||||||
|
if ( TimeInterval::intervals_missing( $expected_interval_count, $db_interval_count, $params['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'], $params['per_page'], $db_interval_count, $expected_interval_count, $query_args['orderby'], $query_args['order'] );
|
||||||
|
} else {
|
||||||
|
$this->update_interval_boundary_dates( $query_args['after'], $query_args['before'], $query_args['interval'], $data->intervals );
|
||||||
|
}
|
||||||
|
$segmenter->add_intervals_segments( $data, $intervals_query, $table_name );
|
||||||
return $data;
|
return $data;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize query objects.
|
|
||||||
*/
|
|
||||||
protected function initialize_queries() {
|
|
||||||
$this->clear_all_clauses();
|
|
||||||
unset( $this->subquery );
|
|
||||||
$this->total_query = new SqlQuery( $this->context . '_total' );
|
|
||||||
$this->total_query->add_sql_clause( 'from', self::get_db_table_name() );
|
|
||||||
|
|
||||||
$this->interval_query = new SqlQuery( $this->context . '_interval' );
|
|
||||||
$this->interval_query->add_sql_clause( 'from', self::get_db_table_name() );
|
|
||||||
$this->interval_query->add_sql_clause( 'group_by', 'time_interval' );
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,12 +22,16 @@ use Automattic\WooCommerce\Admin\API\Reports\Query as ReportsQuery;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* API\Reports\Taxes\Stats\Query
|
* API\Reports\Taxes\Stats\Query
|
||||||
|
*
|
||||||
|
* @deprecated 9.3.0 Taxes\Stats\Query class is deprecated, please use GenericQuery or \WC_Object_Query instead.
|
||||||
*/
|
*/
|
||||||
class Query extends ReportsQuery {
|
class Query extends ReportsQuery {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Valid fields for Taxes report.
|
* Valid fields for Taxes report.
|
||||||
*
|
*
|
||||||
|
* @deprecated 9.3.0 Taxes\Stats\Query class is deprecated, please use GenericQuery or \WC_Object_Query instead.
|
||||||
|
*
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
protected function get_default_query_vars() {
|
protected function get_default_query_vars() {
|
||||||
|
@ -37,6 +41,8 @@ class Query extends ReportsQuery {
|
||||||
/**
|
/**
|
||||||
* Get tax stats data based on the current query vars.
|
* Get tax stats data based on the current query vars.
|
||||||
*
|
*
|
||||||
|
* @deprecated 9.3.0 Taxes\Stats\Query class is deprecated, please use GenericQuery or \WC_Object_Query instead.
|
||||||
|
*
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
public function get_data() {
|
public function get_data() {
|
||||||
|
|
|
@ -9,17 +9,24 @@ namespace Automattic\WooCommerce\Admin\API\Reports\Variations;
|
||||||
|
|
||||||
defined( 'ABSPATH' ) || exit;
|
defined( 'ABSPATH' ) || exit;
|
||||||
|
|
||||||
use Automattic\WooCommerce\Admin\API\Reports\Controller as ReportsController;
|
|
||||||
use Automattic\WooCommerce\Admin\API\Reports\ExportableInterface;
|
use Automattic\WooCommerce\Admin\API\Reports\ExportableInterface;
|
||||||
use Automattic\WooCommerce\Admin\API\Reports\ExportableTraits;
|
use Automattic\WooCommerce\Admin\API\Reports\ExportableTraits;
|
||||||
|
use Automattic\WooCommerce\Admin\API\Reports\GenericController;
|
||||||
|
use Automattic\WooCommerce\Admin\API\Reports\GenericQuery;
|
||||||
|
use Automattic\WooCommerce\Admin\API\Reports\OrderAwareControllerTrait;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* REST API Reports products controller class.
|
* REST API Reports products controller class.
|
||||||
*
|
*
|
||||||
* @internal
|
* @internal
|
||||||
* @extends ReportsController
|
* @extends GenericController
|
||||||
*/
|
*/
|
||||||
class Controller extends ReportsController implements ExportableInterface {
|
class Controller extends GenericController implements ExportableInterface {
|
||||||
|
|
||||||
|
// The controller does not use this trait. It's here for API backward compatibility.
|
||||||
|
use OrderAwareControllerTrait;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Exportable traits.
|
* Exportable traits.
|
||||||
*/
|
*/
|
||||||
|
@ -43,13 +50,52 @@ class Controller extends ReportsController implements ExportableInterface {
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get items.
|
* Get data from `'variations'` Query.
|
||||||
*
|
*
|
||||||
* @param WP_REST_Request $request Request data.
|
* @override GenericController::get_datastore_data()
|
||||||
*
|
*
|
||||||
* @return array|WP_Error
|
* @param array $query_args Query arguments.
|
||||||
|
* @return mixed Results from the data store.
|
||||||
*/
|
*/
|
||||||
public function get_items( $request ) {
|
protected function get_datastore_data( $query_args = array() ) {
|
||||||
|
$query = new GenericQuery( $query_args, 'variations' );
|
||||||
|
return $query->get_data();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepare a report data item for serialization.
|
||||||
|
*
|
||||||
|
* @param array $report Report data item as returned from Data Store.
|
||||||
|
* @param WP_REST_Request $request Request object.
|
||||||
|
* @return WP_REST_Response
|
||||||
|
*/
|
||||||
|
public function prepare_item_for_response( $report, $request ) {
|
||||||
|
// Wrap the data in a response object.
|
||||||
|
$response = parent::prepare_item_for_response( $report, $request );
|
||||||
|
|
||||||
|
$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.
|
||||||
|
*
|
||||||
|
* @since 6.5.0
|
||||||
|
*
|
||||||
|
* @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_variations', $response, $report, $request );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps query arguments from the REST request.
|
||||||
|
*
|
||||||
|
* @param array $request Request array.
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
protected function prepare_reports_query( $request ) {
|
||||||
$args = array();
|
$args = array();
|
||||||
/**
|
/**
|
||||||
* Experimental: Filter the list of parameters provided when querying data from the data store.
|
* Experimental: Filter the list of parameters provided when querying data from the data store.
|
||||||
|
@ -57,6 +103,8 @@ class Controller extends ReportsController implements ExportableInterface {
|
||||||
* @ignore
|
* @ignore
|
||||||
*
|
*
|
||||||
* @param array $collection_params List of parameters.
|
* @param array $collection_params List of parameters.
|
||||||
|
*
|
||||||
|
* @since 6.5.0
|
||||||
*/
|
*/
|
||||||
$collection_params = apply_filters(
|
$collection_params = apply_filters(
|
||||||
'experimental_woocommerce_analytics_variations_collection_params',
|
'experimental_woocommerce_analytics_variations_collection_params',
|
||||||
|
@ -72,54 +120,7 @@ class Controller extends ReportsController implements ExportableInterface {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return $args;
|
||||||
$reports = new 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 );
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->add_pagination_headers(
|
|
||||||
$request,
|
|
||||||
$data,
|
|
||||||
(int) $products_data->total,
|
|
||||||
(int) $products_data->page_no,
|
|
||||||
(int) $products_data->pages
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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_variations', $response, $report, $request );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -244,38 +245,15 @@ class Controller extends ReportsController implements ExportableInterface {
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
public function get_collection_params() {
|
public function get_collection_params() {
|
||||||
$params = array();
|
$params = parent::get_collection_params();
|
||||||
$params['context'] = $this->get_context_param( array( 'default' => 'view' ) );
|
$params['orderby']['enum'] = array(
|
||||||
$params['page'] = array(
|
'date',
|
||||||
'description' => __( 'Current page of the collection.', 'woocommerce' ),
|
'net_revenue',
|
||||||
'type' => 'integer',
|
'orders_count',
|
||||||
'default' => 1,
|
'items_sold',
|
||||||
'sanitize_callback' => 'absint',
|
'sku',
|
||||||
'validate_callback' => 'rest_validate_request_arg',
|
|
||||||
'minimum' => 1,
|
|
||||||
);
|
);
|
||||||
$params['per_page'] = array(
|
$params['match'] = 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['match'] = array(
|
|
||||||
'description' => __( 'Indicates whether all the conditions should be true for the resulting set, or if any one of them is sufficient. Match affects the following parameters: status_is, status_is_not, product_includes, product_excludes, coupon_includes, coupon_excludes, customer, categories', 'woocommerce' ),
|
'description' => __( 'Indicates whether all the conditions should be true for the resulting set, or if any one of them is sufficient. Match affects the following parameters: status_is, status_is_not, product_includes, product_excludes, coupon_includes, coupon_excludes, customer, categories', 'woocommerce' ),
|
||||||
'type' => 'string',
|
'type' => 'string',
|
||||||
'default' => 'all',
|
'default' => 'all',
|
||||||
|
@ -285,27 +263,7 @@ class Controller extends ReportsController implements ExportableInterface {
|
||||||
),
|
),
|
||||||
'validate_callback' => 'rest_validate_request_arg',
|
'validate_callback' => 'rest_validate_request_arg',
|
||||||
);
|
);
|
||||||
$params['order'] = array(
|
$params['product_includes'] = 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',
|
|
||||||
'items_sold',
|
|
||||||
'sku',
|
|
||||||
),
|
|
||||||
'validate_callback' => 'rest_validate_request_arg',
|
|
||||||
);
|
|
||||||
$params['product_includes'] = array(
|
|
||||||
'description' => __( 'Limit result set to items that have the specified parent product(s).', 'woocommerce' ),
|
'description' => __( 'Limit result set to items that have the specified parent product(s).', 'woocommerce' ),
|
||||||
'type' => 'array',
|
'type' => 'array',
|
||||||
'items' => array(
|
'items' => array(
|
||||||
|
@ -315,7 +273,7 @@ class Controller extends ReportsController implements ExportableInterface {
|
||||||
'sanitize_callback' => 'wp_parse_id_list',
|
'sanitize_callback' => 'wp_parse_id_list',
|
||||||
'validate_callback' => 'rest_validate_request_arg',
|
'validate_callback' => 'rest_validate_request_arg',
|
||||||
);
|
);
|
||||||
$params['product_excludes'] = array(
|
$params['product_excludes'] = array(
|
||||||
'description' => __( 'Limit result set to items that don\'t have the specified parent product(s).', 'woocommerce' ),
|
'description' => __( 'Limit result set to items that don\'t have the specified parent product(s).', 'woocommerce' ),
|
||||||
'type' => 'array',
|
'type' => 'array',
|
||||||
'items' => array(
|
'items' => array(
|
||||||
|
@ -325,7 +283,7 @@ class Controller extends ReportsController implements ExportableInterface {
|
||||||
'validate_callback' => 'rest_validate_request_arg',
|
'validate_callback' => 'rest_validate_request_arg',
|
||||||
'sanitize_callback' => 'wp_parse_id_list',
|
'sanitize_callback' => 'wp_parse_id_list',
|
||||||
);
|
);
|
||||||
$params['variations'] = array(
|
$params['variations'] = array(
|
||||||
'description' => __( 'Limit result to items with specified variation ids.', 'woocommerce' ),
|
'description' => __( 'Limit result to items with specified variation ids.', 'woocommerce' ),
|
||||||
'type' => 'array',
|
'type' => 'array',
|
||||||
'sanitize_callback' => 'wp_parse_id_list',
|
'sanitize_callback' => 'wp_parse_id_list',
|
||||||
|
@ -334,14 +292,14 @@ class Controller extends ReportsController implements ExportableInterface {
|
||||||
'type' => 'integer',
|
'type' => 'integer',
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
$params['extended_info'] = array(
|
$params['extended_info'] = array(
|
||||||
'description' => __( 'Add additional piece of info about each variation to the report.', 'woocommerce' ),
|
'description' => __( 'Add additional piece of info about each variation to the report.', 'woocommerce' ),
|
||||||
'type' => 'boolean',
|
'type' => 'boolean',
|
||||||
'default' => false,
|
'default' => false,
|
||||||
'sanitize_callback' => 'wc_string_to_bool',
|
'sanitize_callback' => 'wc_string_to_bool',
|
||||||
'validate_callback' => 'rest_validate_request_arg',
|
'validate_callback' => 'rest_validate_request_arg',
|
||||||
);
|
);
|
||||||
$params['attribute_is'] = array(
|
$params['attribute_is'] = array(
|
||||||
'description' => __( 'Limit result set to variations that include the specified attributes.', 'woocommerce' ),
|
'description' => __( 'Limit result set to variations that include the specified attributes.', 'woocommerce' ),
|
||||||
'type' => 'array',
|
'type' => 'array',
|
||||||
'items' => array(
|
'items' => array(
|
||||||
|
@ -350,7 +308,7 @@ class Controller extends ReportsController implements ExportableInterface {
|
||||||
'default' => array(),
|
'default' => array(),
|
||||||
'validate_callback' => 'rest_validate_request_arg',
|
'validate_callback' => 'rest_validate_request_arg',
|
||||||
);
|
);
|
||||||
$params['attribute_is_not'] = array(
|
$params['attribute_is_not'] = array(
|
||||||
'description' => __( 'Limit result set to variations that don\'t include the specified attributes.', 'woocommerce' ),
|
'description' => __( 'Limit result set to variations that don\'t include the specified attributes.', 'woocommerce' ),
|
||||||
'type' => 'array',
|
'type' => 'array',
|
||||||
'items' => array(
|
'items' => array(
|
||||||
|
@ -359,7 +317,7 @@ class Controller extends ReportsController implements ExportableInterface {
|
||||||
'default' => array(),
|
'default' => array(),
|
||||||
'validate_callback' => 'rest_validate_request_arg',
|
'validate_callback' => 'rest_validate_request_arg',
|
||||||
);
|
);
|
||||||
$params['category_includes'] = array(
|
$params['category_includes'] = array(
|
||||||
'description' => __( 'Limit result set to variations in the specified categories.', 'woocommerce' ),
|
'description' => __( 'Limit result set to variations in the specified categories.', 'woocommerce' ),
|
||||||
'type' => 'array',
|
'type' => 'array',
|
||||||
'sanitize_callback' => 'wp_parse_id_list',
|
'sanitize_callback' => 'wp_parse_id_list',
|
||||||
|
@ -368,7 +326,7 @@ class Controller extends ReportsController implements ExportableInterface {
|
||||||
'type' => 'integer',
|
'type' => 'integer',
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
$params['category_excludes'] = array(
|
$params['category_excludes'] = array(
|
||||||
'description' => __( 'Limit result set to variations not in the specified categories.', 'woocommerce' ),
|
'description' => __( 'Limit result set to variations not in the specified categories.', 'woocommerce' ),
|
||||||
'type' => 'array',
|
'type' => 'array',
|
||||||
'sanitize_callback' => 'wp_parse_id_list',
|
'sanitize_callback' => 'wp_parse_id_list',
|
||||||
|
@ -377,13 +335,7 @@ class Controller extends ReportsController implements ExportableInterface {
|
||||||
'type' => 'integer',
|
'type' => 'integer',
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
$params['force_cache_refresh'] = array(
|
$params['products'] = array(
|
||||||
'description' => __( 'Force retrieval of fresh data instead of from the cache.', 'woocommerce' ),
|
|
||||||
'type' => 'boolean',
|
|
||||||
'sanitize_callback' => 'wp_validate_boolean',
|
|
||||||
'validate_callback' => 'rest_validate_request_arg',
|
|
||||||
);
|
|
||||||
$params['products'] = array(
|
|
||||||
'description' => __( 'Limit result to items with specified product ids.', 'woocommerce' ),
|
'description' => __( 'Limit result to items with specified product ids.', 'woocommerce' ),
|
||||||
'type' => 'array',
|
'type' => 'array',
|
||||||
'sanitize_callback' => 'wp_parse_id_list',
|
'sanitize_callback' => 'wp_parse_id_list',
|
||||||
|
|
|
@ -9,7 +9,6 @@ defined( 'ABSPATH' ) || exit;
|
||||||
|
|
||||||
use Automattic\WooCommerce\Admin\API\Reports\DataStore as ReportsDataStore;
|
use Automattic\WooCommerce\Admin\API\Reports\DataStore as ReportsDataStore;
|
||||||
use Automattic\WooCommerce\Admin\API\Reports\DataStoreInterface;
|
use Automattic\WooCommerce\Admin\API\Reports\DataStoreInterface;
|
||||||
use Automattic\WooCommerce\Admin\API\Reports\TimeInterval;
|
|
||||||
use Automattic\WooCommerce\Admin\API\Reports\SqlQuery;
|
use Automattic\WooCommerce\Admin\API\Reports\SqlQuery;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -20,6 +19,8 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||||
/**
|
/**
|
||||||
* Table used to get the data.
|
* Table used to get the data.
|
||||||
*
|
*
|
||||||
|
* @override ReportsDataStore::$table_name
|
||||||
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
protected static $table_name = 'wc_order_product_lookup';
|
protected static $table_name = 'wc_order_product_lookup';
|
||||||
|
@ -27,6 +28,8 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||||
/**
|
/**
|
||||||
* Cache identifier.
|
* Cache identifier.
|
||||||
*
|
*
|
||||||
|
* @override ReportsDataStore::$cache_key
|
||||||
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
protected $cache_key = 'variations';
|
protected $cache_key = 'variations';
|
||||||
|
@ -34,6 +37,8 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||||
/**
|
/**
|
||||||
* Mapping columns to data type to return correct response types.
|
* Mapping columns to data type to return correct response types.
|
||||||
*
|
*
|
||||||
|
* @override ReportsDataStore::$column_types
|
||||||
|
*
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
protected $column_types = array(
|
protected $column_types = array(
|
||||||
|
@ -70,12 +75,16 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||||
/**
|
/**
|
||||||
* Data store context used to pass to filters.
|
* Data store context used to pass to filters.
|
||||||
*
|
*
|
||||||
|
* @override ReportsDataStore::$context
|
||||||
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
protected $context = 'variations';
|
protected $context = 'variations';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Assign report columns once full table name has been assigned.
|
* Assign report columns once full table name has been assigned.
|
||||||
|
*
|
||||||
|
* @override ReportsDataStore::assign_report_columns()
|
||||||
*/
|
*/
|
||||||
protected function assign_report_columns() {
|
protected function assign_report_columns() {
|
||||||
$table_name = self::get_db_table_name();
|
$table_name = self::get_db_table_name();
|
||||||
|
@ -209,6 +218,8 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||||
/**
|
/**
|
||||||
* Maps ordering specified by the user to columns in the database/fields in the data.
|
* Maps ordering specified by the user to columns in the database/fields in the data.
|
||||||
*
|
*
|
||||||
|
* @override ReportsDataStore::normalize_order_by()
|
||||||
|
*
|
||||||
* @param string $order_by Sorting criterion.
|
* @param string $order_by Sorting criterion.
|
||||||
*
|
*
|
||||||
* @return string
|
* @return string
|
||||||
|
@ -372,146 +383,139 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the report data based on parameters supplied by the user.
|
* Get the default query arguments to be used by get_data().
|
||||||
|
* These defaults are only partially applied when used via REST API, as that has its own defaults.
|
||||||
*
|
*
|
||||||
* @param array $query_args Query parameters.
|
* @override ReportsDataStore::get_default_query_vars()
|
||||||
*
|
*
|
||||||
* @return stdClass|WP_Error Data.
|
* @return array Query parameters.
|
||||||
*/
|
*/
|
||||||
public function get_data( $query_args ) {
|
public function get_default_query_vars() {
|
||||||
|
$defaults = parent::get_default_query_vars();
|
||||||
|
$defaults['product_includes'] = array();
|
||||||
|
$defaults['variation_includes'] = array();
|
||||||
|
$defaults['extended_info'] = false;
|
||||||
|
|
||||||
|
return $defaults;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the report data based on normalized parameters.
|
||||||
|
* Will be called by `get_data` if there is no data in cache.
|
||||||
|
*
|
||||||
|
* @override ReportsDataStore::get_noncached_data()
|
||||||
|
*
|
||||||
|
* @see get_data
|
||||||
|
* @param array $query_args Query parameters.
|
||||||
|
* @return stdClass|WP_Error Data object `{ totals: *, intervals: array, total: int, pages: int, page_no: int }`, or error.
|
||||||
|
*/
|
||||||
|
public function get_noncached_data( $query_args ) {
|
||||||
global $wpdb;
|
global $wpdb;
|
||||||
|
|
||||||
$table_name = self::get_db_table_name();
|
$table_name = self::get_db_table_name();
|
||||||
|
|
||||||
// These defaults are only partially applied when used via REST API, as that has its own defaults.
|
$this->initialize_queries();
|
||||||
$defaults = array(
|
|
||||||
'per_page' => get_option( 'posts_per_page' ),
|
$data = (object) array(
|
||||||
'page' => 1,
|
'data' => array(),
|
||||||
'order' => 'DESC',
|
'total' => 0,
|
||||||
'orderby' => 'date',
|
'pages' => 0,
|
||||||
'before' => TimeInterval::default_before(),
|
'page_no' => 0,
|
||||||
'after' => TimeInterval::default_after(),
|
|
||||||
'fields' => '*',
|
|
||||||
'product_includes' => array(),
|
|
||||||
'variation_includes' => array(),
|
|
||||||
'extended_info' => false,
|
|
||||||
);
|
);
|
||||||
$query_args = wp_parse_args( $query_args, $defaults );
|
|
||||||
$this->normalize_timezones( $query_args, $defaults );
|
|
||||||
|
|
||||||
/*
|
$selections = $this->selected_columns( $query_args );
|
||||||
* We need to get the cache key here because
|
$included_variations =
|
||||||
* parent::update_intervals_sql_params() modifies $query_args.
|
( isset( $query_args['variation_includes'] ) && is_array( $query_args['variation_includes'] ) )
|
||||||
*/
|
? $query_args['variation_includes']
|
||||||
$cache_key = $this->get_cache_key( $query_args );
|
: array();
|
||||||
$data = $this->get_cached_data( $cache_key );
|
$params = $this->get_limit_params( $query_args );
|
||||||
|
$this->add_sql_query_params( $query_args );
|
||||||
|
|
||||||
if ( false === $data ) {
|
if ( count( $included_variations ) > 0 ) {
|
||||||
$this->initialize_queries();
|
$total_results = count( $included_variations );
|
||||||
|
$total_pages = (int) ceil( $total_results / $params['per_page'] );
|
||||||
|
|
||||||
$data = (object) array(
|
$this->subquery->clear_sql_clause( 'select' );
|
||||||
'data' => array(),
|
$this->subquery->add_sql_clause( 'select', $selections );
|
||||||
'total' => 0,
|
|
||||||
'pages' => 0,
|
|
||||||
'page_no' => 0,
|
|
||||||
);
|
|
||||||
|
|
||||||
$selections = $this->selected_columns( $query_args );
|
if ( 'date' === $query_args['orderby'] ) {
|
||||||
$included_variations =
|
$this->subquery->add_sql_clause( 'select', ", {$table_name}.date_created" );
|
||||||
( isset( $query_args['variation_includes'] ) && is_array( $query_args['variation_includes'] ) )
|
|
||||||
? $query_args['variation_includes']
|
|
||||||
: array();
|
|
||||||
$params = $this->get_limit_params( $query_args );
|
|
||||||
$this->add_sql_query_params( $query_args );
|
|
||||||
|
|
||||||
if ( count( $included_variations ) > 0 ) {
|
|
||||||
$total_results = count( $included_variations );
|
|
||||||
$total_pages = (int) ceil( $total_results / $params['per_page'] );
|
|
||||||
|
|
||||||
$this->subquery->clear_sql_clause( 'select' );
|
|
||||||
$this->subquery->add_sql_clause( 'select', $selections );
|
|
||||||
|
|
||||||
if ( 'date' === $query_args['orderby'] ) {
|
|
||||||
$this->subquery->add_sql_clause( 'select', ", {$table_name}.date_created" );
|
|
||||||
}
|
|
||||||
|
|
||||||
$fields = $this->get_fields( $query_args );
|
|
||||||
$join_selections = $this->format_join_selections( $fields, array( 'variation_id' ) );
|
|
||||||
$ids_table = $this->get_ids_table( $included_variations, 'variation_id' );
|
|
||||||
|
|
||||||
$this->add_sql_clause( 'select', $join_selections );
|
|
||||||
$this->add_sql_clause( 'from', '(' );
|
|
||||||
$this->add_sql_clause( 'from', $this->subquery->get_query_statement() );
|
|
||||||
$this->add_sql_clause( 'from', ") AS {$table_name}" );
|
|
||||||
$this->add_sql_clause(
|
|
||||||
'right_join',
|
|
||||||
"RIGHT JOIN ( {$ids_table} ) AS default_results
|
|
||||||
ON default_results.variation_id = {$table_name}.variation_id"
|
|
||||||
);
|
|
||||||
|
|
||||||
$variations_query = $this->get_query_statement();
|
|
||||||
} else {
|
|
||||||
|
|
||||||
$this->subquery->clear_sql_clause( 'select' );
|
|
||||||
$this->subquery->add_sql_clause( 'select', $selections );
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Experimental: Filter the Variations SQL query allowing extensions to add additional SQL clauses.
|
|
||||||
*
|
|
||||||
* @since 7.4.0
|
|
||||||
* @param array $query_args Query parameters.
|
|
||||||
* @param SqlQuery $subquery Variations query class.
|
|
||||||
*/
|
|
||||||
apply_filters( 'experimental_woocommerce_analytics_variations_additional_clauses', $query_args, $this->subquery );
|
|
||||||
|
|
||||||
/* phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared */
|
|
||||||
$db_records_count = (int) $wpdb->get_var(
|
|
||||||
"SELECT COUNT(*) FROM (
|
|
||||||
{$this->subquery->get_query_statement()}
|
|
||||||
) AS tt"
|
|
||||||
);
|
|
||||||
/* phpcs:enable */
|
|
||||||
|
|
||||||
$total_results = $db_records_count;
|
|
||||||
$total_pages = (int) ceil( $db_records_count / $params['per_page'] );
|
|
||||||
|
|
||||||
if ( $query_args['page'] < 1 || $query_args['page'] > $total_pages ) {
|
|
||||||
return $data;
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->subquery->add_sql_clause( 'order_by', $this->get_sql_clause( 'order_by' ) );
|
|
||||||
$this->subquery->add_sql_clause( 'limit', $this->get_sql_clause( 'limit' ) );
|
|
||||||
$variations_query = $this->subquery->get_query_statement();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* phpcs:disable WordPress.DB.PreparedSQL.NotPrepared */
|
$fields = $this->get_fields( $query_args );
|
||||||
$product_data = $wpdb->get_results(
|
$join_selections = $this->format_join_selections( $fields, array( 'variation_id' ) );
|
||||||
$variations_query,
|
$ids_table = $this->get_ids_table( $included_variations, 'variation_id' );
|
||||||
ARRAY_A
|
|
||||||
|
$this->add_sql_clause( 'select', $join_selections );
|
||||||
|
$this->add_sql_clause( 'from', '(' );
|
||||||
|
$this->add_sql_clause( 'from', $this->subquery->get_query_statement() );
|
||||||
|
$this->add_sql_clause( 'from', ") AS {$table_name}" );
|
||||||
|
$this->add_sql_clause(
|
||||||
|
'right_join',
|
||||||
|
"RIGHT JOIN ( {$ids_table} ) AS default_results
|
||||||
|
ON default_results.variation_id = {$table_name}.variation_id"
|
||||||
|
);
|
||||||
|
|
||||||
|
$variations_query = $this->get_query_statement();
|
||||||
|
} else {
|
||||||
|
|
||||||
|
$this->subquery->clear_sql_clause( 'select' );
|
||||||
|
$this->subquery->add_sql_clause( 'select', $selections );
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Experimental: Filter the Variations SQL query allowing extensions to add additional SQL clauses.
|
||||||
|
*
|
||||||
|
* @since 7.4.0
|
||||||
|
* @param array $query_args Query parameters.
|
||||||
|
* @param SqlQuery $subquery Variations query class.
|
||||||
|
*/
|
||||||
|
apply_filters( 'experimental_woocommerce_analytics_variations_additional_clauses', $query_args, $this->subquery );
|
||||||
|
|
||||||
|
/* phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared */
|
||||||
|
$db_records_count = (int) $wpdb->get_var(
|
||||||
|
"SELECT COUNT(*) FROM (
|
||||||
|
{$this->subquery->get_query_statement()}
|
||||||
|
) AS tt"
|
||||||
);
|
);
|
||||||
/* phpcs:enable */
|
/* phpcs:enable */
|
||||||
|
|
||||||
if ( null === $product_data ) {
|
$total_results = $db_records_count;
|
||||||
|
$total_pages = (int) ceil( $db_records_count / $params['per_page'] );
|
||||||
|
|
||||||
|
if ( $query_args['page'] < 1 || $query_args['page'] > $total_pages ) {
|
||||||
return $data;
|
return $data;
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->include_extended_info( $product_data, $query_args );
|
$this->subquery->add_sql_clause( 'order_by', $this->get_sql_clause( 'order_by' ) );
|
||||||
|
$this->subquery->add_sql_clause( 'limit', $this->get_sql_clause( 'limit' ) );
|
||||||
if ( $query_args['extended_info'] ) {
|
$variations_query = $this->subquery->get_query_statement();
|
||||||
$this->fill_deleted_product_name( $product_data );
|
|
||||||
}
|
|
||||||
|
|
||||||
$product_data = array_map( array( $this, 'cast_numbers' ), $product_data );
|
|
||||||
$data = (object) array(
|
|
||||||
'data' => $product_data,
|
|
||||||
'total' => $total_results,
|
|
||||||
'pages' => $total_pages,
|
|
||||||
'page_no' => (int) $query_args['page'],
|
|
||||||
);
|
|
||||||
|
|
||||||
$this->set_cached_data( $cache_key, $data );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* phpcs:disable WordPress.DB.PreparedSQL.NotPrepared */
|
||||||
|
$product_data = $wpdb->get_results(
|
||||||
|
$variations_query,
|
||||||
|
ARRAY_A
|
||||||
|
);
|
||||||
|
/* phpcs:enable */
|
||||||
|
|
||||||
|
if ( null === $product_data ) {
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->include_extended_info( $product_data, $query_args );
|
||||||
|
|
||||||
|
if ( $query_args['extended_info'] ) {
|
||||||
|
$this->fill_deleted_product_name( $product_data );
|
||||||
|
}
|
||||||
|
|
||||||
|
$product_data = array_map( array( $this, 'cast_numbers' ), $product_data );
|
||||||
|
$data = (object) array(
|
||||||
|
'data' => $product_data,
|
||||||
|
'total' => $total_results,
|
||||||
|
'pages' => $total_pages,
|
||||||
|
'page_no' => (int) $query_args['page'],
|
||||||
|
);
|
||||||
|
|
||||||
return $data;
|
return $data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,12 +22,16 @@ use Automattic\WooCommerce\Admin\API\Reports\Query as ReportsQuery;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* API\Reports\Variations\Query
|
* API\Reports\Variations\Query
|
||||||
|
*
|
||||||
|
* @deprecated 9.3.0 Variations\Query class is deprecated, please use GenericQuery or \WC_Object_Query instead.
|
||||||
*/
|
*/
|
||||||
class Query extends ReportsQuery {
|
class Query extends ReportsQuery {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Valid fields for Products report.
|
* Valid fields for Products report.
|
||||||
*
|
*
|
||||||
|
* @deprecated 9.3.0 Variations\Query class is deprecated, please use GenericQuery or \WC_Object_Query instead.
|
||||||
|
*
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
protected function get_default_query_vars() {
|
protected function get_default_query_vars() {
|
||||||
|
@ -37,6 +41,8 @@ class Query extends ReportsQuery {
|
||||||
/**
|
/**
|
||||||
* Get product data based on the current query vars.
|
* Get product data based on the current query vars.
|
||||||
*
|
*
|
||||||
|
* @deprecated 9.3.0 Variations\Query class is deprecated, please use GenericQuery or \WC_Object_Query instead.
|
||||||
|
*
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
public function get_data() {
|
public function get_data() {
|
||||||
|
|
|
@ -9,8 +9,8 @@ namespace Automattic\WooCommerce\Admin\API\Reports\Variations\Stats;
|
||||||
|
|
||||||
defined( 'ABSPATH' ) || exit;
|
defined( 'ABSPATH' ) || exit;
|
||||||
|
|
||||||
|
use Automattic\WooCommerce\Admin\API\Reports\GenericQuery;
|
||||||
use Automattic\WooCommerce\Admin\API\Reports\GenericStatsController;
|
use Automattic\WooCommerce\Admin\API\Reports\GenericStatsController;
|
||||||
use Automattic\WooCommerce\Admin\API\Reports\ParameterException;
|
|
||||||
use WP_REST_Request;
|
use WP_REST_Request;
|
||||||
use WP_REST_Response;
|
use WP_REST_Response;
|
||||||
|
|
||||||
|
@ -46,12 +46,25 @@ class Controller extends GenericStatsController {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all reports.
|
* Get data from `'variations-stats'` Query.
|
||||||
*
|
*
|
||||||
* @param WP_REST_Request $request Request data.
|
* @override GenericController::get_datastore_data()
|
||||||
* @return array|WP_Error
|
*
|
||||||
|
* @param array $query_args Query arguments.
|
||||||
|
* @return mixed Results from the data store.
|
||||||
*/
|
*/
|
||||||
public function get_items( $request ) {
|
protected function get_datastore_data( $query_args = array() ) {
|
||||||
|
$query = new GenericQuery( $query_args, 'variations-stats' );
|
||||||
|
return $query->get_data();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps query arguments from the REST request, to be fed to Query.
|
||||||
|
*
|
||||||
|
* @param \WP_REST_Request $request Full request object.
|
||||||
|
* @return array Simplified array of params.
|
||||||
|
*/
|
||||||
|
protected function prepare_reports_query( $request ) {
|
||||||
$query_args = array(
|
$query_args = array(
|
||||||
'fields' => array(
|
'fields' => array(
|
||||||
'items_sold',
|
'items_sold',
|
||||||
|
@ -79,36 +92,13 @@ class Controller extends GenericStatsController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$query = new Query( $query_args );
|
return $query_args;
|
||||||
try {
|
|
||||||
$report_data = $query->get_data();
|
|
||||||
} catch ( ParameterException $e ) {
|
|
||||||
return new \WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
$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 );
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->add_pagination_headers(
|
|
||||||
$request,
|
|
||||||
$out_data,
|
|
||||||
(int) $report_data->total,
|
|
||||||
(int) $report_data->page_no,
|
|
||||||
(int) $report_data->pages
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prepare a report object for serialization.
|
* Prepare a report data item for serialization.
|
||||||
*
|
*
|
||||||
* @param array $report Report data.
|
* @param array $report Report data item as returned from Data Store.
|
||||||
* @param WP_REST_Request $request Request object.
|
* @param WP_REST_Request $request Request object.
|
||||||
* @return WP_REST_Response
|
* @return WP_REST_Response
|
||||||
*/
|
*/
|
||||||
|
@ -288,15 +278,6 @@ class Controller extends GenericStatsController {
|
||||||
),
|
),
|
||||||
'validate_callback' => 'rest_validate_request_arg',
|
'validate_callback' => 'rest_validate_request_arg',
|
||||||
);
|
);
|
||||||
$params['fields'] = array(
|
|
||||||
'description' => __( 'Limit stats fields to the specified items.', 'woocommerce' ),
|
|
||||||
'type' => 'array',
|
|
||||||
'sanitize_callback' => 'wp_parse_slug_list',
|
|
||||||
'validate_callback' => 'rest_validate_request_arg',
|
|
||||||
'items' => array(
|
|
||||||
'type' => 'string',
|
|
||||||
),
|
|
||||||
);
|
|
||||||
$params['attribute_is'] = array(
|
$params['attribute_is'] = array(
|
||||||
'description' => __( 'Limit result set to orders that include products with the specified attributes.', 'woocommerce' ),
|
'description' => __( 'Limit result set to orders that include products with the specified attributes.', 'woocommerce' ),
|
||||||
'type' => 'array',
|
'type' => 'array',
|
||||||
|
|
|
@ -10,16 +10,19 @@ defined( 'ABSPATH' ) || exit;
|
||||||
use Automattic\WooCommerce\Admin\API\Reports\Variations\DataStore as VariationsDataStore;
|
use Automattic\WooCommerce\Admin\API\Reports\Variations\DataStore as VariationsDataStore;
|
||||||
use Automattic\WooCommerce\Admin\API\Reports\DataStoreInterface;
|
use Automattic\WooCommerce\Admin\API\Reports\DataStoreInterface;
|
||||||
use Automattic\WooCommerce\Admin\API\Reports\TimeInterval;
|
use Automattic\WooCommerce\Admin\API\Reports\TimeInterval;
|
||||||
use Automattic\WooCommerce\Admin\API\Reports\SqlQuery;
|
use Automattic\WooCommerce\Admin\API\Reports\StatsDataStoreTrait;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* API\Reports\Variations\Stats\DataStore.
|
* API\Reports\Variations\Stats\DataStore.
|
||||||
*/
|
*/
|
||||||
class DataStore extends VariationsDataStore implements DataStoreInterface {
|
class DataStore extends VariationsDataStore implements DataStoreInterface {
|
||||||
|
use StatsDataStoreTrait;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mapping columns to data type to return correct response types.
|
* Mapping columns to data type to return correct response types.
|
||||||
*
|
*
|
||||||
|
* @override VariationsDataStore::$column_types
|
||||||
|
*
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
protected $column_types = array(
|
protected $column_types = array(
|
||||||
|
@ -32,6 +35,8 @@ class DataStore extends VariationsDataStore implements DataStoreInterface {
|
||||||
/**
|
/**
|
||||||
* Cache identifier.
|
* Cache identifier.
|
||||||
*
|
*
|
||||||
|
* @override VariationsDataStore::$cache_key
|
||||||
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
protected $cache_key = 'variations_stats';
|
protected $cache_key = 'variations_stats';
|
||||||
|
@ -39,12 +44,16 @@ class DataStore extends VariationsDataStore implements DataStoreInterface {
|
||||||
/**
|
/**
|
||||||
* Data store context used to pass to filters.
|
* Data store context used to pass to filters.
|
||||||
*
|
*
|
||||||
|
* @override VariationsDataStore::$context
|
||||||
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
protected $context = 'variations_stats';
|
protected $context = 'variations_stats';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Assign report columns once full table name has been assigned.
|
* Assign report columns once full table name has been assigned.
|
||||||
|
*
|
||||||
|
* @override VariationsDataStore::assign_report_columns()
|
||||||
*/
|
*/
|
||||||
protected function assign_report_columns() {
|
protected function assign_report_columns() {
|
||||||
$table_name = self::get_db_table_name();
|
$table_name = self::get_db_table_name();
|
||||||
|
@ -133,144 +142,131 @@ class DataStore extends VariationsDataStore implements DataStoreInterface {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the report data based on parameters supplied by the user.
|
* Get the default query arguments to be used by get_data().
|
||||||
|
* These defaults are only partially applied when used via REST API, as that has its own defaults.
|
||||||
*
|
*
|
||||||
* @since 3.5.0
|
* @override VariationsDataStore::get_default_query_vars()
|
||||||
* @param array $query_args Query parameters.
|
*
|
||||||
* @return stdClass|WP_Error Data.
|
* @return array Query parameters.
|
||||||
*/
|
*/
|
||||||
public function get_data( $query_args ) {
|
public function get_default_query_vars() {
|
||||||
|
$defaults = parent::get_default_query_vars();
|
||||||
|
$defaults['category_includes'] = array();
|
||||||
|
$defaults['interval'] = 'week';
|
||||||
|
unset( $defaults['extended_info'] );
|
||||||
|
|
||||||
|
return $defaults;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the report data based on normalized parameters.
|
||||||
|
* Will be called by `get_data` if there is no data in cache.
|
||||||
|
*
|
||||||
|
* @override VariationsDataStore::get_noncached_stats_data()
|
||||||
|
*
|
||||||
|
* @see get_data
|
||||||
|
* @see get_noncached_stats_data
|
||||||
|
* @param array $query_args Query parameters.
|
||||||
|
* @param array $params Query limit parameters.
|
||||||
|
* @param stdClass $data Reference to the data object to fill.
|
||||||
|
* @param int $expected_interval_count Number of expected intervals.
|
||||||
|
* @return stdClass|WP_Error Data object `{ totals: *, intervals: array, total: int, pages: int, page_no: int }`, or error.
|
||||||
|
*/
|
||||||
|
public function get_noncached_stats_data( $query_args, $params, &$data, $expected_interval_count ) {
|
||||||
global $wpdb;
|
global $wpdb;
|
||||||
|
|
||||||
$table_name = self::get_db_table_name();
|
$table_name = self::get_db_table_name();
|
||||||
|
|
||||||
// These defaults are only partially applied when used via REST API, as that has its own defaults.
|
$this->initialize_queries();
|
||||||
$defaults = array(
|
|
||||||
'per_page' => get_option( 'posts_per_page' ),
|
$selections = $this->selected_columns( $query_args );
|
||||||
'page' => 1,
|
|
||||||
'order' => 'DESC',
|
$this->update_sql_query_params( $query_args );
|
||||||
'orderby' => 'date',
|
$this->get_limit_sql_params( $query_args );
|
||||||
'before' => TimeInterval::default_before(),
|
$this->interval_query->add_sql_clause( 'where_time', $this->get_sql_clause( 'where_time' ) );
|
||||||
'after' => TimeInterval::default_after(),
|
|
||||||
'fields' => '*',
|
/* phpcs:disable WordPress.DB.PreparedSQL.NotPrepared */
|
||||||
'category_includes' => array(),
|
$db_intervals = $wpdb->get_col(
|
||||||
'interval' => 'week',
|
$this->interval_query->get_query_statement()
|
||||||
'product_includes' => array(),
|
|
||||||
'variation_includes' => array(),
|
|
||||||
);
|
);
|
||||||
$query_args = wp_parse_args( $query_args, $defaults );
|
/* phpcs:enable */
|
||||||
$this->normalize_timezones( $query_args, $defaults );
|
|
||||||
|
|
||||||
/*
|
$db_interval_count = count( $db_intervals );
|
||||||
* We need to get the cache key here because
|
|
||||||
* parent::update_intervals_sql_params() modifies $query_args.
|
|
||||||
*/
|
|
||||||
$cache_key = $this->get_cache_key( $query_args );
|
|
||||||
$data = $this->get_cached_data( $cache_key );
|
|
||||||
|
|
||||||
if ( false === $data ) {
|
$intervals = array();
|
||||||
$this->initialize_queries();
|
$this->update_intervals_sql_params( $query_args, $db_interval_count, $expected_interval_count, $table_name );
|
||||||
|
$this->total_query->add_sql_clause( 'select', $selections );
|
||||||
|
$this->total_query->add_sql_clause( 'where_time', $this->get_sql_clause( 'where_time' ) );
|
||||||
|
|
||||||
$selections = $this->selected_columns( $query_args );
|
/* phpcs:disable WordPress.DB.PreparedSQL.NotPrepared */
|
||||||
$params = $this->get_limit_params( $query_args );
|
$totals = $wpdb->get_results(
|
||||||
|
$this->total_query->get_query_statement(),
|
||||||
|
ARRAY_A
|
||||||
|
);
|
||||||
|
/* phpcs:enable */
|
||||||
|
|
||||||
$this->update_sql_query_params( $query_args );
|
// phpcs:ignore Generic.Commenting.Todo.TaskFound
|
||||||
$this->get_limit_sql_params( $query_args );
|
// @todo remove these assignements when refactoring segmenter classes to use query objects.
|
||||||
$this->interval_query->add_sql_clause( 'where_time', $this->get_sql_clause( 'where_time' ) );
|
$totals_query = array(
|
||||||
|
'from_clause' => $this->total_query->get_sql_clause( 'join' ),
|
||||||
|
'where_time_clause' => $this->total_query->get_sql_clause( 'where_time' ),
|
||||||
|
'where_clause' => $this->total_query->get_sql_clause( 'where' ),
|
||||||
|
);
|
||||||
|
$intervals_query = array(
|
||||||
|
'select_clause' => $this->get_sql_clause( 'select' ),
|
||||||
|
'from_clause' => $this->interval_query->get_sql_clause( 'join' ),
|
||||||
|
'where_time_clause' => $this->interval_query->get_sql_clause( 'where_time' ),
|
||||||
|
'where_clause' => $this->interval_query->get_sql_clause( 'where' ),
|
||||||
|
'order_by' => $this->get_sql_clause( 'order_by' ),
|
||||||
|
'limit' => $this->get_sql_clause( 'limit' ),
|
||||||
|
);
|
||||||
|
$segmenter = new Segmenter( $query_args, $this->report_columns );
|
||||||
|
$totals[0]['segments'] = $segmenter->get_totals_segments( $totals_query, $table_name );
|
||||||
|
|
||||||
/* phpcs:disable WordPress.DB.PreparedSQL.NotPrepared */
|
if ( null === $totals ) {
|
||||||
$db_intervals = $wpdb->get_col(
|
return new \WP_Error( 'woocommerce_analytics_variations_stats_result_failed', __( 'Sorry, fetching revenue data failed.', 'woocommerce' ) );
|
||||||
$this->interval_query->get_query_statement()
|
|
||||||
);
|
|
||||||
/* phpcs:enable */
|
|
||||||
|
|
||||||
$db_interval_count = count( $db_intervals );
|
|
||||||
$expected_interval_count = TimeInterval::intervals_between( $query_args['after'], $query_args['before'], $query_args['interval'] );
|
|
||||||
$total_pages = (int) ceil( $expected_interval_count / $params['per_page'] );
|
|
||||||
if ( $query_args['page'] < 1 || $query_args['page'] > $total_pages ) {
|
|
||||||
return array();
|
|
||||||
}
|
|
||||||
|
|
||||||
$intervals = array();
|
|
||||||
$this->update_intervals_sql_params( $query_args, $db_interval_count, $expected_interval_count, $table_name );
|
|
||||||
$this->total_query->add_sql_clause( 'select', $selections );
|
|
||||||
$this->total_query->add_sql_clause( 'where_time', $this->get_sql_clause( 'where_time' ) );
|
|
||||||
|
|
||||||
/* phpcs:disable WordPress.DB.PreparedSQL.NotPrepared */
|
|
||||||
$totals = $wpdb->get_results(
|
|
||||||
$this->total_query->get_query_statement(),
|
|
||||||
ARRAY_A
|
|
||||||
);
|
|
||||||
/* phpcs:enable */
|
|
||||||
|
|
||||||
// @todo remove these assignements when refactoring segmenter classes to use query objects.
|
|
||||||
$totals_query = array(
|
|
||||||
'from_clause' => $this->total_query->get_sql_clause( 'join' ),
|
|
||||||
'where_time_clause' => $this->total_query->get_sql_clause( 'where_time' ),
|
|
||||||
'where_clause' => $this->total_query->get_sql_clause( 'where' ),
|
|
||||||
);
|
|
||||||
$intervals_query = array(
|
|
||||||
'select_clause' => $this->get_sql_clause( 'select' ),
|
|
||||||
'from_clause' => $this->interval_query->get_sql_clause( 'join' ),
|
|
||||||
'where_time_clause' => $this->interval_query->get_sql_clause( 'where_time' ),
|
|
||||||
'where_clause' => $this->interval_query->get_sql_clause( 'where' ),
|
|
||||||
'order_by' => $this->get_sql_clause( 'order_by' ),
|
|
||||||
'limit' => $this->get_sql_clause( 'limit' ),
|
|
||||||
);
|
|
||||||
$segmenter = new Segmenter( $query_args, $this->report_columns );
|
|
||||||
$totals[0]['segments'] = $segmenter->get_totals_segments( $totals_query, $table_name );
|
|
||||||
|
|
||||||
if ( null === $totals ) {
|
|
||||||
return new \WP_Error( 'woocommerce_analytics_variations_stats_result_failed', __( 'Sorry, fetching revenue data failed.', 'woocommerce' ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->interval_query->add_sql_clause( 'order_by', $this->get_sql_clause( 'order_by' ) );
|
|
||||||
$this->interval_query->add_sql_clause( 'limit', $this->get_sql_clause( 'limit' ) );
|
|
||||||
$this->interval_query->add_sql_clause( 'select', ", MAX({$table_name}.date_created) AS datetime_anchor" );
|
|
||||||
if ( '' !== $selections ) {
|
|
||||||
$this->interval_query->add_sql_clause( 'select', ', ' . $selections );
|
|
||||||
}
|
|
||||||
|
|
||||||
/* phpcs:disable WordPress.DB.PreparedSQL.NotPrepared */
|
|
||||||
$intervals = $wpdb->get_results(
|
|
||||||
$this->interval_query->get_query_statement(),
|
|
||||||
ARRAY_A
|
|
||||||
);
|
|
||||||
/* phpcs:enable */
|
|
||||||
|
|
||||||
if ( null === $intervals ) {
|
|
||||||
return new \WP_Error( 'woocommerce_analytics_variations_stats_result_failed', __( 'Sorry, fetching revenue data failed.', 'woocommerce' ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
$totals = (object) $this->cast_numbers( $totals[0] );
|
|
||||||
|
|
||||||
$data = (object) array(
|
|
||||||
'totals' => $totals,
|
|
||||||
'intervals' => $intervals,
|
|
||||||
'total' => $expected_interval_count,
|
|
||||||
'pages' => $total_pages,
|
|
||||||
'page_no' => (int) $query_args['page'],
|
|
||||||
);
|
|
||||||
|
|
||||||
if ( TimeInterval::intervals_missing( $expected_interval_count, $db_interval_count, $params['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'], $params['per_page'], $db_interval_count, $expected_interval_count, $query_args['orderby'], $query_args['order'] );
|
|
||||||
} else {
|
|
||||||
$this->update_interval_boundary_dates( $query_args['after'], $query_args['before'], $query_args['interval'], $data->intervals );
|
|
||||||
}
|
|
||||||
$segmenter->add_intervals_segments( $data, $intervals_query, $table_name );
|
|
||||||
$this->create_interval_subtotals( $data->intervals );
|
|
||||||
|
|
||||||
$this->set_cached_data( $cache_key, $data );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$this->interval_query->add_sql_clause( 'order_by', $this->get_sql_clause( 'order_by' ) );
|
||||||
|
$this->interval_query->add_sql_clause( 'limit', $this->get_sql_clause( 'limit' ) );
|
||||||
|
$this->interval_query->add_sql_clause( 'select', ", MAX({$table_name}.date_created) AS datetime_anchor" );
|
||||||
|
if ( '' !== $selections ) {
|
||||||
|
$this->interval_query->add_sql_clause( 'select', ', ' . $selections );
|
||||||
|
}
|
||||||
|
|
||||||
|
/* phpcs:disable WordPress.DB.PreparedSQL.NotPrepared */
|
||||||
|
$intervals = $wpdb->get_results(
|
||||||
|
$this->interval_query->get_query_statement(),
|
||||||
|
ARRAY_A
|
||||||
|
);
|
||||||
|
/* phpcs:enable */
|
||||||
|
|
||||||
|
if ( null === $intervals ) {
|
||||||
|
return new \WP_Error( 'woocommerce_analytics_variations_stats_result_failed', __( 'Sorry, fetching revenue data failed.', 'woocommerce' ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
$totals = (object) $this->cast_numbers( $totals[0] );
|
||||||
|
|
||||||
|
$data->totals = $totals;
|
||||||
|
$data->intervals = $intervals;
|
||||||
|
|
||||||
|
if ( TimeInterval::intervals_missing( $expected_interval_count, $db_interval_count, $params['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'], $params['per_page'], $db_interval_count, $expected_interval_count, $query_args['orderby'], $query_args['order'] );
|
||||||
|
} else {
|
||||||
|
$this->update_interval_boundary_dates( $query_args['after'], $query_args['before'], $query_args['interval'], $data->intervals );
|
||||||
|
}
|
||||||
|
$segmenter->add_intervals_segments( $data, $intervals_query, $table_name );
|
||||||
|
|
||||||
return $data;
|
return $data;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Normalizes order_by clause to match to SQL query.
|
* Normalizes order_by clause to match to SQL query.
|
||||||
*
|
*
|
||||||
|
* @override VariationsDataStore::normalize_order_by()
|
||||||
|
*
|
||||||
* @param string $order_by Order by option requeste by user.
|
* @param string $order_by Order by option requeste by user.
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
|
@ -281,18 +277,4 @@ class DataStore extends VariationsDataStore implements DataStoreInterface {
|
||||||
|
|
||||||
return $order_by;
|
return $order_by;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize query objects.
|
|
||||||
*/
|
|
||||||
protected function initialize_queries() {
|
|
||||||
$this->clear_all_clauses();
|
|
||||||
unset( $this->subquery );
|
|
||||||
$this->total_query = new SqlQuery( $this->context . '_total' );
|
|
||||||
$this->total_query->add_sql_clause( 'from', self::get_db_table_name() );
|
|
||||||
|
|
||||||
$this->interval_query = new SqlQuery( $this->context . '_interval' );
|
|
||||||
$this->interval_query->add_sql_clause( 'from', self::get_db_table_name() );
|
|
||||||
$this->interval_query->add_sql_clause( 'group_by', 'time_interval' );
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,12 +22,16 @@ use Automattic\WooCommerce\Admin\API\Reports\Query as ReportsQuery;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* API\Reports\Variations\Stats\Query
|
* API\Reports\Variations\Stats\Query
|
||||||
|
*
|
||||||
|
* @deprecated 9.3.0 Variations\Stats\Query class is deprecated, please use GenericQuery or \WC_Object_Query instead.
|
||||||
*/
|
*/
|
||||||
class Query extends ReportsQuery {
|
class Query extends ReportsQuery {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Valid fields for Products report.
|
* Valid fields for Products report.
|
||||||
*
|
*
|
||||||
|
* @deprecated 9.3.0 Variations\Stats\Query class is deprecated, please use GenericQuery or \WC_Object_Query instead.
|
||||||
|
*
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
protected function get_default_query_vars() {
|
protected function get_default_query_vars() {
|
||||||
|
@ -37,6 +41,8 @@ class Query extends ReportsQuery {
|
||||||
/**
|
/**
|
||||||
* Get variations data based on the current query vars.
|
* Get variations data based on the current query vars.
|
||||||
*
|
*
|
||||||
|
* @deprecated 9.3.0 Variations\Stats\Query class is deprecated, please use GenericQuery or \WC_Object_Query instead.
|
||||||
|
*
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
public function get_data() {
|
public function get_data() {
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use Automattic\WooCommerce\Admin\API\Reports\Coupons\Stats\DataStore as CouponsStatsDataStore;
|
use Automattic\WooCommerce\Admin\API\Reports\Coupons\Stats\DataStore as CouponsStatsDataStore;
|
||||||
use Automattic\WooCommerce\Admin\API\Reports\Coupons\Stats\Query as CouponsStatsQuery;
|
use Automattic\WooCommerce\Admin\API\Reports\GenericQuery;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class WC_Admin_Tests_Reports_Coupons_Stats
|
* Class WC_Admin_Tests_Reports_Coupons_Stats
|
||||||
|
@ -100,8 +100,8 @@ class WC_Admin_Tests_Reports_Coupons_Stats extends WC_Unit_Test_Case {
|
||||||
);
|
);
|
||||||
$this->assertEquals( $expected_data, $data );
|
$this->assertEquals( $expected_data, $data );
|
||||||
|
|
||||||
// Test retrieving the stats through the query class.
|
// Test retrieving the stats through the generic query class.
|
||||||
$query = new CouponsStatsQuery( $args );
|
$query = new GenericQuery( $args, 'coupons-stats' );
|
||||||
$this->assertEquals( $expected_data, $query->get_data() );
|
$this->assertEquals( $expected_data, $query->get_data() );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -143,8 +143,8 @@ class WC_Admin_Tests_Reports_Coupons_Stats extends WC_Unit_Test_Case {
|
||||||
'interval' => 'day',
|
'interval' => 'day',
|
||||||
);
|
);
|
||||||
|
|
||||||
// Test retrieving the stats through the query class.
|
// Test retrieving the stats through the generic query class.
|
||||||
$query = new CouponsStatsQuery( $args );
|
$query = new GenericQuery( $args, 'coupons-stats' );
|
||||||
$start_datetime = new DateTime( $start_time );
|
$start_datetime = new DateTime( $start_time );
|
||||||
$end_datetime = new DateTime( $end_time );
|
$end_datetime = new DateTime( $end_time );
|
||||||
$expected_data = (object) array(
|
$expected_data = (object) array(
|
||||||
|
|
|
@ -5,9 +5,9 @@
|
||||||
* @package WooCommerce\Admin\Tests\Coupons
|
* @package WooCommerce\Admin\Tests\Coupons
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
use Automattic\WooCommerce\Admin\API\Reports\GenericQuery;
|
||||||
use Automattic\WooCommerce\Admin\ReportCSVExporter;
|
use Automattic\WooCommerce\Admin\ReportCSVExporter;
|
||||||
use Automattic\WooCommerce\Admin\API\Reports\Coupons\DataStore as CouponsDataStore;
|
use Automattic\WooCommerce\Admin\API\Reports\Coupons\DataStore as CouponsDataStore;
|
||||||
use Automattic\WooCommerce\Admin\API\Reports\Coupons\Query as CouponsQuery;
|
|
||||||
use Automattic\WooCommerce\Admin\API\Reports\TimeInterval;
|
use Automattic\WooCommerce\Admin\API\Reports\TimeInterval;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -96,8 +96,8 @@ class WC_Admin_Tests_Reports_Coupons extends WC_Unit_Test_Case {
|
||||||
);
|
);
|
||||||
$this->assertEquals( $expected_data, $data );
|
$this->assertEquals( $expected_data, $data );
|
||||||
|
|
||||||
// Test retrieving the stats through the query class.
|
// Test retrieving the stats through the generic query class.
|
||||||
$query = new CouponsQuery( $args );
|
$query = new GenericQuery( $args, 'coupons' );
|
||||||
$this->assertEquals( $expected_data, $query->get_data() );
|
$this->assertEquals( $expected_data, $query->get_data() );
|
||||||
|
|
||||||
// Test order by orders_count DESC.
|
// Test order by orders_count DESC.
|
||||||
|
|
|
@ -6,8 +6,6 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use Automattic\WooCommerce\Admin\API\Reports\Orders\DataStore as OrdersDataStore;
|
use Automattic\WooCommerce\Admin\API\Reports\Orders\DataStore as OrdersDataStore;
|
||||||
use Automattic\WooCommerce\Admin\API\Reports\Orders\Query as OrdersQuery;
|
|
||||||
use Automattic\WooCommerce\Admin\API\Reports\TimeInterval;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class WC_Admin_Tests_Reports_Orders
|
* Class WC_Admin_Tests_Reports_Orders
|
||||||
|
|
|
@ -6,8 +6,8 @@
|
||||||
* @todo Finish up unit testing to verify bug-free product reports.
|
* @todo Finish up unit testing to verify bug-free product reports.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
use Automattic\WooCommerce\Admin\API\Reports\GenericQuery;
|
||||||
use Automattic\WooCommerce\Admin\API\Reports\Products\DataStore as ProductsDataStore;
|
use Automattic\WooCommerce\Admin\API\Reports\Products\DataStore as ProductsDataStore;
|
||||||
use Automattic\WooCommerce\Admin\API\Reports\Products\Query as ProductsQuery;
|
|
||||||
use Automattic\WooCommerce\Admin\ReportCSVExporter;
|
use Automattic\WooCommerce\Admin\ReportCSVExporter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -70,8 +70,8 @@ class WC_Admin_Tests_Reports_Products extends WC_Unit_Test_Case {
|
||||||
);
|
);
|
||||||
$this->assertEquals( $expected_data, $data );
|
$this->assertEquals( $expected_data, $data );
|
||||||
|
|
||||||
// Test retrieving the stats through the query class.
|
// Test retrieving the stats through the generic query class.
|
||||||
$query = new ProductsQuery( $args );
|
$query = new GenericQuery( $args, 'products' );
|
||||||
$this->assertEquals( $expected_data, $query->get_data() );
|
$this->assertEquals( $expected_data, $query->get_data() );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -186,8 +186,8 @@ class WC_Admin_Tests_Reports_Products extends WC_Unit_Test_Case {
|
||||||
);
|
);
|
||||||
$this->assertEquals( $expected_data, $data );
|
$this->assertEquals( $expected_data, $data );
|
||||||
|
|
||||||
// Test retrieving the stats through the query class.
|
// Test retrieving the stats through the generic query class.
|
||||||
$query = new ProductsQuery( $args );
|
$query = new GenericQuery( $args, 'products' );
|
||||||
$this->assertEquals( $expected_data, $query->get_data() );
|
$this->assertEquals( $expected_data, $query->get_data() );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -411,8 +411,8 @@ class WC_Admin_Tests_Reports_Products extends WC_Unit_Test_Case {
|
||||||
);
|
);
|
||||||
$this->assertEquals( $expected_data, $data );
|
$this->assertEquals( $expected_data, $data );
|
||||||
|
|
||||||
// Test retrieving the stats through the query class.
|
// Test retrieving the stats through the generic query class.
|
||||||
$query = new ProductsQuery( $args );
|
$query = new GenericQuery( $args, 'products' );
|
||||||
$this->assertEquals( $expected_data, $query->get_data() );
|
$this->assertEquals( $expected_data, $query->get_data() );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,8 +6,8 @@
|
||||||
* @todo Finish up unit testing to verify bug-free order reports.
|
* @todo Finish up unit testing to verify bug-free order reports.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
use Automattic\WooCommerce\Admin\API\Reports\GenericQuery;
|
||||||
use Automattic\WooCommerce\Admin\API\Reports\Variations\DataStore as VariationsDataStore;
|
use Automattic\WooCommerce\Admin\API\Reports\Variations\DataStore as VariationsDataStore;
|
||||||
use Automattic\WooCommerce\Admin\API\Reports\Variations\Query as VariationsQuery;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reports order stats tests class.
|
* Reports order stats tests class.
|
||||||
|
@ -71,8 +71,8 @@ class WC_Admin_Tests_Reports_Variations extends WC_Unit_Test_Case {
|
||||||
);
|
);
|
||||||
$this->assertEquals( $expected_data, $data );
|
$this->assertEquals( $expected_data, $data );
|
||||||
|
|
||||||
// Test retrieving the stats through the query class.
|
// Test retrieving the stats through the generic query class.
|
||||||
$query = new VariationsQuery( $args );
|
$query = new GenericQuery( $args, 'variations' );
|
||||||
$this->assertEquals( $expected_data, $query->get_data() );
|
$this->assertEquals( $expected_data, $query->get_data() );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue