erformance_indicators', array( 'revenue/total_sales', 'revenue/net_revenue', 'orders/orders_count', 'orders/avg_order_value', 'products/items_sold', 'revenue/refunds', 'coupons/orders_count', 'coupons/amount', 'taxes/total_tax', 'taxes/order_tax', 'taxes/shipping_tax', 'revenue/shipping', 'downloads/download_count', ) ); $a = array_search( $a->stat, $stat_order, true ); $b = array_search( $b->stat, $stat_order, true ); if ( false === $a && false === $b ) { return 0; } elseif ( false === $a ) { return 1; } elseif ( false === $b ) { return -1; } else { return $a - $b; } } /** * Get report stats data, avoiding duplicate requests for stats that use the same endpoint. * * @param string $report Report slug to request data for. * @param array $query_args Report query args. * @return WP_REST_Response|WP_Error Report stats data. */ private function get_stats_data( $report, $query_args ) { // Return from cache if we've already requested these report stats. if ( isset( $this->stats_data[ $report ] ) ) { return $this->stats_data[ $report ]; } // Request the report stats. $request_url = $this->endpoints[ $report ]; $request = new \WP_REST_Request( 'GET', $request_url ); $request->set_param( 'before', $query_args['before'] ); $request->set_param( 'after', $query_args['after'] ); $response = rest_do_request( $request ); // Cache the response. $this->stats_data[ $report ] = $response; return $response; } /** * Get all reports. * * @param WP_REST_Request $request Request data. * @return array|WP_Error */ public function get_items( $request ) { $indicator_data = $this->get_indicator_data(); if ( is_wp_error( $indicator_data ) ) { return $indicator_data; } $query_args = $this->prepare_reports_query( $request ); if ( empty( $query_args['stats'] ) ) { return new \WP_Error( 'woocommerce_analytics_performance_indicators_empty_query', __( 'A list of stats to query must be provided.', 'woocommerce' ), 400 ); } $stats = array(); foreach ( $query_args['stats'] as $stat ) { $is_error = false; $pieces = $this->get_stats_parts( $stat ); $report = $pieces[0]; $chart = $pieces[1]; if ( ! in_array( $stat, $this->allowed_stats, true ) ) { continue; } $response = $this->get_stats_data( $report, $query_args ); if ( is_wp_error( $response ) ) { return $response; } $data = $response->get_data(); $format = $this->formats[ $stat ]; $label = $this->labels[ $stat ]; if ( 200 !== $response->get_status() ) { $stats[] = (object) array( 'stat' => $stat, 'chart' => $chart, 'label' => $label, 'format' => $format, 'value' => null, ); continue; } $stats[] = (object) array( 'stat' => $stat, 'chart' => $chart, 'label' => $label, 'format' => $format, 'value' => apply_filters( 'woocommerce_rest_performance_indicators_data_value', $data, $stat, $report, $chart, $query_args ), ); } usort( $stats, array( $this, 'sort' ) ); $objects = array(); foreach ( $stats as $stat ) { $data = $this->prepare_item_for_response( $stat, $request ); $objects[] = $this->prepare_response_for_collection( $data ); } $response = rest_ensure_response( $objects ); $response->header( 'X-WP-Total', count( $stats ) ); $response->header( 'X-WP-TotalPages', 1 ); $base = add_query_arg( $request->get_query_params(), rest_url( sprintf( '/%s/%s', $this->namespace, $this->rest_base ) ) ); return $response; } /** * Prepare a report data item for serialization. * * @param array $stat_data Report data item as returned from Data Store. * @param WP_REST_Request $request Request object. * @return WP_REST_Response */ public function prepare_item_for_response( $stat_data, $request ) { $response = parent::prepare_item_for_response( $stat_data, $request ); $response->add_links( $this->prepare_links( $stat_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_performance_indicators', $response, $stat_data, $request ); } /** * Prepare links for the request. * * @param object $object data. * @return array */ protected function prepare_links( $object ) { $pieces = $this->get_stats_parts( $object->stat ); $endpoint = $pieces[0]; $stat = $pieces[1]; $url = isset( $this->urls[ $endpoint ] ) ? $this->urls[ $endpoint ] : ''; $links = array( 'api' => array( 'href' => rest_url( $this->endpoints[ $endpoint ] ), ), 'report' => array( 'href' => $url, ), ); return $links; } /** * Returns the endpoint part of a stat request (prefix) and the actual stat total we want. * To allow extensions to namespace (example: fue/emails/sent), we break on the last forward slash. * * @param string $full_stat A stat request string like orders/avg_order_value or fue/emails/sent. * @return array Containing the prefix (endpoint) and suffix (stat). */ private function get_stats_parts( $full_stat ) { $endpoint = substr( $full_stat, 0, strrpos( $full_stat, '/' ) ); $stat = substr( $full_stat, ( strrpos( $full_stat, '/' ) + 1 ) ); return array( $endpoint, $stat, ); } /** * Format the data returned from the API for given stats. * * @param array $data Data from external endpoint. * @param string $stat Name of the stat. * @param string $report Name of the report. * @param string $chart Name of the chart. * @param array $query_args Query args. * @return mixed */ public function format_data_value( $data, $stat, $report, $chart, $query_args ) { if ( 'jetpack/stats' === $report ) { $index = false; // Get the index of the field to tally. if ( isset( $data['general']->visits->fields ) && is_array( $data['general']->visits->fields ) ) { $index = array_search( $chart, $data['general']->visits->fields, true ); } if ( ! $index ) { return null; } // Loop over provided data and filter by the queried date. // Note that this is currently limited to 30 days via the Jetpack API // but the WordPress.com endpoint allows up to 90 days. $total = 0; $before = gmdate( 'Y-m-d', strtotime( isset( $query_args['before'] ) ? $query_args['before'] : TimeInterval::default_before() ) ); $after = gmdate( 'Y-m-d', strtotime( isset( $query_args['after'] ) ? $query_args['after'] : TimeInterval::default_after() ) ); foreach ( $data['general']->visits->data as $datum ) { if ( $datum[0] >= $after && $datum[0] <= $before ) { $total += $datum[ $index ]; } } return $total; } if ( isset( $data['totals'] ) && isset( $data['totals'][ $chart ] ) ) { return $data['totals'][ $chart ]; } return null; } /** * Get the Report's schema, conforming to JSON Schema. * * @return array */ public function get_item_schema() { $indicator_data = $this->get_indicator_data(); if ( is_wp_error( $indicator_data ) ) { $allowed_stats = array(); } else { $allowed_stats = $this->allowed_stats; } $schema = array( '$schema' => 'http://json-schema.org/draft-04/schema#', 'title' => 'report_performance_indicator', 'type' => 'object', 'properties' => array( 'stat' => array( 'description' => __( 'Unique identifier for the resource.', 'woocommerce' ), 'type' => 'string', 'context' => array( 'view', 'edit' ), 'readonly' => true, 'enum' => $allowed_stats, ), 'chart' => array( 'description' => __( 'The specific chart this stat referrers to.', 'woocommerce' ), 'type' => 'string', 'context' => array( 'view', 'edit' ), 'readonly' => true, ), 'label' => array( 'description' => __( 'Human readable label for the stat.', 'woocommerce' ), 'type' => 'string', 'context' => array( 'view', 'edit' ), 'readonly' => true, ), 'format' => array( 'description' => __( 'Format of the stat.', 'woocommerce' ), 'type' => 'number', 'context' => array( 'view', 'edit' ), 'readonly' => true, 'enum' => array( 'number', 'currency' ), ), 'value' => array( 'description' => __( 'Value of the stat. Returns null if the stat does not exist or cannot be loaded.', 'woocommerce' ), 'type' => 'number', 'context' => array( 'view', 'edit' ), 'readonly' => true, ), ), ); return $this->add_additional_fields_schema( $schema ); } /** * Get schema for the list of allowed performance indicators. * * @return array $schema */ public function get_public_allowed_item_schema() { $schema = $this->get_public_item_schema(); unset( $schema['properties']['value'] ); unset( $schema['properties']['format'] ); return $schema; } /** * Get the query params for collections. * * @return array */ public function get_collection_params() { $indicator_data = $this->get_indicator_data(); if ( is_wp_error( $indicator_data ) ) { $allowed_stats = __( 'There was an issue loading the report endpoints', 'woocommerce' ); } else { $allowed_stats = implode( ', ', $this->allowed_stats ); } $params = array(); $params['context'] = $this->get_context_param( array( 'default' => 'view' ) ); $params['stats'] = array( 'description' => sprintf( /* translators: Allowed values is a list of stat endpoints. */ __( 'Limit response to specific report stats. Allowed values: %s.', 'woocommerce' ), $allowed_stats ), 'type' => 'array', 'validate_callback' => 'rest_validate_request_arg', 'items' => array( 'type' => 'string', 'enum' => $this->allowed_stats, ), 'default' => $this->allowed_stats, ); $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', ); return $params; } }