From d2ac6cdbe1bee5fba14a197d9dd475678227aaf2 Mon Sep 17 00:00:00 2001 From: Peter Fabian Date: Mon, 17 Sep 2018 16:32:03 +0200 Subject: [PATCH 01/20] Added reporting REST API extensions to wc-admin. --- ...min-rest-reports-categories-controller.php | 280 +++++++++ ...class-wc-admin-rest-reports-controller.php | 262 ++++++++ ...-admin-rest-reports-coupons-controller.php | 33 + ...-rest-reports-coupons-stats-controller.php | 34 + ...dmin-rest-reports-customers-controller.php | 34 + ...dmin-rest-reports-downloads-controller.php | 34 + ...est-reports-downloads-files-controller.php | 34 + ...est-reports-downloads-stats-controller.php | 34 + ...n-rest-reports-orders-stats-controller.php | 349 +++++++++++ ...admin-rest-reports-products-controller.php | 256 ++++++++ ...rest-reports-products-stats-controller.php | 313 ++++++++++ ...-rest-reports-revenue-stats-controller.php | 324 ++++++++++ ...wc-admin-rest-reports-taxes-controller.php | 34 + ...in-rest-reports-taxes-stats-controller.php | 34 + ...in-rest-system-status-tools-controller.php | 71 +++ .../includes/class-wc-admin-api-init.php | 199 ++++++ ...c-admin-order-stats-background-process.php | 77 +++ ...lass-wc-admin-reports-categories-query.php | 49 ++ .../class-wc-admin-reports-interval.php | 413 +++++++++++++ ...ss-wc-admin-reports-orders-stats-query.php | 60 ++ .../class-wc-admin-reports-products-query.php | 52 ++ ...-wc-admin-reports-products-stats-query.php | 52 ++ .../includes/class-wc-admin-reports-query.php | 29 + .../class-wc-admin-reports-revenue-query.php | 68 ++ ...wc-admin-reports-categories-data-store.php | 265 ++++++++ .../class-wc-admin-reports-data-store.php | 579 ++++++++++++++++++ ...ass-wc-admin-reports-orders-data-store.php | 465 ++++++++++++++ ...s-wc-admin-reports-products-data-store.php | 228 +++++++ ...dmin-reports-products-stats-data-store.php | 200 ++++++ ...-wc-admin-reports-data-store-interface.php | 27 + .../includes/wc-admin-order-functions.php | 42 ++ plugins/woocommerce-admin/wc-admin.php | 3 + 32 files changed, 4934 insertions(+) create mode 100644 plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-categories-controller.php create mode 100644 plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-controller.php create mode 100644 plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-coupons-controller.php create mode 100644 plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-coupons-stats-controller.php create mode 100644 plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-customers-controller.php create mode 100644 plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-downloads-controller.php create mode 100644 plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-downloads-files-controller.php create mode 100644 plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-downloads-stats-controller.php create mode 100644 plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-orders-stats-controller.php create mode 100644 plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-products-controller.php create mode 100644 plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-products-stats-controller.php create mode 100644 plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-revenue-stats-controller.php create mode 100644 plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-taxes-controller.php create mode 100644 plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-taxes-stats-controller.php create mode 100644 plugins/woocommerce-admin/includes/api/class-wc-admin-rest-system-status-tools-controller.php create mode 100644 plugins/woocommerce-admin/includes/class-wc-admin-api-init.php create mode 100644 plugins/woocommerce-admin/includes/class-wc-admin-order-stats-background-process.php create mode 100644 plugins/woocommerce-admin/includes/class-wc-admin-reports-categories-query.php create mode 100644 plugins/woocommerce-admin/includes/class-wc-admin-reports-interval.php create mode 100644 plugins/woocommerce-admin/includes/class-wc-admin-reports-orders-stats-query.php create mode 100644 plugins/woocommerce-admin/includes/class-wc-admin-reports-products-query.php create mode 100644 plugins/woocommerce-admin/includes/class-wc-admin-reports-products-stats-query.php create mode 100644 plugins/woocommerce-admin/includes/class-wc-admin-reports-query.php create mode 100644 plugins/woocommerce-admin/includes/class-wc-admin-reports-revenue-query.php create mode 100644 plugins/woocommerce-admin/includes/data-stores/class-wc-admin-reports-categories-data-store.php create mode 100644 plugins/woocommerce-admin/includes/data-stores/class-wc-admin-reports-data-store.php create mode 100644 plugins/woocommerce-admin/includes/data-stores/class-wc-admin-reports-orders-data-store.php create mode 100644 plugins/woocommerce-admin/includes/data-stores/class-wc-admin-reports-products-data-store.php create mode 100644 plugins/woocommerce-admin/includes/data-stores/class-wc-admin-reports-products-stats-data-store.php create mode 100644 plugins/woocommerce-admin/includes/interfaces/class-wc-admin-reports-data-store-interface.php create mode 100644 plugins/woocommerce-admin/includes/wc-admin-order-functions.php diff --git a/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-categories-controller.php b/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-categories-controller.php new file mode 100644 index 00000000000..394141891c9 --- /dev/null +++ b/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-categories-controller.php @@ -0,0 +1,280 @@ +prepare_reports_query( $request ); + $categories_query = new WC_Reports_Categories_Query( $query_args ); + $report_data = $categories_query->get_data(); + + if ( is_wp_error( $report_data ) ) { + return $report_data; + } + + if ( ! isset( $report_data->data ) || ! isset( $report_data->page_no ) || ! isset( $report_data->pages ) ) { + return new WP_Error( 'woocommerce_rest_reports_categories_invalid_response', __( 'Invalid response from data store.', 'woocommerce' ), array( 'status' => 500 ) ); + } + + $out_data = array(); + + foreach ( $report_data->data as $datum ) { + $item = $this->prepare_item_for_response( $datum, $request ); + $out_data[] = $this->prepare_response_for_collection( $item ); + } + + $response = rest_ensure_response( $out_data ); + $response->header( 'X-WP-Total', (int) $report_data->total ); + $response->header( 'X-WP-TotalPages', (int) $report_data->pages ); + + $page = $report_data->page_no; + $max_pages = $report_data->pages; + $base = add_query_arg( $request->get_query_params(), rest_url( sprintf( '/%s/%s', $this->namespace, $this->rest_base ) ) ); + if ( $page > 1 ) { + $prev_page = $page - 1; + if ( $prev_page > $max_pages ) { + $prev_page = $max_pages; + } + $prev_link = add_query_arg( 'page', $prev_page, $base ); + $response->link_header( 'prev', $prev_link ); + } + if ( $max_pages > $page ) { + $next_page = $page + 1; + $next_link = add_query_arg( 'page', $next_page, $base ); + $response->link_header( 'next', $next_link ); + } + + return $response; + } + + /** + * Prepare a report object for serialization. + * + * @param stdClass $report Report data. + * @param WP_REST_Request $request Request object. + * @return WP_REST_Response + */ + public function prepare_item_for_response( $report, $request ) { + $data = $report; + + $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; + $data = $this->add_additional_fields_to_object( $data, $request ); + $data = $this->filter_response_by_context( $data, $context ); + + // Wrap the data in a response object. + $response = rest_ensure_response( $data ); + $response->add_links( $this->prepare_links( $report ) ); + + /** + * Filter a report returned from the API. + * + * Allows modification of the report data right before it is returned. + * + * @param WP_REST_Response $response The response object. + * @param object $report The original report object. + * @param WP_REST_Request $request Request used to generate the response. + */ + return apply_filters( 'woocommerce_rest_prepare_report_categories', $response, $report, $request ); + } + + /** + * Prepare links for the request. + * + * @param WC_Admin_Reports_Query $object Object data. + * @return array + */ + protected function prepare_links( $object ) { + $links = array( + 'category' => array( + 'href' => rest_url( sprintf( '/%s/products/categories/%d', $this->namespace, $object['category_id'] ) ), + ), + ); + + return $links; + } + + /** + * Get the Report's schema, conforming to JSON Schema. + * + * @return array + */ + public function get_item_schema() { + $schema = array( + '$schema' => 'http://json-schema.org/draft-04/schema#', + 'title' => 'report_categories', + 'type' => 'object', + 'properties' => array( + 'category_id' => array( + 'description' => __( 'Category ID.', 'woocommerce' ), + 'type' => 'integer', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ), + 'items_sold' => array( + 'description' => __( 'Amount of items sold.', 'woocommerce' ), + 'type' => 'integer', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ), + 'gross_revenue' => array( + 'description' => __( 'Gross revenue.', 'woocommerce' ), + 'type' => 'number', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ), + 'orders_count' => array( + 'description' => __( 'Amount of orders.', 'woocommerce' ), + 'type' => 'integer', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ), + 'products_count' => array( + 'description' => __( 'Amount of products.', 'woocommerce' ), + 'type' => 'integer', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ), + ), + ); + + return $this->add_additional_fields_schema( $schema ); + } + + /** + * Get the query params for collections. + * + * @return array + */ + public function get_collection_params() { + $params = array(); + $params['context'] = $this->get_context_param( array( 'default' => 'view' ) ); + $params['page'] = array( + 'description' => __( 'Current page of the collection.', 'woocommerce' ), + 'type' => 'integer', + 'default' => 1, + 'sanitize_callback' => 'absint', + 'validate_callback' => 'rest_validate_request_arg', + 'minimum' => 1, + ); + $params['per_page'] = array( + 'description' => __( 'Maximum number of items to be returned in result set.', 'woocommerce' ), + 'type' => 'integer', + 'default' => 10, + 'minimum' => 1, + 'maximum' => 100, + 'sanitize_callback' => 'absint', + 'validate_callback' => 'rest_validate_request_arg', + ); + $params['after'] = array( + 'description' => __( 'Limit response to resources published after a given ISO8601 compliant date.', 'woocommerce' ), + 'type' => 'string', + 'format' => 'date-time', + 'validate_callback' => 'rest_validate_request_arg', + ); + $params['before'] = array( + 'description' => __( 'Limit response to resources published before a given ISO8601 compliant date.', 'woocommerce' ), + 'type' => 'string', + 'format' => 'date-time', + 'validate_callback' => 'rest_validate_request_arg', + ); + $params['order'] = array( + 'description' => __( 'Order sort attribute ascending or descending.', 'woocommerce' ), + 'type' => 'string', + 'default' => 'desc', + 'enum' => array( 'asc', 'desc' ), + 'validate_callback' => 'rest_validate_request_arg', + ); + $params['orderby'] = array( + 'description' => __( 'Sort collection by object attribute.', 'woocommerce' ), + 'type' => 'string', + 'default' => 'date', + 'enum' => array( + 'date', + 'items_sold', + 'gross_revenue', + 'orders_count', + 'products_count', + ), + 'validate_callback' => 'rest_validate_request_arg', + ); + $params['interval'] = array( + 'description' => __( 'Time interval to use for buckets in the returned data.', 'woocommerce' ), + 'type' => 'string', + 'default' => 'week', + 'enum' => array( + 'hour', + 'day', + 'week', + 'month', + 'quarter', + 'year', + ), + 'validate_callback' => 'rest_validate_request_arg', + ); + $params['categories'] = array( + 'description' => __( 'Limit result set to all items that have the specified term assigned in the categories taxonomy.', 'woocommerce' ), + 'type' => 'array', + 'sanitize_callback' => 'wp_parse_id_list', + 'validate_callback' => 'rest_validate_request_arg', + 'items' => array( + 'type' => 'integer', + ), + ); + + return $params; + } +} diff --git a/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-controller.php b/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-controller.php new file mode 100644 index 00000000000..f1b9382b943 --- /dev/null +++ b/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-controller.php @@ -0,0 +1,262 @@ + 'orders/count', + 'description' => __( 'Orders stats count.', 'woocommerce' ), + ); + $reports[] = array( + 'slug' => 'products/count', + 'description' => __( 'Customers stats count.', 'woocommerce' ), + ); + $reports[] = array( + 'slug' => 'customers/count', + 'description' => __( 'Customers stats count.', 'woocommerce' ), + ); + $reports[] = array( + 'slug' => 'coupons/count', + 'description' => __( 'Coupons stats count.', 'woocommerce' ), + ); + $reports[] = array( + 'slug' => 'reviews/count', + 'description' => __( 'Reviews stats count.', 'woocommerce' ), + ); + $reports[] = array( + 'slug' => 'categories/count', + 'description' => __( 'Categories stats count.', 'woocommerce' ), + ); + $reports[] = array( + 'slug' => 'tags/count', + 'description' => __( 'Tags stats count.', 'woocommerce' ), + ); + $reports[] = array( + 'slug' => 'attributes/count', + 'description' => __( 'Attributes stats count.', 'woocommerce' ), + ); + + return $reports; + } + + /** + * Register the routes for reports. + */ + public function register_routes() { + register_rest_route( $this->namespace, '/' . $this->rest_base, array( + array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => array( $this, 'get_items' ), + 'permission_callback' => array( $this, 'get_items_permissions_check' ), + 'args' => $this->get_collection_params(), + ), + 'schema' => array( $this, 'get_public_item_schema' ), + ) ); + } + + /** + * Check whether a given request has permission to read reports. + * + * @param WP_REST_Request $request Full details about the request. + * @return WP_Error|boolean + */ + public function get_items_permissions_check( $request ) { + if ( ! wc_rest_check_manager_permissions( 'reports', 'read' ) ) { + return new WP_Error( 'woocommerce_rest_cannot_view', __( 'Sorry, you cannot list resources.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) ); + } + + return true; + } + + /** + * Get all reports. + * + * @param WP_REST_Request $request Request data. + * @return array|WP_Error + */ + public function get_items( $request ) { + $data = array(); + $reports = array( + array( + 'slug' => 'revenue/stats', + 'description' => __( 'Stats about revenue.', 'woocommerce' ), + ), + array( + 'slug' => 'orders/stats', + 'description' => __( 'Stats about orders.', 'woocommerce' ), + ), + array( + 'slug' => 'products', + 'description' => __( 'Products detailed reports.', 'woocommerce' ), + ), + array( + 'slug' => 'products/stats', + 'description' => __( 'Stats about products.', 'woocommerce' ), + ), + array( + 'slug' => 'categories', + 'description' => __( 'Product categories detailed reports.', 'woocommerce' ), + ), + array( + 'slug' => 'categories/stats', + 'description' => __( 'Stats about product categories.', 'woocommerce' ), + ), + array( + 'slug' => 'coupons', + 'description' => __( 'Coupons detailed reports.', 'woocommerce' ), + ), + array( + 'slug' => 'coupons/stats', + 'description' => __( 'Stats about coupons.', 'woocommerce' ), + ), + array( + 'slug' => 'taxes', + 'description' => __( 'Taxes detailed reports.', 'woocommerce' ), + ), + array( + 'slug' => 'taxes/stats', + 'description' => __( 'Stats about taxes.', 'woocommerce' ), + ), + array( + 'slug' => 'downloads', + 'description' => __( 'Product downloads detailed reports.', 'woocommerce' ), + ), + array( + 'slug' => 'downloads/files', + 'description' => __( 'Product download files detailed reports.', 'woocommerce' ), + ), + array( + 'slug' => 'downloads/stats', + 'description' => __( 'Stats about product downloads.', 'woocommerce' ), + ), + array( + 'slug' => 'customers', + 'description' => __( 'Customers detailed reports.', 'woocommerce' ), + ), + ); + + foreach ( $reports as $report ) { + $item = $this->prepare_item_for_response( (object) $report, $request ); + $data[] = $this->prepare_response_for_collection( $item ); + } + + return rest_ensure_response( $data ); + } + + /** + * Prepare a report object for serialization. + * + * @param stdClass $report Report data. + * @param WP_REST_Request $request Request object. + * @return WP_REST_Response + */ + public function prepare_item_for_response( $report, $request ) { + $data = array( + 'slug' => $report->slug, + 'description' => $report->description, + ); + + $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; + $data = $this->add_additional_fields_to_object( $data, $request ); + $data = $this->filter_response_by_context( $data, $context ); + + // Wrap the data in a response object. + $response = rest_ensure_response( $data ); + $response->add_links( array( + 'self' => array( + 'href' => rest_url( sprintf( '/%s/%s/%s', $this->namespace, $this->rest_base, $report->slug ) ), + ), + 'collection' => array( + 'href' => rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ), + ), + ) ); + + /** + * Filter a report returned from the API. + * + * Allows modification of the report data right before it is returned. + * + * @param WP_REST_Response $response The response object. + * @param object $report The original report object. + * @param WP_REST_Request $request Request used to generate the response. + */ + return apply_filters( 'woocommerce_rest_prepare_report', $response, $report, $request ); + } + + /** + * Get the Report's schema, conforming to JSON Schema. + * + * @return array + */ + public function get_item_schema() { + $schema = array( + '$schema' => 'http://json-schema.org/draft-04/schema#', + 'title' => 'report', + 'type' => 'object', + 'properties' => array( + 'slug' => array( + 'description' => __( 'An alphanumeric identifier for the resource.', 'woocommerce' ), + 'type' => 'string', + 'context' => array( 'view' ), + 'readonly' => true, + ), + 'description' => array( + 'description' => __( 'A human-readable description of the resource.', 'woocommerce' ), + 'type' => 'string', + 'context' => array( 'view' ), + 'readonly' => true, + ), + ), + ); + + return $this->add_additional_fields_schema( $schema ); + } + + /** + * Get the query params for collections. + * + * @return array + */ + public function get_collection_params() { + return array( + 'context' => $this->get_context_param( array( 'default' => 'view' ) ), + ); + } +} diff --git a/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-coupons-controller.php b/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-coupons-controller.php new file mode 100644 index 00000000000..7059103bab8 --- /dev/null +++ b/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-coupons-controller.php @@ -0,0 +1,33 @@ +prepare_reports_query( $request ); + $orders_query = new WC_Reports_Orders_Stats_Query( $query_args ); + $report_data = $orders_query->get_data(); + + $out_data = array( + 'totals' => get_object_vars( $report_data->totals ), + 'intervals' => array(), + ); + + foreach ( $report_data->intervals as $interval_data ) { + $item = $this->prepare_item_for_response( $interval_data, $request ); + $out_data['intervals'][] = $this->prepare_response_for_collection( $item ); + } + + $response = rest_ensure_response( $out_data ); + $response->header( 'X-WP-Total', (int) $report_data->total ); + $response->header( 'X-WP-TotalPages', (int) $report_data->pages ); + + $page = $report_data->page_no; + $max_pages = $report_data->pages; + $base = add_query_arg( $request->get_query_params(), rest_url( sprintf( '/%s/%s', $this->namespace, $this->rest_base ) ) ); + if ( $page > 1 ) { + $prev_page = $page - 1; + if ( $prev_page > $max_pages ) { + $prev_page = $max_pages; + } + $prev_link = add_query_arg( 'page', $prev_page, $base ); + $response->link_header( 'prev', $prev_link ); + } + if ( $max_pages > $page ) { + $next_page = $page + 1; + $next_link = add_query_arg( 'page', $next_page, $base ); + $response->link_header( 'next', $next_link ); + } + + return $response; + } + + /** + * Prepare a report object for serialization. + * + * @param Array $report Report data. + * @param WP_REST_Request $request Request object. + * @return WP_REST_Response + */ + public function prepare_item_for_response( $report, $request ) { + $data = $report; + + $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; + $data = $this->add_additional_fields_to_object( $data, $request ); + $data = $this->filter_response_by_context( $data, $context ); + + // Wrap the data in a response object. + $response = rest_ensure_response( $data ); + + /** + * Filter a report returned from the API. + * + * Allows modification of the report data right before it is returned. + * + * @param WP_REST_Response $response The response object. + * @param object $report The original report object. + * @param WP_REST_Request $request Request used to generate the response. + */ + return apply_filters( 'woocommerce_rest_prepare_report_orders_stats', $response, $report, $request ); + } + + /** + * Get the Report's schema, conforming to JSON Schema. + * + * @return array + */ + public function get_item_schema() { + $totals = array( + 'net_revenue' => array( + 'description' => __( 'Net revenue.', 'woocommerce' ), + 'type' => 'number', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ), + 'avg_order_value' => array( + 'description' => __( 'Average order value.', 'woocommerce' ), + 'type' => 'number', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ), + 'orders_count' => array( + 'description' => __( 'Amount of orders', 'woocommerce' ), + 'type' => 'integer', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ), + 'avg_items_per_order' => array( + 'description' => __( 'Average items per order', 'woocommerce' ), + 'type' => 'integer', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ), + ); + + $schema = array( + '$schema' => 'http://json-schema.org/draft-04/schema#', + 'title' => 'report_orders_stats', + 'type' => 'object', + 'properties' => array( + 'totals' => array( + 'description' => __( 'Totals data.', 'woocommerce' ), + 'type' => 'object', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + 'properties' => $totals, + ), + 'intervals' => array( + 'description' => __( 'Reports data grouped by intervals.', 'woocommerce' ), + 'type' => 'array', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + 'items' => array( + 'type' => 'object', + 'properties' => array( + 'interval' => array( + 'description' => __( 'Type of interval.', 'woocommerce' ), + 'type' => 'string', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + 'enum' => array( 'day', 'week', 'month', 'year' ), + ), + 'date_start' => array( + 'description' => __( "The date the report start, in the site's timezone.", 'woocommerce' ), + 'type' => 'date-time', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ), + 'date_start_gmt' => array( + 'description' => __( 'The date the report start, as GMT.', 'woocommerce' ), + 'type' => 'date-time', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ), + 'date_end' => array( + 'description' => __( "The date the report end, in the site's timezone.", 'woocommerce' ), + 'type' => 'date-time', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ), + 'date_end_gmt' => array( + 'description' => __( 'The date the report end, as GMT.', 'woocommerce' ), + 'type' => 'date-time', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ), + 'subtotals' => array( + 'description' => __( 'Interval subtotals.', 'woocommerce' ), + 'type' => 'object', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + 'properties' => $totals, + ), + ), + ), + ), + ), + ); + + return $this->add_additional_fields_schema( $schema ); + } + + /** + * Get the query params for collections. + * + * @return array + */ + public function get_collection_params() { + $params = array(); + $params['context'] = $this->get_context_param( array( 'default' => 'view' ) ); + $params['page'] = array( + 'description' => __( 'Current page of the collection.', 'woocommerce' ), + 'type' => 'integer', + 'default' => 1, + 'sanitize_callback' => 'absint', + 'validate_callback' => 'rest_validate_request_arg', + 'minimum' => 1, + ); + $params['per_page'] = array( + 'description' => __( 'Maximum number of items to be returned in result set.', 'woocommerce' ), + 'type' => 'integer', + 'default' => 10, + 'minimum' => 1, + 'maximum' => 100, + 'sanitize_callback' => 'absint', + 'validate_callback' => 'rest_validate_request_arg', + ); + $params['after'] = array( + 'description' => __( 'Limit response to resources published after a given ISO8601 compliant date.', 'woocommerce' ), + 'type' => 'string', + 'format' => 'date-time', + 'validate_callback' => 'rest_validate_request_arg', + ); + $params['before'] = array( + 'description' => __( 'Limit response to resources published before a given ISO8601 compliant date.', 'woocommerce' ), + 'type' => 'string', + 'format' => 'date-time', + 'validate_callback' => 'rest_validate_request_arg', + ); + $params['order'] = array( + 'description' => __( 'Order sort attribute ascending or descending.', 'woocommerce' ), + 'type' => 'string', + 'default' => 'desc', + 'enum' => array( 'asc', 'desc' ), + 'validate_callback' => 'rest_validate_request_arg', + ); + $params['orderby'] = array( + 'description' => __( 'Sort collection by object attribute.', 'woocommerce' ), + 'type' => 'string', + 'default' => 'date', + 'enum' => array( + 'date', + 'net_revenue', + 'orders_count', + 'avg_order_value', + ), + 'validate_callback' => 'rest_validate_request_arg', + ); + $params['interval'] = array( + 'description' => __( 'Time interval to use for buckets in the returned data.', 'woocommerce' ), + 'type' => 'string', + 'default' => 'week', + 'enum' => array( + 'hour', + 'day', + 'week', + 'month', + 'quarter', + 'year', + ), + 'validate_callback' => 'rest_validate_request_arg', + ); + $params['categories'] = array( + 'description' => __( 'Limit result set to all items that have the specified term assigned in the categories taxonomy.', 'woocommerce' ), + 'type' => 'string', + 'sanitize_callback' => 'wp_parse_id_list', + 'validate_callback' => 'rest_validate_request_arg', + ); + $params['coupons'] = array( + 'description' => __( 'Limit result set to all items that have the specified coupon assigned.', 'woocommerce' ), + 'type' => 'string', + 'sanitize_callback' => 'wp_parse_id_list', + 'validate_callback' => 'rest_validate_request_arg', + ); + $params['products'] = array( + 'description' => __( 'Limit result set to all items that have the specified product assigned.', 'woocommerce' ), + 'type' => 'string', + 'sanitize_callback' => 'wp_parse_id_list', + 'validate_callback' => 'rest_validate_request_arg', + ); + $params['order_status'] = array( + 'default' => array( 'completed', 'processing', 'on-hold' ), + 'description' => __( 'Limit result set to orders assigned one or more statuses', 'woocommerce' ), + 'type' => 'array', + 'sanitize_callback' => 'wp_parse_slug_list', + 'validate_callback' => 'rest_validate_request_arg', + 'items' => array( + 'enum' => $this->get_order_statuses(), + 'type' => 'string', + ), + ); + + return $params; + } + + /** + * Get order statuses without prefixes. + * + * @return array + */ + protected function get_order_statuses() { + $order_statuses = array(); + + foreach ( array_keys( wc_get_order_statuses() ) as $status ) { + $order_statuses[] = str_replace( 'wc-', '', $status ); + } + + return $order_statuses; + } +} diff --git a/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-products-controller.php b/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-products-controller.php new file mode 100644 index 00000000000..caf9bbca9e1 --- /dev/null +++ b/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-products-controller.php @@ -0,0 +1,256 @@ +get_collection_params() ); + foreach ( $registered as $param_name ) { + if ( isset( $request[ $param_name ] ) ) { + $args[ $param_name ] = $request[ $param_name ]; + } + } + + $reports = new WC_Reports_Products_Query( $args ); + $products_data = $reports->get_data(); + + $data = array(); + + foreach ( $products_data->data as $product_data ) { + $item = $this->prepare_item_for_response( $product_data, $request ); + $data[] = $this->prepare_response_for_collection( $item ); + } + + $response = rest_ensure_response( $data ); + $response->header( 'X-WP-Total', (int) $products_data->total ); + $response->header( 'X-WP-TotalPages', (int) $products_data->pages ); + + $page = $products_data->page_no; + $max_pages = $products_data->pages; + $base = add_query_arg( $request->get_query_params(), rest_url( sprintf( '/%s/%s', $this->namespace, $this->rest_base ) ) ); + if ( $page > 1 ) { + $prev_page = $page - 1; + if ( $prev_page > $max_pages ) { + $prev_page = $max_pages; + } + $prev_link = add_query_arg( 'page', $prev_page, $base ); + $response->link_header( 'prev', $prev_link ); + } + if ( $max_pages > $page ) { + $next_page = $page + 1; + $next_link = add_query_arg( 'page', $next_page, $base ); + $response->link_header( 'next', $next_link ); + } + + return $response; + } + + /** + * Prepare a report object for serialization. + * + * @param Array $report Report data. + * @param WP_REST_Request $request Request object. + * @return WP_REST_Response + */ + public function prepare_item_for_response( $report, $request ) { + $data = $report; + + $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; + $data = $this->add_additional_fields_to_object( $data, $request ); + $data = $this->filter_response_by_context( $data, $context ); + + // Wrap the data in a response object. + $response = rest_ensure_response( $data ); + $response->add_links( $this->prepare_links( $report ) ); + + /** + * Filter a report returned from the API. + * + * Allows modification of the report data right before it is returned. + * + * @param WP_REST_Response $response The response object. + * @param object $report The original report object. + * @param WP_REST_Request $request Request used to generate the response. + */ + return apply_filters( 'woocommerce_rest_prepare_report_products', $response, $report, $request ); + } + + /** + * Prepare links for the request. + * + * @param Array $object Object data. + * @return array Links for the given post. + */ + protected function prepare_links( $object ) { + $links = array( + 'product' => array( + 'href' => rest_url( sprintf( '/%s/%s/%d', $this->namespace, 'products', $object['product_id'] ) ), + ), + ); + + return $links; + } + + /** + * Get the Report's schema, conforming to JSON Schema. + * + * @return array + */ + public function get_item_schema() { + $schema = array( + '$schema' => 'http://json-schema.org/draft-04/schema#', + 'title' => 'report_products', + 'type' => 'object', + 'properties' => array( + 'product_id' => array( + 'type' => 'integer', + 'readonly' => true, + 'context' => array( 'view', 'edit' ), + 'description' => __( 'Product ID.', 'woocommerce' ), + ), + 'items_sold' => array( + 'type' => 'integer', + 'readonly' => true, + 'context' => array( 'view', 'edit' ), + 'description' => __( 'Number of items sold.', 'woocommerce' ), + ), + 'gross_revenue' => array( + 'type' => 'number', + 'readonly' => true, + 'context' => array( 'view', 'edit' ), + 'description' => __( 'Total gross revenue of all items sold.', 'woocommerce' ), + ), + 'orders_count' => array( + 'type' => 'integer', + 'readonly' => true, + 'context' => array( 'view', 'edit' ), + 'description' => __( 'Number of orders product appeared in.', 'woocommerce' ), + ), + ), + ); + + return $this->add_additional_fields_schema( $schema ); + } + + /** + * Get the query params for collections. + * + * @return array + */ + public function get_collection_params() { + $params = array(); + $params['context'] = $this->get_context_param( array( 'default' => 'view' ) ); + $params['page'] = array( + 'description' => __( 'Current page of the collection.', 'woocommerce' ), + 'type' => 'integer', + 'default' => 1, + 'sanitize_callback' => 'absint', + 'validate_callback' => 'rest_validate_request_arg', + 'minimum' => 1, + ); + $params['per_page'] = array( + 'description' => __( 'Maximum number of items to be returned in result set.', 'woocommerce' ), + 'type' => 'integer', + 'default' => 10, + 'minimum' => 1, + 'maximum' => 100, + 'sanitize_callback' => 'absint', + 'validate_callback' => 'rest_validate_request_arg', + ); + $params['after'] = array( + 'description' => __( 'Limit response to resources published after a given ISO8601 compliant date.', 'woocommerce' ), + 'type' => 'string', + 'format' => 'date-time', + 'validate_callback' => 'rest_validate_request_arg', + ); + $params['before'] = array( + 'description' => __( 'Limit response to resources published before a given ISO8601 compliant date.', 'woocommerce' ), + 'type' => 'string', + 'format' => 'date-time', + 'validate_callback' => 'rest_validate_request_arg', + ); + $params['order'] = array( + 'description' => __( 'Order sort attribute ascending or descending.', 'woocommerce' ), + 'type' => 'string', + 'default' => 'desc', + 'enum' => array( 'asc', 'desc' ), + 'validate_callback' => 'rest_validate_request_arg', + ); + $params['orderby'] = array( + 'description' => __( 'Sort collection by object attribute.', 'woocommerce' ), + 'type' => 'string', + 'default' => 'date', + 'enum' => array( + 'date', + 'gross_revenue', + 'orders_count', + 'items_sold', + ), + 'validate_callback' => 'rest_validate_request_arg', + ); + $params['categories'] = array( + 'description' => __( 'Limit result to items from the specified categories.', 'woocommerce' ), + 'type' => 'array', + 'sanitize_callback' => 'wp_parse_id_list', + 'validate_callback' => 'rest_validate_request_arg', + 'items' => array( + 'type' => 'integer', + ), + ); + $params['products'] = array( + 'description' => __( 'Limit result to items with specified product ids.', 'woocommerce' ), + 'type' => 'array', + 'sanitize_callback' => 'wp_parse_id_list', + 'validate_callback' => 'rest_validate_request_arg', + 'items' => array( + 'type' => 'integer', + ), + ); + $params['extended_product_info'] = array( + 'description' => __( 'Add additional piece of info about each product to the report.', 'woocommerce' ), + 'type' => 'boolean', + 'default' => false, + 'sanitize_callback' => 'wc_string_to_bool', + 'validate_callback' => 'rest_validate_request_arg', + ); + + return $params; + } +} diff --git a/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-products-stats-controller.php b/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-products-stats-controller.php new file mode 100644 index 00000000000..e79bea86643 --- /dev/null +++ b/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-products-stats-controller.php @@ -0,0 +1,313 @@ + array( + 'items_sold', + 'gross_revenue', + 'orders_count', + 'products_count', + ), + ); + + foreach ( array_keys( $this->get_collection_params() ) as $arg ) { + if ( isset( $request[ $arg ] ) ) { + $query_args[ $arg ] = $request[ $arg ]; + } + } + + $query = new WC_Reports_Products_Stats_Query( $query_args ); + $report_data = $query->get_data(); + + $out_data = array( + 'totals' => get_object_vars( $report_data->totals ), + 'intervals' => array(), + ); + + foreach ( $report_data->intervals as $interval_data ) { + $item = $this->prepare_item_for_response( $interval_data, $request ); + $out_data['intervals'][] = $this->prepare_response_for_collection( $item ); + } + + $response = rest_ensure_response( $out_data ); + $response->header( 'X-WP-Total', (int) $report_data->total ); + $response->header( 'X-WP-TotalPages', (int) $report_data->pages ); + + $page = $report_data->page_no; + $max_pages = $report_data->pages; + $base = add_query_arg( $request->get_query_params(), rest_url( sprintf( '/%s/%s', $this->namespace, $this->rest_base ) ) ); + if ( $page > 1 ) { + $prev_page = $page - 1; + if ( $prev_page > $max_pages ) { + $prev_page = $max_pages; + } + $prev_link = add_query_arg( 'page', $prev_page, $base ); + $response->link_header( 'prev', $prev_link ); + } + if ( $max_pages > $page ) { + $next_page = $page + 1; + $next_link = add_query_arg( 'page', $next_page, $base ); + $response->link_header( 'next', $next_link ); + } + + return $response; + } + + /** + * Prepare a report object for serialization. + * + * @param Array $report Report data. + * @param WP_REST_Request $request Request object. + * @return WP_REST_Response + */ + public function prepare_item_for_response( $report, $request ) { + $data = $report; + + $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; + $data = $this->add_additional_fields_to_object( $data, $request ); + $data = $this->filter_response_by_context( $data, $context ); + + // Wrap the data in a response object. + $response = rest_ensure_response( $data ); + + /** + * Filter a report returned from the API. + * + * Allows modification of the report data right before it is returned. + * + * @param WP_REST_Response $response The response object. + * @param object $report The original report object. + * @param WP_REST_Request $request Request used to generate the response. + */ + return apply_filters( 'woocommerce_rest_prepare_report_products_stats', $response, $report, $request ); + } + + /** + * Get the Report's schema, conforming to JSON Schema. + * + * @return array + */ + public function get_item_schema() { + $totals = array( + 'items_sold' => array( + 'description' => __( 'Number of items sold.', 'woocommerce' ), + 'type' => 'integer', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ), + 'gross_revenue' => array( + 'description' => __( 'Gross revenue.', 'woocommerce' ), + 'type' => 'number', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ), + 'orders_count' => array( + 'description' => __( 'Number of orders.', 'woocommerce' ), + 'type' => 'integer', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ), + ); + + $schema = array( + '$schema' => 'http://json-schema.org/draft-04/schema#', + 'title' => 'report_products_stats', + 'type' => 'object', + 'properties' => array( + 'totals' => array( + 'description' => __( 'Totals data.', 'woocommerce' ), + 'type' => 'object', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + 'properties' => $totals, + ), + 'intervals' => array( + 'description' => __( 'Reports data grouped by intervals.', 'woocommerce' ), + 'type' => 'array', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + 'items' => array( + 'type' => 'object', + 'properties' => array( + 'interval' => array( + 'description' => __( 'Type of interval.', 'woocommerce' ), + 'type' => 'string', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + 'enum' => array( 'day', 'week', 'month', 'year' ), + ), + 'date_start' => array( + 'description' => __( "The date the report start, in the site's timezone.", 'woocommerce' ), + 'type' => 'date-time', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ), + 'date_start_gmt' => array( + 'description' => __( 'The date the report start, as GMT.', 'woocommerce' ), + 'type' => 'date-time', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ), + 'date_end' => array( + 'description' => __( "The date the report end, in the site's timezone.", 'woocommerce' ), + 'type' => 'date-time', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ), + 'date_end_gmt' => array( + 'description' => __( 'The date the report end, as GMT.', 'woocommerce' ), + 'type' => 'date-time', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ), + 'subtotals' => array( + 'description' => __( 'Interval subtotals.', 'woocommerce' ), + 'type' => 'object', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + 'properties' => $totals, + ), + ), + ), + ), + ), + ); + + return $this->add_additional_fields_schema( $schema ); + } + + /** + * Get the query params for collections. + * + * @return array + */ + public function get_collection_params() { + $params = array(); + $params['context'] = $this->get_context_param( array( 'default' => 'view' ) ); + $params['page'] = array( + 'description' => __( 'Current page of the collection.', 'woocommerce' ), + 'type' => 'integer', + 'default' => 1, + 'sanitize_callback' => 'absint', + 'validate_callback' => 'rest_validate_request_arg', + 'minimum' => 1, + ); + $params['per_page'] = array( + 'description' => __( 'Maximum number of items to be returned in result set.', 'woocommerce' ), + 'type' => 'integer', + 'default' => 10, + 'minimum' => 1, + 'maximum' => 100, + 'sanitize_callback' => 'absint', + 'validate_callback' => 'rest_validate_request_arg', + ); + $params['after'] = array( + 'description' => __( 'Limit response to resources published after a given ISO8601 compliant date.', 'woocommerce' ), + 'type' => 'string', + 'format' => 'date-time', + 'validate_callback' => 'rest_validate_request_arg', + ); + $params['before'] = array( + 'description' => __( 'Limit response to resources published before a given ISO8601 compliant date.', 'woocommerce' ), + 'type' => 'string', + 'format' => 'date-time', + 'validate_callback' => 'rest_validate_request_arg', + ); + $params['order'] = array( + 'description' => __( 'Order sort attribute ascending or descending.', 'woocommerce' ), + 'type' => 'string', + 'default' => 'desc', + 'enum' => array( 'asc', 'desc' ), + 'validate_callback' => 'rest_validate_request_arg', + ); + $params['orderby'] = array( + 'description' => __( 'Sort collection by object attribute.', 'woocommerce' ), + 'type' => 'string', + 'default' => 'date', + 'enum' => array( + 'date', + 'gross_revenue', + 'coupons', + 'refunds', + 'shipping', + 'taxes', + 'net_revenue', + 'orders_count', + 'items_sold', + ), + 'validate_callback' => 'rest_validate_request_arg', + ); + $params['interval'] = array( + 'description' => __( 'Time interval to use for buckets in the returned data.', 'woocommerce' ), + 'type' => 'string', + 'default' => 'week', + 'enum' => array( + 'hour', + 'day', + 'week', + 'month', + 'quarter', + 'year', + ), + 'validate_callback' => 'rest_validate_request_arg', + ); + $params['categories'] = array( + 'description' => __( 'Limit result to items from the specified categories.', 'woocommerce' ), + 'type' => 'array', + 'sanitize_callback' => 'wp_parse_id_list', + 'validate_callback' => 'rest_validate_request_arg', + 'items' => array( + 'type' => 'integer', + ), + ); + $params['products'] = array( + 'description' => __( 'Limit result to items with specified product ids.', 'woocommerce' ), + 'type' => 'array', + 'sanitize_callback' => 'wp_parse_id_list', + 'validate_callback' => 'rest_validate_request_arg', + 'items' => array( + 'type' => 'integer', + ), + ); + + return $params; + } +} diff --git a/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-revenue-stats-controller.php b/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-revenue-stats-controller.php new file mode 100644 index 00000000000..7dbbe000a63 --- /dev/null +++ b/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-revenue-stats-controller.php @@ -0,0 +1,324 @@ +prepare_reports_query( $request ); + $reports_revenue = new WC_Reports_Revenue_Query( $query_args ); + $report_data = $reports_revenue->get_data(); + + $out_data = array( + 'totals' => get_object_vars( $report_data->totals ), + 'intervals' => array(), + ); + + foreach ( $report_data->intervals as $interval_data ) { + $item = $this->prepare_item_for_response( $interval_data, $request ); + $out_data['intervals'][] = $this->prepare_response_for_collection( $item ); + } + + $response = rest_ensure_response( $out_data ); + $response->header( 'X-WP-Total', (int) $report_data->total ); + $response->header( 'X-WP-TotalPages', (int) $report_data->pages ); + + $page = $report_data->page_no; + $max_pages = $report_data->pages; + $base = add_query_arg( $request->get_query_params(), rest_url( sprintf( '/%s/%s', $this->namespace, $this->rest_base ) ) ); + if ( $page > 1 ) { + $prev_page = $page - 1; + if ( $prev_page > $max_pages ) { + $prev_page = $max_pages; + } + $prev_link = add_query_arg( 'page', $prev_page, $base ); + $response->link_header( 'prev', $prev_link ); + } + if ( $max_pages > $page ) { + $next_page = $page + 1; + $next_link = add_query_arg( 'page', $next_page, $base ); + $response->link_header( 'next', $next_link ); + } + + return $response; + } + + /** + * Prepare a report object for serialization. + * + * @param Array $report Report data. + * @param WP_REST_Request $request Request object. + * @return WP_REST_Response + */ + public function prepare_item_for_response( $report, $request ) { + $data = $report; + + $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; + $data = $this->add_additional_fields_to_object( $data, $request ); + $data = $this->filter_response_by_context( $data, $context ); + + // Wrap the data in a response object. + $response = rest_ensure_response( $data ); + + /** + * Filter a report returned from the API. + * + * Allows modification of the report data right before it is returned. + * + * @param WP_REST_Response $response The response object. + * @param object $report The original report object. + * @param WP_REST_Request $request Request used to generate the response. + */ + return apply_filters( 'woocommerce_rest_prepare_report_revenue_stats', $response, $report, $request ); + } + + /** + * Get the Report's schema, conforming to JSON Schema. + * + * @return array + */ + public function get_item_schema() { + $totals = array( + 'gross_revenue' => array( + 'description' => __( 'Gross revenue.', 'woocommerce' ), + 'type' => 'number', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ), + 'net_revenue' => array( + 'description' => __( 'Net revenue.', 'woocommerce' ), + 'type' => 'number', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ), + 'coupons' => array( + 'description' => __( 'Total of coupons.', 'woocommerce' ), + 'type' => 'number', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ), + 'shipping' => array( + 'description' => __( 'Total of shipping.', 'woocommerce' ), + 'type' => 'number', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ), + 'taxes' => array( + 'description' => __( 'Total of taxes.', 'woocommerce' ), + 'type' => 'number', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ), + 'refunds' => array( + 'description' => __( 'Total of refunds.', 'woocommerce' ), + 'type' => 'number', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ), + 'orders_count' => array( + 'description' => __( 'Amount of orders', 'woocommerce' ), + 'type' => 'integer', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ), + ); + + $schema = array( + '$schema' => 'http://json-schema.org/draft-04/schema#', + 'title' => 'report_revenue_stats', + 'type' => 'object', + 'properties' => array( + 'totals' => array( + 'description' => __( 'Totals data.', 'woocommerce' ), + 'type' => 'object', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + 'properties' => $totals, + ), + 'intervals' => array( + 'description' => __( 'Reports data grouped by intervals.', 'woocommerce' ), + 'type' => 'array', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + 'items' => array( + 'type' => 'object', + 'properties' => array( + 'interval' => array( + 'description' => __( 'Type of interval.', 'woocommerce' ), + 'type' => 'string', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + 'enum' => array( 'day', 'week', 'month', 'year' ), + ), + 'date_start' => array( + 'description' => __( "The date the report start, in the site's timezone.", 'woocommerce' ), + 'type' => 'date-time', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ), + 'date_start_gmt' => array( + 'description' => __( 'The date the report start, as GMT.', 'woocommerce' ), + 'type' => 'date-time', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ), + 'date_end' => array( + 'description' => __( "The date the report end, in the site's timezone.", 'woocommerce' ), + 'type' => 'date-time', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ), + 'date_end_gmt' => array( + 'description' => __( 'The date the report end, as GMT.', 'woocommerce' ), + 'type' => 'date-time', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ), + 'subtotals' => array( + 'description' => __( 'Interval subtotals.', 'woocommerce' ), + 'type' => 'object', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + 'properties' => $totals, + ), + ), + ), + ), + ), + ); + + return $this->add_additional_fields_schema( $schema ); + } + + /** + * Get the query params for collections. + * + * @return array + */ + public function get_collection_params() { + $params = array(); + $params['context'] = $this->get_context_param( array( 'default' => 'view' ) ); + $params['page'] = array( + 'description' => __( 'Current page of the collection.', 'woocommerce' ), + 'type' => 'integer', + 'default' => 1, + 'sanitize_callback' => 'absint', + 'validate_callback' => 'rest_validate_request_arg', + 'minimum' => 1, + ); + $params['per_page'] = array( + 'description' => __( 'Maximum number of items to be returned in result set.', 'woocommerce' ), + 'type' => 'integer', + 'default' => 10, + 'minimum' => 1, + 'maximum' => 100, + 'sanitize_callback' => 'absint', + 'validate_callback' => 'rest_validate_request_arg', + ); + $params['after'] = array( + 'description' => __( 'Limit response to resources published after a given ISO8601 compliant date.', 'woocommerce' ), + 'type' => 'string', + 'format' => 'date-time', + 'validate_callback' => 'rest_validate_request_arg', + ); + $params['before'] = array( + 'description' => __( 'Limit response to resources published before a given ISO8601 compliant date.', 'woocommerce' ), + 'type' => 'string', + 'format' => 'date-time', + 'validate_callback' => 'rest_validate_request_arg', + ); + $params['order'] = array( + 'description' => __( 'Order sort attribute ascending or descending.', 'woocommerce' ), + 'type' => 'string', + 'default' => 'desc', + 'enum' => array( 'asc', 'desc' ), + 'validate_callback' => 'rest_validate_request_arg', + ); + $params['orderby'] = array( + 'description' => __( 'Sort collection by object attribute.', 'woocommerce' ), + 'type' => 'string', + 'default' => 'date', + 'enum' => array( + 'date', + 'gross_revenue', + 'coupons', + 'refunds', + 'shipping', + 'taxes', + 'net_revenue', + 'orders_count', + 'items_sold', + ), + 'validate_callback' => 'rest_validate_request_arg', + ); + $params['interval'] = array( + 'description' => __( 'Time interval to use for buckets in the returned data.', 'woocommerce' ), + 'type' => 'string', + 'default' => 'week', + 'enum' => array( + 'hour', + 'day', + 'week', + 'month', + 'quarter', + 'year', + ), + 'validate_callback' => 'rest_validate_request_arg', + ); + + return $params; + } +} diff --git a/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-taxes-controller.php b/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-taxes-controller.php new file mode 100644 index 00000000000..e18d72d0fde --- /dev/null +++ b/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-taxes-controller.php @@ -0,0 +1,34 @@ + array( + 'name' => __( 'Rebuild reports data', 'woocommerce' ), + 'button' => __( 'Rebuild reports', 'woocommerce' ), + 'desc' => __( 'This tool will rebuild all of the information used by the reports.', 'woocommerce' ), + ), + ) + ); + } + + /** + * Actually executes a tool. + * + * @param string $tool Tool. + * @return array + */ + public function execute_tool( $tool ) { + $ran = true; + $message = ''; + + switch ( $tool ) { + case 'rebuild_stats': + WC_Reports_Orders_Data_Store::queue_order_stats_repopulate_database(); + $message = __( 'Rebuilding reports data in the background . . .', 'woocommerce' ); + break; + default: + return parent::execute_tool( $tool ); + } + + return array( + 'success' => $ran, + 'message' => $message, + ); + } +} diff --git a/plugins/woocommerce-admin/includes/class-wc-admin-api-init.php b/plugins/woocommerce-admin/includes/class-wc-admin-api-init.php new file mode 100644 index 00000000000..e0254a6f7f7 --- /dev/null +++ b/plugins/woocommerce-admin/includes/class-wc-admin-api-init.php @@ -0,0 +1,199 @@ +create_db_tables(); + } + + public function init_classes() { + // Interfaces. + require_once( dirname( __FILE__ ) . '/interfaces/class-wc-admin-reports-data-store-interface.php' ); + + // Query classes for reports. + require_once( dirname( __FILE__ ) . '/class-wc-admin-reports-revenue-query.php' ); + require_once( dirname( __FILE__ ) . '/class-wc-admin-reports-orders-stats-query.php' ); + require_once( dirname( __FILE__ ) . '/class-wc-admin-reports-products-query.php' ); + require_once( dirname( __FILE__ ) . '/class-wc-admin-reports-products-stats-query.php' ); + require_once( dirname( __FILE__ ) . '/class-wc-admin-reports-categories-query.php' ); + + // Reports data stores. + require_once( dirname( __FILE__ ) . '/data-stores/class-wc-admin-reports-data-store.php' ); + require_once( dirname( __FILE__ ) . '/data-stores/class-wc-admin-reports-orders-data-store.php' ); + require_once( dirname( __FILE__ ) . '/data-stores/class-wc-admin-reports-products-data-store.php' ); + require_once( dirname( __FILE__ ) . '/data-stores/class-wc-admin-reports-products-stats-data-store.php' ); + require_once( dirname( __FILE__ ) . '/data-stores/class-wc-admin-reports-categories-data-store.php' ); + } + + public function rest_api_init() { + require_once( dirname( __FILE__ ) . '/api/class-wc-admin-rest-reports-controller.php' ); + require_once( dirname( __FILE__ ) . '/api/class-wc-admin-rest-system-status-tools-controller.php' ); + require_once( dirname( __FILE__ ) . '/api/class-wc-admin-rest-reports-categories-controller.php' ); + require_once( dirname( __FILE__ ) . '/api/class-wc-admin-rest-reports-coupons-controller.php' ); + require_once( dirname( __FILE__ ) . '/api/class-wc-admin-rest-reports-coupons-stats-controller.php' ); + require_once( dirname( __FILE__ ) . '/api/class-wc-admin-rest-reports-customers-controller.php' ); + require_once( dirname( __FILE__ ) . '/api/class-wc-admin-rest-reports-downloads-controller.php' ); + require_once( dirname( __FILE__ ) . '/api/class-wc-admin-rest-reports-downloads-files-controller.php' ); + require_once( dirname( __FILE__ ) . '/api/class-wc-admin-rest-reports-downloads-stats-controller.php' ); + require_once( dirname( __FILE__ ) . '/api/class-wc-admin-rest-reports-orders-stats-controller.php' ); + require_once( dirname( __FILE__ ) . '/api/class-wc-admin-rest-reports-products-controller.php' ); + require_once( dirname( __FILE__ ) . '/api/class-wc-admin-rest-reports-products-stats-controller.php' ); + require_once( dirname( __FILE__ ) . '/api/class-wc-admin-rest-reports-revenue-stats-controller.php' ); + require_once( dirname( __FILE__ ) . '/api/class-wc-admin-rest-reports-taxes-controller.php' ); + require_once( dirname( __FILE__ ) . '/api/class-wc-admin-rest-reports-taxes-stats-controller.php' ); + + $controllers = array( + 'WC_Admin_REST_Reports_Controller', + 'WC_Admin_REST_System_Status_Tools_Controller', + 'WC_Admin_REST_Reports_Products_Controller', + 'WC_Admin_REST_Reports_Products_Stats_Controller', + 'WC_Admin_REST_Reports_Revenue_Stats_Controller', + 'WC_Admin_REST_Reports_Orders_Stats_Controller', + 'WC_Admin_REST_Reports_Categories_Controller', + ); + + foreach ( $controllers as $controller ) { + $this->$controller = new $controller(); + $this->$controller->register_routes(); + } + } + + public static function orders_data_store_init() { + WC_Admin_Reports_Orders_Data_Store::init(); + } + + public static function order_product_lookup_store_init() { + global $wpdb; + + $orders = get_transient( 'wc_update_350_all_orders' ); + if ( false === $orders ) { + $orders = wc_get_orders( array( + 'limit' => -1, + 'return' => 'ids', + ) ); + set_transient( 'wc_update_350_all_orders', $orders, DAY_IN_SECONDS ); + } + + // Process orders untill close to running out of memory timeouts on large sites then requeue. + foreach ( $orders as $order_id ) { + $order = wc_get_order( $order_id ); + if ( ! $order ) { + continue; + } + foreach ( $order->get_items() as $order_item ) { + $wpdb->replace( + $wpdb->prefix . 'wc_order_product_lookup', + array( + 'order_item_id' => $order_item->get_id(), + 'order_id' => $order->get_id(), + 'product_id' => $order_item->get_product_id( 'edit' ), + 'customer_id' => ( 0 < $order->get_customer_id( 'edit' ) ) ? $order->get_customer_id( 'edit' ) : null, + 'product_qty' => $order_item->get_quantity( 'edit' ), + 'product_gross_revenue' => $order_item->get_subtotal( 'edit' ), + 'date_created' => date( 'Y-m-d H:i:s', $order->get_date_created( 'edit' )->getTimestamp() ), + ), + array( + '%d', + '%d', + '%d', + '%d', + '%d', + '%f', + '%s', + ) + ); + } + // Pop the order ID from the array for updating the transient later should we near memory exhaustion. + unset( $orders[ $order_id ] ); + if ( $updater instanceof WC_Background_Updater && $updater->is_memory_exceeded() ) { + // Update the transient for the next run to avoid processing the same orders again. + set_transient( 'wc_update_350_all_orders', $orders, DAY_IN_SECONDS ); + return true; + } + } + } + + public static function add_data_stores( $data_stores ) { + return array_merge( + $data_stores, + array( + 'report-revenue-stats' => 'WC_Reports_Orders_Data_Store', + 'report-orders-stats' => 'WC_Reports_Orders_Data_Store', + 'report-products' => 'WC_Reports_Products_Data_Store', + 'report-products-stats' => 'WC_Reports_Products_Stats_Data_Store', + 'report-categories' => 'WC_Reports_Categories_Data_Store', + ) + ); + } + + public static function add_report_tables( $wc_tables ) { + return array_merge( + $wc_tables, + array( + // TODO: will this work on multisite? + "{$wpdb->prefix}wc_admin_order_stats", + "{$wpdb->prefix}wc_admin_order_product_lookup", + ) + ); + } + + private static function get_schema() { + global $wpdb; + + if ( $wpdb->has_cap( 'collation' ) ) { + $collate = $wpdb->get_charset_collate(); + } + + $tables = " + CREATE TABLE {$wpdb->prefix}wc_admin_order_stats ( + order_id bigint(20) unsigned NOT NULL, + date_created timestamp DEFAULT '0000-00-00 00:00:00' NOT NULL, + num_items_sold int(11) UNSIGNED DEFAULT 0 NOT NULL, + gross_total double DEFAULT 0 NOT NULL, + coupon_total double DEFAULT 0 NOT NULL, + refund_total double DEFAULT 0 NOT NULL, + tax_total double DEFAULT 0 NOT NULL, + shipping_total double DEFAULT 0 NOT NULL, + net_total double DEFAULT 0 NOT NULL, + PRIMARY KEY (order_id), + KEY date_created (date_created) + ) $collate; + CREATE TABLE {$wpdb->prefix}wc_admin_order_product_lookup ( + order_item_id BIGINT UNSIGNED NOT NULL, + order_id BIGINT UNSIGNED NOT NULL, + product_id BIGINT UNSIGNED NOT NULL, + customer_id BIGINT UNSIGNED NULL, + date_created timestamp DEFAULT '0000-00-00 00:00:00' NOT NULL, + product_qty INT UNSIGNED NOT NULL, + product_gross_revenue double DEFAULT 0 NOT NULL, + PRIMARY KEY (order_item_id), + KEY order_id (order_id), + KEY product_id (product_id), + KEY customer_id (customer_id), + KEY date_created (date_created) + ) $collate;"; + } + + protected function create_db_tables() { + require_once(ABSPATH . 'wp-admin/includes/upgrade.php'); + + dbDelta( self::get_schema() ); + } + +} + +new WC_Admin_Api_Init(); diff --git a/plugins/woocommerce-admin/includes/class-wc-admin-order-stats-background-process.php b/plugins/woocommerce-admin/includes/class-wc-admin-order-stats-background-process.php new file mode 100644 index 00000000000..c7daf2e8e4a --- /dev/null +++ b/plugins/woocommerce-admin/includes/class-wc-admin-order-stats-background-process.php @@ -0,0 +1,77 @@ +prefix = 'wp_' . get_current_blog_id(); + $this->action = 'wc_order_stats'; + parent::__construct(); + } + + /** + * Push to queue without scheduling duplicate recalculation events. + * Overrides WC_Background_Process::push_to_queue. + * + * @param integer $data Timestamp of hour to generate stats. + */ + public function push_to_queue( $data ) { + $data = absint( $data ); + if ( ! in_array( $data, $this->data, true ) ) { + $this->data[] = $data; + } + + return $this; + } + + /** + * Dispatch but only if there is data to update. + * Overrides WC_Background_Process::dispatch. + */ + public function dispatch() { + if ( ! $this->data ) { + return false; + } + + return parent::dispatch(); + } + + /** + * Code to execute for each item in the queue + * + * @param string $item Queue item to iterate over. + * @return bool + */ + protected function task( $item ) { + if ( ! $item ) { + return false; + } + + $order = wc_get_order( $item ); + if ( ! $order ) { + return false; + } + + WC_Admin_Reports_Orders_Data_Store::update( $order ); + return false; + } +} diff --git a/plugins/woocommerce-admin/includes/class-wc-admin-reports-categories-query.php b/plugins/woocommerce-admin/includes/class-wc-admin-reports-categories-query.php new file mode 100644 index 00000000000..8a55c8c0d93 --- /dev/null +++ b/plugins/woocommerce-admin/includes/class-wc-admin-reports-categories-query.php @@ -0,0 +1,49 @@ + '2018-07-19 00:00:00', + * 'after' => '2018-07-05 00:00:00', + * 'page' => 2, + * 'order' => 'desc', + * 'orderby' => 'items_sold', + * ); + * $report = new WC_Admin_Reports_Categories_Query( $args ); + * $mydata = $report->get_data(); + * + * @package WooCommerce Admin/Classes + */ + +defined( 'ABSPATH' ) || exit; + +/** + * WC_Admin_Reports_Categories_Query + * + */ +class WC_Admin_Reports_Categories_Query extends WC_Admin_Reports_Query { + + const REPORT_NAME = 'report-categories'; + + /** + * Valid fields for Categories report. + * + * @return array + */ + protected function get_default_query_vars() { + return array(); + } + + /** + * Get categories data based on the current query vars. + * + * @return array + */ + public function get_data() { + $args = apply_filters( 'woocommerce_reports_categories_query_args', $this->get_query_vars() ); + $results = WC_Data_Store::load( self::REPORT_NAME )->get_data( $args ); + return apply_filters( 'woocommerce_reports_categories_select_query', $results, $args ); + } + +} diff --git a/plugins/woocommerce-admin/includes/class-wc-admin-reports-interval.php b/plugins/woocommerce-admin/includes/class-wc-admin-reports-interval.php new file mode 100644 index 00000000000..b770e808066 --- /dev/null +++ b/plugins/woocommerce-admin/includes/class-wc-admin-reports-interval.php @@ -0,0 +1,413 @@ + "DATE_FORMAT(date_created, '%Y-%m-%d %k')", + 'day' => "DATE_FORMAT(date_created, '%Y-%m-%d')", + 'week' => $week_format, + 'month' => "DATE_FORMAT(date_created, '%Y-%m')", + 'quarter' => "CONCAT(YEAR(date_created), '-', QUARTER(date_created))", + 'year' => 'YEAR(date_created)', + + ); + + return $mysql_date_format_mapping[ $time_interval ]; + } + + /** + * Returns quarter for the DateTime. + * + * @param DateTime $datetime Date & time. + * @return int|null + */ + public static function quarter( $datetime ) { + // TODO: is there a smarter way? Is floor((m - 1)/3) + 1 better? + switch ( (int) $datetime->format( 'm' ) ) { + case 1: + case 2: + case 3: + return 1; + case 4: + case 5: + case 6: + return 2; + case 7: + case 8: + case 9: + return 3; + case 10: + case 11: + case 12: + return 4; + + } + return null; + } + + /** + * Returns simple week number for the DateTime, for week starting on $first_day_of_week. + * + * The first week of the year is considered to be the week containing January 1. + * The second week starts on the next $first_day_of_week. + * + * @param DateTime $datetime Date for which the week number is to be calculated. + * @param int $first_day_of_week 0 for Sunday to 6 for Saturday. + * @return int + */ + public static function simple_week_number( $datetime, $first_day_of_week ) { + $beg_of_year_day = new DateTime( "{$datetime->format('Y')}-01-01" ); + $adj_day_beg_of_year = ( (int) $beg_of_year_day->format( 'w' ) - $first_day_of_week + 7 ) % 7; + $days_since_start_of_year = (int) $datetime->format( 'z' ) + 1; + + return (int) floor( ( ( $days_since_start_of_year + $adj_day_beg_of_year - 1 ) / 7 ) ) + 1; + } + + /** + * Returns ISO 8601 week number for the DateTime, if week starts on Monday, + * otherwise returns simple week number. + * + * @see WC_Admin_Reports_Interval::simple_week_number() + * + * @param DateTime $datetime Date for which the week number is to be calculated. + * @param int $first_day_of_week 0 for Sunday to 6 for Saturday. + * @return int + */ + public static function week_number( $datetime, $first_day_of_week ) { + if ( 1 === $first_day_of_week ) { + $week_number = (int) $datetime->format( 'W' ); + } else { + $week_number = WC_Admin_Reports_Interval::simple_week_number( $datetime, $first_day_of_week ); + } + return $week_number; + } + + /** + * Returns time interval id for the DateTime. + * + * @param string $time_interval Time interval type (week, day, etc). + * @param DateTime $datetime Date & time. + * @return string + */ + public static function time_interval_id( $time_interval, $datetime ) { + $php_time_format_for = array( + 'hour' => 'Y-m-d H', + 'day' => 'Y-m-d', + 'week' => 'o-W', + 'month' => 'Y-m', + 'quarter' => 'Y-' . WC_Admin_Reports_Interval::quarter( $datetime ), + 'year' => 'Y', + ); + + // If the week does not begin on Monday. + $first_day_of_week = absint( get_option( 'start_of_week' ) ); + + if ( 'week' === $time_interval && 1 !== $first_day_of_week ) { + $week_no = WC_Admin_Reports_Interval::simple_week_number( $datetime, $first_day_of_week ); + $week_no = str_pad( $week_no, 2, '0', STR_PAD_LEFT ); + $year_no = $datetime->format( 'Y' ); + return "$year_no-$week_no"; + } + + return $datetime->format( $php_time_format_for[ $time_interval ] ); + } + + /** + * Calculates number of time intervals between two dates, closed interval on both sides. + * + * @param string $start Start date & time. + * @param string $end End date & time. + * @param string $interval Time interval increment, e.g. hour, day, week. + * @return int + */ + public static function intervals_between( $start, $end, $interval ) { + $start_datetime = new DateTime( $start ); + $end_datetime = new DateTime( $end ); + + switch ( $interval ) { + case 'hour': + $diff_timestamp = (int) $end_datetime->format( 'U' ) - (int) $start_datetime->format( 'U' ); + return (int) floor( ( (int) $diff_timestamp ) / HOUR_IN_SECONDS ) + 1; + case 'day': + $diff_timestamp = (int) $end_datetime->format( 'U' ) - (int) $start_datetime->format( 'U' ); + return (int) floor( ( (int) $diff_timestamp ) / DAY_IN_SECONDS ) + 1; + case 'week': + // TODO: optimize? approximately day count / 7, but year end is tricky, a week can have fewer days. + $week_count = 0; + do { + $start_datetime = WC_Admin_Reports_Interval::next_week_start( $start_datetime ); + $week_count++; + } while ( $start_datetime <= $end_datetime ); + return $week_count; + case 'month': + // Year diff in months: (end_year - start_year - 1) * 12. + $year_diff_in_months = ( (int) $end_datetime->format( 'Y' ) - (int) $start_datetime->format( 'Y' ) - 1 ) * 12; + // All the months in end_date year plus months from X to 12 in the start_date year. + $month_diff = (int) $end_datetime->format( 'n' ) + ( 12 - (int) $start_datetime->format( 'n' ) ); + // Add months for number of years between end_date and start_date. + $month_diff += $year_diff_in_months + 1; + return $month_diff; + case 'quarter': + // Year diff in quarters: (end_year - start_year - 1) * 4. + $year_diff_in_quarters = ( (int) $end_datetime->format( 'Y' ) - (int) $start_datetime->format( 'Y' ) - 1 ) * 4; + // All the quarters in end_date year plus quarters from X to 4 in the start_date year. + $quarter_diff = WC_Admin_Reports_Interval::quarter( $end_datetime ) + ( 4 - WC_Admin_Reports_Interval::quarter( $start_datetime ) ); + // Add quarters for number of years between end_date and start_date. + $quarter_diff += $year_diff_in_quarters + 1; + return $quarter_diff; + case 'year': + $year_diff = (int) $end_datetime->format( 'Y' ) - (int) $start_datetime->format( 'Y' ); + return $year_diff + 1; + } + return 0; + } + + /** + * Returns a new DateTime object representing the next hour start/previous hour end if reversed. + * + * @param DateTime $datetime Date and time. + * @param bool $reversed Going backwards in time instead of forward. + * @return DateTime + */ + public static function next_hour_start( $datetime, $reversed = false ) { + $hour_increment = $reversed ? 0 : 1; + $timestamp = (int) $datetime->format( 'U' ); + $hours_offset_timestamp = ( floor( $timestamp / HOUR_IN_SECONDS ) + $hour_increment ) * HOUR_IN_SECONDS; + + if ( $reversed ) { + $hours_offset_timestamp --; + } + + $hours_offset_time = new DateTime(); + $hours_offset_time->setTimestamp( $hours_offset_timestamp ); + return $hours_offset_time; + } + + /** + * Returns a new DateTime object representing the next day start, or previous day end if reversed. + * + * @param DateTime $datetime Date and time. + * @param bool $reversed Going backwards in time instead of forward. + * @return DateTime + */ + public static function next_day_start( $datetime, $reversed = false ) { + $day_increment = $reversed ? -1 : 1; + $timestamp = (int) $datetime->format( 'U' ); + $next_day_timestamp = ( floor( $timestamp / DAY_IN_SECONDS ) + $day_increment ) * DAY_IN_SECONDS; + $next_day = new DateTime(); + $next_day->setTimestamp( $next_day_timestamp ); + + // The day boundary is actually next midnight when going in reverse, so set it to day -1 at 23:59:59. + if ( $reversed ) { + $timestamp = (int) $next_day->format( 'U' ); + $end_of_day_timestamp = floor( $timestamp / DAY_IN_SECONDS ) * DAY_IN_SECONDS + DAY_IN_SECONDS - 1; + $next_day->setTimestamp( $end_of_day_timestamp ); + } + + return $next_day; + } + + /** + * Returns DateTime object representing the next week start, or previous week end if reversed. + * + * @param DateTime $datetime Date and time. + * @param bool $reversed Going backwards in time instead of forward. + * @return DateTime + */ + public static function next_week_start( $datetime, $reversed = false ) { + $first_day_of_week = absint( get_option( 'start_of_week' ) ); + $initial_week_no = WC_Admin_Reports_Interval::week_number( $datetime, $first_day_of_week ); + + do { + $datetime = WC_Admin_Reports_Interval::next_day_start( $datetime, $reversed ); + $current_week_no = WC_Admin_Reports_Interval::week_number( $datetime, $first_day_of_week ); + } while ( $current_week_no === $initial_week_no ); + + // The week boundary is actually next midnight when going in reverse, so set it to day -1 at 23:59:59. + if ( $reversed ) { + $timestamp = (int) $datetime->format( 'U' ); + $end_of_day_timestamp = floor( $timestamp / DAY_IN_SECONDS ) * DAY_IN_SECONDS + DAY_IN_SECONDS - 1; + $datetime->setTimestamp( $end_of_day_timestamp ); + } + + return $datetime; + } + + /** + * Returns a new DateTime object representing the next month start, or previous month end if reversed. + * + * @param DateTime $datetime Date and time. + * @param bool $reversed Going backwards in time instead of forward. + * @return DateTime + */ + public static function next_month_start( $datetime, $reversed = false ) { + $month_increment = 1; + $year = $datetime->format( 'Y' ); + $month = (int) $datetime->format( 'm' ); + + if ( $reversed ) { + $beg_of_month_datetime = new DateTime( "$year-$month-01 00:00:00" ); + $timestamp = (int) $beg_of_month_datetime->format( 'U' ); + $end_of_prev_month_timestamp = $timestamp - 1; + $datetime->setTimestamp( $end_of_prev_month_timestamp ); + } else { + $month += $month_increment; + if ( $month > 12 ) { + $month = 1; + $year ++; + } + $day = '01'; + $datetime = new DateTime( "$year-$month-$day 00:00:00" ); + } + + return $datetime; + } + + /** + * Returns a new DateTime object representing the next quarter start, or previous quarter end if reversed. + * + * @param DateTime $datetime Date and time. + * @param bool $reversed Going backwards in time instead of forward. + * @return DateTime + */ + public static function next_quarter_start( $datetime, $reversed = false ) { + $year = $datetime->format( 'Y' ); + $month = (int) $datetime->format( 'n' ); + + switch ( $month ) { + case 1: + case 2: + case 3: + if ( $reversed ) { + $month = 1; + } else { + $month = 4; + } + break; + case 4: + case 5: + case 6: + if ( $reversed ) { + $month = 4; + } else { + $month = 7; + } + break; + case 7: + case 8: + case 9: + if ( $reversed ) { + $month = 7; + } else { + $month = 10; + } + break; + case 10: + case 11: + case 12: + if ( $reversed ) { + $month = 10; + } else { + $month = 1; + $year ++; + } + break; + } + $datetime = new DateTime( "$year-$month-01 00:00:00" ); + if ( $reversed ) { + $timestamp = (int) $datetime->format( 'U' ); + $end_of_prev_month_timestamp = $timestamp - 1; + $datetime->setTimestamp( $end_of_prev_month_timestamp ); + } + + return $datetime; + } + + /** + * Return a new DateTime object representing the next year start, or previous year end if reversed. + * + * @param DateTime $datetime Date and time. + * @param bool $reversed Going backwards in time instead of forward. + * @return DateTime + */ + public static function next_year_start( $datetime, $reversed = false ) { + $year_increment = 1; + $year = (int) $datetime->format( 'Y' ); + $month = '01'; + $day = '01'; + + if ( $reversed ) { + $datetime = new DateTime( "$year-$month-$day 00:00:00" ); + $timestamp = (int) $datetime->format( 'U' ); + $end_of_prev_year_timestamp = $timestamp - 1; + $datetime->setTimestamp( $end_of_prev_year_timestamp ); + } else { + $year += $year_increment; + $datetime = new DateTime( "$year-$month-$day 00:00:00" ); + } + + return $datetime; + } + + /** + * Returns beginning of next time interval for provided DateTime. + * + * E.g. for current DateTime, beginning of next day, week, quarter, etc. + * + * @param DateTime $datetime Date and time. + * @param string $time_interval Time interval, e.g. week, day, hour. + * @param bool $reversed Going backwards in time instead of forward. + * @return DateTime + */ + public static function iterate( $datetime, $time_interval, $reversed = false ) { +// $result_datetime = +// $result_timestamp_adjusted = $result_datetime->format( 'U' ) - 1; +// $result_datetime->setTimestamp( $result_timestamp_adjusted ); + return call_user_func( array( __CLASS__, "next_{$time_interval}_start" ), $datetime, $reversed ); + } + +} diff --git a/plugins/woocommerce-admin/includes/class-wc-admin-reports-orders-stats-query.php b/plugins/woocommerce-admin/includes/class-wc-admin-reports-orders-stats-query.php new file mode 100644 index 00000000000..12d91b7bcc0 --- /dev/null +++ b/plugins/woocommerce-admin/includes/class-wc-admin-reports-orders-stats-query.php @@ -0,0 +1,60 @@ + '2018-07-19 00:00:00', + * 'after' => '2018-07-05 00:00:00', + * 'interval' => 'week', + * 'categories' => array(15, 18), + * 'coupons' => array(138), + * 'order_status' => array('completed'), + * ); + * $report = new WC_Reports_Orders_Stats_Query( $args ); + * $mydata = $report->get_data(); + * + * @package WooCommerce/Classes + * @version 3.5.0 + * @since 3.5.0 + */ + +defined( 'ABSPATH' ) || exit; + +/** + * WC_Reports_Orders_Stats_Query + * + * @version 3.5.0 + */ +class WC_Reports_Orders_Stats_Query extends WC_Reports_Query { + + const REPORT_NAME = 'report-orders-stats'; + + /** + * Valid fields for Orders report. + * + * @return array + */ + protected function get_default_query_vars() { + return array( + 'fields' => array( + 'net_revenue', + 'avg_order_value', + 'orders_count', + 'avg_items_per_order', + ), + ); + } + + /** + * Get revenue data based on the current query vars. + * + * @return array + */ + public function get_data() { + $args = apply_filters( 'woocommerce_reports_orders_stats_query_args', $this->get_query_vars() ); + $results = WC_Data_Store::load( $this::REPORT_NAME )->get_data( $args ); + return apply_filters( 'woocommerce_reports_orders_stats_select_query', $results, $args ); + } + +} \ No newline at end of file diff --git a/plugins/woocommerce-admin/includes/class-wc-admin-reports-products-query.php b/plugins/woocommerce-admin/includes/class-wc-admin-reports-products-query.php new file mode 100644 index 00000000000..b969b60c1ea --- /dev/null +++ b/plugins/woocommerce-admin/includes/class-wc-admin-reports-products-query.php @@ -0,0 +1,52 @@ + '2018-07-19 00:00:00', + * 'after' => '2018-07-05 00:00:00', + * 'page' => 2, + * 'categories' => array(15, 18), + * 'products' => array(1,2,3) + * ); + * $report = new WC_Reports_Products_Query( $args ); + * $mydata = $report->get_data(); + * + * @package WooCommerce/Classes + * @version 3.5.0 + * @since 3.5.0 + */ + +defined( 'ABSPATH' ) || exit; + +/** + * WC_Reports_Products_Query + * + * @version 3.5.0 + */ +class WC_Reports_Products_Query extends WC_Reports_Query { + + const REPORT_NAME = 'report-products'; + + /** + * Valid fields for Products report. + * + * @return array + */ + protected function get_default_query_vars() { + return array(); + } + + /** + * Get product data based on the current query vars. + * + * @return array + */ + public function get_data() { + $args = apply_filters( 'woocommerce_reports_products_query_args', $this->get_query_vars() ); + $results = WC_Data_Store::load( $this::REPORT_NAME )->get_data( $args ); + return apply_filters( 'woocommerce_reports_products_select_query', $results, $args ); + } + +} diff --git a/plugins/woocommerce-admin/includes/class-wc-admin-reports-products-stats-query.php b/plugins/woocommerce-admin/includes/class-wc-admin-reports-products-stats-query.php new file mode 100644 index 00000000000..ff7101edc75 --- /dev/null +++ b/plugins/woocommerce-admin/includes/class-wc-admin-reports-products-stats-query.php @@ -0,0 +1,52 @@ + '2018-07-19 00:00:00', + * 'after' => '2018-07-05 00:00:00', + * 'page' => 2, + * 'categories' => array(15, 18), + * 'product_ids' => array(1,2,3) + * ); + * $report = new WC_Admin_Reports_Products_Stats_Query( $args ); + * $mydata = $report->get_data(); + * + * @package WooCommerce Admin/Classes + * @version 3.5.0 + * @since 3.5.0 + */ + +defined( 'ABSPATH' ) || exit; + +/** + * WC_Admin_Reports_Products_Query + * + * @version 3.5.0 + */ +class WC_Admin_Reports_Products_Stats_Query extends WC_Admin_Reports_Query { + + const REPORT_NAME = 'report-products-stats'; + + /** + * Valid fields for Products report. + * + * @return array + */ + protected function get_default_query_vars() { + return array(); + } + + /** + * Get product data based on the current query vars. + * + * @return array + */ + public function get_data() { + $args = apply_filters( 'woocommerce_reports_products_stats_query_args', $this->get_query_vars() ); + $results = WC_Data_Store::load( $this::REPORT_NAME )->get_data( $args ); + return apply_filters( 'woocommerce_reports_products_stats_select_query', $results, $args ); + } + +} diff --git a/plugins/woocommerce-admin/includes/class-wc-admin-reports-query.php b/plugins/woocommerce-admin/includes/class-wc-admin-reports-query.php new file mode 100644 index 00000000000..59f55548ffa --- /dev/null +++ b/plugins/woocommerce-admin/includes/class-wc-admin-reports-query.php @@ -0,0 +1,29 @@ + 405 ) ); + } + +} diff --git a/plugins/woocommerce-admin/includes/class-wc-admin-reports-revenue-query.php b/plugins/woocommerce-admin/includes/class-wc-admin-reports-revenue-query.php new file mode 100644 index 00000000000..decc20e6132 --- /dev/null +++ b/plugins/woocommerce-admin/includes/class-wc-admin-reports-revenue-query.php @@ -0,0 +1,68 @@ + '2018-07-19 00:00:00', + * 'after' => '2018-07-05 00:00:00', + * 'interval' => 'week', + * ); + * $report = new WC_Reports_Revenue_Query( $args ); + * $mydata = $report->get_data(); + * + * @package WooCommerce/Classes + * @version 3.5.0 + * @since 3.5.0 + */ + +defined( 'ABSPATH' ) || exit; + +/** + * WC_Reports_Revenue_Query + * + * @version 3.5.0 + */ +class WC_Reports_Revenue_Query extends WC_Reports_Query { + + const REPORT_NAME = 'report-revenue-stats'; + + /** + * Valid fields for Revenue report. + * + * @return array + */ + protected function get_default_query_vars() { + return array( + 'per_page' => get_option( 'posts_per_page' ), // not sure if this should be the default. + 'page' => 1, + 'order' => 'DESC', + 'orderby' => 'date', + 'before' => '', + 'after' => '', + 'interval' => 'week', + 'fields' => array( + 'orders_count', + 'num_items_sold', + 'gross_revenue', + 'coupons', + 'refunds', + 'taxes', + 'shipping', + 'net_revenue', + ), + ); + } + + /** + * Get revenue data based on the current query vars. + * + * @return array + */ + public function get_data() { + $args = apply_filters( 'woocommerce_reports_revenue_query_args', $this->get_query_vars() ); + $results = WC_Data_Store::load( $this::REPORT_NAME )->get_data( $args ); + return apply_filters( 'woocommerce_reports_revenue_select_query', $results, $args ); + } + +} diff --git a/plugins/woocommerce-admin/includes/data-stores/class-wc-admin-reports-categories-data-store.php b/plugins/woocommerce-admin/includes/data-stores/class-wc-admin-reports-categories-data-store.php new file mode 100644 index 00000000000..95680f78648 --- /dev/null +++ b/plugins/woocommerce-admin/includes/data-stores/class-wc-admin-reports-categories-data-store.php @@ -0,0 +1,265 @@ + 'intval', + 'items_sold' => 'intval', + 'gross_revenue' => 'floatval', + 'orders_count' => 'intval', + 'products_count' => 'intval', + ); + + /** + * SQL columns to select in the db query. + * + * @var array + */ + protected $report_columns = array( + 'items_sold' => 'SUM(product_qty) as items_sold', + 'gross_revenue' => 'SUM(product_gross_revenue) AS gross_revenue', + 'orders_count' => 'COUNT(DISTINCT order_id) as orders_count', + // 'products_count' is not a SQL column at the moment, see below. + ); + + /** + * Return the database query with parameters used for Categories report: time span and order status. + * + * @param array $query_args Query arguments supplied by the user. + * @return array Array of parameters used for SQL query. + */ + protected function get_sql_query_params( $query_args ) { + global $wpdb; + + $sql_query_params = $this->get_time_period_sql_params( $query_args ); + // Limit is left out here so that the grouping in code by PHP can be applied correctly. + $sql_query_params = array_merge( $sql_query_params, $this->get_order_by_sql_params( $query_args ) ); + + $order_product_lookup_table = $wpdb->prefix . self::TABLE_NAME; + + $allowed_products = $this->get_allowed_products( $query_args ); + + if ( count( $allowed_products ) > 0 ) { + $allowed_products_str = implode( ',', $allowed_products ); + $sql_query_params['where_clause'] .= " AND {$order_product_lookup_table}.product_id IN ({$allowed_products_str})"; + } + + if ( is_array( $query_args['order_status'] ) && count( $query_args['order_status'] ) > 0 ) { + $statuses = array_map( array( $this, 'normalize_order_status' ), $query_args['order_status'] ); + + $sql_query_params['from_clause'] .= " JOIN {$wpdb->prefix}posts ON {$order_product_lookup_table}.order_id = {$wpdb->prefix}posts.ID"; + $sql_query_params['where_clause'] .= " AND {$wpdb->prefix}posts.post_status IN ( '" . implode( "','", $statuses ) . "' ) "; + } + + return $sql_query_params; + } + + /** + * Maps ordering specified by the user to columns in the database/fields in the data. + * + * @param string $order_by Sorting criterion. + * @return string + */ + protected function normalize_order_by( $order_by ) { + if ( 'date' === $order_by ) { + return 'date_created'; + } + + return $order_by; + } + + /** + * Compares values in 2 arrays based on criterion specified when called via sort_records. + * + * @param array $a Array 1 to compare. + * @param array $b Array 2 to compare. + * @return integer|WP_Error + */ + private function sort_callback( $a, $b ) { + if ( '' === $this->order_by || '' === $this->order ) { + return new WP_Error( 'woocommerce_reports_categories_sort_failed', __( 'Sorry, fetching categories data failed.', 'woocommerce' ) ); + } + if ( $a[ $this->order_by ] === $b[ $this->order_by ] ) { + return 0; + } elseif ( $a[ $this->order_by ] > $b[ $this->order_by ] ) { + return strtolower( $this->order ) === 'desc' ? -1 : 1; + } elseif ( $a[ $this->order_by ] < $b[ $this->order_by ] ) { + return strtolower( $this->order ) === 'desc' ? 1 : -1; + } + } + + /** + * Sorts the data based on given sorting criterion and order. + * + * @param array $data Data to sort. + * @param string $sort_by Sorting criterion, any of the column_types keys. + * @param string $direction Sorting direction: asc/desc. + */ + protected function sort_records( &$data, $sort_by, $direction ) { + $this->order_by = $this->normalize_order_by( $sort_by ); + $this->order = $direction; + usort( $data, array( $this, 'sort_callback' ) ); + } + + /** + * Returns the page of data according to page number and items per page. + * + * @param array $data Data to paginate. + * @param integer $page_no Page number. + * @param integer $items_per_page Number of items per page. + * @return array + */ + protected function page_records( $data, $page_no, $items_per_page ) { + $offset = ( $page_no - 1 ) * $items_per_page; + return array_slice( $data, $offset, $items_per_page ); + } + + /** + * Returns the report data based on parameters supplied by the user. + * + * @param array $query_args Query parameters. + * @return stdClass|WP_Error Data. + */ + public function get_data( $query_args ) { + global $wpdb; + + $table_name = $wpdb->prefix . self::TABLE_NAME; + $now = time(); + $week_back = $now - WEEK_IN_SECONDS; + + // These defaults are only partially applied when used via REST API, as that has its own defaults. + $defaults = array( + 'per_page' => get_option( 'posts_per_page' ), + 'page' => 1, + 'order' => 'DESC', + 'orderby' => 'date', + 'before' => date( WC_Admin_Reports_Interval::$iso_datetime_format, $now ), + 'after' => date( WC_Admin_Reports_Interval::$iso_datetime_format, $week_back ), + 'fields' => '*', + 'categories' => array(), + // This is not a parameter for products reports per se, but maybe we should restricts order statuses here, too? + 'order_status' => parent::get_report_order_statuses(), + + ); + $query_args = wp_parse_args( $query_args, $defaults ); + + $cache_key = $this->get_cache_key( $query_args ); + $data = wp_cache_get( $cache_key, $this->cache_group ); + + if ( false === $data ) { + $data = (object) array( + 'data' => array(), + 'total' => 0, + 'pages' => 0, + 'page_no' => 0, + ); + + $selections = $this->selected_columns( $query_args ); + $sql_query_params = $this->get_sql_query_params( $query_args ); + + $products_data = $wpdb->get_results( + "SELECT + product_id, + date_created, + {$selections} + FROM + {$table_name} + {$sql_query_params['from_clause']} + WHERE + 1=1 + {$sql_query_params['where_clause']} + GROUP BY + product_id + ", ARRAY_A + ); // WPCS: cache ok, DB call ok, unprepared SQL ok. + + if ( null === $products_data ) { + return new WP_Error( 'woocommerce_reports_categories_result_failed', __( 'Sorry, fetching revenue data failed.', 'woocommerce' ), array( 'status' => 500 ) ); + } + + // Group by category without a helper table, worst case we add it and change the SQL afterwards. + // Other option would be a join with wp_post and taxonomies, but a) performance might be bad, b) how to handle custom product tables? + $categories_data = array(); + foreach ( $products_data as $product_data ) { + $categories = get_the_terms( $product_data['product_id'], 'product_cat' ); + foreach ( $categories as $category ) { + $cat_id = $category->term_id; + if ( ! key_exists( $cat_id, $categories_data ) ) { + $categories_data[ $cat_id ] = array( + 'category_id' => 0, + 'items_sold' => 0, + 'gross_revenue' => 0.0, + 'orders_count' => 0, + 'products_count' => 0, + ); + } + + $categories_data[ $cat_id ]['category_id'] = $cat_id; + $categories_data[ $cat_id ]['items_sold'] += $product_data['items_sold']; + $categories_data[ $cat_id ]['gross_revenue'] += $product_data['gross_revenue']; + $categories_data[ $cat_id ]['orders_count'] += $product_data['orders_count']; + $categories_data[ $cat_id ]['products_count'] ++; + } + } + $record_count = count( $categories_data ); + $total_pages = (int) ceil( $record_count / $query_args['per_page'] ); + if ( $query_args['page'] < 1 || $query_args['page'] > $total_pages ) { + return $data; + } + + $this->sort_records( $categories_data, $query_args['orderby'], $query_args['order'] ); + $categories_data = $this->page_records( $categories_data, $query_args['page'], $query_args['per_page'] ); + + $categories_data = array_map( array( $this, 'cast_numbers' ), $categories_data ); + $data = (object) array( + 'data' => $categories_data, + 'total' => $record_count, + 'pages' => $total_pages, + 'page_no' => (int) $query_args['page'], + ); + + wp_cache_set( $cache_key, $data, $this->cache_group ); + } + + return $data; + } + +} diff --git a/plugins/woocommerce-admin/includes/data-stores/class-wc-admin-reports-data-store.php b/plugins/woocommerce-admin/includes/data-stores/class-wc-admin-reports-data-store.php new file mode 100644 index 00000000000..bfe5cafa695 --- /dev/null +++ b/plugins/woocommerce-admin/includes/data-stores/class-wc-admin-reports-data-store.php @@ -0,0 +1,579 @@ +order_by || '' === $this->order ) { + return 0; + // TODO: should return WP_Error here perhaps? + } + if ( $a[ $this->order_by ] === $b[ $this->order_by ] ) { + return 0; + } elseif ( $a[ $this->order_by ] > $b[ $this->order_by ] ) { + return strtolower( $this->order ) === 'desc' ? -1 : 1; + } elseif ( $a[ $this->order_by ] < $b[ $this->order_by ] ) { + return strtolower( $this->order ) === 'desc' ? 1 : -1; + } + } + + /** + * Sorts intervals according to user's request. + * + * They are pre-sorted in SQL, but after adding gaps, they need to be sorted including the added ones. + * + * @param stdClass $data Data object, must contain an array under $data->intervals. + * @param string $sort_by Ordering property. + * @param string $direction DESC/ASC. + */ + protected function sort_intervals( &$data, $sort_by, $direction ) { + $this->order_by = $this->normalize_order_by( $sort_by ); + $this->order = $direction; + usort( $data->intervals, array( $this, 'interval_cmp' ) ); + } + + /** + * Fills in interval gaps from DB with 0-filled objects. + * + * @param array $db_intervals Array of all intervals present in the db. + * @param DateTime $datetime_start Start date. + * @param DateTime $datetime_end End date. + * @param string $time_interval Time interval, e.g. day, week, month. + * @param stdClass $data Data with SQL extracted intervals. + * @return stdClass + */ + protected function fill_in_missing_intervals( $db_intervals, $datetime_start, $datetime_end, $time_interval, &$data ) { + // TODO: this is ugly and messy. + // At this point, we don't know when we can stop iterating, as the ordering can be based on any value. + $end_datetime = new DateTime( $datetime_end ); + $time_ids = array_flip( wp_list_pluck( $data->intervals, 'time_interval' ) ); + $db_intervals = array_flip( $db_intervals ); + $datetime = new DateTime( $datetime_start ); + // Totals object used to get all needed properties. + $totals_arr = get_object_vars( $data->totals ); + foreach ( $totals_arr as $key => $val ) { + $totals_arr[ $key ] = 0; + } + while ( $datetime <= $end_datetime ) { + $next_start = WC_Admin_Reports_Interval::iterate( $datetime, $time_interval ); + $time_id = WC_Admin_Reports_Interval::time_interval_id( $time_interval, $datetime ); + // Either create fill-zero interval or use data from db. + if ( $next_start > $end_datetime ) { + $interval_end = $end_datetime->format( 'Y-m-d H:i:s' ); + } else { + $prev_end_timestamp = (int) $next_start->format( 'U' ) - 1; + $prev_end = new DateTime(); + $prev_end->setTimestamp( $prev_end_timestamp ); + $interval_end = $prev_end->format( 'Y-m-d H:i:s' ); + } + if ( array_key_exists( $time_id, $time_ids ) ) { + // For interval present in the db for this time frame, just fill in dates. + $record = &$data->intervals[ $time_ids[ $time_id ] ]; + $record['date_start'] = $datetime->format( 'Y-m-d H:i:s' ); + $record['date_end'] = $interval_end; + } elseif ( array_key_exists( $time_id, $db_intervals ) ) { + // For intervals present in the db outside of this time frame, do nothing. + } else { + // For intervals not present in the db, fabricate it. + $record_arr = array(); + $record_arr['time_interval'] = $time_id; + $record_arr['date_start'] = $datetime->format( 'Y-m-d H:i:s' ); + $record_arr['date_end'] = $interval_end; + $data->intervals[] = array_merge( $record_arr, $totals_arr ); + } + $datetime = $next_start; + } + return $data; + } + + /** + * Removes extra records from intervals so that only requested number of records get returned. + * + * @param stdClass $data Data from whose intervals the records get removed. + * @param int $page_no Offset requested by the user. + * @param int $items_per_page Number of records requested by the user. + * @param int $db_interval_count + * @param int $expected_interval_count + * @param string $order_by + */ + protected function remove_extra_records( &$data, $page_no, $items_per_page, $db_interval_count, $expected_interval_count, $order_by ) { + if ( 'date' === strtolower( $order_by ) ) { + $offset = 0; + } else { + $offset = ( $page_no - 1 ) * $items_per_page - $db_interval_count; + $offset = $offset < 0 ? 0 : $offset; + } + $count = $expected_interval_count - ( $page_no - 1 ) * $items_per_page; + if ( $count < 0 ) { + $count = 0; + } elseif ( $count > $items_per_page ) { + $count = $items_per_page; + } + $data->intervals = array_slice( $data->intervals, $offset, $count ); + } + + /** + * Updates the LIMIT query part for Intervals query of the report. + * + * If there are less records in the database than time intervals, then we need to remap offset in SQL query + * to fetch correct records. + * + * @param array $intervals_query Array with clauses for the Intervals SQL query. + * @param int $db_records Number of records in the db for requested time period. + */ + protected function update_intervals_sql_params( &$intervals_query, &$query_args, $db_interval_count, $expected_interval_count ) { + if ( $db_interval_count === $expected_interval_count ) { + return; + } + if ( 'date' === strtolower( $query_args['orderby'] ) ) { + // page X in request translates to slightly different dates in the db, in case some + // records are missing from the db. + $start_iteration = 0; + $end_iteration = 0; + if ( 'asc' === strtolower( $query_args['order'] ) ) { + // ORDER BY date ASC. + $new_start_date = new DateTime( $query_args['after'] ); + $intervals_to_skip = ( $query_args['page'] - 1 ) * $intervals_query['per_page']; + $latest_end_date = new DateTime( $query_args['before'] ); + for ( $i = 0; $i < $intervals_to_skip; $i++ ) { + if ( $new_start_date > $latest_end_date ) { + $new_start_date = $latest_end_date; + $start_iteration = 0; + break; + } + $new_start_date = WC_Admin_Reports_Interval::iterate( $new_start_date, $query_args['interval'] ); + $start_iteration ++; + } + + $new_end_date = clone $new_start_date; + for ( $i = 0; $i < $intervals_query['per_page']; $i++ ) { + if ( $new_end_date > $latest_end_date ) { + $new_end_date = $latest_end_date; + $end_iteration = 0; + break; + } + $new_end_date = WC_Admin_Reports_Interval::iterate( $new_end_date, $query_args['interval'] ); + $end_iteration ++; + } + if ( $end_iteration ) { + $new_end_date_timestamp = (int) $new_end_date->format( 'U' ) - 1; + $new_end_date->setTimestamp( $new_end_date_timestamp ); + } + } else { + // ORDER BY date DESC. + $new_end_date = new DateTime( $query_args['before'] ); + $intervals_to_skip = ( $query_args['page'] - 1 ) * $intervals_query['per_page']; + $earliest_start_date = new DateTime( $query_args['after'] ); + for ( $i = 0; $i < $intervals_to_skip; $i++ ) { + if ( $new_end_date < $earliest_start_date ) { + $new_end_date = $earliest_start_date; + $end_iteration = 0; + break; + } + $new_end_date = WC_Admin_Reports_Interval::iterate( $new_end_date, $query_args['interval'], true ); + $end_iteration ++; + } + + $new_start_date = clone $new_end_date; + for ( $i = 0; $i < $intervals_query['per_page']; $i++ ) { + if ( $new_start_date < $earliest_start_date ) { + $new_start_date = $earliest_start_date; + $start_iteration = 0; + break; + } + $new_start_date = WC_Admin_Reports_Interval::iterate( $new_start_date, $query_args['interval'], true ); + $start_iteration ++; + } + if ( $start_iteration ) { + // TODO: is this correct? should it only be added if iterate runs? other two iterate instances, too? + $new_start_date_timestamp = (int) $new_start_date->format( 'U' ) + 1; + $new_start_date->setTimestamp( $new_start_date_timestamp ); + } + } + $query_args['adj_after'] = $new_start_date->format( WC_Admin_Reports_Interval::$iso_datetime_format ); + $query_args['adj_before'] = $new_end_date->format( WC_Admin_Reports_Interval::$iso_datetime_format ); + $intervals_query['where_clause'] = ''; + $intervals_query['where_clause'] .= " AND date_created <= '{$query_args['adj_before']}'"; + $intervals_query['where_clause'] .= " AND date_created >= '{$query_args['adj_after']}'"; + $intervals_query['limit'] = 'LIMIT 0,' . $intervals_query['per_page']; + } else { + if ( 'asc' === $query_args['order'] ) { + $offset = ( ( $query_args['page'] - 1 ) * $intervals_query['per_page'] ) - ( $expected_interval_count - $db_interval_count ); + $offset = $offset < 0 ? 0 : $offset; + $count = $query_args['page'] * $intervals_query['per_page'] - ( $expected_interval_count - $db_interval_count ); + if ( $count < 0 ) { + $count = 0; + } elseif ( $count > $intervals_query['per_page'] ) { + $count = $intervals_query['per_page']; + } + $intervals_query['limit'] = 'LIMIT ' . $offset . ',' . $count; + } + // Otherwise no change in limit clause. + $query_args['adj_after'] = $query_args['after']; + $query_args['adj_before'] = $query_args['before']; + } + } + + /** + * Returns string to be used as cache key for the data. + * + * @param array $params Query parameters. + * @return string + */ + protected function get_cache_key( $params ) { + // TODO: this is not working in PHP 5.2 (but revenue class has static methods, so it cannot use object property). + return 'woocommerce_' . $this::TABLE_NAME . '_' . md5( wp_json_encode( $params ) ); + } + + /** + * Casts strings returned from the database to appropriate data types for output. + * + * @param array $array Associative array of values extracted from the database. + * @return array|WP_Error + */ + protected function cast_numbers( $array ) { + $retyped_array = array(); + $column_types = apply_filters( 'woocommerce_rest_reports_column_types', $this->column_types, $array ); + foreach ( $array as $column_name => $value ) { + if ( isset( $column_types[ $column_name ] ) ) { + $retyped_array[ $column_name ] = $column_types[ $column_name ]( $value ); + } else { + $retyped_array[ $column_name ] = $value; + } + } + return $retyped_array; + } + + /** + * Returns a list of columns selected by the query_args formatted as a comma separated string. + * + * @param array $query_args User-supplied options. + * @return string + */ + protected function selected_columns( $query_args ) { + $selections = $this->report_columns; + + if ( isset( $query_args['fields'] ) && is_array( $query_args['fields'] ) ) { + $keep = array(); + foreach ( $query_args['fields'] as $field ) { + if ( isset( $selections[ $field ] ) ) { + $keep[ $field ] = $selections[ $field ]; + } + } + $selections = implode( ', ', $keep ); + } else { + $selections = implode( ', ', $selections ); + } + return $selections; + } + + /** + * Get the order statuses used when calculating reports. + * + * @return array + */ + protected static function get_report_order_statuses() { + return apply_filters( 'woocommerce_reports_order_statuses', array( 'completed', 'processing', 'on-hold' ) ); + } + + /** + * Maps order status provided by the user to the one used in the database. + * + * @param string $status Order status. + * @return string + */ + protected function normalize_order_status( $status ) { + $status = trim( $status ); + return 'wc-' . $status; + } + + /** + * Normalizes order_by clause to match to SQL query. + * + * @param string $order_by Order by option requeste by user. + * @return string + */ + protected function normalize_order_by( $order_by ) { + if ( 'date' === $order_by ) { + return 'time_interval'; + } + + return $order_by; + } + + /** + * Updates start and end dates for intervals so that they represent intervals' borders, not times when data in db were recorded. + * + * E.g. if there are db records for only Tuesday and Thursday this week, the actual week interval is [Mon, Sun], not [Tue, Thu]. + * + * @param DateTime $datetime_start Start date. + * @param DateTime $datetime_end End date. + * @param string $time_interval Time interval, e.g. day, week, month. + * @param array $intervals Array of intervals extracted from SQL db. + */ + protected function update_interval_boundary_dates( $datetime_start, $datetime_end, $time_interval, &$intervals ) { + foreach ( $intervals as $key => $interval ) { + $datetime = new DateTime( $interval['datetime_anchor'] ); + + $prev_start = WC_Admin_Reports_Interval::iterate( $datetime, $time_interval, true ); + // TODO: not sure if the +1/-1 here are correct, especially as they are applied before the ?: below. + $prev_start_timestamp = (int) $prev_start->format( 'U' ) + 1; + $prev_start->setTimestamp( $prev_start_timestamp ); + if ( $datetime_start ) { + $start_datetime = new DateTime( $datetime_start ); + $intervals[ $key ]['date_start'] = ( $prev_start < $start_datetime ? $start_datetime : $prev_start )->format( 'Y-m-d H:i:s' ); + } else { + $intervals[ $key ]['date_start'] = $prev_start->format( 'Y-m-d H:i:s' ); + } + + $next_end = WC_Admin_Reports_Interval::iterate( $datetime, $time_interval ); + $next_end_timestamp = (int) $next_end->format( 'U' ) - 1; + $next_end->setTimestamp( $next_end_timestamp ); + if ( $datetime_end ) { + $end_datetime = new DateTime( $datetime_end ); + $intervals[ $key ]['date_end'] = ( $next_end > $end_datetime ? $end_datetime : $next_end )->format( 'Y-m-d H:i:s' ); + } else { + $intervals[ $key ]['date_end'] = $next_end->format( 'Y-m-d H:i:s' ); + } + + $intervals[ $key ]['interval'] = $time_interval; + } + } + + /** + * Change structure of intervals to form a correct response. + * + * @param array $intervals Time interval, e.g. day, week, month. + */ + protected function create_interval_subtotals( &$intervals ) { + foreach ( $intervals as $key => $interval ) { + // Move intervals result to subtotals object. + $intervals[ $key ] = array( + 'interval' => $interval['time_interval'], + 'date_start' => $interval['date_start'], + 'date_start_gmt' => $interval['date_start'], + 'date_end' => $interval['date_end'], + 'date_end_gmt' => $interval['date_end'], + ); + + unset( $interval['interval'] ); + unset( $interval['date_start'] ); + unset( $interval['date_end'] ); + unset( $interval['datetime_anchor'] ); + unset( $interval['time_interval'] ); + $intervals[ $key ]['subtotals'] = (object) $this->cast_numbers( $interval ); + } + } + + /** + * Fills WHERE clause of SQL request for 'Totals' section of data response based on user supplied parameters. + * + * @param array $query_args Parameters supplied by the user. + * @return array + */ + protected function get_time_period_sql_params( $query_args ) { + $sql_query = array( + 'from_clause' => '', + 'where_clause' => '', + ); + + if ( isset( $query_args['before'] ) && '' !== $query_args['before'] ) { + $datetime = new DateTime( $query_args['before'] ); + $datetime_str = $datetime->format( WC_Admin_Reports_Interval::$sql_datetime_format ); + $sql_query['where_clause'] .= " AND date_created <= '$datetime_str'"; + + } + + if ( isset( $query_args['after'] ) && '' !== $query_args['after'] ) { + $datetime = new DateTime( $query_args['after'] ); + $datetime_str = $datetime->format( WC_Admin_Reports_Interval::$sql_datetime_format ); + $sql_query['where_clause'] .= " AND date_created >= '$datetime_str'"; + } + + return $sql_query; + } + + /** + * Fills LIMIT clause of SQL request based on user supplied parameters. + * + * @param array $query_args Parameters supplied by the user. + * @return array + */ + protected function get_limit_sql_params( $query_args ) { + $sql_query['per_page'] = get_option( 'posts_per_page' ); + if ( isset( $query_args['per_page'] ) && is_numeric( $query_args['per_page'] ) ) { + $sql_query['per_page'] = (int) $query_args['per_page']; + } + + $sql_query['offset'] = 0; + if ( isset( $query_args['page'] ) ) { + $sql_query['offset'] = ( (int) $query_args['page'] - 1 ) * $sql_query['per_page']; + } + + $sql_query['limit'] = "LIMIT {$sql_query['offset']}, {$sql_query['per_page']}"; + return $sql_query; + } + + /** + * Fills ORDER BY clause of SQL request based on user supplied parameters. + * + * @param array $query_args Parameters supplied by the user. + * @return array + */ + protected function get_order_by_sql_params( $query_args ) { + $sql_query['order_by_clause'] = ''; + if ( isset( $query_args['orderby'] ) ) { + $sql_query['order_by_clause'] = $this->normalize_order_by( $query_args['orderby'] ); + } + + if ( isset( $query_args['order'] ) ) { + $sql_query['order_by_clause'] .= ' ' . $query_args['order']; + } else { + $sql_query['order_by_clause'] .= ' DESC'; + } + + return $sql_query; + } + + /** + * Fills FROM and WHERE clauses of SQL request for 'Intervals' section of data response based on user supplied parameters. + * + * @param array $query_args Parameters supplied by the user. + * @return array + */ + protected function get_intervals_sql_params( $query_args ) { + $intervals_query = array( + 'from_clause' => '', + 'where_clause' => '', + ); + + $intervals_query = array_merge( $intervals_query, $this->get_time_period_sql_params( $query_args ) ); + + if ( isset( $query_args['interval'] ) && '' !== $query_args['interval'] ) { + $interval = $query_args['interval']; + $intervals_query['select_clause'] = WC_Admin_Reports_Interval::mysql_datetime_format( $interval ); + } + + $intervals_query = array_merge( $intervals_query, $this->get_limit_sql_params( $query_args ) ); + + $intervals_query = array_merge( $intervals_query, $this->get_order_by_sql_params( $query_args ) ); + + return $intervals_query; + } + + /** + * Returns an array of products belonging to given categories. + * + * @param array $categories List of categories IDs. + * @return array|stdClass + */ + protected function get_products_by_cat_ids( $categories ) { + $product_categories = get_categories( array( + 'hide_empty' => 0, + 'taxonomy' => 'product_cat', + ) ); + $cat_slugs = array(); + $categories = array_flip( $categories ); + foreach ( $product_categories as $product_cat ) { + if ( key_exists( $product_cat->cat_ID, $categories ) ) { + $cat_slugs[] = $product_cat->slug; + } + } + $args = array( + 'category' => $cat_slugs, + 'limit' => -1, + ); + return wc_get_products( $args ); + } + + /** + * Returns ids of allowed products, based on query arguments from the user. + * + * @param array $query_args Parameters supplied by the user. + * @return array + */ + protected function get_allowed_products( $query_args ) { + $allowed_products = array(); + if ( isset( $query_args['categories'] ) && is_array( $query_args['categories'] ) && count( $query_args['categories'] ) > 0 ) { + $allowed_products = $this->get_products_by_cat_ids( $query_args['categories'] ); + $allowed_products = wc_list_pluck( $allowed_products, 'get_id' ); + } + + if ( isset( $query_args['products'] ) && is_array( $query_args['products'] ) && count( $query_args['products'] ) > 0 ) { + if ( count( $allowed_products ) > 0 ) { + $allowed_products = array_intersect( $allowed_products, $query_args['products'] ); + } else { + $allowed_products = $query_args['products']; + } + } + return $allowed_products; + } + +} diff --git a/plugins/woocommerce-admin/includes/data-stores/class-wc-admin-reports-orders-data-store.php b/plugins/woocommerce-admin/includes/data-stores/class-wc-admin-reports-orders-data-store.php new file mode 100644 index 00000000000..6f1bb8fb4cf --- /dev/null +++ b/plugins/woocommerce-admin/includes/data-stores/class-wc-admin-reports-orders-data-store.php @@ -0,0 +1,465 @@ + 'intval', + 'num_items_sold' => 'intval', + 'gross_revenue' => 'floatval', + 'coupons' => 'floatval', + 'refunds' => 'floatval', + 'taxes' => 'floatval', + 'shipping' => 'floatval', + 'net_revenue' => 'floatval', + 'avg_items_per_order' => 'floatval', + 'avg_order_value' => 'floatval', + ); + + protected $report_columns = array( + 'orders_count' => 'COUNT(*) as orders_count', + 'num_items_sold' => 'SUM(num_items_sold) as num_items_sold', + 'gross_revenue' => 'SUM(gross_total) AS gross_revenue', + 'coupons' => 'SUM(coupon_total) AS coupons', + 'refunds' => 'SUM(refund_total) AS refunds', + 'taxes' => 'SUM(tax_total) AS taxes', + 'shipping' => 'SUM(shipping_total) AS shipping', + 'net_revenue' => 'SUM(net_total) AS net_revenue', + 'avg_items_per_order' => 'AVG(num_items_sold) AS avg_items_per_order', + 'avg_order_value' => 'AVG(gross_total) AS avg_order_value', + ); + + /** + * Background process to populate order stats. + * + * @var WC_Admin_Order_Stats_Background_Process + */ + protected static $background_process; + + /** + * Constructor. + */ + public function __construct() { + if ( ! self::$background_process ) { + self::$background_process = new WC_Admin_Order_Stats_Background_Process(); + } + } + + /** + * Set up all the hooks for maintaining and populating table data. + */ + public static function init() { + add_action( 'save_post', array( __CLASS__, 'sync_order' ) ); + + if ( ! self::$background_process ) { + self::$background_process = new WC_Admin_Order_Stats_Background_Process(); + } + } + + /** + * Returns expected number of items on the page in case of date ordering. + * + * @param $expected_interval_count + * @param $items_per_page + * @param $page_no + * + * @return float|int + */ + protected function expected_intervals_on_page( $expected_interval_count, $items_per_page, $page_no ) { + $total_pages = (int) ceil( $expected_interval_count / $items_per_page ); + if ( $page_no < $total_pages ) { + return $items_per_page; + } elseif ( $page_no === $total_pages ) { + return $expected_interval_count - ( $page_no - 1 ) * $items_per_page; + } else { + return 0; + } + } + + /** + * Returns true if there are any intervals that need to be filled in the response. + * + * @param $expected_interval_count + * @param $db_records + * @param $items_per_page + * @param $page_no + * @param $order + * @param $order_by + * @param $intervals_count + * + * @return bool + */ + protected function intervals_missing( $expected_interval_count, $db_records, $items_per_page, $page_no, $order, $order_by, $intervals_count ) { + if ( $expected_interval_count > $db_records ) { + if ( 'date' === $order_by ) { + $expected_intervals_on_page = $this->expected_intervals_on_page( $expected_interval_count, $items_per_page, $page_no ); + if ( $intervals_count < $expected_intervals_on_page ) { + return true; + } else { + return false; + } + } else { + if ( 'desc' === $order ) { + if ( $page_no > floor( $db_records / $items_per_page ) ) { + return true; + } else { + return false; + } + } elseif ( 'asc' === $order ) { + if ( $page_no <= ceil( ( $expected_interval_count - $db_records ) / $items_per_page ) ) { + return true; + } else { + return false; + } + } else { + // Invalid ordering. + return false; + } + } + } else { + return false; + } + } + + /** + * Updates the totals and intervals database queries with parameters used for Orders report: categories, coupons and order status. + * + * @param array $query_args Query arguments supplied by the user. + * @param array $totals_query Array of options for totals db query. + * @param array $intervals_query Array of options for intervals db query. + */ + protected function orders_stats_sql_filter( $query_args, &$totals_query, &$intervals_query ) { + // TODO: performance of all of this? + global $wpdb; + + $where_clause = ''; + $from_clause = ''; + + $orders_stats_table = $wpdb->prefix . self::TABLE_NAME; + $allowed_products = $this->get_allowed_products( $query_args ); + + if ( count( $allowed_products ) > 0 ) { + $allowed_products_str = implode( ',', $allowed_products ); + + $where_clause .= " AND {$orders_stats_table}.order_id IN ( + SELECT + DISTINCT {$wpdb->prefix}wc_admin_order_product_lookup.order_id + FROM + {$wpdb->prefix}wc_admin_order_product_lookup + WHERE + {$wpdb->prefix}wc_admin_order_product_lookup.product_id IN ({$allowed_products_str}) + )"; + } + + if ( is_array( $query_args['coupons'] ) && count( $query_args['coupons'] ) > 0 ) { + $allowed_coupons_str = implode( ', ', $query_args['coupons'] ); + + $where_clause .= " AND {$orders_stats_table}.order_id IN ( + SELECT + DISTINCT {$wpdb->prefix}wc_order_coupon_lookup.order_id + FROM + {$wpdb->prefix}wc_order_coupon_lookup + WHERE + {$wpdb->prefix}wc_order_coupon_lookup.coupon_id IN ({$allowed_coupons_str}) + )"; + } + + if ( is_array( $query_args['order_status'] ) && count( $query_args['order_status'] ) > 0 ) { + $statuses = array_map( array( $this, 'normalize_order_status' ), $query_args['order_status'] ); + + $from_clause .= " JOIN {$wpdb->prefix}posts ON {$orders_stats_table}.order_id = {$wpdb->prefix}posts.ID"; + $where_clause .= " AND {$wpdb->prefix}posts.post_status IN ( '" . implode( "','", $statuses ) . "' ) "; + } + + // To avoid requesting the subqueries twice, the result is applied to all queries passed to the method. + $totals_query['where_clause'] .= $where_clause; + $totals_query['from_clause'] .= $from_clause; + $intervals_query['where_clause'] .= $where_clause; + $intervals_query['from_clause'] .= $from_clause; + } + + /** + * Returns the report data based on parameters supplied by the user. + * + * @since 3.5.0 + * @param array $query_args Query parameters. + * @return stdClass|WP_Error Data. + */ + public function get_data( $query_args ) { + global $wpdb; + + $table_name = $wpdb->prefix . self::TABLE_NAME; + $now = time(); + $week_back = $now - WEEK_IN_SECONDS; + + // These defaults are only applied when not using REST API, as the API has its own defaults that overwrite these for most values (except before, after, etc). + $defaults = array( + 'per_page' => get_option( 'posts_per_page' ), + 'page' => 1, + 'order' => 'DESC', + 'orderby' => 'date', + 'before' => date( WC_Admin_Reports_Interval::$iso_datetime_format, $now ), + 'after' => date( WC_Admin_Reports_Interval::$iso_datetime_format, $week_back ), + 'interval' => 'week', + 'fields' => '*', + 'categories' => array(), + 'coupons' => array(), + 'order_status' => parent::get_report_order_statuses(), + 'products' => array(), + ); + $query_args = wp_parse_args( $query_args, $defaults ); + + $cache_key = $this->get_cache_key( $query_args ); + $data = wp_cache_get( $cache_key, $this->cache_group ); + + if ( false === $data ) { + $data = (object) array( + 'totals' => (object) array(), + 'intervals' => (object) array(), + 'total' => 0, + 'pages' => 0, + 'page_no' => 0, + ); + + $selections = $this->selected_columns( $query_args ); + $totals_query = $this->get_time_period_sql_params( $query_args ); + $intervals_query = $this->get_intervals_sql_params( $query_args ); + + // Additional filtering for Orders report. + $this->orders_stats_sql_filter( $query_args, $totals_query, $intervals_query ); + + $totals = $wpdb->get_results( + "SELECT + {$selections} + FROM + {$table_name} + {$totals_query['from_clause']} + WHERE + 1=1 + {$totals_query['where_clause']}", ARRAY_A + ); // WPCS: cache ok, DB call ok, unprepared SQL ok. + if ( null === $totals ) { + return new WP_Error( 'woocommerce_reports_revenue_result_failed', __( 'Sorry, fetching revenue data failed.', 'woocommerce' ) ); + } + + // Specification says these are not included in totals. + unset( $totals[0]['date_start'] ); + unset( $totals[0]['date_end'] ); + + $totals = (object) $this->cast_numbers( $totals[0] ); + + $db_intervals = $wpdb->get_col( + "SELECT + {$intervals_query['select_clause']} AS time_interval + FROM + {$table_name} + {$intervals_query['from_clause']} + WHERE + 1=1 + {$intervals_query['where_clause']} + GROUP BY + time_interval" + ); // WPCS: cache ok, DB call ok, , unprepared SQL ok. + + $db_interval_count = count( $db_intervals ); + $expected_interval_count = WC_Admin_Reports_Interval::intervals_between( $query_args['after'], $query_args['before'], $query_args['interval'] ); + $total_pages = (int) ceil( $expected_interval_count / $intervals_query['per_page'] ); + + if ( $query_args['page'] < 1 || $query_args['page'] > $total_pages ) { + return $data; + } + + $this->update_intervals_sql_params( $intervals_query, $query_args, $db_interval_count, $expected_interval_count ); + + if ( '' !== $selections ) { + $selections = ', ' . $selections; + } + + $intervals = $wpdb->get_results( + "SELECT + MAX(date_created) AS datetime_anchor, + {$intervals_query['select_clause']} AS time_interval + {$selections} + FROM + {$table_name} + {$intervals_query['from_clause']} + WHERE + 1=1 + {$intervals_query['where_clause']} + GROUP BY + time_interval + ORDER BY + {$intervals_query['order_by_clause']} + {$intervals_query['limit']}", ARRAY_A + ); // WPCS: cache ok, DB call ok, unprepared SQL ok. + + echo $wpdb->last_query; + + if ( null === $intervals ) { + return new WP_Error( 'woocommerce_reports_revenue_result_failed', __( 'Sorry, fetching revenue data failed.', 'woocommerce' ) ); + } + + $data = (object) array( + 'totals' => $totals, + 'intervals' => $intervals, + 'total' => $expected_interval_count, + 'pages' => $total_pages, + 'page_no' => (int) $query_args['page'], + ); + + if ( $this->intervals_missing( $expected_interval_count, $db_interval_count, $intervals_query['per_page'], $query_args['page'], $query_args['order'], $query_args['orderby'], count( $intervals ) ) ) { + $this->fill_in_missing_intervals( $db_intervals, $query_args['adj_after'], $query_args['adj_before'], $query_args['interval'], $data ); + $this->sort_intervals( $data, $query_args['orderby'], $query_args['order'] ); + $this->remove_extra_records( $data, $query_args['page'], $intervals_query['per_page'], $db_interval_count, $expected_interval_count, $query_args['orderby'] ); + } else { + $this->update_interval_boundary_dates( $query_args['adj_after'], $query_args['adj_before'], $query_args['interval'], $data->intervals ); + } + $this->create_interval_subtotals( $data->intervals ); + + wp_cache_set( $cache_key, $data, $this->cache_group ); + } + + return $data; + } + + /** + * Queue a background process that will repopulate the entire orders stats database. + * + * @todo Make this work on large DBs. + */ + public static function queue_order_stats_repopulate_database() { + + // This needs to be updated to work in batches instead of getting all orders, as + // that will not work well on DBs with more than a few hundred orders. + $order_ids = wc_get_orders( array( + 'limit' => -1, + 'status' => parent::get_report_order_statuses(), + 'type' => 'shop_order', + 'return' => 'ids', + ) ); + + foreach ( $order_ids as $id ) { + self::$background_process->push_to_queue( $id ); + } + + self::$background_process->save(); + self::$background_process->dispatch(); + } + + /** + * Add order information to the lookup table when orders are created or modified. + * + * @param int $post_id Post ID. + */ + public static function sync_order( $post_id ) { + if ( 'shop_order' !== get_post_type( $post_id ) ) { + return; + } + + $order = wc_get_order( $post_id ); + if ( ! $order ) { + return; + } + + self::update( $order ); + } + + /** + * Update the database with stats data. + * + * @param WC_Order $order Order to update row for. + * @return int|bool Number or rows modified or false on failure. + */ + public static function update( $order ) { + global $wpdb; + $table_name = $wpdb->prefix . self::TABLE_NAME; + + if ( ! $order->get_id() || ! $order->get_date_created() ) { + return false; + } + + if ( ! in_array( $order->get_status(), parent::get_report_order_statuses(), true ) ) { + $wpdb->delete( $table_name, array( + 'order_id' => $order->get_id(), + ) ); + return; + } + + $data = array( + 'order_id' => $order->get_id(), + 'date_created' => $order->get_date_created()->date( 'Y-m-d H:i:s' ), + 'num_items_sold' => self::get_num_items_sold( $order ), + 'gross_total' => $order->get_total(), + 'coupon_total' => $order->get_total_discount(), + 'refund_total' => $order->get_total_refunded(), + 'tax_total' => $order->get_total_tax(), + 'shipping_total' => $order->get_shipping_total(), + 'net_total' => $order->get_total() - $order->get_total_tax() - $order->get_shipping_total(), + ); + + // Update or add the information to the DB. + return $wpdb->replace( + $table_name, + $data, + array( + '%d', + '%s', + '%d', + '%f', + '%f', + '%f', + '%f', + '%f', + '%f', + ) + ); + } + + /** + * Calculation methods. + */ + + /** + * Get number of items sold among all orders. + * + * @param array $order WC_Order object. + * @return int + */ + protected static function get_num_items_sold( $order ) { + $num_items = 0; + + $line_items = $order->get_items( 'line_item' ); + foreach ( $line_items as $line_item ) { + $num_items += $line_item->get_quantity(); + } + + return $num_items; + } +} diff --git a/plugins/woocommerce-admin/includes/data-stores/class-wc-admin-reports-products-data-store.php b/plugins/woocommerce-admin/includes/data-stores/class-wc-admin-reports-products-data-store.php new file mode 100644 index 00000000000..334532a1488 --- /dev/null +++ b/plugins/woocommerce-admin/includes/data-stores/class-wc-admin-reports-products-data-store.php @@ -0,0 +1,228 @@ + 'strval', + 'date_end' => 'strval', + 'product_id' => 'intval', + 'items_sold' => 'intval', + 'gross_revenue' => 'floatval', + 'orders_count' => 'intval', + // Extended attributes. + 'name' => 'strval', + 'price' => 'floatval', + 'image' => 'strval', + 'permalink' => 'strval', + ); + + /** + * SQL columns to select in the db query and their mapping to SQL code. + * + * @var array + */ + protected $report_columns = array( + 'product_id' => 'product_id', + 'items_sold' => 'SUM(product_qty) as items_sold', + 'gross_revenue' => 'SUM(product_gross_revenue) AS gross_revenue', + 'orders_count' => 'COUNT(DISTINCT order_id) as orders_count', + ); + + /** + * Extended product attributes to include in the data. + * + * @var array + */ + protected $extended_attributes = array( + 'name', + 'price', + 'image', + 'permalink', + ); + + + /** + * Updates the database query with parameters used for Products report: categories and order status. + * + * @param array $query_args Query arguments supplied by the user. + * @return array Array of parameters used for SQL query. + */ + protected function get_sql_query_params( $query_args ) { + global $wpdb; + + $sql_query_params = $this->get_time_period_sql_params( $query_args ); + $sql_query_params = array_merge( $sql_query_params, $this->get_limit_sql_params( $query_args ) ); + $sql_query_params = array_merge( $sql_query_params, $this->get_order_by_sql_params( $query_args ) ); + + $order_product_lookup_table = $wpdb->prefix . self::TABLE_NAME; + $allowed_products = $this->get_allowed_products( $query_args ); + + if ( count( $allowed_products ) > 0 ) { + $allowed_products_str = implode( ',', $allowed_products ); + $sql_query_params['where_clause'] .= " AND {$order_product_lookup_table}.product_id IN ({$allowed_products_str})"; + } + + if ( is_array( $query_args['order_status'] ) && count( $query_args['order_status'] ) > 0 ) { + $statuses = array_map( array( $this, 'normalize_order_status' ), $query_args['order_status'] ); + + $sql_query_params['from_clause'] .= " JOIN {$wpdb->prefix}posts ON {$order_product_lookup_table}.order_id = {$wpdb->prefix}posts.ID"; + $sql_query_params['where_clause'] .= " AND {$wpdb->prefix}posts.post_status IN ( '" . implode( "','", $statuses ) . "' ) "; + } + + return $sql_query_params; + } + + /** + * Maps ordering specified by the user to columns in the database/fields in the data. + * + * @param string $order_by Sorting criterion. + * @return string + */ + protected function normalize_order_by( $order_by ) { + if ( 'date' === $order_by ) { + return 'date_created'; + } + + return $order_by; + } + + /** + * Enriches the product data with attributes specified by the extended_attributes. + * + * @param array $products_data Product data. + */ + protected function include_extended_product_info( &$products_data ) { + foreach ( $products_data as $key => $product_data ) { + $product = wc_get_product( $product_data['product_id'] ); + $extended_attributes = apply_filters( 'woocommerce_rest_reports_products_extended_attributes', $this->extended_attributes, $product_data ); + foreach ( $extended_attributes as $extended_attribute ) { + $function = 'get_' . $extended_attribute; + if ( is_callable( array( $product, $function ) ) ) { + $value = $product->{$function}(); + $products_data[ $key ][ $extended_attribute ] = $value; + } + } + } + } + + /** + * Returns the report data based on parameters supplied by the user. + * + * @param array $query_args Query parameters. + * @return stdClass|WP_Error Data. + */ + public function get_data( $query_args ) { + global $wpdb; + + $table_name = $wpdb->prefix . self::TABLE_NAME; + $now = time(); + $week_back = $now - WEEK_IN_SECONDS; + + // These defaults are only partially applied when used via REST API, as that has its own defaults. + $defaults = array( + 'per_page' => get_option( 'posts_per_page' ), + 'page' => 1, + 'order' => 'DESC', + 'orderby' => 'date', + 'before' => date( WC_Admin_Reports_Interval::$iso_datetime_format, $now ), + 'after' => date( WC_Admin_Reports_Interval::$iso_datetime_format, $week_back ), + 'fields' => '*', + 'categories' => array(), + 'products' => array(), + 'extended_product_info' => false, + // This is not a parameter for products reports per se, but we want to only take into account selected order types. + 'order_status' => parent::get_report_order_statuses(), + + ); + $query_args = wp_parse_args( $query_args, $defaults ); + + $cache_key = $this->get_cache_key( $query_args ); + $data = wp_cache_get( $cache_key, $this->cache_group ); + + if ( false === $data ) { + + $selections = $this->selected_columns( $query_args ); + $sql_query_params = $this->get_sql_query_params( $query_args ); + + $db_records_count = (int) $wpdb->get_var( + "SELECT COUNT(*) FROM ( + SELECT + product_id + FROM + {$table_name} + {$sql_query_params['from_clause']} + WHERE + 1=1 + {$sql_query_params['where_clause']} + GROUP BY + product_id + ) AS tt" + ); // WPCS: cache ok, DB call ok, unprepared SQL ok. + + $total_pages = (int) ceil( $db_records_count / $sql_query_params['per_page'] ); + if ( $query_args['page'] < 1 || $query_args['page'] > $total_pages ) { + return array(); + } + + $product_data = $wpdb->get_results( + "SELECT + {$selections} + FROM + {$table_name} + {$sql_query_params['from_clause']} + WHERE + 1=1 + {$sql_query_params['where_clause']} + GROUP BY + product_id + ORDER BY + {$sql_query_params['order_by_clause']} + {$sql_query_params['limit']} + ", ARRAY_A + ); // WPCS: cache ok, DB call ok, unprepared SQL ok. + + if ( null === $product_data ) { + return new WP_Error( 'woocommerce_reports_products_result_failed', __( 'Sorry, fetching revenue data failed.', 'woocommerce' ) ); + } + + if ( $query_args['extended_product_info'] ) { + $this->include_extended_product_info( $product_data ); + } + $product_data = array_map( array( $this, 'cast_numbers' ), $product_data ); + $data = (object) array( + 'data' => $product_data, + 'total' => $db_records_count, + 'pages' => $total_pages, + 'page_no' => (int) $query_args['page'], + ); + + wp_cache_set( $cache_key, $data, $this->cache_group ); + } + + return $data; + } + +} diff --git a/plugins/woocommerce-admin/includes/data-stores/class-wc-admin-reports-products-stats-data-store.php b/plugins/woocommerce-admin/includes/data-stores/class-wc-admin-reports-products-stats-data-store.php new file mode 100644 index 00000000000..731498a6cf1 --- /dev/null +++ b/plugins/woocommerce-admin/includes/data-stores/class-wc-admin-reports-products-stats-data-store.php @@ -0,0 +1,200 @@ + 'strval', + 'date_end' => 'strval', + 'product_id' => 'intval', + 'items_sold' => 'intval', + 'gross_revenue' => 'floatval', + 'orders_count' => 'intval', + 'products_count' => 'intval', + ); + + /** + * SQL columns to select in the db query. + * + * @var array + */ + protected $report_columns = array( + 'items_sold' => 'SUM(product_qty) as items_sold', + 'gross_revenue' => 'SUM(product_gross_revenue) AS gross_revenue', + 'orders_count' => 'COUNT(DISTINCT order_id) as orders_count', + 'products_count' => 'COUNT(DISTINCT product_id) as products_count', + ); + + /** + * Updates the database query with parameters used for Products Stats report: categories and order status. + * + * @param array $query_args Query arguments supplied by the user. + * @param array $totals_params SQL parameters for the totals query. + * @param array $intervals_params SQL parameters for the intervals query. + */ + protected function update_sql_query_params( $query_args, &$totals_params, &$intervals_params ) { + global $wpdb; + + $allowed_products = $this->get_allowed_products( $query_args ); + $products_where_clause = ''; + $products_from_clause = ''; + + $order_product_lookup_table = $wpdb->prefix . self::TABLE_NAME; + if ( count( $allowed_products ) > 0 ) { + $allowed_products_str = implode( ',', $allowed_products ); + $products_where_clause .= " AND {$order_product_lookup_table}.product_id IN ({$allowed_products_str})"; + } + + if ( is_array( $query_args['order_status'] ) && count( $query_args['order_status'] ) > 0 ) { + $statuses = array_map( array( $this, 'normalize_order_status' ), $query_args['order_status'] ); + + $products_from_clause .= " JOIN {$wpdb->prefix}posts ON {$order_product_lookup_table}.order_id = {$wpdb->prefix}posts.ID"; + $products_where_clause .= " AND {$wpdb->prefix}posts.post_status IN ( '" . implode( "','", $statuses ) . "' ) "; + } + + $totals_params = array_merge( $totals_params, $this->get_time_period_sql_params( $query_args ) ); + $totals_params['where_clause'] .= $products_where_clause; + $totals_params['from_clause'] .= $products_from_clause; + + $intervals_params = array_merge( $intervals_params, $this->get_intervals_sql_params( $query_args ) ); + $intervals_params['where_clause'] .= $products_where_clause; + $intervals_params['from_clause'] .= $products_from_clause; + } + + /** + * Returns the report data based on parameters supplied by the user. + * + * @since 3.5.0 + * @param array $query_args Query parameters. + * @return stdClass|WP_Error Data. + */ + public function get_data( $query_args ) { + global $wpdb; + + $table_name = $wpdb->prefix . self::TABLE_NAME; + $now = time(); + $week_back = $now - WEEK_IN_SECONDS; + + // These defaults are only partially applied when used via REST API, as that has its own defaults. + $defaults = array( + 'per_page' => get_option( 'posts_per_page' ), + 'page' => 1, + 'order' => 'DESC', + 'orderby' => 'date', + 'before' => date( WC_Admin_Reports_Interval::$iso_datetime_format, $now ), + 'after' => date( WC_Admin_Reports_Interval::$iso_datetime_format, $week_back ), + 'fields' => '*', + 'categories' => array(), + 'interval' => 'week', + 'products' => array(), + // This is not a parameter for products reports per se, but we should probably restricts order statuses here, too. + 'order_status' => parent::get_report_order_statuses(), + ); + $query_args = wp_parse_args( $query_args, $defaults ); + + $cache_key = $this->get_cache_key( $query_args ); + $product_data = wp_cache_get( $cache_key, $this->cache_group ); + + if ( false === $product_data ) { + $selections = $this->selected_columns( $query_args ); + $totals_query = array(); + $intervals_query = array(); + $this->update_sql_query_params( $query_args, $totals_query, $intervals_query ); + + $db_records_count = (int) $wpdb->get_var( + "SELECT COUNT(*) FROM ( + SELECT + {$intervals_query['select_clause']} AS time_interval + FROM + {$table_name} + {$intervals_query['from_clause']} + WHERE + 1=1 + {$intervals_query['where_clause']} + GROUP BY + time_interval + ) AS t" + ); // WPCS: cache ok, DB call ok, unprepared SQL ok. + + $total_pages = (int) ceil( $db_records_count / $intervals_query['per_page'] ); + if ( $query_args['page'] < 1 || $query_args['page'] > $total_pages ) { + return array(); + } + + $totals = $wpdb->get_results( + "SELECT + {$selections} + FROM + {$table_name} + {$totals_query['from_clause']} + WHERE + 1=1 + {$totals_query['where_clause']}", ARRAY_A + ); // WPCS: cache ok, DB call ok, unprepared SQL ok. + + if ( null === $totals ) { + return new WP_Error( 'woocommerce_reports_products_stats_result_failed', __( 'Sorry, fetching revenue data failed.', 'woocommerce' ) ); + } + + if ( '' !== $selections ) { + $selections = ', ' . $selections; + } + + $intervals = $wpdb->get_results( + "SELECT + MAX(date_created) AS datetime_anchor, + {$intervals_query['select_clause']} AS time_interval + {$selections} + FROM + {$table_name} + {$intervals_query['from_clause']} + WHERE + 1=1 + {$intervals_query['where_clause']} + GROUP BY + time_interval + ORDER BY + {$intervals_query['order_by_clause']} + {$intervals_query['limit']}", ARRAY_A + ); // WPCS: cache ok, DB call ok, unprepared SQL ok. + + if ( null === $intervals ) { + return new WP_Error( 'woocommerce_reports_products_stats_result_failed', __( 'Sorry, fetching revenue data failed.', 'woocommerce' ) ); + } + + $totals = (object) $this->cast_numbers( $totals[0] ); + + $this->update_interval_boundary_dates( $query_args['after'], $query_args['before'], $query_args['interval'], $intervals ); + $this->create_interval_subtotals( $intervals ); + + $data = (object) array( + 'totals' => $totals, + 'intervals' => $intervals, + 'total' => $db_records_count, + 'pages' => $total_pages, + 'page_no' => (int) $query_args['page'], + ); + + wp_cache_set( $cache_key, $data, $this->cache_group ); + } + + return $data; + } + +} diff --git a/plugins/woocommerce-admin/includes/interfaces/class-wc-admin-reports-data-store-interface.php b/plugins/woocommerce-admin/includes/interfaces/class-wc-admin-reports-data-store-interface.php new file mode 100644 index 00000000000..528cf83dd99 --- /dev/null +++ b/plugins/woocommerce-admin/includes/interfaces/class-wc-admin-reports-data-store-interface.php @@ -0,0 +1,27 @@ +get_items() as $order_item ) { + $wpdb->replace( + $wpdb->prefix . 'wc_admin_order_product_lookup', + array( + 'order_item_id' => $order_item->get_id(), + 'order_id' => $order->get_id(), + 'product_id' => $order_item->get_product_id( 'edit' ), + 'customer_id' => ( 0 < $order->get_customer_id( 'edit' ) ) ? $order->get_customer_id( 'edit' ) : null, + 'product_qty' => $order_item->get_quantity( 'edit' ), + 'product_gross_revenue' => $order_item->get_subtotal( 'edit' ), + 'date_created' => date( 'Y-m-d H:i:s', $order->get_date_created( 'edit' )->getTimestamp() ), + ), + array( + '%d', + '%d', + '%d', + '%d', + '%d', + '%f', + '%s', + ) + ); + } +} +add_action( 'save_post', 'wc_admin_order_product_lookup_entry', 10, 1 ); diff --git a/plugins/woocommerce-admin/wc-admin.php b/plugins/woocommerce-admin/wc-admin.php index bb1cb3bc19a..2c31eb7d68a 100755 --- a/plugins/woocommerce-admin/wc-admin.php +++ b/plugins/woocommerce-admin/wc-admin.php @@ -40,6 +40,9 @@ function wc_admin_plugins_loaded() { return; } + // Initialize the WC API extensions. + require_once dirname( __FILE__ ) . 'includes/class-wc-admin-api-init.php'; + // Some common utilities require_once dirname( __FILE__ ) . '/lib/common.php'; From bd295af793c27e8c9a634832b7675215dace69d1 Mon Sep 17 00:00:00 2001 From: Peter Fabian Date: Mon, 17 Sep 2018 16:54:31 +0200 Subject: [PATCH 02/20] PHPCS (require_once is not a function). --- .../includes/class-wc-admin-api-init.php | 54 +++++++++---------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/plugins/woocommerce-admin/includes/class-wc-admin-api-init.php b/plugins/woocommerce-admin/includes/class-wc-admin-api-init.php index e0254a6f7f7..9bb425dee8e 100644 --- a/plugins/woocommerce-admin/includes/class-wc-admin-api-init.php +++ b/plugins/woocommerce-admin/includes/class-wc-admin-api-init.php @@ -22,39 +22,39 @@ class WC_Admin_Api_Init { public function init_classes() { // Interfaces. - require_once( dirname( __FILE__ ) . '/interfaces/class-wc-admin-reports-data-store-interface.php' ); + require_once dirname( __FILE__ ) . '/interfaces/class-wc-admin-reports-data-store-interface.php'; // Query classes for reports. - require_once( dirname( __FILE__ ) . '/class-wc-admin-reports-revenue-query.php' ); - require_once( dirname( __FILE__ ) . '/class-wc-admin-reports-orders-stats-query.php' ); - require_once( dirname( __FILE__ ) . '/class-wc-admin-reports-products-query.php' ); - require_once( dirname( __FILE__ ) . '/class-wc-admin-reports-products-stats-query.php' ); - require_once( dirname( __FILE__ ) . '/class-wc-admin-reports-categories-query.php' ); + require_once dirname( __FILE__ ) . '/class-wc-admin-reports-revenue-query.php'; + require_once dirname( __FILE__ ) . '/class-wc-admin-reports-orders-stats-query.php'; + require_once dirname( __FILE__ ) . '/class-wc-admin-reports-products-query.php'; + require_once dirname( __FILE__ ) . '/class-wc-admin-reports-products-stats-query.php'; + require_once dirname( __FILE__ ) . '/class-wc-admin-reports-categories-query.php'; // Reports data stores. - require_once( dirname( __FILE__ ) . '/data-stores/class-wc-admin-reports-data-store.php' ); - require_once( dirname( __FILE__ ) . '/data-stores/class-wc-admin-reports-orders-data-store.php' ); - require_once( dirname( __FILE__ ) . '/data-stores/class-wc-admin-reports-products-data-store.php' ); - require_once( dirname( __FILE__ ) . '/data-stores/class-wc-admin-reports-products-stats-data-store.php' ); - require_once( dirname( __FILE__ ) . '/data-stores/class-wc-admin-reports-categories-data-store.php' ); + require_once dirname( __FILE__ ) . '/data-stores/class-wc-admin-reports-data-store.php'; + require_once dirname( __FILE__ ) . '/data-stores/class-wc-admin-reports-orders-data-store.php'; + require_once dirname( __FILE__ ) . '/data-stores/class-wc-admin-reports-products-data-store.php'; + require_once dirname( __FILE__ ) . '/data-stores/class-wc-admin-reports-products-stats-data-store.php'; + require_once dirname( __FILE__ ) . '/data-stores/class-wc-admin-reports-categories-data-store.php'; } public function rest_api_init() { - require_once( dirname( __FILE__ ) . '/api/class-wc-admin-rest-reports-controller.php' ); - require_once( dirname( __FILE__ ) . '/api/class-wc-admin-rest-system-status-tools-controller.php' ); - require_once( dirname( __FILE__ ) . '/api/class-wc-admin-rest-reports-categories-controller.php' ); - require_once( dirname( __FILE__ ) . '/api/class-wc-admin-rest-reports-coupons-controller.php' ); - require_once( dirname( __FILE__ ) . '/api/class-wc-admin-rest-reports-coupons-stats-controller.php' ); - require_once( dirname( __FILE__ ) . '/api/class-wc-admin-rest-reports-customers-controller.php' ); - require_once( dirname( __FILE__ ) . '/api/class-wc-admin-rest-reports-downloads-controller.php' ); - require_once( dirname( __FILE__ ) . '/api/class-wc-admin-rest-reports-downloads-files-controller.php' ); - require_once( dirname( __FILE__ ) . '/api/class-wc-admin-rest-reports-downloads-stats-controller.php' ); - require_once( dirname( __FILE__ ) . '/api/class-wc-admin-rest-reports-orders-stats-controller.php' ); - require_once( dirname( __FILE__ ) . '/api/class-wc-admin-rest-reports-products-controller.php' ); - require_once( dirname( __FILE__ ) . '/api/class-wc-admin-rest-reports-products-stats-controller.php' ); - require_once( dirname( __FILE__ ) . '/api/class-wc-admin-rest-reports-revenue-stats-controller.php' ); - require_once( dirname( __FILE__ ) . '/api/class-wc-admin-rest-reports-taxes-controller.php' ); - require_once( dirname( __FILE__ ) . '/api/class-wc-admin-rest-reports-taxes-stats-controller.php' ); + require_once dirname( __FILE__ ) . '/api/class-wc-admin-rest-reports-controller.php'; + require_once dirname( __FILE__ ) . '/api/class-wc-admin-rest-system-status-tools-controller.php'; + require_once dirname( __FILE__ ) . '/api/class-wc-admin-rest-reports-categories-controller.php'; + require_once dirname( __FILE__ ) . '/api/class-wc-admin-rest-reports-coupons-controller.php'; + require_once dirname( __FILE__ ) . '/api/class-wc-admin-rest-reports-coupons-stats-controller.php'; + require_once dirname( __FILE__ ) . '/api/class-wc-admin-rest-reports-customers-controller.php'; + require_once dirname( __FILE__ ) . '/api/class-wc-admin-rest-reports-downloads-controller.php'; + require_once dirname( __FILE__ ) . '/api/class-wc-admin-rest-reports-downloads-files-controller.php'; + require_once dirname( __FILE__ ) . '/api/class-wc-admin-rest-reports-downloads-stats-controller.php'; + require_once dirname( __FILE__ ) . '/api/class-wc-admin-rest-reports-orders-stats-controller.php'; + require_once dirname( __FILE__ ) . '/api/class-wc-admin-rest-reports-products-controller.php'; + require_once dirname( __FILE__ ) . '/api/class-wc-admin-rest-reports-products-stats-controller.php'; + require_once dirname( __FILE__ ) . '/api/class-wc-admin-rest-reports-revenue-stats-controller.php'; + require_once dirname( __FILE__ ) . '/api/class-wc-admin-rest-reports-taxes-controller.php'; + require_once dirname( __FILE__ ) . '/api/class-wc-admin-rest-reports-taxes-stats-controller.php'; $controllers = array( 'WC_Admin_REST_Reports_Controller', @@ -189,7 +189,7 @@ class WC_Admin_Api_Init { } protected function create_db_tables() { - require_once(ABSPATH . 'wp-admin/includes/upgrade.php'); + require_once ABSPATH . 'wp-admin/includes/upgrade.php'; dbDelta( self::get_schema() ); } From a39fc58da2d854ab3c5a1a612f28360ae4feb144 Mon Sep 17 00:00:00 2001 From: Peter Fabian Date: Mon, 17 Sep 2018 19:53:57 +0200 Subject: [PATCH 03/20] Added missing vars. --- .../woocommerce-admin/includes/class-wc-admin-api-init.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/plugins/woocommerce-admin/includes/class-wc-admin-api-init.php b/plugins/woocommerce-admin/includes/class-wc-admin-api-init.php index 9bb425dee8e..ab729020aab 100644 --- a/plugins/woocommerce-admin/includes/class-wc-admin-api-init.php +++ b/plugins/woocommerce-admin/includes/class-wc-admin-api-init.php @@ -76,7 +76,7 @@ class WC_Admin_Api_Init { WC_Admin_Reports_Orders_Data_Store::init(); } - public static function order_product_lookup_store_init() { + public static function order_product_lookup_store_init( $updater = false ) { global $wpdb; $orders = get_transient( 'wc_update_350_all_orders' ); @@ -141,6 +141,8 @@ class WC_Admin_Api_Init { } public static function add_report_tables( $wc_tables ) { + global $wpdb; + return array_merge( $wc_tables, array( From a92a78ce9d6e9011aaa4483121e41efd9f98f2f8 Mon Sep 17 00:00:00 2001 From: Peter Fabian Date: Mon, 17 Sep 2018 20:19:36 +0200 Subject: [PATCH 04/20] Fixed order of initialization so that classes are loaded in correct order. --- plugins/woocommerce-admin/includes/class-wc-admin-api-init.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/woocommerce-admin/includes/class-wc-admin-api-init.php b/plugins/woocommerce-admin/includes/class-wc-admin-api-init.php index ab729020aab..8575e82f41c 100644 --- a/plugins/woocommerce-admin/includes/class-wc-admin-api-init.php +++ b/plugins/woocommerce-admin/includes/class-wc-admin-api-init.php @@ -5,7 +5,7 @@ class WC_Admin_Api_Init { public function __construct() { // Initialize classes. - add_action( 'plugins_loaded', array( $this, 'init_classes' ) ); + add_action( 'plugins_loaded', array( $this, 'init_classes' ), 19 ); // Hook in data stores. add_filter( 'woocommerce_data_stores', array( 'WC_Admin_Api_Init', 'add_data_stores' ) ); // Add wc-admin report tables to list of WooCommerce tables. From e692c45c877268ab8c416f0cbb103699c1a14679 Mon Sep 17 00:00:00 2001 From: Peter Fabian Date: Mon, 17 Sep 2018 20:20:00 +0200 Subject: [PATCH 05/20] Added missing path separator. --- plugins/woocommerce-admin/wc-admin.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/woocommerce-admin/wc-admin.php b/plugins/woocommerce-admin/wc-admin.php index 2c31eb7d68a..b59618c29e3 100755 --- a/plugins/woocommerce-admin/wc-admin.php +++ b/plugins/woocommerce-admin/wc-admin.php @@ -41,7 +41,7 @@ function wc_admin_plugins_loaded() { } // Initialize the WC API extensions. - require_once dirname( __FILE__ ) . 'includes/class-wc-admin-api-init.php'; + require_once dirname( __FILE__ ) . '/includes/class-wc-admin-api-init.php'; // Some common utilities require_once dirname( __FILE__ ) . '/lib/common.php'; From aa4035adb96a1b1065d3ef39ef91f1b975bb00bd Mon Sep 17 00:00:00 2001 From: Peter Fabian Date: Mon, 17 Sep 2018 20:20:34 +0200 Subject: [PATCH 06/20] Added WC_Admin prefix for query classes where it was missing. --- .../includes/class-wc-admin-reports-interval.php | 5 +---- .../class-wc-admin-reports-orders-stats-query.php | 14 ++++++-------- .../class-wc-admin-reports-products-query.php | 10 ++++------ ...class-wc-admin-reports-products-stats-query.php | 3 --- .../includes/class-wc-admin-reports-query.php | 9 +++------ .../class-wc-admin-reports-revenue-query.php | 11 ++++------- 6 files changed, 18 insertions(+), 34 deletions(-) diff --git a/plugins/woocommerce-admin/includes/class-wc-admin-reports-interval.php b/plugins/woocommerce-admin/includes/class-wc-admin-reports-interval.php index b770e808066..9f7928969a2 100644 --- a/plugins/woocommerce-admin/includes/class-wc-admin-reports-interval.php +++ b/plugins/woocommerce-admin/includes/class-wc-admin-reports-interval.php @@ -2,9 +2,7 @@ /** * Class for time interval handling for reports * - * @package WooCommerce/Classes - * @version 3.5.0 - * @since 3.5.0 + * @package WooCommerce Admin/Classes */ defined( 'ABSPATH' ) || exit; @@ -66,7 +64,6 @@ class WC_Admin_Reports_Interval { * @return int|null */ public static function quarter( $datetime ) { - // TODO: is there a smarter way? Is floor((m - 1)/3) + 1 better? switch ( (int) $datetime->format( 'm' ) ) { case 1: case 2: diff --git a/plugins/woocommerce-admin/includes/class-wc-admin-reports-orders-stats-query.php b/plugins/woocommerce-admin/includes/class-wc-admin-reports-orders-stats-query.php index 12d91b7bcc0..04b153928a7 100644 --- a/plugins/woocommerce-admin/includes/class-wc-admin-reports-orders-stats-query.php +++ b/plugins/woocommerce-admin/includes/class-wc-admin-reports-orders-stats-query.php @@ -11,22 +11,20 @@ * 'coupons' => array(138), * 'order_status' => array('completed'), * ); - * $report = new WC_Reports_Orders_Stats_Query( $args ); + * $report = new WC_Admin_Reports_Orders_Stats_Query( $args ); * $mydata = $report->get_data(); * - * @package WooCommerce/Classes - * @version 3.5.0 - * @since 3.5.0 + * @package WooCommerce Admin/Classes + */ defined( 'ABSPATH' ) || exit; /** - * WC_Reports_Orders_Stats_Query + * WC_Admin_Reports_Orders_Stats_Query * - * @version 3.5.0 */ -class WC_Reports_Orders_Stats_Query extends WC_Reports_Query { +class WC_Admin_Reports_Orders_Stats_Query extends WC_Admin_Reports_Query { const REPORT_NAME = 'report-orders-stats'; @@ -57,4 +55,4 @@ class WC_Reports_Orders_Stats_Query extends WC_Reports_Query { return apply_filters( 'woocommerce_reports_orders_stats_select_query', $results, $args ); } -} \ No newline at end of file +} diff --git a/plugins/woocommerce-admin/includes/class-wc-admin-reports-products-query.php b/plugins/woocommerce-admin/includes/class-wc-admin-reports-products-query.php index b969b60c1ea..f32835851c6 100644 --- a/plugins/woocommerce-admin/includes/class-wc-admin-reports-products-query.php +++ b/plugins/woocommerce-admin/includes/class-wc-admin-reports-products-query.php @@ -10,12 +10,11 @@ * 'categories' => array(15, 18), * 'products' => array(1,2,3) * ); - * $report = new WC_Reports_Products_Query( $args ); + * $report = new WC_Admin_Reports_Products_Query( $args ); * $mydata = $report->get_data(); * - * @package WooCommerce/Classes - * @version 3.5.0 - * @since 3.5.0 + * @package WooCommerce Admin/Classes + */ defined( 'ABSPATH' ) || exit; @@ -23,9 +22,8 @@ defined( 'ABSPATH' ) || exit; /** * WC_Reports_Products_Query * - * @version 3.5.0 */ -class WC_Reports_Products_Query extends WC_Reports_Query { +class WC_Admin_Reports_Products_Query extends WC_Admin_Reports_Query { const REPORT_NAME = 'report-products'; diff --git a/plugins/woocommerce-admin/includes/class-wc-admin-reports-products-stats-query.php b/plugins/woocommerce-admin/includes/class-wc-admin-reports-products-stats-query.php index ff7101edc75..2655ac36dde 100644 --- a/plugins/woocommerce-admin/includes/class-wc-admin-reports-products-stats-query.php +++ b/plugins/woocommerce-admin/includes/class-wc-admin-reports-products-stats-query.php @@ -14,8 +14,6 @@ * $mydata = $report->get_data(); * * @package WooCommerce Admin/Classes - * @version 3.5.0 - * @since 3.5.0 */ defined( 'ABSPATH' ) || exit; @@ -23,7 +21,6 @@ defined( 'ABSPATH' ) || exit; /** * WC_Admin_Reports_Products_Query * - * @version 3.5.0 */ class WC_Admin_Reports_Products_Stats_Query extends WC_Admin_Reports_Query { diff --git a/plugins/woocommerce-admin/includes/class-wc-admin-reports-query.php b/plugins/woocommerce-admin/includes/class-wc-admin-reports-query.php index 59f55548ffa..87a6dd15904 100644 --- a/plugins/woocommerce-admin/includes/class-wc-admin-reports-query.php +++ b/plugins/woocommerce-admin/includes/class-wc-admin-reports-query.php @@ -2,19 +2,16 @@ /** * Class for parameter-based Reports querying * - * @package WooCommerce/Classes - * @version 3.5.0 - * @since 3.5.0 + * @package WooCommerce Admin/Classes */ defined( 'ABSPATH' ) || exit; /** - * WC_Reports_Query + * WC_Admin_Reports_Query * - * @version 3.5.0 */ -abstract class WC_Reports_Query extends WC_Object_Query { +abstract class WC_Admin_Reports_Query extends WC_Object_Query { /** * Get report data matching the current query vars. diff --git a/plugins/woocommerce-admin/includes/class-wc-admin-reports-revenue-query.php b/plugins/woocommerce-admin/includes/class-wc-admin-reports-revenue-query.php index decc20e6132..03c23ce4365 100644 --- a/plugins/woocommerce-admin/includes/class-wc-admin-reports-revenue-query.php +++ b/plugins/woocommerce-admin/includes/class-wc-admin-reports-revenue-query.php @@ -8,22 +8,19 @@ * 'after' => '2018-07-05 00:00:00', * 'interval' => 'week', * ); - * $report = new WC_Reports_Revenue_Query( $args ); + * $report = new WC_Admin_Reports_Revenue_Query( $args ); * $mydata = $report->get_data(); * - * @package WooCommerce/Classes - * @version 3.5.0 - * @since 3.5.0 + * @package WooCommerce Admin/Classes */ defined( 'ABSPATH' ) || exit; /** - * WC_Reports_Revenue_Query + * WC_Admin_Reports_Revenue_Query * - * @version 3.5.0 */ -class WC_Reports_Revenue_Query extends WC_Reports_Query { +class WC_Admin_Reports_Revenue_Query extends WC_Admin_Reports_Query { const REPORT_NAME = 'report-revenue-stats'; From 405703c17ade7e6d1c0b40eb61715532ced7e510 Mon Sep 17 00:00:00 2001 From: Peter Fabian Date: Mon, 17 Sep 2018 20:35:23 +0200 Subject: [PATCH 07/20] Added missing interface. --- .../class-wc-reports-data-store-interface.php | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 plugins/woocommerce-admin/includes/interfaces/class-wc-reports-data-store-interface.php diff --git a/plugins/woocommerce-admin/includes/interfaces/class-wc-reports-data-store-interface.php b/plugins/woocommerce-admin/includes/interfaces/class-wc-reports-data-store-interface.php new file mode 100644 index 00000000000..c17936b9e6a --- /dev/null +++ b/plugins/woocommerce-admin/includes/interfaces/class-wc-reports-data-store-interface.php @@ -0,0 +1,26 @@ + Date: Mon, 17 Sep 2018 20:36:03 +0200 Subject: [PATCH 08/20] Added required includes. --- plugins/woocommerce-admin/includes/class-wc-admin-api-init.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plugins/woocommerce-admin/includes/class-wc-admin-api-init.php b/plugins/woocommerce-admin/includes/class-wc-admin-api-init.php index 8575e82f41c..4dab0b743ab 100644 --- a/plugins/woocommerce-admin/includes/class-wc-admin-api-init.php +++ b/plugins/woocommerce-admin/includes/class-wc-admin-api-init.php @@ -23,6 +23,9 @@ class WC_Admin_Api_Init { public function init_classes() { // Interfaces. require_once dirname( __FILE__ ) . '/interfaces/class-wc-admin-reports-data-store-interface.php'; + require_once dirname( __FILE__ ) . '/interfaces/class-wc-reports-data-store-interface.php'; + require_once dirname( __FILE__ ) . '/class-wc-admin-reports-query.php'; + // Query classes for reports. require_once dirname( __FILE__ ) . '/class-wc-admin-reports-revenue-query.php'; From 1b71184f2346f9c6f359db1d9f0f66728f7a5aca Mon Sep 17 00:00:00 2001 From: Peter Fabian Date: Mon, 17 Sep 2018 20:36:29 +0200 Subject: [PATCH 09/20] Updated path to background process class. --- .../data-stores/class-wc-admin-reports-orders-data-store.php | 2 +- plugins/woocommerce-admin/wc-admin.php | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/plugins/woocommerce-admin/includes/data-stores/class-wc-admin-reports-orders-data-store.php b/plugins/woocommerce-admin/includes/data-stores/class-wc-admin-reports-orders-data-store.php index 6f1bb8fb4cf..131de190c59 100644 --- a/plugins/woocommerce-admin/includes/data-stores/class-wc-admin-reports-orders-data-store.php +++ b/plugins/woocommerce-admin/includes/data-stores/class-wc-admin-reports-orders-data-store.php @@ -8,7 +8,7 @@ defined( 'ABSPATH' ) || exit; if ( ! class_exists( 'WC_Admin_Order_Stats_Background_Process', false ) ) { - include_once WC_ABSPATH . 'includes/class-wc-admin-order-stats-background-process.php'; + include_once WC_ADMIN_ABSPATH . '/includes/class-wc-admin-order-stats-background-process.php'; } /** diff --git a/plugins/woocommerce-admin/wc-admin.php b/plugins/woocommerce-admin/wc-admin.php index b59618c29e3..8426a7a706f 100755 --- a/plugins/woocommerce-admin/wc-admin.php +++ b/plugins/woocommerce-admin/wc-admin.php @@ -16,6 +16,10 @@ if ( ! defined( 'WC_ADMIN_APP' ) ) { define( 'WC_ADMIN_APP', 'wc-admin-app' ); } +if ( ! defined( 'WC_ADMIN_ABSPATH' ) ) { + define( 'WC_ADMIN_ABSPATH', dirname( __FILE__ ) ); +} + /** * Notify users of the plugin requirements */ From 7b2111ec05832209838542762ad5a3d5eca36d3c Mon Sep 17 00:00:00 2001 From: Peter Fabian Date: Mon, 17 Sep 2018 20:46:55 +0200 Subject: [PATCH 10/20] Added missing return statement. --- plugins/woocommerce-admin/includes/class-wc-admin-api-init.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/woocommerce-admin/includes/class-wc-admin-api-init.php b/plugins/woocommerce-admin/includes/class-wc-admin-api-init.php index 4dab0b743ab..9333b3412df 100644 --- a/plugins/woocommerce-admin/includes/class-wc-admin-api-init.php +++ b/plugins/woocommerce-admin/includes/class-wc-admin-api-init.php @@ -191,6 +191,8 @@ class WC_Admin_Api_Init { KEY customer_id (customer_id), KEY date_created (date_created) ) $collate;"; + + return $tables; } protected function create_db_tables() { From 1614d482cc93e86d212993afa3e753924300f04b Mon Sep 17 00:00:00 2001 From: Peter Fabian Date: Mon, 17 Sep 2018 20:56:04 +0200 Subject: [PATCH 11/20] Postponed class init only after registering post types. --- .../woocommerce-admin/includes/class-wc-admin-api-init.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/woocommerce-admin/includes/class-wc-admin-api-init.php b/plugins/woocommerce-admin/includes/class-wc-admin-api-init.php index 9333b3412df..b9ef4839f82 100644 --- a/plugins/woocommerce-admin/includes/class-wc-admin-api-init.php +++ b/plugins/woocommerce-admin/includes/class-wc-admin-api-init.php @@ -13,8 +13,8 @@ class WC_Admin_Api_Init { // REST API extensions init. add_action( 'rest_api_init', array( $this, 'rest_api_init' ) ); // Initialize report classes. - add_action( 'plugins_loaded', array( 'WC_Admin_Api_Init', 'orders_data_store_init' ), 20 ); - add_action( 'plugins_loaded', array( 'WC_Admin_Api_Init', 'order_product_lookup_store_init' ), 20 ); + add_action( 'woocommerce_after_register_post_type', array( 'WC_Admin_Api_Init', 'orders_data_store_init' ), 20 ); + add_action( 'woocommerce_after_register_post_type', array( 'WC_Admin_Api_Init', 'order_product_lookup_store_init' ), 20 ); // Create tables. $this->create_db_tables(); From 429803cc2555e53f0b1b02e6905378d05da3f7a6 Mon Sep 17 00:00:00 2001 From: Peter Fabian Date: Mon, 17 Sep 2018 21:01:44 +0200 Subject: [PATCH 12/20] Updated prefix for stats db table. --- .../woocommerce-admin/includes/class-wc-admin-api-init.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/woocommerce-admin/includes/class-wc-admin-api-init.php b/plugins/woocommerce-admin/includes/class-wc-admin-api-init.php index b9ef4839f82..9d703514aa6 100644 --- a/plugins/woocommerce-admin/includes/class-wc-admin-api-init.php +++ b/plugins/woocommerce-admin/includes/class-wc-admin-api-init.php @@ -91,7 +91,7 @@ class WC_Admin_Api_Init { set_transient( 'wc_update_350_all_orders', $orders, DAY_IN_SECONDS ); } - // Process orders untill close to running out of memory timeouts on large sites then requeue. + // Process orders until close to running out of memory timeouts on large sites then requeue. foreach ( $orders as $order_id ) { $order = wc_get_order( $order_id ); if ( ! $order ) { @@ -99,7 +99,7 @@ class WC_Admin_Api_Init { } foreach ( $order->get_items() as $order_item ) { $wpdb->replace( - $wpdb->prefix . 'wc_order_product_lookup', + $wpdb->prefix . 'wc_admin_order_product_lookup', array( 'order_item_id' => $order_item->get_id(), 'order_id' => $order->get_id(), From 9ac635fd424ac0a3e111ac689ec3bbaa618a6022 Mon Sep 17 00:00:00 2001 From: Peter Fabian Date: Mon, 17 Sep 2018 21:13:19 +0200 Subject: [PATCH 13/20] Updated prefixes for wc-admin plugin. --- ...class-wc-admin-rest-reports-controller.php | 45 ------------------- ...-rest-reports-coupons-stats-controller.php | 5 +-- ...dmin-rest-reports-customers-controller.php | 5 +-- ...dmin-rest-reports-downloads-controller.php | 5 +-- ...est-reports-downloads-files-controller.php | 5 +-- ...est-reports-downloads-stats-controller.php | 5 +-- ...n-rest-reports-orders-stats-controller.php | 7 ++- ...admin-rest-reports-products-controller.php | 5 +-- ...rest-reports-products-stats-controller.php | 5 +-- ...-rest-reports-revenue-stats-controller.php | 5 +-- ...wc-admin-rest-reports-taxes-controller.php | 5 +-- ...in-rest-reports-taxes-stats-controller.php | 5 +-- ...in-rest-system-status-tools-controller.php | 11 +++-- 13 files changed, 28 insertions(+), 85 deletions(-) diff --git a/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-controller.php b/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-controller.php index f1b9382b943..6d5cba84e10 100644 --- a/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-controller.php +++ b/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-controller.php @@ -31,51 +31,6 @@ class WC_Admin_REST_Reports_Controller extends WC_REST_Reports_Controller { */ protected $rest_base = 'reports'; - /** - * Get reports list. - * - * @since 3.5.0 - * @return array - */ - protected function get_reports() { - $reports = parent::get_reports(); - - $reports[] = array( - 'slug' => 'orders/count', - 'description' => __( 'Orders stats count.', 'woocommerce' ), - ); - $reports[] = array( - 'slug' => 'products/count', - 'description' => __( 'Customers stats count.', 'woocommerce' ), - ); - $reports[] = array( - 'slug' => 'customers/count', - 'description' => __( 'Customers stats count.', 'woocommerce' ), - ); - $reports[] = array( - 'slug' => 'coupons/count', - 'description' => __( 'Coupons stats count.', 'woocommerce' ), - ); - $reports[] = array( - 'slug' => 'reviews/count', - 'description' => __( 'Reviews stats count.', 'woocommerce' ), - ); - $reports[] = array( - 'slug' => 'categories/count', - 'description' => __( 'Categories stats count.', 'woocommerce' ), - ); - $reports[] = array( - 'slug' => 'tags/count', - 'description' => __( 'Tags stats count.', 'woocommerce' ), - ); - $reports[] = array( - 'slug' => 'attributes/count', - 'description' => __( 'Attributes stats count.', 'woocommerce' ), - ); - - return $reports; - } - /** * Register the routes for reports. */ diff --git a/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-coupons-stats-controller.php b/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-coupons-stats-controller.php index 6223ed67fa3..0aa3b8def5c 100644 --- a/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-coupons-stats-controller.php +++ b/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-coupons-stats-controller.php @@ -4,8 +4,7 @@ * * Handles requests to the /reports/coupons/stats endpoint. * - * @package WooCommerce/API - * @since 3.5.0 + * @package WooCommerce Admin/API */ defined( 'ABSPATH' ) || exit; @@ -16,7 +15,7 @@ defined( 'ABSPATH' ) || exit; * @package WooCommerce/API * @extends WC_REST_Reports_Controller */ -class WC_REST_Reports_Coupons_Stats_Controller extends WC_REST_Reports_Controller { +class WC_Admin_REST_Reports_Coupons_Stats_Controller extends WC_REST_Reports_Controller { /** * Endpoint namespace. diff --git a/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-customers-controller.php b/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-customers-controller.php index 86ed1cec0e2..9afef26c6a5 100644 --- a/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-customers-controller.php +++ b/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-customers-controller.php @@ -4,8 +4,7 @@ * * Handles requests to the /reports/customers endpoint. * - * @package WooCommerce/API - * @since 3.5.0 + * @package WooCommerce Admin/API */ defined( 'ABSPATH' ) || exit; @@ -16,7 +15,7 @@ defined( 'ABSPATH' ) || exit; * @package WooCommerce/API * @extends WC_REST_Reports_Controller */ -class WC_REST_Reports_Customers_Controller extends WC_REST_Reports_Controller { +class WC_Admin_REST_Reports_Customers_Controller extends WC_REST_Reports_Controller { /** * Endpoint namespace. diff --git a/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-downloads-controller.php b/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-downloads-controller.php index 1860bdb4f91..e70a3538c30 100644 --- a/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-downloads-controller.php +++ b/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-downloads-controller.php @@ -4,8 +4,7 @@ * * Handles requests to the /reports/downloads endpoint. * - * @package WooCommerce/API - * @since 3.5.0 + * @package WooCommerce Admin/API */ defined( 'ABSPATH' ) || exit; @@ -16,7 +15,7 @@ defined( 'ABSPATH' ) || exit; * @package WooCommerce/API * @extends WC_REST_Reports_Controller */ -class WC_REST_Reports_Downloads_Controller extends WC_REST_Reports_Controller { +class WC_Admin_REST_Reports_Downloads_Controller extends WC_REST_Reports_Controller { /** * Endpoint namespace. diff --git a/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-downloads-files-controller.php b/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-downloads-files-controller.php index c0284522b78..d6407f433c9 100644 --- a/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-downloads-files-controller.php +++ b/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-downloads-files-controller.php @@ -4,8 +4,7 @@ * * Handles requests to the /reports/downloads/files endpoint. * - * @package WooCommerce/API - * @since 3.5.0 + * @package WooCommerce Admin/API */ defined( 'ABSPATH' ) || exit; @@ -16,7 +15,7 @@ defined( 'ABSPATH' ) || exit; * @package WooCommerce/API * @extends WC_REST_Reports_Controller */ -class WC_REST_Reports_Downloads_Files_Controller extends WC_REST_Reports_Controller { +class WC_Admin_REST_Reports_Downloads_Files_Controller extends WC_REST_Reports_Controller { /** * Endpoint namespace. diff --git a/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-downloads-stats-controller.php b/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-downloads-stats-controller.php index e341ccba816..a1339e34806 100644 --- a/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-downloads-stats-controller.php +++ b/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-downloads-stats-controller.php @@ -4,8 +4,7 @@ * * Handles requests to the /reports/downloads/stats endpoint. * - * @package WooCommerce/API - * @since 3.5.0 + * @package WooCommerce Admin/API */ defined( 'ABSPATH' ) || exit; @@ -16,7 +15,7 @@ defined( 'ABSPATH' ) || exit; * @package WooCommerce/API * @extends WC_REST_Reports_Controller */ -class WC_REST_Reports_Downloads_Stats_Controller extends WC_REST_Reports_Controller { +class WC_Admin_REST_Reports_Downloads_Stats_Controller extends WC_REST_Reports_Controller { /** * Endpoint namespace. diff --git a/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-orders-stats-controller.php b/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-orders-stats-controller.php index 8e81118b608..790be3c6bd4 100644 --- a/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-orders-stats-controller.php +++ b/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-orders-stats-controller.php @@ -4,8 +4,7 @@ * * Handles requests to the /reports/orders/stats endpoint. * - * @package WooCommerce/API - * @since 3.5.0 + * @package WooCommerce Admin/API */ defined( 'ABSPATH' ) || exit; @@ -16,7 +15,7 @@ defined( 'ABSPATH' ) || exit; * @package WooCommerce/API * @extends WC_REST_Reports_Controller */ -class WC_REST_Reports_Orders_Stats_Controller extends WC_REST_Reports_Controller { +class WC_Admin_REST_Reports_Orders_Stats_Controller extends WC_REST_Reports_Controller { /** * Endpoint namespace. @@ -63,7 +62,7 @@ class WC_REST_Reports_Orders_Stats_Controller extends WC_REST_Reports_Controller */ public function get_items( $request ) { $query_args = $this->prepare_reports_query( $request ); - $orders_query = new WC_Reports_Orders_Stats_Query( $query_args ); + $orders_query = new WC_Admin_Reports_Orders_Stats_Query( $query_args ); $report_data = $orders_query->get_data(); $out_data = array( diff --git a/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-products-controller.php b/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-products-controller.php index caf9bbca9e1..a9517af822f 100644 --- a/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-products-controller.php +++ b/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-products-controller.php @@ -4,8 +4,7 @@ * * Handles requests to the /reports/products endpoint. * - * @package WooCommerce/API - * @since 3.5.0 + * @package WooCommerce Admin/API */ defined( 'ABSPATH' ) || exit; @@ -16,7 +15,7 @@ defined( 'ABSPATH' ) || exit; * @package WooCommerce/API * @extends WC_REST_Reports_Controller */ -class WC_REST_Reports_Products_Controller extends WC_REST_Reports_Controller { +class WC_Admin_REST_Reports_Products_Controller extends WC_REST_Reports_Controller { /** * Endpoint namespace. diff --git a/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-products-stats-controller.php b/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-products-stats-controller.php index e79bea86643..22c45c67ebf 100644 --- a/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-products-stats-controller.php +++ b/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-products-stats-controller.php @@ -4,8 +4,7 @@ * * Handles requests to the /reports/products/stats endpoint. * - * @package WooCommerce/API - * @since 3.5.0 + * @package WooCommerce Admin/API */ defined( 'ABSPATH' ) || exit; @@ -16,7 +15,7 @@ defined( 'ABSPATH' ) || exit; * @package WooCommerce/API * @extends WC_REST_Reports_Controller */ -class WC_REST_Reports_Products_Stats_Controller extends WC_REST_Reports_Controller { +class WC_Admin_REST_Reports_Products_Stats_Controller extends WC_REST_Reports_Controller { /** * Endpoint namespace. diff --git a/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-revenue-stats-controller.php b/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-revenue-stats-controller.php index 7dbbe000a63..369cce77aee 100644 --- a/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-revenue-stats-controller.php +++ b/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-revenue-stats-controller.php @@ -4,8 +4,7 @@ * * Handles requests to the /reports/revenue/stats endpoint. * - * @package WooCommerce/API - * @since 3.5.0 + * @package WooCommerce Admin/API */ defined( 'ABSPATH' ) || exit; @@ -16,7 +15,7 @@ defined( 'ABSPATH' ) || exit; * @package WooCommerce/API * @extends WC_REST_Reports_Controller */ -class WC_REST_Reports_Revenue_Stats_Controller extends WC_REST_Reports_Controller { +class WC_Admin_REST_Reports_Revenue_Stats_Controller extends WC_REST_Reports_Controller { /** * Endpoint namespace. diff --git a/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-taxes-controller.php b/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-taxes-controller.php index e18d72d0fde..2af28527cdb 100644 --- a/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-taxes-controller.php +++ b/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-taxes-controller.php @@ -4,8 +4,7 @@ * * Handles requests to the /reports/taxes endpoint. * - * @package WooCommerce/API - * @since 3.5.0 + * @package WooCommerce Admin/API */ defined( 'ABSPATH' ) || exit; @@ -16,7 +15,7 @@ defined( 'ABSPATH' ) || exit; * @package WooCommerce/API * @extends WC_REST_Reports_Controller */ -class WC_REST_Reports_Taxes_Controller extends WC_REST_Reports_Controller { +class WC_Admin_REST_Reports_Taxes_Controller extends WC_REST_Reports_Controller { /** * Endpoint namespace. diff --git a/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-taxes-stats-controller.php b/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-taxes-stats-controller.php index 7910ab3e1f6..ddc0fc3fee4 100644 --- a/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-taxes-stats-controller.php +++ b/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-taxes-stats-controller.php @@ -4,8 +4,7 @@ * * Handles requests to the /reports/taxes/stats endpoint. * - * @package WooCommerce/API - * @since 3.5.0 + * @package WooCommerce Admin/API */ defined( 'ABSPATH' ) || exit; @@ -16,7 +15,7 @@ defined( 'ABSPATH' ) || exit; * @package WooCommerce/API * @extends WC_REST_Reports_Controller */ -class WC_REST_Reports_Taxes_Stats_Controller extends WC_REST_Reports_Controller { +class WC_Admin_REST_Reports_Taxes_Stats_Controller extends WC_REST_Reports_Controller { /** * Endpoint namespace. diff --git a/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-system-status-tools-controller.php b/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-system-status-tools-controller.php index 083bbfc3ddf..88c0ec275c6 100644 --- a/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-system-status-tools-controller.php +++ b/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-system-status-tools-controller.php @@ -4,8 +4,7 @@ * * Handles requests to the /system_status/tools/* endpoints. * - * @package WooCommerce/API - * @since 3.0.0 + * @package WooCommerce Admin/API */ defined( 'ABSPATH' ) || exit; @@ -13,10 +12,10 @@ defined( 'ABSPATH' ) || exit; /** * System status tools controller. * - * @package WooCommerce/API - * @extends WC_REST_System_Status_Tools_V2_Controller + * @package WooCommerce Admin/API + * @extends WC_REST_System_Status_Tools_Controller */ -class WC_REST_System_Status_Tools_Controller extends WC_REST_System_Status_Tools_V2_Controller { +class WC_Admin_REST_System_Status_Tools_Controller extends WC_REST_System_Status_Tools_Controller { /** * Endpoint namespace. @@ -56,7 +55,7 @@ class WC_REST_System_Status_Tools_Controller extends WC_REST_System_Status_Tools switch ( $tool ) { case 'rebuild_stats': - WC_Reports_Orders_Data_Store::queue_order_stats_repopulate_database(); + WC_Admin_Reports_Orders_Data_Store::queue_order_stats_repopulate_database(); $message = __( 'Rebuilding reports data in the background . . .', 'woocommerce' ); break; default: From b556d55ca6fb45d249ec823811b8f4fb50ec2c67 Mon Sep 17 00:00:00 2001 From: Peter Fabian Date: Tue, 18 Sep 2018 12:24:43 +0200 Subject: [PATCH 14/20] - Removed duplicate class. - Overridden REST API endpoints via filters. - Updated data store class prefixes. - Removed debugging echo. --- ...min-rest-reports-categories-controller.php | 2 +- ...admin-rest-reports-products-controller.php | 2 +- ...rest-reports-products-stats-controller.php | 2 +- ...-rest-reports-revenue-stats-controller.php | 2 +- .../includes/class-wc-admin-api-init.php | 61 +++++++++++++++++-- .../class-wc-admin-reports-products-query.php | 2 +- ...ass-wc-admin-reports-orders-data-store.php | 2 - ...-wc-admin-reports-data-store-interface.php | 4 +- .../class-wc-reports-data-store-interface.php | 26 -------- 9 files changed, 62 insertions(+), 41 deletions(-) delete mode 100644 plugins/woocommerce-admin/includes/interfaces/class-wc-reports-data-store-interface.php diff --git a/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-categories-controller.php b/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-categories-controller.php index 394141891c9..602432f1290 100644 --- a/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-categories-controller.php +++ b/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-categories-controller.php @@ -59,7 +59,7 @@ class WC_Admin_REST_Reports_Categories_Controller extends WC_REST_Reports_Contro */ public function get_items( $request ) { $query_args = $this->prepare_reports_query( $request ); - $categories_query = new WC_Reports_Categories_Query( $query_args ); + $categories_query = new WC_Admin_Reports_Categories_Query( $query_args ); $report_data = $categories_query->get_data(); if ( is_wp_error( $report_data ) ) { diff --git a/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-products-controller.php b/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-products-controller.php index a9517af822f..0b5769140cc 100644 --- a/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-products-controller.php +++ b/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-products-controller.php @@ -47,7 +47,7 @@ class WC_Admin_REST_Reports_Products_Controller extends WC_REST_Reports_Controll } } - $reports = new WC_Reports_Products_Query( $args ); + $reports = new WC_Admin_Reports_Products_Query( $args ); $products_data = $reports->get_data(); $data = array(); diff --git a/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-products-stats-controller.php b/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-products-stats-controller.php index 22c45c67ebf..ffc2257fe33 100644 --- a/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-products-stats-controller.php +++ b/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-products-stats-controller.php @@ -53,7 +53,7 @@ class WC_Admin_REST_Reports_Products_Stats_Controller extends WC_REST_Reports_Co } } - $query = new WC_Reports_Products_Stats_Query( $query_args ); + $query = new WC_Admin_Reports_Products_Stats_Query( $query_args ); $report_data = $query->get_data(); $out_data = array( diff --git a/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-revenue-stats-controller.php b/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-revenue-stats-controller.php index 369cce77aee..9de47b44f4e 100644 --- a/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-revenue-stats-controller.php +++ b/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-revenue-stats-controller.php @@ -58,7 +58,7 @@ class WC_Admin_REST_Reports_Revenue_Stats_Controller extends WC_REST_Reports_Con */ public function get_items( $request ) { $query_args = $this->prepare_reports_query( $request ); - $reports_revenue = new WC_Reports_Revenue_Query( $query_args ); + $reports_revenue = new WC_Admin_Reports_Revenue_Query( $query_args ); $report_data = $reports_revenue->get_data(); $out_data = array( diff --git a/plugins/woocommerce-admin/includes/class-wc-admin-api-init.php b/plugins/woocommerce-admin/includes/class-wc-admin-api-init.php index 9d703514aa6..3bf86d61e36 100644 --- a/plugins/woocommerce-admin/includes/class-wc-admin-api-init.php +++ b/plugins/woocommerce-admin/includes/class-wc-admin-api-init.php @@ -12,6 +12,8 @@ class WC_Admin_Api_Init { add_filter( 'woocommerce_install_get_tables', array( 'WC_Admin_Api_Init', 'add_report_tables' ) ); // REST API extensions init. add_action( 'rest_api_init', array( $this, 'rest_api_init' ) ); + add_filter( 'rest_endpoints', array( 'WC_Admin_Api_Init', 'filter_rest_endpoints' ), 10, 1 ); + add_filter( 'woocommerce_debug_tools', array( 'WC_Admin_Api_Init', 'add_regenerate_tool' ) ); // Initialize report classes. add_action( 'woocommerce_after_register_post_type', array( 'WC_Admin_Api_Init', 'orders_data_store_init' ), 20 ); add_action( 'woocommerce_after_register_post_type', array( 'WC_Admin_Api_Init', 'order_product_lookup_store_init' ), 20 ); @@ -23,9 +25,10 @@ class WC_Admin_Api_Init { public function init_classes() { // Interfaces. require_once dirname( __FILE__ ) . '/interfaces/class-wc-admin-reports-data-store-interface.php'; - require_once dirname( __FILE__ ) . '/interfaces/class-wc-reports-data-store-interface.php'; require_once dirname( __FILE__ ) . '/class-wc-admin-reports-query.php'; + // Common date time code. + require_once dirname( __FILE__ ) . '/class-wc-admin-reports-interval.php'; // Query classes for reports. require_once dirname( __FILE__ ) . '/class-wc-admin-reports-revenue-query.php'; @@ -75,6 +78,52 @@ class WC_Admin_Api_Init { } } + public static function filter_rest_endpoints( $endpoints ) { + // Override GET /wc/v3/system_status/tools. + if ( isset( $endpoints['/wc/v3/system_status/tools'] ) + && isset( $endpoints['/wc/v3/system_status/tools'][1] ) + && isset( $endpoints['/wc/v3/system_status/tools'][0] ) + && $endpoints['/wc/v3/system_status/tools'][1]['callback'][0] instanceof WC_Admin_REST_System_Status_Tools_Controller + ) { + $endpoints['/wc/v3/system_status/tools'][0] = $endpoints['/wc/v3/system_status/tools'][1]; + } + // // Override GET & PUT for /wc/v3/system_status/tools. + if ( isset( $endpoints['/wc/v3/system_status/tools/(?P[\w-]+)'] ) + && isset( $endpoints['/wc/v3/system_status/tools/(?P[\w-]+)'][3] ) + && isset( $endpoints['/wc/v3/system_status/tools/(?P[\w-]+)'][2] ) + && $endpoints['/wc/v3/system_status/tools/(?P[\w-]+)'][2]['callback'][0] instanceof WC_Admin_REST_System_Status_Tools_Controller + && $endpoints['/wc/v3/system_status/tools/(?P[\w-]+)'][3]['callback'][0] instanceof WC_Admin_REST_System_Status_Tools_Controller + ) { + $endpoints['/wc/v3/system_status/tools/(?P[\w-]+)'][0] = $endpoints['/wc/v3/system_status/tools/(?P[\w-]+)'][2]; + $endpoints['/wc/v3/system_status/tools/(?P[\w-]+)'][1] = $endpoints['/wc/v3/system_status/tools/(?P[\w-]+)'][3]; + } + + // Override GET /wc/v3/reports. + if ( isset( $endpoints['/wc/v3/reports'] ) + && isset( $endpoints['/wc/v3/reports'][1] ) + && isset( $endpoints['/wc/v3/reports'][0] ) + && $endpoints['/wc/v3/reports'][1]['callback'][0] instanceof WC_Admin_REST_Reports_Controller + ) { + $endpoints['/wc/v3/reports'][0] = $endpoints['/wc/v3/reports'][1]; + } + + return $endpoints; + } + + public static function add_regenerate_tool( $tools ) { + return array_merge( + $tools, + array( + 'rebuild_stats' => array( + 'name' => __( 'Rebuild reports data', 'woocommerce' ), + 'button' => __( 'Rebuild reports', 'woocommerce' ), + 'desc' => __( 'This tool will rebuild all of the information used by the reports.', 'woocommerce' ), + 'callback' => array( 'WC_Admin_Reports_Orders_Data_Store', 'queue_order_stats_repopulate_database' ), + ), + ) + ); + } + public static function orders_data_store_init() { WC_Admin_Reports_Orders_Data_Store::init(); } @@ -134,11 +183,11 @@ class WC_Admin_Api_Init { return array_merge( $data_stores, array( - 'report-revenue-stats' => 'WC_Reports_Orders_Data_Store', - 'report-orders-stats' => 'WC_Reports_Orders_Data_Store', - 'report-products' => 'WC_Reports_Products_Data_Store', - 'report-products-stats' => 'WC_Reports_Products_Stats_Data_Store', - 'report-categories' => 'WC_Reports_Categories_Data_Store', + 'report-revenue-stats' => 'WC_Admin_Reports_Orders_Data_Store', + 'report-orders-stats' => 'WC_Admin_Reports_Orders_Data_Store', + 'report-products' => 'WC_Admin_Reports_Products_Data_Store', + 'report-products-stats' => 'WC_Admin_Reports_Products_Stats_Data_Store', + 'report-categories' => 'WC_Admin_Reports_Categories_Data_Store', ) ); } diff --git a/plugins/woocommerce-admin/includes/class-wc-admin-reports-products-query.php b/plugins/woocommerce-admin/includes/class-wc-admin-reports-products-query.php index f32835851c6..2b3b1efc25d 100644 --- a/plugins/woocommerce-admin/includes/class-wc-admin-reports-products-query.php +++ b/plugins/woocommerce-admin/includes/class-wc-admin-reports-products-query.php @@ -20,7 +20,7 @@ defined( 'ABSPATH' ) || exit; /** - * WC_Reports_Products_Query + * WC_Admin_Reports_Products_Query * */ class WC_Admin_Reports_Products_Query extends WC_Admin_Reports_Query { diff --git a/plugins/woocommerce-admin/includes/data-stores/class-wc-admin-reports-orders-data-store.php b/plugins/woocommerce-admin/includes/data-stores/class-wc-admin-reports-orders-data-store.php index 131de190c59..82b9e196dd2 100644 --- a/plugins/woocommerce-admin/includes/data-stores/class-wc-admin-reports-orders-data-store.php +++ b/plugins/woocommerce-admin/includes/data-stores/class-wc-admin-reports-orders-data-store.php @@ -320,8 +320,6 @@ class WC_Admin_Reports_Orders_Data_Store extends WC_Admin_Reports_Data_Store imp {$intervals_query['limit']}", ARRAY_A ); // WPCS: cache ok, DB call ok, unprepared SQL ok. - echo $wpdb->last_query; - if ( null === $intervals ) { return new WP_Error( 'woocommerce_reports_revenue_result_failed', __( 'Sorry, fetching revenue data failed.', 'woocommerce' ) ); } diff --git a/plugins/woocommerce-admin/includes/interfaces/class-wc-admin-reports-data-store-interface.php b/plugins/woocommerce-admin/includes/interfaces/class-wc-admin-reports-data-store-interface.php index 528cf83dd99..1ed81dcfbf4 100644 --- a/plugins/woocommerce-admin/includes/interfaces/class-wc-admin-reports-data-store-interface.php +++ b/plugins/woocommerce-admin/includes/interfaces/class-wc-admin-reports-data-store-interface.php @@ -3,7 +3,7 @@ * Reports Data Store Interface * * @version 3.5.0 - * @package WooCommerce/Interface + * @package WooCommerce Admin/Interface */ if ( ! defined( 'ABSPATH' ) ) { @@ -15,7 +15,7 @@ if ( ! defined( 'ABSPATH' ) ) { * * @since 3.5.0 */ -interface WC_Reports_Data_Store_Interface { +interface WC_Admin_Reports_Data_Store_Interface { /** * Get the data based on args. diff --git a/plugins/woocommerce-admin/includes/interfaces/class-wc-reports-data-store-interface.php b/plugins/woocommerce-admin/includes/interfaces/class-wc-reports-data-store-interface.php deleted file mode 100644 index c17936b9e6a..00000000000 --- a/plugins/woocommerce-admin/includes/interfaces/class-wc-reports-data-store-interface.php +++ /dev/null @@ -1,26 +0,0 @@ - Date: Tue, 18 Sep 2018 13:20:42 +0200 Subject: [PATCH 15/20] Added code from feature/20781, tests pending. --- ...dmin-rest-reports-customers-controller.php | 271 ++++++++++++++++++ 1 file changed, 271 insertions(+) diff --git a/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-customers-controller.php b/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-customers-controller.php index 9afef26c6a5..8475126ba35 100644 --- a/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-customers-controller.php +++ b/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-customers-controller.php @@ -30,4 +30,275 @@ class WC_Admin_REST_Reports_Customers_Controller extends WC_REST_Reports_Control * @var string */ protected $rest_base = 'reports/customers'; + + /** + * 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['page'] = $request['page']; + $args['per_page'] = $request['per_page']; + $args['name'] = $request['name']; + $args['username'] = $request['username']; + $args['email'] = $request['email']; + $args['country'] = $request['country']; + $args['last_active_before'] = $request['last_active_before']; + $args['last_active_after'] = $request['last_active_after']; + $args['order_count_min'] = $request['order_count_min']; + $args['order_count_max'] = $request['order_count_max']; + $args['total_spend_min'] = $request['total_spend_min']; + $args['total_spend_max'] = $request['total_spend_max']; + $args['avg_order_value_min'] = $request['avg_order_value_min']; + $args['avg_order_value_max'] = $request['avg_order_value_max']; + return $args; + } + + /** + * Get all reports. + * + * @param WP_REST_Request $request Request data. + * @return array|WP_Error + */ + public function get_items( $request ) { + $query_args = $this->prepare_reports_query( $request ); + $customers_query = new WC_Reports_Orders_Stats_Query( $query_args ); // @todo change to correct class. + $report_data = $customers_query->get_data(); + $out_data = array( + 'totals' => get_object_vars( $report_data->totals ), + 'customers' => array(), + ); + foreach ( $report_data->customers as $customer_data ) { + $item_data = $this->prepare_item_for_response( (object) $customer_data, $request ); + $out_data['customers'][] = $item_data; + } + $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 ); + $response->add_links( $this->prepare_links( $report ) ); + /** + * Filter a report returned from the API. + * + * Allows modification of the report data right before it is returned. + * + * @param WP_REST_Response $response The response object. + * @param object $report The original report object. + * @param WP_REST_Request $request Request used to generate the response. + */ + return apply_filters( 'woocommerce_rest_prepare_report_customers', $response, $report, $request ); + } + + /** + * Prepare links for the request. + * + * @param WC_Reports_Query $object Object data. + * @return array + */ + protected function prepare_links( $object ) { + $links = array( + 'customer' => array( + 'href' => rest_url( sprintf( '/%s/customers/%d', $this->namespace, $object->customer_id ) ), + ), + ); + return $links; + } + + /** + * Get the Report's schema, conforming to JSON Schema. + * + * @return array + */ + public function get_item_schema() { + $schema = array( + '$schema' => 'http://json-schema.org/draft-04/schema#', + 'title' => 'report_customers', + 'type' => 'object', + 'properties' => array( + 'id' => array( + 'description' => __( 'ID.', 'woocommerce' ), + 'type' => 'integer', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ), + 'customer_id' => array( + 'description' => __( 'Customer ID.', 'woocommerce' ), + 'type' => 'integer', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ), + 'date_last_active' => array( + 'description' => __( 'Date last active.', 'woocommerce' ), + 'type' => 'date-time', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ), + 'date_last_active_gmt' => array( + 'description' => __( 'Date last active GMT.', 'woocommerce' ), + 'type' => 'date-time', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ), + 'orders_count' => array( + 'description' => __( 'Order count.', 'woocommerce' ), + 'type' => 'integer', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ), + 'total_spend' => array( + 'description' => __( 'Total spend.', 'woocommerce' ), + 'type' => 'number', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ), + 'avg_order_value' => array( + 'description' => __( 'Avg order value.', 'woocommerce' ), + 'type' => 'number', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ), + ), + ); + return $this->add_additional_fields_schema( $schema ); + } + + /** + * Get the query params for collections. + * + * @return array + */ + public function get_collection_params() { + $params = array(); + $params['context'] = $this->get_context_param( array( 'default' => 'view' ) ); + $params['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['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['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['name'] = array( + 'description' => __( 'Limit response to objects with a specfic customer name.', 'woocommerce' ), + 'type' => 'string', + 'validate_callback' => 'rest_validate_request_arg', + ); + $params['username'] = array( + 'description' => __( 'Limit response to objects with a specfic username.', 'woocommerce' ), + 'type' => 'string', + 'validate_callback' => 'rest_validate_request_arg', + ); + $params['email'] = array( + 'description' => __( 'Limit response to objects equal to an email.', 'woocommerce' ), + 'type' => 'string', + 'validate_callback' => 'rest_validate_request_arg', + ); + $params['country'] = array( + 'description' => __( 'Limit response to objects with a specfic country.', 'woocommerce' ), + 'type' => 'string', + 'validate_callback' => 'rest_validate_request_arg', + ); + $params['last_active_before'] = array( + 'description' => __( 'Limit response to objects last active before (or at) a given ISO8601 compliant datetime.', 'woocommerce' ), + 'type' => 'string', + 'format' => 'date-time', + 'validate_callback' => 'rest_validate_request_arg', + ); + $params['last_active_after'] = array( + 'description' => __( 'Limit response to objects last active after (or at) a given ISO8601 compliant datetime.', 'woocommerce' ), + 'type' => 'string', + 'format' => 'date-time', + 'validate_callback' => 'rest_validate_request_arg', + ); + $params['order_count_min'] = array( + 'description' => __( 'Limit response to objects with an order count greater than or equal to given integer.', 'woocommerce' ), + 'type' => 'integer', + 'sanitize_callback' => 'absint', + 'validate_callback' => 'rest_validate_request_arg', + ); + $params['order_count_max'] = array( + 'description' => __( 'Limit response to objects with an order count less than or equal to given integer.', 'woocommerce' ), + 'type' => 'integer', + 'sanitize_callback' => 'absint', + 'validate_callback' => 'rest_validate_request_arg', + ); + $params['total_spend_min'] = array( + 'description' => __( 'Limit response to objects with a total order spend greater than or equal to given number.', 'woocommerce' ), + 'type' => 'number', + 'validate_callback' => 'rest_validate_request_arg', + ); + $params['total_spend_max'] = array( + 'description' => __( 'Limit response to objects with a total order spend less than or equal to given number.', 'woocommerce' ), + 'type' => 'number', + 'validate_callback' => 'rest_validate_request_arg', + ); + $params['avg_order_value_min'] = array( + 'description' => __( 'Limit response to objects with an average order spend greater than or equal to given number.', 'woocommerce' ), + 'type' => 'number', + 'validate_callback' => 'rest_validate_request_arg', + ); + $params['avg_order_value_max'] = array( + 'description' => __( 'Limit response to objects with an average order spend less than or equal to given number.', 'woocommerce' ), + 'type' => 'number', + 'validate_callback' => 'rest_validate_request_arg', + ); + return $params; + } } From 6cddb517865d9e1d47354f1dd96cd372680c3962 Mon Sep 17 00:00:00 2001 From: Peter Fabian Date: Tue, 18 Sep 2018 13:24:11 +0200 Subject: [PATCH 16/20] Added code from feature/20770. --- .../includes/class-wc-admin-api-init.php | 12 ++++++ .../includes/wc-admin-order-functions.php | 37 +++++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/plugins/woocommerce-admin/includes/class-wc-admin-api-init.php b/plugins/woocommerce-admin/includes/class-wc-admin-api-init.php index 3bf86d61e36..ba05f9bbcee 100644 --- a/plugins/woocommerce-admin/includes/class-wc-admin-api-init.php +++ b/plugins/woocommerce-admin/includes/class-wc-admin-api-init.php @@ -201,6 +201,7 @@ class WC_Admin_Api_Init { // TODO: will this work on multisite? "{$wpdb->prefix}wc_admin_order_stats", "{$wpdb->prefix}wc_admin_order_product_lookup", + "{$wpdb->prefix}wc_order_tax_lookup", ) ); } @@ -239,6 +240,17 @@ class WC_Admin_Api_Init { KEY product_id (product_id), KEY customer_id (customer_id), KEY date_created (date_created) + ) $collate; + CREATE TABLE {$wpdb->prefix}wc_order_tax_lookup ( + order_id BIGINT UNSIGNED NOT NULL, + tax_rate_id BIGINT UNSIGNED NOT NULL, + date_created timestamp DEFAULT '0000-00-00 00:00:00' NOT NULL, + shipping_tax double DEFAULT 0 NOT NULL, + order_tax double DEFAULT 0 NOT NULL, + total_tax double DEFAULT 0 NOT NULL, + KEY order_id (order_id), + KEY tax_rate_id (tax_rate_id), + KEY date_created (date_created) ) $collate;"; return $tables; diff --git a/plugins/woocommerce-admin/includes/wc-admin-order-functions.php b/plugins/woocommerce-admin/includes/wc-admin-order-functions.php index f11c151aa0f..a15be7084ed 100644 --- a/plugins/woocommerce-admin/includes/wc-admin-order-functions.php +++ b/plugins/woocommerce-admin/includes/wc-admin-order-functions.php @@ -40,3 +40,40 @@ function wc_admin_order_product_lookup_entry( $order_id ) { } } add_action( 'save_post', 'wc_admin_order_product_lookup_entry', 10, 1 ); + +/** + * Make an entry in the wc_order_tax_lookup table for an order. + * + * @since 3.5.0 + * @param int $order_id Order ID. + * @return void + */ +function wc_order_tax_lookup_entry( $order_id ) { + global $wpdb; + $order = wc_get_order( $order_id ); + if ( ! $order ) { + return; + } + foreach ( $order->get_items( 'tax' ) as $tax_item ) { + $wpdb->replace( + $wpdb->prefix . 'wc_order_tax_lookup', + array( + 'order_id' => $order->get_id(), + 'date_created' => date( 'Y-m-d H:i:s', $order->get_date_created( 'edit' )->getTimestamp() ), + 'tax_rate_id' => $tax_item->get_rate_id(), + 'shipping_tax' => $tax_item->get_shipping_tax_total(), + 'order_tax' => $tax_item->get_tax_total(), + 'total_tax' => $tax_item->get_tax_total() + $tax_item->get_shipping_tax_total(), + ), + array( + '%d', + '%s', + '%d', + '%f', + '%f', + '%f', + ) + ); + } +} +add_action( 'save_post', 'wc_order_tax_lookup_entry', 10, 1 ); From aaef322e00e0ad968a56defc60531dbd10ddd6bc Mon Sep 17 00:00:00 2001 From: Peter Fabian Date: Tue, 18 Sep 2018 13:27:34 +0200 Subject: [PATCH 17/20] Added code from feature/20778, tests pending. --- ...wc-admin-rest-reports-taxes-controller.php | 221 ++++++++++++++ ...in-rest-reports-taxes-stats-controller.php | 278 ++++++++++++++++++ .../includes/class-wc-admin-api-init.php | 2 + 3 files changed, 501 insertions(+) diff --git a/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-taxes-controller.php b/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-taxes-controller.php index 2af28527cdb..8b19a288aa7 100644 --- a/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-taxes-controller.php +++ b/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-taxes-controller.php @@ -30,4 +30,225 @@ class WC_Admin_REST_Reports_Taxes_Controller extends WC_REST_Reports_Controller * @var string */ protected $rest_base = 'reports/taxes'; + + /** + * 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 WC_Reports_Orders_Stats_Query( $query_args ); // @todo change to correct class. + $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 ); + $response->add_links( $this->prepare_links( $report ) ); + + /** + * Filter a report returned from the API. + * + * Allows modification of the report data right before it is returned. + * + * @param WP_REST_Response $response The response object. + * @param object $report The original report object. + * @param WP_REST_Request $request Request used to generate the response. + */ + return apply_filters( 'woocommerce_rest_prepare_report_taxes', $response, $report, $request ); + } + + /** + * Prepare links for the request. + * + * @param WC_Reports_Query $object Object data. + * @return array + */ + protected function prepare_links( $object ) { + $links = array( + 'tax' => array( + 'href' => rest_url( sprintf( '/%s/taxes/%d', $this->namespace, $object->category_id ) ), + ), + ); + + return $links; + } + + /** + * Get the Report's schema, conforming to JSON Schema. + * + * @return array + */ + public function get_item_schema() { + $schema = array( + '$schema' => 'http://json-schema.org/draft-04/schema#', + 'title' => 'report_taxes', + 'type' => 'object', + 'properties' => array( + 'tax_rate_id' => array( + 'description' => __( 'Tax rate ID.', 'woocommerce' ), + 'type' => 'integer', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ), + 'total_tax' => array( + 'description' => __( 'Total tax.', 'woocommerce' ), + 'type' => 'number', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ), + 'order_tax' => array( + 'description' => __( 'Order tax.', 'woocommerce' ), + 'type' => 'number', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ), + 'shipping_tax' => array( + 'description' => __( 'Shipping tax.', 'woocommerce' ), + 'type' => 'number', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ), + 'orders_count' => array( + 'description' => __( 'Amount of orders.', 'woocommerce' ), + 'type' => 'integer', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ), + ), + ); + + 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', + 'name', + 'tax_rate_id', + 'orders_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['code'] = array( + 'description' => __( 'Limit result set to items assigned one or more code.', 'woocommerce' ), + 'type' => 'array', + 'sanitize_callback' => 'wp_parse_slug_list', + 'validate_callback' => 'rest_validate_request_arg', + 'items' => array( + 'type' => 'string', + ), + ); + + return $params; + } } diff --git a/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-taxes-stats-controller.php b/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-taxes-stats-controller.php index ddc0fc3fee4..ed5a2c1c9c7 100644 --- a/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-taxes-stats-controller.php +++ b/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-taxes-stats-controller.php @@ -30,4 +30,282 @@ class WC_Admin_REST_Reports_Taxes_Stats_Controller extends WC_REST_Reports_Contr * @var string */ protected $rest_base = 'reports/taxes/stats'; + + /** + * Maps query arguments from the REST request. + * + * @param array $request Request array. + * @return array + */ + protected function prepare_reports_query( $request ) { + $args = array(); + $args['before'] = $request['before']; + $args['after'] = $request['after']; + $args['interval'] = $request['interval']; + $args['page'] = $request['page']; + $args['per_page'] = $request['per_page']; + $args['orderby'] = $request['orderby']; + $args['order'] = $request['order']; + $args['code'] = (array) $request['code']; + + 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 WC_Reports_Orders_Stats_Query( $query_args ); // @todo change to correct class. + $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() { + $totals = array( + 'total_tax' => array( + 'description' => __( 'Total tax.', 'woocommerce' ), + 'type' => 'number', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ), + 'order_tax' => array( + 'description' => __( 'Order tax.', 'woocommerce' ), + 'type' => 'number', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ), + 'shipping_tax' => array( + 'description' => __( 'Shipping tax.', 'woocommerce' ), + 'type' => 'number', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ), + 'orders_count' => array( + 'description' => __( 'Amount of orders', 'woocommerce' ), + 'type' => 'integer', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ), + ); + + $schema = array( + '$schema' => 'http://json-schema.org/draft-04/schema#', + 'title' => 'report_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', + 'gross_revenue', + 'orders_count', + 'products_count', + ), + 'validate_callback' => 'rest_validate_request_arg', + ); + $params['interval'] = array( + 'description' => __( 'Time interval to use for buckets in the returned data.', 'woocommerce' ), + 'type' => 'string', + 'default' => 'week', + 'enum' => array( + 'hour', + 'day', + 'week', + 'month', + 'quarter', + 'year', + ), + 'validate_callback' => 'rest_validate_request_arg', + ); + $params['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', + ), + ); + + return $params; + } } diff --git a/plugins/woocommerce-admin/includes/class-wc-admin-api-init.php b/plugins/woocommerce-admin/includes/class-wc-admin-api-init.php index ba05f9bbcee..7d53e1aff47 100644 --- a/plugins/woocommerce-admin/includes/class-wc-admin-api-init.php +++ b/plugins/woocommerce-admin/includes/class-wc-admin-api-init.php @@ -70,6 +70,8 @@ class WC_Admin_Api_Init { 'WC_Admin_REST_Reports_Revenue_Stats_Controller', 'WC_Admin_REST_Reports_Orders_Stats_Controller', 'WC_Admin_REST_Reports_Categories_Controller', + 'WC_Admin_REST_Reports_Taxes_Controller', + 'WC_Admin_REST_Reports_Taxes_Stats_Controller', ); foreach ( $controllers as $controller ) { From b8732e157e07dc58a54f24f1fbcd0a3b69a11342 Mon Sep 17 00:00:00 2001 From: Peter Fabian Date: Tue, 18 Sep 2018 13:29:58 +0200 Subject: [PATCH 18/20] Added code from feature/couponordertable. --- .../includes/class-wc-admin-api-init.php | 10 ++++++ .../includes/wc-admin-order-functions.php | 36 +++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/plugins/woocommerce-admin/includes/class-wc-admin-api-init.php b/plugins/woocommerce-admin/includes/class-wc-admin-api-init.php index 7d53e1aff47..c88f6d32d3e 100644 --- a/plugins/woocommerce-admin/includes/class-wc-admin-api-init.php +++ b/plugins/woocommerce-admin/includes/class-wc-admin-api-init.php @@ -204,6 +204,7 @@ class WC_Admin_Api_Init { "{$wpdb->prefix}wc_admin_order_stats", "{$wpdb->prefix}wc_admin_order_product_lookup", "{$wpdb->prefix}wc_order_tax_lookup", + "{$wpdb->prefix}wc_order_coupon_lookup", ) ); } @@ -253,6 +254,15 @@ class WC_Admin_Api_Init { KEY order_id (order_id), KEY tax_rate_id (tax_rate_id), KEY date_created (date_created) + ) $collate; + CREATE TABLE {$wpdb->prefix}wc_order_coupon_lookup ( + order_id BIGINT UNSIGNED NOT NULL, + coupon_id BIGINT UNSIGNED NOT NULL, + date_created timestamp DEFAULT '0000-00-00 00:00:00' NOT NULL, + coupon_gross_discount double DEFAULT 0 NOT NULL, + KEY order_id (order_id), + KEY coupon_id (coupon_id), + KEY date_created (date_created) ) $collate;"; return $tables; diff --git a/plugins/woocommerce-admin/includes/wc-admin-order-functions.php b/plugins/woocommerce-admin/includes/wc-admin-order-functions.php index a15be7084ed..86914f3ced2 100644 --- a/plugins/woocommerce-admin/includes/wc-admin-order-functions.php +++ b/plugins/woocommerce-admin/includes/wc-admin-order-functions.php @@ -77,3 +77,39 @@ function wc_order_tax_lookup_entry( $order_id ) { } } add_action( 'save_post', 'wc_order_tax_lookup_entry', 10, 1 ); + +/** + * Make an entry in the wc_order_coupon_lookup table for an order. + * + * @since 3.5.0 + * @param int $order_id Order ID. + * @return void + */ +function wc_order_coupon_lookup_entry( $order_id ) { + global $wpdb; + + $order = wc_get_order( $order_id ); + if ( ! $order ) { + return; + } + + $coupon_items = $order->get_items( 'coupon' ); + foreach ( $coupon_items as $coupon_item ) { + $wpdb->replace( + $wpdb->prefix . 'wc_order_coupon_lookup', + array( + 'order_id' => $order_id, + 'coupon_id' => $coupon_item->get_id(), + 'coupon_gross_discount' => $coupon_item->get_discount(), + 'date_created' => date( 'Y-m-d H:i:s', $order->get_date_created( 'edit' )->getTimestamp() ), + ), + array( + '%d', + '%d', + '%f', + '%s', + ) + ); + } +} +add_action( 'save_post', 'wc_order_coupon_lookup_entry', 10, 1 ); From 0a669544d68790e61c10c3b8be13a14fcbe62cd7 Mon Sep 17 00:00:00 2001 From: Peter Fabian Date: Tue, 18 Sep 2018 13:32:25 +0200 Subject: [PATCH 19/20] Added code from feature/20777, pending tests. --- ...-admin-rest-reports-coupons-controller.php | 230 +++++++++++++++ ...-rest-reports-coupons-stats-controller.php | 273 ++++++++++++++++++ .../includes/class-wc-admin-api-init.php | 2 + 3 files changed, 505 insertions(+) diff --git a/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-coupons-controller.php b/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-coupons-controller.php index 7059103bab8..f86c1f3e5c7 100644 --- a/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-coupons-controller.php +++ b/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-coupons-controller.php @@ -30,4 +30,234 @@ class WC_Admin_REST_Reports_Coupons_Controller extends WC_REST_Reports_Controlle * @var string */ protected $rest_base = 'reports/coupons'; + + /** + * 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['code'] = (array) $request['code']; + + 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 ); + $coupons_query = new WC_Reports_Orders_Stats_Query( $query_args ); // @todo change to correct class. + $report_data = $coupons_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 ); + $response->add_links( $this->prepare_links( $report ) ); + + /** + * Filter a report returned from the API. + * + * Allows modification of the report data right before it is returned. + * + * @param WP_REST_Response $response The response object. + * @param object $report The original report object. + * @param WP_REST_Request $request Request used to generate the response. + */ + return apply_filters( 'woocommerce_rest_prepare_report_coupons', $response, $report, $request ); + } + + /** + * Prepare links for the request. + * + * @param WC_Reports_Query $object Object data. + * @return array + */ + protected function prepare_links( $object ) { + $links = array( + 'coupon' => array( + 'href' => rest_url( sprintf( '/%s/coupons/%d', $this->namespace, $object->coupon_id ) ), + ), + ); + + return $links; + } + + /** + * Get the Report's schema, conforming to JSON Schema. + * + * @return array + */ + public function get_item_schema() { + $schema = array( + '$schema' => 'http://json-schema.org/draft-04/schema#', + 'title' => 'report_coupons', + 'type' => 'object', + 'properties' => array( + 'coupon_id' => array( + 'description' => __( 'Coupon ID.', 'woocommerce' ), + 'type' => 'integer', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ), + 'gross_discount' => array( + 'description' => __( 'Gross discount.', 'woocommerce' ), + 'type' => 'number', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ), + 'orders_count' => array( + 'description' => __( 'Amount of orders.', 'woocommerce' ), + 'type' => 'integer', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ), + ), + ); + + return $this->add_additional_fields_schema( $schema ); + } + + /** + * Get the query params for collections. + * + * @return array + */ + public function get_collection_params() { + $params = array(); + $params['context'] = $this->get_context_param( array( 'default' => 'view' ) ); + $params['page'] = array( + 'description' => __( 'Current page of the collection.', 'woocommerce' ), + 'type' => 'integer', + 'default' => 1, + 'sanitize_callback' => 'absint', + 'validate_callback' => 'rest_validate_request_arg', + 'minimum' => 1, + ); + $params['per_page'] = array( + 'description' => __( 'Maximum number of items to be returned in result set.', 'woocommerce' ), + 'type' => 'integer', + 'default' => 10, + 'minimum' => 1, + 'maximum' => 100, + 'sanitize_callback' => 'absint', + 'validate_callback' => 'rest_validate_request_arg', + ); + $params['after'] = array( + 'description' => __( 'Limit response to resources published after a given ISO8601 compliant date.', 'woocommerce' ), + 'type' => 'string', + 'format' => 'date-time', + 'validate_callback' => 'rest_validate_request_arg', + ); + $params['before'] = array( + 'description' => __( 'Limit response to resources published before a given ISO8601 compliant date.', 'woocommerce' ), + 'type' => 'string', + 'format' => 'date-time', + 'validate_callback' => 'rest_validate_request_arg', + ); + $params['order'] = array( + 'description' => __( 'Order sort attribute ascending or descending.', 'woocommerce' ), + 'type' => 'string', + 'default' => 'desc', + 'enum' => array( 'asc', 'desc' ), + 'validate_callback' => 'rest_validate_request_arg', + ); + $params['orderby'] = array( + 'description' => __( 'Sort collection by object attribute.', 'woocommerce' ), + 'type' => 'string', + 'default' => 'date', + 'enum' => array( + 'date', + 'items_sold', + 'gross_revenue', + 'orders_count', + 'products_count', + ), + 'validate_callback' => 'rest_validate_request_arg', + ); + $params['interval'] = array( + 'description' => __( 'Time interval to use for buckets in the returned data.', 'woocommerce' ), + 'type' => 'string', + 'default' => 'week', + 'enum' => array( + 'hour', + 'day', + 'week', + 'month', + 'quarter', + 'year', + ), + 'validate_callback' => 'rest_validate_request_arg', + ); + $params['coupons'] = array( + 'description' => __( 'Limit result set to items assigned one or more code.', 'woocommerce' ), + 'type' => 'array', + 'sanitize_callback' => 'wp_parse_slug_list', + 'validate_callback' => 'rest_validate_request_arg', + 'items' => array( + 'type' => 'string', + ), + ); + + return $params; + } } diff --git a/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-coupons-stats-controller.php b/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-coupons-stats-controller.php index 0aa3b8def5c..3d92718b698 100644 --- a/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-coupons-stats-controller.php +++ b/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-coupons-stats-controller.php @@ -30,4 +30,277 @@ class WC_Admin_REST_Reports_Coupons_Stats_Controller extends WC_REST_Reports_Con * @var string */ protected $rest_base = 'reports/coupons/stats'; + + + /** + * Maps query arguments from the REST request. + * + * @param array $request Request array. + * @return array + */ + protected function prepare_reports_query( $request ) { + $args = array(); + $args['before'] = $request['before']; + $args['after'] = $request['after']; + $args['interval'] = $request['interval']; + $args['page'] = $request['page']; + $args['per_page'] = $request['per_page']; + $args['orderby'] = $request['orderby']; + $args['order'] = $request['order']; + $args['code'] = (array) $request['code']; + + 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 ); + $coupons_query = new WC_Reports_Orders_Stats_Query( $query_args ); // @todo change to correct class. + $report_data = $coupons_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_coupons_stats', $response, $report, $request ); + } + + /** + * Get the Report's schema, conforming to JSON Schema. + * + * @return array + */ + public function get_item_schema() { + $totals = array( + 'gross_discount' => array( + 'description' => __( 'Gross discount.', 'woocommerce' ), + 'type' => 'number', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ), + 'coupons_count' => array( + 'description' => __( 'Amount of coupons.', 'woocommerce' ), + 'type' => 'integer', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ), + 'orders_count' => array( + 'description' => __( 'Amount of orders.', 'woocommerce' ), + 'type' => 'integer', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ), + ); + + $schema = array( + '$schema' => 'http://json-schema.org/draft-04/schema#', + 'title' => 'report_coupons_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', + 'gross_revenue', + 'orders_count', + 'products_count', + ), + 'validate_callback' => 'rest_validate_request_arg', + ); + $params['interval'] = array( + 'description' => __( 'Time interval to use for buckets in the returned data.', 'woocommerce' ), + 'type' => 'string', + 'default' => 'week', + 'enum' => array( + 'hour', + 'day', + 'week', + 'month', + 'quarter', + 'year', + ), + 'validate_callback' => 'rest_validate_request_arg', + ); + $params['coupons'] = array( + 'description' => __( 'Limit result set to items assigned one or more code.', 'woocommerce' ), + 'type' => 'array', + 'sanitize_callback' => 'wp_parse_slug_list', + 'validate_callback' => 'rest_validate_request_arg', + 'items' => array( + 'type' => 'string', + ), + ); + + return $params; + } } diff --git a/plugins/woocommerce-admin/includes/class-wc-admin-api-init.php b/plugins/woocommerce-admin/includes/class-wc-admin-api-init.php index c88f6d32d3e..19ffe5f77a3 100644 --- a/plugins/woocommerce-admin/includes/class-wc-admin-api-init.php +++ b/plugins/woocommerce-admin/includes/class-wc-admin-api-init.php @@ -72,6 +72,8 @@ class WC_Admin_Api_Init { 'WC_Admin_REST_Reports_Categories_Controller', 'WC_Admin_REST_Reports_Taxes_Controller', 'WC_Admin_REST_Reports_Taxes_Stats_Controller', + 'WC_Admin_REST_Reports_Coupons_Controller', + 'WC_Admin_REST_Reports_Coupons_Stats_Controller', ); foreach ( $controllers as $controller ) { From 661b5d6ef7aa972808b6da14de69158cf999b24a Mon Sep 17 00:00:00 2001 From: Claudio Sanches Date: Tue, 18 Sep 2018 16:41:45 -0300 Subject: [PATCH 20/20] Included docblocks --- .../includes/class-wc-admin-api-init.php | 63 ++++++++++++++++++- 1 file changed, 62 insertions(+), 1 deletion(-) diff --git a/plugins/woocommerce-admin/includes/class-wc-admin-api-init.php b/plugins/woocommerce-admin/includes/class-wc-admin-api-init.php index 19ffe5f77a3..2a5e274badc 100644 --- a/plugins/woocommerce-admin/includes/class-wc-admin-api-init.php +++ b/plugins/woocommerce-admin/includes/class-wc-admin-api-init.php @@ -1,8 +1,20 @@ create_db_tables(); } + /** + * Init classes. + */ public function init_classes() { // Interfaces. require_once dirname( __FILE__ ) . '/interfaces/class-wc-admin-reports-data-store-interface.php'; @@ -45,6 +60,9 @@ class WC_Admin_Api_Init { require_once dirname( __FILE__ ) . '/data-stores/class-wc-admin-reports-categories-data-store.php'; } + /** + * Init REST API. + */ public function rest_api_init() { require_once dirname( __FILE__ ) . '/api/class-wc-admin-rest-reports-controller.php'; require_once dirname( __FILE__ ) . '/api/class-wc-admin-rest-system-status-tools-controller.php'; @@ -82,6 +100,12 @@ class WC_Admin_Api_Init { } } + /** + * Filter REST API endpoints. + * + * @param array $endpoints List of endpoints. + * @return array + */ public static function filter_rest_endpoints( $endpoints ) { // Override GET /wc/v3/system_status/tools. if ( isset( $endpoints['/wc/v3/system_status/tools'] ) @@ -114,6 +138,12 @@ class WC_Admin_Api_Init { return $endpoints; } + /** + * Adds regenerate tool. + * + * @param array $tools List of tools. + * @return array + */ public static function add_regenerate_tool( $tools ) { return array_merge( $tools, @@ -128,11 +158,20 @@ class WC_Admin_Api_Init { ); } + /** + * Init orders data store. + */ public static function orders_data_store_init() { WC_Admin_Reports_Orders_Data_Store::init(); } - public static function order_product_lookup_store_init( $updater = false ) { + /** + * Init orders product looup store. + * + * @param WC_Background_Updater|null $updater Updater instance. + * @return bool + */ + public static function order_product_lookup_store_init( $updater = null ) { global $wpdb; $orders = get_transient( 'wc_update_350_all_orders' ); @@ -181,8 +220,16 @@ class WC_Admin_Api_Init { return true; } } + + return true; } + /** + * Adds data stores. + * + * @param array $data_stores List of data stores. + * @return array + */ public static function add_data_stores( $data_stores ) { return array_merge( $data_stores, @@ -196,6 +243,12 @@ class WC_Admin_Api_Init { ); } + /** + * Adds report tables. + * + * @param array $wc_tables List of WooCommerce tables. + * @return array + */ public static function add_report_tables( $wc_tables ) { global $wpdb; @@ -211,6 +264,11 @@ class WC_Admin_Api_Init { ); } + /** + * Get database schema. + * + * @return string + */ private static function get_schema() { global $wpdb; @@ -270,6 +328,9 @@ class WC_Admin_Api_Init { return $tables; } + /** + * Create database tables. + */ protected function create_db_tables() { require_once ABSPATH . 'wp-admin/includes/upgrade.php';