initial commit
This commit is contained in:
@ -0,0 +1,400 @@
|
||||
<?php
|
||||
/**
|
||||
* REST API Reports taxes stats controller
|
||||
*
|
||||
* Handles requests to the /reports/taxes/stats endpoint.
|
||||
*/
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\API\Reports\Taxes\Stats;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* REST API Reports taxes stats controller class.
|
||||
*
|
||||
* @extends WC_REST_Reports_Controller
|
||||
*/
|
||||
class Controller extends \WC_REST_Reports_Controller {
|
||||
|
||||
/**
|
||||
* Endpoint namespace.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $namespace = 'wc-analytics';
|
||||
|
||||
/**
|
||||
* Route base.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $rest_base = 'reports/taxes/stats';
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
add_filter( 'woocommerce_analytics_taxes_stats_select_query', array( $this, 'set_default_report_data' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the default results to 0 if API returns an empty array
|
||||
*
|
||||
* @param Mixed $results Report data.
|
||||
* @return object
|
||||
*/
|
||||
public function set_default_report_data( $results ) {
|
||||
if ( empty( $results ) ) {
|
||||
$results = new \stdClass();
|
||||
$results->total = 0;
|
||||
$results->totals = new \stdClass();
|
||||
$results->totals->tax_codes = 0;
|
||||
$results->totals->total_tax = 0;
|
||||
$results->totals->order_tax = 0;
|
||||
$results->totals->shipping_tax = 0;
|
||||
$results->totals->orders = 0;
|
||||
$results->intervals = array();
|
||||
$results->pages = 1;
|
||||
$results->page_no = 1;
|
||||
}
|
||||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps query arguments from the REST request.
|
||||
*
|
||||
* @param array $request Request array.
|
||||
* @return array
|
||||
*/
|
||||
protected function prepare_reports_query( $request ) {
|
||||
$args = array();
|
||||
$args['before'] = $request['before'];
|
||||
$args['after'] = $request['after'];
|
||||
$args['interval'] = $request['interval'];
|
||||
$args['page'] = $request['page'];
|
||||
$args['per_page'] = $request['per_page'];
|
||||
$args['orderby'] = $request['orderby'];
|
||||
$args['order'] = $request['order'];
|
||||
$args['taxes'] = (array) $request['taxes'];
|
||||
$args['segmentby'] = $request['segmentby'];
|
||||
$args['fields'] = $request['fields'];
|
||||
|
||||
return $args;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all reports.
|
||||
*
|
||||
* @param WP_REST_Request $request Request data.
|
||||
* @return array|WP_Error
|
||||
*/
|
||||
public function get_items( $request ) {
|
||||
$query_args = $this->prepare_reports_query( $request );
|
||||
$taxes_query = new Query( $query_args );
|
||||
$report_data = $taxes_query->get_data();
|
||||
|
||||
$out_data = array(
|
||||
'totals' => get_object_vars( $report_data->totals ),
|
||||
'intervals' => array(),
|
||||
);
|
||||
|
||||
foreach ( $report_data->intervals as $interval_data ) {
|
||||
$item = $this->prepare_item_for_response( (object) $interval_data, $request );
|
||||
$out_data['intervals'][] = $this->prepare_response_for_collection( $item );
|
||||
}
|
||||
|
||||
$response = rest_ensure_response( $out_data );
|
||||
$response->header( 'X-WP-Total', (int) $report_data->total );
|
||||
$response->header( 'X-WP-TotalPages', (int) $report_data->pages );
|
||||
|
||||
$page = $report_data->page_no;
|
||||
$max_pages = $report_data->pages;
|
||||
$base = add_query_arg( $request->get_query_params(), rest_url( sprintf( '/%s/%s', $this->namespace, $this->rest_base ) ) );
|
||||
if ( $page > 1 ) {
|
||||
$prev_page = $page - 1;
|
||||
if ( $prev_page > $max_pages ) {
|
||||
$prev_page = $max_pages;
|
||||
}
|
||||
$prev_link = add_query_arg( 'page', $prev_page, $base );
|
||||
$response->link_header( 'prev', $prev_link );
|
||||
}
|
||||
if ( $max_pages > $page ) {
|
||||
$next_page = $page + 1;
|
||||
$next_link = add_query_arg( 'page', $next_page, $base );
|
||||
$response->link_header( 'next', $next_link );
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare a report object for serialization.
|
||||
*
|
||||
* @param stdClass $report Report data.
|
||||
* @param WP_REST_Request $request Request object.
|
||||
* @return WP_REST_Response
|
||||
*/
|
||||
public function prepare_item_for_response( $report, $request ) {
|
||||
$data = get_object_vars( $report );
|
||||
|
||||
$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
|
||||
$data = $this->add_additional_fields_to_object( $data, $request );
|
||||
$data = $this->filter_response_by_context( $data, $context );
|
||||
|
||||
// Wrap the data in a response object.
|
||||
$response = rest_ensure_response( $data );
|
||||
|
||||
/**
|
||||
* Filter a report returned from the API.
|
||||
*
|
||||
* Allows modification of the report data right before it is returned.
|
||||
*
|
||||
* @param WP_REST_Response $response The response object.
|
||||
* @param object $report The original report object.
|
||||
* @param WP_REST_Request $request Request used to generate the response.
|
||||
*/
|
||||
return apply_filters( 'woocommerce_rest_prepare_report_taxes_stats', $response, $report, $request );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Report's schema, conforming to JSON Schema.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_item_schema() {
|
||||
$data_values = array(
|
||||
'total_tax' => array(
|
||||
'description' => __( 'Total tax.', 'woocommerce' ),
|
||||
'type' => 'number',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
'indicator' => true,
|
||||
'format' => 'currency',
|
||||
),
|
||||
'order_tax' => array(
|
||||
'description' => __( 'Order tax.', 'woocommerce' ),
|
||||
'type' => 'number',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
'indicator' => true,
|
||||
'format' => 'currency',
|
||||
),
|
||||
'shipping_tax' => array(
|
||||
'description' => __( 'Shipping tax.', 'woocommerce' ),
|
||||
'type' => 'number',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
'indicator' => true,
|
||||
'format' => 'currency',
|
||||
),
|
||||
'orders_count' => array(
|
||||
'description' => __( 'Number of orders.', 'woocommerce' ),
|
||||
'type' => 'integer',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'tax_codes' => array(
|
||||
'description' => __( 'Amount of tax codes.', 'woocommerce' ),
|
||||
'type' => 'integer',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
);
|
||||
|
||||
$segments = array(
|
||||
'segments' => array(
|
||||
'description' => __( 'Reports data grouped by segment condition.', 'woocommerce' ),
|
||||
'type' => 'array',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
'items' => array(
|
||||
'type' => 'object',
|
||||
'properties' => array(
|
||||
'segment_id' => array(
|
||||
'description' => __( 'Segment identificator.', 'woocommerce' ),
|
||||
'type' => 'integer',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'subtotals' => array(
|
||||
'description' => __( 'Interval subtotals.', 'woocommerce' ),
|
||||
'type' => 'object',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
'properties' => $data_values,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
$totals = array_merge( $data_values, $segments );
|
||||
|
||||
$schema = array(
|
||||
'$schema' => 'http://json-schema.org/draft-04/schema#',
|
||||
'title' => 'report_taxes_stats',
|
||||
'type' => 'object',
|
||||
'properties' => array(
|
||||
'totals' => array(
|
||||
'description' => __( 'Totals data.', 'woocommerce' ),
|
||||
'type' => 'object',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
'properties' => $totals,
|
||||
),
|
||||
'intervals' => array(
|
||||
'description' => __( 'Reports data grouped by intervals.', 'woocommerce' ),
|
||||
'type' => 'array',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
'items' => array(
|
||||
'type' => 'object',
|
||||
'properties' => array(
|
||||
'interval' => array(
|
||||
'description' => __( 'Type of interval.', 'woocommerce' ),
|
||||
'type' => 'string',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
'enum' => array( 'day', 'week', 'month', 'year' ),
|
||||
),
|
||||
'date_start' => array(
|
||||
'description' => __( "The date the report start, in the site's timezone.", 'woocommerce' ),
|
||||
'type' => 'date-time',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'date_start_gmt' => array(
|
||||
'description' => __( 'The date the report start, as GMT.', 'woocommerce' ),
|
||||
'type' => 'date-time',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'date_end' => array(
|
||||
'description' => __( "The date the report end, in the site's timezone.", 'woocommerce' ),
|
||||
'type' => 'date-time',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'date_end_gmt' => array(
|
||||
'description' => __( 'The date the report end, as GMT.', 'woocommerce' ),
|
||||
'type' => 'date-time',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'subtotals' => array(
|
||||
'description' => __( 'Interval subtotals.', 'woocommerce' ),
|
||||
'type' => 'object',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
'properties' => $totals,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
return $this->add_additional_fields_schema( $schema );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the query params for collections.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_collection_params() {
|
||||
$params = array();
|
||||
$params['context'] = $this->get_context_param( array( 'default' => 'view' ) );
|
||||
$params['page'] = array(
|
||||
'description' => __( 'Current page of the collection.', 'woocommerce' ),
|
||||
'type' => 'integer',
|
||||
'default' => 1,
|
||||
'sanitize_callback' => 'absint',
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
'minimum' => 1,
|
||||
);
|
||||
$params['per_page'] = array(
|
||||
'description' => __( 'Maximum number of items to be returned in result set.', 'woocommerce' ),
|
||||
'type' => 'integer',
|
||||
'default' => 10,
|
||||
'minimum' => 1,
|
||||
'maximum' => 100,
|
||||
'sanitize_callback' => 'absint',
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
);
|
||||
$params['after'] = array(
|
||||
'description' => __( 'Limit response to resources published after a given ISO8601 compliant date.', 'woocommerce' ),
|
||||
'type' => 'string',
|
||||
'format' => 'date-time',
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
);
|
||||
$params['before'] = array(
|
||||
'description' => __( 'Limit response to resources published before a given ISO8601 compliant date.', 'woocommerce' ),
|
||||
'type' => 'string',
|
||||
'format' => 'date-time',
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
);
|
||||
$params['order'] = array(
|
||||
'description' => __( 'Order sort attribute ascending or descending.', 'woocommerce' ),
|
||||
'type' => 'string',
|
||||
'default' => 'desc',
|
||||
'enum' => array( 'asc', 'desc' ),
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
);
|
||||
$params['orderby'] = array(
|
||||
'description' => __( 'Sort collection by object attribute.', 'woocommerce' ),
|
||||
'type' => 'string',
|
||||
'default' => 'date',
|
||||
'enum' => array(
|
||||
'date',
|
||||
'items_sold',
|
||||
'total_sales',
|
||||
'orders_count',
|
||||
'products_count',
|
||||
),
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
);
|
||||
$params['interval'] = array(
|
||||
'description' => __( 'Time interval to use for buckets in the returned data.', 'woocommerce' ),
|
||||
'type' => 'string',
|
||||
'default' => 'week',
|
||||
'enum' => array(
|
||||
'hour',
|
||||
'day',
|
||||
'week',
|
||||
'month',
|
||||
'quarter',
|
||||
'year',
|
||||
),
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
);
|
||||
$params['taxes'] = array(
|
||||
'description' => __( 'Limit result set to all items that have the specified term assigned in the taxes taxonomy.', 'woocommerce' ),
|
||||
'type' => 'array',
|
||||
'sanitize_callback' => 'wp_parse_id_list',
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
'items' => array(
|
||||
'type' => 'integer',
|
||||
),
|
||||
);
|
||||
$params['segmentby'] = array(
|
||||
'description' => __( 'Segment the response by additional constraint.', 'woocommerce' ),
|
||||
'type' => 'string',
|
||||
'enum' => array(
|
||||
'tax_rate_id',
|
||||
),
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
);
|
||||
$params['fields'] = array(
|
||||
'description' => __( 'Limit stats fields to the specified items.', 'woocommerce' ),
|
||||
'type' => 'array',
|
||||
'sanitize_callback' => 'wp_parse_slug_list',
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
'items' => array(
|
||||
'type' => 'string',
|
||||
),
|
||||
);
|
||||
|
||||
return $params;
|
||||
}
|
||||
}
|
@ -0,0 +1,263 @@
|
||||
<?php
|
||||
/**
|
||||
* API\Reports\Taxes\Stats\DataStore class file.
|
||||
*/
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\API\Reports\Taxes\Stats;
|
||||
|
||||
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.
|
||||
*/
|
||||
class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||
|
||||
/**
|
||||
* Table used to get the data.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected static $table_name = 'wc_order_tax_lookup';
|
||||
|
||||
/**
|
||||
* Cache identifier.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $cache_key = 'taxes_stats';
|
||||
|
||||
/**
|
||||
* Mapping columns to data type to return correct response types.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $column_types = array(
|
||||
'tax_codes' => 'intval',
|
||||
'total_tax' => 'floatval',
|
||||
'order_tax' => 'floatval',
|
||||
'shipping_tax' => 'floatval',
|
||||
'orders_count' => 'intval',
|
||||
);
|
||||
|
||||
/**
|
||||
* Data store context used to pass to filters.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $context = 'taxes_stats';
|
||||
|
||||
/**
|
||||
* 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(
|
||||
'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 Stats report
|
||||
*
|
||||
* @param array $query_args Query arguments supplied by the user.
|
||||
*/
|
||||
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})";
|
||||
}
|
||||
|
||||
$order_status_filter = $this->get_status_subquery( $query_args );
|
||||
if ( $order_status_filter ) {
|
||||
$taxes_where_clause .= " AND ( {$order_status_filter} )";
|
||||
}
|
||||
|
||||
$this->add_time_period_sql_params( $query_args, $order_tax_lookup_table );
|
||||
$this->total_query->add_sql_clause( 'where', $taxes_where_clause );
|
||||
|
||||
$this->add_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' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get taxes associated with a store.
|
||||
*
|
||||
* @param array $args Array of args to filter the query by. Supports `include`.
|
||||
* @return array An array of all taxes.
|
||||
*/
|
||||
public static function get_taxes( $args ) {
|
||||
global $wpdb;
|
||||
$query = "
|
||||
SELECT
|
||||
tax_rate_id,
|
||||
tax_rate_country,
|
||||
tax_rate_state,
|
||||
tax_rate_name,
|
||||
tax_rate_priority
|
||||
FROM {$wpdb->prefix}woocommerce_tax_rates
|
||||
";
|
||||
if ( ! empty( $args['include'] ) ) {
|
||||
$included_taxes = implode( ',', $args['include'] );
|
||||
$query .= " WHERE tax_rate_id IN ({$included_taxes})";
|
||||
}
|
||||
return $wpdb->get_results( $query, ARRAY_A ); // WPCS: cache ok, DB call ok, unprepared SQL ok.
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the report data based on parameters supplied by the user.
|
||||
*
|
||||
* @param array $query_args Query parameters.
|
||||
* @return stdClass|WP_Error Data.
|
||||
*/
|
||||
public function get_data( $query_args ) {
|
||||
global $wpdb;
|
||||
|
||||
$table_name = self::get_db_table_name();
|
||||
|
||||
// These defaults are only partially applied when used via REST API, as that has its own defaults.
|
||||
$defaults = array(
|
||||
'per_page' => get_option( 'posts_per_page' ),
|
||||
'page' => 1,
|
||||
'order' => 'DESC',
|
||||
'orderby' => 'tax_rate_id',
|
||||
'before' => TimeInterval::default_before(),
|
||||
'after' => TimeInterval::default_after(),
|
||||
'fields' => '*',
|
||||
'taxes' => array(),
|
||||
);
|
||||
$query_args = wp_parse_args( $query_args, $defaults );
|
||||
$this->normalize_timezones( $query_args, $defaults );
|
||||
|
||||
/*
|
||||
* We need to get the cache key here because
|
||||
* parent::update_intervals_sql_params() modifies $query_args.
|
||||
*/
|
||||
$cache_key = $this->get_cache_key( $query_args );
|
||||
$data = $this->get_cached_data( $cache_key );
|
||||
|
||||
if ( false === $data ) {
|
||||
$this->initialize_queries();
|
||||
|
||||
$data = (object) array(
|
||||
'totals' => (object) array(),
|
||||
'intervals' => (object) array(),
|
||||
'total' => 0,
|
||||
'pages' => 0,
|
||||
'page_no' => 0,
|
||||
);
|
||||
|
||||
$selections = $this->selected_columns( $query_args );
|
||||
$params = $this->get_limit_params( $query_args );
|
||||
$order_stats_join = "JOIN {$wpdb->prefix}wc_order_stats ON {$table_name}.order_id = {$wpdb->prefix}wc_order_stats.order_id";
|
||||
$this->update_sql_query_params( $query_args );
|
||||
$this->interval_query->add_sql_clause( 'join', $order_stats_join );
|
||||
|
||||
$db_intervals = $wpdb->get_col(
|
||||
$this->interval_query->get_query_statement()
|
||||
); // WPCS: cache ok, DB call ok, unprepared SQL ok.
|
||||
$db_interval_count = count( $db_intervals );
|
||||
$expected_interval_count = TimeInterval::intervals_between( $query_args['after'], $query_args['before'], $query_args['interval'] );
|
||||
$total_pages = (int) ceil( $expected_interval_count / $params['per_page'] );
|
||||
|
||||
if ( $query_args['page'] < 1 || $query_args['page'] > $total_pages ) {
|
||||
return $data;
|
||||
}
|
||||
$this->total_query->add_sql_clause( 'select', $selections );
|
||||
$this->total_query->add_sql_clause( 'join', $order_stats_join );
|
||||
$this->total_query->add_sql_clause( 'where_time', $this->get_sql_clause( 'where_time' ) );
|
||||
|
||||
$totals = $wpdb->get_results(
|
||||
$this->total_query->get_query_statement(),
|
||||
ARRAY_A
|
||||
); // WPCS: cache ok, DB call ok, unprepared SQL ok.
|
||||
|
||||
if ( null === $totals ) {
|
||||
return new \WP_Error( 'woocommerce_analytics_taxes_stats_result_failed', __( 'Sorry, fetching revenue data failed.', 'woocommerce' ) );
|
||||
}
|
||||
|
||||
// @todo remove these assignements when refactoring segmenter classes to use query objects.
|
||||
$totals_query = array(
|
||||
'from_clause' => $this->total_query->get_sql_clause( 'join' ),
|
||||
'where_time_clause' => $this->total_query->get_sql_clause( 'where_time' ),
|
||||
'where_clause' => $this->total_query->get_sql_clause( 'where' ),
|
||||
);
|
||||
$intervals_query = array(
|
||||
'select_clause' => $this->get_sql_clause( 'select' ),
|
||||
'from_clause' => $this->interval_query->get_sql_clause( 'join' ),
|
||||
'where_time_clause' => $this->interval_query->get_sql_clause( 'where_time' ),
|
||||
'where_clause' => $this->interval_query->get_sql_clause( 'where' ),
|
||||
);
|
||||
$segmenter = new Segmenter( $query_args, $this->report_columns );
|
||||
$totals[0]['segments'] = $segmenter->get_totals_segments( $totals_query, $table_name );
|
||||
|
||||
$this->update_intervals_sql_params( $query_args, $db_interval_count, $expected_interval_count, $table_name );
|
||||
|
||||
if ( '' !== $selections ) {
|
||||
$this->interval_query->add_sql_clause( 'select', ', ' . $selections );
|
||||
}
|
||||
|
||||
$this->interval_query->add_sql_clause( 'select', ", MAX({$table_name}.date_created) AS datetime_anchor" );
|
||||
$this->interval_query->add_sql_clause( 'order_by', $this->get_sql_clause( 'order_by' ) );
|
||||
$this->interval_query->add_sql_clause( 'limit', $this->get_sql_clause( 'limit' ) );
|
||||
|
||||
$intervals = $wpdb->get_results(
|
||||
$this->interval_query->get_query_statement(),
|
||||
ARRAY_A
|
||||
); // WPCS: cache ok, DB call ok, unprepared SQL ok.
|
||||
|
||||
if ( null === $intervals ) {
|
||||
return new \WP_Error( 'woocommerce_analytics_taxes_stats_result_failed', __( 'Sorry, fetching tax data failed.', 'woocommerce' ) );
|
||||
}
|
||||
|
||||
$totals = (object) $this->cast_numbers( $totals[0] );
|
||||
|
||||
$data = (object) array(
|
||||
'totals' => $totals,
|
||||
'intervals' => $intervals,
|
||||
'total' => $expected_interval_count,
|
||||
'pages' => $total_pages,
|
||||
'page_no' => (int) $query_args['page'],
|
||||
);
|
||||
|
||||
if ( TimeInterval::intervals_missing( $expected_interval_count, $db_interval_count, $params['per_page'], $query_args['page'], $query_args['order'], $query_args['orderby'], count( $intervals ) ) ) {
|
||||
$this->fill_in_missing_intervals( $db_intervals, $query_args['adj_after'], $query_args['adj_before'], $query_args['interval'], $data );
|
||||
$this->sort_intervals( $data, $query_args['orderby'], $query_args['order'] );
|
||||
$this->remove_extra_records( $data, $query_args['page'], $params['per_page'], $db_interval_count, $expected_interval_count, $query_args['orderby'], $query_args['order'] );
|
||||
} else {
|
||||
$this->update_interval_boundary_dates( $query_args['after'], $query_args['before'], $query_args['interval'], $data->intervals );
|
||||
}
|
||||
$segmenter->add_intervals_segments( $data, $intervals_query, $table_name );
|
||||
$this->create_interval_subtotals( $data->intervals );
|
||||
$this->set_cached_data( $cache_key, $data );
|
||||
}
|
||||
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' );
|
||||
}
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
<?php
|
||||
/**
|
||||
* Class for parameter-based Taxes Stats Report querying
|
||||
*
|
||||
* Example usage:
|
||||
* $args = array(
|
||||
* 'before' => '2018-07-19 00:00:00',
|
||||
* 'after' => '2018-07-05 00:00:00',
|
||||
* 'page' => 2,
|
||||
* 'categories' => array(15, 18),
|
||||
* 'product_ids' => array(1,2,3)
|
||||
* );
|
||||
* $report = new \Automattic\WooCommerce\Admin\API\Reports\Taxes\Stats\Query( $args );
|
||||
* $mydata = $report->get_data();
|
||||
*/
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\API\Reports\Taxes\Stats;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
use \Automattic\WooCommerce\Admin\API\Reports\Query as ReportsQuery;
|
||||
|
||||
/**
|
||||
* API\Reports\Taxes\Stats\Query
|
||||
*/
|
||||
class Query extends ReportsQuery {
|
||||
|
||||
/**
|
||||
* Valid fields for Taxes report.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_default_query_vars() {
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get tax stats data based on the current query vars.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_data() {
|
||||
$args = apply_filters( 'woocommerce_analytics_taxes_stats_query_args', $this->get_query_vars() );
|
||||
|
||||
$data_store = \WC_Data_Store::load( 'report-taxes-stats' );
|
||||
$results = $data_store->get_data( $args );
|
||||
return apply_filters( 'woocommerce_analytics_taxes_stats_select_query', $results, $args );
|
||||
}
|
||||
}
|
@ -0,0 +1,151 @@
|
||||
<?php
|
||||
/**
|
||||
* Class for adding segmenting support without cluttering the data stores.
|
||||
*/
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\API\Reports\Taxes\Stats;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
use \Automattic\WooCommerce\Admin\API\Reports\Segmenter as ReportsSegmenter;
|
||||
|
||||
/**
|
||||
* Date & time interval and numeric range handling class for Reporting API.
|
||||
*/
|
||||
class Segmenter extends ReportsSegmenter {
|
||||
|
||||
/**
|
||||
* Returns column => query mapping to be used for order-related order-level segmenting query (e.g. tax_rate_id).
|
||||
*
|
||||
* @param string $lookup_table Name of SQL table containing the order-level segmenting info.
|
||||
*
|
||||
* @return array Column => SELECT query mapping.
|
||||
*/
|
||||
protected function get_segment_selections_order_level( $lookup_table ) {
|
||||
$columns_mapping = array(
|
||||
'tax_codes' => "COUNT(DISTINCT $lookup_table.tax_rate_id) as tax_codes",
|
||||
'total_tax' => "SUM($lookup_table.total_tax) AS total_tax",
|
||||
'order_tax' => "SUM($lookup_table.order_tax) as order_tax",
|
||||
'shipping_tax' => "SUM($lookup_table.shipping_tax) as shipping_tax",
|
||||
'orders_count' => "COUNT(DISTINCT $lookup_table.order_id) as orders_count",
|
||||
);
|
||||
|
||||
return $columns_mapping;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate segments for totals query where the segmenting property is bound to order (e.g. coupon or customer type).
|
||||
*
|
||||
* @param string $segmenting_select SELECT part of segmenting SQL query.
|
||||
* @param string $segmenting_from FROM part of segmenting SQL query.
|
||||
* @param string $segmenting_where WHERE part of segmenting SQL query.
|
||||
* @param string $segmenting_groupby GROUP BY part of segmenting SQL query.
|
||||
* @param string $table_name Name of SQL table which is the stats table for orders.
|
||||
* @param array $totals_query Array of SQL clauses for intervals query.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_order_related_totals_segments( $segmenting_select, $segmenting_from, $segmenting_where, $segmenting_groupby, $table_name, $totals_query ) {
|
||||
global $wpdb;
|
||||
|
||||
$totals_segments = $wpdb->get_results(
|
||||
"SELECT
|
||||
$segmenting_groupby
|
||||
$segmenting_select
|
||||
FROM
|
||||
$table_name
|
||||
$segmenting_from
|
||||
{$totals_query['from_clause']}
|
||||
WHERE
|
||||
1=1
|
||||
{$totals_query['where_time_clause']}
|
||||
{$totals_query['where_clause']}
|
||||
$segmenting_where
|
||||
GROUP BY
|
||||
$segmenting_groupby",
|
||||
ARRAY_A
|
||||
); // WPCS: cache ok, DB call ok, unprepared SQL ok.
|
||||
|
||||
// Reformat result.
|
||||
$totals_segments = $this->reformat_totals_segments( $totals_segments, $segmenting_groupby );
|
||||
return $totals_segments;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate segments for intervals query where the segmenting property is bound to order (e.g. coupon or customer type).
|
||||
*
|
||||
* @param string $segmenting_select SELECT part of segmenting SQL query.
|
||||
* @param string $segmenting_from FROM part of segmenting SQL query.
|
||||
* @param string $segmenting_where WHERE part of segmenting SQL query.
|
||||
* @param string $segmenting_groupby GROUP BY part of segmenting SQL query.
|
||||
* @param string $table_name Name of SQL table which is the stats table for orders.
|
||||
* @param array $intervals_query Array of SQL clauses for intervals query.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_order_related_intervals_segments( $segmenting_select, $segmenting_from, $segmenting_where, $segmenting_groupby, $table_name, $intervals_query ) {
|
||||
global $wpdb;
|
||||
$segmenting_limit = '';
|
||||
$limit_parts = explode( ',', $intervals_query['limit'] );
|
||||
if ( 2 === count( $limit_parts ) ) {
|
||||
$orig_rowcount = intval( $limit_parts[1] );
|
||||
$segmenting_limit = $limit_parts[0] . ',' . $orig_rowcount * count( $this->get_all_segments() );
|
||||
}
|
||||
|
||||
$intervals_segments = $wpdb->get_results(
|
||||
"SELECT
|
||||
MAX($table_name.date_created) AS datetime_anchor,
|
||||
{$intervals_query['select_clause']} AS time_interval,
|
||||
$segmenting_groupby
|
||||
$segmenting_select
|
||||
FROM
|
||||
$table_name
|
||||
$segmenting_from
|
||||
{$intervals_query['from_clause']}
|
||||
WHERE
|
||||
1=1
|
||||
{$intervals_query['where_time_clause']}
|
||||
{$intervals_query['where_clause']}
|
||||
$segmenting_where
|
||||
GROUP BY
|
||||
time_interval, $segmenting_groupby
|
||||
$segmenting_limit",
|
||||
ARRAY_A
|
||||
); // WPCS: cache ok, DB call ok, unprepared SQL ok.
|
||||
|
||||
// Reformat result.
|
||||
$intervals_segments = $this->reformat_intervals_segments( $intervals_segments, $segmenting_groupby );
|
||||
return $intervals_segments;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return array of segments formatted for REST response.
|
||||
*
|
||||
* @param string $type Type of segments to return--'totals' or 'intervals'.
|
||||
* @param array $query_params SQL query parameter array.
|
||||
* @param string $table_name Name of main SQL table for the data store (used as basis for JOINS).
|
||||
*
|
||||
* @return array
|
||||
* @throws \Automattic\WooCommerce\Admin\API\Reports\ParameterException In case of segmenting by variations, when no parent product is specified.
|
||||
*/
|
||||
protected function get_segments( $type, $query_params, $table_name ) {
|
||||
if ( ! isset( $this->query_args['segmentby'] ) || '' === $this->query_args['segmentby'] ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$segmenting_where = '';
|
||||
$segmenting_from = '';
|
||||
$segments = array();
|
||||
|
||||
if ( 'tax_rate_id' === $this->query_args['segmentby'] ) {
|
||||
$tax_rate_level_columns = $this->get_segment_selections_order_level( $table_name );
|
||||
$segmenting_select = $this->prepare_selections( $tax_rate_level_columns );
|
||||
$this->report_columns = $tax_rate_level_columns;
|
||||
$segmenting_groupby = $table_name . '.tax_rate_id';
|
||||
|
||||
$segments = $this->get_order_related_segments( $type, $segmenting_select, $segmenting_from, $segmenting_where, $segmenting_groupby, $table_name, $query_params );
|
||||
}
|
||||
|
||||
return $segments;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user