Merge pull request woocommerce/woocommerce-admin#2502 from woocommerce/add/311-reports-csv-exporter-class
Add Reports CSV Exporter Class
This commit is contained in:
commit
356f1ada77
|
@ -214,17 +214,23 @@ class WC_Admin_REST_Reports_Orders_Controller extends WC_Admin_REST_Reports_Cont
|
|||
'readonly' => true,
|
||||
),
|
||||
'extended_info' => array(
|
||||
'products' => array(
|
||||
'products' => array(
|
||||
'type' => 'array',
|
||||
'readonly' => true,
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'description' => __( 'List of product IDs and names.', 'woocommerce-admin' ),
|
||||
'description' => __( 'List of order product IDs, names, quantities.', 'woocommerce-admin' ),
|
||||
),
|
||||
'categories' => array(
|
||||
'coupons' => array(
|
||||
'type' => 'array',
|
||||
'readonly' => true,
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'description' => __( 'Category IDs.', 'woocommerce-admin' ),
|
||||
'description' => __( 'List of order coupons.', 'woocommerce-admin' ),
|
||||
),
|
||||
'customer' => array(
|
||||
'type' => 'object',
|
||||
'readonly' => true,
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'description' => __( 'Order customer information.', 'woocommerce-admin' ),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
@ -200,11 +200,11 @@ class WC_Admin_REST_Reports_Products_Controller extends WC_REST_Reports_Controll
|
|||
'context' => array( 'view', 'edit' ),
|
||||
'description' => __( 'Product link.', 'woocommerce-admin' ),
|
||||
),
|
||||
'attributes' => array(
|
||||
'category_ids' => array(
|
||||
'type' => 'array',
|
||||
'readonly' => true,
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'description' => __( 'Product attributes.', 'woocommerce-admin' ),
|
||||
'description' => __( 'Product category IDs.', 'woocommerce-admin' ),
|
||||
),
|
||||
'stock_status' => array(
|
||||
'type' => 'string',
|
||||
|
|
|
@ -0,0 +1,222 @@
|
|||
<?php
|
||||
/**
|
||||
* Handles reports CSV export.
|
||||
*
|
||||
* @package WooCommerce/Export
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Include dependencies.
|
||||
*/
|
||||
if ( ! class_exists( 'WC_CSV_Batch_Exporter', false ) ) {
|
||||
include_once WC_ABSPATH . 'includes/export/abstract-wc-csv-batch-exporter.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* WC_Admin_Report_CSV_Exporter Class.
|
||||
*/
|
||||
class WC_Admin_Report_CSV_Exporter extends WC_CSV_Batch_Exporter {
|
||||
/**
|
||||
* Type of report being exported.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $report_type;
|
||||
|
||||
/**
|
||||
* Parameters for the report query.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $report_args;
|
||||
|
||||
/**
|
||||
* REST controller for the report.
|
||||
*
|
||||
* @var WC_REST_Reports_Controller
|
||||
*/
|
||||
protected $controller;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param string $type Report type. E.g. 'customers'.
|
||||
* @param array $args Report parameters.
|
||||
*/
|
||||
public function __construct( $type, $args ) {
|
||||
parent::__construct();
|
||||
|
||||
$this->set_limit( 10 );
|
||||
$this->set_report_type( $type );
|
||||
$this->set_report_args( $args );
|
||||
$this->set_column_names( $this->get_report_columns() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter for report type.
|
||||
*
|
||||
* @param string $type The report type. E.g. customers.
|
||||
*/
|
||||
public function set_report_type( $type ) {
|
||||
$this->report_type = $type;
|
||||
$this->export_type = "admin_{$type}_report";
|
||||
$this->filename = "wc-{$type}-report-export.csv";
|
||||
$this->controller = $this->map_report_controller();
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter for report args.
|
||||
*
|
||||
* @param array $args The report args.
|
||||
*/
|
||||
public function set_report_args( $args ) {
|
||||
// Use our own internal limit and include all extended info.
|
||||
$report_args = array_merge(
|
||||
$args,
|
||||
array(
|
||||
'per_page' => $this->get_limit(),
|
||||
'extended_info' => true,
|
||||
)
|
||||
);
|
||||
|
||||
// Should this happen externally?
|
||||
if ( isset( $report_args['page'] ) ) {
|
||||
$this->set_page( $report_args['page'] );
|
||||
}
|
||||
|
||||
$this->report_args = $report_args;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a REST controller instance for the report type.
|
||||
*
|
||||
* @return bool|WC_REST_Reports_Controller Report controller instance or boolean false on error.
|
||||
*/
|
||||
protected function map_report_controller() {
|
||||
$controller_map = array(
|
||||
'products' => 'WC_Admin_REST_Reports_Products_Controller',
|
||||
'variations' => 'WC_Admin_REST_Reports_Variations_Controller',
|
||||
'orders' => 'WC_Admin_REST_Reports_Orders_Controller',
|
||||
'categories' => 'WC_Admin_REST_Reports_Categories_Controller',
|
||||
'taxes' => 'WC_Admin_REST_Reports_Taxes_Controller',
|
||||
'coupons' => 'WC_Admin_REST_Reports_Coupons_Controller',
|
||||
'stock' => 'WC_Admin_REST_Reports_Stock_Controller',
|
||||
'downloads' => 'WC_Admin_REST_Reports_Downloads_Controller',
|
||||
'customers' => 'WC_Admin_REST_Reports_Customers_Controller',
|
||||
);
|
||||
|
||||
if ( isset( $controller_map[ $this->report_type ] ) ) {
|
||||
// @todo: load the controllers if accessing outside a context where the REST API is loaded?
|
||||
return new $controller_map[ $this->report_type ]();
|
||||
}
|
||||
|
||||
// Should this do something else?
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the report columns from the schema.
|
||||
*
|
||||
* @return array Array of report column names.
|
||||
*/
|
||||
protected function get_report_columns() {
|
||||
$report_columns = array();
|
||||
$report_schema = $this->controller->get_item_schema();
|
||||
|
||||
if ( isset( $report_schema['properties'] ) ) {
|
||||
foreach ( $report_schema['properties'] as $column_name => $column_info ) {
|
||||
// Expand extended info columns into export.
|
||||
if ( 'extended_info' === $column_name ) {
|
||||
// Remove columns with questionable CSV values, like markup.
|
||||
$extended_info = array_diff( array_keys( $column_info ), array( 'image' ) );
|
||||
$report_columns = array_merge( $report_columns, $extended_info );
|
||||
} else {
|
||||
$report_columns[] = $column_name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $report_columns;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get total % complete.
|
||||
*
|
||||
* Forces an int from parent::get_percent_complete(), which can return a float.
|
||||
*
|
||||
* @return int Percent complete.
|
||||
*/
|
||||
public function get_percent_complete() {
|
||||
return intval( parent::get_percent_complete() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare data for export.
|
||||
*/
|
||||
public function prepare_data_to_export() {
|
||||
$request = new WP_REST_Request( 'GET', "/wc/v4/reports/{$this->report_type}" );
|
||||
$params = $this->controller->get_collection_params();
|
||||
$defaults = array();
|
||||
|
||||
foreach ( $params as $arg => $options ) {
|
||||
if ( isset( $options['default'] ) ) {
|
||||
$defaults[ $arg ] = $options['default'];
|
||||
}
|
||||
}
|
||||
|
||||
$request->set_default_params( $defaults );
|
||||
$request->set_query_params( $this->report_args );
|
||||
|
||||
$response = $this->controller->get_items( $request );
|
||||
$report_meta = $response->get_headers();
|
||||
$report_data = $response->get_data();
|
||||
$this->total_rows = $report_meta['X-WP-Total'];
|
||||
$this->row_data = array_map( array( $this, 'generate_row_data' ), $report_data );
|
||||
}
|
||||
|
||||
/**
|
||||
* Take a report item and generate row data from it for export.
|
||||
*
|
||||
* @param object $item Report item data.
|
||||
* @return array CSV row data.
|
||||
*/
|
||||
protected function generate_row_data( $item ) {
|
||||
$columns = $this->get_column_names();
|
||||
$row = array();
|
||||
|
||||
// Expand extended info.
|
||||
if ( isset( $item['extended_info'] ) ) {
|
||||
// Pull extended info property from report item object.
|
||||
$extended_info = (array) $item['extended_info'];
|
||||
unset( $item['extended_info'] );
|
||||
|
||||
// Merge extended info columns into report item object.
|
||||
$item = array_merge( $item, $extended_info );
|
||||
}
|
||||
|
||||
foreach ( $columns as $column_id => $column_name ) {
|
||||
$value = isset( $item[ $column_name ] ) ? $item[ $column_name ] : null;
|
||||
|
||||
if ( has_filter( "woocommerce_export_{$this->export_type}_column_{$column_name}" ) ) {
|
||||
// Filter for 3rd parties.
|
||||
$value = apply_filters( "woocommerce_export_{$this->export_type}_column_{$column_name}", '', $item );
|
||||
|
||||
} elseif ( is_callable( array( $this, "get_column_value_{$column_name}" ) ) ) {
|
||||
// Handle special columns which don't map 1:1 to item data.
|
||||
$value = $this->{"get_column_value_{$column_name}"}( $item, $this->export_type );
|
||||
|
||||
} elseif ( ! is_scalar( $value ) ) {
|
||||
// Ensure that the value is somewhat readable in CSV.
|
||||
$value = wp_json_encode( $value );
|
||||
}
|
||||
|
||||
$row[ $column_id ] = $value;
|
||||
}
|
||||
|
||||
return apply_filters( "woocommerce_export_{$this->export_type}_row_data", $row, $item );
|
||||
}
|
||||
}
|
|
@ -267,5 +267,61 @@ class WC_Tests_Reports_Coupons extends WC_Unit_Test_Case {
|
|||
);
|
||||
$this->assertEquals( $expected_data, $data );
|
||||
|
||||
// Test the CSV export.
|
||||
$expected_csv_columns = array(
|
||||
'coupon_id',
|
||||
'amount',
|
||||
'orders_count',
|
||||
'code',
|
||||
'date_created',
|
||||
'date_created_gmt',
|
||||
'date_expires',
|
||||
'date_expires_gmt',
|
||||
'discount_type',
|
||||
);
|
||||
|
||||
// Expected CSV for Coupon 2.
|
||||
$coupon_2_csv = array(
|
||||
$coupon_2_response['coupon_id'],
|
||||
$coupon_2_response['amount'],
|
||||
$coupon_2_response['orders_count'],
|
||||
$coupon_2_response['extended_info']['code'],
|
||||
$coupon_2_response['extended_info']['date_created'],
|
||||
$coupon_2_response['extended_info']['date_created_gmt'],
|
||||
$coupon_2_response['extended_info']['date_expires'],
|
||||
$coupon_2_response['extended_info']['date_expires_gmt'],
|
||||
$coupon_2_response['extended_info']['discount_type'],
|
||||
);
|
||||
|
||||
// Expected CSV for Coupon 1.
|
||||
$coupon_1_csv = array(
|
||||
$coupon_1_response['coupon_id'],
|
||||
$coupon_1_response['amount'],
|
||||
$coupon_1_response['orders_count'],
|
||||
$coupon_1_response['extended_info']['code'],
|
||||
$coupon_1_response['extended_info']['date_created'],
|
||||
$coupon_1_response['extended_info']['date_created_gmt'],
|
||||
$coupon_1_response['extended_info']['date_expires'],
|
||||
$coupon_1_response['extended_info']['date_expires_gmt'],
|
||||
$coupon_1_response['extended_info']['discount_type'],
|
||||
);
|
||||
|
||||
// Build the expected CSV data, including Excel header (see: WC_CSV_Exporter::export).
|
||||
$expected_csv = chr( 239 ) . chr( 187 ) . chr( 191 );
|
||||
$expected_csv .= implode( ',', $expected_csv_columns ) . PHP_EOL;
|
||||
$expected_csv .= implode( ',', $coupon_2_csv ) . PHP_EOL;
|
||||
$expected_csv .= implode( ',', $coupon_1_csv ) . PHP_EOL;
|
||||
|
||||
// Ensure our exporter and report controller have been loaded.
|
||||
do_action( 'rest_api_init' );
|
||||
|
||||
// Run the export and compare values.
|
||||
$export = new WC_Admin_Report_CSV_Exporter( 'coupons', $args );
|
||||
$export->generate_file();
|
||||
$actual_csv = $export->get_file();
|
||||
|
||||
$this->assertEquals( 100, $export->get_percent_complete() );
|
||||
$this->assertEquals( 2, $export->get_total_exported() );
|
||||
$this->assertEquals( $expected_csv, $actual_csv );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -145,6 +145,7 @@ class WC_Admin_Feature_Plugin {
|
|||
require_once WC_ADMIN_ABSPATH . 'includes/class-wc-admin-install.php';
|
||||
require_once WC_ADMIN_ABSPATH . 'includes/class-wc-admin-events.php';
|
||||
require_once WC_ADMIN_ABSPATH . 'includes/class-wc-admin-api-init.php';
|
||||
require_once WC_ADMIN_ABSPATH . 'includes/export/class-wc-admin-report-csv-exporter.php';
|
||||
|
||||
// Data triggers.
|
||||
require_once WC_ADMIN_ABSPATH . 'includes/data-stores/class-wc-admin-notes-data-store.php';
|
||||
|
|
Loading…
Reference in New Issue