Data store refactor (https://github.com/woocommerce/woocommerce-admin/pull/2961)
* Base Report data store utility functions - 1 Standardizing the data store classes will be easier if the base class contains simple utility functions that can replace logic implemented in multiple data stores. - set_db_table_name() assigns a WP DB table name class variable for the data store - get_db_table_name() retrieves the WP DB table name - prepend_table_name() prepends a field in a query fragment with the data store table name * add context, clause handling, and filters to reports data store * add generated SQL clauses to class properties * reduce id getter logic to single function with filter, add phpdocs to new filters * update table_name to private string for use in constructor * extract SQL query clause handling to its own class - Will allow for use in subquery processing without creating a get_data stub - Swap parameter order in add_sql_clause for readability - Add support for clearing multple clauses in one call * add context var to SqlQuery class * implement SqlQuery in Categories data store * implement subquery in categories data store * coupons data stores, more underlying refactor - fix warnings - make filtered id functions static - add limit parameter handling - update coupons data store - update coupon stats data store * refactor coupon stats data store * refactor customers and customer stats data stores * add context to subqueries * add missed prepend table name call * refactor downloads data store, fix some warnings * fix warnings, add separator parameter to filtered IDs * refactor taxes and tax stats data stores * refactor variations data store * refactor product and product stats data stores * make table_name static throughout for compat with static hook functions * refactor order and order stats datastores - use consistent visibility on initialize_queries() - update db_table_name logic to use static keyword instead of self * fix missed whitespace * fix segmenting query, add SqlQuery join clause * DRY data store constructors, class properties * prefix table name when not yet assigned * fix unit tests, interpolations, WPDB delete calls * DRY get_object_where_filter() * remove redundant table prefix from unit test init * fix refactored SQL queries * restore product paging * remove unused query param arrays * add first pass on data docs readme * remove debug code, errant SQL spacing * refactor out outer_from query element * merge wheres, joins before filtering * move all report column definitions to assign_report_columns * fix data readme markdown * small code formating fixes from review * remove static from query/datastore context * missed self:: in previous, add comments, small code moves * rename get_statement() to get_query_statement() * remove temporary query references * static reference, remove reference parameter, fix coupon compare * add todo reminders * use correct query parameter in coupon data stores
This commit is contained in:
parent
9a9d812e60
commit
afed4fba36
|
@ -1,4 +1,84 @@
|
|||
Data
|
||||
====
|
||||
|
||||
TBA, this will document our data implementation.
|
||||
WooCommerce Admin data stores implement the [`SqlQuery` class](https://github.com/woocommerce/woocommerce-admin/blob/master/src/API/Reports/SqlQuery.php).
|
||||
|
||||
### SqlQuery Class
|
||||
|
||||
The `SqlQuery` class is a SQL Query statement object. Its properties consist of
|
||||
|
||||
- A `context` string identifying the context of the query.
|
||||
- SQL clause (`type`) string arrays used to construct the SQL statement:
|
||||
- `select`
|
||||
- `from`
|
||||
- `right_join`
|
||||
- `join`
|
||||
- `left_join`
|
||||
- `where`
|
||||
- `where_time`
|
||||
- `group_by`
|
||||
- `having`
|
||||
- `order_by`
|
||||
- `limit`
|
||||
|
||||
### 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:
|
||||
|
||||
| Data Store | Context | Class Query | Sub Query | Interval Query | Total Query |
|
||||
| ---------- | ------- | ----------- | --------- | -------------- | ----------- |
|
||||
| Categories | categories | Yes | Yes | - | - |
|
||||
| Coupons | coupons | Yes | Yes | - | - |
|
||||
| Coupon Stats | coupon_stats | Yes | - | Yes | Yes |
|
||||
| Customers | customers | Yes | Yes | - | - |
|
||||
| Customer Stats | customer_stats | Yes | - | Yes | Yes |
|
||||
| Downloads | downloads | Yes | Yes | - | - |
|
||||
| Download Stats | download_stats | Yes | - | Yes | Yes |
|
||||
| Orders | orders | Yes | Yes | - | - |
|
||||
| Order Stats | order_stats | Yes | - | Yes | Yes |
|
||||
| Products | products | Yes | Yes | - | - |
|
||||
| Product Stats | product_stats | Yes | - | Yes | Yes |
|
||||
| Taxes | taxes | Yes | Yes | - | - |
|
||||
| Tax Stats | tax_stats | Yes | - | Yes | Yes |
|
||||
| Variations | variations | Yes | Yes | - | - |
|
||||
|
||||
Query contexts are named as follows:
|
||||
|
||||
- Class Query = Class Context
|
||||
- Sub Query = Class Context + `_subquery`
|
||||
- Interval Query = Class Context + `_interval`
|
||||
- Total Query = Class Context + `_total`
|
||||
|
||||
### Filters
|
||||
|
||||
When getting the full statement the clause arrays are passed through two filters where `$context` is the query object context and `$type` is:
|
||||
|
||||
- `select`
|
||||
- `from`
|
||||
- `join` = `right_join` + `join` + `left_join`
|
||||
- `where` = `where` + `where_time`
|
||||
- `group_by`
|
||||
- `having`
|
||||
- `order_by`
|
||||
- `limit`
|
||||
|
||||
The filters are:
|
||||
|
||||
- `apply_filters( "wc_admin_clauses_{$type}", $clauses, $context );`
|
||||
- `apply_filters( "wc_admin_clauses_{$type}_{$context}", $clauses );`
|
||||
|
||||
Example usage
|
||||
|
||||
```
|
||||
add_filter( 'wc_admin_clauses_product_stats_select_total', 'my_custom_product_stats' );
|
||||
/**
|
||||
* Add sample data to product stats totals.
|
||||
*
|
||||
* @param array $clauses array of SELECT clauses.
|
||||
* @return array
|
||||
*/
|
||||
function my_custom_product_stats( $clauses ) {
|
||||
$clauses[] = ', SUM( sample_column ) as sample_total';
|
||||
return $clauses;
|
||||
}
|
||||
```
|
|
@ -12,6 +12,7 @@ defined( 'ABSPATH' ) || exit;
|
|||
use \Automattic\WooCommerce\Admin\API\Reports\DataStore as ReportsDataStore;
|
||||
use \Automattic\WooCommerce\Admin\API\Reports\DataStoreInterface;
|
||||
use \Automattic\WooCommerce\Admin\API\Reports\TimeInterval;
|
||||
use \Automattic\WooCommerce\Admin\API\Reports\SqlQuery;
|
||||
|
||||
/**
|
||||
* API\Reports\Categories\DataStore.
|
||||
|
@ -23,7 +24,7 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
|||
*
|
||||
* @var string
|
||||
*/
|
||||
const TABLE_NAME = 'wc_order_product_lookup';
|
||||
protected static $table_name = 'wc_order_product_lookup';
|
||||
|
||||
/**
|
||||
* Cache identifier.
|
||||
|
@ -60,102 +61,83 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
|||
);
|
||||
|
||||
/**
|
||||
* SQL columns to select in the db query.
|
||||
* Data store context used to pass to filters.
|
||||
*
|
||||
* @var array
|
||||
* @var string
|
||||
*/
|
||||
protected $report_columns = array(
|
||||
'items_sold' => 'SUM(product_qty) as items_sold',
|
||||
'net_revenue' => 'SUM(product_net_revenue) AS net_revenue',
|
||||
'orders_count' => 'COUNT(DISTINCT order_id) as orders_count',
|
||||
'products_count' => 'COUNT(DISTINCT product_id) as products_count',
|
||||
);
|
||||
protected $context = 'categories';
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* Assign report columns once full table name has been assigned.
|
||||
*/
|
||||
public function __construct() {
|
||||
global $wpdb;
|
||||
$table_name = $wpdb->prefix . self::TABLE_NAME;
|
||||
// Avoid ambigious column order_id in SQL query.
|
||||
$this->report_columns['products_count'] = str_replace( 'product_id', $table_name . '.product_id', $this->report_columns['products_count'] );
|
||||
$this->report_columns['orders_count'] = str_replace( 'order_id', $table_name . '.order_id', $this->report_columns['orders_count'] );
|
||||
protected function assign_report_columns() {
|
||||
$table_name = self::get_db_table_name();
|
||||
$this->report_columns = array(
|
||||
'items_sold' => 'SUM(product_qty) as items_sold',
|
||||
'net_revenue' => 'SUM(product_net_revenue) AS net_revenue',
|
||||
'orders_count' => "COUNT(DISTINCT {$table_name}.order_id) as orders_count",
|
||||
'products_count' => "COUNT(DISTINCT {$table_name}.product_id) as products_count",
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the database query with parameters used for Categories report: time span and order status.
|
||||
*
|
||||
* @param array $query_args Query arguments supplied by the user.
|
||||
* @return array Array of parameters used for SQL query.
|
||||
*/
|
||||
protected function get_sql_query_params( $query_args ) {
|
||||
global $wpdb;
|
||||
$order_product_lookup_table = $wpdb->prefix . self::TABLE_NAME;
|
||||
$order_product_lookup_table = self::get_db_table_name();
|
||||
|
||||
$sql_query_params = $this->get_time_period_sql_params( $query_args, $order_product_lookup_table );
|
||||
$this->get_time_period_sql_params( $query_args, $order_product_lookup_table );
|
||||
|
||||
// join wp_order_product_lookup_table with relationships and taxonomies.
|
||||
$sql_query_params['from_clause'] .= " LEFT JOIN {$wpdb->term_relationships} ON {$order_product_lookup_table}.product_id = {$wpdb->term_relationships}.object_id";
|
||||
$sql_query_params['from_clause'] .= " LEFT JOIN {$wpdb->wc_category_lookup} ON {$wpdb->term_relationships}.term_taxonomy_id = {$wpdb->wc_category_lookup}.category_id";
|
||||
// join wp_order_product_lookup_table with relationships and taxonomies
|
||||
// @todo How to handle custom product tables?
|
||||
$this->subquery->add_sql_clause( 'left_join', "LEFT JOIN {$wpdb->term_relationships} ON {$order_product_lookup_table}.product_id = {$wpdb->term_relationships}.object_id" );
|
||||
$this->subquery->add_sql_clause( 'left_join', "LEFT JOIN {$wpdb->wc_category_lookup} ON {$wpdb->term_relationships}.term_taxonomy_id = {$wpdb->wc_category_lookup}.category_id" );
|
||||
|
||||
$included_categories = $this->get_included_categories( $query_args );
|
||||
if ( $included_categories ) {
|
||||
$sql_query_params['where_clause'] .= " AND {$wpdb->wc_category_lookup}.category_tree_id IN ({$included_categories})";
|
||||
$this->subquery->add_sql_clause( 'where', "AND {$wpdb->wc_category_lookup}.category_tree_id IN ({$included_categories})" );
|
||||
|
||||
// Limit is left out here so that the grouping in code by PHP can be applied correctly.
|
||||
// This also needs to be put after the term_taxonomy JOIN so that we can match the correct term name.
|
||||
$sql_query_params = $this->get_order_by_params( $query_args, $sql_query_params, 'outer_from_clause', 'default_results.category_id' );
|
||||
$this->get_order_by_params( $query_args, 'outer', 'default_results.category_id' );
|
||||
} else {
|
||||
$sql_query_params = $this->get_order_by_params( $query_args, $sql_query_params, 'from_clause', "{$wpdb->wc_category_lookup}.category_tree_id" );
|
||||
$this->get_order_by_params( $query_args, 'inner', "{$wpdb->wc_category_lookup}.category_tree_id" );
|
||||
}
|
||||
|
||||
// @todo Only products in the category C or orders with products from category C (and, possibly others?).
|
||||
$included_products = $this->get_included_products( $query_args );
|
||||
if ( $included_products ) {
|
||||
$sql_query_params['where_clause'] .= " AND {$order_product_lookup_table}.product_id IN ({$included_products})";
|
||||
$this->subquery->add_sql_clause( 'where', "AND {$order_product_lookup_table}.product_id IN ({$included_products})" );
|
||||
}
|
||||
|
||||
$order_status_filter = $this->get_status_subquery( $query_args );
|
||||
if ( $order_status_filter ) {
|
||||
$sql_query_params['from_clause'] .= " JOIN {$wpdb->prefix}wc_order_stats ON {$order_product_lookup_table}.order_id = {$wpdb->prefix}wc_order_stats.order_id";
|
||||
$sql_query_params['where_clause'] .= " AND ( {$order_status_filter} )";
|
||||
}
|
||||
|
||||
$sql_query_params['where_clause'] .= " AND {$wpdb->wc_category_lookup}.category_tree_id IS NOT NULL";
|
||||
|
||||
return $sql_query_params;
|
||||
$this->add_order_status_clause( $query_args, $order_product_lookup_table, $this->subquery );
|
||||
$this->subquery->add_sql_clause( 'where', "AND {$wpdb->wc_category_lookup}.category_tree_id IS NOT NULL" );
|
||||
}
|
||||
|
||||
/**
|
||||
* Fills ORDER BY clause of SQL request based on user supplied parameters.
|
||||
*
|
||||
* @param array $query_args Parameters supplied by the user.
|
||||
* @param array $sql_query Current SQL query array.
|
||||
* @param string $from_arg Name of the FROM sql param.
|
||||
* @param string $from_arg Target of the JOIN sql param.
|
||||
* @param string $id_cell ID cell identifier, like `table_name.id_column_name`.
|
||||
* @return array
|
||||
*/
|
||||
protected function get_order_by_params( $query_args, $sql_query, $from_arg, $id_cell ) {
|
||||
protected function get_order_by_params( $query_args, $from_arg, $id_cell ) {
|
||||
global $wpdb;
|
||||
$lookup_table = $wpdb->prefix . self::TABLE_NAME;
|
||||
$lookup_table = self::get_db_table_name();
|
||||
$order_by_clause = $this->add_order_by_clause( $query_args, $this );
|
||||
$this->add_orderby_order_clause( $query_args, $this );
|
||||
|
||||
$sql_query['order_by_clause'] = '';
|
||||
if ( isset( $query_args['orderby'] ) ) {
|
||||
$sql_query['order_by_clause'] = $this->normalize_order_by( $query_args['orderby'] );
|
||||
if ( false !== strpos( $order_by_clause, '_terms' ) ) {
|
||||
$join = "JOIN {$wpdb->terms} AS _terms ON {$id_cell} = _terms.term_id";
|
||||
if ( 'inner' === $from_arg ) {
|
||||
$this->subquery->add_sql_clause( 'join', $join );
|
||||
} else {
|
||||
$this->add_sql_clause( 'join', $join );
|
||||
}
|
||||
}
|
||||
|
||||
$sql_query['outer_from_clause'] = '';
|
||||
if ( false !== strpos( $sql_query['order_by_clause'], '_terms' ) ) {
|
||||
$sql_query[ $from_arg ] .= " JOIN {$wpdb->prefix}terms AS _terms ON {$id_cell} = _terms.term_id";
|
||||
}
|
||||
|
||||
if ( isset( $query_args['order'] ) ) {
|
||||
$sql_query['order_by_clause'] .= ' ' . $query_args['order'];
|
||||
} else {
|
||||
$sql_query['order_by_clause'] .= ' DESC';
|
||||
}
|
||||
|
||||
return $sql_query;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -187,17 +169,6 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
|||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns comma separated ids of included categories, based on query arguments from the user.
|
||||
*
|
||||
* @param array $query_args Parameters supplied by the user.
|
||||
* @return string
|
||||
*/
|
||||
protected function get_included_categories( $query_args ) {
|
||||
$included_categories = $this->get_included_categories_array( $query_args );
|
||||
return implode( ',', $included_categories );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the page of data according to page number and items per page.
|
||||
*
|
||||
|
@ -236,7 +207,7 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
|||
public function get_data( $query_args ) {
|
||||
global $wpdb;
|
||||
|
||||
$table_name = $wpdb->prefix . self::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.
|
||||
$defaults = array(
|
||||
|
@ -261,6 +232,8 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
|||
$data = $this->get_cached_data( $cache_key );
|
||||
|
||||
if ( false === $data ) {
|
||||
$this->initialize_queries();
|
||||
|
||||
$data = (object) array(
|
||||
'data' => array(),
|
||||
'total' => 0,
|
||||
|
@ -268,45 +241,31 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
|||
'page_no' => 0,
|
||||
);
|
||||
|
||||
$selections = $this->selected_columns( $query_args );
|
||||
$sql_query_params = $this->get_sql_query_params( $query_args );
|
||||
$this->subquery->add_sql_clause( 'select', $this->selected_columns( $query_args ) );
|
||||
$included_categories = $this->get_included_categories_array( $query_args );
|
||||
$this->get_sql_query_params( $query_args );
|
||||
|
||||
if ( count( $included_categories ) > 0 ) {
|
||||
$fields = $this->get_fields( $query_args );
|
||||
$join_selections = $this->format_join_selections( array_merge( array( 'category_id' ), $fields ), array( 'category_id' ) );
|
||||
$ids_table = $this->get_ids_table( $included_categories, 'category_id' );
|
||||
$fields = $this->get_fields( $query_args );
|
||||
$ids_table = $this->get_ids_table( $included_categories, 'category_id' );
|
||||
|
||||
$prefix = "SELECT {$join_selections} FROM (";
|
||||
$suffix = ") AS {$table_name}";
|
||||
$right_join = "RIGHT JOIN ( {$ids_table} ) AS default_results
|
||||
ON default_results.category_id = {$table_name}.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 {
|
||||
$prefix = '';
|
||||
$suffix = '';
|
||||
$right_join = '';
|
||||
$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(
|
||||
"${prefix}
|
||||
SELECT
|
||||
{$wpdb->wc_category_lookup}.category_tree_id as category_id,
|
||||
{$selections}
|
||||
FROM
|
||||
{$table_name}
|
||||
{$sql_query_params['from_clause']}
|
||||
WHERE
|
||||
1=1
|
||||
{$sql_query_params['where_time_clause']}
|
||||
{$sql_query_params['where_clause']}
|
||||
GROUP BY
|
||||
{$wpdb->wc_category_lookup}.category_tree_id
|
||||
{$suffix}
|
||||
{$right_join}
|
||||
{$sql_query_params['outer_from_clause']}
|
||||
ORDER BY
|
||||
{$sql_query_params['order_by_clause']}
|
||||
",
|
||||
$categories_query,
|
||||
ARRAY_A
|
||||
); // WPCS: cache ok, DB call ok, unprepared SQL ok.
|
||||
|
||||
|
@ -335,4 +294,15 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
|||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize query objects.
|
||||
*/
|
||||
protected function initialize_queries() {
|
||||
global $wpdb;
|
||||
$this->subquery = new SqlQuery( $this->context . '_subquery' );
|
||||
$this->subquery->add_sql_clause( 'select', "{$wpdb->wc_category_lookup}.category_tree_id as category_id," );
|
||||
$this->subquery->add_sql_clause( 'from', self::get_db_table_name() );
|
||||
$this->subquery->add_sql_clause( 'group_by', "{$wpdb->wc_category_lookup}.category_tree_id" );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ defined( 'ABSPATH' ) || exit;
|
|||
use \Automattic\WooCommerce\Admin\API\Reports\DataStore as ReportsDataStore;
|
||||
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\Cache as ReportsCache;
|
||||
|
||||
/**
|
||||
|
@ -24,7 +25,7 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
|||
*
|
||||
* @var string
|
||||
*/
|
||||
const TABLE_NAME = 'wc_order_coupon_lookup';
|
||||
protected static $table_name = 'wc_order_coupon_lookup';
|
||||
|
||||
/**
|
||||
* Cache identifier.
|
||||
|
@ -45,24 +46,22 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
|||
);
|
||||
|
||||
/**
|
||||
* SQL columns to select in the db query and their mapping to SQL code.
|
||||
* Data store context used to pass to filters.
|
||||
*
|
||||
* @var array
|
||||
* @var string
|
||||
*/
|
||||
protected $report_columns = array(
|
||||
'coupon_id' => 'coupon_id',
|
||||
'amount' => 'SUM(discount_amount) as amount',
|
||||
'orders_count' => 'COUNT(DISTINCT order_id) as orders_count',
|
||||
);
|
||||
protected $context = 'coupons';
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* Assign report columns once full table name has been assigned.
|
||||
*/
|
||||
public function __construct() {
|
||||
global $wpdb;
|
||||
$table_name = $wpdb->prefix . self::TABLE_NAME;
|
||||
// Avoid ambigious column order_id in SQL query.
|
||||
$this->report_columns['orders_count'] = str_replace( 'order_id', $table_name . '.order_id', $this->report_columns['orders_count'] );
|
||||
protected function assign_report_columns() {
|
||||
$table_name = self::get_db_table_name();
|
||||
$this->report_columns = array(
|
||||
'coupon_id' => 'coupon_id',
|
||||
'amount' => 'SUM(discount_amount) as amount',
|
||||
'orders_count' => "COUNT(DISTINCT {$table_name}.order_id) as orders_count",
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -86,78 +85,55 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
|||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns comma separated ids of included coupons, based on query arguments from the user.
|
||||
*
|
||||
* @param array $query_args Parameters supplied by the user.
|
||||
* @return string
|
||||
*/
|
||||
protected function get_included_coupons( $query_args ) {
|
||||
$included_coupons = $this->get_included_coupons_array( $query_args );
|
||||
return implode( ',', $included_coupons );
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the database query with parameters used for Products report: categories and order status.
|
||||
*
|
||||
* @param array $query_args Query arguments supplied by the user.
|
||||
* @return array Array of parameters used for SQL query.
|
||||
*/
|
||||
protected function get_sql_query_params( $query_args ) {
|
||||
global $wpdb;
|
||||
$order_coupon_lookup_table = $wpdb->prefix . self::TABLE_NAME;
|
||||
$order_coupon_lookup_table = self::get_db_table_name();
|
||||
|
||||
$sql_query_params = $this->get_time_period_sql_params( $query_args, $order_coupon_lookup_table );
|
||||
$sql_query_params = array_merge( $sql_query_params, $this->get_limit_sql_params( $query_args ) );
|
||||
$this->get_time_period_sql_params( $query_args, $order_coupon_lookup_table );
|
||||
$this->get_limit_sql_params( $query_args );
|
||||
|
||||
$included_coupons = $this->get_included_coupons( $query_args );
|
||||
$included_coupons = $this->get_included_coupons( $query_args, 'coupons' );
|
||||
if ( $included_coupons ) {
|
||||
$sql_query_params['where_clause'] .= " AND {$order_coupon_lookup_table}.coupon_id IN ({$included_coupons})";
|
||||
$this->subquery->add_sql_clause( 'where', "AND {$order_coupon_lookup_table}.coupon_id IN ({$included_coupons})" );
|
||||
|
||||
$sql_query_params = array_merge( $sql_query_params, $this->get_order_by_params( $query_args, 'outer_from_clause', 'default_results.coupon_id' ) );
|
||||
$this->get_order_by_params( $query_args, 'outer', 'default_results.coupon_id' );
|
||||
} else {
|
||||
$sql_query_params = array_merge( $sql_query_params, $this->get_order_by_params( $query_args, 'from_clause', "{$order_coupon_lookup_table}.coupon_id" ) );
|
||||
$this->get_order_by_params( $query_args, 'inner', "{$order_coupon_lookup_table}.coupon_id" );
|
||||
}
|
||||
|
||||
$order_status_filter = $this->get_status_subquery( $query_args );
|
||||
if ( $order_status_filter ) {
|
||||
$sql_query_params['from_clause'] .= " JOIN {$wpdb->prefix}wc_order_stats ON {$order_coupon_lookup_table}.order_id = {$wpdb->prefix}wc_order_stats.order_id";
|
||||
$sql_query_params['where_clause'] .= " AND ( {$order_status_filter} )";
|
||||
}
|
||||
|
||||
return $sql_query_params;
|
||||
$this->add_order_status_clause( $query_args, $order_coupon_lookup_table, $this->subquery );
|
||||
}
|
||||
|
||||
/**
|
||||
* Fills ORDER BY clause of SQL request based on user supplied parameters.
|
||||
*
|
||||
* @param array $query_args Parameters supplied by the user.
|
||||
* @param string $from_arg Name of the FROM sql param.
|
||||
* @param string $from_arg Target of the JOIN sql param.
|
||||
* @param string $id_cell ID cell identifier, like `table_name.id_column_name`.
|
||||
* @return array
|
||||
*/
|
||||
protected function get_order_by_params( $query_args, $from_arg, $id_cell ) {
|
||||
global $wpdb;
|
||||
$lookup_table = $wpdb->prefix . self::TABLE_NAME;
|
||||
$sql_query = array();
|
||||
$sql_query['from_clause'] = '';
|
||||
$sql_query['outer_from_clause'] = '';
|
||||
$sql_query['order_by_clause'] = '';
|
||||
if ( isset( $query_args['orderby'] ) ) {
|
||||
$sql_query['order_by_clause'] = $this->normalize_order_by( $query_args['orderby'] );
|
||||
}
|
||||
$lookup_table = self::get_db_table_name();
|
||||
$order_by_clause = $this->add_order_by_clause( $query_args, $this );
|
||||
$join = "JOIN {$wpdb->posts} AS _coupons ON {$id_cell} = _coupons.ID";
|
||||
$this->add_orderby_order_clause( $query_args, $this );
|
||||
|
||||
if ( false !== strpos( $sql_query['order_by_clause'], '_coupons' ) ) {
|
||||
$sql_query[ $from_arg ] .= " JOIN {$wpdb->prefix}posts AS _coupons ON {$id_cell} = _coupons.ID";
|
||||
}
|
||||
|
||||
if ( isset( $query_args['order'] ) ) {
|
||||
$sql_query['order_by_clause'] .= ' ' . $query_args['order'];
|
||||
if ( 'inner' === $from_arg ) {
|
||||
$this->subquery->clear_sql_clause( 'join' );
|
||||
if ( false !== strpos( $order_by_clause, '_coupons' ) ) {
|
||||
$this->subquery->add_sql_clause( 'join', $join );
|
||||
}
|
||||
} else {
|
||||
$sql_query['order_by_clause'] .= ' DESC';
|
||||
$this->clear_sql_clause( 'join' );
|
||||
if ( false !== strpos( $order_by_clause, '_coupons' ) ) {
|
||||
$this->add_sql_clause( 'join', $join );
|
||||
}
|
||||
}
|
||||
|
||||
return $sql_query;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -235,7 +211,7 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
|||
public function get_data( $query_args ) {
|
||||
global $wpdb;
|
||||
|
||||
$table_name = $wpdb->prefix . self::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.
|
||||
$defaults = array(
|
||||
|
@ -260,6 +236,8 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
|||
$data = $this->get_cached_data( $cache_key );
|
||||
|
||||
if ( false === $data ) {
|
||||
$this->initialize_queries();
|
||||
|
||||
$data = (object) array(
|
||||
'data' => array(),
|
||||
'total' => 0,
|
||||
|
@ -268,69 +246,51 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
|||
);
|
||||
|
||||
$selections = $this->selected_columns( $query_args );
|
||||
$sql_query_params = $this->get_sql_query_params( $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->get_sql_query_params( $query_args );
|
||||
|
||||
if ( count( $included_coupons ) > 0 ) {
|
||||
$total_results = count( $included_coupons );
|
||||
$total_pages = (int) ceil( $total_results / $sql_query_params['per_page'] );
|
||||
$total_pages = (int) ceil( $total_results / $limit_params['per_page'] );
|
||||
|
||||
$fields = $this->get_fields( $query_args );
|
||||
$join_selections = $this->format_join_selections( $fields, array( 'coupon_id' ) );
|
||||
$ids_table = $this->get_ids_table( $included_coupons, 'coupon_id' );
|
||||
$fields = $this->get_fields( $query_args );
|
||||
$ids_table = $this->get_ids_table( $included_coupons, 'coupon_id' );
|
||||
|
||||
$prefix = "SELECT {$join_selections} FROM (";
|
||||
$suffix = ") AS {$table_name}";
|
||||
$right_join = "RIGHT JOIN ( {$ids_table} ) AS default_results
|
||||
ON default_results.coupon_id = {$table_name}.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' ) );
|
||||
$coupons_query = $this->subquery->get_query_statement();
|
||||
|
||||
$this->subquery->clear_sql_clause( array( 'select', 'order_by' ) );
|
||||
$this->subquery->add_sql_clause( 'select', 'coupon_id' );
|
||||
|
||||
$db_records_count = (int) $wpdb->get_var(
|
||||
"SELECT COUNT(*) FROM (
|
||||
SELECT
|
||||
coupon_id
|
||||
FROM
|
||||
{$table_name}
|
||||
{$sql_query_params['from_clause']}
|
||||
WHERE
|
||||
1=1
|
||||
{$sql_query_params['where_time_clause']}
|
||||
{$sql_query_params['where_clause']}
|
||||
GROUP BY
|
||||
coupon_id
|
||||
) AS tt"
|
||||
{$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 / $sql_query_params['per_page'] );
|
||||
$total_pages = (int) ceil( $db_records_count / $limit_params['per_page'] );
|
||||
if ( $query_args['page'] < 1 || $query_args['page'] > $total_pages ) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
$prefix = '';
|
||||
$suffix = '';
|
||||
$right_join = '';
|
||||
}
|
||||
|
||||
$coupon_data = $wpdb->get_results(
|
||||
"${prefix}
|
||||
SELECT
|
||||
{$selections}
|
||||
FROM
|
||||
{$table_name}
|
||||
{$sql_query_params['from_clause']}
|
||||
WHERE
|
||||
1=1
|
||||
{$sql_query_params['where_time_clause']}
|
||||
{$sql_query_params['where_clause']}
|
||||
GROUP BY
|
||||
coupon_id
|
||||
{$suffix}
|
||||
{$right_join}
|
||||
{$sql_query_params['outer_from_clause']}
|
||||
ORDER BY
|
||||
{$sql_query_params['order_by_clause']}
|
||||
{$sql_query_params['limit']}
|
||||
",
|
||||
$coupons_query,
|
||||
ARRAY_A
|
||||
); // WPCS: cache ok, DB call ok, unprepared SQL ok.
|
||||
|
||||
|
@ -388,7 +348,7 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
|||
}
|
||||
|
||||
$result = $wpdb->replace(
|
||||
$wpdb->prefix . self::TABLE_NAME,
|
||||
self::get_db_table_name(),
|
||||
array(
|
||||
'order_id' => $order_id,
|
||||
'coupon_id' => $coupon_id,
|
||||
|
@ -426,15 +386,7 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
|||
public static function sync_on_order_delete( $order_id ) {
|
||||
global $wpdb;
|
||||
|
||||
$table_name = $wpdb->prefix . self::TABLE_NAME;
|
||||
|
||||
$wpdb->query(
|
||||
$wpdb->prepare(
|
||||
"DELETE FROM ${table_name} WHERE order_id = %d",
|
||||
$order_id
|
||||
)
|
||||
);
|
||||
|
||||
$wpdb->delete( self::get_db_table_name(), array( 'order_id' => $order_id ) );
|
||||
/**
|
||||
* Fires when coupon's reports are removed from database.
|
||||
*
|
||||
|
@ -460,7 +412,7 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
|||
}
|
||||
|
||||
$wpdb->delete(
|
||||
$wpdb->prefix . self::TABLE_NAME,
|
||||
self::get_db_table_name(),
|
||||
array( 'coupon_id' => $post_id )
|
||||
);
|
||||
|
||||
|
@ -474,15 +426,25 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
|||
* @param array $args Array of args to filter the query by. Supports `include`.
|
||||
* @return array Array of results.
|
||||
*/
|
||||
public static function get_coupons( $args ) {
|
||||
public function get_coupons( $args ) {
|
||||
global $wpdb;
|
||||
$query = "SELECT ID, post_title FROM {$wpdb->prefix}posts WHERE post_type='shop_coupon'";
|
||||
$query = "SELECT ID, post_title FROM {$wpdb->posts} WHERE post_type='shop_coupon'";
|
||||
|
||||
if ( ! empty( $args['include'] ) ) {
|
||||
$included_coupons = implode( ',', $args['include'] );
|
||||
$query .= " AND ID IN ({$included_coupons})";
|
||||
$included_coupons = $this->get_included_coupons( $args, 'include' );
|
||||
if ( ! empty( $included_coupons ) ) {
|
||||
$query .= " AND ID IN ({$included_coupons})";
|
||||
}
|
||||
|
||||
return $wpdb->get_results( $query ); // WPCS: cache ok, DB call ok, unprepared SQL ok.
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize query objects.
|
||||
*/
|
||||
protected function initialize_queries() {
|
||||
$this->clear_all_clauses();
|
||||
$this->subquery = new SqlQuery( $this->context . '_subquery' );
|
||||
$this->subquery->add_sql_clause( 'from', self::get_db_table_name() );
|
||||
$this->subquery->add_sql_clause( 'group_by', 'coupon_id' );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ defined( 'ABSPATH' ) || exit;
|
|||
use \Automattic\WooCommerce\Admin\API\Reports\Coupons\DataStore as CouponsDataStore;
|
||||
use \Automattic\WooCommerce\Admin\API\Reports\DataStoreInterface;
|
||||
use \Automattic\WooCommerce\Admin\API\Reports\TimeInterval;
|
||||
use \Automattic\WooCommerce\Admin\API\Reports\SqlQuery;
|
||||
|
||||
/**
|
||||
* API\Reports\Coupons\Stats\DataStore.
|
||||
|
@ -36,11 +37,14 @@ class DataStore extends CouponsDataStore implements DataStoreInterface {
|
|||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $report_columns = array(
|
||||
'amount' => 'SUM(discount_amount) as amount',
|
||||
'coupons_count' => 'COUNT(DISTINCT coupon_id) as coupons_count',
|
||||
'orders_count' => 'COUNT(DISTINCT order_id) as orders_count',
|
||||
);
|
||||
protected $report_columns;
|
||||
|
||||
/**
|
||||
* Data store context used to pass to filters.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $context = 'coupon_stats';
|
||||
|
||||
/**
|
||||
* Cache identifier.
|
||||
|
@ -50,48 +54,56 @@ class DataStore extends CouponsDataStore implements DataStoreInterface {
|
|||
protected $cache_key = 'coupons_stats';
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* Assign report columns once full table name has been assigned.
|
||||
*/
|
||||
public function __construct() {
|
||||
global $wpdb;
|
||||
$table_name = $wpdb->prefix . self::TABLE_NAME;
|
||||
// Avoid ambigious column order_id in SQL query.
|
||||
$this->report_columns['orders_count'] = str_replace( 'order_id', $table_name . '.order_id', $this->report_columns['orders_count'] );
|
||||
protected function assign_report_columns() {
|
||||
$table_name = self::get_db_table_name();
|
||||
$this->report_columns = array(
|
||||
'amount' => 'SUM(discount_amount) as amount',
|
||||
'coupons_count' => 'COUNT(DISTINCT coupon_id) as coupons_count',
|
||||
'orders_count' => "COUNT(DISTINCT {$table_name}.order_id) as orders_count",
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the database query with parameters used for Products Stats report: categories and order status.
|
||||
*
|
||||
* @param array $query_args Query arguments supplied by the user.
|
||||
* @param array $totals_params SQL parameters for the totals query.
|
||||
* @param array $intervals_params SQL parameters for the intervals query.
|
||||
*/
|
||||
protected function update_sql_query_params( $query_args, &$totals_params, &$intervals_params ) {
|
||||
protected function update_sql_query_params( $query_args ) {
|
||||
global $wpdb;
|
||||
|
||||
$coupons_where_clause = '';
|
||||
$coupons_from_clause = '';
|
||||
$clauses = array(
|
||||
'where' => '',
|
||||
'join' => '',
|
||||
);
|
||||
|
||||
$order_coupon_lookup_table = $wpdb->prefix . self::TABLE_NAME;
|
||||
$order_coupon_lookup_table = self::get_db_table_name();
|
||||
|
||||
$included_coupons = $this->get_included_coupons( $query_args );
|
||||
$included_coupons = $this->get_included_coupons( $query_args, 'coupons' );
|
||||
if ( $included_coupons ) {
|
||||
$coupons_where_clause .= " AND {$order_coupon_lookup_table}.coupon_id IN ({$included_coupons})";
|
||||
$clauses['where'] .= " AND {$order_coupon_lookup_table}.coupon_id IN ({$included_coupons})";
|
||||
}
|
||||
|
||||
$order_status_filter = $this->get_status_subquery( $query_args );
|
||||
if ( $order_status_filter ) {
|
||||
$coupons_from_clause .= " JOIN {$wpdb->prefix}wc_order_stats ON {$order_coupon_lookup_table}.order_id = {$wpdb->prefix}wc_order_stats.order_id";
|
||||
$coupons_where_clause .= " AND ( {$order_status_filter} )";
|
||||
$clauses['join'] .= " JOIN {$wpdb->prefix}wc_order_stats ON {$order_coupon_lookup_table}.order_id = {$wpdb->prefix}wc_order_stats.order_id";
|
||||
$clauses['where'] .= " AND ( {$order_status_filter} )";
|
||||
}
|
||||
|
||||
$totals_params = array_merge( $totals_params, $this->get_time_period_sql_params( $query_args, $order_coupon_lookup_table ) );
|
||||
$totals_params['where_clause'] .= $coupons_where_clause;
|
||||
$totals_params['from_clause'] .= $coupons_from_clause;
|
||||
$this->get_time_period_sql_params( $query_args, $order_coupon_lookup_table );
|
||||
$this->get_intervals_sql_params( $query_args, $order_coupon_lookup_table );
|
||||
$clauses['where_time'] = $this->get_sql_clause( 'where_time' );
|
||||
|
||||
$intervals_params = array_merge( $intervals_params, $this->get_intervals_sql_params( $query_args, $order_coupon_lookup_table ) );
|
||||
$intervals_params['where_clause'] .= $coupons_where_clause;
|
||||
$intervals_params['from_clause'] .= $coupons_from_clause;
|
||||
$this->interval_query->add_sql_clause( 'limit', $this->get_sql_clause( 'limit' ) );
|
||||
$this->interval_query->add_sql_clause( 'order_by', $this->get_sql_clause( 'order_by' ) );
|
||||
$this->interval_query->add_sql_clause( 'select', $this->get_sql_clause( 'select' ) );
|
||||
$this->interval_query->add_sql_clause( 'select', 'AS time_interval' );
|
||||
|
||||
foreach ( array( 'join', 'where_time', 'where' ) as $clause ) {
|
||||
$this->interval_query->add_sql_clause( $clause, $clauses[ $clause ] );
|
||||
$this->total_query->add_sql_clause( $clause, $clauses[ $clause ] );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -104,7 +116,7 @@ class DataStore extends CouponsDataStore implements DataStoreInterface {
|
|||
public function get_data( $query_args ) {
|
||||
global $wpdb;
|
||||
|
||||
$table_name = $wpdb->prefix . self::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.
|
||||
$defaults = array(
|
||||
|
@ -129,6 +141,8 @@ class DataStore extends CouponsDataStore implements DataStoreInterface {
|
|||
$data = $this->get_cached_data( $cache_key );
|
||||
|
||||
if ( false === $data ) {
|
||||
$this->initialize_queries();
|
||||
|
||||
$data = (object) array(
|
||||
'data' => array(),
|
||||
'total' => 0,
|
||||
|
@ -139,73 +153,57 @@ class DataStore extends CouponsDataStore implements DataStoreInterface {
|
|||
$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(
|
||||
"SELECT
|
||||
{$intervals_query['select_clause']} AS time_interval
|
||||
FROM
|
||||
{$table_name}
|
||||
{$intervals_query['from_clause']}
|
||||
WHERE
|
||||
1=1
|
||||
{$intervals_query['where_time_clause']}
|
||||
{$intervals_query['where_clause']}
|
||||
GROUP BY
|
||||
time_interval"
|
||||
$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 / $intervals_query['per_page'] );
|
||||
$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(
|
||||
"SELECT
|
||||
{$selections}
|
||||
FROM
|
||||
{$table_name}
|
||||
{$totals_query['from_clause']}
|
||||
WHERE
|
||||
1=1
|
||||
{$totals_query['where_time_clause']}
|
||||
{$totals_query['where_clause']}",
|
||||
$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( $intervals_query, $query_args, $db_interval_count, $expected_interval_count, $table_name );
|
||||
$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 ) {
|
||||
$selections = ', ' . $selections;
|
||||
$this->interval_query->add_sql_clause( 'select', ', ' . $selections );
|
||||
}
|
||||
|
||||
$intervals = $wpdb->get_results(
|
||||
"SELECT
|
||||
MAX({$table_name}.date_created) AS datetime_anchor,
|
||||
{$intervals_query['select_clause']} AS time_interval
|
||||
{$selections}
|
||||
FROM
|
||||
{$table_name}
|
||||
{$intervals_query['from_clause']}
|
||||
WHERE
|
||||
1=1
|
||||
{$intervals_query['where_time_clause']}
|
||||
{$intervals_query['where_clause']}
|
||||
GROUP BY
|
||||
time_interval
|
||||
ORDER BY
|
||||
{$intervals_query['order_by_clause']}
|
||||
{$intervals_query['limit']}",
|
||||
$this->interval_query->get_query_statement(),
|
||||
ARRAY_A
|
||||
); // WPCS: cache ok, DB call ok, unprepared SQL ok.
|
||||
|
||||
|
@ -221,10 +219,10 @@ class DataStore extends CouponsDataStore implements DataStoreInterface {
|
|||
'page_no' => (int) $query_args['page'],
|
||||
);
|
||||
|
||||
if ( TimeInterval::intervals_missing( $expected_interval_count, $db_interval_count, $intervals_query['per_page'], $query_args['page'], $query_args['order'], $query_args['orderby'], count( $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'], $intervals_query['per_page'], $db_interval_count, $expected_interval_count, $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 );
|
||||
}
|
||||
|
@ -236,4 +234,18 @@ class DataStore extends CouponsDataStore implements DataStoreInterface {
|
|||
|
||||
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' );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ defined( 'ABSPATH' ) || exit;
|
|||
use \Automattic\WooCommerce\Admin\API\Reports\DataStore as ReportsDataStore;
|
||||
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\Cache as ReportsCache;
|
||||
|
||||
/**
|
||||
|
@ -24,7 +25,7 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
|||
*
|
||||
* @var string
|
||||
*/
|
||||
const TABLE_NAME = 'wc_customer_lookup';
|
||||
protected static $table_name = 'wc_customer_lookup';
|
||||
|
||||
/**
|
||||
* Cache identifier.
|
||||
|
@ -47,36 +48,35 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
|||
);
|
||||
|
||||
/**
|
||||
* SQL columns to select in the db query and their mapping to SQL code.
|
||||
* Data store context used to pass to filters.
|
||||
*
|
||||
* @var array
|
||||
* @var string
|
||||
*/
|
||||
protected $report_columns = array(
|
||||
'id' => 'customer_id as id',
|
||||
'user_id' => 'user_id',
|
||||
'username' => 'username',
|
||||
'name' => "CONCAT_WS( ' ', first_name, last_name ) as name", // @todo What does this mean for RTL?
|
||||
'email' => 'email',
|
||||
'country' => 'country',
|
||||
'city' => 'city',
|
||||
'state' => 'state',
|
||||
'postcode' => 'postcode',
|
||||
'date_registered' => 'date_registered',
|
||||
'date_last_active' => 'IF( date_last_active <= "0000-00-00 00:00:00", NULL, date_last_active ) AS date_last_active',
|
||||
'orders_count' => 'SUM( CASE WHEN parent_id = 0 THEN 1 ELSE 0 END ) as orders_count',
|
||||
'total_spend' => 'SUM( gross_total ) as total_spend',
|
||||
'avg_order_value' => '( SUM( gross_total ) / COUNT( order_id ) ) as avg_order_value',
|
||||
);
|
||||
protected $context = 'customers';
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
* Assign report columns once full table name has been assigned.
|
||||
*/
|
||||
public function __construct() {
|
||||
protected function assign_report_columns() {
|
||||
global $wpdb;
|
||||
|
||||
// Initialize some report columns that need disambiguation.
|
||||
$this->report_columns['id'] = $wpdb->prefix . self::TABLE_NAME . '.customer_id as id';
|
||||
$this->report_columns['date_last_order'] = "MAX( {$wpdb->prefix}wc_order_stats.date_created ) as date_last_order";
|
||||
$table_name = self::get_db_table_name();
|
||||
$this->report_columns = array(
|
||||
'id' => "{$table_name}.customer_id as id",
|
||||
'user_id' => 'user_id',
|
||||
'username' => 'username',
|
||||
'name' => "CONCAT_WS( ' ', first_name, last_name ) as name", // @todo What does this mean for RTL?
|
||||
'email' => 'email',
|
||||
'country' => 'country',
|
||||
'city' => 'city',
|
||||
'state' => 'state',
|
||||
'postcode' => 'postcode',
|
||||
'date_registered' => 'date_registered',
|
||||
'date_last_active' => 'IF( date_last_active <= "0000-00-00 00:00:00", NULL, date_last_active ) AS date_last_active',
|
||||
'date_last_order' => "MAX( {$wpdb->prefix}wc_order_stats.date_created ) as date_last_order",
|
||||
'orders_count' => 'SUM( CASE WHEN parent_id = 0 THEN 1 ELSE 0 END ) as orders_count',
|
||||
'total_spend' => 'SUM( gross_total ) as total_spend',
|
||||
'avg_order_value' => '( SUM( gross_total ) / COUNT( order_id ) ) as avg_order_value',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -115,43 +115,16 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
|||
return $order_by;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fills ORDER BY clause of SQL request based on user supplied parameters.
|
||||
*
|
||||
* @param array $query_args Parameters supplied by the user.
|
||||
* @return array
|
||||
*/
|
||||
protected function get_order_by_sql_params( $query_args ) {
|
||||
$sql_query['order_by_clause'] = '';
|
||||
|
||||
if ( isset( $query_args['orderby'] ) ) {
|
||||
$sql_query['order_by_clause'] = $this->normalize_order_by( $query_args['orderby'] );
|
||||
}
|
||||
|
||||
if ( isset( $query_args['order'] ) ) {
|
||||
$sql_query['order_by_clause'] .= ' ' . $query_args['order'];
|
||||
} else {
|
||||
$sql_query['order_by_clause'] .= ' DESC';
|
||||
}
|
||||
|
||||
return $sql_query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fills WHERE clause of SQL request with date-related constraints.
|
||||
*
|
||||
* @param array $query_args Parameters supplied by the user.
|
||||
* @param string $table_name Name of the db table relevant for the date constraint.
|
||||
* @return array
|
||||
*/
|
||||
protected function get_time_period_sql_params( $query_args, $table_name ) {
|
||||
global $wpdb;
|
||||
|
||||
$sql_query = array(
|
||||
'where_time_clause' => '',
|
||||
'where_clause' => '',
|
||||
'having_clause' => '',
|
||||
);
|
||||
$this->clear_sql_clause( array( 'where', 'where_time', 'having' ) );
|
||||
$date_param_mapping = array(
|
||||
'registered' => array(
|
||||
'clause' => 'where',
|
||||
|
@ -202,31 +175,28 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
|||
}
|
||||
|
||||
if ( $where_time_clauses ) {
|
||||
$sql_query['where_time_clause'] = ' AND ' . implode( " {$match_operator} ", $where_time_clauses );
|
||||
$this->subquery->add_sql_clause( 'where_time', 'AND ' . implode( " {$match_operator} ", $where_time_clauses ) );
|
||||
}
|
||||
|
||||
if ( $having_time_clauses ) {
|
||||
$sql_query['having_clause'] = ' AND ' . implode( " {$match_operator} ", $having_time_clauses );
|
||||
$this->subquery->add_sql_clause( 'having', 'AND ' . implode( " {$match_operator} ", $having_time_clauses ) );
|
||||
}
|
||||
|
||||
return $sql_query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the database query with parameters used for Customers report: categories and order status.
|
||||
*
|
||||
* @param array $query_args Query arguments supplied by the user.
|
||||
* @return array Array of parameters used for SQL query.
|
||||
*/
|
||||
protected function get_sql_query_params( $query_args ) {
|
||||
global $wpdb;
|
||||
$customer_lookup_table = $wpdb->prefix . self::TABLE_NAME;
|
||||
$customer_lookup_table = self::get_db_table_name();
|
||||
$order_stats_table_name = $wpdb->prefix . 'wc_order_stats';
|
||||
|
||||
$sql_query_params = $this->get_time_period_sql_params( $query_args, $customer_lookup_table );
|
||||
$sql_query_params = array_merge( $sql_query_params, $this->get_limit_sql_params( $query_args ) );
|
||||
$sql_query_params = array_merge( $sql_query_params, $this->get_order_by_sql_params( $query_args ) );
|
||||
$sql_query_params['from_clause'] = " LEFT JOIN {$order_stats_table_name} ON {$customer_lookup_table}.customer_id = {$order_stats_table_name}.customer_id";
|
||||
$this->get_time_period_sql_params( $query_args, $customer_lookup_table );
|
||||
$this->get_limit_sql_params( $query_args );
|
||||
$this->get_order_by_sql_params( $query_args );
|
||||
$this->subquery->add_sql_clause( 'left_join', "LEFT JOIN {$order_stats_table_name} ON {$customer_lookup_table}.customer_id = {$order_stats_table_name}.customer_id" );
|
||||
|
||||
$match_operator = $this->get_match_operator( $query_args );
|
||||
$where_clauses = array();
|
||||
|
@ -279,7 +249,7 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
|||
|
||||
// Allow a list of customer IDs to be specified.
|
||||
if ( ! empty( $query_args['customers'] ) ) {
|
||||
$included_customers = implode( ',', array_map( 'intval', $query_args['customers'] ) );
|
||||
$included_customers = $this->get_filtered_ids( $query_args, 'customers' );
|
||||
$where_clauses[] = "{$customer_lookup_table}.customer_id IN ({$included_customers})";
|
||||
}
|
||||
|
||||
|
@ -324,21 +294,19 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
|||
}
|
||||
|
||||
if ( $where_clauses ) {
|
||||
$preceding_match = empty( $sql_query_params['where_time_clause'] ) ? ' AND ' : " {$match_operator} ";
|
||||
$sql_query_params['where_clause'] = $preceding_match . implode( " {$match_operator} ", $where_clauses );
|
||||
$preceding_match = empty( $this->get_sql_clause( 'where_time' ) ) ? ' AND ' : " {$match_operator} ";
|
||||
$this->subquery->add_sql_clause( 'where', $preceding_match . implode( " {$match_operator} ", $where_clauses ) );
|
||||
}
|
||||
|
||||
$order_status_filter = $this->get_status_subquery( $query_args );
|
||||
if ( $order_status_filter ) {
|
||||
$sql_query_params['from_clause'] .= " AND ( {$order_status_filter} )";
|
||||
$this->subquery->add_sql_clause( 'left_join', "AND ( {$order_status_filter} )" );
|
||||
}
|
||||
|
||||
if ( $having_clauses ) {
|
||||
$preceding_match = empty( $sql_query_params['having_clause'] ) ? ' AND ' : " {$match_operator} ";
|
||||
$sql_query_params['having_clause'] .= $preceding_match . implode( " {$match_operator} ", $having_clauses );
|
||||
$preceding_match = empty( $this->get_sql_clause( 'having' ) ) ? ' AND ' : " {$match_operator} ";
|
||||
$this->subquery->add_sql_clause( 'having', $preceding_match . implode( " {$match_operator} ", $having_clauses ) );
|
||||
}
|
||||
|
||||
return $sql_query_params;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -350,7 +318,7 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
|||
public function get_data( $query_args ) {
|
||||
global $wpdb;
|
||||
|
||||
$customers_table_name = $wpdb->prefix . self::TABLE_NAME;
|
||||
$customers_table_name = self::get_db_table_name();
|
||||
$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.
|
||||
|
@ -374,6 +342,8 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
|||
$data = $this->get_cached_data( $cache_key );
|
||||
|
||||
if ( false === $data ) {
|
||||
$this->initialize_queries();
|
||||
|
||||
$data = (object) array(
|
||||
'data' => array(),
|
||||
'total' => 0,
|
||||
|
@ -386,46 +356,23 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
|||
|
||||
$db_records_count = (int) $wpdb->get_var(
|
||||
"SELECT COUNT(*) FROM (
|
||||
SELECT {$customers_table_name}.customer_id
|
||||
FROM
|
||||
{$customers_table_name}
|
||||
{$sql_query_params['from_clause']}
|
||||
WHERE
|
||||
1=1
|
||||
{$sql_query_params['where_time_clause']}
|
||||
{$sql_query_params['where_clause']}
|
||||
GROUP BY
|
||||
{$customers_table_name}.customer_id
|
||||
HAVING
|
||||
1=1
|
||||
{$sql_query_params['having_clause']}
|
||||
{$this->subquery->get_query_statement()}
|
||||
) as tt
|
||||
"
|
||||
); // WPCS: cache ok, DB call ok, unprepared SQL ok.
|
||||
|
||||
$total_pages = (int) ceil( $db_records_count / $sql_query_params['per_page'] );
|
||||
$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(
|
||||
"SELECT
|
||||
{$selections}
|
||||
FROM
|
||||
{$customers_table_name}
|
||||
{$sql_query_params['from_clause']}
|
||||
WHERE
|
||||
1=1
|
||||
{$sql_query_params['where_time_clause']}
|
||||
{$sql_query_params['where_clause']}
|
||||
GROUP BY
|
||||
{$customers_table_name}.customer_id
|
||||
HAVING
|
||||
1=1
|
||||
{$sql_query_params['having_clause']}
|
||||
ORDER BY
|
||||
{$sql_query_params['order_by_clause']}
|
||||
{$sql_query_params['limit']}
|
||||
",
|
||||
$this->subquery->get_query_statement(),
|
||||
ARRAY_A
|
||||
); // WPCS: cache ok, DB call ok, unprepared SQL ok.
|
||||
|
||||
|
@ -542,7 +489,7 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
|||
$format[] = '%s';
|
||||
}
|
||||
|
||||
$result = $wpdb->insert( $wpdb->prefix . self::TABLE_NAME, $data, $format );
|
||||
$result = $wpdb->insert( self::get_db_table_name(), $data, $format );
|
||||
$customer_id = $wpdb->insert_id;
|
||||
|
||||
/**
|
||||
|
@ -564,7 +511,7 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
|||
public static function get_guest_id_by_email( $email ) {
|
||||
global $wpdb;
|
||||
|
||||
$table_name = $wpdb->prefix . self::TABLE_NAME;
|
||||
$table_name = self::get_db_table_name();
|
||||
$customer_id = $wpdb->get_var(
|
||||
$wpdb->prepare(
|
||||
"SELECT customer_id FROM {$table_name} WHERE email = %s AND user_id IS NULL LIMIT 1",
|
||||
|
@ -584,7 +531,7 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
|||
public static function get_customer_id_by_user_id( $user_id ) {
|
||||
global $wpdb;
|
||||
|
||||
$table_name = $wpdb->prefix . self::TABLE_NAME;
|
||||
$table_name = self::get_db_table_name();
|
||||
$customer_id = $wpdb->get_var(
|
||||
$wpdb->prepare(
|
||||
"SELECT customer_id FROM {$table_name} WHERE user_id = %d LIMIT 1",
|
||||
|
@ -680,7 +627,7 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
|||
$format[] = '%d';
|
||||
}
|
||||
|
||||
$results = $wpdb->replace( $wpdb->prefix . self::TABLE_NAME, $data, $format );
|
||||
$results = $wpdb->replace( self::get_db_table_name(), $data, $format );
|
||||
|
||||
/**
|
||||
* Fires when customser's reports are updated.
|
||||
|
@ -723,14 +670,8 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
|||
public static function delete_customer( $customer_id ) {
|
||||
global $wpdb;
|
||||
$customer_id = (int) $customer_id;
|
||||
$table_name = $wpdb->prefix . self::TABLE_NAME;
|
||||
|
||||
$wpdb->query(
|
||||
$wpdb->prepare(
|
||||
"DELETE FROM ${table_name} WHERE customer_id = %d",
|
||||
$customer_id
|
||||
)
|
||||
);
|
||||
$wpdb->delete( self::get_db_table_name(), array( 'customer_id' => $customer_id ) );
|
||||
|
||||
/**
|
||||
* Fires when a customer is deleted.
|
||||
|
@ -739,4 +680,16 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
|||
*/
|
||||
do_action( 'woocommerce_reports_delete_customer', $customer_id );
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize query objects.
|
||||
*/
|
||||
protected function initialize_queries() {
|
||||
$this->clear_all_clauses();
|
||||
$table_name = self::get_db_table_name();
|
||||
$this->subquery = new SqlQuery( $this->context . '_subquery' );
|
||||
$this->subquery->add_sql_clause( 'from', $table_name );
|
||||
$this->subquery->add_sql_clause( 'select', "{$table_name}.customer_id" );
|
||||
$this->subquery->add_sql_clause( 'group_by', "{$table_name}.customer_id" );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,18 +28,6 @@ class DataStore extends CustomersDataStore implements DataStoreInterface {
|
|||
'avg_avg_order_value' => 'floatval',
|
||||
);
|
||||
|
||||
/**
|
||||
* SQL columns to select in the db query and their mapping to SQL code.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $report_columns = array(
|
||||
'customers_count' => 'COUNT( * ) as customers_count',
|
||||
'avg_orders_count' => 'AVG( orders_count ) as avg_orders_count',
|
||||
'avg_total_spend' => 'AVG( total_spend ) as avg_total_spend',
|
||||
'avg_avg_order_value' => 'AVG( avg_order_value ) as avg_avg_order_value',
|
||||
);
|
||||
|
||||
/**
|
||||
* Cache identifier.
|
||||
*
|
||||
|
@ -48,10 +36,22 @@ class DataStore extends CustomersDataStore implements DataStoreInterface {
|
|||
protected $cache_key = 'customers_stats';
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
* Data store context used to pass to filters.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public function __construct() {
|
||||
// This space intentionally left blank (to avoid parent constructor).
|
||||
protected $context = 'customer_stats';
|
||||
|
||||
/**
|
||||
* Assign report columns once full table name has been assigned.
|
||||
*/
|
||||
protected function assign_report_columns() {
|
||||
$this->report_columns = array(
|
||||
'customers_count' => 'COUNT( * ) as customers_count',
|
||||
'avg_orders_count' => 'AVG( orders_count ) as avg_orders_count',
|
||||
'avg_total_spend' => 'AVG( total_spend ) as avg_total_spend',
|
||||
'avg_avg_order_value' => 'AVG( avg_order_value ) as avg_avg_order_value',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -63,7 +63,7 @@ class DataStore extends CustomersDataStore implements DataStoreInterface {
|
|||
public function get_data( $query_args ) {
|
||||
global $wpdb;
|
||||
|
||||
$customers_table_name = $wpdb->prefix . self::TABLE_NAME;
|
||||
$customers_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(
|
||||
|
@ -84,6 +84,8 @@ class DataStore extends CustomersDataStore implements DataStoreInterface {
|
|||
$data = $this->get_cached_data( $cache_key );
|
||||
|
||||
if ( false === $data ) {
|
||||
$this->initialize_queries();
|
||||
|
||||
$data = (object) array(
|
||||
'customers_count' => 0,
|
||||
'avg_orders_count' => 0,
|
||||
|
@ -93,32 +95,23 @@ class DataStore extends CustomersDataStore implements DataStoreInterface {
|
|||
|
||||
$selections = $this->selected_columns( $query_args );
|
||||
$sql_query_params = $this->get_sql_query_params( $query_args );
|
||||
// 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( gross_total ) AS total_spend,' );
|
||||
$this->subquery->add_sql_clause(
|
||||
'select',
|
||||
'CASE WHEN COUNT( order_id ) = 0 THEN NULL ELSE COUNT( order_id ) END AS orders_count,'
|
||||
);
|
||||
$this->subquery->add_sql_clause(
|
||||
'select',
|
||||
'CASE WHEN COUNT( order_id ) = 0 THEN NULL ELSE SUM( gross_total ) / COUNT( order_id ) 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(
|
||||
"SELECT {$selections} FROM
|
||||
(
|
||||
SELECT
|
||||
(
|
||||
CASE WHEN COUNT( order_id ) = 0
|
||||
THEN NULL
|
||||
ELSE COUNT( order_id )
|
||||
END
|
||||
) as orders_count,
|
||||
SUM( gross_total ) as total_spend,
|
||||
( SUM( gross_total ) / COUNT( order_id ) ) as avg_order_value
|
||||
FROM
|
||||
{$customers_table_name}
|
||||
{$sql_query_params['from_clause']}
|
||||
WHERE
|
||||
1=1
|
||||
{$sql_query_params['where_time_clause']}
|
||||
{$sql_query_params['where_clause']}
|
||||
GROUP BY
|
||||
{$customers_table_name}.customer_id
|
||||
HAVING
|
||||
1=1
|
||||
{$sql_query_params['having_clause']}
|
||||
) as tt",
|
||||
$this->get_query_statement(),
|
||||
ARRAY_A
|
||||
); // WPCS: cache ok, DB call ok, unprepared SQL ok.
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ use \Automattic\WooCommerce\Admin\API\Reports\TimeInterval;
|
|||
/**
|
||||
* Admin\API\Reports\DataStore: Common parent for custom report data stores.
|
||||
*/
|
||||
class DataStore {
|
||||
class DataStore extends SqlQuery {
|
||||
|
||||
/**
|
||||
* Cache group for the reports.
|
||||
|
@ -37,7 +37,7 @@ class DataStore {
|
|||
*
|
||||
* @var string
|
||||
*/
|
||||
const TABLE_NAME = '';
|
||||
protected static $table_name = '';
|
||||
|
||||
/**
|
||||
* Mapping columns to data type to return correct response types.
|
||||
|
@ -46,13 +46,6 @@ class DataStore {
|
|||
*/
|
||||
protected $column_types = array();
|
||||
|
||||
/**
|
||||
* SQL columns to select in the db query.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $report_columns = array();
|
||||
|
||||
// @todo This does not really belong here, maybe factor out the comparison as separate class?
|
||||
/**
|
||||
* Order by property, used in the cmp function.
|
||||
|
@ -66,6 +59,65 @@ class DataStore {
|
|||
* @var string
|
||||
*/
|
||||
private $order = '';
|
||||
/**
|
||||
* Query limit parameters.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $limit_parameters = array();
|
||||
/**
|
||||
* Data store context used to pass to filters.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $context = 'reports';
|
||||
|
||||
/**
|
||||
* Subquery object for query nesting.
|
||||
*
|
||||
* @var SqlQuery
|
||||
*/
|
||||
protected $subquery;
|
||||
|
||||
/**
|
||||
* Totals query object.
|
||||
*
|
||||
* @var SqlQuery
|
||||
*/
|
||||
protected $total_query;
|
||||
|
||||
/**
|
||||
* Intervals query object.
|
||||
*
|
||||
* @var SqlQuery
|
||||
*/
|
||||
protected $interval_query;
|
||||
|
||||
/**
|
||||
* Class constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
self::set_db_table_name();
|
||||
$this->assign_report_columns();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get table name from database class.
|
||||
*/
|
||||
public static function get_db_table_name() {
|
||||
global $wpdb;
|
||||
return isset( $wpdb->{static::$table_name} ) ? $wpdb->{static::$table_name} : $wpdb->prefix . static::$table_name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set table name from database class.
|
||||
*/
|
||||
protected static function set_db_table_name() {
|
||||
global $wpdb;
|
||||
if ( static::$table_name && ! isset( $wpdb->{static::$table_name} ) ) {
|
||||
$wpdb->{static::$table_name} = $wpdb->prefix . static::$table_name;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns string to be used as cache key for the data.
|
||||
|
@ -328,16 +380,17 @@ class DataStore {
|
|||
* If there are less records in the database than time intervals, then we need to remap offset in SQL query
|
||||
* to fetch correct records.
|
||||
*
|
||||
* @param array $intervals_query Array with clauses for the Intervals SQL query.
|
||||
* @param array $query_args Query arguments.
|
||||
* @param int $db_interval_count Database interval count.
|
||||
* @param int $expected_interval_count Expected interval count on the output.
|
||||
* @param string $table_name Name of the db table relevant for the date constraint.
|
||||
*/
|
||||
protected function update_intervals_sql_params( &$intervals_query, &$query_args, $db_interval_count, $expected_interval_count, $table_name ) {
|
||||
protected function update_intervals_sql_params( &$query_args, $db_interval_count, $expected_interval_count, $table_name ) {
|
||||
if ( $db_interval_count === $expected_interval_count ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$params = $this->get_limit_params( $query_args );
|
||||
$local_tz = new \DateTimeZone( wc_timezone_string() );
|
||||
if ( 'date' === strtolower( $query_args['orderby'] ) ) {
|
||||
// page X in request translates to slightly different dates in the db, in case some
|
||||
|
@ -347,7 +400,7 @@ class DataStore {
|
|||
if ( 'asc' === strtolower( $query_args['order'] ) ) {
|
||||
// ORDER BY date ASC.
|
||||
$new_start_date = $query_args['after'];
|
||||
$intervals_to_skip = ( $query_args['page'] - 1 ) * $intervals_query['per_page'];
|
||||
$intervals_to_skip = ( $query_args['page'] - 1 ) * $params['per_page'];
|
||||
$latest_end_date = $query_args['before'];
|
||||
for ( $i = 0; $i < $intervals_to_skip; $i++ ) {
|
||||
if ( $new_start_date > $latest_end_date ) {
|
||||
|
@ -360,7 +413,7 @@ class DataStore {
|
|||
}
|
||||
|
||||
$new_end_date = clone $new_start_date;
|
||||
for ( $i = 0; $i < $intervals_query['per_page']; $i++ ) {
|
||||
for ( $i = 0; $i < $params['per_page']; $i++ ) {
|
||||
if ( $new_end_date > $latest_end_date ) {
|
||||
break;
|
||||
}
|
||||
|
@ -378,7 +431,7 @@ class DataStore {
|
|||
} else {
|
||||
// ORDER BY date DESC.
|
||||
$new_end_date = $query_args['before'];
|
||||
$intervals_to_skip = ( $query_args['page'] - 1 ) * $intervals_query['per_page'];
|
||||
$intervals_to_skip = ( $query_args['page'] - 1 ) * $params['per_page'];
|
||||
$earliest_start_date = $query_args['after'];
|
||||
for ( $i = 0; $i < $intervals_to_skip; $i++ ) {
|
||||
if ( $new_end_date < $earliest_start_date ) {
|
||||
|
@ -391,7 +444,7 @@ class DataStore {
|
|||
}
|
||||
|
||||
$new_start_date = clone $new_end_date;
|
||||
for ( $i = 0; $i < $intervals_query['per_page']; $i++ ) {
|
||||
for ( $i = 0; $i < $params['per_page']; $i++ ) {
|
||||
if ( $new_start_date < $earliest_start_date ) {
|
||||
break;
|
||||
}
|
||||
|
@ -413,21 +466,24 @@ class DataStore {
|
|||
$query_args['adj_before'] = $new_end_date;
|
||||
$adj_after = $new_start_date->format( TimeInterval::$sql_datetime_format );
|
||||
$adj_before = $new_end_date->format( TimeInterval::$sql_datetime_format );
|
||||
$intervals_query['where_time_clause'] = '';
|
||||
$intervals_query['where_time_clause'] .= " AND {$table_name}.date_created <= '$adj_before'";
|
||||
$intervals_query['where_time_clause'] .= " AND {$table_name}.date_created >= '$adj_after'";
|
||||
$intervals_query['limit'] = 'LIMIT 0,' . $intervals_query['per_page'];
|
||||
$this->interval_query->clear_sql_clause( array( 'where_time', 'limit' ) );
|
||||
$this->interval_query->add_sql_clause( 'where_time', "AND {$table_name}.date_created <= '$adj_before'" );
|
||||
$this->interval_query->add_sql_clause( 'where_time', "AND {$table_name}.date_created >= '$adj_after'" );
|
||||
$this->clear_sql_clause( 'limit' );
|
||||
$this->add_sql_clause( 'limit', 'LIMIT 0,' . $params['per_page'] );
|
||||
} else {
|
||||
if ( 'asc' === $query_args['order'] ) {
|
||||
$offset = ( ( $query_args['page'] - 1 ) * $intervals_query['per_page'] ) - ( $expected_interval_count - $db_interval_count );
|
||||
$offset = ( ( $query_args['page'] - 1 ) * $params['per_page'] ) - ( $expected_interval_count - $db_interval_count );
|
||||
$offset = $offset < 0 ? 0 : $offset;
|
||||
$count = $query_args['page'] * $intervals_query['per_page'] - ( $expected_interval_count - $db_interval_count );
|
||||
$count = $query_args['page'] * $params['per_page'] - ( $expected_interval_count - $db_interval_count );
|
||||
if ( $count < 0 ) {
|
||||
$count = 0;
|
||||
} elseif ( $count > $intervals_query['per_page'] ) {
|
||||
$count = $intervals_query['per_page'];
|
||||
} elseif ( $count > $params['per_page'] ) {
|
||||
$count = $params['per_page'];
|
||||
}
|
||||
$intervals_query['limit'] = 'LIMIT ' . $offset . ',' . $count;
|
||||
|
||||
$this->clear_sql_clause( 'limit' );
|
||||
$this->add_sql_clause( 'limit', 'LIMIT ' . $offset . ',' . $count );
|
||||
}
|
||||
// Otherwise no change in limit clause.
|
||||
// @todo - Do this without modifying $query_args?
|
||||
|
@ -592,14 +648,12 @@ class DataStore {
|
|||
*
|
||||
* @param array $query_args Parameters supplied by the user.
|
||||
* @param string $table_name Name of the db table relevant for the date constraint.
|
||||
* @return array
|
||||
*/
|
||||
protected function get_time_period_sql_params( $query_args, $table_name ) {
|
||||
$sql_query = array(
|
||||
'from_clause' => '',
|
||||
'where_time_clause' => '',
|
||||
'where_clause' => '',
|
||||
);
|
||||
$this->clear_sql_clause( array( 'from', 'where_time', 'where' ) );
|
||||
if ( isset( $this->subquery ) ) {
|
||||
$this->subquery->clear_sql_clause( 'where_time' );
|
||||
}
|
||||
|
||||
if ( isset( $query_args['before'] ) && '' !== $query_args['before'] ) {
|
||||
if ( is_a( $query_args['before'], 'WC_DateTime' ) ) {
|
||||
|
@ -607,8 +661,11 @@ class DataStore {
|
|||
} else {
|
||||
$datetime_str = $query_args['before']->format( TimeInterval::$sql_datetime_format );
|
||||
}
|
||||
$sql_query['where_time_clause'] .= " AND {$table_name}.date_created <= '$datetime_str'";
|
||||
|
||||
if ( isset( $this->subquery ) ) {
|
||||
$this->subquery->add_sql_clause( 'where_time', "AND {$table_name}.date_created <= '$datetime_str'" );
|
||||
} else {
|
||||
$this->add_sql_clause( 'where_time', "AND {$table_name}.date_created <= '$datetime_str'" );
|
||||
}
|
||||
}
|
||||
|
||||
if ( isset( $query_args['after'] ) && '' !== $query_args['after'] ) {
|
||||
|
@ -617,10 +674,12 @@ class DataStore {
|
|||
} else {
|
||||
$datetime_str = $query_args['after']->format( TimeInterval::$sql_datetime_format );
|
||||
}
|
||||
$sql_query['where_time_clause'] .= " AND {$table_name}.date_created >= '$datetime_str'";
|
||||
if ( isset( $this->subquery ) ) {
|
||||
$this->subquery->add_sql_clause( 'where_time', "AND {$table_name}.date_created >= '$datetime_str'" );
|
||||
} else {
|
||||
$this->add_sql_clause( 'where_time', "AND {$table_name}.date_created >= '$datetime_str'" );
|
||||
}
|
||||
}
|
||||
|
||||
return $sql_query;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -630,18 +689,32 @@ class DataStore {
|
|||
* @return array
|
||||
*/
|
||||
protected function get_limit_sql_params( $query_args ) {
|
||||
$sql_query['per_page'] = get_option( 'posts_per_page' );
|
||||
$params = $this->get_limit_params( $query_args );
|
||||
|
||||
$this->clear_sql_clause( 'limit' );
|
||||
$this->add_sql_clause( 'limit', "LIMIT {$params['offset']}, {$params['per_page']}" );
|
||||
return $params;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fills LIMIT parameters of SQL request based on user supplied parameters.
|
||||
*
|
||||
* @param array $query_args Parameters supplied by the user.
|
||||
* @return array
|
||||
*/
|
||||
protected function get_limit_params( $query_args = array() ) {
|
||||
if ( isset( $query_args['per_page'] ) && is_numeric( $query_args['per_page'] ) ) {
|
||||
$sql_query['per_page'] = (int) $query_args['per_page'];
|
||||
$this->limit_parameters['per_page'] = (int) $query_args['per_page'];
|
||||
} else {
|
||||
$this->limit_parameters['per_page'] = get_option( 'posts_per_page' );
|
||||
}
|
||||
|
||||
$sql_query['offset'] = 0;
|
||||
$this->limit_parameters['offset'] = 0;
|
||||
if ( isset( $query_args['page'] ) ) {
|
||||
$sql_query['offset'] = ( (int) $query_args['page'] - 1 ) * $sql_query['per_page'];
|
||||
$this->limit_parameters['offset'] = ( (int) $query_args['page'] - 1 ) * $this->limit_parameters['per_page'];
|
||||
}
|
||||
|
||||
$sql_query['limit'] = "LIMIT {$sql_query['offset']}, {$sql_query['per_page']}";
|
||||
return $sql_query;
|
||||
return $this->limit_parameters;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -703,21 +776,17 @@ class DataStore {
|
|||
* Fills ORDER BY clause of SQL request based on user supplied parameters.
|
||||
*
|
||||
* @param array $query_args Parameters supplied by the user.
|
||||
* @return array
|
||||
*/
|
||||
protected function get_order_by_sql_params( $query_args ) {
|
||||
$sql_query['order_by_clause'] = '';
|
||||
if ( isset( $query_args['orderby'] ) ) {
|
||||
$sql_query['order_by_clause'] = $this->normalize_order_by( $query_args['orderby'] );
|
||||
}
|
||||
|
||||
if ( isset( $query_args['order'] ) ) {
|
||||
$sql_query['order_by_clause'] .= ' ' . $query_args['order'];
|
||||
$order_by_clause = $this->normalize_order_by( $query_args['orderby'] );
|
||||
} else {
|
||||
$sql_query['order_by_clause'] .= ' DESC';
|
||||
$order_by_clause = '';
|
||||
}
|
||||
|
||||
return $sql_query;
|
||||
$this->clear_sql_clause( 'order_by' );
|
||||
$this->add_sql_clause( 'order_by', $order_by_clause );
|
||||
$this->add_orderby_order_clause( $query_args, $this );
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -725,27 +794,17 @@ class DataStore {
|
|||
*
|
||||
* @param array $query_args Parameters supplied by the user.
|
||||
* @param string $table_name Name of the db table relevant for the date constraint.
|
||||
* @return array
|
||||
*/
|
||||
protected function get_intervals_sql_params( $query_args, $table_name ) {
|
||||
$intervals_query = array(
|
||||
'from_clause' => '',
|
||||
'where_time_clause' => '',
|
||||
'where_clause' => '',
|
||||
);
|
||||
$this->clear_sql_clause( array( 'from', 'where_time', 'where' ) );
|
||||
|
||||
$intervals_query = array_merge( $intervals_query, $this->get_time_period_sql_params( $query_args, $table_name ) );
|
||||
$this->get_time_period_sql_params( $query_args, $table_name );
|
||||
|
||||
if ( isset( $query_args['interval'] ) && '' !== $query_args['interval'] ) {
|
||||
$interval = $query_args['interval'];
|
||||
$intervals_query['select_clause'] = TimeInterval::db_datetime_format( $interval, $table_name );
|
||||
$this->clear_sql_clause( 'select' );
|
||||
$this->add_sql_clause( 'select', TimeInterval::db_datetime_format( $interval, $table_name ) );
|
||||
}
|
||||
|
||||
$intervals_query = array_merge( $intervals_query, $this->get_limit_sql_params( $query_args ) );
|
||||
|
||||
$intervals_query = array_merge( $intervals_query, $this->get_order_by_sql_params( $query_args ) );
|
||||
|
||||
return $intervals_query;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -815,6 +874,35 @@ class DataStore {
|
|||
return wc_get_products( $args );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get WHERE filter by object ids subquery.
|
||||
*
|
||||
* @param string $select_table Select table name.
|
||||
* @param string $select_field Select table object ID field name.
|
||||
* @param string $filter_table Lookup table name.
|
||||
* @param string $filter_field Lookup table object ID field name.
|
||||
* @param string $compare Comparison string (IN|NOT IN).
|
||||
* @param string $id_list Comma separated ID list.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_object_where_filter( $select_table, $select_field, $filter_table, $filter_field, $compare, $id_list ) {
|
||||
global $wpdb;
|
||||
if ( empty( $id_list ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$lookup_name = isset( $wpdb->$filter_table ) ? $wpdb->$filter_table : $wpdb->prefix . $filter_table;
|
||||
return " {$select_table}.{$select_field} {$compare} (
|
||||
SELECT
|
||||
DISTINCT {$filter_table}.{$select_field}
|
||||
FROM
|
||||
{$filter_table}
|
||||
WHERE
|
||||
{$filter_table}.{$filter_field} IN ({$id_list})
|
||||
)";
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of ids of allowed products, based on query arguments from the user.
|
||||
*
|
||||
|
@ -864,15 +952,11 @@ class DataStore {
|
|||
* @return string
|
||||
*/
|
||||
protected function get_included_variations( $query_args ) {
|
||||
$included_variations = array();
|
||||
$operator = $this->get_match_operator( $query_args );
|
||||
|
||||
if ( isset( $query_args['variations'] ) && is_array( $query_args['variations'] ) && count( $query_args['variations'] ) > 0 ) {
|
||||
$included_variations = array_filter( array_map( 'intval', $query_args['variations'] ) );
|
||||
$query_args['variations'] = array_filter( array_map( 'intval', $query_args['variations'] ) );
|
||||
}
|
||||
|
||||
$included_variations_str = implode( ',', $included_variations );
|
||||
return $included_variations_str;
|
||||
return $this->get_filtered_ids( $query_args, 'variations' );
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -882,27 +966,28 @@ class DataStore {
|
|||
* @return string
|
||||
*/
|
||||
protected function get_excluded_products( $query_args ) {
|
||||
$excluded_products_str = '';
|
||||
return $this->get_filtered_ids( $query_args, 'product_excludes' );
|
||||
}
|
||||
|
||||
if ( isset( $query_args['product_excludes'] ) && is_array( $query_args['product_excludes'] ) && count( $query_args['product_excludes'] ) > 0 ) {
|
||||
$excluded_products_str = implode( ',', $query_args['product_excludes'] );
|
||||
}
|
||||
return $excluded_products_str;
|
||||
/**
|
||||
* Returns comma separated ids of included categories, based on query arguments from the user.
|
||||
*
|
||||
* @param array $query_args Parameters supplied by the user.
|
||||
* @return string
|
||||
*/
|
||||
protected function get_included_categories( $query_args ) {
|
||||
return $this->get_filtered_ids( $query_args, 'categories' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns comma separated ids of included coupons, based on query arguments from the user.
|
||||
*
|
||||
* @param array $query_args Parameters supplied by the user.
|
||||
* @param array $query_args Parameters supplied by the user.
|
||||
* @param string $field Field name in the parameter list.
|
||||
* @return string
|
||||
*/
|
||||
protected function get_included_coupons( $query_args ) {
|
||||
$included_coupons_str = '';
|
||||
|
||||
if ( isset( $query_args['coupon_includes'] ) && is_array( $query_args['coupon_includes'] ) && count( $query_args['coupon_includes'] ) > 0 ) {
|
||||
$included_coupons_str = implode( ',', $query_args['coupon_includes'] );
|
||||
}
|
||||
return $included_coupons_str;
|
||||
protected function get_included_coupons( $query_args, $field = 'coupon_includes' ) {
|
||||
return $this->get_filtered_ids( $query_args, $field );
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -912,12 +997,7 @@ class DataStore {
|
|||
* @return string
|
||||
*/
|
||||
protected function get_excluded_coupons( $query_args ) {
|
||||
$excluded_coupons_str = '';
|
||||
|
||||
if ( isset( $query_args['coupon_excludes'] ) && is_array( $query_args['coupon_excludes'] ) && count( $query_args['coupon_excludes'] ) > 0 ) {
|
||||
$excluded_coupons_str = implode( ',', $query_args['coupon_excludes'] );
|
||||
}
|
||||
return $excluded_coupons_str;
|
||||
return $this->get_filtered_ids( $query_args, 'coupon_excludes' );
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -927,12 +1007,7 @@ class DataStore {
|
|||
* @return string
|
||||
*/
|
||||
protected function get_included_orders( $query_args ) {
|
||||
$included_orders_str = '';
|
||||
|
||||
if ( isset( $query_args['order_includes'] ) && is_array( $query_args['order_includes'] ) && count( $query_args['order_includes'] ) > 0 ) {
|
||||
$included_orders_str = implode( ',', $query_args['order_includes'] );
|
||||
}
|
||||
return $included_orders_str;
|
||||
return $this->get_filtered_ids( $query_args, 'order_includes' );
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -942,12 +1017,7 @@ class DataStore {
|
|||
* @return string
|
||||
*/
|
||||
protected function get_excluded_orders( $query_args ) {
|
||||
$excluded_orders_str = '';
|
||||
|
||||
if ( isset( $query_args['order_excludes'] ) && is_array( $query_args['order_excludes'] ) && count( $query_args['order_excludes'] ) > 0 ) {
|
||||
$excluded_orders_str = implode( ',', $query_args['order_excludes'] );
|
||||
}
|
||||
return $excluded_orders_str;
|
||||
return $this->get_filtered_ids( $query_args, 'order_excludes' );
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -957,12 +1027,7 @@ class DataStore {
|
|||
* @return string
|
||||
*/
|
||||
protected function get_included_users( $query_args ) {
|
||||
$included_users_str = '';
|
||||
|
||||
if ( isset( $query_args['user_includes'] ) && is_array( $query_args['user_includes'] ) && count( $query_args['user_includes'] ) > 0 ) {
|
||||
$included_users_str = implode( ',', $query_args['user_includes'] );
|
||||
}
|
||||
return $included_users_str;
|
||||
return $this->get_filtered_ids( $query_args, 'user_includes' );
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -972,12 +1037,7 @@ class DataStore {
|
|||
* @return string
|
||||
*/
|
||||
protected function get_excluded_users( $query_args ) {
|
||||
$excluded_users_str = '';
|
||||
|
||||
if ( isset( $query_args['user_excludes'] ) && is_array( $query_args['user_excludes'] ) && count( $query_args['user_excludes'] ) > 0 ) {
|
||||
$excluded_users_str = implode( ',', $query_args['user_excludes'] );
|
||||
}
|
||||
return $excluded_users_str;
|
||||
return $this->get_filtered_ids( $query_args, 'user_excludes' );
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1016,6 +1076,56 @@ class DataStore {
|
|||
return implode( " $operator ", $subqueries );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add order status SQL clauses if included in query.
|
||||
*
|
||||
* @param array $query_args Parameters supplied by the user.
|
||||
* @param string $table_name Database table name.
|
||||
* @param SqlQuery $sql_query Query object.
|
||||
*/
|
||||
protected function add_order_status_clause( $query_args, $table_name, &$sql_query ) {
|
||||
global $wpdb;
|
||||
$order_status_filter = $this->get_status_subquery( $query_args );
|
||||
if ( $order_status_filter ) {
|
||||
$sql_query->add_sql_clause( 'join', "JOIN {$wpdb->prefix}wc_order_stats ON {$table_name}.order_id = {$wpdb->prefix}wc_order_stats.order_id" );
|
||||
$sql_query->add_sql_clause( 'where', "AND ( {$order_status_filter} )" );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add order by SQL clause if included in query.
|
||||
*
|
||||
* @param array $query_args Parameters supplied by the user.
|
||||
* @param SqlQuery $sql_query Query object.
|
||||
* @return string Order by clause.
|
||||
*/
|
||||
protected function add_order_by_clause( $query_args, &$sql_query ) {
|
||||
$order_by_clause = '';
|
||||
|
||||
$sql_query->clear_sql_clause( array( 'order_by' ) );
|
||||
if ( isset( $query_args['orderby'] ) ) {
|
||||
$order_by_clause = $this->normalize_order_by( $query_args['orderby'] );
|
||||
$sql_query->add_sql_clause( 'order_by', $order_by_clause );
|
||||
}
|
||||
|
||||
// Return ORDER BY clause to allow adding the sort field(s) to query via a JOIN.
|
||||
return $order_by_clause;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add order by order SQL clause.
|
||||
*
|
||||
* @param array $query_args Parameters supplied by the user.
|
||||
* @param SqlQuery $sql_query Query object.
|
||||
*/
|
||||
protected function add_orderby_order_clause( $query_args, &$sql_query ) {
|
||||
if ( isset( $query_args['order'] ) ) {
|
||||
$sql_query->add_sql_clause( 'order_by', $query_args['order'] );
|
||||
} else {
|
||||
$sql_query->add_sql_clause( 'order_by', 'DESC' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns customer subquery to be used in WHERE SQL query, based on query arguments from the user.
|
||||
*
|
||||
|
@ -1057,4 +1167,39 @@ class DataStore {
|
|||
}
|
||||
return $operator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns filtered comma separated ids, based on query arguments from the user.
|
||||
*
|
||||
* @param array $query_args Parameters supplied by the user.
|
||||
* @param string $field Query field to filter.
|
||||
* @param string $separator Field separator.
|
||||
* @return string
|
||||
*/
|
||||
protected function get_filtered_ids( $query_args, $field, $separator = ',' ) {
|
||||
$ids_str = '';
|
||||
$ids = isset( $query_args[ $field ] ) && is_array( $query_args[ $field ] ) ? $query_args[ $field ] : array();
|
||||
|
||||
/**
|
||||
* Filter the IDs before retrieving report data.
|
||||
*
|
||||
* Allows filtering of the objects included or excluded from reports.
|
||||
*
|
||||
* @param array $ids List of object Ids.
|
||||
* @param array $query_args The original arguments for the request.
|
||||
* @param string $field The object type.
|
||||
* @param string $context The data store context.
|
||||
*/
|
||||
$ids = apply_filters( 'wc_admin_reports_ ' . $field, $ids, $query_args, $field, $this->context );
|
||||
|
||||
if ( ! empty( $ids ) ) {
|
||||
$ids_str = implode( $separator, $ids );
|
||||
}
|
||||
return $ids_str;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assign report columns once full table name has been assigned.
|
||||
*/
|
||||
protected function assign_report_columns() {}
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ defined( 'ABSPATH' ) || exit;
|
|||
use \Automattic\WooCommerce\Admin\API\Reports\DataStore as ReportsDataStore;
|
||||
use \Automattic\WooCommerce\Admin\API\Reports\DataStoreInterface;
|
||||
use \Automattic\WooCommerce\Admin\API\Reports\TimeInterval;
|
||||
use \Automattic\WooCommerce\Admin\API\Reports\SqlQuery;
|
||||
|
||||
/**
|
||||
* API\Reports\Downloads\DataStore.
|
||||
|
@ -23,7 +24,7 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
|||
*
|
||||
* @var string
|
||||
*/
|
||||
const TABLE_NAME = 'wc_download_log';
|
||||
protected static $table_name = 'wc_download_log';
|
||||
|
||||
/**
|
||||
* Cache identifier.
|
||||
|
@ -50,139 +51,136 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
|||
);
|
||||
|
||||
/**
|
||||
* SQL columns to select in the db query and their mapping to SQL code.
|
||||
* Data store context used to pass to filters.
|
||||
*
|
||||
* @var array
|
||||
* @var string
|
||||
*/
|
||||
protected $report_columns = array(
|
||||
'id' => 'download_log_id as id',
|
||||
'date' => 'timestamp as date_gmt',
|
||||
'download_id' => 'product_permissions.download_id',
|
||||
'product_id' => 'product_permissions.product_id',
|
||||
'order_id' => 'product_permissions.order_id',
|
||||
'user_id' => 'product_permissions.user_id',
|
||||
'ip_address' => 'user_ip_address as ip_address',
|
||||
);
|
||||
protected $context = 'downloads';
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* Assign report columns once full table name has been assigned.
|
||||
*/
|
||||
public function __construct() {
|
||||
global $wpdb;
|
||||
protected function assign_report_columns() {
|
||||
$this->report_columns = array(
|
||||
'id' => 'download_log_id as id',
|
||||
'date' => 'timestamp as date_gmt',
|
||||
'download_id' => 'product_permissions.download_id',
|
||||
'product_id' => 'product_permissions.product_id',
|
||||
'order_id' => 'product_permissions.order_id',
|
||||
'user_id' => 'product_permissions.user_id',
|
||||
'ip_address' => 'user_ip_address as ip_address',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the database query with parameters used for downloads report.
|
||||
*
|
||||
* @param array $query_args Query arguments supplied by the user.
|
||||
* @return array Array of parameters used for SQL query.
|
||||
*/
|
||||
protected function get_sql_query_params( $query_args ) {
|
||||
global $wpdb;
|
||||
|
||||
$lookup_table = $wpdb->prefix . self::TABLE_NAME;
|
||||
$operator = $this->get_match_operator( $query_args );
|
||||
$where_filters = array();
|
||||
$lookup_table = self::get_db_table_name();
|
||||
$permission_table = $wpdb->prefix . 'woocommerce_downloadable_product_permissions';
|
||||
$operator = $this->get_match_operator( $query_args );
|
||||
$where_filters = array();
|
||||
$join = "JOIN {$permission_table} as product_permissions ON {$lookup_table}.permission_id = product_permissions.permission_id";
|
||||
|
||||
$sql_query_params = $this->get_time_period_sql_params( $query_args, $lookup_table );
|
||||
$sql_query_params = array_merge( $sql_query_params, $this->get_limit_sql_params( $query_args ) );
|
||||
|
||||
$included_products = $this->get_included_products( $query_args );
|
||||
$excluded_products = $this->get_excluded_products( $query_args );
|
||||
if ( $included_products ) {
|
||||
$where_filters[] = " {$lookup_table}.permission_id IN (
|
||||
SELECT
|
||||
DISTINCT {$wpdb->prefix}woocommerce_downloadable_product_permissions.permission_id
|
||||
FROM
|
||||
{$wpdb->prefix}woocommerce_downloadable_product_permissions
|
||||
WHERE
|
||||
{$wpdb->prefix}woocommerce_downloadable_product_permissions.product_id IN ({$included_products})
|
||||
)";
|
||||
$where_time = $this->get_time_period_sql_params( $query_args, $lookup_table );
|
||||
if ( $where_time ) {
|
||||
if ( isset( $this->subquery ) ) {
|
||||
$this->subquery->add_sql_clause( 'where_time', $where_time );
|
||||
} else {
|
||||
$this->interval_query->add_sql_clause( 'where_time', $where_time );
|
||||
}
|
||||
}
|
||||
$this->get_limit_sql_params( $query_args );
|
||||
|
||||
if ( $excluded_products ) {
|
||||
$where_filters[] = " {$lookup_table}.permission_id NOT IN (
|
||||
SELECT
|
||||
DISTINCT {$wpdb->prefix}woocommerce_downloadable_product_permissions.permission_id
|
||||
FROM
|
||||
{$wpdb->prefix}woocommerce_downloadable_product_permissions
|
||||
WHERE
|
||||
{$wpdb->prefix}woocommerce_downloadable_product_permissions.product_id IN ({$excluded_products})
|
||||
)";
|
||||
}
|
||||
|
||||
$included_orders = $this->get_included_orders( $query_args );
|
||||
$excluded_orders = $this->get_excluded_orders( $query_args );
|
||||
if ( $included_orders ) {
|
||||
$where_filters[] = " {$lookup_table}.permission_id IN (
|
||||
SELECT
|
||||
DISTINCT {$wpdb->prefix}woocommerce_downloadable_product_permissions.permission_id
|
||||
FROM
|
||||
{$wpdb->prefix}woocommerce_downloadable_product_permissions
|
||||
WHERE
|
||||
{$wpdb->prefix}woocommerce_downloadable_product_permissions.order_id IN ({$included_orders})
|
||||
)";
|
||||
}
|
||||
|
||||
if ( $excluded_orders ) {
|
||||
$where_filters[] = " {$lookup_table}.permission_id NOT IN (
|
||||
SELECT
|
||||
DISTINCT {$wpdb->prefix}woocommerce_downloadable_product_permissions.permission_id
|
||||
FROM
|
||||
{$wpdb->prefix}woocommerce_downloadable_product_permissions
|
||||
WHERE
|
||||
{$wpdb->prefix}woocommerce_downloadable_product_permissions.order_id IN ({$excluded_orders})
|
||||
)";
|
||||
}
|
||||
$where_filters[] = $this->get_object_where_filter(
|
||||
$lookup_table,
|
||||
'permission_id',
|
||||
$permission_table,
|
||||
'product_id',
|
||||
'IN',
|
||||
$this->get_included_products( $query_args )
|
||||
);
|
||||
$where_filters[] = $this->get_object_where_filter(
|
||||
$lookup_table,
|
||||
'permission_id',
|
||||
$permission_table,
|
||||
'product_id',
|
||||
'NOT IN',
|
||||
$this->get_excluded_products( $query_args )
|
||||
);
|
||||
$where_filters[] = $this->get_object_where_filter(
|
||||
$lookup_table,
|
||||
'permission_id',
|
||||
$permission_table,
|
||||
'order_id',
|
||||
'IN',
|
||||
$this->get_included_orders( $query_args )
|
||||
);
|
||||
$where_filters[] = $this->get_object_where_filter(
|
||||
$lookup_table,
|
||||
'permission_id',
|
||||
$permission_table,
|
||||
'order_id',
|
||||
'NOT IN',
|
||||
$this->get_excluded_orders( $query_args )
|
||||
);
|
||||
|
||||
$customer_lookup_table = $wpdb->prefix . 'wc_customer_lookup';
|
||||
$customer_lookup = "SELECT {$customer_lookup_table}.user_id FROM {$customer_lookup_table} WHERE {$customer_lookup_table}.customer_id IN (%s)";
|
||||
$included_customers = $this->get_included_customers( $query_args );
|
||||
$excluded_customers = $this->get_excluded_customers( $query_args );
|
||||
if ( $included_customers ) {
|
||||
$where_filters[] = " {$lookup_table}.permission_id IN (
|
||||
SELECT
|
||||
DISTINCT {$wpdb->prefix}woocommerce_downloadable_product_permissions.permission_id
|
||||
FROM
|
||||
{$wpdb->prefix}woocommerce_downloadable_product_permissions
|
||||
WHERE
|
||||
{$wpdb->prefix}woocommerce_downloadable_product_permissions.user_id IN (
|
||||
SELECT {$customer_lookup_table}.user_id FROM {$customer_lookup_table} WHERE {$customer_lookup_table}.customer_id IN ({$included_customers})
|
||||
)
|
||||
)";
|
||||
$where_filters[] = $this->get_object_where_filter(
|
||||
$lookup_table,
|
||||
'permission_id',
|
||||
$permission_table,
|
||||
'user_id',
|
||||
'IN',
|
||||
sprintf( $customer_lookup, $included_customers )
|
||||
);
|
||||
}
|
||||
|
||||
if ( $excluded_customers ) {
|
||||
$where_filters[] = " {$lookup_table}.permission_id NOT IN (
|
||||
SELECT
|
||||
DISTINCT {$wpdb->prefix}woocommerce_downloadable_product_permissions.permission_id
|
||||
FROM
|
||||
{$wpdb->prefix}woocommerce_downloadable_product_permissions
|
||||
WHERE
|
||||
{$wpdb->prefix}woocommerce_downloadable_product_permissions.user_id IN (
|
||||
SELECT {$customer_lookup_table}.user_id FROM {$customer_lookup_table} WHERE {$customer_lookup_table}.customer_id IN ({$excluded_customers})
|
||||
)
|
||||
)";
|
||||
$where_filters[] = $this->get_object_where_filter(
|
||||
$lookup_table,
|
||||
'permission_id',
|
||||
$permission_table,
|
||||
'user_id',
|
||||
'NOT IN',
|
||||
sprintf( $customer_lookup, $excluded_customers )
|
||||
);
|
||||
}
|
||||
|
||||
$included_ip_addresses = $this->get_included_ip_addresses( $query_args );
|
||||
$excluded_ip_addresses = $this->get_excluded_ip_addresses( $query_args );
|
||||
if ( $included_ip_addresses ) {
|
||||
$where_filters[] = " {$lookup_table}.user_ip_address IN ('{$included_ip_addresses}')";
|
||||
$where_filters[] = "{$lookup_table}.user_ip_address IN ('{$included_ip_addresses}')";
|
||||
}
|
||||
|
||||
if ( $excluded_ip_addresses ) {
|
||||
$where_filters[] = " {$lookup_table}.user_ip_address NOT IN ('{$excluded_ip_addresses}')";
|
||||
$where_filters[] = "{$lookup_table}.user_ip_address NOT IN ('{$excluded_ip_addresses}')";
|
||||
}
|
||||
|
||||
$where_filters = array_filter( $where_filters );
|
||||
$where_subclause = implode( " $operator ", $where_filters );
|
||||
if ( $where_subclause ) {
|
||||
$sql_query_params['where_clause'] .= " AND ( $where_subclause )";
|
||||
if ( isset( $this->subquery ) ) {
|
||||
$this->subquery->add_sql_clause( 'where', "AND ( $where_subclause )" );
|
||||
} else {
|
||||
$this->interval_query->add_sql_clause( 'where', "AND ( $where_subclause )" );
|
||||
}
|
||||
}
|
||||
|
||||
$sql_query_params['from_clause'] .= " JOIN {$wpdb->prefix}woocommerce_downloadable_product_permissions as product_permissions ON {$lookup_table}.permission_id = product_permissions.permission_id";
|
||||
$sql_query_params = $this->get_order_by( $query_args, $sql_query_params );
|
||||
|
||||
return $sql_query_params;
|
||||
if ( isset( $this->subquery ) ) {
|
||||
$this->subquery->add_sql_clause( 'join', $join );
|
||||
} else {
|
||||
$this->interval_query->add_sql_clause( 'join', $join );
|
||||
}
|
||||
$this->get_order_by( $query_args );
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -192,16 +190,10 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
|||
* @return string
|
||||
*/
|
||||
protected function get_included_ip_addresses( $query_args ) {
|
||||
$included_ips_str = '';
|
||||
|
||||
if ( isset( $query_args['ip_address_includes'] ) && is_array( $query_args['ip_address_includes'] ) && count( $query_args['ip_address_includes'] ) > 0 ) {
|
||||
$ip_includes = array();
|
||||
foreach ( $query_args['ip_address_includes'] as $ip ) {
|
||||
$ip_includes[] = esc_sql( $ip );
|
||||
}
|
||||
$included_ips_str = implode( "','", $ip_includes );
|
||||
$query_args['ip_address_includes'] = array_map( 'esc_sql', $query_args['ip_address_includes'] );
|
||||
}
|
||||
return $included_ips_str;
|
||||
return self::get_filtered_ids( $query_args, 'ip_address_includes', "','" );
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -211,16 +203,10 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
|||
* @return string
|
||||
*/
|
||||
protected function get_excluded_ip_addresses( $query_args ) {
|
||||
$excluded_ips_str = '';
|
||||
|
||||
if ( isset( $query_args['ip_address_excludes'] ) && is_array( $query_args['ip_address_excludes'] ) && count( $query_args['ip_address_excludes'] ) > 0 ) {
|
||||
$ip_excludes = array();
|
||||
foreach ( $query_args['ip_address_excludes'] as $ip ) {
|
||||
$ip_excludes[] = esc_sql( $ip );
|
||||
}
|
||||
$excluded_ips_str = implode( ',', $ip_excludes );
|
||||
$query_args['ip_address_excludes'] = array_map( 'esc_sql', $query_args['ip_address_excludes'] );
|
||||
}
|
||||
return $excluded_ips_str;
|
||||
return self::get_filtered_ids( $query_args, 'ip_address_excludes', "','" );
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -230,12 +216,7 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
|||
* @return string
|
||||
*/
|
||||
protected function get_included_customers( $query_args ) {
|
||||
$included_customers_str = '';
|
||||
|
||||
if ( isset( $query_args['customer_includes'] ) && is_array( $query_args['customer_includes'] ) && count( $query_args['customer_includes'] ) > 0 ) {
|
||||
$included_customers_str = implode( ',', $query_args['customer_includes'] );
|
||||
}
|
||||
return $included_customers_str;
|
||||
return self::get_filtered_ids( $query_args, 'customer_includes' );
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -245,67 +226,51 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
|||
* @return string
|
||||
*/
|
||||
protected function get_excluded_customers( $query_args ) {
|
||||
$excluded_customer_str = '';
|
||||
|
||||
if ( isset( $query_args['customer_excludes'] ) && is_array( $query_args['customer_excludes'] ) && count( $query_args['customer_excludes'] ) > 0 ) {
|
||||
$excluded_customer_str = implode( ',', $query_args['customer_excludes'] );
|
||||
}
|
||||
return $excluded_customer_str;
|
||||
return self::get_filtered_ids( $query_args, 'customer_excludes' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Fills WHERE clause of SQL request with date-related constraints.
|
||||
* Gets WHERE time clause of SQL request with date-related constraints.
|
||||
*
|
||||
* @param array $query_args Parameters supplied by the user.
|
||||
* @param string $table_name Name of the db table relevant for the date constraint.
|
||||
* @return array
|
||||
* @return string
|
||||
*/
|
||||
protected function get_time_period_sql_params( $query_args, $table_name ) {
|
||||
$sql_query = array(
|
||||
'from_clause' => '',
|
||||
'where_time_clause' => '',
|
||||
'where_clause' => '',
|
||||
);
|
||||
|
||||
$where_time = '';
|
||||
if ( $query_args['before'] ) {
|
||||
$datetime_str = $query_args['before']->format( TimeInterval::$sql_datetime_format );
|
||||
$sql_query['where_time_clause'] .= " AND {$table_name}.timestamp <= '$datetime_str'";
|
||||
$datetime_str = $query_args['before']->format( TimeInterval::$sql_datetime_format );
|
||||
$where_time .= " AND {$table_name}.timestamp <= '$datetime_str'";
|
||||
|
||||
}
|
||||
|
||||
if ( $query_args['after'] ) {
|
||||
$datetime_str = $query_args['after']->format( TimeInterval::$sql_datetime_format );
|
||||
$sql_query['where_time_clause'] .= " AND {$table_name}.timestamp >= '$datetime_str'";
|
||||
$datetime_str = $query_args['after']->format( TimeInterval::$sql_datetime_format );
|
||||
$where_time .= " AND {$table_name}.timestamp >= '$datetime_str'";
|
||||
}
|
||||
|
||||
return $sql_query;
|
||||
return $where_time;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fills ORDER BY clause of SQL request based on user supplied parameters.
|
||||
*
|
||||
* @param array $query_args Parameters supplied by the user.
|
||||
* @param array $sql_query Current SQL query array.
|
||||
* @return array
|
||||
*/
|
||||
protected function get_order_by( $query_args, $sql_query ) {
|
||||
protected function get_order_by( $query_args ) {
|
||||
global $wpdb;
|
||||
$sql_query['order_by_clause'] = '';
|
||||
$this->clear_sql_clause( 'order_by' );
|
||||
$order_by = '';
|
||||
if ( isset( $query_args['orderby'] ) ) {
|
||||
$sql_query['order_by_clause'] = $this->normalize_order_by( $query_args['orderby'] );
|
||||
$order_by = $this->normalize_order_by( $query_args['orderby'] );
|
||||
$this->add_sql_clause( 'order_by', $order_by );
|
||||
}
|
||||
|
||||
if ( false !== strpos( $sql_query['order_by_clause'], '_products' ) ) {
|
||||
$sql_query['from_clause'] .= " JOIN {$wpdb->prefix}posts AS _products ON product_permissions.product_id = _products.ID";
|
||||
if ( false !== strpos( $order_by, '_products' ) ) {
|
||||
$this->subquery->add_sql_clause( 'join', "JOIN {$wpdb->posts} AS _products ON product_permissions.product_id = _products.ID" );
|
||||
}
|
||||
|
||||
if ( isset( $query_args['order'] ) ) {
|
||||
$sql_query['order_by_clause'] .= ' ' . $query_args['order'];
|
||||
} else {
|
||||
$sql_query['order_by_clause'] .= ' DESC';
|
||||
}
|
||||
|
||||
return $sql_query;
|
||||
$this->add_orderby_order_clause( $query_args, $this );
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -317,7 +282,7 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
|||
public function get_data( $query_args ) {
|
||||
global $wpdb;
|
||||
|
||||
$table_name = $wpdb->prefix . self::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.
|
||||
$defaults = array(
|
||||
|
@ -340,6 +305,8 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
|||
$data = $this->get_cached_data( $cache_key );
|
||||
|
||||
if ( false === $data ) {
|
||||
$this->initialize_queries();
|
||||
|
||||
$data = (object) array(
|
||||
'data' => array(),
|
||||
'total' => 0,
|
||||
|
@ -352,41 +319,23 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
|||
|
||||
$db_records_count = (int) $wpdb->get_var(
|
||||
"SELECT COUNT(*) FROM (
|
||||
SELECT
|
||||
{$table_name}.download_log_id
|
||||
FROM
|
||||
{$table_name}
|
||||
{$sql_query_params['from_clause']}
|
||||
WHERE
|
||||
1=1
|
||||
{$sql_query_params['where_time_clause']}
|
||||
{$sql_query_params['where_clause']}
|
||||
GROUP BY
|
||||
{$table_name}.download_log_id
|
||||
) AS tt"
|
||||
{$this->subquery->get_query_statement()}
|
||||
) AS tt"
|
||||
); // WPCS: cache ok, DB call ok, unprepared SQL ok.
|
||||
|
||||
$total_pages = (int) ceil( $db_records_count / $sql_query_params['per_page'] );
|
||||
$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(
|
||||
"SELECT
|
||||
{$selections}
|
||||
FROM
|
||||
{$table_name}
|
||||
{$sql_query_params['from_clause']}
|
||||
WHERE
|
||||
1=1
|
||||
{$sql_query_params['where_time_clause']}
|
||||
{$sql_query_params['where_clause']}
|
||||
GROUP BY
|
||||
{$table_name}.download_log_id
|
||||
ORDER BY
|
||||
{$sql_query_params['order_by_clause']}
|
||||
{$sql_query_params['limit']}
|
||||
",
|
||||
$this->subquery->get_query_statement(),
|
||||
ARRAY_A
|
||||
); // WPCS: cache ok, DB call ok, unprepared SQL ok.
|
||||
|
||||
|
@ -427,4 +376,16 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
|||
|
||||
return $order_by;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize query objects.
|
||||
*/
|
||||
protected function initialize_queries() {
|
||||
$this->clear_all_clauses();
|
||||
$table_name = self::get_db_table_name();
|
||||
$this->subquery = new SqlQuery( $this->context . '_subquery' );
|
||||
$this->subquery->add_sql_clause( 'from', $table_name );
|
||||
$this->subquery->add_sql_clause( 'select', "{$table_name}.download_log_id" );
|
||||
$this->subquery->add_sql_clause( 'group_by', "{$table_name}.download_log_id" );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ defined( 'ABSPATH' ) || exit;
|
|||
use \Automattic\WooCommerce\Admin\API\Reports\Downloads\DataStore as DownloadsDataStore;
|
||||
use \Automattic\WooCommerce\Admin\API\Reports\DataStoreInterface;
|
||||
use \Automattic\WooCommerce\Admin\API\Reports\TimeInterval;
|
||||
use \Automattic\WooCommerce\Admin\API\Reports\SqlQuery;
|
||||
|
||||
/**
|
||||
* API\Reports\Downloads\Stats\DataStore.
|
||||
|
@ -27,15 +28,6 @@ class DataStore extends DownloadsDataStore implements DataStoreInterface {
|
|||
'download_count' => 'intval',
|
||||
);
|
||||
|
||||
/**
|
||||
* SQL columns to select in the db query and their mapping to SQL code.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $report_columns = array(
|
||||
'download_count' => 'COUNT(DISTINCT download_log_id) as download_count',
|
||||
);
|
||||
|
||||
/**
|
||||
* Cache identifier.
|
||||
*
|
||||
|
@ -44,12 +36,20 @@ class DataStore extends DownloadsDataStore implements DataStoreInterface {
|
|||
protected $cache_key = 'downloads_stats';
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* Data store context used to pass to filters.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public function __construct() {
|
||||
global $wpdb;
|
||||
}
|
||||
protected $context = 'download_stats';
|
||||
|
||||
/**
|
||||
* Assign report columns once full table name has been assigned.
|
||||
*/
|
||||
protected function assign_report_columns() {
|
||||
$this->report_columns = array(
|
||||
'download_count' => 'COUNT(DISTINCT download_log_id) as download_count',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the report data based on parameters supplied by the user.
|
||||
|
@ -60,7 +60,7 @@ class DataStore extends DownloadsDataStore implements DataStoreInterface {
|
|||
public function get_data( $query_args ) {
|
||||
global $wpdb;
|
||||
|
||||
$table_name = $wpdb->prefix . self::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.
|
||||
$defaults = array(
|
||||
|
@ -84,53 +84,35 @@ class DataStore extends DownloadsDataStore implements DataStoreInterface {
|
|||
$data = $this->get_cached_data( $cache_key );
|
||||
|
||||
if ( false === $data ) {
|
||||
$selections = $this->selected_columns( $query_args );
|
||||
$sql_query_params = $this->get_sql_query_params( $query_args );
|
||||
$totals_query = array_merge( array(), $this->get_time_period_sql_params( $query_args, $table_name ) );
|
||||
$intervals_query = array_merge( array(), $this->get_intervals_sql_params( $query_args, $table_name ) );
|
||||
$this->initialize_queries();
|
||||
$selections = $this->selected_columns( $query_args );
|
||||
$this->get_sql_query_params( $query_args );
|
||||
$this->get_time_period_sql_params( $query_args, $table_name );
|
||||
$this->get_intervals_sql_params( $query_args, $table_name );
|
||||
|
||||
$totals_query['where_clause'] .= $sql_query_params['where_clause'];
|
||||
$totals_query['from_clause'] .= $sql_query_params['from_clause'];
|
||||
$intervals_query['where_clause'] .= $sql_query_params['where_clause'];
|
||||
$intervals_query['from_clause'] .= $sql_query_params['from_clause'];
|
||||
$intervals_query['select_clause'] = str_replace( 'date_created', 'timestamp', $intervals_query['select_clause'] );
|
||||
$intervals_query['where_time_clause'] = str_replace( 'date_created', 'timestamp', $intervals_query['where_time_clause'] );
|
||||
$this->interval_query->add_sql_clause( 'select', $this->get_sql_clause( 'select' ) . ' AS time_interval' );
|
||||
$this->interval_query->str_replace_clause( 'select', 'date_created', 'timestamp' );
|
||||
$this->interval_query->str_replace_clause( 'where_time', 'date_created', 'timestamp' );
|
||||
|
||||
$db_intervals = $wpdb->get_col(
|
||||
"SELECT
|
||||
{$intervals_query['select_clause']} AS time_interval
|
||||
FROM
|
||||
{$table_name}
|
||||
{$intervals_query['from_clause']}
|
||||
WHERE
|
||||
1=1
|
||||
{$intervals_query['where_time_clause']}
|
||||
{$intervals_query['where_clause']}
|
||||
GROUP BY
|
||||
time_interval"
|
||||
$this->interval_query->get_query_statement()
|
||||
); // WPCS: cache ok, DB call ok, , unprepared SQL ok.
|
||||
|
||||
$db_records_count = count( $db_intervals );
|
||||
|
||||
$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 / $intervals_query['per_page'] );
|
||||
$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( $intervals_query, $query_args, $db_records_count, $expected_interval_count, $table_name );
|
||||
$intervals_query['where_time_clause'] = str_replace( 'date_created', 'timestamp', $intervals_query['where_time_clause'] );
|
||||
|
||||
$this->update_intervals_sql_params( $query_args, $db_records_count, $expected_interval_count, $table_name );
|
||||
$this->interval_query->str_replace_clause( 'where_time', 'date_created', 'timestamp' );
|
||||
$this->total_query->add_sql_clause( 'select', $selections );
|
||||
$this->total_query->add_sql_clause( 'where', $this->interval_query->get_sql_clause( 'where' ) );
|
||||
$totals = $wpdb->get_results(
|
||||
"SELECT
|
||||
{$selections}
|
||||
FROM
|
||||
{$table_name}
|
||||
{$totals_query['from_clause']}
|
||||
WHERE
|
||||
1=1
|
||||
{$totals_query['where_time_clause']}
|
||||
{$totals_query['where_clause']}",
|
||||
$this->total_query->get_query_statement(),
|
||||
ARRAY_A
|
||||
); // WPCS: cache ok, DB call ok, unprepared SQL ok.
|
||||
|
||||
|
@ -138,27 +120,15 @@ class DataStore extends DownloadsDataStore implements DataStoreInterface {
|
|||
return new \WP_Error( 'woocommerce_reports_downloads_stats_result_failed', __( 'Sorry, fetching downloads data failed.', 'woocommerce-admin' ) );
|
||||
}
|
||||
|
||||
$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(timestamp) AS datetime_anchor' );
|
||||
if ( '' !== $selections ) {
|
||||
$selections = ', ' . $selections;
|
||||
$this->interval_query->add_sql_clause( 'select', ', ' . $selections );
|
||||
}
|
||||
|
||||
$intervals = $wpdb->get_results(
|
||||
"SELECT
|
||||
MAX(timestamp) AS datetime_anchor,
|
||||
{$intervals_query['select_clause']} AS time_interval
|
||||
{$selections}
|
||||
FROM
|
||||
{$table_name}
|
||||
{$intervals_query['from_clause']}
|
||||
WHERE
|
||||
1=1
|
||||
{$intervals_query['where_time_clause']}
|
||||
{$intervals_query['where_clause']}
|
||||
GROUP BY
|
||||
time_interval
|
||||
ORDER BY
|
||||
{$intervals_query['order_by_clause']}
|
||||
{$intervals_query['limit']}",
|
||||
$this->interval_query->get_query_statement(),
|
||||
ARRAY_A
|
||||
); // WPCS: cache ok, DB call ok, unprepared SQL ok.
|
||||
|
||||
|
@ -175,10 +145,10 @@ class DataStore extends DownloadsDataStore implements DataStoreInterface {
|
|||
'page_no' => (int) $query_args['page'],
|
||||
);
|
||||
|
||||
if ( $this->intervals_missing( $expected_interval_count, $db_records_count, $intervals_query['per_page'], $query_args['page'], $query_args['order'], $query_args['orderby'], count( $intervals ) ) ) {
|
||||
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'], $intervals_query['per_page'], $db_records_count, $expected_interval_count, $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 );
|
||||
}
|
||||
|
@ -203,4 +173,18 @@ class DataStore extends DownloadsDataStore implements DataStoreInterface {
|
|||
|
||||
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,6 +11,7 @@ defined( 'ABSPATH' ) || exit;
|
|||
|
||||
use \Automattic\WooCommerce\Admin\API\Reports\DataStore as ReportsDataStore;
|
||||
use \Automattic\WooCommerce\Admin\API\Reports\DataStoreInterface;
|
||||
use \Automattic\WooCommerce\Admin\API\Reports\SqlQuery;
|
||||
|
||||
/**
|
||||
* API\Reports\Orders\DataStore.
|
||||
|
@ -22,7 +23,7 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
|||
*
|
||||
* @var string
|
||||
*/
|
||||
const TABLE_NAME = 'wc_order_stats';
|
||||
protected static $table_name = 'wc_order_stats';
|
||||
|
||||
/**
|
||||
* Cache identifier.
|
||||
|
@ -50,18 +51,17 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
|||
);
|
||||
|
||||
/**
|
||||
* SQL columns to select in the db query and their mapping to SQL code.
|
||||
* Data store context used to pass to filters.
|
||||
*
|
||||
* @var array
|
||||
* @var string
|
||||
*/
|
||||
protected $report_columns = array();
|
||||
protected $context = 'orders';
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* Assign report columns once full table name has been assigned.
|
||||
*/
|
||||
public function __construct() {
|
||||
global $wpdb;
|
||||
$table_name = $wpdb->prefix . self::TABLE_NAME;
|
||||
protected function assign_report_columns() {
|
||||
$table_name = self::get_db_table_name();
|
||||
// Avoid ambigious columns in SQL query.
|
||||
$this->report_columns = array(
|
||||
'order_id' => "{$table_name}.order_id",
|
||||
|
@ -81,22 +81,23 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
|||
* Updates the database query with parameters used for orders report: coupons and products filters.
|
||||
*
|
||||
* @param array $query_args Query arguments supplied by the user.
|
||||
* @return array Array of parameters used for SQL query.
|
||||
*/
|
||||
protected function get_sql_query_params( $query_args ) {
|
||||
global $wpdb;
|
||||
$order_stats_lookup_table = $wpdb->prefix . self::TABLE_NAME;
|
||||
$operator = $this->get_match_operator( $query_args );
|
||||
$where_subquery = array();
|
||||
$order_stats_lookup_table = self::get_db_table_name();
|
||||
$order_coupon_lookup_table = $wpdb->prefix . 'wc_order_coupon_lookup';
|
||||
$order_product_lookup_table = $wpdb->prefix . 'wc_order_product_lookup';
|
||||
$operator = $this->get_match_operator( $query_args );
|
||||
$where_subquery = array();
|
||||
|
||||
$sql_query_params = $this->get_time_period_sql_params( $query_args, $order_stats_lookup_table );
|
||||
$sql_query_params = array_merge( $sql_query_params, $this->get_limit_sql_params( $query_args ) );
|
||||
$sql_query_params = array_merge( $sql_query_params, $this->get_order_by_sql_params( $query_args ) );
|
||||
$this->get_time_period_sql_params( $query_args, $order_stats_lookup_table );
|
||||
$this->get_limit_sql_params( $query_args );
|
||||
$this->get_order_by_sql_params( $query_args );
|
||||
|
||||
$status_subquery = $this->get_status_subquery( $query_args );
|
||||
if ( $status_subquery ) {
|
||||
if ( empty( $query_args['status_is'] ) && empty( $query_args['status_is_not'] ) ) {
|
||||
$sql_query_params['where_clause'] .= " AND {$status_subquery}";
|
||||
$this->subquery->add_sql_clause( 'where', "AND {$status_subquery}" );
|
||||
} else {
|
||||
$where_subquery[] = $status_subquery;
|
||||
}
|
||||
|
@ -117,17 +118,16 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
|||
$where_subquery[] = "{$order_stats_lookup_table}.returning_customer = ${returning_customer}";
|
||||
}
|
||||
|
||||
$refund_subquery = $this->get_refund_subquery( $query_args );
|
||||
$sql_query_params['from_clause'] .= $refund_subquery['from_clause'] ? $refund_subquery['from_clause'] : '';
|
||||
$refund_subquery = $this->get_refund_subquery( $query_args );
|
||||
$this->subquery->add_sql_clause( 'from', $refund_subquery['from_clause'] );
|
||||
if ( $refund_subquery['where_clause'] ) {
|
||||
$where_subquery[] = $refund_subquery['where_clause'];
|
||||
}
|
||||
|
||||
$included_coupons = $this->get_included_coupons( $query_args );
|
||||
$excluded_coupons = $this->get_excluded_coupons( $query_args );
|
||||
$order_coupon_lookup_table = $wpdb->prefix . 'wc_order_coupon_lookup';
|
||||
$included_coupons = $this->get_included_coupons( $query_args );
|
||||
$excluded_coupons = $this->get_excluded_coupons( $query_args );
|
||||
if ( $included_coupons || $excluded_coupons ) {
|
||||
$sql_query_params['from_clause'] .= " JOIN {$order_coupon_lookup_table} ON {$order_stats_lookup_table}.order_id = {$order_coupon_lookup_table}.order_id";
|
||||
$this->subquery->add_sql_clause( 'join', "JOIN {$order_coupon_lookup_table} ON {$order_stats_lookup_table}.order_id = {$order_coupon_lookup_table}.order_id" );
|
||||
}
|
||||
if ( $included_coupons ) {
|
||||
$where_subquery[] = "{$order_coupon_lookup_table}.coupon_id IN ({$included_coupons})";
|
||||
|
@ -136,11 +136,10 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
|||
$where_subquery[] = "{$order_coupon_lookup_table}.coupon_id NOT IN ({$excluded_coupons})";
|
||||
}
|
||||
|
||||
$included_products = $this->get_included_products( $query_args );
|
||||
$excluded_products = $this->get_excluded_products( $query_args );
|
||||
$order_product_lookup_table = $wpdb->prefix . 'wc_order_product_lookup';
|
||||
$included_products = $this->get_included_products( $query_args );
|
||||
$excluded_products = $this->get_excluded_products( $query_args );
|
||||
if ( $included_products || $excluded_products ) {
|
||||
$sql_query_params['from_clause'] .= " JOIN {$order_product_lookup_table} ON {$order_stats_lookup_table}.order_id = {$order_product_lookup_table}.order_id";
|
||||
$this->subquery->add_sql_clause( 'join', "JOIN {$order_product_lookup_table} ON {$order_stats_lookup_table}.order_id = {$order_product_lookup_table}.order_id" );
|
||||
}
|
||||
if ( $included_products ) {
|
||||
$where_subquery[] = "{$order_product_lookup_table}.product_id IN ({$included_products})";
|
||||
|
@ -150,10 +149,8 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
|||
}
|
||||
|
||||
if ( 0 < count( $where_subquery ) ) {
|
||||
$sql_query_params['where_clause'] .= ' AND (' . implode( " {$operator} ", $where_subquery ) . ')';
|
||||
$this->subquery->add_sql_clause( 'where', 'AND (' . implode( " {$operator} ", $where_subquery ) . ')' );
|
||||
}
|
||||
|
||||
return $sql_query_params;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -165,7 +162,7 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
|||
public function get_data( $query_args ) {
|
||||
global $wpdb;
|
||||
|
||||
$table_name = $wpdb->prefix . self::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.
|
||||
$defaults = array(
|
||||
|
@ -198,6 +195,8 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
|||
$data = $this->get_cached_data( $cache_key );
|
||||
|
||||
if ( false === $data ) {
|
||||
$this->initialize_queries();
|
||||
|
||||
$data = (object) array(
|
||||
'data' => array(),
|
||||
'total' => 0,
|
||||
|
@ -205,26 +204,19 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
|||
'page_no' => 0,
|
||||
);
|
||||
|
||||
$selections = $this->selected_columns( $query_args );
|
||||
$sql_query_params = $this->get_sql_query_params( $query_args );
|
||||
$selections = $this->selected_columns( $query_args );
|
||||
$params = $this->get_limit_params( $query_args );
|
||||
$this->get_sql_query_params( $query_args );
|
||||
$db_records_count = (int) $wpdb->get_var(
|
||||
"SELECT COUNT(*) FROM (
|
||||
SELECT
|
||||
{$table_name}.order_id
|
||||
FROM
|
||||
{$table_name}
|
||||
{$sql_query_params['from_clause']}
|
||||
WHERE
|
||||
1=1
|
||||
{$sql_query_params['where_time_clause']}
|
||||
{$sql_query_params['where_clause']}
|
||||
) AS tt"
|
||||
{$this->subquery->get_query_statement()}
|
||||
) AS tt"
|
||||
); // WPCS: cache ok, DB call ok, unprepared SQL ok.
|
||||
|
||||
if ( 0 === $sql_query_params['per_page'] ) {
|
||||
if ( 0 === $params['per_page'] ) {
|
||||
$total_pages = 0;
|
||||
} else {
|
||||
$total_pages = (int) ceil( $db_records_count / $sql_query_params['per_page'] );
|
||||
$total_pages = (int) ceil( $db_records_count / $params['per_page'] );
|
||||
}
|
||||
if ( $query_args['page'] < 1 || $query_args['page'] > $total_pages ) {
|
||||
$data = (object) array(
|
||||
|
@ -236,20 +228,12 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
|||
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' ) );
|
||||
$orders_data = $wpdb->get_results(
|
||||
"SELECT
|
||||
{$selections}
|
||||
FROM
|
||||
{$table_name}
|
||||
{$sql_query_params['from_clause']}
|
||||
WHERE
|
||||
1=1
|
||||
{$sql_query_params['where_time_clause']}
|
||||
{$sql_query_params['where_clause']}
|
||||
ORDER BY
|
||||
{$sql_query_params['order_by_clause']}
|
||||
{$sql_query_params['limit']}
|
||||
",
|
||||
$this->subquery->get_query_statement(),
|
||||
ARRAY_A
|
||||
); // WPCS: cache ok, DB call ok, unprepared SQL ok.
|
||||
|
||||
|
@ -271,7 +255,6 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
|||
|
||||
$this->set_cached_data( $cache_key, $data );
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
|
@ -368,8 +351,8 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
|||
|
||||
$products = $wpdb->get_results(
|
||||
"SELECT order_id, ID as product_id, post_title as product_name, product_qty as product_quantity
|
||||
FROM {$wpdb->prefix}posts
|
||||
JOIN {$order_product_lookup_table} ON {$order_product_lookup_table}.product_id = {$wpdb->prefix}posts.ID
|
||||
FROM {$wpdb->posts}
|
||||
JOIN {$order_product_lookup_table} ON {$order_product_lookup_table}.product_id = {$wpdb->posts}.ID
|
||||
WHERE
|
||||
order_id IN ({$included_order_ids})
|
||||
",
|
||||
|
@ -423,8 +406,8 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
|||
|
||||
$coupons = $wpdb->get_results(
|
||||
"SELECT order_id, coupon_id, post_title as coupon_code
|
||||
FROM {$wpdb->prefix}posts
|
||||
JOIN {$order_coupon_lookup_table} ON {$order_coupon_lookup_table}.coupon_id = {$wpdb->prefix}posts.ID
|
||||
FROM {$wpdb->posts}
|
||||
JOIN {$order_coupon_lookup_table} ON {$order_coupon_lookup_table}.coupon_id = {$wpdb->posts}.ID
|
||||
WHERE
|
||||
order_id IN ({$included_order_ids})
|
||||
",
|
||||
|
@ -433,4 +416,14 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
|||
|
||||
return $coupons;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize query objects.
|
||||
*/
|
||||
protected function initialize_queries() {
|
||||
$this->clear_all_clauses();
|
||||
$this->subquery = new SqlQuery( $this->context . '_subquery' );
|
||||
$this->subquery->add_sql_clause( 'select', self::get_db_table_name() . '.order_id' );
|
||||
$this->subquery->add_sql_clause( 'from', self::get_db_table_name() );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ defined( 'ABSPATH' ) || exit;
|
|||
use \Automattic\WooCommerce\Admin\API\Reports\DataStore as ReportsDataStore;
|
||||
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\Cache as ReportsCache;
|
||||
|
||||
/**
|
||||
|
@ -24,7 +25,7 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
|||
*
|
||||
* @var string
|
||||
*/
|
||||
const TABLE_NAME = 'wc_order_stats';
|
||||
protected static $table_name = 'wc_order_stats';
|
||||
|
||||
/**
|
||||
* Cron event name.
|
||||
|
@ -62,18 +63,17 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
|||
);
|
||||
|
||||
/**
|
||||
* SQL definition for each column.
|
||||
* Data store context used to pass to filters.
|
||||
*
|
||||
* @var array
|
||||
* @var string
|
||||
*/
|
||||
protected $report_columns = array();
|
||||
protected $context = 'order_stats';
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* Assign report columns once full table name has been assigned.
|
||||
*/
|
||||
public function __construct() {
|
||||
global $wpdb;
|
||||
$table_name = $wpdb->prefix . self::TABLE_NAME;
|
||||
protected function assign_report_columns() {
|
||||
$table_name = self::get_db_table_name();
|
||||
// Avoid ambigious columns in SQL query.
|
||||
$this->report_columns = array(
|
||||
'orders_count' => "SUM( CASE WHEN {$table_name}.parent_id = 0 THEN 1 ELSE 0 END ) as orders_count",
|
||||
|
@ -104,81 +104,63 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
|||
* Updates the totals and intervals database queries with parameters used for Orders report: categories, coupons and order status.
|
||||
*
|
||||
* @param array $query_args Query arguments supplied by the user.
|
||||
* @param array $totals_query Array of options for totals db query.
|
||||
* @param array $intervals_query Array of options for intervals db query.
|
||||
*/
|
||||
protected function orders_stats_sql_filter( $query_args, &$totals_query, &$intervals_query ) {
|
||||
protected function orders_stats_sql_filter( $query_args ) {
|
||||
// @todo Performance of all of this?
|
||||
global $wpdb;
|
||||
|
||||
$from_clause = '';
|
||||
$orders_stats_table = $wpdb->prefix . self::TABLE_NAME;
|
||||
$orders_stats_table = self::get_db_table_name();
|
||||
$product_lookup = $wpdb->prefix . 'wc_order_product_lookup';
|
||||
$coupon_lookup = $wpdb->prefix . 'wc_order_coupon_lookup';
|
||||
$operator = $this->get_match_operator( $query_args );
|
||||
|
||||
$where_filters = array();
|
||||
|
||||
// @todo Maybe move the sql inside the get_included/excluded functions?
|
||||
// Products filters.
|
||||
$included_products = $this->get_included_products( $query_args );
|
||||
$excluded_products = $this->get_excluded_products( $query_args );
|
||||
if ( $included_products ) {
|
||||
$where_filters[] = " {$orders_stats_table}.order_id IN (
|
||||
SELECT
|
||||
DISTINCT {$wpdb->prefix}wc_order_product_lookup.order_id
|
||||
FROM
|
||||
{$wpdb->prefix}wc_order_product_lookup
|
||||
WHERE
|
||||
{$wpdb->prefix}wc_order_product_lookup.product_id IN ({$included_products})
|
||||
)";
|
||||
}
|
||||
|
||||
if ( $excluded_products ) {
|
||||
$where_filters[] = " {$orders_stats_table}.order_id NOT IN (
|
||||
SELECT
|
||||
DISTINCT {$wpdb->prefix}wc_order_product_lookup.order_id
|
||||
FROM
|
||||
{$wpdb->prefix}wc_order_product_lookup
|
||||
WHERE
|
||||
{$wpdb->prefix}wc_order_product_lookup.product_id IN ({$excluded_products})
|
||||
)";
|
||||
}
|
||||
$where_filters[] = $this->get_object_where_filter(
|
||||
$orders_stats_table,
|
||||
'order_id',
|
||||
$product_lookup,
|
||||
'product_id',
|
||||
'IN',
|
||||
$this->get_included_products( $query_args )
|
||||
);
|
||||
$where_filters[] = $this->get_object_where_filter(
|
||||
$orders_stats_table,
|
||||
'order_id',
|
||||
$product_lookup,
|
||||
'product_id',
|
||||
'NOT IN',
|
||||
$this->get_excluded_products( $query_args )
|
||||
);
|
||||
|
||||
// Coupons filters.
|
||||
$included_coupons = $this->get_included_coupons( $query_args );
|
||||
$excluded_coupons = $this->get_excluded_coupons( $query_args );
|
||||
if ( $included_coupons ) {
|
||||
$where_filters[] = " {$orders_stats_table}.order_id IN (
|
||||
SELECT
|
||||
DISTINCT {$wpdb->prefix}wc_order_coupon_lookup.order_id
|
||||
FROM
|
||||
{$wpdb->prefix}wc_order_coupon_lookup
|
||||
WHERE
|
||||
{$wpdb->prefix}wc_order_coupon_lookup.coupon_id IN ({$included_coupons})
|
||||
)";
|
||||
}
|
||||
|
||||
if ( $excluded_coupons ) {
|
||||
$where_filters[] = " {$orders_stats_table}.order_id NOT IN (
|
||||
SELECT
|
||||
DISTINCT {$wpdb->prefix}wc_order_coupon_lookup.order_id
|
||||
FROM
|
||||
{$wpdb->prefix}wc_order_coupon_lookup
|
||||
WHERE
|
||||
{$wpdb->prefix}wc_order_coupon_lookup.coupon_id IN ({$excluded_coupons})
|
||||
)";
|
||||
}
|
||||
|
||||
$customer_filter = $this->get_customer_subquery( $query_args );
|
||||
if ( $customer_filter ) {
|
||||
$where_filters[] = $customer_filter;
|
||||
}
|
||||
$where_filters[] = $this->get_object_where_filter(
|
||||
$orders_stats_table,
|
||||
'order_id',
|
||||
$coupon_lookup,
|
||||
'coupon_id',
|
||||
'IN',
|
||||
$this->get_included_coupons( $query_args )
|
||||
);
|
||||
$where_filters[] = $this->get_object_where_filter(
|
||||
$orders_stats_table,
|
||||
'order_id',
|
||||
$coupon_lookup,
|
||||
'coupon_id',
|
||||
'NOT IN',
|
||||
$this->get_excluded_coupons( $query_args )
|
||||
);
|
||||
|
||||
$where_filters[] = $this->get_customer_subquery( $query_args );
|
||||
$refund_subquery = $this->get_refund_subquery( $query_args );
|
||||
$from_clause .= $refund_subquery['from_clause'];
|
||||
if ( $refund_subquery['where_clause'] ) {
|
||||
$where_filters[] = $refund_subquery['where_clause'];
|
||||
$from_clause .= $refund_subquery['from_clause'];
|
||||
}
|
||||
|
||||
$where_filters = array_filter( $where_filters );
|
||||
$where_subclause = implode( " $operator ", $where_filters );
|
||||
|
||||
// Append status filter after to avoid matching ANY on default statuses.
|
||||
|
@ -192,10 +174,10 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
|||
|
||||
// To avoid requesting the subqueries twice, the result is applied to all queries passed to the method.
|
||||
if ( $where_subclause ) {
|
||||
$totals_query['where_clause'] .= " AND ( $where_subclause )";
|
||||
$totals_query['from_clause'] .= $from_clause;
|
||||
$intervals_query['where_clause'] .= " AND ( $where_subclause )";
|
||||
$intervals_query['from_clause'] .= $from_clause;
|
||||
$this->total_query->add_sql_clause( 'where', "AND ( $where_subclause )" );
|
||||
$this->total_query->add_sql_clause( 'join', $from_clause );
|
||||
$this->interval_query->add_sql_clause( 'where', "AND ( $where_subclause )" );
|
||||
$this->interval_query->add_sql_clause( 'join', $from_clause );
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -208,7 +190,7 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
|||
public function get_data( $query_args ) {
|
||||
global $wpdb;
|
||||
|
||||
$table_name = $wpdb->prefix . self::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(
|
||||
|
@ -243,6 +225,8 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
|||
$data = $this->get_cached_data( $cache_key );
|
||||
|
||||
if ( false === $data ) {
|
||||
$this->initialize_queries();
|
||||
|
||||
$data = (object) array(
|
||||
'totals' => (object) array(),
|
||||
'intervals' => (object) array(),
|
||||
|
@ -251,10 +235,13 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
|||
'page_no' => 0,
|
||||
);
|
||||
|
||||
$selections = $this->selected_columns( $query_args );
|
||||
$totals_query = $this->get_time_period_sql_params( $query_args, $table_name );
|
||||
$intervals_query = $this->get_intervals_sql_params( $query_args, $table_name );
|
||||
$coupon_join = "LEFT JOIN (
|
||||
$selections = $this->selected_columns( $query_args );
|
||||
$this->get_time_period_sql_params( $query_args, $table_name );
|
||||
$this->get_intervals_sql_params( $query_args, $table_name );
|
||||
$this->get_order_by_sql_params( $query_args );
|
||||
$where_time = $this->get_sql_clause( 'where_time' );
|
||||
$params = $this->get_limit_sql_params( $query_args );
|
||||
$coupon_join = "LEFT JOIN (
|
||||
SELECT
|
||||
order_id,
|
||||
SUM(discount_amount) AS discount_amount,
|
||||
|
@ -267,25 +254,32 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
|||
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, $totals_query, $intervals_query );
|
||||
|
||||
$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(
|
||||
"SELECT
|
||||
{$selections}
|
||||
FROM
|
||||
{$table_name}
|
||||
{$totals_query['from_clause']}
|
||||
{$coupon_join}
|
||||
WHERE
|
||||
1=1
|
||||
{$totals_query['where_time_clause']}
|
||||
{$totals_query['where_clause']}",
|
||||
$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_reports_revenue_result_failed', __( 'Sorry, fetching revenue data failed.', 'woocommerce-admin' ) );
|
||||
}
|
||||
|
||||
// @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 );
|
||||
|
@ -294,53 +288,30 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
|||
$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(
|
||||
"SELECT
|
||||
{$intervals_query['select_clause']} AS time_interval
|
||||
FROM
|
||||
{$table_name}
|
||||
{$intervals_query['from_clause']}
|
||||
{$coupon_join}
|
||||
WHERE
|
||||
1=1
|
||||
{$intervals_query['where_time_clause']}
|
||||
{$intervals_query['where_clause']}
|
||||
GROUP BY
|
||||
time_interval"
|
||||
$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 / $intervals_query['per_page'] );
|
||||
$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( $intervals_query, $query_args, $db_interval_count, $expected_interval_count, $table_name );
|
||||
|
||||
$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 ) {
|
||||
$selections = ', ' . $selections;
|
||||
$this->interval_query->add_sql_clause( 'select', ', ' . $selections );
|
||||
}
|
||||
|
||||
$intervals = $wpdb->get_results(
|
||||
"SELECT
|
||||
MAX({$table_name}.date_created) AS datetime_anchor,
|
||||
{$intervals_query['select_clause']} AS time_interval
|
||||
{$selections}
|
||||
FROM
|
||||
{$table_name}
|
||||
{$intervals_query['from_clause']}
|
||||
{$coupon_join}
|
||||
WHERE
|
||||
1=1
|
||||
{$intervals_query['where_time_clause']}
|
||||
{$intervals_query['where_clause']}
|
||||
GROUP BY
|
||||
time_interval
|
||||
ORDER BY
|
||||
{$intervals_query['order_by_clause']}
|
||||
{$intervals_query['limit']}",
|
||||
$this->interval_query->get_query_statement(),
|
||||
ARRAY_A
|
||||
); // WPCS: cache ok, DB call ok, unprepared SQL ok.
|
||||
|
||||
|
@ -361,10 +332,10 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
|||
'page_no' => (int) $query_args['page'],
|
||||
);
|
||||
|
||||
if ( TimeInterval::intervals_missing( $expected_interval_count, $db_interval_count, $intervals_query['per_page'], $query_args['page'], $query_args['order'], $query_args['orderby'], count( $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'], $intervals_query['per_page'], $db_interval_count, $expected_interval_count, $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 );
|
||||
}
|
||||
|
@ -388,8 +359,7 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
|||
public function get_unique_product_count( $from_clause, $where_time_clause, $where_clause ) {
|
||||
global $wpdb;
|
||||
|
||||
$table_name = $wpdb->prefix . self::TABLE_NAME;
|
||||
|
||||
$table_name = self::get_db_table_name();
|
||||
return $wpdb->get_var(
|
||||
"SELECT
|
||||
COUNT( DISTINCT {$wpdb->prefix}wc_order_product_lookup.product_id )
|
||||
|
@ -414,7 +384,7 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
|||
public function get_unique_coupon_count( $from_clause, $where_time_clause, $where_clause ) {
|
||||
global $wpdb;
|
||||
|
||||
$table_name = $wpdb->prefix . self::TABLE_NAME;
|
||||
$table_name = self::get_db_table_name();
|
||||
return $wpdb->get_var(
|
||||
"SELECT
|
||||
COUNT(DISTINCT coupon_id)
|
||||
|
@ -455,7 +425,7 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
|||
*/
|
||||
public static function update( $order ) {
|
||||
global $wpdb;
|
||||
$table_name = $wpdb->prefix . self::TABLE_NAME;
|
||||
$table_name = self::get_db_table_name();
|
||||
|
||||
if ( ! $order->get_id() || ! $order->get_date_created() ) {
|
||||
return -1;
|
||||
|
@ -521,20 +491,13 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
|||
*/
|
||||
public static function delete_order( $post_id ) {
|
||||
global $wpdb;
|
||||
$order_id = (int) $post_id;
|
||||
$table_name = $wpdb->prefix . self::TABLE_NAME;
|
||||
$order_id = (int) $post_id;
|
||||
|
||||
if ( 'shop_order' !== get_post_type( $order_id ) && 'shop_order_refund' !== get_post_type( $order_id ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$wpdb->query(
|
||||
$wpdb->prepare(
|
||||
"DELETE FROM ${table_name} WHERE order_id = %d",
|
||||
$order_id
|
||||
)
|
||||
);
|
||||
|
||||
$wpdb->delete( self::get_db_table_name(), array( 'order_id' => $order_id ) );
|
||||
/**
|
||||
* Fires when orders stats are deleted.
|
||||
*
|
||||
|
@ -634,7 +597,7 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
|||
*/
|
||||
protected static function set_customer_first_order( $customer_id, $order_id ) {
|
||||
global $wpdb;
|
||||
$orders_stats_table = $wpdb->prefix . self::TABLE_NAME;
|
||||
$orders_stats_table = self::get_db_table_name();
|
||||
|
||||
$wpdb->query(
|
||||
$wpdb->prepare(
|
||||
|
@ -644,4 +607,18 @@ 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' );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ defined( 'ABSPATH' ) || exit;
|
|||
use \Automattic\WooCommerce\Admin\API\Reports\DataStore as ReportsDataStore;
|
||||
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\Cache as ReportsCache;
|
||||
|
||||
/**
|
||||
|
@ -24,7 +25,7 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
|||
*
|
||||
* @var string
|
||||
*/
|
||||
const TABLE_NAME = 'wc_order_product_lookup';
|
||||
protected static $table_name = 'wc_order_product_lookup';
|
||||
|
||||
/**
|
||||
* Cache identifier.
|
||||
|
@ -58,18 +59,6 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
|||
'sku' => 'strval',
|
||||
);
|
||||
|
||||
/**
|
||||
* SQL columns to select in the db query and their mapping to SQL code.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $report_columns = array(
|
||||
'product_id' => 'product_id',
|
||||
'items_sold' => 'SUM(product_qty) as items_sold',
|
||||
'net_revenue' => 'SUM(product_net_revenue) AS net_revenue',
|
||||
'orders_count' => 'COUNT( DISTINCT ( CASE WHEN product_gross_revenue >= 0 THEN order_id END ) ) as orders_count',
|
||||
);
|
||||
|
||||
/**
|
||||
* Extended product attributes to include in the data.
|
||||
*
|
||||
|
@ -90,13 +79,23 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
|||
);
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* Data store context used to pass to filters.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public function __construct() {
|
||||
global $wpdb;
|
||||
$table_name = $wpdb->prefix . self::TABLE_NAME;
|
||||
// Avoid ambigious column order_id in SQL query.
|
||||
$this->report_columns['orders_count'] = str_replace( 'order_id', $table_name . '.order_id', $this->report_columns['orders_count'] );
|
||||
protected $context = 'products';
|
||||
|
||||
/**
|
||||
* Assign report columns once full table name has been assigned.
|
||||
*/
|
||||
protected function assign_report_columns() {
|
||||
$table_name = self::get_db_table_name();
|
||||
$this->report_columns = array(
|
||||
'product_id' => 'product_id',
|
||||
'items_sold' => 'SUM(product_qty) as items_sold',
|
||||
'net_revenue' => 'SUM(product_net_revenue) AS net_revenue',
|
||||
'orders_count' => "COUNT( DISTINCT ( CASE WHEN product_gross_revenue >= 0 THEN {$table_name}.order_id END ) ) as orders_count",
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -110,62 +109,69 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
|||
* Fills FROM clause of SQL request based on user supplied parameters.
|
||||
*
|
||||
* @param array $query_args Parameters supplied by the user.
|
||||
* @param string $arg_name Name of the FROM sql param.
|
||||
* @param string $arg_name Target of the JOIN sql param.
|
||||
* @param string $id_cell ID cell identifier, like `table_name.id_column_name`.
|
||||
* @return array
|
||||
*/
|
||||
protected function get_from_sql_params( $query_args, $arg_name, $id_cell ) {
|
||||
global $wpdb;
|
||||
$sql_query['outer_from_clause'] = '';
|
||||
|
||||
$type = 'join';
|
||||
// Order by product name requires extra JOIN.
|
||||
if ( 'product_name' === $query_args['orderby'] ) {
|
||||
$sql_query[ $arg_name ] .= " JOIN {$wpdb->prefix}posts AS _products ON {$id_cell} = _products.ID";
|
||||
switch ( $query_args['orderby'] ) {
|
||||
case 'product_name':
|
||||
$join = " JOIN {$wpdb->posts} AS _products ON {$id_cell} = _products.ID";
|
||||
break;
|
||||
case 'sku':
|
||||
$join = " JOIN {$wpdb->postmeta} AS postmeta ON {$id_cell} = postmeta.post_id AND postmeta.meta_key = '_sku'";
|
||||
break;
|
||||
case 'variations':
|
||||
$type = 'left_join';
|
||||
$join = "LEFT JOIN ( SELECT post_parent, COUNT(*) AS variations FROM {$wpdb->posts} WHERE post_type = 'product_variation' GROUP BY post_parent ) AS _variations ON {$id_cell} = _variations.post_parent";
|
||||
break;
|
||||
default:
|
||||
$join = '';
|
||||
break;
|
||||
}
|
||||
if ( 'sku' === $query_args['orderby'] ) {
|
||||
$sql_query[ $arg_name ] .= " JOIN {$wpdb->prefix}postmeta AS postmeta ON {$id_cell} = postmeta.post_id AND postmeta.meta_key = '_sku'";
|
||||
if ( $join ) {
|
||||
if ( 'inner' === $arg_name ) {
|
||||
$this->subquery->add_sql_clause( $type, $join );
|
||||
} else {
|
||||
$this->add_sql_clause( $type, $join );
|
||||
}
|
||||
}
|
||||
if ( 'variations' === $query_args['orderby'] ) {
|
||||
$sql_query[ $arg_name ] .= " LEFT JOIN ( SELECT post_parent, COUNT(*) AS variations FROM {$wpdb->prefix}posts WHERE post_type = 'product_variation' GROUP BY post_parent ) AS _variations ON {$id_cell} = _variations.post_parent";
|
||||
}
|
||||
|
||||
return $sql_query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the database query with parameters used for Products report: categories and order status.
|
||||
*
|
||||
* @param array $query_args Query arguments supplied by the user.
|
||||
* @return array Array of parameters used for SQL query.
|
||||
*/
|
||||
protected function get_sql_query_params( $query_args ) {
|
||||
global $wpdb;
|
||||
$order_product_lookup_table = $wpdb->prefix . self::TABLE_NAME;
|
||||
$order_product_lookup_table = self::get_db_table_name();
|
||||
|
||||
$sql_query_params = $this->get_time_period_sql_params( $query_args, $order_product_lookup_table );
|
||||
$sql_query_params = array_merge( $sql_query_params, $this->get_limit_sql_params( $query_args ) );
|
||||
$sql_query_params = array_merge( $sql_query_params, $this->get_order_by_sql_params( $query_args ) );
|
||||
$this->get_time_period_sql_params( $query_args, $order_product_lookup_table );
|
||||
$this->get_limit_sql_params( $query_args );
|
||||
$this->get_order_by_sql_params( $query_args );
|
||||
|
||||
$included_products = $this->get_included_products( $query_args );
|
||||
if ( $included_products ) {
|
||||
$sql_query_params = array_merge( $sql_query_params, $this->get_from_sql_params( $query_args, 'outer_from_clause', 'default_results.product_id' ) );
|
||||
$sql_query_params['where_clause'] .= " AND {$order_product_lookup_table}.product_id IN ({$included_products})";
|
||||
$this->get_from_sql_params( $query_args, 'outer', 'default_results.product_id' );
|
||||
$this->subquery->add_sql_clause( 'where', "AND {$order_product_lookup_table}.product_id IN ({$included_products})" );
|
||||
} else {
|
||||
$sql_query_params = array_merge( $sql_query_params, $this->get_from_sql_params( $query_args, 'from_clause', "{$order_product_lookup_table}.product_id" ) );
|
||||
$this->get_from_sql_params( $query_args, 'inner', "{$order_product_lookup_table}.product_id" );
|
||||
}
|
||||
|
||||
$included_variations = $this->get_included_variations( $query_args );
|
||||
if ( $included_variations ) {
|
||||
$sql_query_params['where_clause'] .= " AND {$order_product_lookup_table}.variation_id IN ({$included_variations})";
|
||||
$this->subquery->add_sql_clause( 'where', "AND {$order_product_lookup_table}.variation_id IN ({$included_variations})" );
|
||||
}
|
||||
|
||||
$order_status_filter = $this->get_status_subquery( $query_args );
|
||||
if ( $order_status_filter ) {
|
||||
$sql_query_params['from_clause'] .= " JOIN {$wpdb->prefix}wc_order_stats ON {$order_product_lookup_table}.order_id = {$wpdb->prefix}wc_order_stats.order_id";
|
||||
$sql_query_params['where_clause'] .= " AND ( {$order_status_filter} )";
|
||||
$this->subquery->add_sql_clause( 'join', "JOIN {$wpdb->prefix}wc_order_stats ON {$order_product_lookup_table}.order_id = {$wpdb->prefix}wc_order_stats.order_id" );
|
||||
$this->subquery->add_sql_clause( 'where', "AND ( {$order_status_filter} )" );
|
||||
}
|
||||
|
||||
return $sql_query_params;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -175,11 +181,8 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
|||
* @return string
|
||||
*/
|
||||
protected function normalize_order_by( $order_by ) {
|
||||
global $wpdb;
|
||||
$order_product_lookup_table = $wpdb->prefix . self::TABLE_NAME;
|
||||
|
||||
if ( 'date' === $order_by ) {
|
||||
return $order_product_lookup_table . '.date_created';
|
||||
return self::get_db_table_name() . '.date_created';
|
||||
}
|
||||
if ( 'product_name' === $order_by ) {
|
||||
return 'post_title';
|
||||
|
@ -261,7 +264,7 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
|||
public function get_data( $query_args ) {
|
||||
global $wpdb;
|
||||
|
||||
$table_name = $wpdb->prefix . self::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.
|
||||
$defaults = array(
|
||||
|
@ -287,6 +290,8 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
|||
$data = $this->get_cached_data( $cache_key );
|
||||
|
||||
if ( false === $data ) {
|
||||
$this->initialize_queries();
|
||||
|
||||
$data = (object) array(
|
||||
'data' => array(),
|
||||
'total' => 0,
|
||||
|
@ -295,12 +300,13 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
|||
);
|
||||
|
||||
$selections = $this->selected_columns( $query_args );
|
||||
$sql_query_params = $this->get_sql_query_params( $query_args );
|
||||
$included_products = $this->get_included_products_array( $query_args );
|
||||
$params = $this->get_limit_params( $query_args );
|
||||
$this->get_sql_query_params( $query_args );
|
||||
|
||||
if ( count( $included_products ) > 0 ) {
|
||||
$total_results = count( $included_products );
|
||||
$total_pages = (int) ceil( $total_results / $sql_query_params['per_page'] );
|
||||
$total_pages = (int) ceil( $total_results / $params['per_page'] );
|
||||
|
||||
if ( 'date' === $query_args['orderby'] ) {
|
||||
$selections .= ", {$table_name}.date_created";
|
||||
|
@ -309,59 +315,43 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
|||
$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' );
|
||||
$prefix = "SELECT {$join_selections} FROM (";
|
||||
$suffix = ") AS {$table_name}";
|
||||
$right_join = "RIGHT JOIN ( {$ids_table} ) AS default_results
|
||||
ON default_results.product_id = {$table_name}.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"
|
||||
);
|
||||
|
||||
$products_query = $this->get_query_statement();
|
||||
} else {
|
||||
$db_records_count = (int) $wpdb->get_var(
|
||||
"SELECT COUNT(*) FROM (
|
||||
SELECT
|
||||
product_id
|
||||
FROM
|
||||
{$table_name}
|
||||
{$sql_query_params['from_clause']}
|
||||
WHERE
|
||||
1=1
|
||||
{$sql_query_params['where_time_clause']}
|
||||
{$sql_query_params['where_clause']}
|
||||
GROUP BY
|
||||
product_id
|
||||
) AS tt"
|
||||
{$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 / $sql_query_params['per_page'] );
|
||||
$total_pages = (int) ceil( $db_records_count / $params['per_page'] );
|
||||
|
||||
if ( ( $query_args['page'] < 1 || $query_args['page'] > $total_pages ) ) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
$prefix = '';
|
||||
$suffix = '';
|
||||
$right_join = '';
|
||||
$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(
|
||||
"${prefix}
|
||||
SELECT
|
||||
{$selections}
|
||||
FROM
|
||||
{$table_name}
|
||||
{$sql_query_params['from_clause']}
|
||||
WHERE
|
||||
1=1
|
||||
{$sql_query_params['where_time_clause']}
|
||||
{$sql_query_params['where_clause']}
|
||||
GROUP BY
|
||||
product_id
|
||||
{$suffix}
|
||||
{$right_join}
|
||||
{$sql_query_params['outer_from_clause']}
|
||||
ORDER BY
|
||||
{$sql_query_params['order_by_clause']}
|
||||
{$sql_query_params['limit']}
|
||||
",
|
||||
$products_query,
|
||||
ARRAY_A
|
||||
); // WPCS: cache ok, DB call ok, unprepared SQL ok.
|
||||
|
||||
|
@ -433,7 +423,7 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
|||
}
|
||||
|
||||
$result = $wpdb->replace(
|
||||
$wpdb->prefix . self::TABLE_NAME,
|
||||
self::get_db_table_name(),
|
||||
array(
|
||||
'order_item_id' => $order_item_id,
|
||||
'order_id' => $order->get_id(),
|
||||
|
@ -490,14 +480,7 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
|||
public static function sync_on_order_delete( $order_id ) {
|
||||
global $wpdb;
|
||||
|
||||
$table_name = $wpdb->prefix . self::TABLE_NAME;
|
||||
|
||||
$wpdb->query(
|
||||
$wpdb->prepare(
|
||||
"DELETE FROM ${table_name} WHERE order_id = %d",
|
||||
$order_id
|
||||
)
|
||||
);
|
||||
$wpdb->delete( self::get_db_table_name(), array( 'order_id' => $order_id ) );
|
||||
|
||||
/**
|
||||
* Fires when product's reports are removed from database.
|
||||
|
@ -509,4 +492,15 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
|||
|
||||
ReportsCache::invalidate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize query objects.
|
||||
*/
|
||||
protected function initialize_queries() {
|
||||
$this->clear_all_clauses();
|
||||
$this->subquery = new SqlQuery( $this->context . '_subquery' );
|
||||
$this->subquery->add_sql_clause( 'select', 'product_id' );
|
||||
$this->subquery->add_sql_clause( 'from', self::get_db_table_name() );
|
||||
$this->subquery->add_sql_clause( 'group_by', 'product_id' );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ defined( 'ABSPATH' ) || exit;
|
|||
use \Automattic\WooCommerce\Admin\API\Reports\Products\DataStore as ProductsDataStore;
|
||||
use \Automattic\WooCommerce\Admin\API\Reports\DataStoreInterface;
|
||||
use \Automattic\WooCommerce\Admin\API\Reports\TimeInterval;
|
||||
use \Automattic\WooCommerce\Admin\API\Reports\SqlQuery;
|
||||
|
||||
/**
|
||||
* API\Reports\Products\Stats\DataStore.
|
||||
|
@ -35,42 +36,37 @@ class DataStore extends ProductsDataStore implements DataStoreInterface {
|
|||
);
|
||||
|
||||
/**
|
||||
* SQL columns to select in the db query.
|
||||
* Data store context used to pass to filters.
|
||||
*
|
||||
* @var array
|
||||
* @var string
|
||||
*/
|
||||
protected $report_columns = array(
|
||||
'items_sold' => 'SUM(product_qty) as items_sold',
|
||||
'net_revenue' => 'SUM(product_net_revenue) AS net_revenue',
|
||||
'orders_count' => 'COUNT( DISTINCT ( CASE WHEN product_gross_revenue >= 0 THEN order_id END ) ) as orders_count',
|
||||
'products_count' => 'COUNT(DISTINCT product_id) as products_count',
|
||||
'variations_count' => 'COUNT(DISTINCT variation_id) as variations_count',
|
||||
);
|
||||
protected $context = 'product_stats';
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* Assign report columns once full table name has been assigned.
|
||||
*/
|
||||
public function __construct() {
|
||||
global $wpdb;
|
||||
$table_name = $wpdb->prefix . self::TABLE_NAME;
|
||||
// Avoid ambigious column order_id in SQL query.
|
||||
$this->report_columns['orders_count'] = str_replace( 'order_id', $table_name . '.order_id', $this->report_columns['orders_count'] );
|
||||
protected function assign_report_columns() {
|
||||
$table_name = self::get_db_table_name();
|
||||
$this->report_columns = array(
|
||||
'items_sold' => 'SUM(product_qty) as items_sold',
|
||||
'net_revenue' => 'SUM(product_net_revenue) AS net_revenue',
|
||||
'orders_count' => "COUNT( DISTINCT ( CASE WHEN product_gross_revenue >= 0 THEN {$table_name}.order_id END ) ) as orders_count",
|
||||
'products_count' => 'COUNT(DISTINCT product_id) as products_count',
|
||||
'variations_count' => 'COUNT(DISTINCT variation_id) as variations_count',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the database query with parameters used for Products Stats report: categories and order status.
|
||||
*
|
||||
* @param array $query_args Query arguments supplied by the user.
|
||||
* @param array $totals_params SQL parameters for the totals query.
|
||||
* @param array $intervals_params SQL parameters for the intervals query.
|
||||
*/
|
||||
protected function update_sql_query_params( $query_args, &$totals_params, &$intervals_params ) {
|
||||
protected function update_sql_query_params( $query_args ) {
|
||||
global $wpdb;
|
||||
|
||||
$products_where_clause = '';
|
||||
$products_from_clause = '';
|
||||
|
||||
$order_product_lookup_table = $wpdb->prefix . self::TABLE_NAME;
|
||||
$products_where_clause = '';
|
||||
$products_from_clause = '';
|
||||
$order_product_lookup_table = self::get_db_table_name();
|
||||
|
||||
$included_products = $this->get_included_products( $query_args );
|
||||
if ( $included_products ) {
|
||||
|
@ -88,13 +84,14 @@ class DataStore extends ProductsDataStore implements DataStoreInterface {
|
|||
$products_where_clause .= " AND ( {$order_status_filter} )";
|
||||
}
|
||||
|
||||
$totals_params = array_merge( $totals_params, $this->get_time_period_sql_params( $query_args, $order_product_lookup_table ) );
|
||||
$totals_params['where_clause'] .= $products_where_clause;
|
||||
$totals_params['from_clause'] .= $products_from_clause;
|
||||
$this->get_time_period_sql_params( $query_args, $order_product_lookup_table );
|
||||
$this->total_query->add_sql_clause( 'where', $products_where_clause );
|
||||
$this->total_query->add_sql_clause( 'join', $products_from_clause );
|
||||
|
||||
$intervals_params = array_merge( $intervals_params, $this->get_intervals_sql_params( $query_args, $order_product_lookup_table ) );
|
||||
$intervals_params['where_clause'] .= $products_where_clause;
|
||||
$intervals_params['from_clause'] .= $products_from_clause;
|
||||
$this->get_intervals_sql_params( $query_args, $order_product_lookup_table );
|
||||
$this->interval_query->add_sql_clause( 'where', $products_where_clause );
|
||||
$this->interval_query->add_sql_clause( 'join', $products_from_clause );
|
||||
$this->interval_query->add_sql_clause( 'select', $this->get_sql_clause( 'select' ) . ' AS time_interval' );
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -107,7 +104,7 @@ class DataStore extends ProductsDataStore implements DataStoreInterface {
|
|||
public function get_data( $query_args ) {
|
||||
global $wpdb;
|
||||
|
||||
$table_name = $wpdb->prefix . self::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.
|
||||
$defaults = array(
|
||||
|
@ -133,47 +130,50 @@ class DataStore extends ProductsDataStore implements DataStoreInterface {
|
|||
$data = $this->get_cached_data( $cache_key );
|
||||
|
||||
if ( false === $data ) {
|
||||
$selections = $this->selected_columns( $query_args );
|
||||
$totals_query = array();
|
||||
$intervals_query = array();
|
||||
$this->update_sql_query_params( $query_args, $totals_query, $intervals_query );
|
||||
$this->initialize_queries();
|
||||
|
||||
$selections = $this->selected_columns( $query_args );
|
||||
$params = $this->get_limit_params( $query_args );
|
||||
|
||||
$this->update_sql_query_params( $query_args );
|
||||
$this->get_limit_sql_params( $query_args );
|
||||
$this->interval_query->add_sql_clause( 'where_time', $this->get_sql_clause( 'where_time' ) );
|
||||
|
||||
$db_intervals = $wpdb->get_col(
|
||||
"SELECT
|
||||
{$intervals_query['select_clause']} AS time_interval
|
||||
FROM
|
||||
{$table_name}
|
||||
{$intervals_query['from_clause']}
|
||||
WHERE
|
||||
1=1
|
||||
{$intervals_query['where_time_clause']}
|
||||
{$intervals_query['where_clause']}
|
||||
GROUP BY
|
||||
time_interval"
|
||||
); // WPCS: cache ok, DB call ok, , unprepared SQL ok.
|
||||
$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 / $intervals_query['per_page'] );
|
||||
$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( $intervals_query, $query_args, $db_interval_count, $expected_interval_count, $table_name );
|
||||
$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(
|
||||
"SELECT
|
||||
{$selections}
|
||||
FROM
|
||||
{$table_name}
|
||||
{$totals_query['from_clause']}
|
||||
WHERE
|
||||
1=1
|
||||
{$totals_query['where_time_clause']}
|
||||
{$totals_query['where_clause']}",
|
||||
$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 );
|
||||
|
||||
|
@ -181,27 +181,15 @@ class DataStore extends ProductsDataStore implements DataStoreInterface {
|
|||
return new \WP_Error( 'woocommerce_reports_products_stats_result_failed', __( 'Sorry, fetching revenue data failed.', 'woocommerce-admin' ) );
|
||||
}
|
||||
|
||||
$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 ) {
|
||||
$selections = ', ' . $selections;
|
||||
$this->interval_query->add_sql_clause( 'select', ', ' . $selections );
|
||||
}
|
||||
|
||||
$intervals = $wpdb->get_results(
|
||||
"SELECT
|
||||
MAX(${table_name}.date_created) AS datetime_anchor,
|
||||
{$intervals_query['select_clause']} AS time_interval
|
||||
{$selections}
|
||||
FROM
|
||||
{$table_name}
|
||||
{$intervals_query['from_clause']}
|
||||
WHERE
|
||||
1=1
|
||||
{$intervals_query['where_time_clause']}
|
||||
{$intervals_query['where_clause']}
|
||||
GROUP BY
|
||||
time_interval
|
||||
ORDER BY
|
||||
{$intervals_query['order_by_clause']}
|
||||
{$intervals_query['limit']}",
|
||||
$this->interval_query->get_query_statement(),
|
||||
ARRAY_A
|
||||
); // WPCS: cache ok, DB call ok, unprepared SQL ok.
|
||||
|
||||
|
@ -219,10 +207,10 @@ class DataStore extends ProductsDataStore implements DataStoreInterface {
|
|||
'page_no' => (int) $query_args['page'],
|
||||
);
|
||||
|
||||
if ( TimeInterval::intervals_missing( $expected_interval_count, $db_interval_count, $intervals_query['per_page'], $query_args['page'], $query_args['order'], $query_args['orderby'], count( $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'], $intervals_query['per_page'], $db_interval_count, $expected_interval_count, $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 );
|
||||
}
|
||||
|
@ -248,4 +236,18 @@ class DataStore extends ProductsDataStore implements DataStoreInterface {
|
|||
|
||||
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' );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -98,11 +98,15 @@ class Segmenter {
|
|||
$segment_dimension = substr( strstr( $segment_dimension, '.' ), 1 );
|
||||
}
|
||||
|
||||
$segment_labels = $this->get_segment_labels();
|
||||
foreach ( $segments_db_result as $segment_data ) {
|
||||
$segment_id = $segment_data[ $segment_dimension ];
|
||||
$segment_labels = $this->get_segment_labels();
|
||||
$segment_id = $segment_data[ $segment_dimension ];
|
||||
if ( ! isset( $segment_labels[ $segment_id ] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
unset( $segment_data[ $segment_dimension ] );
|
||||
$segment_datum = array(
|
||||
$segment_datum = array(
|
||||
'segment_id' => $segment_id,
|
||||
'segment_label' => $segment_labels[ $segment_id ],
|
||||
'subtotals' => $segment_data,
|
||||
|
@ -292,6 +296,11 @@ class Segmenter {
|
|||
$segment_labels = $this->get_segment_labels();
|
||||
|
||||
foreach ( $segments_db_result as $segment_data ) {
|
||||
$segment_id = $segment_data[ $segment_dimension ];
|
||||
if ( ! isset( $segment_labels[ $segment_id ] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$time_interval = $segment_data['time_interval'];
|
||||
if ( ! isset( $aggregated_segment_result[ $time_interval ] ) ) {
|
||||
$aggregated_segment_result[ $time_interval ] = array();
|
||||
|
@ -299,7 +308,6 @@ class Segmenter {
|
|||
}
|
||||
unset( $segment_data['time_interval'] );
|
||||
unset( $segment_data['datetime_anchor'] );
|
||||
$segment_id = $segment_data[ $segment_dimension ];
|
||||
unset( $segment_data[ $segment_dimension ] );
|
||||
$segment_datum = array(
|
||||
'segment_label' => $segment_labels[ $segment_id ],
|
||||
|
@ -407,7 +415,8 @@ class Segmenter {
|
|||
if ( isset( $this->query_args['coupons'] ) ) {
|
||||
$args['include'] = $this->query_args['coupons'];
|
||||
}
|
||||
$coupons = CouponsDataStore::get_coupons( $args );
|
||||
$coupons_store = new CouponsDataStore();
|
||||
$coupons = $coupons_store->get_coupons( $args );
|
||||
$segments = wp_list_pluck( $coupons, 'ID' );
|
||||
$segment_labels = wp_list_pluck( $coupons, 'post_title', 'ID' );
|
||||
$segment_labels = array_map( 'wc_format_coupon_code', $segment_labels );
|
||||
|
|
|
@ -0,0 +1,220 @@
|
|||
<?php
|
||||
/**
|
||||
* Admin\API\Reports\SqlQuery class file.
|
||||
*
|
||||
* @package WooCommerce Admin/Classes
|
||||
*/
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\API\Reports;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Admin\API\Reports\SqlQuery: Common parent for manipulating SQL query clauses.
|
||||
*/
|
||||
class SqlQuery {
|
||||
/**
|
||||
* List of SQL clauses.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $sql_clauses = array(
|
||||
'select' => array(),
|
||||
'from' => array(),
|
||||
'left_join' => array(),
|
||||
'join' => array(),
|
||||
'right_join' => array(),
|
||||
'where' => array(),
|
||||
'where_time' => array(),
|
||||
'group_by' => array(),
|
||||
'having' => array(),
|
||||
'limit' => array(),
|
||||
'order_by' => array(),
|
||||
);
|
||||
/**
|
||||
* SQL clause merge filters.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $sql_filters = array(
|
||||
'where' => array(
|
||||
'where',
|
||||
'where_time',
|
||||
),
|
||||
'join' => array(
|
||||
'right_join',
|
||||
'join',
|
||||
'left_join',
|
||||
),
|
||||
);
|
||||
/**
|
||||
* Data store context used to pass to filters.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $context;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param string $context Optional context passed to filters. Default empty string.
|
||||
*/
|
||||
public function __construct( $context = '' ) {
|
||||
$this->context = $context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a SQL clause to be included when get_data is called.
|
||||
*
|
||||
* @param string $type Clause type.
|
||||
* @param string $clause SQL clause.
|
||||
*/
|
||||
protected function add_sql_clause( $type, $clause ) {
|
||||
if ( isset( $this->sql_clauses[ $type ] ) && ! empty( $clause ) ) {
|
||||
$this->sql_clauses[ $type ][] = $clause;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get SQL clause by type.
|
||||
*
|
||||
* @param string $type Clause type.
|
||||
* @param string $handling Whether to filter the return value (filtered|unfiltered). Default unfiltered.
|
||||
*
|
||||
* @return string SQL clause.
|
||||
*/
|
||||
protected function get_sql_clause( $type, $handling = 'unfiltered' ) {
|
||||
if ( ! isset( $this->sql_clauses[ $type ] ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Default to bypassing filters for clause retrieval internal to data stores.
|
||||
* The filters are applied when the full SQL statement is retrieved.
|
||||
*/
|
||||
if ( 'unfiltered' === $handling ) {
|
||||
return implode( ' ', $this->sql_clauses[ $type ] );
|
||||
}
|
||||
|
||||
if ( isset( $this->sql_filters[ $type ] ) ) {
|
||||
$clauses = array();
|
||||
foreach( $this->sql_filters[ $type ] as $subset ) {
|
||||
$clauses = array_merge( $clauses, $this->sql_clauses[ $subset ] );
|
||||
}
|
||||
} else {
|
||||
$clauses = $this->sql_clauses[ $type ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter SQL clauses by type and context.
|
||||
*
|
||||
* @param array $clauses The original arguments for the request.
|
||||
* @param string $context The data store context.
|
||||
*/
|
||||
$clauses = apply_filters( "wc_admin_clauses_{$type}", $clauses, $this->context );
|
||||
/**
|
||||
* Filter SQL clauses by type and context.
|
||||
*
|
||||
* @param array $clauses The original arguments for the request.
|
||||
*/
|
||||
$clauses = apply_filters( "wc_admin_clauses_{$type}_{$this->context}", $clauses );
|
||||
return implode( ' ', $clauses );
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear SQL clauses by type.
|
||||
*
|
||||
* @param string|array $types Clause type.
|
||||
*/
|
||||
protected function clear_sql_clause( $types ) {
|
||||
foreach ( (array) $types as $type ) {
|
||||
if ( isset( $this->sql_clauses[ $type ] ) ) {
|
||||
$this->sql_clauses[ $type ] = array();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace strings within SQL clauses by type.
|
||||
*
|
||||
* @param string $type Clause type.
|
||||
* @param string $search String to search for.
|
||||
* @param string $replace Replacement string.
|
||||
*/
|
||||
protected function str_replace_clause( $type, $search, $replace ) {
|
||||
if ( isset( $this->sql_clauses[ $type ] ) ) {
|
||||
foreach ( $this->sql_clauses[ $type ] as $key => $sql ) {
|
||||
$this->sql_clauses[ $type ][ $key ] = str_replace( $search, $replace, $sql );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the full SQL statement.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_query_statement() {
|
||||
$join = $this->get_sql_clause( 'join', 'filtered' );
|
||||
$where = $this->get_sql_clause( 'where', 'filtered' );
|
||||
$group_by = $this->get_sql_clause( 'group_by', 'filtered' );
|
||||
$having = $this->get_sql_clause( 'having', 'filtered' );
|
||||
$order_by = $this->get_sql_clause( 'order_by', 'filtered' );
|
||||
|
||||
|
||||
$statement = "
|
||||
SELECT
|
||||
{$this->get_sql_clause( 'select', 'filtered' )}
|
||||
FROM
|
||||
{$this->get_sql_clause( 'from', 'filtered' )}
|
||||
{$join}
|
||||
WHERE
|
||||
1=1
|
||||
{$where}
|
||||
";
|
||||
|
||||
if ( ! empty( $group_by ) ) {
|
||||
$statement .= "
|
||||
GROUP BY
|
||||
{$group_by}
|
||||
";
|
||||
if ( ! empty( $having ) ) {
|
||||
$statement .= "
|
||||
HAVING
|
||||
1=1
|
||||
{$having}
|
||||
";
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! empty( $order_by ) ) {
|
||||
$statement .= "
|
||||
ORDER BY
|
||||
{$order_by}
|
||||
";
|
||||
}
|
||||
|
||||
return $statement . $this->get_sql_clause( 'limit', 'filtered' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Reinitialize the clause array.
|
||||
*/
|
||||
public function clear_all_clauses() {
|
||||
$this->sql_clauses = array(
|
||||
'select' => array(),
|
||||
'from' => array(),
|
||||
'left_join' => array(),
|
||||
'join' => array(),
|
||||
'right_join' => array(),
|
||||
'where' => array(),
|
||||
'where_time' => array(),
|
||||
'group_by' => array(),
|
||||
'having' => array(),
|
||||
'limit' => array(),
|
||||
'order_by' => array(),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -12,6 +12,7 @@ defined( 'ABSPATH' ) || exit;
|
|||
use \Automattic\WooCommerce\Admin\API\Reports\DataStore as ReportsDataStore;
|
||||
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\Cache as ReportsCache;
|
||||
|
||||
/**
|
||||
|
@ -24,7 +25,7 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
|||
*
|
||||
* @var string
|
||||
*/
|
||||
const TABLE_NAME = 'wc_order_tax_lookup';
|
||||
protected static $table_name = 'wc_order_tax_lookup';
|
||||
|
||||
/**
|
||||
* Cache identifier.
|
||||
|
@ -52,32 +53,29 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
|||
);
|
||||
|
||||
/**
|
||||
* SQL columns to select in the db query and their mapping to SQL code.
|
||||
* Data store context used to pass to filters.
|
||||
*
|
||||
* @var array
|
||||
* @var string
|
||||
*/
|
||||
protected $report_columns = array(
|
||||
'tax_rate_id' => 'tax_rate_id',
|
||||
'name' => 'tax_rate_name as name',
|
||||
'tax_rate' => 'tax_rate',
|
||||
'country' => 'tax_rate_country as country',
|
||||
'state' => 'tax_rate_state as state',
|
||||
'priority' => 'tax_rate_priority as priority',
|
||||
'total_tax' => 'SUM(total_tax) as total_tax',
|
||||
'order_tax' => 'SUM(order_tax) as order_tax',
|
||||
'shipping_tax' => 'SUM(shipping_tax) as shipping_tax',
|
||||
'orders_count' => 'COUNT( DISTINCT ( CASE WHEN total_tax >= 0 THEN order_id END ) ) as orders_count',
|
||||
);
|
||||
protected $context = 'taxes';
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* Assign report columns once full table name has been assigned.
|
||||
*/
|
||||
public function __construct() {
|
||||
global $wpdb;
|
||||
$table_name = $wpdb->prefix . self::TABLE_NAME;
|
||||
// Avoid ambigious columns in SQL query.
|
||||
$this->report_columns['tax_rate_id'] = $table_name . '.' . $this->report_columns['tax_rate_id'];
|
||||
$this->report_columns['orders_count'] = str_replace( 'order_id', $table_name . '.order_id', $this->report_columns['orders_count'] );
|
||||
protected function assign_report_columns() {
|
||||
$table_name = self::get_db_table_name();
|
||||
$this->report_columns = array(
|
||||
'tax_rate_id' => "{$table_name}.tax_rate_id",
|
||||
'name' => 'tax_rate_name as name',
|
||||
'tax_rate' => 'tax_rate',
|
||||
'country' => 'tax_rate_country as country',
|
||||
'state' => 'tax_rate_state as state',
|
||||
'priority' => 'tax_rate_priority as priority',
|
||||
'total_tax' => 'SUM(total_tax) as total_tax',
|
||||
'order_tax' => 'SUM(order_tax) as order_tax',
|
||||
'shipping_tax' => 'SUM(shipping_tax) as shipping_tax',
|
||||
'orders_count' => "COUNT( DISTINCT ( CASE WHEN total_tax >= 0 THEN {$table_name}.order_id END ) ) as orders_count",
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -92,76 +90,46 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
|||
*
|
||||
* @param array $query_args Query arguments supplied by the user.
|
||||
* @param string $order_status_filter Order status subquery.
|
||||
* @return array Array of parameters used for SQL query.
|
||||
*/
|
||||
protected function get_from_sql_params( $query_args, $order_status_filter ) {
|
||||
global $wpdb;
|
||||
$table_name = $wpdb->prefix . self::TABLE_NAME;
|
||||
|
||||
$sql_query['from_clause'] = '';
|
||||
$sql_query['outer_from_clause'] = '';
|
||||
$table_name = self::get_db_table_name();
|
||||
|
||||
if ( $order_status_filter ) {
|
||||
$sql_query['from_clause'] .= " JOIN {$wpdb->prefix}wc_order_stats ON {$table_name}.order_id = {$wpdb->prefix}wc_order_stats.order_id";
|
||||
$this->subquery->add_sql_clause( 'join', "JOIN {$wpdb->prefix}wc_order_stats ON {$table_name}.order_id = {$wpdb->prefix}wc_order_stats.order_id" );
|
||||
}
|
||||
|
||||
if ( isset( $query_args['taxes'] ) && ! empty( $query_args['taxes'] ) ) {
|
||||
$sql_query['outer_from_clause'] .= " JOIN {$wpdb->prefix}woocommerce_tax_rates ON default_results.tax_rate_id = {$wpdb->prefix}woocommerce_tax_rates.tax_rate_id";
|
||||
$this->add_sql_clause( 'join', "JOIN {$wpdb->prefix}woocommerce_tax_rates ON default_results.tax_rate_id = {$wpdb->prefix}woocommerce_tax_rates.tax_rate_id" );
|
||||
} else {
|
||||
$sql_query['from_clause'] .= " JOIN {$wpdb->prefix}woocommerce_tax_rates ON {$table_name}.tax_rate_id = {$wpdb->prefix}woocommerce_tax_rates.tax_rate_id";
|
||||
$this->subquery->add_sql_clause( 'join', "JOIN {$wpdb->prefix}woocommerce_tax_rates ON {$table_name}.tax_rate_id = {$wpdb->prefix}woocommerce_tax_rates.tax_rate_id" );
|
||||
}
|
||||
|
||||
return $sql_query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the database query with parameters used for Taxes report: categories and order status.
|
||||
*
|
||||
* @param array $query_args Query arguments supplied by the user.
|
||||
* @return array Array of parameters used for SQL query.
|
||||
*/
|
||||
protected function get_sql_query_params( $query_args ) {
|
||||
global $wpdb;
|
||||
|
||||
$order_tax_lookup_table = $wpdb->prefix . self::TABLE_NAME;
|
||||
$order_tax_lookup_table = self::get_db_table_name();
|
||||
|
||||
$sql_query_params = $this->get_time_period_sql_params( $query_args, $order_tax_lookup_table );
|
||||
$sql_query_params = array_merge( $sql_query_params, $this->get_limit_sql_params( $query_args ) );
|
||||
$sql_query_params = array_merge( $sql_query_params, $this->get_order_by_sql_params( $query_args ) );
|
||||
$this->get_time_period_sql_params( $query_args, $order_tax_lookup_table );
|
||||
$this->get_limit_sql_params( $query_args );
|
||||
$this->get_order_by_sql_params( $query_args );
|
||||
$order_status_filter = $this->get_status_subquery( $query_args );
|
||||
$sql_query_params = array_merge( $sql_query_params, $this->get_from_sql_params( $query_args, $order_status_filter ) );
|
||||
$this->get_from_sql_params( $query_args, $order_status_filter );
|
||||
|
||||
if ( isset( $query_args['taxes'] ) && ! empty( $query_args['taxes'] ) ) {
|
||||
$allowed_taxes = implode( ',', $query_args['taxes'] );
|
||||
$sql_query_params['where_clause'] .= " AND {$order_tax_lookup_table}.tax_rate_id IN ({$allowed_taxes})";
|
||||
$allowed_taxes = self::get_filtered_ids( $query_args, 'taxes' );
|
||||
$this->subquery->add_sql_clause( 'where', "AND {$order_tax_lookup_table}.tax_rate_id IN ({$allowed_taxes})" );
|
||||
}
|
||||
|
||||
if ( $order_status_filter ) {
|
||||
$sql_query_params['where_clause'] .= " AND ( {$order_status_filter} )";
|
||||
$this->subquery->add_sql_clause( 'where', "AND ( {$order_status_filter} )" );
|
||||
}
|
||||
|
||||
return $sql_query_params;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fills ORDER BY clause of SQL request based on user supplied parameters.
|
||||
*
|
||||
* @param array $query_args Parameters supplied by the user.
|
||||
* @return array
|
||||
*/
|
||||
protected function get_order_by_sql_params( $query_args ) {
|
||||
$sql_query['order_by_clause'] = '';
|
||||
if ( isset( $query_args['orderby'] ) ) {
|
||||
$sql_query['order_by_clause'] = $this->normalize_order_by( $query_args['orderby'] );
|
||||
}
|
||||
|
||||
if ( isset( $query_args['order'] ) ) {
|
||||
$sql_query['order_by_clause'] .= ' ' . $query_args['order'];
|
||||
} else {
|
||||
$sql_query['order_by_clause'] .= ' DESC';
|
||||
}
|
||||
|
||||
return $sql_query;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -173,7 +141,7 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
|||
public function get_data( $query_args ) {
|
||||
global $wpdb;
|
||||
|
||||
$table_name = $wpdb->prefix . self::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.
|
||||
$defaults = array(
|
||||
|
@ -197,6 +165,8 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
|||
$data = $this->get_cached_data( $cache_key );
|
||||
|
||||
if ( false === $data ) {
|
||||
$this->initialize_queries();
|
||||
|
||||
$data = (object) array(
|
||||
'data' => array(),
|
||||
'total' => 0,
|
||||
|
@ -204,11 +174,12 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
|||
'page_no' => 0,
|
||||
);
|
||||
|
||||
$sql_query_params = $this->get_sql_query_params( $query_args );
|
||||
$this->get_sql_query_params( $query_args );
|
||||
$params = $this->get_limit_params( $query_args );
|
||||
|
||||
if ( isset( $query_args['taxes'] ) && ! empty( $query_args['taxes'] ) ) {
|
||||
$total_results = count( $query_args['taxes'] );
|
||||
$total_pages = (int) ceil( $total_results / $sql_query_params['per_page'] );
|
||||
$total_pages = (int) ceil( $total_results / $params['per_page'] );
|
||||
|
||||
$inner_selections = array( 'tax_rate_id', 'total_tax', 'order_tax', 'shipping_tax', 'orders_count' );
|
||||
$outer_selections = array( 'name', 'tax_rate', 'country', 'state', 'priority' );
|
||||
|
@ -217,61 +188,42 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
|||
$fields = $this->get_fields( $query_args );
|
||||
$join_selections = $this->format_join_selections( $fields, array( 'tax_rate_id' ), $outer_selections );
|
||||
$ids_table = $this->get_ids_table( $query_args['taxes'], 'tax_rate_id' );
|
||||
$prefix = "SELECT {$join_selections} FROM (";
|
||||
$suffix = ") AS {$table_name}";
|
||||
$right_join = "RIGHT JOIN ( {$ids_table} ) AS default_results
|
||||
ON default_results.tax_rate_id = {$table_name}.tax_rate_id";
|
||||
|
||||
$this->subquery->clear_sql_clause( 'select' );
|
||||
$this->subquery->add_sql_clause( 'select', $this->selected_columns( array( 'fields' => $inner_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.tax_rate_id = {$table_name}.tax_rate_id"
|
||||
);
|
||||
|
||||
$taxes_query = $this->get_query_statement();
|
||||
} else {
|
||||
$db_records_count = (int) $wpdb->get_var(
|
||||
"SELECT COUNT(*) FROM (
|
||||
SELECT
|
||||
{$table_name}.tax_rate_id
|
||||
FROM
|
||||
{$table_name}
|
||||
{$sql_query_params['from_clause']}
|
||||
WHERE
|
||||
1=1
|
||||
{$sql_query_params['where_time_clause']}
|
||||
{$sql_query_params['where_clause']}
|
||||
GROUP BY
|
||||
{$table_name}.tax_rate_id
|
||||
) AS tt"
|
||||
{$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 / $sql_query_params['per_page'] );
|
||||
$total_pages = (int) ceil( $db_records_count / $params['per_page'] );
|
||||
|
||||
if ( $query_args['page'] < 1 || $query_args['page'] > $total_pages ) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
$selections = $this->selected_columns( $query_args );
|
||||
|
||||
$prefix = '';
|
||||
$suffix = '';
|
||||
$right_join = '';
|
||||
$this->subquery->clear_sql_clause( 'select' );
|
||||
$this->subquery->add_sql_clause( 'select', $this->selected_columns( $query_args ) );
|
||||
$this->subquery->add_sql_clause( 'order_by', $this->get_sql_clause( 'order_by' ) );
|
||||
$taxes_query = $this->subquery->get_query_statement();
|
||||
}
|
||||
|
||||
$tax_data = $wpdb->get_results(
|
||||
"{$prefix}
|
||||
SELECT
|
||||
{$selections}
|
||||
FROM
|
||||
{$table_name}
|
||||
{$sql_query_params['from_clause']}
|
||||
WHERE
|
||||
1=1
|
||||
{$sql_query_params['where_time_clause']}
|
||||
{$sql_query_params['where_clause']}
|
||||
GROUP BY
|
||||
{$table_name}.tax_rate_id
|
||||
{$suffix}
|
||||
{$right_join}
|
||||
{$sql_query_params['outer_from_clause']}
|
||||
ORDER BY
|
||||
{$sql_query_params['order_by_clause']}
|
||||
{$sql_query_params['limit']}
|
||||
",
|
||||
$taxes_query,
|
||||
ARRAY_A
|
||||
); // WPCS: cache ok, DB call ok, unprepared SQL ok.
|
||||
|
||||
|
@ -330,7 +282,7 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
|||
|
||||
foreach ( $tax_items as $tax_item ) {
|
||||
$result = $wpdb->replace(
|
||||
$wpdb->prefix . self::TABLE_NAME,
|
||||
self::get_db_table_name(),
|
||||
array(
|
||||
'order_id' => $order->get_id(),
|
||||
'date_created' => $order->get_date_created( 'edit' )->date( TimeInterval::$sql_datetime_format ),
|
||||
|
@ -372,14 +324,7 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
|||
public static function sync_on_order_delete( $order_id ) {
|
||||
global $wpdb;
|
||||
|
||||
$table_name = $wpdb->prefix . self::TABLE_NAME;
|
||||
|
||||
$wpdb->query(
|
||||
$wpdb->prepare(
|
||||
"DELETE FROM ${table_name} WHERE order_id = %d",
|
||||
$order_id
|
||||
)
|
||||
);
|
||||
$wpdb->delete( self::get_db_table_name(), array( 'order_id' => $order_id ) );
|
||||
|
||||
/**
|
||||
* Fires when tax's reports are removed from database.
|
||||
|
@ -391,4 +336,15 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
|||
|
||||
ReportsCache::invalidate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize query objects.
|
||||
*/
|
||||
protected function initialize_queries() {
|
||||
$this->clear_all_clauses();
|
||||
$this->subquery = new SqlQuery( $this->context . '_subquery' );
|
||||
$this->subquery->add_sql_clause( 'select', self::get_db_table_name() . '.tax_rate_id' );
|
||||
$this->subquery->add_sql_clause( 'from', self::get_db_table_name() );
|
||||
$this->subquery->add_sql_clause( 'group_by', self::get_db_table_name() . '.tax_rate_id' );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ defined( 'ABSPATH' ) || exit;
|
|||
use \Automattic\WooCommerce\Admin\API\Reports\DataStore as ReportsDataStore;
|
||||
use \Automattic\WooCommerce\Admin\API\Reports\DataStoreInterface;
|
||||
use \Automattic\WooCommerce\Admin\API\Reports\TimeInterval;
|
||||
use \Automattic\WooCommerce\Admin\API\Reports\SqlQuery;
|
||||
|
||||
/**
|
||||
* API\Reports\Taxes\Stats\DataStore.
|
||||
|
@ -23,7 +24,7 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
|||
*
|
||||
* @var string
|
||||
*/
|
||||
const TABLE_NAME = 'wc_order_tax_lookup';
|
||||
protected static $table_name = 'wc_order_tax_lookup';
|
||||
|
||||
/**
|
||||
* Cache identifier.
|
||||
|
@ -46,81 +47,72 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
|||
);
|
||||
|
||||
/**
|
||||
* SQL columns to select in the db query.
|
||||
* Data store context used to pass to filters.
|
||||
*
|
||||
* @var array
|
||||
* @var string
|
||||
*/
|
||||
protected $report_columns = array(
|
||||
'tax_codes' => 'COUNT(DISTINCT tax_rate_id) as tax_codes',
|
||||
'total_tax' => 'SUM(total_tax) AS total_tax',
|
||||
'order_tax' => 'SUM(order_tax) as order_tax',
|
||||
'shipping_tax' => 'SUM(shipping_tax) as shipping_tax',
|
||||
'orders_count' => 'COUNT( DISTINCT ( CASE WHEN parent_id = 0 THEN order_id END ) ) as orders_count',
|
||||
);
|
||||
protected $context = 'tax_stats';
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* Assign report columns once full table name has been assigned.
|
||||
*/
|
||||
public function __construct() {
|
||||
global $wpdb;
|
||||
$table_name = $wpdb->prefix . self::TABLE_NAME;
|
||||
// Avoid ambigious column order_id in SQL query.
|
||||
$this->report_columns['orders_count'] = str_replace( 'order_id', $table_name . '.order_id', $this->report_columns['orders_count'] );
|
||||
protected function assign_report_columns() {
|
||||
$table_name = self::get_db_table_name();
|
||||
$this->report_columns = array(
|
||||
'tax_codes' => 'COUNT(DISTINCT tax_rate_id) as tax_codes',
|
||||
'total_tax' => 'SUM(total_tax) AS total_tax',
|
||||
'order_tax' => 'SUM(order_tax) as order_tax',
|
||||
'shipping_tax' => 'SUM(shipping_tax) as shipping_tax',
|
||||
'orders_count' => "COUNT( DISTINCT ( CASE WHEN parent_id = 0 THEN {$table_name}.order_id END ) ) as orders_count",
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the database query with parameters used for Taxes report: categories and order status.
|
||||
*
|
||||
* @param array $query_args Query arguments supplied by the user.
|
||||
* @return array Array of parameters used for SQL query.
|
||||
*/
|
||||
protected function get_sql_query_params( $query_args ) {
|
||||
global $wpdb;
|
||||
|
||||
$order_tax_lookup_table = $wpdb->prefix . self::TABLE_NAME;
|
||||
$order_tax_lookup_table = self::get_db_table_name();
|
||||
|
||||
$sql_query_params = $this->get_time_period_sql_params( $query_args, $order_tax_lookup_table );
|
||||
$sql_query_params = array_merge( $sql_query_params, $this->get_limit_sql_params( $query_args ) );
|
||||
$sql_query_params = array_merge( $sql_query_params, $this->get_order_by_sql_params( $query_args ) );
|
||||
$this->get_time_period_sql_params( $query_args, $order_tax_lookup_table );
|
||||
$this->get_limit_sql_params( $query_args );
|
||||
$this->get_order_by_sql_params( $query_args );
|
||||
|
||||
if ( isset( $query_args['taxes'] ) && ! empty( $query_args['taxes'] ) ) {
|
||||
$allowed_taxes = implode( ',', $query_args['taxes'] );
|
||||
$sql_query_params['where_clause'] .= " AND {$order_tax_lookup_table}.tax_rate_id IN ({$allowed_taxes})";
|
||||
$allowed_taxes = self::get_filtered_ids( $query_args, 'taxes' );
|
||||
$this->interval_query->add_sql_clause( 'where', "AND {$order_tax_lookup_table}.tax_rate_id IN ({$allowed_taxes})" );
|
||||
}
|
||||
|
||||
$order_status_filter = $this->get_status_subquery( $query_args );
|
||||
if ( $order_status_filter ) {
|
||||
$sql_query_params['where_clause'] .= " AND ( {$order_status_filter} )";
|
||||
$this->interval_query->add_sql_clause( 'where', "AND ( {$order_status_filter} )" );
|
||||
}
|
||||
|
||||
return $sql_query_params;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the database query with parameters used for Taxes Stats report
|
||||
*
|
||||
* @param array $query_args Query arguments supplied by the user.
|
||||
* @param array $totals_params SQL parameters for the totals query.
|
||||
* @param array $intervals_params SQL parameters for the intervals query.
|
||||
*/
|
||||
protected function update_sql_query_params( $query_args, &$totals_params, &$intervals_params ) {
|
||||
global $wpdb;
|
||||
|
||||
$taxes_where_clause = '';
|
||||
$taxes_from_clause = '';
|
||||
|
||||
$order_tax_lookup_table = $wpdb->prefix . self::TABLE_NAME;
|
||||
protected function update_sql_query_params( $query_args ) {
|
||||
$taxes_where_clause = '';
|
||||
$order_tax_lookup_table = self::get_db_table_name();
|
||||
|
||||
if ( isset( $query_args['taxes'] ) && ! empty( $query_args['taxes'] ) ) {
|
||||
$allowed_taxes = implode( ',', $query_args['taxes'] );
|
||||
$taxes_where_clause .= " AND {$order_tax_lookup_table}.tax_rate_id IN ({$allowed_taxes})";
|
||||
}
|
||||
|
||||
$totals_params = array_merge( $totals_params, $this->get_time_period_sql_params( $query_args, $order_tax_lookup_table ) );
|
||||
$totals_params['where_clause'] .= $taxes_where_clause;
|
||||
$this->get_time_period_sql_params( $query_args, $order_tax_lookup_table );
|
||||
$this->total_query->add_sql_clause( 'where', $taxes_where_clause );
|
||||
|
||||
$intervals_params = array_merge( $intervals_params, $this->get_intervals_sql_params( $query_args, $order_tax_lookup_table ) );
|
||||
$intervals_params['where_clause'] .= $taxes_where_clause;
|
||||
$this->get_intervals_sql_params( $query_args, $order_tax_lookup_table );
|
||||
$this->interval_query->add_sql_clause( 'where', $taxes_where_clause );
|
||||
$this->interval_query->add_sql_clause( 'select', $this->get_sql_clause( 'select' ) . ' AS time_interval' );
|
||||
$this->interval_query->add_sql_clause( 'where_time', $this->get_sql_clause( 'where_time' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -156,7 +148,7 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
|||
public function get_data( $query_args ) {
|
||||
global $wpdb;
|
||||
|
||||
$table_name = $wpdb->prefix . self::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.
|
||||
$defaults = array(
|
||||
|
@ -180,6 +172,8 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
|||
$data = $this->get_cached_data( $cache_key );
|
||||
|
||||
if ( false === $data ) {
|
||||
$this->initialize_queries();
|
||||
|
||||
$data = (object) array(
|
||||
'totals' => (object) array(),
|
||||
'intervals' => (object) array(),
|
||||
|
@ -188,81 +182,62 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
|||
'page_no' => 0,
|
||||
);
|
||||
|
||||
$selections = $this->selected_columns( $query_args );
|
||||
$totals_query = array();
|
||||
$intervals_query = array();
|
||||
$this->update_sql_query_params( $query_args, $totals_query, $intervals_query );
|
||||
$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(
|
||||
"SELECT
|
||||
{$intervals_query['select_clause']} AS time_interval
|
||||
FROM
|
||||
{$table_name}
|
||||
{$intervals_query['from_clause']}
|
||||
JOIN
|
||||
{$wpdb->prefix}wc_order_stats ON {$table_name}.order_id = {$wpdb->prefix}wc_order_stats.order_id
|
||||
WHERE
|
||||
1=1
|
||||
{$intervals_query['where_time_clause']}
|
||||
{$intervals_query['where_clause']}
|
||||
GROUP BY
|
||||
time_interval"
|
||||
$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 / $intervals_query['per_page'] );
|
||||
$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(
|
||||
"SELECT
|
||||
{$selections}
|
||||
FROM
|
||||
{$table_name}
|
||||
{$totals_query['from_clause']}
|
||||
JOIN
|
||||
{$wpdb->prefix}wc_order_stats ON {$table_name}.order_id = {$wpdb->prefix}wc_order_stats.order_id
|
||||
WHERE
|
||||
1=1
|
||||
{$totals_query['where_time_clause']}
|
||||
{$totals_query['where_clause']}",
|
||||
$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_reports_taxes_stats_result_failed', __( 'Sorry, fetching revenue data failed.', 'woocommerce-admin' ) );
|
||||
}
|
||||
|
||||
// @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( $intervals_query, $query_args, $db_interval_count, $expected_interval_count, $table_name );
|
||||
$this->update_intervals_sql_params( $query_args, $db_interval_count, $expected_interval_count, $table_name );
|
||||
|
||||
if ( '' !== $selections ) {
|
||||
$selections = ', ' . $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(
|
||||
"SELECT
|
||||
MAX({$table_name}.date_created) AS datetime_anchor,
|
||||
{$intervals_query['select_clause']} AS time_interval
|
||||
{$selections}
|
||||
FROM
|
||||
{$table_name}
|
||||
{$intervals_query['from_clause']}
|
||||
JOIN
|
||||
{$wpdb->prefix}wc_order_stats ON {$table_name}.order_id = {$wpdb->prefix}wc_order_stats.order_id
|
||||
WHERE
|
||||
1=1
|
||||
{$intervals_query['where_time_clause']}
|
||||
{$intervals_query['where_clause']}
|
||||
GROUP BY
|
||||
time_interval
|
||||
ORDER BY
|
||||
{$intervals_query['order_by_clause']}
|
||||
{$intervals_query['limit']}",
|
||||
$this->interval_query->get_query_statement(),
|
||||
ARRAY_A
|
||||
); // WPCS: cache ok, DB call ok, unprepared SQL ok.
|
||||
|
||||
|
@ -280,19 +255,31 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
|||
'page_no' => (int) $query_args['page'],
|
||||
);
|
||||
|
||||
if ( TimeInterval::intervals_missing( $expected_interval_count, $db_interval_count, $intervals_query['per_page'], $query_args['page'], $query_args['order'], $query_args['orderby'], count( $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'], $intervals_query['per_page'], $db_interval_count, $expected_interval_count, $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 );
|
||||
}
|
||||
|
||||
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' );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ defined( 'ABSPATH' ) || exit;
|
|||
use \Automattic\WooCommerce\Admin\API\Reports\DataStore as ReportsDataStore;
|
||||
use \Automattic\WooCommerce\Admin\API\Reports\DataStoreInterface;
|
||||
use \Automattic\WooCommerce\Admin\API\Reports\TimeInterval;
|
||||
use \Automattic\WooCommerce\Admin\API\Reports\SqlQuery;
|
||||
|
||||
/**
|
||||
* API\Reports\Variations\DataStore.
|
||||
|
@ -23,7 +24,7 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
|||
*
|
||||
* @var string
|
||||
*/
|
||||
const TABLE_NAME = 'wc_order_product_lookup';
|
||||
protected static $table_name = 'wc_order_product_lookup';
|
||||
|
||||
/**
|
||||
* Cache identifier.
|
||||
|
@ -52,19 +53,6 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
|||
'sku' => 'strval',
|
||||
);
|
||||
|
||||
/**
|
||||
* SQL columns to select in the db query and their mapping to SQL code.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $report_columns = array(
|
||||
'product_id' => 'product_id',
|
||||
'variation_id' => 'variation_id',
|
||||
'items_sold' => 'SUM(product_qty) as items_sold',
|
||||
'net_revenue' => 'SUM(product_net_revenue) AS net_revenue',
|
||||
'orders_count' => 'COUNT(DISTINCT order_id) as orders_count',
|
||||
);
|
||||
|
||||
/**
|
||||
* Extended product attributes to include in the data.
|
||||
*
|
||||
|
@ -82,72 +70,83 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
|||
);
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* Data store context used to pass to filters.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public function __construct() {
|
||||
global $wpdb;
|
||||
$table_name = $wpdb->prefix . self::TABLE_NAME;
|
||||
// Avoid ambigious column order_id in SQL query.
|
||||
$this->report_columns['orders_count'] = str_replace( 'order_id', $table_name . '.order_id', $this->report_columns['orders_count'] );
|
||||
protected $context = 'variations';
|
||||
|
||||
/**
|
||||
* Assign report columns once full table name has been assigned.
|
||||
*/
|
||||
protected function assign_report_columns() {
|
||||
$table_name = self::get_db_table_name();
|
||||
$this->report_columns = array(
|
||||
'product_id' => 'product_id',
|
||||
'variation_id' => 'variation_id',
|
||||
'items_sold' => 'SUM(product_qty) as items_sold',
|
||||
'net_revenue' => 'SUM(product_net_revenue) AS net_revenue',
|
||||
'orders_count' => "COUNT(DISTINCT {$table_name}.order_id) as orders_count",
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fills FROM clause of SQL request based on user supplied parameters.
|
||||
*
|
||||
* @param array $query_args Parameters supplied by the user.
|
||||
* @param string $arg_name Name of the FROM sql param.
|
||||
* @return array
|
||||
* @param string $arg_name Target of the JOIN sql param.
|
||||
*/
|
||||
protected function get_from_sql_params( $query_args, $arg_name ) {
|
||||
global $wpdb;
|
||||
$order_product_lookup_table = $wpdb->prefix . self::TABLE_NAME;
|
||||
|
||||
$sql_query['from_clause'] = '';
|
||||
$sql_query['outer_from_clause'] = '';
|
||||
if ( 'sku' === $query_args['orderby'] ) {
|
||||
$sql_query[ $arg_name ] .= " JOIN {$wpdb->prefix}postmeta AS postmeta ON {$order_product_lookup_table}.variation_id = postmeta.post_id AND postmeta.meta_key = '_sku'";
|
||||
if ( 'sku' !== $query_args['orderby'] ) {
|
||||
return;
|
||||
}
|
||||
|
||||
return $sql_query;
|
||||
$table_name = self::get_db_table_name();
|
||||
$join = "JOIN {$wpdb->postmeta} AS postmeta ON {$table_name}.variation_id = postmeta.post_id AND postmeta.meta_key = '_sku'";
|
||||
|
||||
if ( 'inner' === $arg_name ) {
|
||||
$this->subquery->add_sql_clause( 'join', $join );
|
||||
} else {
|
||||
$this->add_sql_clause( 'join', $join );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the database query with parameters used for Products report: categories and order status.
|
||||
*
|
||||
* @param array $query_args Query arguments supplied by the user.
|
||||
* @return array Array of parameters used for SQL query.
|
||||
*/
|
||||
protected function get_sql_query_params( $query_args ) {
|
||||
global $wpdb;
|
||||
$order_product_lookup_table = $wpdb->prefix . self::TABLE_NAME;
|
||||
$order_product_lookup_table = self::get_db_table_name();
|
||||
|
||||
$sql_query_params = $this->get_time_period_sql_params( $query_args, $order_product_lookup_table );
|
||||
$sql_query_params = array_merge( $sql_query_params, $this->get_limit_sql_params( $query_args ) );
|
||||
$sql_query_params = array_merge( $sql_query_params, $this->get_order_by_sql_params( $query_args ) );
|
||||
$this->get_time_period_sql_params( $query_args, $order_product_lookup_table );
|
||||
$this->get_limit_sql_params( $query_args );
|
||||
$this->get_order_by_sql_params( $query_args );
|
||||
|
||||
if ( count( $query_args['variations'] ) > 0 ) {
|
||||
$sql_query_params = array_merge( $sql_query_params, $this->get_from_sql_params( $query_args, 'outer_from_clause' ) );
|
||||
$this->get_from_sql_params( $query_args, 'outer' );
|
||||
} else {
|
||||
$sql_query_params = array_merge( $sql_query_params, $this->get_from_sql_params( $query_args, 'from_clause' ) );
|
||||
$this->get_from_sql_params( $query_args, 'inner' );
|
||||
}
|
||||
|
||||
$included_products = $this->get_included_products( $query_args );
|
||||
if ( $included_products ) {
|
||||
$sql_query_params['where_clause'] .= " AND {$order_product_lookup_table}.product_id IN ({$included_products})";
|
||||
$this->subquery->add_sql_clause( 'where', "AND {$order_product_lookup_table}.product_id IN ({$included_products})" );
|
||||
}
|
||||
|
||||
if ( count( $query_args['variations'] ) > 0 ) {
|
||||
$allowed_variations_str = implode( ',', $query_args['variations'] );
|
||||
$sql_query_params['where_clause'] .= " AND {$order_product_lookup_table}.variation_id IN ({$allowed_variations_str})";
|
||||
$allowed_variations_str = self::get_filtered_ids( $query_args, 'variations' );
|
||||
$this->subquery->add_sql_clause( 'where', "AND {$order_product_lookup_table}.variation_id IN ({$allowed_variations_str})" );
|
||||
}
|
||||
|
||||
$order_status_filter = $this->get_status_subquery( $query_args );
|
||||
if ( $order_status_filter ) {
|
||||
$sql_query_params['from_clause'] .= " JOIN {$wpdb->prefix}wc_order_stats ON {$order_product_lookup_table}.order_id = {$wpdb->prefix}wc_order_stats.order_id";
|
||||
$sql_query_params['where_clause'] .= " AND ( {$order_status_filter} )";
|
||||
$this->subquery->add_sql_clause( 'join', "JOIN {$wpdb->prefix}wc_order_stats ON {$order_product_lookup_table}.order_id = {$wpdb->prefix}wc_order_stats.order_id" );
|
||||
$this->subquery->add_sql_clause( 'where', "AND ( {$order_status_filter} )" );
|
||||
}
|
||||
|
||||
return $sql_query_params;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -158,11 +157,8 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
|||
* @return string
|
||||
*/
|
||||
protected function normalize_order_by( $order_by ) {
|
||||
global $wpdb;
|
||||
$table_name = $wpdb->prefix . self::TABLE_NAME;
|
||||
|
||||
if ( 'date' === $order_by ) {
|
||||
return $table_name . '.date_created';
|
||||
return self::get_db_table_name() . '.date_created';
|
||||
}
|
||||
if ( 'sku' === $order_by ) {
|
||||
return 'meta_value';
|
||||
|
@ -237,7 +233,7 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
|||
public function get_data( $query_args ) {
|
||||
global $wpdb;
|
||||
|
||||
$table_name = $wpdb->prefix . self::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.
|
||||
$defaults = array(
|
||||
|
@ -263,6 +259,8 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
|||
$data = $this->get_cached_data( $cache_key );
|
||||
|
||||
if ( false === $data ) {
|
||||
$this->initialize_queries();
|
||||
|
||||
$data = (object) array(
|
||||
'data' => array(),
|
||||
'total' => 0,
|
||||
|
@ -270,78 +268,56 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
|||
'page_no' => 0,
|
||||
);
|
||||
|
||||
$selections = $this->selected_columns( $query_args );
|
||||
$included_products = $this->get_included_products_array( $query_args );
|
||||
|
||||
$this->get_sql_query_params( $query_args );
|
||||
$params = $this->get_limit_params( $query_args );
|
||||
if ( count( $included_products ) > 0 && count( $query_args['variations'] ) > 0 ) {
|
||||
$sql_query_params = $this->get_sql_query_params( $query_args );
|
||||
|
||||
$this->subquery->add_sql_clause( 'select', $this->selected_columns( $query_args ) );
|
||||
if ( 'date' === $query_args['orderby'] ) {
|
||||
$selections .= ", {$table_name}.date_created";
|
||||
$this->subquery->add_sql_clause( 'select', ", {$table_name}.date_created" );
|
||||
}
|
||||
|
||||
$total_results = count( $query_args['variations'] );
|
||||
$total_pages = (int) ceil( $total_results / $sql_query_params['per_page'] );
|
||||
$total_pages = (int) ceil( $total_results / $params['per_page'] );
|
||||
|
||||
$fields = $this->get_fields( $query_args );
|
||||
$join_selections = $this->format_join_selections( $fields, array( 'product_id', 'variation_id' ) );
|
||||
$ids_table = $this->get_ids_table( $query_args['variations'], 'variation_id', array( 'product_id' => $included_products[0] ) );
|
||||
|
||||
$prefix = "SELECT {$join_selections} FROM (";
|
||||
$suffix = ") AS {$table_name}";
|
||||
$right_join = "RIGHT JOIN ( {$ids_table} ) AS default_results
|
||||
ON default_results.variation_id = {$table_name}.variation_id";
|
||||
} else {
|
||||
$sql_query_params = $this->get_sql_query_params( $query_args );
|
||||
$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 {
|
||||
$db_records_count = (int) $wpdb->get_var(
|
||||
"SELECT COUNT(*) FROM (
|
||||
SELECT
|
||||
product_id
|
||||
FROM
|
||||
{$table_name}
|
||||
{$sql_query_params['from_clause']}
|
||||
WHERE
|
||||
1=1
|
||||
{$sql_query_params['where_time_clause']}
|
||||
{$sql_query_params['where_clause']}
|
||||
GROUP BY
|
||||
product_id, variation_id
|
||||
) AS tt"
|
||||
{$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 / $sql_query_params['per_page'] );
|
||||
$total_pages = (int) ceil( $db_records_count / $params['per_page'] );
|
||||
|
||||
if ( $query_args['page'] < 1 || $query_args['page'] > $total_pages ) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
$prefix = '';
|
||||
$suffix = '';
|
||||
$right_join = '';
|
||||
$this->subquery->clear_sql_clause( 'select' );
|
||||
$this->subquery->add_sql_clause( 'select', $this->selected_columns( $query_args ) );
|
||||
$this->subquery->add_sql_clause( 'order_by', $this->get_sql_clause( 'order_by' ) );
|
||||
$variations_query = $this->subquery->get_query_statement();
|
||||
}
|
||||
|
||||
$product_data = $wpdb->get_results(
|
||||
"{$prefix}
|
||||
SELECT
|
||||
{$selections}
|
||||
FROM
|
||||
{$table_name}
|
||||
{$sql_query_params['from_clause']}
|
||||
WHERE
|
||||
1=1
|
||||
{$sql_query_params['where_time_clause']}
|
||||
{$sql_query_params['where_clause']}
|
||||
GROUP BY
|
||||
product_id, variation_id
|
||||
{$suffix}
|
||||
{$right_join}
|
||||
{$sql_query_params['outer_from_clause']}
|
||||
ORDER BY
|
||||
{$sql_query_params['order_by_clause']}
|
||||
{$sql_query_params['limit']}
|
||||
",
|
||||
$variations_query,
|
||||
ARRAY_A
|
||||
); // WPCS: cache ok, DB call ok, unprepared SQL ok.
|
||||
|
||||
|
@ -364,4 +340,15 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
|||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize query objects.
|
||||
*/
|
||||
protected function initialize_queries() {
|
||||
$this->clear_all_clauses();
|
||||
$this->subquery = new SqlQuery( $this->context . '_subquery' );
|
||||
$this->subquery->add_sql_clause( 'select', 'product_id' );
|
||||
$this->subquery->add_sql_clause( 'from', self::get_db_table_name() );
|
||||
$this->subquery->add_sql_clause( 'group_by', 'product_id, variation_id' );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -137,7 +137,7 @@ class ReportCSVEmail extends \WC_Email {
|
|||
$this->download_url = $download_url;
|
||||
|
||||
if ( isset( $this->report_labels[ $report_type ] ) ) {
|
||||
$this->report_type = $this->report_labels[ $report_type ];
|
||||
$this->report_type = $this->report_labels[ $report_type ];
|
||||
$this->placeholders['{report_name}'] = $this->report_type;
|
||||
}
|
||||
|
||||
|
|
|
@ -42,7 +42,7 @@ class WC_Tests_API_Init extends WC_REST_Unit_Test_Case {
|
|||
|
||||
if (
|
||||
0 === strpos( $query, 'REPLACE INTO' ) &&
|
||||
false !== strpos( $query, OrdersStatsDataStore::TABLE_NAME )
|
||||
false !== strpos( $query, OrdersStatsDataStore::get_db_table_name() )
|
||||
) {
|
||||
remove_filter( 'query', array( $this, 'filter_order_query' ) );
|
||||
return "DESCRIBE $wpdb->posts"; // Execute any random query.
|
||||
|
|
|
@ -17,10 +17,10 @@ class WC_Helper_Reports {
|
|||
*/
|
||||
public static function reset_stats_dbs() {
|
||||
global $wpdb;
|
||||
$wpdb->query( "DELETE FROM $wpdb->prefix" . \Automattic\WooCommerce\Admin\API\Reports\Orders\Stats\DataStore::TABLE_NAME ); // @codingStandardsIgnoreLine.
|
||||
$wpdb->query( "DELETE FROM $wpdb->prefix" . \Automattic\WooCommerce\Admin\API\Reports\Products\DataStore::TABLE_NAME ); // @codingStandardsIgnoreLine.
|
||||
$wpdb->query( "DELETE FROM $wpdb->prefix" . \Automattic\WooCommerce\Admin\API\Reports\Coupons\DataStore::TABLE_NAME ); // @codingStandardsIgnoreLine.
|
||||
$wpdb->query( "DELETE FROM $wpdb->prefix" . \Automattic\WooCommerce\Admin\API\Reports\Customers\DataStore::TABLE_NAME ); // @codingStandardsIgnoreLine.
|
||||
$wpdb->query( 'DELETE FROM ' . \Automattic\WooCommerce\Admin\API\Reports\Orders\Stats\DataStore::get_db_table_name() ); // @codingStandardsIgnoreLine.
|
||||
$wpdb->query( 'DELETE FROM ' . \Automattic\WooCommerce\Admin\API\Reports\Products\DataStore::get_db_table_name() ); // @codingStandardsIgnoreLine.
|
||||
$wpdb->query( 'DELETE FROM ' . \Automattic\WooCommerce\Admin\API\Reports\Coupons\DataStore::get_db_table_name() ); // @codingStandardsIgnoreLine.
|
||||
$wpdb->query( 'DELETE FROM ' . \Automattic\WooCommerce\Admin\API\Reports\Customers\DataStore::get_db_table_name() ); // @codingStandardsIgnoreLine.
|
||||
\Automattic\WooCommerce\Admin\CategoryLookup::instance()->regenerate();
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue