plugin updates
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
declare( strict_types = 1 );
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\API\AI;
|
||||
|
||||
/**
|
||||
* AI Endpoint base controller
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
abstract class AIEndpoint {
|
||||
/**
|
||||
* Endpoint namespace.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $namespace = 'wc-admin';
|
||||
|
||||
/**
|
||||
* Route base.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $rest_base = 'ai';
|
||||
|
||||
|
||||
/**
|
||||
* Endpoint.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $endpoint;
|
||||
|
||||
/**
|
||||
* Register routes.
|
||||
*
|
||||
* @param array $args Optional. Either an array of options for the endpoint,
|
||||
* or an array of arrays for multiple methods. Default empty array.
|
||||
*/
|
||||
public function register( $args ) {
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->rest_base . '/' . $this->endpoint,
|
||||
$args
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return schema properties.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_schema() {
|
||||
return array();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
|
||||
declare( strict_types = 1 );
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\API\AI;
|
||||
|
||||
use WP_Error;
|
||||
use WP_REST_Request;
|
||||
use WP_REST_Response;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* Store Title controller
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class BusinessDescription extends AIEndpoint {
|
||||
/**
|
||||
* Endpoint.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $endpoint = 'business-description';
|
||||
|
||||
/**
|
||||
* Register routes.
|
||||
*/
|
||||
public function register_routes() {
|
||||
$this->register(
|
||||
array(
|
||||
array(
|
||||
'methods' => \WP_REST_Server::CREATABLE,
|
||||
'callback' => array( $this, 'update_business_description' ),
|
||||
'permission_callback' => array( Middleware::class, 'is_authorized' ),
|
||||
'args' => array(
|
||||
'business_description' => array(
|
||||
'description' => __( 'The business description for a given store.', 'woocommerce' ),
|
||||
'type' => 'string',
|
||||
),
|
||||
),
|
||||
),
|
||||
'schema' => array( $this, 'get_schema' ),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the business description.
|
||||
*
|
||||
* @param WP_REST_Request $request Request object.
|
||||
* @return WP_REST_Response|WP_Error Response object.
|
||||
*/
|
||||
public function update_business_description( $request ) {
|
||||
|
||||
$business_description = $request->get_param( 'business_description' );
|
||||
|
||||
if ( ! $business_description ) {
|
||||
return new WP_Error(
|
||||
'invalid_business_description',
|
||||
__( 'Invalid business description.', 'woocommerce' )
|
||||
);
|
||||
}
|
||||
|
||||
update_option( 'last_business_description_with_ai_content_generated', $business_description );
|
||||
|
||||
return rest_ensure_response(
|
||||
array(
|
||||
'ai_content_generated' => true,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Business Description response.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_schema() {
|
||||
return array(
|
||||
'ai_content_generated' => true,
|
||||
);
|
||||
}
|
||||
}
|
||||
105
wp/wp-content/plugins/woocommerce/src/Admin/API/AI/Images.php
Normal file
105
wp/wp-content/plugins/woocommerce/src/Admin/API/AI/Images.php
Normal file
@@ -0,0 +1,105 @@
|
||||
<?php
|
||||
|
||||
declare( strict_types = 1 );
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\API\AI;
|
||||
|
||||
use Automattic\WooCommerce\Blocks\AI\Connection;
|
||||
use Automattic\WooCommerce\Blocks\Images\Pexels;
|
||||
use WP_Error;
|
||||
use WP_REST_Request;
|
||||
use WP_REST_Response;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* Images controller
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class Images extends AIEndpoint {
|
||||
/**
|
||||
* Endpoint.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $endpoint = 'images';
|
||||
|
||||
/**
|
||||
* Register routes.
|
||||
*/
|
||||
public function register_routes() {
|
||||
$this->register(
|
||||
array(
|
||||
array(
|
||||
'methods' => \WP_REST_Server::CREATABLE,
|
||||
'callback' => array( $this, 'generate_images' ),
|
||||
'permission_callback' => array( Middleware::class, 'is_authorized' ),
|
||||
'args' => array(
|
||||
'business_description' => array(
|
||||
'description' => __( 'The business description for a given store.', 'woocommerce' ),
|
||||
'type' => 'string',
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate Images from Pexels
|
||||
*
|
||||
* @param WP_REST_Request $request Request object.
|
||||
*
|
||||
* @return WP_Error|WP_REST_Response
|
||||
*/
|
||||
public function generate_images( WP_REST_Request $request ) {
|
||||
|
||||
$business_description = sanitize_text_field( wp_unslash( $request['business_description'] ) );
|
||||
|
||||
if ( empty( $business_description ) ) {
|
||||
$business_description = get_option( 'woo_ai_describe_store_description' );
|
||||
}
|
||||
|
||||
$last_business_description = get_option( 'last_business_description_with_ai_content_generated' );
|
||||
|
||||
if ( $last_business_description === $business_description ) {
|
||||
return rest_ensure_response(
|
||||
array(
|
||||
'ai_content_generated' => true,
|
||||
'images' => array(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
$ai_connection = new Connection();
|
||||
|
||||
$site_id = $ai_connection->get_site_id();
|
||||
|
||||
if ( is_wp_error( $site_id ) ) {
|
||||
return $site_id;
|
||||
}
|
||||
|
||||
$token = $ai_connection->get_jwt_token( $site_id );
|
||||
|
||||
if ( is_wp_error( $token ) ) {
|
||||
return $token;
|
||||
}
|
||||
|
||||
$images = ( new Pexels() )->get_images( $ai_connection, $token, $business_description );
|
||||
|
||||
if ( is_wp_error( $images ) ) {
|
||||
$images = array(
|
||||
'images' => array(),
|
||||
'search_term' => '',
|
||||
);
|
||||
}
|
||||
|
||||
return rest_ensure_response(
|
||||
array(
|
||||
'ai_content_generated' => true,
|
||||
'images' => $images,
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
declare( strict_types = 1 );
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\API\AI;
|
||||
|
||||
use Automattic\WooCommerce\StoreApi\Exceptions\RouteException;
|
||||
use WP_Error;
|
||||
|
||||
/**
|
||||
* Middleware class.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class Middleware {
|
||||
/**
|
||||
* Ensure that the user is allowed to make this request.
|
||||
*
|
||||
* @return boolean|WP_Error
|
||||
* @throws RouteException If the user is not allowed to make this request.
|
||||
*/
|
||||
public static function is_authorized() {
|
||||
try {
|
||||
if ( ! current_user_can( 'manage_options' ) ) {
|
||||
throw new RouteException( 'woocommerce_rest_invalid_user', __( 'You are not allowed to make this request. Please make sure you are logged in.', 'woocommerce' ), 403 );
|
||||
}
|
||||
} catch ( RouteException $error ) {
|
||||
return new WP_Error(
|
||||
$error->getErrorCode(),
|
||||
$error->getMessage(),
|
||||
array( 'status' => $error->getCode() )
|
||||
);
|
||||
}
|
||||
|
||||
$allow_ai_connection = get_option( 'woocommerce_blocks_allow_ai_connection' );
|
||||
|
||||
if ( ! $allow_ai_connection ) {
|
||||
try {
|
||||
throw new RouteException( 'ai_connection_not_allowed', __( 'AI content generation is not allowed on this store. Update your store settings if you wish to enable this feature.', 'woocommerce' ), 403 );
|
||||
} catch ( RouteException $error ) {
|
||||
return new WP_Error(
|
||||
$error->getErrorCode(),
|
||||
$error->getMessage(),
|
||||
array( 'status' => $error->getCode() )
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
<?php
|
||||
|
||||
declare( strict_types = 1 );
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\API\AI;
|
||||
|
||||
use Automattic\WooCommerce\Blocks\AI\Connection;
|
||||
use Automattic\WooCommerce\Blocks\AIContent\PatternsHelper;
|
||||
use Automattic\WooCommerce\Blocks\AIContent\UpdatePatterns;
|
||||
use WP_Error;
|
||||
use WP_REST_Request;
|
||||
use WP_REST_Response;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* Patterns controller
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class Patterns extends AIEndpoint {
|
||||
/**
|
||||
* Endpoint.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $endpoint = 'patterns';
|
||||
|
||||
/**
|
||||
* Register routes.
|
||||
*/
|
||||
public function register_routes() {
|
||||
$this->register(
|
||||
array(
|
||||
array(
|
||||
'methods' => \WP_REST_Server::CREATABLE,
|
||||
'callback' => array( $this, 'update_patterns' ),
|
||||
'permission_callback' => array( Middleware::class, 'is_authorized' ),
|
||||
'args' => array(
|
||||
'business_description' => array(
|
||||
'description' => __( 'The business description for a given store.', 'woocommerce' ),
|
||||
'type' => 'string',
|
||||
),
|
||||
'images' => array(
|
||||
'description' => __( 'The images for a given store.', 'woocommerce' ),
|
||||
'type' => 'object',
|
||||
),
|
||||
),
|
||||
),
|
||||
array(
|
||||
'methods' => \WP_REST_Server::DELETABLE,
|
||||
'callback' => array( $this, 'delete_patterns' ),
|
||||
'permission_callback' => array( Middleware::class, 'is_authorized' ),
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update patterns with the content and images powered by AI.
|
||||
*
|
||||
* @param WP_REST_Request $request Request object.
|
||||
*
|
||||
* @return WP_Error|WP_REST_Response
|
||||
*/
|
||||
public function update_patterns( WP_REST_Request $request ) {
|
||||
$business_description = sanitize_text_field( wp_unslash( $request['business_description'] ) );
|
||||
|
||||
$ai_connection = new Connection();
|
||||
|
||||
$site_id = $ai_connection->get_site_id();
|
||||
|
||||
if ( is_wp_error( $site_id ) ) {
|
||||
return $site_id;
|
||||
}
|
||||
|
||||
$token = $ai_connection->get_jwt_token( $site_id );
|
||||
|
||||
$images = $request['images'];
|
||||
|
||||
try {
|
||||
( new UpdatePatterns() )->generate_content( $ai_connection, $token, $images, $business_description );
|
||||
return rest_ensure_response( array( 'ai_content_generated' => true ) );
|
||||
} catch ( \Exception $e ) {
|
||||
return rest_ensure_response( array( 'ai_content_generated' => false ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove patterns generated by AI.
|
||||
*
|
||||
* @return WP_Error|WP_REST_Response
|
||||
*/
|
||||
public function delete_patterns() {
|
||||
PatternsHelper::delete_patterns_ai_data_post();
|
||||
return rest_ensure_response( array( 'removed' => true ) );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
<?php
|
||||
|
||||
declare( strict_types = 1 );
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\API\AI;
|
||||
|
||||
use Automattic\WooCommerce\Blocks\AIContent\UpdateProducts;
|
||||
use WP_Error;
|
||||
use WP_REST_Request;
|
||||
use WP_REST_Response;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* Product controller
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class Product extends AIEndpoint {
|
||||
/**
|
||||
* The endpoint response option name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const AI_CONTENT_GENERATED = 'ai_content_generated';
|
||||
|
||||
/**
|
||||
* Endpoint.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $endpoint = 'product';
|
||||
|
||||
/**
|
||||
* Register routes.
|
||||
*/
|
||||
public function register_routes() {
|
||||
$this->register(
|
||||
array(
|
||||
array(
|
||||
'methods' => \WP_REST_Server::CREATABLE,
|
||||
'callback' => array( $this, 'update_product' ),
|
||||
'permission_callback' => array( Middleware::class, 'is_authorized' ),
|
||||
'args' => array(
|
||||
'products_information' => array(
|
||||
'description' => __( 'Data generated by AI for updating dummy products.', 'woocommerce' ),
|
||||
'type' => 'object',
|
||||
),
|
||||
'last_product' => array(
|
||||
'description' => __( 'Whether the product being updated is the last one in the loop', 'woocommerce' ),
|
||||
'type' => 'boolean',
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update product with the content and images powered by AI.
|
||||
*
|
||||
* @param WP_REST_Request $request Request object.
|
||||
*
|
||||
* @return WP_REST_Response
|
||||
*/
|
||||
public function update_product( WP_REST_Request $request ) {
|
||||
$product_information = $request['products_information'] ?? array();
|
||||
|
||||
if ( empty( $product_information ) ) {
|
||||
return rest_ensure_response(
|
||||
array(
|
||||
self::AI_CONTENT_GENERATED => true,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
$product_updater = new UpdateProducts();
|
||||
$product_updater->update_product_content( $product_information );
|
||||
} catch ( \Exception $e ) {
|
||||
return rest_ensure_response( array( 'ai_content_generated' => false ) );
|
||||
}
|
||||
|
||||
$last_product_to_update = $request['last_product'] ?? false;
|
||||
|
||||
if ( $last_product_to_update ) {
|
||||
flush_rewrite_rules();
|
||||
}
|
||||
|
||||
return rest_ensure_response(
|
||||
array(
|
||||
self::AI_CONTENT_GENERATED => true,
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
|
||||
declare( strict_types = 1 );
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\API\AI;
|
||||
|
||||
use Automattic\WooCommerce\Blocks\AI\Connection;
|
||||
use Automattic\WooCommerce\Blocks\AIContent\PatternsHelper;
|
||||
use Automattic\WooCommerce\Blocks\AIContent\UpdateProducts;
|
||||
use WP_Error;
|
||||
use WP_REST_Request;
|
||||
use WP_REST_Response;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* Store Info controller
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class StoreInfo extends AIEndpoint {
|
||||
/**
|
||||
* Endpoint.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $endpoint = 'store-info';
|
||||
|
||||
/**
|
||||
* Register routes.
|
||||
*/
|
||||
public function register_routes() {
|
||||
$this->register(
|
||||
array(
|
||||
array(
|
||||
'methods' => \WP_REST_Server::READABLE,
|
||||
'callback' => array( $this, 'get_response' ),
|
||||
'permission_callback' => array( Middleware::class, 'is_authorized' ),
|
||||
),
|
||||
'schema' => array( $this, 'get_schema' ),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the store title powered by AI.
|
||||
*
|
||||
* @return WP_Error|WP_REST_Response
|
||||
*/
|
||||
public function get_response() {
|
||||
$product_updater = new UpdateProducts();
|
||||
$patterns = PatternsHelper::get_patterns_ai_data_post();
|
||||
|
||||
$products = $product_updater->fetch_product_ids( 'dummy' );
|
||||
|
||||
if ( empty( $products ) && ! isset( $patterns ) ) {
|
||||
return rest_ensure_response(
|
||||
array(
|
||||
'is_ai_generated' => false,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return rest_ensure_response(
|
||||
array(
|
||||
'is_ai_generated' => true,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Business Description response.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_schema() {
|
||||
return array(
|
||||
'ai_content_generated' => true,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
<?php
|
||||
|
||||
declare( strict_types = 1 );
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\API\AI;
|
||||
|
||||
use Automattic\WooCommerce\Blocks\AI\Connection;
|
||||
use WP_Error;
|
||||
use WP_REST_Request;
|
||||
use WP_REST_Response;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* Store Title controller
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class StoreTitle extends AIEndpoint {
|
||||
/**
|
||||
* The store title option name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const STORE_TITLE_OPTION_NAME = 'blogname';
|
||||
|
||||
/**
|
||||
* The AI generated store title option name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const AI_STORE_TITLE_OPTION_NAME = 'ai_generated_site_title';
|
||||
|
||||
/**
|
||||
* The default store title.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const DEFAULT_TITLE = 'Site Title';
|
||||
|
||||
/**
|
||||
* Endpoint.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $endpoint = 'store-title';
|
||||
|
||||
/**
|
||||
* Register routes.
|
||||
*/
|
||||
public function register_routes() {
|
||||
$this->register(
|
||||
array(
|
||||
array(
|
||||
'methods' => \WP_REST_Server::CREATABLE,
|
||||
'callback' => array( $this, 'update_store_title' ),
|
||||
'permission_callback' => array( Middleware::class, 'is_authorized' ),
|
||||
'args' => array(
|
||||
'business_description' => array(
|
||||
'description' => __( 'The business description for a given store.', 'woocommerce' ),
|
||||
'type' => 'string',
|
||||
),
|
||||
),
|
||||
),
|
||||
'schema' => array( $this, 'get_schema' ),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the store title powered by AI.
|
||||
*
|
||||
* @param WP_REST_Request $request Request object.
|
||||
*
|
||||
* @return WP_Error|WP_REST_Response
|
||||
*/
|
||||
public function update_store_title( $request ) {
|
||||
|
||||
$business_description = $request->get_param( 'business_description' );
|
||||
|
||||
if ( ! $business_description ) {
|
||||
return new WP_Error(
|
||||
'invalid_business_description',
|
||||
__( 'Invalid business description.', 'woocommerce' )
|
||||
);
|
||||
}
|
||||
|
||||
$store_title = html_entity_decode( get_option( self::STORE_TITLE_OPTION_NAME, '' ) );
|
||||
$previous_ai_generated_title = html_entity_decode( get_option( self::AI_STORE_TITLE_OPTION_NAME, '' ) );
|
||||
|
||||
if ( strtolower( trim( self::DEFAULT_TITLE ) ) === strtolower( trim( $store_title ) ) || ( ! empty( $store_title ) && $previous_ai_generated_title !== $store_title ) ) {
|
||||
return rest_ensure_response( array( 'ai_content_generated' => false ) );
|
||||
}
|
||||
|
||||
$ai_generated_title = $this->generate_ai_title( $business_description );
|
||||
if ( is_wp_error( $ai_generated_title ) ) {
|
||||
return $ai_generated_title;
|
||||
}
|
||||
|
||||
update_option( self::AI_STORE_TITLE_OPTION_NAME, $ai_generated_title );
|
||||
update_option( self::STORE_TITLE_OPTION_NAME, $ai_generated_title );
|
||||
|
||||
return rest_ensure_response(
|
||||
array(
|
||||
'ai_content_generated' => true,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Generate the store title powered by AI.
|
||||
*
|
||||
* @param string $business_description The business description for a given store.
|
||||
*
|
||||
* @return string|WP_Error|WP_REST_Response The store title generated by AI.
|
||||
*/
|
||||
private function generate_ai_title( $business_description ) {
|
||||
$ai_connection = new Connection();
|
||||
|
||||
$site_id = $ai_connection->get_site_id();
|
||||
if ( is_wp_error( $site_id ) ) {
|
||||
return $site_id;
|
||||
}
|
||||
|
||||
$token = $ai_connection->get_jwt_token( $site_id );
|
||||
if ( is_wp_error( $token ) ) {
|
||||
return $token;
|
||||
}
|
||||
|
||||
$prompt = "Generate a store title for a store that has the following: '$business_description'. The length of the title should be 1 and 3 words. The result should include only the store title without any other explanation, number or punctuation marks";
|
||||
|
||||
$ai_response = $ai_connection->fetch_ai_response( $token, $prompt );
|
||||
if ( is_wp_error( $ai_response ) ) {
|
||||
return $ai_response;
|
||||
}
|
||||
|
||||
if ( ! isset( $ai_response['completion'] ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return $ai_response['completion'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Business Description response.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_schema() {
|
||||
return array(
|
||||
'ai_content_generated' => true,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -86,6 +86,12 @@ class Init {
|
||||
'Automattic\WooCommerce\Admin\API\NavigationFavorites',
|
||||
'Automattic\WooCommerce\Admin\API\MobileAppMagicLink',
|
||||
'Automattic\WooCommerce\Admin\API\ShippingPartnerSuggestions',
|
||||
'Automattic\WooCommerce\Admin\API\AI\StoreTitle',
|
||||
'Automattic\WooCommerce\Admin\API\AI\BusinessDescription',
|
||||
'Automattic\WooCommerce\Admin\API\AI\StoreInfo',
|
||||
'Automattic\WooCommerce\Admin\API\AI\Images',
|
||||
'Automattic\WooCommerce\Admin\API\AI\Patterns',
|
||||
'Automattic\WooCommerce\Admin\API\AI\Product',
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -133,7 +133,7 @@ class LaunchYourStore {
|
||||
$private_link = 'no';
|
||||
$share_key = wp_generate_password( 32, false );
|
||||
|
||||
add_option( 'woocommerce_coming_soon', $coming_soon );
|
||||
update_option( 'woocommerce_coming_soon', $coming_soon );
|
||||
add_option( 'woocommerce_store_pages_only', $store_pages_only );
|
||||
add_option( 'woocommerce_private_link', $private_link );
|
||||
add_option( 'woocommerce_share_key', $share_key );
|
||||
|
||||
@@ -9,16 +9,20 @@ namespace Automattic\WooCommerce\Admin\API\Reports\Categories;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
use Automattic\WooCommerce\Admin\API\Reports\Controller as ReportsController;
|
||||
use Automattic\WooCommerce\Admin\API\Reports\ExportableInterface;
|
||||
use Automattic\WooCommerce\Admin\API\Reports\GenericController;
|
||||
use Automattic\WooCommerce\Admin\API\Reports\GenericQuery;
|
||||
use Automattic\WooCommerce\Admin\API\Reports\OrderAwareControllerTrait;
|
||||
|
||||
/**
|
||||
* REST API Reports categories controller class.
|
||||
*
|
||||
* @internal
|
||||
* @extends \Automattic\WooCommerce\Admin\API\Reports\Controller
|
||||
* @extends \Automattic\WooCommerce\Admin\API\Reports\GenericController
|
||||
*/
|
||||
class Controller extends ReportsController implements ExportableInterface {
|
||||
class Controller extends GenericController implements ExportableInterface {
|
||||
|
||||
use OrderAwareControllerTrait;
|
||||
|
||||
/**
|
||||
* Route base.
|
||||
@@ -27,6 +31,19 @@ class Controller extends ReportsController implements ExportableInterface {
|
||||
*/
|
||||
protected $rest_base = 'reports/categories';
|
||||
|
||||
/**
|
||||
* Get data from `'categories'` Query.
|
||||
*
|
||||
* @override GenericController::get_datastore_data()
|
||||
*
|
||||
* @param array $query_args Query arguments.
|
||||
* @return mixed Results from the data store.
|
||||
*/
|
||||
protected function get_datastore_data( $query_args = array() ) {
|
||||
$query = new GenericQuery( $query_args, 'categories' );
|
||||
return $query->get_data();
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps query arguments from the REST request.
|
||||
*
|
||||
@@ -52,56 +69,15 @@ class Controller extends ReportsController implements ExportableInterface {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all reports.
|
||||
* Prepare a report data item for serialization.
|
||||
*
|
||||
* @param WP_REST_Request $request Request data.
|
||||
* @return array|WP_Error
|
||||
*/
|
||||
public function get_items( $request ) {
|
||||
$query_args = $this->prepare_reports_query( $request );
|
||||
$categories_query = new 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 );
|
||||
}
|
||||
|
||||
return $this->add_pagination_headers(
|
||||
$request,
|
||||
$out_data,
|
||||
(int) $report_data->total,
|
||||
(int) $report_data->page_no,
|
||||
(int) $report_data->pages
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare a report object for serialization.
|
||||
*
|
||||
* @param stdClass $report Report data.
|
||||
* @param WP_REST_Request $request Request object.
|
||||
* @return WP_REST_Response
|
||||
* @param mixed $report 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( $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 = parent::prepare_item_for_response( $report, $request );
|
||||
$response->add_links( $this->prepare_links( $report ) );
|
||||
|
||||
/**
|
||||
@@ -119,7 +95,7 @@ class Controller extends ReportsController implements ExportableInterface {
|
||||
/**
|
||||
* Prepare links for the request.
|
||||
*
|
||||
* @param \Automattic\WooCommerce\Admin\API\Reports\Query $object Object data.
|
||||
* @param \Automattic\WooCommerce\Admin\API\Reports\GenericQuery $object Object data.
|
||||
* @return array
|
||||
*/
|
||||
protected function prepare_links( $object ) {
|
||||
@@ -193,59 +169,17 @@ class Controller extends ReportsController implements ExportableInterface {
|
||||
* @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 = parent::get_collection_params();
|
||||
$params['orderby']['default'] = 'category_id';
|
||||
$params['orderby']['enum'] = array(
|
||||
'category_id',
|
||||
'items_sold',
|
||||
'net_revenue',
|
||||
'orders_count',
|
||||
'products_count',
|
||||
'category',
|
||||
);
|
||||
$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' => 'category_id',
|
||||
'enum' => array(
|
||||
'category_id',
|
||||
'items_sold',
|
||||
'net_revenue',
|
||||
'orders_count',
|
||||
'products_count',
|
||||
'category',
|
||||
),
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
);
|
||||
$params['interval'] = array(
|
||||
$params['interval'] = array(
|
||||
'description' => __( 'Time interval to use for buckets in the returned data.', 'woocommerce' ),
|
||||
'type' => 'string',
|
||||
'default' => 'week',
|
||||
@@ -259,7 +193,7 @@ class Controller extends ReportsController implements ExportableInterface {
|
||||
),
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
);
|
||||
$params['status_is'] = array(
|
||||
$params['status_is'] = array(
|
||||
'description' => __( 'Limit result set to items that have the specified order status.', 'woocommerce' ),
|
||||
'type' => 'array',
|
||||
'sanitize_callback' => 'wp_parse_slug_list',
|
||||
@@ -269,7 +203,7 @@ class Controller extends ReportsController implements ExportableInterface {
|
||||
'type' => 'string',
|
||||
),
|
||||
);
|
||||
$params['status_is_not'] = array(
|
||||
$params['status_is_not'] = array(
|
||||
'description' => __( 'Limit result set to items that don\'t have the specified order status.', 'woocommerce' ),
|
||||
'type' => 'array',
|
||||
'sanitize_callback' => 'wp_parse_slug_list',
|
||||
@@ -279,7 +213,7 @@ class Controller extends ReportsController implements ExportableInterface {
|
||||
'type' => 'string',
|
||||
),
|
||||
);
|
||||
$params['categories'] = array(
|
||||
$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',
|
||||
@@ -288,19 +222,13 @@ class Controller extends ReportsController implements ExportableInterface {
|
||||
'type' => 'integer',
|
||||
),
|
||||
);
|
||||
$params['extended_info'] = array(
|
||||
$params['extended_info'] = array(
|
||||
'description' => __( 'Add additional piece of info about each category to the report.', 'woocommerce' ),
|
||||
'type' => 'boolean',
|
||||
'default' => false,
|
||||
'sanitize_callback' => 'wc_string_to_bool',
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
);
|
||||
$params['force_cache_refresh'] = array(
|
||||
'description' => __( 'Force retrieval of fresh data instead of from the cache.', 'woocommerce' ),
|
||||
'type' => 'boolean',
|
||||
'sanitize_callback' => 'wp_validate_boolean',
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
);
|
||||
|
||||
return $params;
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ defined( 'ABSPATH' ) || exit;
|
||||
|
||||
use Automattic\WooCommerce\Admin\API\Reports\DataStore as ReportsDataStore;
|
||||
use Automattic\WooCommerce\Admin\API\Reports\DataStoreInterface;
|
||||
use Automattic\WooCommerce\Admin\API\Reports\TimeInterval;
|
||||
use Automattic\WooCommerce\Admin\API\Reports\SqlQuery;
|
||||
|
||||
/**
|
||||
@@ -20,6 +19,8 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||
/**
|
||||
* Table used to get the data.
|
||||
*
|
||||
* @override ReportsDataStore::$table_name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected static $table_name = 'wc_order_product_lookup';
|
||||
@@ -27,6 +28,8 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||
/**
|
||||
* Cache identifier.
|
||||
*
|
||||
* @override ReportsDataStore::$cache_key
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $cache_key = 'categories';
|
||||
@@ -48,6 +51,8 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||
/**
|
||||
* Mapping columns to data type to return correct response types.
|
||||
*
|
||||
* @override ReportsDataStore::$column_types
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $column_types = array(
|
||||
@@ -61,12 +66,16 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||
/**
|
||||
* Data store context used to pass to filters.
|
||||
*
|
||||
* @override ReportsDataStore::$context
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $context = 'categories';
|
||||
|
||||
/**
|
||||
* Assign report columns once full table name has been assigned.
|
||||
*
|
||||
* @override ReportsDataStore::assign_report_columns()
|
||||
*/
|
||||
protected function assign_report_columns() {
|
||||
$table_name = self::get_db_table_name();
|
||||
@@ -145,6 +154,8 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||
/**
|
||||
* Maps ordering specified by the user to columns in the database/fields in the data.
|
||||
*
|
||||
* @override ReportsDataStore::normalize_order_by()
|
||||
*
|
||||
* @param string $order_by Sorting criterion.
|
||||
* @return string
|
||||
*/
|
||||
@@ -201,104 +212,99 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the report data based on parameters supplied by the user.
|
||||
* Get the default query arguments to be used by get_data().
|
||||
* These defaults are only partially applied when used via REST API, as that has its own defaults.
|
||||
*
|
||||
* @param array $query_args Query parameters.
|
||||
* @return stdClass|WP_Error Data.
|
||||
* @override ReportsDataStore::get_default_query_vars()
|
||||
*
|
||||
* @return array Query parameters.
|
||||
*/
|
||||
public function get_data( $query_args ) {
|
||||
public function get_default_query_vars() {
|
||||
$defaults = parent::get_default_query_vars();
|
||||
$defaults['category_includes'] = array();
|
||||
$defaults['extended_info'] = false;
|
||||
|
||||
return $defaults;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the report data based on normalized parameters.
|
||||
* Will be called by `get_data` if there is no data in cache.
|
||||
*
|
||||
* @see get_data
|
||||
* @override ReportsDataStore::get_noncached_data()
|
||||
*
|
||||
* @param array $query_args Query parameters.
|
||||
* @return stdClass|WP_Error Data object `{ totals: *, intervals: array, total: int, pages: int, page_no: int }`, or error.
|
||||
*/
|
||||
public function get_noncached_data( $query_args ) {
|
||||
global $wpdb;
|
||||
|
||||
$table_name = self::get_db_table_name();
|
||||
$this->initialize_queries();
|
||||
|
||||
// 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' => TimeInterval::default_before(),
|
||||
'after' => TimeInterval::default_after(),
|
||||
'fields' => '*',
|
||||
'category_includes' => array(),
|
||||
'extended_info' => false,
|
||||
$data = (object) array(
|
||||
'data' => array(),
|
||||
'total' => 0,
|
||||
'pages' => 0,
|
||||
'page_no' => 0,
|
||||
);
|
||||
$query_args = wp_parse_args( $query_args, $defaults );
|
||||
$this->normalize_timezones( $query_args, $defaults );
|
||||
|
||||
/*
|
||||
* We need to get the cache key here because
|
||||
* parent::update_intervals_sql_params() modifies $query_args.
|
||||
*/
|
||||
$cache_key = $this->get_cache_key( $query_args );
|
||||
$data = $this->get_cached_data( $cache_key );
|
||||
$this->subquery->add_sql_clause( 'select', $this->selected_columns( $query_args ) );
|
||||
$included_categories = $this->get_included_categories_array( $query_args );
|
||||
$this->add_sql_query_params( $query_args );
|
||||
|
||||
if ( false === $data ) {
|
||||
$this->initialize_queries();
|
||||
if ( count( $included_categories ) > 0 ) {
|
||||
$fields = $this->get_fields( $query_args );
|
||||
$ids_table = $this->get_ids_table( $included_categories, 'category_id' );
|
||||
|
||||
$data = (object) array(
|
||||
'data' => array(),
|
||||
'total' => 0,
|
||||
'pages' => 0,
|
||||
'page_no' => 0,
|
||||
$this->add_sql_clause( 'select', $this->format_join_selections( array_merge( array( 'category_id' ), $fields ), array( 'category_id' ) ) );
|
||||
$this->add_sql_clause( 'from', '(' );
|
||||
$this->add_sql_clause( 'from', $this->subquery->get_query_statement() );
|
||||
$this->add_sql_clause( 'from', ") AS {$table_name}" );
|
||||
$this->add_sql_clause(
|
||||
'right_join',
|
||||
"RIGHT JOIN ( {$ids_table} ) AS default_results
|
||||
ON default_results.category_id = {$table_name}.category_id"
|
||||
);
|
||||
|
||||
$this->subquery->add_sql_clause( 'select', $this->selected_columns( $query_args ) );
|
||||
$included_categories = $this->get_included_categories_array( $query_args );
|
||||
$this->add_sql_query_params( $query_args );
|
||||
|
||||
if ( count( $included_categories ) > 0 ) {
|
||||
$fields = $this->get_fields( $query_args );
|
||||
$ids_table = $this->get_ids_table( $included_categories, 'category_id' );
|
||||
|
||||
$this->add_sql_clause( 'select', $this->format_join_selections( array_merge( array( 'category_id' ), $fields ), array( 'category_id' ) ) );
|
||||
$this->add_sql_clause( 'from', '(' );
|
||||
$this->add_sql_clause( 'from', $this->subquery->get_query_statement() );
|
||||
$this->add_sql_clause( 'from', ") AS {$table_name}" );
|
||||
$this->add_sql_clause(
|
||||
'right_join',
|
||||
"RIGHT JOIN ( {$ids_table} ) AS default_results
|
||||
ON default_results.category_id = {$table_name}.category_id"
|
||||
);
|
||||
|
||||
$categories_query = $this->get_query_statement();
|
||||
} else {
|
||||
$this->subquery->add_sql_clause( 'order_by', $this->get_sql_clause( 'order_by' ) );
|
||||
$categories_query = $this->subquery->get_query_statement();
|
||||
}
|
||||
$categories_data = $wpdb->get_results(
|
||||
$categories_query, // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
ARRAY_A
|
||||
);
|
||||
|
||||
if ( null === $categories_data ) {
|
||||
return new \WP_Error( 'woocommerce_analytics_categories_result_failed', __( 'Sorry, fetching revenue data failed.', 'woocommerce' ), array( 'status' => 500 ) );
|
||||
}
|
||||
|
||||
$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;
|
||||
}
|
||||
|
||||
$categories_data = $this->page_records( $categories_data, $query_args['page'], $query_args['per_page'] );
|
||||
$this->include_extended_info( $categories_data, $query_args );
|
||||
$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'],
|
||||
);
|
||||
|
||||
$this->set_cached_data( $cache_key, $data );
|
||||
$categories_query = $this->get_query_statement();
|
||||
} else {
|
||||
$this->subquery->add_sql_clause( 'order_by', $this->get_sql_clause( 'order_by' ) );
|
||||
$categories_query = $this->subquery->get_query_statement();
|
||||
}
|
||||
$categories_data = $wpdb->get_results(
|
||||
$categories_query, // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
ARRAY_A
|
||||
);
|
||||
|
||||
if ( null === $categories_data ) {
|
||||
return new \WP_Error( 'woocommerce_analytics_categories_result_failed', __( 'Sorry, fetching revenue data failed.', 'woocommerce' ), array( 'status' => 500 ) );
|
||||
}
|
||||
|
||||
$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;
|
||||
}
|
||||
|
||||
$categories_data = $this->page_records( $categories_data, $query_args['page'], $query_args['per_page'] );
|
||||
$this->include_extended_info( $categories_data, $query_args );
|
||||
$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'],
|
||||
);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize query objects.
|
||||
*
|
||||
* @override ReportsDataStore::initialize_queries()
|
||||
*/
|
||||
protected function initialize_queries() {
|
||||
global $wpdb;
|
||||
|
||||
@@ -21,7 +21,9 @@ defined( 'ABSPATH' ) || exit;
|
||||
use Automattic\WooCommerce\Admin\API\Reports\Query as ReportsQuery;
|
||||
|
||||
/**
|
||||
* API\Reports\Query
|
||||
* API\Reports\Categories\Query
|
||||
*
|
||||
* @deprecated 9.3.0 Categories\Query class is deprecated, please use GenericQuery or \WC_Object_Query instead.
|
||||
*/
|
||||
class Query extends ReportsQuery {
|
||||
|
||||
@@ -30,6 +32,8 @@ class Query extends ReportsQuery {
|
||||
/**
|
||||
* Valid fields for Categories report.
|
||||
*
|
||||
* @deprecated 9.3.0 Categories\Query class is deprecated, please use GenericQuery or \WC_Object_Query instead.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_default_query_vars() {
|
||||
@@ -39,6 +43,8 @@ class Query extends ReportsQuery {
|
||||
/**
|
||||
* Get categories data based on the current query vars.
|
||||
*
|
||||
* @deprecated 9.3.0 Categories\Query class is deprecated, please use GenericQuery or \WC_Object_Query instead.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_data() {
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
<?php
|
||||
/**
|
||||
* REST API Reports controller extended by WC Admin plugin.
|
||||
*
|
||||
* Handles requests to the reports endpoint.
|
||||
* REST API Reports controller extended to handle requests to the reports endpoint.
|
||||
*/
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\API\Reports;
|
||||
@@ -10,15 +8,20 @@ namespace Automattic\WooCommerce\Admin\API\Reports;
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
use Automattic\WooCommerce\Admin\API\Reports\GenericController;
|
||||
use Automattic\WooCommerce\Admin\API\Reports\OrderAwareControllerTrait;
|
||||
|
||||
/**
|
||||
* REST API Reports controller class.
|
||||
* Reports controller class.
|
||||
*
|
||||
* Controller that handles the endpoint that returns all available analytics endpoints.
|
||||
*
|
||||
* @internal
|
||||
* @extends GenericController
|
||||
*/
|
||||
class Controller extends GenericController {
|
||||
|
||||
use OrderAwareControllerTrait;
|
||||
|
||||
/**
|
||||
* Get all reports.
|
||||
*
|
||||
@@ -135,71 +138,6 @@ class Controller extends GenericController {
|
||||
return rest_ensure_response( $data );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the order number for an order. If no filter is present for `woocommerce_order_number`, we can just return the ID.
|
||||
* Returns the parent order number if the order is actually a refund.
|
||||
*
|
||||
* @param int $order_id Order ID.
|
||||
* @return string|null The Order Number or null if the order doesn't exist.
|
||||
*/
|
||||
protected function get_order_number( $order_id ) {
|
||||
$order = wc_get_order( $order_id );
|
||||
|
||||
if ( ! $this->is_valid_order( $order ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ( 'shop_order_refund' === $order->get_type() ) {
|
||||
$order = wc_get_order( $order->get_parent_id() );
|
||||
|
||||
// If the parent order doesn't exist, return null.
|
||||
if ( ! $this->is_valid_order( $order ) ) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! has_filter( 'woocommerce_order_number' ) ) {
|
||||
return $order->get_id();
|
||||
}
|
||||
|
||||
return $order->get_order_number();
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the order is valid.
|
||||
*
|
||||
* @param bool|WC_Order|WC_Order_Refund $order Order object.
|
||||
* @return bool True if the order is valid, false otherwise.
|
||||
*/
|
||||
protected function is_valid_order( $order ) {
|
||||
return $order instanceof \WC_Order || $order instanceof \WC_Order_Refund;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the order total with the related currency formatting.
|
||||
* Returns the parent order total if the order is actually a refund.
|
||||
*
|
||||
* @param int $order_id Order ID.
|
||||
* @return string|null The Order Number or null if the order doesn't exist.
|
||||
*/
|
||||
protected function get_total_formatted( $order_id ) {
|
||||
$order = wc_get_order( $order_id );
|
||||
|
||||
if ( ! $this->is_valid_order( $order ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ( 'shop_order_refund' === $order->get_type() ) {
|
||||
$order = wc_get_order( $order->get_parent_id() );
|
||||
|
||||
if ( ! $this->is_valid_order( $order ) ) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return wp_strip_all_tags( html_entity_decode( $order->get_formatted_order_total() ), true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare a report object for serialization.
|
||||
*
|
||||
@@ -214,12 +152,8 @@ class Controller extends GenericController {
|
||||
'path' => $report->path,
|
||||
);
|
||||
|
||||
$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 = parent::prepare_item_for_response( $data, $request );
|
||||
$response->add_links(
|
||||
array(
|
||||
'self' => array(
|
||||
@@ -249,6 +183,8 @@ class Controller extends GenericController {
|
||||
/**
|
||||
* Get the Report's schema, conforming to JSON Schema.
|
||||
*
|
||||
* @override WP_REST_Controller::get_item_schema()
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_item_schema() {
|
||||
@@ -291,42 +227,4 @@ class Controller extends GenericController {
|
||||
'context' => $this->get_context_param( array( 'default' => 'view' ) ),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get order statuses without prefixes.
|
||||
* Includes unregistered statuses that have been marked "actionable".
|
||||
*
|
||||
* @internal
|
||||
* @return array
|
||||
*/
|
||||
public static function get_order_statuses() {
|
||||
// Allow all statuses selected as "actionable" - this may include unregistered statuses.
|
||||
// See: https://github.com/woocommerce/woocommerce-admin/issues/5592.
|
||||
$actionable_statuses = get_option( 'woocommerce_actionable_order_statuses', array() );
|
||||
|
||||
// See WC_REST_Orders_V2_Controller::get_collection_params() re: any/trash statuses.
|
||||
$registered_statuses = array_merge( array( 'any', 'trash' ), array_keys( self::get_order_status_labels() ) );
|
||||
|
||||
// Merge the status arrays (using flip to avoid array_unique()).
|
||||
$allowed_statuses = array_keys( array_merge( array_flip( $registered_statuses ), array_flip( $actionable_statuses ) ) );
|
||||
|
||||
return $allowed_statuses;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get order statuses (and labels) without prefixes.
|
||||
*
|
||||
* @internal
|
||||
* @return array
|
||||
*/
|
||||
public static function get_order_status_labels() {
|
||||
$order_statuses = array();
|
||||
|
||||
foreach ( wc_get_order_statuses() as $key => $label ) {
|
||||
$new_key = str_replace( 'wc-', '', $key );
|
||||
$order_statuses[ $new_key ] = $label;
|
||||
}
|
||||
|
||||
return $order_statuses;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ defined( 'ABSPATH' ) || exit;
|
||||
|
||||
use Automattic\WooCommerce\Admin\API\Reports\GenericController;
|
||||
use Automattic\WooCommerce\Admin\API\Reports\ExportableInterface;
|
||||
use Automattic\WooCommerce\Admin\API\Reports\GenericQuery;
|
||||
use WP_REST_Request;
|
||||
use WP_REST_Response;
|
||||
|
||||
@@ -29,6 +30,19 @@ class Controller extends GenericController implements ExportableInterface {
|
||||
*/
|
||||
protected $rest_base = 'reports/coupons';
|
||||
|
||||
/**
|
||||
* Get data from `'coupons'` Query.
|
||||
*
|
||||
* @override GenericController::get_datastore_data()
|
||||
*
|
||||
* @param array $query_args Query arguments.
|
||||
* @return mixed Results from the data store.
|
||||
*/
|
||||
protected function get_datastore_data( $query_args = array() ) {
|
||||
$query = new GenericQuery( $query_args, 'coupons' );
|
||||
return $query->get_data();
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps query arguments from the REST request.
|
||||
*
|
||||
@@ -50,38 +64,11 @@ class Controller extends GenericController implements ExportableInterface {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all reports.
|
||||
* Prepare a report data item for serialization.
|
||||
*
|
||||
* @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 Query( $query_args );
|
||||
$report_data = $coupons_query->get_data();
|
||||
|
||||
$data = array();
|
||||
|
||||
foreach ( $report_data->data as $coupons_data ) {
|
||||
$item = $this->prepare_item_for_response( $coupons_data, $request );
|
||||
$data[] = $this->prepare_response_for_collection( $item );
|
||||
}
|
||||
|
||||
return $this->add_pagination_headers(
|
||||
$request,
|
||||
$data,
|
||||
(int) $report_data->total,
|
||||
(int) $report_data->page_no,
|
||||
(int) $report_data->pages
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare a report object for serialization.
|
||||
*
|
||||
* @param array $report Report data.
|
||||
* @param WP_REST_Request $request Request object.
|
||||
* @return WP_REST_Response
|
||||
* @param array $report 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( $report, $request ) {
|
||||
$response = parent::prepare_item_for_response( $report, $request );
|
||||
|
||||
@@ -21,6 +21,8 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||
/**
|
||||
* Table used to get the data.
|
||||
*
|
||||
* @override ReportsDataStore::$table_name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected static $table_name = 'wc_order_coupon_lookup';
|
||||
@@ -28,6 +30,8 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||
/**
|
||||
* Cache identifier.
|
||||
*
|
||||
* @override ReportsDataStore::$cache_key
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $cache_key = 'coupons';
|
||||
@@ -35,6 +39,8 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||
/**
|
||||
* Mapping columns to data type to return correct response types.
|
||||
*
|
||||
* @override ReportsDataStore::$column_types
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $column_types = array(
|
||||
@@ -46,12 +52,16 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||
/**
|
||||
* Data store context used to pass to filters.
|
||||
*
|
||||
* @override ReportsDataStore::$context
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $context = 'coupons';
|
||||
|
||||
/**
|
||||
* Assign report columns once full table name has been assigned.
|
||||
*
|
||||
* @override ReportsDataStore::assign_report_columns()
|
||||
*/
|
||||
protected function assign_report_columns() {
|
||||
$table_name = self::get_db_table_name();
|
||||
@@ -148,6 +158,8 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||
/**
|
||||
* Maps ordering specified by the user to columns in the database/fields in the data.
|
||||
*
|
||||
* @override ReportsDataStore::normalize_order_by()
|
||||
*
|
||||
* @param string $order_by Sorting criterion.
|
||||
* @return string
|
||||
*/
|
||||
@@ -223,119 +235,6 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the report data based on parameters supplied by the user.
|
||||
*
|
||||
* @param array $query_args Query parameters.
|
||||
* @return stdClass|WP_Error Data.
|
||||
*/
|
||||
public function get_data( $query_args ) {
|
||||
global $wpdb;
|
||||
|
||||
$table_name = self::get_db_table_name();
|
||||
|
||||
// These defaults are only partially applied when used via REST API, as that has its own defaults.
|
||||
$defaults = array(
|
||||
'per_page' => get_option( 'posts_per_page' ),
|
||||
'page' => 1,
|
||||
'order' => 'DESC',
|
||||
'orderby' => 'coupon_id',
|
||||
'before' => TimeInterval::default_before(),
|
||||
'after' => TimeInterval::default_after(),
|
||||
'fields' => '*',
|
||||
'coupons' => array(),
|
||||
'extended_info' => false,
|
||||
);
|
||||
$query_args = wp_parse_args( $query_args, $defaults );
|
||||
$this->normalize_timezones( $query_args, $defaults );
|
||||
|
||||
/*
|
||||
* We need to get the cache key here because
|
||||
* parent::update_intervals_sql_params() modifies $query_args.
|
||||
*/
|
||||
$cache_key = $this->get_cache_key( $query_args );
|
||||
$data = $this->get_cached_data( $cache_key );
|
||||
|
||||
if ( false === $data ) {
|
||||
$this->initialize_queries();
|
||||
|
||||
$data = (object) array(
|
||||
'data' => array(),
|
||||
'total' => 0,
|
||||
'pages' => 0,
|
||||
'page_no' => 0,
|
||||
);
|
||||
|
||||
$selections = $this->selected_columns( $query_args );
|
||||
$included_coupons = $this->get_included_coupons_array( $query_args );
|
||||
$limit_params = $this->get_limit_params( $query_args );
|
||||
$this->subquery->add_sql_clause( 'select', $selections );
|
||||
$this->add_sql_query_params( $query_args );
|
||||
|
||||
if ( count( $included_coupons ) > 0 ) {
|
||||
$total_results = count( $included_coupons );
|
||||
$total_pages = (int) ceil( $total_results / $limit_params['per_page'] );
|
||||
|
||||
$fields = $this->get_fields( $query_args );
|
||||
$ids_table = $this->get_ids_table( $included_coupons, 'coupon_id' );
|
||||
|
||||
$this->add_sql_clause( 'select', $this->format_join_selections( $fields, array( 'coupon_id' ) ) );
|
||||
$this->add_sql_clause( 'from', '(' );
|
||||
$this->add_sql_clause( 'from', $this->subquery->get_query_statement() );
|
||||
$this->add_sql_clause( 'from', ") AS {$table_name}" );
|
||||
$this->add_sql_clause(
|
||||
'right_join',
|
||||
"RIGHT JOIN ( {$ids_table} ) AS default_results
|
||||
ON default_results.coupon_id = {$table_name}.coupon_id"
|
||||
);
|
||||
|
||||
$coupons_query = $this->get_query_statement();
|
||||
} else {
|
||||
$this->subquery->add_sql_clause( 'order_by', $this->get_sql_clause( 'order_by' ) );
|
||||
$this->subquery->add_sql_clause( 'limit', $this->get_sql_clause( 'limit' ) );
|
||||
$coupons_query = $this->subquery->get_query_statement();
|
||||
|
||||
$this->subquery->clear_sql_clause( array( 'select', 'order_by', 'limit' ) );
|
||||
$this->subquery->add_sql_clause( 'select', 'coupon_id' );
|
||||
$coupon_subquery = "SELECT COUNT(*) FROM (
|
||||
{$this->subquery->get_query_statement()}
|
||||
) AS tt";
|
||||
|
||||
$db_records_count = (int) $wpdb->get_var(
|
||||
$coupon_subquery // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
);
|
||||
|
||||
$total_results = $db_records_count;
|
||||
$total_pages = (int) ceil( $db_records_count / $limit_params['per_page'] );
|
||||
if ( $query_args['page'] < 1 || $query_args['page'] > $total_pages ) {
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
|
||||
$coupon_data = $wpdb->get_results(
|
||||
$coupons_query, // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
ARRAY_A
|
||||
);
|
||||
if ( null === $coupon_data ) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
$this->include_extended_info( $coupon_data, $query_args );
|
||||
|
||||
$coupon_data = array_map( array( $this, 'cast_numbers' ), $coupon_data );
|
||||
$data = (object) array(
|
||||
'data' => $coupon_data,
|
||||
'total' => $total_results,
|
||||
'pages' => $total_pages,
|
||||
'page_no' => (int) $query_args['page'],
|
||||
);
|
||||
|
||||
$this->set_cached_data( $cache_key, $data );
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get coupon ID for an order.
|
||||
*
|
||||
@@ -363,6 +262,115 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||
return wc_get_coupon_id_by_code( $coupon_item->get_code() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default query arguments to be used by get_data().
|
||||
* These defaults are only partially applied when used via REST API, as that has its own defaults.
|
||||
*
|
||||
* @override ReportsDataStore::get_default_query_vars()
|
||||
*
|
||||
* @return array Query parameters.
|
||||
*/
|
||||
public function get_default_query_vars() {
|
||||
$defaults = parent::get_default_query_vars();
|
||||
$defaults['orderby'] = 'coupon_id';
|
||||
$defaults['coupons'] = array();
|
||||
$defaults['extended_info'] = false;
|
||||
|
||||
return $defaults;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the report data based on normalized parameters.
|
||||
* Will be called by `get_data` if there is no data in cache.
|
||||
*
|
||||
* @override ReportsDataStore::get_noncached_data()
|
||||
*
|
||||
* @see get_data
|
||||
* @param array $query_args Query parameters.
|
||||
* @return stdClass|WP_Error Data object `{ totals: *, intervals: array, total: int, pages: int, page_no: int }`, or error.
|
||||
*/
|
||||
public function get_noncached_data( $query_args ) {
|
||||
global $wpdb;
|
||||
|
||||
$table_name = self::get_db_table_name();
|
||||
|
||||
$this->initialize_queries();
|
||||
|
||||
$data = (object) array(
|
||||
'data' => array(),
|
||||
'total' => 0,
|
||||
'pages' => 0,
|
||||
'page_no' => 0,
|
||||
);
|
||||
|
||||
$selections = $this->selected_columns( $query_args );
|
||||
$included_coupons = $this->get_included_coupons_array( $query_args );
|
||||
$limit_params = $this->get_limit_params( $query_args );
|
||||
$this->subquery->add_sql_clause( 'select', $selections );
|
||||
$this->add_sql_query_params( $query_args );
|
||||
|
||||
if ( count( $included_coupons ) > 0 ) {
|
||||
$total_results = count( $included_coupons );
|
||||
$total_pages = (int) ceil( $total_results / $limit_params['per_page'] );
|
||||
|
||||
$fields = $this->get_fields( $query_args );
|
||||
$ids_table = $this->get_ids_table( $included_coupons, 'coupon_id' );
|
||||
|
||||
$this->add_sql_clause( 'select', $this->format_join_selections( $fields, array( 'coupon_id' ) ) );
|
||||
$this->add_sql_clause( 'from', '(' );
|
||||
$this->add_sql_clause( 'from', $this->subquery->get_query_statement() );
|
||||
$this->add_sql_clause( 'from', ") AS {$table_name}" );
|
||||
$this->add_sql_clause(
|
||||
'right_join',
|
||||
"RIGHT JOIN ( {$ids_table} ) AS default_results
|
||||
ON default_results.coupon_id = {$table_name}.coupon_id"
|
||||
);
|
||||
|
||||
$coupons_query = $this->get_query_statement();
|
||||
} else {
|
||||
$this->subquery->add_sql_clause( 'order_by', $this->get_sql_clause( 'order_by' ) );
|
||||
$this->subquery->add_sql_clause( 'limit', $this->get_sql_clause( 'limit' ) );
|
||||
$coupons_query = $this->subquery->get_query_statement();
|
||||
|
||||
$this->subquery->clear_sql_clause( array( 'select', 'order_by', 'limit' ) );
|
||||
$this->subquery->add_sql_clause( 'select', 'coupon_id' );
|
||||
$coupon_subquery = "SELECT COUNT(*) FROM (
|
||||
{$this->subquery->get_query_statement()}
|
||||
) AS tt";
|
||||
|
||||
$db_records_count = (int) $wpdb->get_var(
|
||||
$coupon_subquery // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
);
|
||||
|
||||
$total_results = $db_records_count;
|
||||
$total_pages = (int) ceil( $db_records_count / $limit_params['per_page'] );
|
||||
if ( $query_args['page'] < 1 || $query_args['page'] > $total_pages ) {
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
|
||||
$coupon_data = $wpdb->get_results(
|
||||
$coupons_query, // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
ARRAY_A
|
||||
);
|
||||
if ( null === $coupon_data ) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
$this->include_extended_info( $coupon_data, $query_args );
|
||||
|
||||
$coupon_data = array_map( array( $this, 'cast_numbers' ), $coupon_data );
|
||||
$data = (object) array(
|
||||
'data' => $coupon_data,
|
||||
'total' => $total_results,
|
||||
'pages' => $total_pages,
|
||||
'page_no' => (int) $query_args['page'],
|
||||
);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create or update an an entry in the wc_order_coupon_lookup table for an order.
|
||||
*
|
||||
|
||||
@@ -21,12 +21,16 @@ use Automattic\WooCommerce\Admin\API\Reports\Query as ReportsQuery;
|
||||
|
||||
/**
|
||||
* API\Reports\Coupons\Query
|
||||
*
|
||||
* @deprecated 9.3.0 Coupons\Query class is deprecated, please use GenericQuery or \WC_Object_Query instead.
|
||||
*/
|
||||
class Query extends ReportsQuery {
|
||||
|
||||
/**
|
||||
* Valid fields for Products report.
|
||||
*
|
||||
* @deprecated 9.3.0 Coupons\Query class is deprecated, please use GenericQuery or \WC_Object_Query instead.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_default_query_vars() {
|
||||
@@ -36,6 +40,8 @@ class Query extends ReportsQuery {
|
||||
/**
|
||||
* Get product data based on the current query vars.
|
||||
*
|
||||
* @deprecated 9.3.0 Coupons\Query class is deprecated, please use GenericQuery or \WC_Object_Query instead.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_data() {
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace Automattic\WooCommerce\Admin\API\Reports\Coupons\Stats;
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
use Automattic\WooCommerce\Admin\API\Reports\GenericStatsController;
|
||||
use Automattic\WooCommerce\Admin\API\Reports\ParameterException;
|
||||
use Automattic\WooCommerce\Admin\API\Reports\GenericQuery;
|
||||
use WP_REST_Request;
|
||||
use WP_REST_Response;
|
||||
|
||||
@@ -54,51 +54,30 @@ class Controller extends GenericStatsController {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all reports.
|
||||
* Get data from `'coupons-stats'` Query.
|
||||
*
|
||||
* @param WP_REST_Request $request Request data.
|
||||
* @return array|WP_Error
|
||||
* @override GenericController::get_datastore_data()
|
||||
*
|
||||
* @param array $query_args Query arguments.
|
||||
* @return mixed Results from the data store.
|
||||
*/
|
||||
public function get_items( $request ) {
|
||||
$query_args = $this->prepare_reports_query( $request );
|
||||
$coupons_query = new Query( $query_args );
|
||||
try {
|
||||
$report_data = $coupons_query->get_data();
|
||||
} catch ( ParameterException $e ) {
|
||||
return new \WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) );
|
||||
}
|
||||
|
||||
$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 );
|
||||
}
|
||||
|
||||
return $this->add_pagination_headers(
|
||||
$request,
|
||||
$out_data,
|
||||
(int) $report_data->total,
|
||||
(int) $report_data->page_no,
|
||||
(int) $report_data->pages
|
||||
);
|
||||
protected function get_datastore_data( $query_args = array() ) {
|
||||
$query = new GenericQuery( $query_args, 'coupons-stats' );
|
||||
return $query->get_data();
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare a report object for serialization.
|
||||
* Prepare a report data item for serialization.
|
||||
*
|
||||
* @param stdClass $report Report data.
|
||||
* @param mixed $report 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( $report, $request ) {
|
||||
$data = get_object_vars( $report );
|
||||
|
||||
$response = parent::prepare_item_for_response( $data, $request );
|
||||
$response = parent::prepare_item_for_response( $report, $request );
|
||||
|
||||
// Map to `object` for backwards compatibility.
|
||||
$report = (object) $report;
|
||||
/**
|
||||
* Filter a report returned from the API.
|
||||
*
|
||||
@@ -189,15 +168,6 @@ class Controller extends GenericStatsController {
|
||||
),
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
);
|
||||
$params['fields'] = array(
|
||||
'description' => __( 'Limit stats fields to the specified items.', 'woocommerce' ),
|
||||
'type' => 'array',
|
||||
'sanitize_callback' => 'wp_parse_slug_list',
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
'items' => array(
|
||||
'type' => 'string',
|
||||
),
|
||||
);
|
||||
|
||||
return $params;
|
||||
}
|
||||
|
||||
@@ -9,15 +9,19 @@ defined( 'ABSPATH' ) || exit;
|
||||
use Automattic\WooCommerce\Admin\API\Reports\Coupons\DataStore as CouponsDataStore;
|
||||
use Automattic\WooCommerce\Admin\API\Reports\DataStoreInterface;
|
||||
use Automattic\WooCommerce\Admin\API\Reports\TimeInterval;
|
||||
use Automattic\WooCommerce\Admin\API\Reports\SqlQuery;
|
||||
use Automattic\WooCommerce\Admin\API\Reports\StatsDataStoreTrait;
|
||||
|
||||
/**
|
||||
* API\Reports\Coupons\Stats\DataStore.
|
||||
*/
|
||||
class DataStore extends CouponsDataStore implements DataStoreInterface {
|
||||
use StatsDataStoreTrait;
|
||||
|
||||
/**
|
||||
* Mapping columns to data type to return correct response types.
|
||||
*
|
||||
* @override CouponsDataStore::$column_types
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $column_types = array(
|
||||
@@ -33,6 +37,8 @@ class DataStore extends CouponsDataStore implements DataStoreInterface {
|
||||
/**
|
||||
* SQL columns to select in the db query.
|
||||
*
|
||||
* @override CouponsDataStore::$report_columns
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $report_columns;
|
||||
@@ -40,6 +46,8 @@ class DataStore extends CouponsDataStore implements DataStoreInterface {
|
||||
/**
|
||||
* Data store context used to pass to filters.
|
||||
*
|
||||
* @override CouponsDataStore::$context
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $context = 'coupons_stats';
|
||||
@@ -47,12 +55,16 @@ class DataStore extends CouponsDataStore implements DataStoreInterface {
|
||||
/**
|
||||
* Cache identifier.
|
||||
*
|
||||
* @override CouponsDataStore::get_default_query_vars()
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $cache_key = 'coupons_stats';
|
||||
|
||||
/**
|
||||
* Assign report columns once full table name has been assigned.
|
||||
*
|
||||
* @override CouponsDataStore::assign_report_columns()
|
||||
*/
|
||||
protected function assign_report_columns() {
|
||||
$table_name = self::get_db_table_name();
|
||||
@@ -105,145 +117,114 @@ class DataStore extends CouponsDataStore implements DataStoreInterface {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the report data based on parameters supplied by the user.
|
||||
* Get the default query arguments to be used by get_data().
|
||||
* These defaults are only partially applied when used via REST API, as that has its own defaults.
|
||||
*
|
||||
* @since 3.5.0
|
||||
* @param array $query_args Query parameters.
|
||||
* @return stdClass|WP_Error Data.
|
||||
* @override CouponsDataStore::get_default_query_vars()
|
||||
*
|
||||
* @return array Query parameters.
|
||||
*/
|
||||
public function get_data( $query_args ) {
|
||||
public function get_default_query_vars() {
|
||||
$defaults = parent::get_default_query_vars();
|
||||
$defaults['coupons'] = array();
|
||||
$defaults['interval'] = 'week';
|
||||
|
||||
return $defaults;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the report data based on normalized parameters.
|
||||
* Will be called by `get_data` if there is no data in cache.
|
||||
*
|
||||
* @override CouponsDataStore::get_noncached_stats_data()
|
||||
*
|
||||
* @see get_data
|
||||
* @see get_noncached_stats_data
|
||||
* @param array $query_args Query parameters.
|
||||
* @param array $params Query limit parameters.
|
||||
* @param stdClass $data Reference to the data object to fill.
|
||||
* @param int $expected_interval_count Number of expected intervals.
|
||||
* @return stdClass|WP_Error Data object `{ totals: *, intervals: array, total: int, pages: int, page_no: int }`, or error.
|
||||
*/
|
||||
public function get_noncached_stats_data( $query_args, $params, &$data, $expected_interval_count ) {
|
||||
global $wpdb;
|
||||
|
||||
$table_name = self::get_db_table_name();
|
||||
|
||||
// These defaults are only partially applied when used via REST API, as that has its own defaults.
|
||||
$defaults = array(
|
||||
'per_page' => get_option( 'posts_per_page' ),
|
||||
'page' => 1,
|
||||
'order' => 'DESC',
|
||||
'orderby' => 'date',
|
||||
'before' => TimeInterval::default_before(),
|
||||
'after' => TimeInterval::default_after(),
|
||||
'fields' => '*',
|
||||
'interval' => 'week',
|
||||
'coupons' => array(),
|
||||
$this->initialize_queries();
|
||||
|
||||
$selections = $this->selected_columns( $query_args );
|
||||
$totals_query = array();
|
||||
$intervals_query = array();
|
||||
$limit_params = $this->get_limit_sql_params( $query_args );
|
||||
$this->update_sql_query_params( $query_args, $totals_query, $intervals_query );
|
||||
|
||||
$db_intervals = $wpdb->get_col(
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- cache ok, DB call ok, unprepared SQL ok.
|
||||
$this->interval_query->get_query_statement()
|
||||
);
|
||||
$query_args = wp_parse_args( $query_args, $defaults );
|
||||
$this->normalize_timezones( $query_args, $defaults );
|
||||
|
||||
/*
|
||||
* We need to get the cache key here because
|
||||
* parent::update_intervals_sql_params() modifies $query_args.
|
||||
*/
|
||||
$cache_key = $this->get_cache_key( $query_args );
|
||||
$data = $this->get_cached_data( $cache_key );
|
||||
$db_interval_count = count( $db_intervals );
|
||||
|
||||
if ( false === $data ) {
|
||||
$this->initialize_queries();
|
||||
$this->total_query->add_sql_clause( 'select', $selections );
|
||||
$totals = $wpdb->get_results(
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- cache ok, DB call ok, unprepared SQL ok.
|
||||
$this->total_query->get_query_statement(),
|
||||
ARRAY_A
|
||||
);
|
||||
|
||||
$data = (object) array(
|
||||
'data' => array(),
|
||||
'total' => 0,
|
||||
'pages' => 0,
|
||||
'page_no' => 0,
|
||||
);
|
||||
|
||||
$selections = $this->selected_columns( $query_args );
|
||||
$totals_query = array();
|
||||
$intervals_query = array();
|
||||
$limit_params = $this->get_limit_sql_params( $query_args );
|
||||
$this->update_sql_query_params( $query_args, $totals_query, $intervals_query );
|
||||
|
||||
$db_intervals = $wpdb->get_col(
|
||||
$this->interval_query->get_query_statement()
|
||||
); // WPCS: cache ok, DB call ok, unprepared SQL ok.
|
||||
|
||||
$db_interval_count = count( $db_intervals );
|
||||
$expected_interval_count = TimeInterval::intervals_between( $query_args['after'], $query_args['before'], $query_args['interval'] );
|
||||
$total_pages = (int) ceil( $expected_interval_count / $limit_params['per_page'] );
|
||||
if ( $query_args['page'] < 1 || $query_args['page'] > $total_pages ) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
$this->total_query->add_sql_clause( 'select', $selections );
|
||||
$totals = $wpdb->get_results(
|
||||
$this->total_query->get_query_statement(),
|
||||
ARRAY_A
|
||||
); // WPCS: cache ok, DB call ok, unprepared SQL ok.
|
||||
|
||||
if ( null === $totals ) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
// @todo remove these assignements when refactoring segmenter classes to use query objects.
|
||||
$totals_query = array(
|
||||
'from_clause' => $this->total_query->get_sql_clause( 'join' ),
|
||||
'where_time_clause' => $this->total_query->get_sql_clause( 'where_time' ),
|
||||
'where_clause' => $this->total_query->get_sql_clause( 'where' ),
|
||||
);
|
||||
$intervals_query = array(
|
||||
'select_clause' => $this->get_sql_clause( 'select' ),
|
||||
'from_clause' => $this->interval_query->get_sql_clause( 'join' ),
|
||||
'where_time_clause' => $this->interval_query->get_sql_clause( 'where_time' ),
|
||||
'where_clause' => $this->interval_query->get_sql_clause( 'where' ),
|
||||
'limit' => $this->get_sql_clause( 'limit' ),
|
||||
);
|
||||
$segmenter = new Segmenter( $query_args, $this->report_columns );
|
||||
$totals[0]['segments'] = $segmenter->get_totals_segments( $totals_query, $table_name );
|
||||
$totals = (object) $this->cast_numbers( $totals[0] );
|
||||
|
||||
// Intervals.
|
||||
$this->update_intervals_sql_params( $query_args, $db_interval_count, $expected_interval_count, $table_name );
|
||||
$this->interval_query->add_sql_clause( 'select', ", MAX({$table_name}.date_created) AS datetime_anchor" );
|
||||
|
||||
if ( '' !== $selections ) {
|
||||
$this->interval_query->add_sql_clause( 'select', ', ' . $selections );
|
||||
}
|
||||
|
||||
$intervals = $wpdb->get_results(
|
||||
$this->interval_query->get_query_statement(),
|
||||
ARRAY_A
|
||||
); // WPCS: cache ok, DB call ok, unprepared SQL ok.
|
||||
|
||||
if ( null === $intervals ) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
$data = (object) array(
|
||||
'totals' => $totals,
|
||||
'intervals' => $intervals,
|
||||
'total' => $expected_interval_count,
|
||||
'pages' => $total_pages,
|
||||
'page_no' => (int) $query_args['page'],
|
||||
);
|
||||
|
||||
if ( TimeInterval::intervals_missing( $expected_interval_count, $db_interval_count, $limit_params['per_page'], $query_args['page'], $query_args['order'], $query_args['orderby'], count( $intervals ) ) ) {
|
||||
$this->fill_in_missing_intervals( $db_intervals, $query_args['adj_after'], $query_args['adj_before'], $query_args['interval'], $data );
|
||||
$this->sort_intervals( $data, $query_args['orderby'], $query_args['order'] );
|
||||
$this->remove_extra_records( $data, $query_args['page'], $limit_params['per_page'], $db_interval_count, $expected_interval_count, $query_args['orderby'], $query_args['order'] );
|
||||
} else {
|
||||
$this->update_interval_boundary_dates( $query_args['after'], $query_args['before'], $query_args['interval'], $data->intervals );
|
||||
}
|
||||
$segmenter->add_intervals_segments( $data, $intervals_query, $table_name );
|
||||
$this->create_interval_subtotals( $data->intervals );
|
||||
|
||||
$this->set_cached_data( $cache_key, $data );
|
||||
if ( null === $totals ) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
// phpcs:ignore Generic.Commenting.Todo.TaskFound
|
||||
// @todo remove these assignements when refactoring segmenter classes to use query objects.
|
||||
$totals_query = array(
|
||||
'from_clause' => $this->total_query->get_sql_clause( 'join' ),
|
||||
'where_time_clause' => $this->total_query->get_sql_clause( 'where_time' ),
|
||||
'where_clause' => $this->total_query->get_sql_clause( 'where' ),
|
||||
);
|
||||
$intervals_query = array(
|
||||
'select_clause' => $this->get_sql_clause( 'select' ),
|
||||
'from_clause' => $this->interval_query->get_sql_clause( 'join' ),
|
||||
'where_time_clause' => $this->interval_query->get_sql_clause( 'where_time' ),
|
||||
'where_clause' => $this->interval_query->get_sql_clause( 'where' ),
|
||||
'limit' => $this->get_sql_clause( 'limit' ),
|
||||
);
|
||||
$segmenter = new Segmenter( $query_args, $this->report_columns );
|
||||
$totals[0]['segments'] = $segmenter->get_totals_segments( $totals_query, $table_name );
|
||||
$totals = (object) $this->cast_numbers( $totals[0] );
|
||||
|
||||
// Intervals.
|
||||
$this->update_intervals_sql_params( $query_args, $db_interval_count, $expected_interval_count, $table_name );
|
||||
$this->interval_query->add_sql_clause( 'select', ", MAX({$table_name}.date_created) AS datetime_anchor" );
|
||||
|
||||
if ( '' !== $selections ) {
|
||||
$this->interval_query->add_sql_clause( 'select', ', ' . $selections );
|
||||
}
|
||||
|
||||
$intervals = $wpdb->get_results(
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- cache ok, DB call ok, unprepared SQL ok.
|
||||
$this->interval_query->get_query_statement(),
|
||||
ARRAY_A
|
||||
);
|
||||
|
||||
if ( null === $intervals ) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
$data->totals = $totals;
|
||||
$data->intervals = $intervals;
|
||||
|
||||
if ( TimeInterval::intervals_missing( $expected_interval_count, $db_interval_count, $limit_params['per_page'], $query_args['page'], $query_args['order'], $query_args['orderby'], count( $intervals ) ) ) {
|
||||
$this->fill_in_missing_intervals( $db_intervals, $query_args['adj_after'], $query_args['adj_before'], $query_args['interval'], $data );
|
||||
$this->sort_intervals( $data, $query_args['orderby'], $query_args['order'] );
|
||||
$this->remove_extra_records( $data, $query_args['page'], $limit_params['per_page'], $db_interval_count, $expected_interval_count, $query_args['orderby'], $query_args['order'] );
|
||||
} else {
|
||||
$this->update_interval_boundary_dates( $query_args['after'], $query_args['before'], $query_args['interval'], $data->intervals );
|
||||
}
|
||||
$segmenter->add_intervals_segments( $data, $intervals_query, $table_name );
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize query objects.
|
||||
*/
|
||||
protected function initialize_queries() {
|
||||
$this->clear_all_clauses();
|
||||
unset( $this->subquery );
|
||||
$this->total_query = new SqlQuery( $this->context . '_total' );
|
||||
$this->total_query->add_sql_clause( 'from', self::get_db_table_name() );
|
||||
|
||||
$this->interval_query = new SqlQuery( $this->context . '_interval' );
|
||||
$this->interval_query->add_sql_clause( 'from', self::get_db_table_name() );
|
||||
$this->interval_query->add_sql_clause( 'group_by', 'time_interval' );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,12 +21,16 @@ use Automattic\WooCommerce\Admin\API\Reports\Query as ReportsQuery;
|
||||
|
||||
/**
|
||||
* API\Reports\Coupons\Stats\Query
|
||||
*
|
||||
* @deprecated 9.3.0 Coupons\Stats\Query class is deprecated, please use GenericQuery or \WC_Object_Query instead.
|
||||
*/
|
||||
class Query extends ReportsQuery {
|
||||
|
||||
/**
|
||||
* Valid fields for Products report.
|
||||
*
|
||||
* @deprecated 9.3.0 Coupons\Stats\Query class is deprecated, please use GenericQuery or \WC_Object_Query instead.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_default_query_vars() {
|
||||
@@ -36,6 +40,8 @@ class Query extends ReportsQuery {
|
||||
/**
|
||||
* Get product data based on the current query vars.
|
||||
*
|
||||
* @deprecated 9.3.0 Coupons\Stats\Query class is deprecated, please use GenericQuery or \WC_Object_Query instead.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_data() {
|
||||
|
||||
@@ -33,6 +33,19 @@ class Controller extends GenericController implements ExportableInterface {
|
||||
*/
|
||||
protected $rest_base = 'reports/customers';
|
||||
|
||||
/**
|
||||
* Get data from Query.
|
||||
*
|
||||
* @override GenericController::get_datastore_data()
|
||||
*
|
||||
* @param array $query_args Query arguments.
|
||||
* @return mixed Results from the data store.
|
||||
*/
|
||||
protected function get_datastore_data( $query_args = array() ) {
|
||||
$query = new Query( $query_args );
|
||||
return $query->get_data();
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps query arguments from the REST request.
|
||||
*
|
||||
@@ -84,34 +97,6 @@ class Controller extends GenericController implements ExportableInterface {
|
||||
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 Query( $query_args );
|
||||
$report_data = $customers_query->get_data();
|
||||
|
||||
$data = array();
|
||||
|
||||
foreach ( $report_data->data as $customer_data ) {
|
||||
$item = $this->prepare_item_for_response( $customer_data, $request );
|
||||
$data[] = $this->prepare_response_for_collection( $item );
|
||||
}
|
||||
|
||||
return $this->add_pagination_headers(
|
||||
$request,
|
||||
$data,
|
||||
(int) $report_data->total,
|
||||
(int) $report_data->page_no,
|
||||
(int) $report_data->pages
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get one report.
|
||||
*
|
||||
@@ -139,11 +124,11 @@ class Controller extends GenericController implements ExportableInterface {
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare a report object for serialization.
|
||||
* Prepare a report data item for serialization.
|
||||
*
|
||||
* @param array $report Report data.
|
||||
* @param WP_REST_Request $request Request object.
|
||||
* @return WP_REST_Response
|
||||
* @param array $report 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( $report, $request ) {
|
||||
$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
|
||||
|
||||
@@ -22,6 +22,8 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||
/**
|
||||
* Table used to get the data.
|
||||
*
|
||||
* @override ReportsDataStore::$table_name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected static $table_name = 'wc_customer_lookup';
|
||||
@@ -29,6 +31,8 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||
/**
|
||||
* Cache identifier.
|
||||
*
|
||||
* @override ReportsDataStore::$cache_key
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $cache_key = 'customers';
|
||||
@@ -36,6 +40,8 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||
/**
|
||||
* Mapping columns to data type to return correct response types.
|
||||
*
|
||||
* @override ReportsDataStore::$column_types
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $column_types = array(
|
||||
@@ -49,12 +55,16 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||
/**
|
||||
* Data store context used to pass to filters.
|
||||
*
|
||||
* @override ReportsDataStore::$context
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $context = 'customers';
|
||||
|
||||
/**
|
||||
* Assign report columns once full table name has been assigned.
|
||||
*
|
||||
* @override ReportsDataStore::assign_report_columns()
|
||||
*/
|
||||
protected function assign_report_columns() {
|
||||
global $wpdb;
|
||||
@@ -168,6 +178,8 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||
/**
|
||||
* Maps ordering specified by the user to columns in the database/fields in the data.
|
||||
*
|
||||
* @override ReportsDataStore::normalize_order_by()
|
||||
*
|
||||
* @param string $order_by Sorting criterion.
|
||||
* @return string
|
||||
*/
|
||||
@@ -182,6 +194,8 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||
/**
|
||||
* Fills WHERE clause of SQL request with date-related constraints.
|
||||
*
|
||||
* @override ReportsDataStore::add_time_period_sql_params()
|
||||
*
|
||||
* @param array $query_args Parameters supplied by the user.
|
||||
* @param string $table_name Name of the db table relevant for the date constraint.
|
||||
*/
|
||||
@@ -409,89 +423,20 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the report data based on parameters supplied by the user.
|
||||
* Get the default query arguments to be used by get_data().
|
||||
* These defaults are only partially applied when used via REST API, as that has its own defaults.
|
||||
*
|
||||
* @param array $query_args Query parameters.
|
||||
* @return stdClass|WP_Error Data.
|
||||
* @override ReportsDataStore::get_default_query_vars()
|
||||
*
|
||||
* @return array Query parameters.
|
||||
*/
|
||||
public function get_data( $query_args ) {
|
||||
global $wpdb;
|
||||
public function get_default_query_vars() {
|
||||
$defaults = parent::get_default_query_vars();
|
||||
$defaults['orderby'] = 'date_registered';
|
||||
$defaults['order_before'] = TimeInterval::default_before();
|
||||
$defaults['order_after'] = TimeInterval::default_after();
|
||||
|
||||
$customers_table_name = self::get_db_table_name();
|
||||
$order_stats_table_name = $wpdb->prefix . 'wc_order_stats';
|
||||
|
||||
// These defaults are only partially applied when used via REST API, as that has its own defaults.
|
||||
$defaults = array(
|
||||
'per_page' => get_option( 'posts_per_page' ),
|
||||
'page' => 1,
|
||||
'order' => 'DESC',
|
||||
'orderby' => 'date_registered',
|
||||
'order_before' => TimeInterval::default_before(),
|
||||
'order_after' => TimeInterval::default_after(),
|
||||
'fields' => '*',
|
||||
);
|
||||
$query_args = wp_parse_args( $query_args, $defaults );
|
||||
$this->normalize_timezones( $query_args, $defaults );
|
||||
|
||||
/*
|
||||
* We need to get the cache key here because
|
||||
* parent::update_intervals_sql_params() modifies $query_args.
|
||||
*/
|
||||
$cache_key = $this->get_cache_key( $query_args );
|
||||
$data = $this->get_cached_data( $cache_key );
|
||||
|
||||
if ( false === $data ) {
|
||||
$this->initialize_queries();
|
||||
|
||||
$data = (object) array(
|
||||
'data' => array(),
|
||||
'total' => 0,
|
||||
'pages' => 0,
|
||||
'page_no' => 0,
|
||||
);
|
||||
|
||||
$selections = $this->selected_columns( $query_args );
|
||||
$sql_query_params = $this->add_sql_query_params( $query_args );
|
||||
$count_query = "SELECT COUNT(*) FROM (
|
||||
{$this->subquery->get_query_statement()}
|
||||
) as tt
|
||||
";
|
||||
$db_records_count = (int) $wpdb->get_var(
|
||||
$count_query // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
);
|
||||
|
||||
$params = $this->get_limit_params( $query_args );
|
||||
$total_pages = (int) ceil( $db_records_count / $params['per_page'] );
|
||||
if ( $query_args['page'] < 1 || $query_args['page'] > $total_pages ) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
$this->subquery->clear_sql_clause( 'select' );
|
||||
$this->subquery->add_sql_clause( 'select', $selections );
|
||||
$this->subquery->add_sql_clause( 'order_by', $this->get_sql_clause( 'order_by' ) );
|
||||
$this->subquery->add_sql_clause( 'limit', $this->get_sql_clause( 'limit' ) );
|
||||
|
||||
$customer_data = $wpdb->get_results(
|
||||
$this->subquery->get_query_statement(), // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
ARRAY_A
|
||||
);
|
||||
|
||||
if ( null === $customer_data ) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
$customer_data = array_map( array( $this, 'cast_numbers' ), $customer_data );
|
||||
$data = (object) array(
|
||||
'data' => $customer_data,
|
||||
'total' => $db_records_count,
|
||||
'pages' => $total_pages,
|
||||
'page_no' => (int) $query_args['page'],
|
||||
);
|
||||
|
||||
$this->set_cached_data( $cache_key, $data );
|
||||
}
|
||||
|
||||
return $data;
|
||||
return $defaults;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -533,6 +478,69 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the report data based on normalized parameters.
|
||||
* Will be called by `get_data` if there is no data in cache.
|
||||
*
|
||||
* @override ReportsDataStore::get_noncached_data()
|
||||
*
|
||||
* @see get_data
|
||||
* @param array $query_args Query parameters.
|
||||
* @return stdClass|WP_Error Data object `{ totals: *, intervals: array, total: int, pages: int, page_no: int }`, or error.
|
||||
*/
|
||||
public function get_noncached_data( $query_args ) {
|
||||
global $wpdb;
|
||||
|
||||
$this->initialize_queries();
|
||||
|
||||
$data = (object) array(
|
||||
'data' => array(),
|
||||
'total' => 0,
|
||||
'pages' => 0,
|
||||
'page_no' => 0,
|
||||
);
|
||||
|
||||
$selections = $this->selected_columns( $query_args );
|
||||
$sql_query_params = $this->add_sql_query_params( $query_args );
|
||||
$count_query = "SELECT COUNT(*) FROM (
|
||||
{$this->subquery->get_query_statement()}
|
||||
) as tt
|
||||
";
|
||||
$db_records_count = (int) $wpdb->get_var(
|
||||
$count_query // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
);
|
||||
|
||||
$params = $this->get_limit_params( $query_args );
|
||||
$total_pages = (int) ceil( $db_records_count / $params['per_page'] );
|
||||
if ( $query_args['page'] < 1 || $query_args['page'] > $total_pages ) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
$this->subquery->clear_sql_clause( 'select' );
|
||||
$this->subquery->add_sql_clause( 'select', $selections );
|
||||
$this->subquery->add_sql_clause( 'order_by', $this->get_sql_clause( 'order_by' ) );
|
||||
$this->subquery->add_sql_clause( 'limit', $this->get_sql_clause( 'limit' ) );
|
||||
|
||||
$customer_data = $wpdb->get_results(
|
||||
$this->subquery->get_query_statement(), // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
ARRAY_A
|
||||
);
|
||||
|
||||
if ( null === $customer_data ) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
$customer_data = array_map( array( $this, 'cast_numbers' ), $customer_data );
|
||||
$data = (object) array(
|
||||
'data' => $customer_data,
|
||||
'total' => $db_records_count,
|
||||
'pages' => $total_pages,
|
||||
'page_no' => (int) $query_args['page'],
|
||||
);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get or create a customer from a given order.
|
||||
*
|
||||
|
||||
@@ -16,14 +16,23 @@
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\API\Reports\Customers;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
use Automattic\WooCommerce\Admin\API\Reports\GenericQuery;
|
||||
|
||||
use Automattic\WooCommerce\Admin\API\Reports\Query as ReportsQuery;
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* API\Reports\Customers\Query
|
||||
*/
|
||||
class Query extends ReportsQuery {
|
||||
class Query extends GenericQuery {
|
||||
|
||||
/**
|
||||
* Specific query name.
|
||||
* Will be used to load the `report-{name}` data store,
|
||||
* and to call `woocommerce_analytics_{snake_case(name)}_*` filters.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $name = 'customers';
|
||||
|
||||
/**
|
||||
* Valid fields for Customers report.
|
||||
@@ -39,17 +48,4 @@ class Query extends ReportsQuery {
|
||||
'fields' => '*',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get product data based on the current query vars.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_data() {
|
||||
$args = apply_filters( 'woocommerce_analytics_customers_query_args', $this->get_query_vars() );
|
||||
|
||||
$data_store = \WC_Data_Store::load( 'report-customers' );
|
||||
$results = $data_store->get_data( $args );
|
||||
return apply_filters( 'woocommerce_analytics_customers_select_query', $results, $args );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\API\Reports\Customers\Stats;
|
||||
|
||||
use Automattic\WooCommerce\Admin\API\Reports\Customers\Query;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
use Automattic\WooCommerce\Admin\API\Reports\TimeInterval;
|
||||
@@ -83,7 +85,7 @@ class Controller extends \WC_REST_Reports_Controller {
|
||||
*/
|
||||
public function get_items( $request ) {
|
||||
$query_args = $this->prepare_reports_query( $request );
|
||||
$customers_query = new Query( $query_args );
|
||||
$customers_query = new Query( $query_args, 'customers-stats' );
|
||||
$report_data = $customers_query->get_data();
|
||||
$out_data = array(
|
||||
'totals' => $report_data,
|
||||
@@ -93,11 +95,11 @@ class Controller extends \WC_REST_Reports_Controller {
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare a report object for serialization.
|
||||
* Prepare a report data item for serialization.
|
||||
*
|
||||
* @param Array $report Report data.
|
||||
* @param WP_REST_Request $request Request object.
|
||||
* @return WP_REST_Response
|
||||
* @param array $report 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( $report, $request ) {
|
||||
$data = $report;
|
||||
|
||||
@@ -8,6 +8,7 @@ namespace Automattic\WooCommerce\Admin\API\Reports\Customers\Stats;
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
use Automattic\WooCommerce\Admin\API\Reports\Customers\DataStore as CustomersDataStore;
|
||||
use Automattic\WooCommerce\Admin\API\Reports\DataStore as ReportsDataStore;
|
||||
use Automattic\WooCommerce\Admin\API\Reports\DataStoreInterface;
|
||||
|
||||
/**
|
||||
@@ -17,6 +18,8 @@ class DataStore extends CustomersDataStore implements DataStoreInterface {
|
||||
/**
|
||||
* Mapping columns to data type to return correct response types.
|
||||
*
|
||||
* @override CustomersDataStore::$column_types
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $column_types = array(
|
||||
@@ -29,6 +32,8 @@ class DataStore extends CustomersDataStore implements DataStoreInterface {
|
||||
/**
|
||||
* Cache identifier.
|
||||
*
|
||||
* @override CustomersDataStore::$cache_key
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $cache_key = 'customers_stats';
|
||||
@@ -36,12 +41,16 @@ class DataStore extends CustomersDataStore implements DataStoreInterface {
|
||||
/**
|
||||
* Data store context used to pass to filters.
|
||||
*
|
||||
* @override CustomersDataStore::$context
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $context = 'customers_stats';
|
||||
|
||||
/**
|
||||
* Assign report columns once full table name has been assigned.
|
||||
*
|
||||
* @override CustomersDataStore::assign_report_columns()
|
||||
*/
|
||||
protected function assign_report_columns() {
|
||||
$this->report_columns = array(
|
||||
@@ -53,76 +62,70 @@ class DataStore extends CustomersDataStore implements DataStoreInterface {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the report data based on parameters supplied by the user.
|
||||
* Get the default query arguments to be used by get_data().
|
||||
* These defaults are only partially applied when used via REST API, as that has its own defaults.
|
||||
*
|
||||
* @param array $query_args Query parameters.
|
||||
* @return stdClass|WP_Error Data.
|
||||
* @override CustomersDataStore::get_default_query_vars()
|
||||
*
|
||||
* @return array Query parameters.
|
||||
*/
|
||||
public function get_data( $query_args ) {
|
||||
public function get_default_query_vars() {
|
||||
$defaults = ReportsDataStore::get_default_query_vars();
|
||||
$defaults['orderby'] = 'date_registered';
|
||||
// Do not set `order_before` and `order_after` here, like in the parent class.
|
||||
return $defaults;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the report data based on normalized parameters.
|
||||
* Will be called by `get_data` if there is no data in cache.
|
||||
*
|
||||
* @override CustomersDataStore::get_noncached_data()
|
||||
*
|
||||
* @see get_data
|
||||
* @param array $query_args Query parameters.
|
||||
* @return stdClass|WP_Error Data object `{ totals: *, intervals: array, total: int, pages: int, page_no: int }`, or error.
|
||||
*/
|
||||
public function get_noncached_data( $query_args ) {
|
||||
global $wpdb;
|
||||
$this->initialize_queries();
|
||||
|
||||
$customers_table_name = self::get_db_table_name();
|
||||
|
||||
// These defaults are only partially applied when used via REST API, as that has its own defaults.
|
||||
$defaults = array(
|
||||
'per_page' => get_option( 'posts_per_page' ),
|
||||
'page' => 1,
|
||||
'order' => 'DESC',
|
||||
'orderby' => 'date_registered',
|
||||
'fields' => '*',
|
||||
$data = (object) array(
|
||||
'customers_count' => 0,
|
||||
'avg_orders_count' => 0,
|
||||
'avg_total_spend' => 0.0,
|
||||
'avg_avg_order_value' => 0.0,
|
||||
);
|
||||
$query_args = wp_parse_args( $query_args, $defaults );
|
||||
$this->normalize_timezones( $query_args, $defaults );
|
||||
|
||||
/*
|
||||
* We need to get the cache key here because
|
||||
* parent::update_intervals_sql_params() modifies $query_args.
|
||||
*/
|
||||
$cache_key = $this->get_cache_key( $query_args );
|
||||
$data = $this->get_cached_data( $cache_key );
|
||||
$selections = $this->selected_columns( $query_args );
|
||||
$this->add_sql_query_params( $query_args );
|
||||
// Clear SQL clauses set for parent class queries that are different here.
|
||||
$this->subquery->clear_sql_clause( 'select' );
|
||||
$this->subquery->add_sql_clause( 'select', 'SUM( total_sales ) AS total_spend,' );
|
||||
$this->subquery->add_sql_clause(
|
||||
'select',
|
||||
'SUM( CASE WHEN parent_id = 0 THEN 1 END ) as orders_count,'
|
||||
);
|
||||
$this->subquery->add_sql_clause(
|
||||
'select',
|
||||
'CASE WHEN SUM( CASE WHEN parent_id = 0 THEN 1 ELSE 0 END ) = 0 THEN NULL ELSE SUM( total_sales ) / SUM( CASE WHEN parent_id = 0 THEN 1 ELSE 0 END ) END AS avg_order_value'
|
||||
);
|
||||
|
||||
if ( false === $data ) {
|
||||
$this->initialize_queries();
|
||||
$this->clear_sql_clause( array( 'order_by', 'limit' ) );
|
||||
$this->add_sql_clause( 'select', $selections );
|
||||
$this->add_sql_clause( 'from', "({$this->subquery->get_query_statement()}) AS tt" );
|
||||
|
||||
$data = (object) array(
|
||||
'customers_count' => 0,
|
||||
'avg_orders_count' => 0,
|
||||
'avg_total_spend' => 0.0,
|
||||
'avg_avg_order_value' => 0.0,
|
||||
);
|
||||
$report_data = $wpdb->get_results(
|
||||
$this->get_query_statement(), // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
ARRAY_A
|
||||
);
|
||||
|
||||
$selections = $this->selected_columns( $query_args );
|
||||
$this->add_sql_query_params( $query_args );
|
||||
// Clear SQL clauses set for parent class queries that are different here.
|
||||
$this->subquery->clear_sql_clause( 'select' );
|
||||
$this->subquery->add_sql_clause( 'select', 'SUM( total_sales ) AS total_spend,' );
|
||||
$this->subquery->add_sql_clause(
|
||||
'select',
|
||||
'SUM( CASE WHEN parent_id = 0 THEN 1 END ) as orders_count,'
|
||||
);
|
||||
$this->subquery->add_sql_clause(
|
||||
'select',
|
||||
'CASE WHEN SUM( CASE WHEN parent_id = 0 THEN 1 ELSE 0 END ) = 0 THEN NULL ELSE SUM( total_sales ) / SUM( CASE WHEN parent_id = 0 THEN 1 ELSE 0 END ) END AS avg_order_value'
|
||||
);
|
||||
|
||||
$this->clear_sql_clause( array( 'order_by', 'limit' ) );
|
||||
$this->add_sql_clause( 'select', $selections );
|
||||
$this->add_sql_clause( 'from', "({$this->subquery->get_query_statement()}) AS tt" );
|
||||
|
||||
$report_data = $wpdb->get_results(
|
||||
$this->get_query_statement(), // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
ARRAY_A
|
||||
);
|
||||
|
||||
if ( null === $report_data ) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
$data = (object) $this->cast_numbers( $report_data[0] );
|
||||
|
||||
$this->set_cached_data( $cache_key, $data );
|
||||
if ( null === $report_data ) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
$data = (object) $this->cast_numbers( $report_data[0] );
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,12 +22,16 @@ use Automattic\WooCommerce\Admin\API\Reports\Query as ReportsQuery;
|
||||
|
||||
/**
|
||||
* API\Reports\Customers\Stats\Query
|
||||
*
|
||||
* @deprecated 9.3.0 Customers\Stats\Query class is deprecated, please use Reports\Customers\Query with a custom name, GenericQuery or \WC_Object_Query instead.
|
||||
*/
|
||||
class Query extends ReportsQuery {
|
||||
|
||||
/**
|
||||
* Valid fields for Customers report.
|
||||
*
|
||||
* @deprecated 9.3.0 Customers\Stats\Query class is deprecated, please use Reports\Customers\Query with a custom name, GenericQuery or \WC_Object_Query instead.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_default_query_vars() {
|
||||
@@ -43,6 +47,8 @@ class Query extends ReportsQuery {
|
||||
/**
|
||||
* Get product data based on the current query vars.
|
||||
*
|
||||
* @deprecated 9.3.0 Customers\Stats\Query class is deprecated, please use Reports\Customers\Query with a custom name, GenericQuery or \WC_Object_Query instead.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_data() {
|
||||
|
||||
@@ -9,12 +9,59 @@ if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
use Automattic\WooCommerce\Admin\API\Reports\DataStoreInterface;
|
||||
use Automattic\WooCommerce\Admin\API\Reports\TimeInterval;
|
||||
|
||||
/**
|
||||
* Admin\API\Reports\DataStore: Common parent for custom report data stores.
|
||||
* Common parent for custom report data stores.
|
||||
*
|
||||
* We use Report DataStores to separate DB data retrieval logic from the REST API controllers.
|
||||
*
|
||||
* Handles caching, data normalization, intervals-related methods, and other common functionality.
|
||||
* So, in your custom report DataStore class that extends this class
|
||||
* you can focus on specifics by overriding the `get_noncached_data` method.
|
||||
*
|
||||
* Minimalistic example:
|
||||
* <pre><code class="language-php">class MyDataStore extends DataStore implements DataStoreInterface {
|
||||
* /** Cache identifier, used by the `DataStore` class to handle caching for you. */
|
||||
* protected $cache_key = 'my_thing';
|
||||
* /** Data store context used to pass to filters. */
|
||||
* protected $context = 'my_thing';
|
||||
* /** Table used to get the data. */
|
||||
* protected static $table_name = 'my_table';
|
||||
* /**
|
||||
* * Method that overrides the `DataStore::get_noncached_data()` to return the report data.
|
||||
* * Will be called by `get_data` if there is no data in cache.
|
||||
* */
|
||||
* public function get_noncached_data( $query ) {
|
||||
* // Do your magic.
|
||||
*
|
||||
* // Then return your data in conforming object structure.
|
||||
* return (object) array(
|
||||
* 'data' => $product_data,
|
||||
* 'total' => 1,
|
||||
* 'page_no' => 1,
|
||||
* 'pages' => 1,
|
||||
* );
|
||||
* }
|
||||
* }
|
||||
* </code></pre>
|
||||
*
|
||||
* Please use the `woocommerce_data_stores` filter to add your custom data store to the list of available ones.
|
||||
* Then, your store could be accessed by Controller classes ({@see GenericController::get_datastore_data() GenericController::get_datastore_data()})
|
||||
* or using {@link \WC_Data_Store::load() \WC_Data_Store::load()}.
|
||||
*
|
||||
* We recommend registering using the REST base name of your Controller as the key, e.g.:
|
||||
* <pre><code class="language-php">add_filter( 'woocommerce_data_stores', function( $stores ) {
|
||||
* $stores['reports/my-thing'] = 'MyExtension\Admin\Analytics\Rest_API\MyDataStore';
|
||||
* } );
|
||||
* </code></pre>
|
||||
* This way, `GenericController` will pick it up automatically.
|
||||
*
|
||||
* Note that this class is NOT {@link https://developer.woocommerce.com/docs/how-to-manage-woocommerce-data-stores/ a CRUD data store}.
|
||||
* It does not implement the {@see WC_Object_Data_Store_Interface WC_Object_Data_Store_Interface} nor extend WC_Data & WC_Data_Store_WP classes.
|
||||
*/
|
||||
class DataStore extends SqlQuery {
|
||||
class DataStore extends SqlQuery implements DataStoreInterface {
|
||||
|
||||
/**
|
||||
* Cache group for the reports.
|
||||
@@ -90,6 +137,8 @@ class DataStore extends SqlQuery {
|
||||
/**
|
||||
* Data store context used to pass to filters.
|
||||
*
|
||||
* @override SqlQuery
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $context = 'reports';
|
||||
@@ -138,6 +187,8 @@ class DataStore extends SqlQuery {
|
||||
|
||||
/**
|
||||
* Class constructor.
|
||||
*
|
||||
* @override SqlQuery::__construct()
|
||||
*/
|
||||
public function __construct() {
|
||||
self::set_db_table_name();
|
||||
@@ -160,6 +211,54 @@ class DataStore extends SqlQuery {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the data based on args.
|
||||
*
|
||||
* Returns the report data based on parameters supplied by the user.
|
||||
* Fetches it from cache or returns `get_noncached_data` result.
|
||||
*
|
||||
* @param array $query_args Query parameters.
|
||||
* @return stdClass|WP_Error
|
||||
*/
|
||||
public function get_data( $query_args ) {
|
||||
$defaults = $this->get_default_query_vars();
|
||||
$query_args = wp_parse_args( $query_args, $defaults );
|
||||
$this->normalize_timezones( $query_args, $defaults );
|
||||
|
||||
/*
|
||||
* We need to get the cache key here because
|
||||
* parent::update_intervals_sql_params() modifies $query_args.
|
||||
*/
|
||||
$cache_key = $this->get_cache_key( $query_args );
|
||||
$data = $this->get_cached_data( $cache_key );
|
||||
|
||||
if ( false === $data ) {
|
||||
$data = $this->get_noncached_data( $query_args );
|
||||
$this->set_cached_data( $cache_key, $data );
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default query arguments to be used by get_data().
|
||||
* These defaults are only partially applied when used via REST API, as that has its own defaults.
|
||||
*
|
||||
* @return array Query parameters.
|
||||
*/
|
||||
public function get_default_query_vars() {
|
||||
return array(
|
||||
'per_page' => get_option( 'posts_per_page' ),
|
||||
'page' => 1,
|
||||
'order' => 'DESC',
|
||||
'orderby' => 'date',
|
||||
'before' => TimeInterval::default_before(),
|
||||
'after' => TimeInterval::default_after(),
|
||||
'fields' => '*',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get table name from database class.
|
||||
*/
|
||||
@@ -168,6 +267,19 @@ class DataStore extends SqlQuery {
|
||||
return isset( $wpdb->{static::$table_name} ) ? $wpdb->{static::$table_name} : $wpdb->prefix . static::$table_name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the report data based on normalized parameters.
|
||||
* Will be called by `get_data` if there is no data in cache.
|
||||
*
|
||||
* @see get_data
|
||||
* @param array $query_args Query parameters.
|
||||
* @return stdClass|WP_Error Data object `{ totals: *, intervals: array, total: int, pages: int, page_no: int }`, or error.
|
||||
*/
|
||||
public function get_noncached_data( $query_args ) {
|
||||
/* translators: %s: Method name */
|
||||
return new \WP_Error( 'invalid-method', sprintf( __( "Method '%s' not implemented. Must be overridden in subclass.", 'woocommerce' ), __METHOD__ ), array( 'status' => 405 ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Set table name from database class.
|
||||
*/
|
||||
|
||||
@@ -9,16 +9,20 @@ namespace Automattic\WooCommerce\Admin\API\Reports\Downloads;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
use Automattic\WooCommerce\Admin\API\Reports\Controller as ReportsController;
|
||||
use Automattic\WooCommerce\Admin\API\Reports\ExportableInterface;
|
||||
use Automattic\WooCommerce\Admin\API\Reports\GenericController;
|
||||
use Automattic\WooCommerce\Admin\API\Reports\GenericQuery;
|
||||
use Automattic\WooCommerce\Admin\API\Reports\OrderAwareControllerTrait;
|
||||
|
||||
/**
|
||||
* REST API Reports downloads controller class.
|
||||
*
|
||||
* @internal
|
||||
* @extends Automattic\WooCommerce\Admin\API\Reports\Controller
|
||||
* @extends Automattic\WooCommerce\Admin\API\Reports\GenericController
|
||||
*/
|
||||
class Controller extends ReportsController implements ExportableInterface {
|
||||
class Controller extends GenericController implements ExportableInterface {
|
||||
|
||||
use OrderAwareControllerTrait;
|
||||
|
||||
/**
|
||||
* Route base.
|
||||
@@ -28,67 +32,40 @@ class Controller extends ReportsController implements ExportableInterface {
|
||||
protected $rest_base = 'reports/downloads';
|
||||
|
||||
/**
|
||||
* Get items.
|
||||
* Get data from `'downloads'` Query.
|
||||
*
|
||||
* @param WP_REST_Request $request Request data.
|
||||
* @return array|WP_Error
|
||||
* @override GenericController::get_datastore_data()
|
||||
*
|
||||
* @param array $query_args Query arguments.
|
||||
* @return mixed Results from the data store.
|
||||
*/
|
||||
public function get_items( $request ) {
|
||||
$args = array();
|
||||
$registered = array_keys( $this->get_collection_params() );
|
||||
foreach ( $registered as $param_name ) {
|
||||
if ( isset( $request[ $param_name ] ) ) {
|
||||
$args[ $param_name ] = $request[ $param_name ];
|
||||
}
|
||||
}
|
||||
|
||||
$reports = new Query( $args );
|
||||
$downloads_data = $reports->get_data();
|
||||
|
||||
$data = array();
|
||||
|
||||
foreach ( $downloads_data->data as $download_data ) {
|
||||
$item = $this->prepare_item_for_response( $download_data, $request );
|
||||
$data[] = $this->prepare_response_for_collection( $item );
|
||||
}
|
||||
|
||||
return $this->add_pagination_headers(
|
||||
$request,
|
||||
$data,
|
||||
(int) $downloads_data->total,
|
||||
(int) $downloads_data->page_no,
|
||||
(int) $downloads_data->pages
|
||||
);
|
||||
protected function get_datastore_data( $query_args = array() ) {
|
||||
$query = new GenericQuery( $query_args, 'downloads' );
|
||||
return $query->get_data();
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare a report object for serialization.
|
||||
* Prepare a report data item for serialization.
|
||||
*
|
||||
* @param Array $report Report data.
|
||||
* @param Array $report 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( $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 = parent::prepare_item_for_response( $report, $request );
|
||||
$response->add_links( $this->prepare_links( $report ) );
|
||||
|
||||
$response->data['date'] = get_date_from_gmt( $data['date_gmt'], 'Y-m-d H:i:s' );
|
||||
$response->data['date'] = get_date_from_gmt( $report['date_gmt'], 'Y-m-d H:i:s' );
|
||||
|
||||
// Figure out file name.
|
||||
// Matches https://github.com/woocommerce/woocommerce/blob/4be0018c092e617c5d2b8c46b800eb71ece9ddef/includes/class-wc-download-handler.php#L197.
|
||||
$product_id = intval( $data['product_id'] );
|
||||
$product_id = intval( $report['product_id'] );
|
||||
$_product = wc_get_product( $product_id );
|
||||
|
||||
// Make sure the product hasn't been deleted.
|
||||
if ( $_product ) {
|
||||
$file_path = $_product->get_file_download_path( $data['download_id'] );
|
||||
$file_path = $_product->get_file_download_path( $report['download_id'] );
|
||||
$filename = basename( $file_path );
|
||||
$response->data['file_name'] = apply_filters( 'woocommerce_file_download_filename', $filename, $product_id );
|
||||
$response->data['file_path'] = $file_path;
|
||||
@@ -97,9 +74,9 @@ class Controller extends ReportsController implements ExportableInterface {
|
||||
$response->data['file_path'] = '';
|
||||
}
|
||||
|
||||
$customer = new \WC_Customer( $data['user_id'] );
|
||||
$customer = new \WC_Customer( $report['user_id'] );
|
||||
$response->data['username'] = $customer->get_username();
|
||||
$response->data['order_number'] = $this->get_order_number( $data['order_id'] );
|
||||
$response->data['order_number'] = $this->get_order_number( $report['order_id'] );
|
||||
|
||||
/**
|
||||
* Filter a report returned from the API.
|
||||
@@ -130,6 +107,22 @@ class Controller extends ReportsController implements ExportableInterface {
|
||||
return $links;
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps query arguments from the REST request.
|
||||
*
|
||||
* @param array $request Request array.
|
||||
* @return array
|
||||
*/
|
||||
protected function prepare_reports_query( $request ) {
|
||||
$args = array();
|
||||
$registered = array_keys( $this->get_collection_params() );
|
||||
foreach ( $registered as $param_name ) {
|
||||
if ( isset( $request[ $param_name ] ) ) {
|
||||
$args[ $param_name ] = $request[ $param_name ];
|
||||
}
|
||||
}
|
||||
return $args;
|
||||
}
|
||||
/**
|
||||
* Get the Report's schema, conforming to JSON Schema.
|
||||
*
|
||||
@@ -225,53 +218,10 @@ class Controller extends ReportsController implements ExportableInterface {
|
||||
* @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',
|
||||
'product',
|
||||
),
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
$params = parent::get_collection_params();
|
||||
$params['orderby']['enum'] = array(
|
||||
'date',
|
||||
'product',
|
||||
);
|
||||
$params['match'] = array(
|
||||
'description' => __( 'Indicates whether all the conditions should be true for the resulting set, or if any one of them is sufficient. Match affects the following parameters: products, orders, username, ip_address.', 'woocommerce' ),
|
||||
@@ -355,12 +305,6 @@ class Controller extends ReportsController implements ExportableInterface {
|
||||
'type' => 'string',
|
||||
),
|
||||
);
|
||||
$params['force_cache_refresh'] = array(
|
||||
'description' => __( 'Force retrieval of fresh data instead of from the cache.', 'woocommerce' ),
|
||||
'type' => 'boolean',
|
||||
'sanitize_callback' => 'wp_validate_boolean',
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
);
|
||||
|
||||
return $params;
|
||||
}
|
||||
|
||||
@@ -20,6 +20,8 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||
/**
|
||||
* Table used to get the data.
|
||||
*
|
||||
* @override ReportsDataStore::$table_name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected static $table_name = 'wc_download_log';
|
||||
@@ -27,6 +29,8 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||
/**
|
||||
* Cache identifier.
|
||||
*
|
||||
* @override ReportsDataStore::$cache_key
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $cache_key = 'downloads';
|
||||
@@ -34,6 +38,8 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||
/**
|
||||
* Mapping columns to data type to return correct response types.
|
||||
*
|
||||
* @override ReportsDataStore::$column_types
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $column_types = array(
|
||||
@@ -51,12 +57,16 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||
/**
|
||||
* Data store context used to pass to filters.
|
||||
*
|
||||
* @override ReportsDataStore::$context
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $context = 'downloads';
|
||||
|
||||
/**
|
||||
* Assign report columns once full table name has been assigned.
|
||||
*
|
||||
* @override ReportsDataStore::assign_report_columns()
|
||||
*/
|
||||
protected function assign_report_columns() {
|
||||
$this->report_columns = array(
|
||||
@@ -252,6 +262,8 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||
/**
|
||||
* Gets WHERE time clause of SQL request with date-related constraints.
|
||||
*
|
||||
* @override ReportsDataStore::add_time_period_sql_params()
|
||||
*
|
||||
* @param array $query_args Parameters supplied by the user.
|
||||
* @param string $table_name Name of the db table relevant for the date constraint.
|
||||
* @return string
|
||||
@@ -294,94 +306,89 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the report data based on parameters supplied by the user.
|
||||
* Get the default query arguments to be used by get_data().
|
||||
* These defaults are only partially applied when used via REST API, as that has its own defaults.
|
||||
*
|
||||
* @param array $query_args Query parameters.
|
||||
* @return stdClass|WP_Error Data.
|
||||
* @override ReportsDataStore::get_default_query_vars()
|
||||
*
|
||||
* @return array Query parameters.
|
||||
*/
|
||||
public function get_data( $query_args ) {
|
||||
public function get_default_query_vars() {
|
||||
$defaults = parent::get_default_query_vars();
|
||||
$defaults['orderby'] = 'timestamp';
|
||||
|
||||
return $defaults;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the report data based on normalized parameters.
|
||||
* Will be called by `get_data` if there is no data in cache.
|
||||
*
|
||||
* @override ReportsDataStore::get_noncached_data()
|
||||
*
|
||||
* @see get_data
|
||||
* @param array $query_args Query parameters.
|
||||
* @return stdClass|WP_Error Data object `{ totals: *, intervals: array, total: int, pages: int, page_no: int }`, or error.
|
||||
*/
|
||||
public function get_noncached_data( $query_args ) {
|
||||
global $wpdb;
|
||||
|
||||
$table_name = self::get_db_table_name();
|
||||
$this->initialize_queries();
|
||||
|
||||
// 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' => 'timestamp',
|
||||
'before' => TimeInterval::default_before(),
|
||||
'after' => TimeInterval::default_after(),
|
||||
'fields' => '*',
|
||||
$data = (object) array(
|
||||
'data' => array(),
|
||||
'total' => 0,
|
||||
'pages' => 0,
|
||||
'page_no' => 0,
|
||||
);
|
||||
$query_args = wp_parse_args( $query_args, $defaults );
|
||||
$this->normalize_timezones( $query_args, $defaults );
|
||||
|
||||
/*
|
||||
* We need to get the cache key here because
|
||||
* parent::update_intervals_sql_params() modifies $query_args.
|
||||
*/
|
||||
$cache_key = $this->get_cache_key( $query_args );
|
||||
$data = $this->get_cached_data( $cache_key );
|
||||
$selections = $this->selected_columns( $query_args );
|
||||
$this->add_sql_query_params( $query_args );
|
||||
|
||||
if ( false === $data ) {
|
||||
$this->initialize_queries();
|
||||
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
$db_records_count = (int) $wpdb->get_var(
|
||||
"SELECT COUNT(*) FROM (
|
||||
{$this->subquery->get_query_statement()}
|
||||
) AS tt"
|
||||
);
|
||||
// phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
|
||||
$data = (object) array(
|
||||
'data' => array(),
|
||||
'total' => 0,
|
||||
'pages' => 0,
|
||||
'page_no' => 0,
|
||||
);
|
||||
|
||||
$selections = $this->selected_columns( $query_args );
|
||||
$this->add_sql_query_params( $query_args );
|
||||
|
||||
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
$db_records_count = (int) $wpdb->get_var(
|
||||
"SELECT COUNT(*) FROM (
|
||||
{$this->subquery->get_query_statement()}
|
||||
) AS tt"
|
||||
);
|
||||
// phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
|
||||
$params = $this->get_limit_params( $query_args );
|
||||
$total_pages = (int) ceil( $db_records_count / $params['per_page'] );
|
||||
if ( $query_args['page'] < 1 || $query_args['page'] > $total_pages ) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
$this->subquery->clear_sql_clause( 'select' );
|
||||
$this->subquery->add_sql_clause( 'select', $selections );
|
||||
$this->subquery->add_sql_clause( 'order_by', $this->get_sql_clause( 'order_by' ) );
|
||||
$this->subquery->add_sql_clause( 'limit', $this->get_sql_clause( 'limit' ) );
|
||||
|
||||
$download_data = $wpdb->get_results(
|
||||
$this->subquery->get_query_statement(), // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
ARRAY_A
|
||||
);
|
||||
|
||||
if ( null === $download_data ) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
$download_data = array_map( array( $this, 'cast_numbers' ), $download_data );
|
||||
$data = (object) array(
|
||||
'data' => $download_data,
|
||||
'total' => $db_records_count,
|
||||
'pages' => $total_pages,
|
||||
'page_no' => (int) $query_args['page'],
|
||||
);
|
||||
|
||||
$this->set_cached_data( $cache_key, $data );
|
||||
$params = $this->get_limit_params( $query_args );
|
||||
$total_pages = (int) ceil( $db_records_count / $params['per_page'] );
|
||||
if ( $query_args['page'] < 1 || $query_args['page'] > $total_pages ) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
$this->subquery->clear_sql_clause( 'select' );
|
||||
$this->subquery->add_sql_clause( 'select', $selections );
|
||||
$this->subquery->add_sql_clause( 'order_by', $this->get_sql_clause( 'order_by' ) );
|
||||
$this->subquery->add_sql_clause( 'limit', $this->get_sql_clause( 'limit' ) );
|
||||
|
||||
$download_data = $wpdb->get_results(
|
||||
$this->subquery->get_query_statement(), // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
ARRAY_A
|
||||
);
|
||||
|
||||
if ( null === $download_data ) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
$download_data = array_map( array( $this, 'cast_numbers' ), $download_data );
|
||||
$data = (object) array(
|
||||
'data' => $download_data,
|
||||
'total' => $db_records_count,
|
||||
'pages' => $total_pages,
|
||||
'page_no' => (int) $query_args['page'],
|
||||
);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps ordering specified by the user to columns in the database/fields in the data.
|
||||
*
|
||||
* @override ReportsDataStore::normalize_order_by()
|
||||
*
|
||||
* @param string $order_by Sorting criterion.
|
||||
* @return string
|
||||
*/
|
||||
|
||||
@@ -21,12 +21,16 @@ use Automattic\WooCommerce\Admin\API\Reports\Query as ReportsQuery;
|
||||
|
||||
/**
|
||||
* API\Reports\Downloads\Query
|
||||
*
|
||||
* @deprecated 9.3.0 Downloads\Query class is deprecated, please use GenericQuery or \WC_Object_Query instead.
|
||||
*/
|
||||
class Query extends ReportsQuery {
|
||||
|
||||
/**
|
||||
* Valid fields for downloads report.
|
||||
*
|
||||
* @deprecated 9.3.0 Downloads\Query class is deprecated, please use GenericQuery or \WC_Object_Query instead.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_default_query_vars() {
|
||||
@@ -36,6 +40,8 @@ class Query extends ReportsQuery {
|
||||
/**
|
||||
* Get downloads data based on the current query vars.
|
||||
*
|
||||
* @deprecated 9.3.0 Downloads\Query class is deprecated, please use GenericQuery or \WC_Object_Query instead.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_data() {
|
||||
|
||||
@@ -9,6 +9,7 @@ namespace Automattic\WooCommerce\Admin\API\Reports\Downloads\Stats;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
use Automattic\WooCommerce\Admin\API\Reports\GenericQuery;
|
||||
use Automattic\WooCommerce\Admin\API\Reports\GenericStatsController;
|
||||
use WP_REST_Request;
|
||||
use WP_REST_Response;
|
||||
@@ -59,39 +60,22 @@ class Controller extends GenericStatsController {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all reports.
|
||||
* Get data from `'downloads-stats'` Query.
|
||||
*
|
||||
* @param WP_REST_Request $request Request data.
|
||||
* @return array|WP_Error
|
||||
* @override GenericController::get_datastore_data()
|
||||
*
|
||||
* @param array $query_args Query arguments.
|
||||
* @return mixed Results from the data store.
|
||||
*/
|
||||
public function get_items( $request ) {
|
||||
$query_args = $this->prepare_reports_query( $request );
|
||||
$downloads_query = new Query( $query_args );
|
||||
$report_data = $downloads_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 );
|
||||
}
|
||||
|
||||
return $this->add_pagination_headers(
|
||||
$request,
|
||||
$out_data,
|
||||
(int) $report_data->total,
|
||||
(int) $report_data->page_no,
|
||||
(int) $report_data->pages
|
||||
);
|
||||
protected function get_datastore_data( $query_args = array() ) {
|
||||
$query = new GenericQuery( $query_args, 'downloads-stats' );
|
||||
return $query->get_data();
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare a report object for serialization.
|
||||
* Prepare a report data item for serialization.
|
||||
*
|
||||
* @param array $report Report data.
|
||||
* @param array $report Report data item as returned from Data Store.
|
||||
* @param WP_REST_Request $request Request object.
|
||||
* @return WP_REST_Response
|
||||
*/
|
||||
@@ -110,7 +94,6 @@ class Controller extends GenericStatsController {
|
||||
return apply_filters( 'woocommerce_rest_prepare_report_downloads_stats', $response, $report, $request );
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the Report's item properties schema.
|
||||
* Will be used by `get_item_schema` as `totals` and `subtotals`.
|
||||
@@ -129,6 +112,7 @@ class Controller extends GenericStatsController {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Report's schema, conforming to JSON Schema.
|
||||
* It does not have the segments as in GenericStatsController.
|
||||
@@ -298,15 +282,6 @@ class Controller extends GenericStatsController {
|
||||
'type' => 'string',
|
||||
),
|
||||
);
|
||||
$params['fields'] = array(
|
||||
'description' => __( 'Limit stats fields to the specified items.', 'woocommerce' ),
|
||||
'type' => 'array',
|
||||
'sanitize_callback' => 'wp_parse_slug_list',
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
'items' => array(
|
||||
'type' => 'string',
|
||||
),
|
||||
);
|
||||
|
||||
return $params;
|
||||
}
|
||||
|
||||
@@ -10,16 +10,19 @@ defined( 'ABSPATH' ) || exit;
|
||||
use Automattic\WooCommerce\Admin\API\Reports\Downloads\DataStore as DownloadsDataStore;
|
||||
use Automattic\WooCommerce\Admin\API\Reports\DataStoreInterface;
|
||||
use Automattic\WooCommerce\Admin\API\Reports\TimeInterval;
|
||||
use Automattic\WooCommerce\Admin\API\Reports\SqlQuery;
|
||||
use Automattic\WooCommerce\Admin\API\Reports\StatsDataStoreTrait;
|
||||
|
||||
/**
|
||||
* API\Reports\Downloads\Stats\DataStore.
|
||||
*/
|
||||
class DataStore extends DownloadsDataStore implements DataStoreInterface {
|
||||
use StatsDataStoreTrait;
|
||||
|
||||
/**
|
||||
* Mapping columns to data type to return correct response types.
|
||||
*
|
||||
* @override DownloadsDataStore::$column_types
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $column_types = array(
|
||||
@@ -29,6 +32,8 @@ class DataStore extends DownloadsDataStore implements DataStoreInterface {
|
||||
/**
|
||||
* Cache identifier.
|
||||
*
|
||||
* @override DownloadsDataStore::$cache_key
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $cache_key = 'downloads_stats';
|
||||
@@ -36,12 +41,16 @@ class DataStore extends DownloadsDataStore implements DataStoreInterface {
|
||||
/**
|
||||
* Data store context used to pass to filters.
|
||||
*
|
||||
* @override DownloadsDataStore::$context
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $context = 'downloads_stats';
|
||||
|
||||
/**
|
||||
* Assign report columns once full table name has been assigned.
|
||||
*
|
||||
* @override DownloadsDataStore::assign_report_columns()
|
||||
*/
|
||||
protected function assign_report_columns() {
|
||||
$this->report_columns = array(
|
||||
@@ -50,111 +59,100 @@ class DataStore extends DownloadsDataStore implements DataStoreInterface {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the report data based on parameters supplied by the user.
|
||||
* Get the default query arguments to be used by get_data().
|
||||
* These defaults are only partially applied when used via REST API, as that has its own defaults.
|
||||
*
|
||||
* @param array $query_args Query parameters.
|
||||
* @return stdClass|WP_Error Data.
|
||||
* @override DownloadsDataStore::default_query_args()
|
||||
*
|
||||
* @return array Query parameters.
|
||||
*/
|
||||
public function get_data( $query_args ) {
|
||||
public function get_default_query_vars() {
|
||||
$defaults = parent::get_default_query_vars();
|
||||
$defaults['interval'] = 'week';
|
||||
|
||||
return $defaults;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the report data based on normalized parameters.
|
||||
* Will be called by `get_data` if there is no data in cache.
|
||||
*
|
||||
* @override DownloadsDataStore::get_noncached_data()
|
||||
*
|
||||
* @see get_data
|
||||
* @see get_noncached_stats_data
|
||||
* @param array $query_args Query parameters.
|
||||
* @param array $params Query limit parameters.
|
||||
* @param stdClass $data Reference to the data object to fill.
|
||||
* @param int $expected_interval_count Number of expected intervals.
|
||||
* @return stdClass|WP_Error Data object `{ totals: *, intervals: array, total: int, pages: int, page_no: int }`, or error.
|
||||
*/
|
||||
public function get_noncached_stats_data( $query_args, $params, &$data, $expected_interval_count ) {
|
||||
global $wpdb;
|
||||
|
||||
$table_name = self::get_db_table_name();
|
||||
|
||||
// These defaults are only partially applied when used via REST API, as that has its own defaults.
|
||||
$defaults = array(
|
||||
'per_page' => get_option( 'posts_per_page' ),
|
||||
'page' => 1,
|
||||
'order' => 'DESC',
|
||||
'orderby' => 'date',
|
||||
'fields' => '*',
|
||||
'interval' => 'week',
|
||||
'before' => TimeInterval::default_before(),
|
||||
'after' => TimeInterval::default_after(),
|
||||
$this->initialize_queries();
|
||||
$selections = $this->selected_columns( $query_args );
|
||||
$this->add_sql_query_params( $query_args );
|
||||
$where_time = $this->add_time_period_sql_params( $query_args, $table_name );
|
||||
$this->add_intervals_sql_params( $query_args, $table_name );
|
||||
|
||||
$this->interval_query->add_sql_clause( 'select', $this->get_sql_clause( 'select' ) . ' AS time_interval' );
|
||||
$this->interval_query->str_replace_clause( 'select', 'date_created', 'timestamp' );
|
||||
$this->interval_query->str_replace_clause( 'where_time', 'date_created', 'timestamp' );
|
||||
|
||||
$db_intervals = $wpdb->get_col(
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- cache ok, DB call ok, unprepared SQL ok.
|
||||
$this->interval_query->get_query_statement()
|
||||
);
|
||||
$query_args = wp_parse_args( $query_args, $defaults );
|
||||
$this->normalize_timezones( $query_args, $defaults );
|
||||
|
||||
/*
|
||||
* We need to get the cache key here because
|
||||
* parent::update_intervals_sql_params() modifies $query_args.
|
||||
*/
|
||||
$cache_key = $this->get_cache_key( $query_args );
|
||||
$data = $this->get_cached_data( $cache_key );
|
||||
$db_records_count = count( $db_intervals );
|
||||
|
||||
if ( false === $data ) {
|
||||
$this->initialize_queries();
|
||||
$selections = $this->selected_columns( $query_args );
|
||||
$this->add_sql_query_params( $query_args );
|
||||
$where_time = $this->add_time_period_sql_params( $query_args, $table_name );
|
||||
$this->add_intervals_sql_params( $query_args, $table_name );
|
||||
$this->update_intervals_sql_params( $query_args, $db_records_count, $expected_interval_count, $table_name );
|
||||
$this->interval_query->str_replace_clause( 'where_time', 'date_created', 'timestamp' );
|
||||
$this->total_query->add_sql_clause( 'select', $selections );
|
||||
$this->total_query->add_sql_clause( 'where', $this->interval_query->get_sql_clause( 'where' ) );
|
||||
if ( $where_time ) {
|
||||
$this->total_query->add_sql_clause( 'where_time', $where_time );
|
||||
}
|
||||
$totals = $wpdb->get_results(
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- cache ok, DB call ok, unprepared SQL ok.
|
||||
$this->total_query->get_query_statement(),
|
||||
ARRAY_A
|
||||
);
|
||||
if ( null === $totals ) {
|
||||
return new \WP_Error( 'woocommerce_analytics_downloads_stats_result_failed', __( 'Sorry, fetching downloads data failed.', 'woocommerce' ) );
|
||||
}
|
||||
|
||||
$this->interval_query->add_sql_clause( 'select', $this->get_sql_clause( 'select' ) . ' AS time_interval' );
|
||||
$this->interval_query->str_replace_clause( 'select', 'date_created', 'timestamp' );
|
||||
$this->interval_query->str_replace_clause( 'where_time', 'date_created', 'timestamp' );
|
||||
$this->interval_query->add_sql_clause( 'order_by', $this->get_sql_clause( 'order_by' ) );
|
||||
$this->interval_query->add_sql_clause( 'limit', $this->get_sql_clause( 'limit' ) );
|
||||
$this->interval_query->add_sql_clause( 'select', ', MAX(timestamp) AS datetime_anchor' );
|
||||
if ( '' !== $selections ) {
|
||||
$this->interval_query->add_sql_clause( 'select', ', ' . $selections );
|
||||
}
|
||||
|
||||
$db_intervals = $wpdb->get_col(
|
||||
$this->interval_query->get_query_statement()
|
||||
); // phpcs:ignore cache ok, DB call ok, unprepared SQL ok.
|
||||
$intervals = $wpdb->get_results(
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- cache ok, DB call ok, unprepared SQL ok.
|
||||
$this->interval_query->get_query_statement(),
|
||||
ARRAY_A
|
||||
);
|
||||
|
||||
$db_records_count = count( $db_intervals );
|
||||
if ( null === $intervals ) {
|
||||
return new \WP_Error( 'woocommerce_analytics_downloads_stats_result_failed', __( 'Sorry, fetching downloads data failed.', 'woocommerce' ) );
|
||||
}
|
||||
|
||||
$params = $this->get_limit_params( $query_args );
|
||||
$expected_interval_count = TimeInterval::intervals_between( $query_args['after'], $query_args['before'], $query_args['interval'] );
|
||||
$total_pages = (int) ceil( $expected_interval_count / $params['per_page'] );
|
||||
if ( $query_args['page'] < 1 || $query_args['page'] > $total_pages ) {
|
||||
return array();
|
||||
}
|
||||
$totals = (object) $this->cast_numbers( $totals[0] );
|
||||
|
||||
$this->update_intervals_sql_params( $query_args, $db_records_count, $expected_interval_count, $table_name );
|
||||
$this->interval_query->str_replace_clause( 'where_time', 'date_created', 'timestamp' );
|
||||
$this->total_query->add_sql_clause( 'select', $selections );
|
||||
$this->total_query->add_sql_clause( 'where', $this->interval_query->get_sql_clause( 'where' ) );
|
||||
if ( $where_time ) {
|
||||
$this->total_query->add_sql_clause( 'where_time', $where_time );
|
||||
}
|
||||
$totals = $wpdb->get_results(
|
||||
$this->total_query->get_query_statement(),
|
||||
ARRAY_A
|
||||
); // phpcs:ignore cache ok, DB call ok, unprepared SQL ok.
|
||||
if ( null === $totals ) {
|
||||
return new \WP_Error( 'woocommerce_analytics_downloads_stats_result_failed', __( 'Sorry, fetching downloads data failed.', 'woocommerce' ) );
|
||||
}
|
||||
$data->totals = $totals;
|
||||
$data->intervals = $intervals;
|
||||
|
||||
$this->interval_query->add_sql_clause( 'order_by', $this->get_sql_clause( 'order_by' ) );
|
||||
$this->interval_query->add_sql_clause( 'limit', $this->get_sql_clause( 'limit' ) );
|
||||
$this->interval_query->add_sql_clause( 'select', ', MAX(timestamp) AS datetime_anchor' );
|
||||
if ( '' !== $selections ) {
|
||||
$this->interval_query->add_sql_clause( 'select', ', ' . $selections );
|
||||
}
|
||||
|
||||
$intervals = $wpdb->get_results(
|
||||
$this->interval_query->get_query_statement(),
|
||||
ARRAY_A
|
||||
); // phpcs:ignore cache ok, DB call ok, unprepared SQL ok.
|
||||
|
||||
if ( null === $intervals ) {
|
||||
return new \WP_Error( 'woocommerce_analytics_downloads_stats_result_failed', __( 'Sorry, fetching downloads data failed.', 'woocommerce' ) );
|
||||
}
|
||||
|
||||
$totals = (object) $this->cast_numbers( $totals[0] );
|
||||
$data = (object) array(
|
||||
'totals' => $totals,
|
||||
'intervals' => $intervals,
|
||||
'total' => $expected_interval_count,
|
||||
'pages' => $total_pages,
|
||||
'page_no' => (int) $query_args['page'],
|
||||
);
|
||||
|
||||
if ( $this->intervals_missing( $expected_interval_count, $db_records_count, $params['per_page'], $query_args['page'], $query_args['order'], $query_args['orderby'], count( $intervals ) ) ) {
|
||||
$this->fill_in_missing_intervals( $db_intervals, $query_args['adj_after'], $query_args['adj_before'], $query_args['interval'], $data );
|
||||
$this->sort_intervals( $data, $query_args['orderby'], $query_args['order'] );
|
||||
$this->remove_extra_records( $data, $query_args['page'], $params['per_page'], $db_records_count, $expected_interval_count, $query_args['orderby'], $query_args['order'] );
|
||||
} else {
|
||||
$this->update_interval_boundary_dates( $query_args['after'], $query_args['before'], $query_args['interval'], $data->intervals );
|
||||
}
|
||||
$this->create_interval_subtotals( $data->intervals );
|
||||
|
||||
$this->set_cached_data( $cache_key, $data );
|
||||
if ( $this->intervals_missing( $expected_interval_count, $db_records_count, $params['per_page'], $query_args['page'], $query_args['order'], $query_args['orderby'], count( $intervals ) ) ) {
|
||||
$this->fill_in_missing_intervals( $db_intervals, $query_args['adj_after'], $query_args['adj_before'], $query_args['interval'], $data );
|
||||
$this->sort_intervals( $data, $query_args['orderby'], $query_args['order'] );
|
||||
$this->remove_extra_records( $data, $query_args['page'], $params['per_page'], $db_records_count, $expected_interval_count, $query_args['orderby'], $query_args['order'] );
|
||||
} else {
|
||||
$this->update_interval_boundary_dates( $query_args['after'], $query_args['before'], $query_args['interval'], $data->intervals );
|
||||
}
|
||||
|
||||
return $data;
|
||||
@@ -163,6 +161,8 @@ class DataStore extends DownloadsDataStore implements DataStoreInterface {
|
||||
/**
|
||||
* Normalizes order_by clause to match to SQL query.
|
||||
*
|
||||
* @override DownloadsDataStore::normalize_order_by()
|
||||
*
|
||||
* @param string $order_by Order by option requeste by user.
|
||||
* @return string
|
||||
*/
|
||||
@@ -173,18 +173,4 @@ class DataStore extends DownloadsDataStore implements DataStoreInterface {
|
||||
|
||||
return $order_by;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize query objects.
|
||||
*/
|
||||
protected function initialize_queries() {
|
||||
$this->clear_all_clauses();
|
||||
unset( $this->subquery );
|
||||
$this->total_query = new SqlQuery( $this->context . '_total' );
|
||||
$this->total_query->add_sql_clause( 'from', self::get_db_table_name() );
|
||||
|
||||
$this->interval_query = new SqlQuery( $this->context . '_interval' );
|
||||
$this->interval_query->add_sql_clause( 'from', self::get_db_table_name() );
|
||||
$this->interval_query->add_sql_clause( 'group_by', 'time_interval' );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,12 +11,16 @@ use Automattic\WooCommerce\Admin\API\Reports\Query as ReportsQuery;
|
||||
|
||||
/**
|
||||
* API\Reports\Downloads\Stats\Query
|
||||
*
|
||||
* @deprecated 9.3.0 Downloads\Stats\Query class is deprecated, please use GenericQuery or \WC_Object_Query instead.
|
||||
*/
|
||||
class Query extends ReportsQuery {
|
||||
|
||||
/**
|
||||
* Valid fields for Orders report.
|
||||
*
|
||||
* @deprecated 9.3.0 Downloads\Stats\Query class is deprecated, please use GenericQuery or \WC_Object_Query instead.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_default_query_vars() {
|
||||
@@ -26,6 +30,8 @@ class Query extends ReportsQuery {
|
||||
/**
|
||||
* Get revenue data based on the current query vars.
|
||||
*
|
||||
* @deprecated 9.3.0 Downloads\Stats\Query class is deprecated, please use GenericQuery or \WC_Object_Query instead.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_data() {
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
declare( strict_types = 1);
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\API\Reports;
|
||||
|
||||
// Exit if accessed directly.
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Trait to call filters on `get_data` methods for data stores.
|
||||
*
|
||||
* It calls the filters `woocommerce_analytics_{$this->context}_query_args` and
|
||||
* `woocommerce_analytics_{$this->context}_select_query` on the `get_data` method.
|
||||
*
|
||||
* Example:
|
||||
* <pre><code class="language-php">class MyStatsDataStore extends DataStore implements DataStoreInterface {
|
||||
* // Use the trait.
|
||||
* use FilteredGetDataTrait;
|
||||
* // Provide all the necessary properties and methods for a regular DataStore.
|
||||
* // ...
|
||||
* }
|
||||
* </code></pre>
|
||||
*
|
||||
* @see DataStore
|
||||
*/
|
||||
trait FilteredGetDataTrait {
|
||||
/**
|
||||
* Get the data based on args.
|
||||
*
|
||||
* Filters query args, calls DataStore::get_data, and returns the filtered data.
|
||||
*
|
||||
* @override ReportsDataStore::get_data()
|
||||
*
|
||||
* @param array $query_args Query parameters.
|
||||
* @return stdClass|WP_Error
|
||||
*/
|
||||
public function get_data( $query_args ) {
|
||||
/**
|
||||
* Called before the data is fetched.
|
||||
*
|
||||
* @since 9.3.0
|
||||
* @param array $query_args Query parameters.
|
||||
*/
|
||||
$args = apply_filters( "woocommerce_analytics_{$this->context}_query_args", $query_args );
|
||||
$results = parent::get_data( $args );
|
||||
/**
|
||||
* Called after the data is fetched.
|
||||
* The results can be modified here.
|
||||
*
|
||||
* @since 9.3.0
|
||||
* @param stdClass|WP_Error $results The results of the query.
|
||||
*/
|
||||
return apply_filters( "woocommerce_analytics_{$this->context}_select_query", $results, $args );
|
||||
}
|
||||
}
|
||||
@@ -7,10 +7,45 @@ use WP_REST_Request;
|
||||
use WP_REST_Response;
|
||||
|
||||
/**
|
||||
* WC REST API Reports controller extended
|
||||
* to be shared as a generic base for all Analytics controllers.
|
||||
* {@see WC_REST_Reports_Controller WC REST API Reports Controller} extended to be shared as a generic base for all Analytics reports controllers.
|
||||
*
|
||||
* Handles pagination HTTP headers and links, basic, conventional params.
|
||||
* Does all the REST API plumbing as `WC_REST_Controller`.
|
||||
*
|
||||
*
|
||||
* Minimalistic example:
|
||||
* <pre><code class="language-php">class MyController extends GenericController {
|
||||
* /** Route of your new REST endpoint. */
|
||||
* protected $rest_base = 'reports/my-thing';
|
||||
* /**
|
||||
* * Provide JSON schema for the response item.
|
||||
* * @override WC_REST_Reports_Controller::get_item_schema()
|
||||
* */
|
||||
* public function get_item_schema() {
|
||||
* $schema = array(
|
||||
* '$schema' => 'http://json-schema.org/draft-04/schema#',
|
||||
* 'title' => 'report_my_thing',
|
||||
* 'type' => 'object',
|
||||
* 'properties' => array(
|
||||
* 'product_id' => array(
|
||||
* 'type' => 'integer',
|
||||
* 'readonly' => true,
|
||||
* 'context' => array( 'view', 'edit' ),
|
||||
* 'description' => __( 'Product ID.', 'my_extension' ),
|
||||
* ),
|
||||
* ),
|
||||
* );
|
||||
* // Add additional fields from `get_additional_fields` method and apply `woocommerce_rest_' . $schema['title'] . '_schema` filter.
|
||||
* return $this->add_additional_fields_schema( $schema );
|
||||
* }
|
||||
* }
|
||||
* </code></pre>
|
||||
*
|
||||
* The above Controller will get the data from a {@see DataStore data store} registered as `$rest_base` (`reports/my-thing`).
|
||||
* (To change this behavior, override the `get_datastore_data()` method).
|
||||
*
|
||||
* To use the controller, please register it with the filter `woocommerce_admin_rest_controllers` filter.
|
||||
*
|
||||
* @internal
|
||||
* @extends WC_REST_Reports_Controller
|
||||
*/
|
||||
abstract class GenericController extends \WC_REST_Reports_Controller {
|
||||
@@ -26,12 +61,12 @@ abstract class GenericController extends \WC_REST_Reports_Controller {
|
||||
/**
|
||||
* Add pagination headers and links.
|
||||
*
|
||||
* @param WP_REST_Request $request Request data.
|
||||
* @param WP_REST_Response|array $response Response data.
|
||||
* @param int $total Total results.
|
||||
* @param int $page Current page.
|
||||
* @param int $max_pages Total amount of pages.
|
||||
* @return WP_REST_Response
|
||||
* @param \WP_REST_Request $request Request data.
|
||||
* @param \WP_REST_Response|array $response Response data.
|
||||
* @param int $total Total results.
|
||||
* @param int $page Current page.
|
||||
* @param int $max_pages Total amount of pages.
|
||||
* @return \WP_REST_Response
|
||||
*/
|
||||
public function add_pagination_headers( $request, $response, int $total, int $page, int $max_pages ) {
|
||||
$response = rest_ensure_response( $response );
|
||||
@@ -62,7 +97,19 @@ abstract class GenericController extends \WC_REST_Reports_Controller {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the query params for collections.
|
||||
* Get data from `{$this->rest_base}` store, based on the given query vars.
|
||||
*
|
||||
* @throws Exception When the data store is not found {@see WC_Data_Store WC_Data_Store}.
|
||||
* @param array $query_args Query arguments.
|
||||
* @return mixed Results from the data store.
|
||||
*/
|
||||
protected function get_datastore_data( $query_args = array() ) {
|
||||
$data_store = \WC_Data_Store::load( $this->rest_base );
|
||||
return $data_store->get_data( $query_args );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the query params definition for collections.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
@@ -124,15 +171,62 @@ abstract class GenericController extends \WC_REST_Reports_Controller {
|
||||
return $params;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Prepare a report object for serialization.
|
||||
* Get the report data.
|
||||
*
|
||||
* @param array $report Report data.
|
||||
* @param WP_REST_Request $request Request object.
|
||||
* Prepares query params, fetches the report data from the Query object,
|
||||
* prepares it for the response, and packs it into the convention-conforming response object.
|
||||
*
|
||||
* @throws \WP_Error When the queried data is invalid.
|
||||
* @param \WP_REST_Request $request Request data.
|
||||
* @return \WP_Error|\WP_REST_Response
|
||||
*/
|
||||
public function get_items( $request ) {
|
||||
$query_args = $this->prepare_reports_query( $request );
|
||||
$report_data = $this->get_datastore_data( $query_args );
|
||||
|
||||
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_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 );
|
||||
}
|
||||
|
||||
return $this->add_pagination_headers(
|
||||
$request,
|
||||
$out_data,
|
||||
(int) $report_data->total,
|
||||
(int) $report_data->page_no,
|
||||
(int) $report_data->pages
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare a report data item for serialization.
|
||||
*
|
||||
* This method is called by `get_items` to prepare a single report data item for serialization.
|
||||
* Calls `add_additional_fields_to_object` and `filter_response_by_context`,
|
||||
* then wpraps the data with `rest_ensure_response`.
|
||||
*
|
||||
* You can extend it to add or filter some fields.
|
||||
*
|
||||
* @override WP_REST_Posts_Controller::prepare_item_for_response()
|
||||
*
|
||||
* @param mixed $report_item 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( $report, $request ) {
|
||||
$data = $report;
|
||||
public function prepare_item_for_response( $report_item, $request ) {
|
||||
$data = $report_item;
|
||||
|
||||
$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
|
||||
$data = $this->add_additional_fields_to_object( $data, $request );
|
||||
@@ -141,4 +235,26 @@ abstract class GenericController extends \WC_REST_Reports_Controller {
|
||||
// Wrap the data in a response object.
|
||||
return rest_ensure_response( $data );
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps query arguments from the REST request, to be fed to Query.
|
||||
*
|
||||
* `WP_REST_Request` does not expose a method to return all params covering defaults,
|
||||
* as it does for `$request['param']` accessor.
|
||||
* Therefore, we re-implement defaults resolution.
|
||||
*
|
||||
* @param \WP_REST_Request $request Full request object.
|
||||
* @return array Simplified array of params.
|
||||
*/
|
||||
protected function prepare_reports_query( $request ) {
|
||||
$args = wp_parse_args(
|
||||
array_intersect_key(
|
||||
$request->get_query_params(),
|
||||
$this->get_collection_params()
|
||||
),
|
||||
$request->get_default_params()
|
||||
);
|
||||
|
||||
return $args;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
<?php
|
||||
declare( strict_types = 1);
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\API\Reports;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
use WC_Data_Store;
|
||||
|
||||
/**
|
||||
* A generic class for a report-specific query to be used in Analytics.
|
||||
*
|
||||
* Example usage:
|
||||
* <pre><code class="language-php">$args = array(
|
||||
* 'before' => '2018-07-19 00:00:00',
|
||||
* 'after' => '2018-07-05 00:00:00',
|
||||
* 'page' => 2,
|
||||
* );
|
||||
* $report = new GenericQuery( $args, 'coupons' );
|
||||
* $mydata = $report->get_data();
|
||||
* </code></pre>
|
||||
*
|
||||
* It uses the name provided in the class property or in the constructor call to load the `report-{name}` data store.
|
||||
*
|
||||
* It's used by the {@see GenericController GenericController}.
|
||||
*
|
||||
* @since 9.3.0
|
||||
*/
|
||||
class GenericQuery extends \WC_Object_Query {
|
||||
|
||||
/**
|
||||
* Specific query name.
|
||||
* Will be used to load the `report-{name}` data store,
|
||||
* and to call `woocommerce_analytics_{snake_case(name)}_*` filters.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $name;
|
||||
|
||||
/**
|
||||
* Create a new query.
|
||||
*
|
||||
* @param array $args Criteria to query on in a format similar to WP_Query.
|
||||
* @param string $name Query name.
|
||||
* @extends WC_Object_Query::_construct
|
||||
*/
|
||||
public function __construct( $args, $name = null ) {
|
||||
$this->name = $name ?? $this->name;
|
||||
|
||||
return parent::__construct( $args ); // phpcs:ignore Universal.CodeAnalysis.ConstructorDestructorReturn.ReturnValueFound
|
||||
}
|
||||
/**
|
||||
* Valid fields for Products report.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_default_query_vars() {
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get data from `report-{$name}` store, based on the current query vars.
|
||||
* Filters query vars through `woocommerce_analytics_{snake_case(name)}_query_args` filter.
|
||||
* Filters results through `woocommerce_analytics_{snake_case(name)}_select_query` filter.
|
||||
*
|
||||
* @return mixed filtered results from the data store.
|
||||
*/
|
||||
public function get_data() {
|
||||
$snake_name = str_replace( '-', '_', $this->name );
|
||||
/**
|
||||
* Filter query args given for the report.
|
||||
*
|
||||
* @since 9.3.0
|
||||
*
|
||||
* @param array $query_args Query args.
|
||||
*/
|
||||
$args = apply_filters( "woocommerce_analytics_{$snake_name}_query_args", $this->get_query_vars() );
|
||||
|
||||
$data_store = \WC_Data_Store::load( "report-{$this->name}" );
|
||||
$results = $data_store->get_data( $args );
|
||||
/**
|
||||
* Filter report query results.
|
||||
*
|
||||
* @since 9.3.0
|
||||
*
|
||||
* @param stdClass|WP_Error $results Results from the data store.
|
||||
* @param array $args Query args used to get the data (potentially filtered).
|
||||
*/
|
||||
return apply_filters( "woocommerce_analytics_{$snake_name}_select_query", $results, $args );
|
||||
}
|
||||
}
|
||||
@@ -6,21 +6,69 @@ defined( 'ABSPATH' ) || exit;
|
||||
use Automattic\WooCommerce\Admin\API\Reports\GenericController;
|
||||
|
||||
/**
|
||||
* Generic base for all Stats controllers.
|
||||
* Generic base for all stats controllers.
|
||||
*
|
||||
* {@see GenericController Generic Controller} extended to be shared as a generic base for all Analytics stats controllers.
|
||||
*
|
||||
* Besides the `GenericController` functionality, it adds conventional stats-specific collection params and item schema.
|
||||
* So, you may want to extend only your report-specific {@see get_item_properties_schema() get_item_properties_schema()}`.
|
||||
* It also uses the stats-specific {@see get_items() get_items()} method,
|
||||
* which packs report data into `totals` and `intervals`.
|
||||
*
|
||||
*
|
||||
* Minimalistic example:
|
||||
* <pre><code class="language-php">class StatsController extends GenericStatsController {
|
||||
* /** Route of your new REST endpoint. */
|
||||
* protected $rest_base = 'reports/my-thing/stats';
|
||||
* /** Define your proeprties schema. */
|
||||
* protected function get_item_properties_schema() {
|
||||
* return array(
|
||||
* 'my_property' => array(
|
||||
* 'title' => __( 'My property', 'my-extension' ),
|
||||
* 'type' => 'integer',
|
||||
* 'readonly' => true,
|
||||
* 'context' => array( 'view', 'edit' ),
|
||||
* 'description' => __( 'Amazing thing.', 'my-extension' ),
|
||||
* 'indicator' => true,
|
||||
* ),
|
||||
* );
|
||||
* }
|
||||
* /** Define overall schema. You can use the defaults,
|
||||
* * just remember to provide your title and call `add_additional_fields_schema`
|
||||
* * to run the filters
|
||||
* */
|
||||
* public function get_item_schema() {
|
||||
* $schema = parent::get_item_schema();
|
||||
* $schema['title'] = 'report_my_thing_stats';
|
||||
*
|
||||
* return $this->add_additional_fields_schema( $schema );
|
||||
* }
|
||||
* }
|
||||
* </code></pre>
|
||||
*
|
||||
* @internal
|
||||
* @extends GenericController
|
||||
*/
|
||||
abstract class GenericStatsController extends GenericController {
|
||||
|
||||
/**
|
||||
* Get the query params for collections.
|
||||
* Adds intervals to the generic list.
|
||||
* Get the query params definition for collections.
|
||||
* Adds `fields` & `intervals` to the generic list.
|
||||
*
|
||||
* @override GenericController::get_collection_params()
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_collection_params() {
|
||||
$params = parent::get_collection_params();
|
||||
$params['fields'] = array(
|
||||
'description' => __( 'Limit stats fields to the specified items.', 'woocommerce' ),
|
||||
'type' => 'array',
|
||||
'sanitize_callback' => 'wp_parse_slug_list',
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
'items' => array(
|
||||
'type' => 'string',
|
||||
),
|
||||
);
|
||||
$params['interval'] = array(
|
||||
'description' => __( 'Time interval to use for buckets in the returned data.', 'woocommerce' ),
|
||||
'type' => 'string',
|
||||
@@ -40,7 +88,7 @@ abstract class GenericStatsController extends GenericController {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Report's item properties schema.
|
||||
* Get the report's item properties schema.
|
||||
* Will be used by `get_item_schema` as `totals` and `subtotals`.
|
||||
*
|
||||
* @return array
|
||||
@@ -50,7 +98,7 @@ abstract class GenericStatsController extends GenericController {
|
||||
/**
|
||||
* Get the Report's schema, conforming to JSON Schema.
|
||||
*
|
||||
* Please note, it does not call add_additional_fields_schema,
|
||||
* Please note that it does not call add_additional_fields_schema,
|
||||
* as you may want to update the `title` first.
|
||||
*
|
||||
* @return array
|
||||
@@ -155,4 +203,43 @@ abstract class GenericStatsController extends GenericController {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the report data.
|
||||
*
|
||||
* Prepares query params, fetches the report data from the Query object,
|
||||
* prepares it for the response, and packs it into the convention-conforming response object.
|
||||
*
|
||||
* @override GenericController::get_items()
|
||||
*
|
||||
* @throws \WP_Error When the queried data is invalid.
|
||||
* @param \WP_REST_Request $request Request data.
|
||||
* @return \WP_REST_Response|\WP_Error
|
||||
*/
|
||||
public function get_items( $request ) {
|
||||
$query_args = $this->prepare_reports_query( $request );
|
||||
try {
|
||||
$report_data = $this->get_datastore_data( $query_args );
|
||||
} catch ( ParameterException $e ) {
|
||||
return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) );
|
||||
}
|
||||
|
||||
$out_data = array(
|
||||
'totals' => $report_data->totals ? get_object_vars( $report_data->totals ) : null,
|
||||
'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 );
|
||||
}
|
||||
|
||||
return $this->add_pagination_headers(
|
||||
$request,
|
||||
$out_data,
|
||||
(int) $report_data->total,
|
||||
(int) $report_data->page_no,
|
||||
(int) $report_data->pages
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
<?php
|
||||
declare( strict_types = 1);
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\API\Reports;
|
||||
|
||||
// Exit if accessed directly.
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Trait to contain shared methods for reports Controllers that use order and orders statuses.
|
||||
*
|
||||
* If your analytics controller needs to work with orders,
|
||||
* you will most probably need to use at least {@see get_order_statuses() get_order_statuses()}
|
||||
* to filter only "actionable" statuses to produce consistent results among other analytics.
|
||||
*
|
||||
* @see GenericController
|
||||
*/
|
||||
trait OrderAwareControllerTrait {
|
||||
|
||||
/**
|
||||
* Get the order number for an order. If no filter is present for `woocommerce_order_number`, we can just return the ID.
|
||||
* Returns the parent order number if the order is actually a refund.
|
||||
*
|
||||
* @param int $order_id Order ID.
|
||||
* @return string|null The Order Number or null if the order doesn't exist.
|
||||
*/
|
||||
protected function get_order_number( $order_id ) {
|
||||
$order = wc_get_order( $order_id );
|
||||
|
||||
if ( ! $this->is_valid_order( $order ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ( 'shop_order_refund' === $order->get_type() ) {
|
||||
$order = wc_get_order( $order->get_parent_id() );
|
||||
|
||||
// If the parent order doesn't exist, return null.
|
||||
if ( ! $this->is_valid_order( $order ) ) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! has_filter( 'woocommerce_order_number' ) ) {
|
||||
return $order->get_id();
|
||||
}
|
||||
|
||||
return $order->get_order_number();
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the order is valid.
|
||||
*
|
||||
* @param bool|WC_Order|WC_Order_Refund $order Order object.
|
||||
* @return bool True if the order is valid, false otherwise.
|
||||
*/
|
||||
protected function is_valid_order( $order ) {
|
||||
return $order instanceof \WC_Order || $order instanceof \WC_Order_Refund;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the order total with the related currency formatting.
|
||||
* Returns the parent order total if the order is actually a refund.
|
||||
*
|
||||
* @param int $order_id Order ID.
|
||||
* @return string|null The Order Number or null if the order doesn't exist.
|
||||
*/
|
||||
protected function get_total_formatted( $order_id ) {
|
||||
$order = wc_get_order( $order_id );
|
||||
|
||||
if ( ! $this->is_valid_order( $order ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ( 'shop_order_refund' === $order->get_type() ) {
|
||||
$order = wc_get_order( $order->get_parent_id() );
|
||||
|
||||
if ( ! $this->is_valid_order( $order ) ) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return wp_strip_all_tags( html_entity_decode( $order->get_formatted_order_total() ), true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get order statuses without prefixes.
|
||||
* Includes unregistered statuses that have been marked "actionable".
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_order_statuses() {
|
||||
// Allow all statuses selected as "actionable" - this may include unregistered statuses.
|
||||
// See: https://github.com/woocommerce/woocommerce-admin/issues/5592.
|
||||
$actionable_statuses = get_option( 'woocommerce_actionable_order_statuses', array() );
|
||||
|
||||
// See WC_REST_Orders_V2_Controller::get_collection_params() re: any/trash statuses.
|
||||
$registered_statuses = array_merge( array( 'any', 'trash' ), array_keys( self::get_order_status_labels() ) );
|
||||
|
||||
// Merge the status arrays (using flip to avoid array_unique()).
|
||||
$allowed_statuses = array_keys( array_merge( array_flip( $registered_statuses ), array_flip( $actionable_statuses ) ) );
|
||||
|
||||
return $allowed_statuses;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get order statuses (and labels) without prefixes.
|
||||
*
|
||||
* @internal
|
||||
* @return array
|
||||
*/
|
||||
public static function get_order_status_labels() {
|
||||
$order_statuses = array();
|
||||
|
||||
foreach ( wc_get_order_statuses() as $key => $label ) {
|
||||
$new_key = str_replace( 'wc-', '', $key );
|
||||
$order_statuses[ $new_key ] = $label;
|
||||
}
|
||||
|
||||
return $order_statuses;
|
||||
}
|
||||
}
|
||||
@@ -9,16 +9,19 @@ namespace Automattic\WooCommerce\Admin\API\Reports\Orders;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
use Automattic\WooCommerce\Admin\API\Reports\Controller as ReportsController;
|
||||
use Automattic\WooCommerce\Admin\API\Reports\ExportableInterface;
|
||||
use Automattic\WooCommerce\Admin\API\Reports\GenericController;
|
||||
use Automattic\WooCommerce\Admin\API\Reports\OrderAwareControllerTrait;
|
||||
|
||||
/**
|
||||
* REST API Reports orders controller class.
|
||||
*
|
||||
* @internal
|
||||
* @extends \Automattic\WooCommerce\Admin\API\Reports\Controller
|
||||
* @extends \Automattic\WooCommerce\Admin\API\Reports\GenericController
|
||||
*/
|
||||
class Controller extends ReportsController implements ExportableInterface {
|
||||
class Controller extends GenericController implements ExportableInterface {
|
||||
|
||||
use OrderAwareControllerTrait;
|
||||
|
||||
/**
|
||||
* Route base.
|
||||
@@ -27,6 +30,19 @@ class Controller extends ReportsController implements ExportableInterface {
|
||||
*/
|
||||
protected $rest_base = 'reports/orders';
|
||||
|
||||
/**
|
||||
* Get data from Query.
|
||||
*
|
||||
* @override GenericController::get_datastore_data()
|
||||
*
|
||||
* @param array $query_args Query arguments.
|
||||
* @return mixed Results from the data store.
|
||||
*/
|
||||
protected function get_datastore_data( $query_args = array() ) {
|
||||
$query = new Query( $query_args );
|
||||
return $query->get_data();
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps query arguments from the REST request.
|
||||
*
|
||||
@@ -65,50 +81,17 @@ class Controller extends ReportsController implements ExportableInterface {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all reports.
|
||||
* Prepare a report data item for serialization.
|
||||
*
|
||||
* @param WP_REST_Request $request Request data.
|
||||
* @return array|WP_Error
|
||||
*/
|
||||
public function get_items( $request ) {
|
||||
$query_args = $this->prepare_reports_query( $request );
|
||||
$orders_query = new Query( $query_args );
|
||||
$report_data = $orders_query->get_data();
|
||||
|
||||
$data = array();
|
||||
|
||||
foreach ( $report_data->data as $orders_data ) {
|
||||
$orders_data['order_number'] = $this->get_order_number( $orders_data['order_id'] );
|
||||
$orders_data['total_formatted'] = $this->get_total_formatted( $orders_data['order_id'] );
|
||||
$item = $this->prepare_item_for_response( $orders_data, $request );
|
||||
$data[] = $this->prepare_response_for_collection( $item );
|
||||
}
|
||||
|
||||
return $this->add_pagination_headers(
|
||||
$request,
|
||||
$data,
|
||||
(int) $report_data->total,
|
||||
(int) $report_data->page_no,
|
||||
(int) $report_data->pages
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare a report object for serialization.
|
||||
*
|
||||
* @param stdClass $report Report data.
|
||||
* @param WP_REST_Request $request Request object.
|
||||
* @return WP_REST_Response
|
||||
* @param array $report 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( $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 );
|
||||
|
||||
$report['order_number'] = $this->get_order_number( $report['order_id'] );
|
||||
$report['total_formatted'] = $this->get_total_formatted( $report['order_id'] );
|
||||
// Wrap the data in a response object.
|
||||
$response = rest_ensure_response( $data );
|
||||
$response = parent::prepare_item_for_response( $report, $request );
|
||||
$response->add_links( $this->prepare_links( $report ) );
|
||||
|
||||
/**
|
||||
@@ -248,54 +231,12 @@ class Controller extends ReportsController implements ExportableInterface {
|
||||
* @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' => 0,
|
||||
'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',
|
||||
'num_items_sold',
|
||||
'net_total',
|
||||
),
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
$params = parent::get_collection_params();
|
||||
$params['per_page']['minimum'] = 0;
|
||||
$params['orderby']['enum'] = array(
|
||||
'date',
|
||||
'num_items_sold',
|
||||
'net_total',
|
||||
);
|
||||
$params['product_includes'] = array(
|
||||
'description' => __( 'Limit result set to items that have the specified product(s) assigned.', 'woocommerce' ),
|
||||
@@ -464,12 +405,6 @@ class Controller extends ReportsController implements ExportableInterface {
|
||||
'default' => array(),
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
);
|
||||
$params['force_cache_refresh'] = array(
|
||||
'description' => __( 'Force retrieval of fresh data instead of from the cache.', 'woocommerce' ),
|
||||
'type' => 'boolean',
|
||||
'sanitize_callback' => 'wp_validate_boolean',
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
);
|
||||
|
||||
return $params;
|
||||
}
|
||||
|
||||
@@ -14,7 +14,6 @@ use Automattic\WooCommerce\Admin\API\Reports\DataStore as ReportsDataStore;
|
||||
use Automattic\WooCommerce\Admin\API\Reports\DataStoreInterface;
|
||||
use Automattic\WooCommerce\Admin\API\Reports\SqlQuery;
|
||||
use Automattic\WooCommerce\Admin\API\Reports\Cache;
|
||||
use Automattic\WooCommerce\Admin\API\Reports\TimeInterval;
|
||||
|
||||
|
||||
/**
|
||||
@@ -25,6 +24,8 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||
|
||||
/**
|
||||
* Dynamically sets the date column name based on configuration
|
||||
*
|
||||
* @override ReportsDataStore::__construct()
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->date_column_name = get_option( 'woocommerce_date_type', 'date_paid' );
|
||||
@@ -34,6 +35,8 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||
/**
|
||||
* Table used to get the data.
|
||||
*
|
||||
* @override ReportsDataStore::$table_name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected static $table_name = 'wc_order_stats';
|
||||
@@ -41,6 +44,8 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||
/**
|
||||
* Cache identifier.
|
||||
*
|
||||
* @override ReportsDataStore::$cache_key
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $cache_key = 'orders';
|
||||
@@ -48,6 +53,8 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||
/**
|
||||
* Mapping columns to data type to return correct response types.
|
||||
*
|
||||
* @override ReportsDataStore::$column_types
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $column_types = array(
|
||||
@@ -66,12 +73,16 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||
/**
|
||||
* Data store context used to pass to filters.
|
||||
*
|
||||
* @override ReportsDataStore::$context
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $context = 'orders';
|
||||
|
||||
/**
|
||||
* Assign report columns once full table name has been assigned.
|
||||
*
|
||||
* @override ReportsDataStore::assign_report_columns()
|
||||
*/
|
||||
protected function assign_report_columns() {
|
||||
$table_name = self::get_db_table_name();
|
||||
@@ -213,117 +224,118 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the report data based on parameters supplied by the user.
|
||||
* Get the default query arguments to be used by get_data().
|
||||
* These defaults are only partially applied when used via REST API, as that has its own defaults.
|
||||
*
|
||||
* @param array $query_args Query parameters.
|
||||
* @return stdClass|WP_Error Data.
|
||||
* @override ReportsDataStore::get_default_query_vars()
|
||||
*
|
||||
* @return array Query parameters.
|
||||
*/
|
||||
public function get_data( $query_args ) {
|
||||
public function get_default_query_vars() {
|
||||
$defaults = array_merge(
|
||||
parent::get_default_query_vars(),
|
||||
array(
|
||||
'orderby' => $this->date_column_name,
|
||||
'product_includes' => array(),
|
||||
'product_excludes' => array(),
|
||||
'coupon_includes' => array(),
|
||||
'coupon_excludes' => array(),
|
||||
'tax_rate_includes' => array(),
|
||||
'tax_rate_excludes' => array(),
|
||||
'customer_type' => null,
|
||||
'status_is' => array(),
|
||||
'extended_info' => false,
|
||||
'refunds' => null,
|
||||
'order_includes' => array(),
|
||||
'order_excludes' => array(),
|
||||
)
|
||||
);
|
||||
|
||||
return $defaults;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the report data based on normalized parameters.
|
||||
* Will be called by `get_data` if there is no data in cache.
|
||||
*
|
||||
* @override ReportsDataStore::get_noncached_data()
|
||||
*
|
||||
* @see get_data
|
||||
* @param array $query_args Query parameters.
|
||||
* @return stdClass|WP_Error Data object `{ totals: *, intervals: array, total: int, pages: int, page_no: int }`, or error.
|
||||
*/
|
||||
public function get_noncached_data( $query_args ) {
|
||||
global $wpdb;
|
||||
|
||||
// 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' => $this->date_column_name,
|
||||
'before' => TimeInterval::default_before(),
|
||||
'after' => TimeInterval::default_after(),
|
||||
'fields' => '*',
|
||||
'product_includes' => array(),
|
||||
'product_excludes' => array(),
|
||||
'coupon_includes' => array(),
|
||||
'coupon_excludes' => array(),
|
||||
'tax_rate_includes' => array(),
|
||||
'tax_rate_excludes' => array(),
|
||||
'customer_type' => null,
|
||||
'status_is' => array(),
|
||||
'extended_info' => false,
|
||||
'refunds' => null,
|
||||
'order_includes' => array(),
|
||||
'order_excludes' => array(),
|
||||
$this->initialize_queries();
|
||||
|
||||
$data = (object) array(
|
||||
'data' => array(),
|
||||
'total' => 0,
|
||||
'pages' => 0,
|
||||
'page_no' => 0,
|
||||
);
|
||||
$query_args = wp_parse_args( $query_args, $defaults );
|
||||
$this->normalize_timezones( $query_args, $defaults );
|
||||
|
||||
/*
|
||||
* We need to get the cache key here because
|
||||
* parent::update_intervals_sql_params() modifies $query_args.
|
||||
*/
|
||||
$cache_key = $this->get_cache_key( $query_args );
|
||||
$data = $this->get_cached_data( $cache_key );
|
||||
|
||||
if ( false === $data ) {
|
||||
$this->initialize_queries();
|
||||
$selections = $this->selected_columns( $query_args );
|
||||
$params = $this->get_limit_params( $query_args );
|
||||
$this->add_sql_query_params( $query_args );
|
||||
/* phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared */
|
||||
$db_records_count = (int) $wpdb->get_var(
|
||||
"SELECT COUNT( DISTINCT tt.order_id ) FROM (
|
||||
{$this->subquery->get_query_statement()}
|
||||
) AS tt"
|
||||
);
|
||||
/* phpcs:enable */
|
||||
|
||||
if ( 0 === $params['per_page'] ) {
|
||||
$total_pages = 0;
|
||||
} else {
|
||||
$total_pages = (int) ceil( $db_records_count / $params['per_page'] );
|
||||
}
|
||||
if ( $query_args['page'] < 1 || $query_args['page'] > $total_pages ) {
|
||||
$data = (object) array(
|
||||
'data' => array(),
|
||||
'total' => 0,
|
||||
'total' => $db_records_count,
|
||||
'pages' => 0,
|
||||
'page_no' => 0,
|
||||
);
|
||||
|
||||
$selections = $this->selected_columns( $query_args );
|
||||
$params = $this->get_limit_params( $query_args );
|
||||
$this->add_sql_query_params( $query_args );
|
||||
/* phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared */
|
||||
$db_records_count = (int) $wpdb->get_var(
|
||||
"SELECT COUNT( DISTINCT tt.order_id ) FROM (
|
||||
{$this->subquery->get_query_statement()}
|
||||
) AS tt"
|
||||
);
|
||||
/* phpcs:enable */
|
||||
|
||||
if ( 0 === $params['per_page'] ) {
|
||||
$total_pages = 0;
|
||||
} else {
|
||||
$total_pages = (int) ceil( $db_records_count / $params['per_page'] );
|
||||
}
|
||||
if ( $query_args['page'] < 1 || $query_args['page'] > $total_pages ) {
|
||||
$data = (object) array(
|
||||
'data' => array(),
|
||||
'total' => $db_records_count,
|
||||
'pages' => 0,
|
||||
'page_no' => 0,
|
||||
);
|
||||
return $data;
|
||||
}
|
||||
|
||||
$this->subquery->clear_sql_clause( 'select' );
|
||||
$this->subquery->add_sql_clause( 'select', $selections );
|
||||
$this->subquery->add_sql_clause( 'order_by', $this->get_sql_clause( 'order_by' ) );
|
||||
$this->subquery->add_sql_clause( 'limit', $this->get_sql_clause( 'limit' ) );
|
||||
/* phpcs:disable WordPress.DB.PreparedSQL.NotPrepared */
|
||||
$orders_data = $wpdb->get_results(
|
||||
$this->subquery->get_query_statement(),
|
||||
ARRAY_A
|
||||
);
|
||||
/* phpcs:enable */
|
||||
|
||||
if ( null === $orders_data ) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
if ( $query_args['extended_info'] ) {
|
||||
$this->include_extended_info( $orders_data, $query_args );
|
||||
}
|
||||
|
||||
$orders_data = array_map( array( $this, 'cast_numbers' ), $orders_data );
|
||||
$data = (object) array(
|
||||
'data' => $orders_data,
|
||||
'total' => $db_records_count,
|
||||
'pages' => $total_pages,
|
||||
'page_no' => (int) $query_args['page'],
|
||||
);
|
||||
|
||||
$this->set_cached_data( $cache_key, $data );
|
||||
return $data;
|
||||
}
|
||||
|
||||
$this->subquery->clear_sql_clause( 'select' );
|
||||
$this->subquery->add_sql_clause( 'select', $selections );
|
||||
$this->subquery->add_sql_clause( 'order_by', $this->get_sql_clause( 'order_by' ) );
|
||||
$this->subquery->add_sql_clause( 'limit', $this->get_sql_clause( 'limit' ) );
|
||||
/* phpcs:disable WordPress.DB.PreparedSQL.NotPrepared */
|
||||
$orders_data = $wpdb->get_results(
|
||||
$this->subquery->get_query_statement(),
|
||||
ARRAY_A
|
||||
);
|
||||
/* phpcs:enable */
|
||||
|
||||
if ( null === $orders_data ) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
if ( $query_args['extended_info'] ) {
|
||||
$this->include_extended_info( $orders_data, $query_args );
|
||||
}
|
||||
|
||||
$orders_data = array_map( array( $this, 'cast_numbers' ), $orders_data );
|
||||
$data = (object) array(
|
||||
'data' => $orders_data,
|
||||
'total' => $db_records_count,
|
||||
'pages' => $total_pages,
|
||||
'page_no' => (int) $query_args['page'],
|
||||
);
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes order_by clause to match to SQL query.
|
||||
*
|
||||
* @override ReportsDataStore::normalize_order_by()
|
||||
*
|
||||
* @param string $order_by Order by option requeste by user.
|
||||
* @return string
|
||||
*/
|
||||
|
||||
@@ -19,24 +19,32 @@
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\API\Reports\Orders;
|
||||
|
||||
use Automattic\WooCommerce\Admin\API\Reports\GenericQuery;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
use Automattic\WooCommerce\Admin\API\Reports\Query as ReportsQuery;
|
||||
|
||||
/**
|
||||
* API\Reports\Orders\Query
|
||||
*/
|
||||
class Query extends ReportsQuery {
|
||||
class Query extends GenericQuery {
|
||||
|
||||
/**
|
||||
* Get order data based on the current query vars.
|
||||
* Specific query name.
|
||||
* Will be used to load the `report-{name}` data store,
|
||||
* and to call `woocommerce_analytics_{snake_case(name)}_*` filters.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $name = 'orders';
|
||||
|
||||
|
||||
/**
|
||||
* Get the default allowed query vars.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_data() {
|
||||
$args = apply_filters( 'woocommerce_analytics_orders_query_args', $this->get_query_vars() );
|
||||
$data_store = \WC_Data_Store::load( 'report-orders' );
|
||||
$results = $data_store->get_data( $args );
|
||||
return apply_filters( 'woocommerce_analytics_orders_select_query', $results, $args );
|
||||
protected function get_default_query_vars() {
|
||||
return \WC_Object_Query::get_default_query_vars();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,15 +9,19 @@ namespace Automattic\WooCommerce\Admin\API\Reports\Orders\Stats;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
use Automattic\WooCommerce\Admin\API\Reports\ParameterException;
|
||||
use Automattic\WooCommerce\Admin\API\Reports\GenericStatsController;
|
||||
use Automattic\WooCommerce\Admin\API\Reports\OrderAwareControllerTrait;
|
||||
use Automattic\WooCommerce\Admin\API\Reports\Orders\Stats\Query;
|
||||
|
||||
/**
|
||||
* REST API Reports orders stats controller class.
|
||||
*
|
||||
* @internal
|
||||
* @extends \Automattic\WooCommerce\Admin\API\Reports\Controller
|
||||
* @extends \Automattic\WooCommerce\Admin\API\Reports\GenericStatsController
|
||||
*/
|
||||
class Controller extends \Automattic\WooCommerce\Admin\API\Reports\Controller {
|
||||
class Controller extends GenericStatsController {
|
||||
|
||||
use OrderAwareControllerTrait;
|
||||
|
||||
/**
|
||||
* Route base.
|
||||
@@ -26,6 +30,19 @@ class Controller extends \Automattic\WooCommerce\Admin\API\Reports\Controller {
|
||||
*/
|
||||
protected $rest_base = 'reports/orders/stats';
|
||||
|
||||
/**
|
||||
* Get data from Query.
|
||||
*
|
||||
* @override GenericController::get_datastore_data()
|
||||
*
|
||||
* @param array $query_args Query arguments.
|
||||
* @return mixed Results from the data store.
|
||||
*/
|
||||
protected function get_datastore_data( $query_args = array() ) {
|
||||
$query = new Query( $query_args );
|
||||
return $query->get_data();
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps query arguments from the REST request.
|
||||
*
|
||||
@@ -70,55 +87,15 @@ class Controller extends \Automattic\WooCommerce\Admin\API\Reports\Controller {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all reports.
|
||||
* Prepare a report data item for serialization.
|
||||
*
|
||||
* @param WP_REST_Request $request Request data.
|
||||
* @return array|WP_Error
|
||||
*/
|
||||
public function get_items( $request ) {
|
||||
$query_args = $this->prepare_reports_query( $request );
|
||||
$orders_query = new Query( $query_args );
|
||||
try {
|
||||
$report_data = $orders_query->get_data();
|
||||
} catch ( ParameterException $e ) {
|
||||
return new \WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) );
|
||||
}
|
||||
|
||||
$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 );
|
||||
}
|
||||
|
||||
return $this->add_pagination_headers(
|
||||
$request,
|
||||
$out_data,
|
||||
(int) $report_data->total,
|
||||
(int) $report_data->page_no,
|
||||
(int) $report_data->pages
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare a report object for serialization.
|
||||
*
|
||||
* @param Array $report Report data.
|
||||
* @param Array $report 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( $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 = parent::prepare_item_for_response( $report, $request );
|
||||
|
||||
/**
|
||||
* Filter a report returned from the API.
|
||||
@@ -132,13 +109,15 @@ class Controller extends \Automattic\WooCommerce\Admin\API\Reports\Controller {
|
||||
return apply_filters( 'woocommerce_rest_prepare_report_orders_stats', $response, $report, $request );
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the Report's schema, conforming to JSON Schema.
|
||||
* Get the Report's item properties schema.
|
||||
* Will be used by `get_item_schema` as `totals` and `subtotals`.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_item_schema() {
|
||||
$data_values = array(
|
||||
protected function get_item_properties_schema() {
|
||||
return array(
|
||||
'net_revenue' => array(
|
||||
'description' => __( 'Net sales.', 'woocommerce' ),
|
||||
'type' => 'number',
|
||||
@@ -199,104 +178,19 @@ class Controller extends \Automattic\WooCommerce\Admin\API\Reports\Controller {
|
||||
'readonly' => true,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
$segments = array(
|
||||
'segments' => array(
|
||||
'description' => __( 'Reports data grouped by segment condition.', 'woocommerce' ),
|
||||
'type' => 'array',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
'items' => array(
|
||||
'type' => 'object',
|
||||
'properties' => array(
|
||||
'segment_id' => array(
|
||||
'description' => __( 'Segment identificator.', 'woocommerce' ),
|
||||
'type' => 'integer',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'subtotals' => array(
|
||||
'description' => __( 'Interval subtotals.', 'woocommerce' ),
|
||||
'type' => 'object',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
'properties' => $data_values,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
$totals = array_merge( $data_values, $segments );
|
||||
/**
|
||||
* Get the Report's schema, conforming to JSON Schema.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_item_schema() {
|
||||
$schema = parent::get_item_schema();
|
||||
$schema['title'] = 'report_orders_stats';
|
||||
|
||||
// Products is not shown in intervals.
|
||||
unset( $data_values['products'] );
|
||||
|
||||
$intervals = array_merge( $data_values, $segments );
|
||||
|
||||
$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' => $intervals,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
unset( $schema['properties']['intervals']['items']['properties']['subtotals']['properties']['products'] );
|
||||
|
||||
return $this->add_additional_fields_schema( $schema );
|
||||
}
|
||||
@@ -307,69 +201,12 @@ class Controller extends \Automattic\WooCommerce\Admin\API\Reports\Controller {
|
||||
* @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 = parent::get_collection_params();
|
||||
$params['orderby']['enum'] = array(
|
||||
'date',
|
||||
'net_revenue',
|
||||
'orders_count',
|
||||
'avg_order_value',
|
||||
);
|
||||
$params['match'] = array(
|
||||
'description' => __( 'Indicates whether all the conditions should be true for the resulting set, or if any one of them is sufficient. Match affects the following parameters: status_is, status_is_not, product_includes, product_excludes, coupon_includes, coupon_excludes, customer, categories', 'woocommerce' ),
|
||||
@@ -412,7 +249,7 @@ class Controller extends \Automattic\WooCommerce\Admin\API\Reports\Controller {
|
||||
'sanitize_callback' => 'wp_parse_id_list',
|
||||
|
||||
);
|
||||
$params['product_excludes'] = array(
|
||||
$params['product_excludes'] = array(
|
||||
'description' => __( 'Limit result set to items that don\'t have the specified product(s) assigned.', 'woocommerce' ),
|
||||
'type' => 'array',
|
||||
'items' => array(
|
||||
@@ -421,7 +258,8 @@ class Controller extends \Automattic\WooCommerce\Admin\API\Reports\Controller {
|
||||
'default' => array(),
|
||||
'sanitize_callback' => 'wp_parse_id_list',
|
||||
);
|
||||
$params['variation_includes'] = array(
|
||||
// Split assignments for PHPCS complaining on aligned.
|
||||
$params['variation_includes'] = array(
|
||||
'description' => __( 'Limit result set to items that have the specified variation(s) assigned.', 'woocommerce' ),
|
||||
'type' => 'array',
|
||||
'items' => array(
|
||||
@@ -431,7 +269,7 @@ class Controller extends \Automattic\WooCommerce\Admin\API\Reports\Controller {
|
||||
'sanitize_callback' => 'wp_parse_id_list',
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
);
|
||||
$params['variation_excludes'] = array(
|
||||
$params['variation_excludes'] = array(
|
||||
'description' => __( 'Limit result set to items that don\'t have the specified variation(s) assigned.', 'woocommerce' ),
|
||||
'type' => 'array',
|
||||
'items' => array(
|
||||
@@ -441,7 +279,7 @@ class Controller extends \Automattic\WooCommerce\Admin\API\Reports\Controller {
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
'sanitize_callback' => 'wp_parse_id_list',
|
||||
);
|
||||
$params['coupon_includes'] = array(
|
||||
$params['coupon_includes'] = array(
|
||||
'description' => __( 'Limit result set to items that have the specified coupon(s) assigned.', 'woocommerce' ),
|
||||
'type' => 'array',
|
||||
'items' => array(
|
||||
@@ -450,7 +288,7 @@ class Controller extends \Automattic\WooCommerce\Admin\API\Reports\Controller {
|
||||
'default' => array(),
|
||||
'sanitize_callback' => 'wp_parse_id_list',
|
||||
);
|
||||
$params['coupon_excludes'] = array(
|
||||
$params['coupon_excludes'] = array(
|
||||
'description' => __( 'Limit result set to items that don\'t have the specified coupon(s) assigned.', 'woocommerce' ),
|
||||
'type' => 'array',
|
||||
'items' => array(
|
||||
@@ -459,7 +297,7 @@ class Controller extends \Automattic\WooCommerce\Admin\API\Reports\Controller {
|
||||
'default' => array(),
|
||||
'sanitize_callback' => 'wp_parse_id_list',
|
||||
);
|
||||
$params['tax_rate_includes'] = array(
|
||||
$params['tax_rate_includes'] = array(
|
||||
'description' => __( 'Limit result set to items that have the specified tax rate(s) assigned.', 'woocommerce' ),
|
||||
'type' => 'array',
|
||||
'items' => array(
|
||||
@@ -469,7 +307,7 @@ class Controller extends \Automattic\WooCommerce\Admin\API\Reports\Controller {
|
||||
'sanitize_callback' => 'wp_parse_id_list',
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
);
|
||||
$params['tax_rate_excludes'] = array(
|
||||
$params['tax_rate_excludes'] = array(
|
||||
'description' => __( 'Limit result set to items that don\'t have the specified tax rate(s) assigned.', 'woocommerce' ),
|
||||
'type' => 'array',
|
||||
'items' => array(
|
||||
@@ -479,7 +317,7 @@ class Controller extends \Automattic\WooCommerce\Admin\API\Reports\Controller {
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
'sanitize_callback' => 'wp_parse_id_list',
|
||||
);
|
||||
$params['customer'] = array(
|
||||
$params['customer'] = array(
|
||||
'description' => __( 'Alias for customer_type (deprecated).', 'woocommerce' ),
|
||||
'type' => 'string',
|
||||
'enum' => array(
|
||||
@@ -488,7 +326,7 @@ class Controller extends \Automattic\WooCommerce\Admin\API\Reports\Controller {
|
||||
),
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
);
|
||||
$params['customer_type'] = array(
|
||||
$params['customer_type'] = array(
|
||||
'description' => __( 'Limit result set to orders that have the specified customer_type', 'woocommerce' ),
|
||||
'type' => 'string',
|
||||
'enum' => array(
|
||||
@@ -497,7 +335,7 @@ class Controller extends \Automattic\WooCommerce\Admin\API\Reports\Controller {
|
||||
),
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
);
|
||||
$params['refunds'] = array(
|
||||
$params['refunds'] = array(
|
||||
'description' => __( 'Limit result set to specific types of refunds.', 'woocommerce' ),
|
||||
'type' => 'string',
|
||||
'default' => '',
|
||||
@@ -510,7 +348,7 @@ class Controller extends \Automattic\WooCommerce\Admin\API\Reports\Controller {
|
||||
),
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
);
|
||||
$params['attribute_is'] = array(
|
||||
$params['attribute_is'] = array(
|
||||
'description' => __( 'Limit result set to orders that include products with the specified attributes.', 'woocommerce' ),
|
||||
'type' => 'array',
|
||||
'items' => array(
|
||||
@@ -519,7 +357,7 @@ class Controller extends \Automattic\WooCommerce\Admin\API\Reports\Controller {
|
||||
'default' => array(),
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
);
|
||||
$params['attribute_is_not'] = array(
|
||||
$params['attribute_is_not'] = array(
|
||||
'description' => __( 'Limit result set to orders that don\'t include products with the specified attributes.', 'woocommerce' ),
|
||||
'type' => 'array',
|
||||
'items' => array(
|
||||
@@ -528,7 +366,7 @@ class Controller extends \Automattic\WooCommerce\Admin\API\Reports\Controller {
|
||||
'default' => array(),
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
);
|
||||
$params['segmentby'] = array(
|
||||
$params['segmentby'] = array(
|
||||
'description' => __( 'Segment the response by additional constraint.', 'woocommerce' ),
|
||||
'type' => 'string',
|
||||
'enum' => array(
|
||||
@@ -540,21 +378,8 @@ class Controller extends \Automattic\WooCommerce\Admin\API\Reports\Controller {
|
||||
),
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
);
|
||||
$params['fields'] = array(
|
||||
'description' => __( 'Limit stats fields to the specified items.', 'woocommerce' ),
|
||||
'type' => 'array',
|
||||
'sanitize_callback' => 'wp_parse_slug_list',
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
'items' => array(
|
||||
'type' => 'string',
|
||||
),
|
||||
);
|
||||
$params['force_cache_refresh'] = array(
|
||||
'description' => __( 'Force retrieval of fresh data instead of from the cache.', 'woocommerce' ),
|
||||
'type' => 'boolean',
|
||||
'sanitize_callback' => 'wp_validate_boolean',
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
);
|
||||
unset( $params['intervals'] );
|
||||
unset( $params['fields'] );
|
||||
|
||||
return $params;
|
||||
}
|
||||
|
||||
@@ -14,15 +14,19 @@ use Automattic\WooCommerce\Admin\API\Reports\SqlQuery;
|
||||
use Automattic\WooCommerce\Admin\API\Reports\Cache as ReportsCache;
|
||||
use Automattic\WooCommerce\Admin\API\Reports\Customers\DataStore as CustomersDataStore;
|
||||
use Automattic\WooCommerce\Utilities\OrderUtil;
|
||||
use Automattic\WooCommerce\Admin\API\Reports\StatsDataStoreTrait;
|
||||
|
||||
/**
|
||||
* API\Reports\Orders\Stats\DataStore.
|
||||
*/
|
||||
class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||
use StatsDataStoreTrait;
|
||||
|
||||
/**
|
||||
* Table used to get the data.
|
||||
*
|
||||
* @override ReportsDataStore::$table_name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected static $table_name = 'wc_order_stats';
|
||||
@@ -35,6 +39,8 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||
/**
|
||||
* Cache identifier.
|
||||
*
|
||||
* @override ReportsDataStore::$cache_key
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $cache_key = 'orders_stats';
|
||||
@@ -42,6 +48,8 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||
/**
|
||||
* Type for each column to cast values correctly later.
|
||||
*
|
||||
* @override ReportsDataStore::$column_types
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $column_types = array(
|
||||
@@ -65,12 +73,16 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||
/**
|
||||
* Data store context used to pass to filters.
|
||||
*
|
||||
* @override ReportsDataStore::$context
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $context = 'orders_stats';
|
||||
|
||||
/**
|
||||
* Dynamically sets the date column name based on configuration
|
||||
*
|
||||
* @override ReportsDataStore::__construct()
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->date_column_name = get_option( 'woocommerce_date_type', 'date_paid' );
|
||||
@@ -79,6 +91,8 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||
|
||||
/**
|
||||
* Assign report columns once full table name has been assigned.
|
||||
*
|
||||
* @override ReportsDataStore::assign_report_columns()
|
||||
*/
|
||||
protected function assign_report_columns() {
|
||||
$table_name = self::get_db_table_name();
|
||||
@@ -260,176 +274,161 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the report data based on parameters supplied by the user.
|
||||
* Get the default query arguments to be used by get_data().
|
||||
* These defaults are only partially applied when used via REST API, as that has its own defaults.
|
||||
*
|
||||
* @param array $query_args Query parameters.
|
||||
* @return stdClass|WP_Error Data.
|
||||
* @override ReportsDataStore::get_default_query_vars()
|
||||
*
|
||||
* @return array Query parameters.
|
||||
*/
|
||||
public function get_data( $query_args ) {
|
||||
public function get_default_query_vars() {
|
||||
$defaults = array_merge(
|
||||
parent::get_default_query_vars(),
|
||||
array(
|
||||
'interval' => 'week',
|
||||
'segmentby' => '',
|
||||
|
||||
'match' => 'all',
|
||||
'status_is' => array(),
|
||||
'status_is_not' => array(),
|
||||
'product_includes' => array(),
|
||||
'product_excludes' => array(),
|
||||
'coupon_includes' => array(),
|
||||
'coupon_excludes' => array(),
|
||||
'tax_rate_includes' => array(),
|
||||
'tax_rate_excludes' => array(),
|
||||
'customer_type' => '',
|
||||
'category_includes' => array(),
|
||||
)
|
||||
);
|
||||
|
||||
return $defaults;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the report data based on normalized parameters.
|
||||
* Will be called by `get_data` if there is no data in cache.
|
||||
*
|
||||
* @override ReportsDataStore::get_noncached_stats_data()
|
||||
*
|
||||
* @see get_data
|
||||
* @see get_noncached_stats_data
|
||||
* @param array $query_args Query parameters.
|
||||
* @param array $params Query limit parameters.
|
||||
* @param stdClass $data Reference to the data object to fill.
|
||||
* @param int $expected_interval_count Number of expected intervals.
|
||||
* @return stdClass|WP_Error Data object `{ totals: *, intervals: array, total: int, pages: int, page_no: int }`, or error.
|
||||
*/
|
||||
public function get_noncached_stats_data( $query_args, $params, &$data, $expected_interval_count ) {
|
||||
global $wpdb;
|
||||
|
||||
$table_name = self::get_db_table_name();
|
||||
|
||||
// These defaults are only applied when not using REST API, as the API has its own defaults that overwrite these for most values (except before, after, etc).
|
||||
$defaults = array(
|
||||
'per_page' => get_option( 'posts_per_page' ),
|
||||
'page' => 1,
|
||||
'order' => 'DESC',
|
||||
'orderby' => 'date',
|
||||
'before' => TimeInterval::default_before(),
|
||||
'after' => TimeInterval::default_after(),
|
||||
'interval' => 'week',
|
||||
'fields' => '*',
|
||||
'segmentby' => '',
|
||||
|
||||
'match' => 'all',
|
||||
'status_is' => array(),
|
||||
'status_is_not' => array(),
|
||||
'product_includes' => array(),
|
||||
'product_excludes' => array(),
|
||||
'coupon_includes' => array(),
|
||||
'coupon_excludes' => array(),
|
||||
'tax_rate_includes' => array(),
|
||||
'tax_rate_excludes' => array(),
|
||||
'customer_type' => '',
|
||||
'category_includes' => array(),
|
||||
);
|
||||
$query_args = wp_parse_args( $query_args, $defaults );
|
||||
$this->normalize_timezones( $query_args, $defaults );
|
||||
|
||||
/*
|
||||
* We need to get the cache key here because
|
||||
* parent::update_intervals_sql_params() modifies $query_args.
|
||||
*/
|
||||
$cache_key = $this->get_cache_key( $query_args );
|
||||
$data = $this->get_cached_data( $cache_key );
|
||||
|
||||
if ( isset( $query_args['date_type'] ) ) {
|
||||
$this->date_column_name = $query_args['date_type'];
|
||||
}
|
||||
|
||||
if ( false === $data ) {
|
||||
$this->initialize_queries();
|
||||
$this->initialize_queries();
|
||||
|
||||
$data = (object) array(
|
||||
'totals' => (object) array(),
|
||||
'intervals' => (object) array(),
|
||||
'total' => 0,
|
||||
'pages' => 0,
|
||||
'page_no' => 0,
|
||||
);
|
||||
$selections = $this->selected_columns( $query_args );
|
||||
$this->add_time_period_sql_params( $query_args, $table_name );
|
||||
$this->add_intervals_sql_params( $query_args, $table_name );
|
||||
$this->add_order_by_sql_params( $query_args );
|
||||
$where_time = $this->get_sql_clause( 'where_time' );
|
||||
$params = $this->get_limit_sql_params( $query_args );
|
||||
$coupon_join = "LEFT JOIN (
|
||||
SELECT
|
||||
order_id,
|
||||
SUM(discount_amount) AS discount_amount,
|
||||
COUNT(DISTINCT coupon_id) AS coupons_count
|
||||
FROM
|
||||
{$wpdb->prefix}wc_order_coupon_lookup
|
||||
GROUP BY
|
||||
order_id
|
||||
) order_coupon_lookup
|
||||
ON order_coupon_lookup.order_id = {$wpdb->prefix}wc_order_stats.order_id";
|
||||
|
||||
$selections = $this->selected_columns( $query_args );
|
||||
$this->add_time_period_sql_params( $query_args, $table_name );
|
||||
$this->add_intervals_sql_params( $query_args, $table_name );
|
||||
$this->add_order_by_sql_params( $query_args );
|
||||
$where_time = $this->get_sql_clause( 'where_time' );
|
||||
$params = $this->get_limit_sql_params( $query_args );
|
||||
$coupon_join = "LEFT JOIN (
|
||||
SELECT
|
||||
order_id,
|
||||
SUM(discount_amount) AS discount_amount,
|
||||
COUNT(DISTINCT coupon_id) AS coupons_count
|
||||
FROM
|
||||
{$wpdb->prefix}wc_order_coupon_lookup
|
||||
GROUP BY
|
||||
order_id
|
||||
) order_coupon_lookup
|
||||
ON order_coupon_lookup.order_id = {$wpdb->prefix}wc_order_stats.order_id";
|
||||
|
||||
// Additional filtering for Orders report.
|
||||
$this->orders_stats_sql_filter( $query_args );
|
||||
$this->total_query->add_sql_clause( 'select', $selections );
|
||||
$this->total_query->add_sql_clause( 'left_join', $coupon_join );
|
||||
$this->total_query->add_sql_clause( 'where_time', $where_time );
|
||||
$totals = $wpdb->get_results(
|
||||
$this->total_query->get_query_statement(),
|
||||
ARRAY_A
|
||||
); // phpcs:ignore cache ok, DB call ok, unprepared SQL ok.
|
||||
if ( null === $totals ) {
|
||||
return new \WP_Error( 'woocommerce_analytics_revenue_result_failed', __( 'Sorry, fetching revenue data failed.', 'woocommerce' ) );
|
||||
}
|
||||
|
||||
// phpcs:ignore Generic.Commenting.Todo.TaskFound
|
||||
// @todo Remove these assignements when refactoring segmenter classes to use query objects.
|
||||
$totals_query = array(
|
||||
'from_clause' => $this->total_query->get_sql_clause( 'join' ),
|
||||
'where_time_clause' => $where_time,
|
||||
'where_clause' => $this->total_query->get_sql_clause( 'where' ),
|
||||
);
|
||||
$intervals_query = array(
|
||||
'select_clause' => $this->get_sql_clause( 'select' ),
|
||||
'from_clause' => $this->interval_query->get_sql_clause( 'join' ),
|
||||
'where_time_clause' => $where_time,
|
||||
'where_clause' => $this->interval_query->get_sql_clause( 'where' ),
|
||||
'limit' => $this->get_sql_clause( 'limit' ),
|
||||
);
|
||||
|
||||
$unique_products = $this->get_unique_product_count( $totals_query['from_clause'], $totals_query['where_time_clause'], $totals_query['where_clause'] );
|
||||
$totals[0]['products'] = $unique_products;
|
||||
$segmenter = new Segmenter( $query_args, $this->report_columns );
|
||||
$unique_coupons = $this->get_unique_coupon_count( $totals_query['from_clause'], $totals_query['where_time_clause'], $totals_query['where_clause'] );
|
||||
$totals[0]['coupons_count'] = $unique_coupons;
|
||||
$totals[0]['segments'] = $segmenter->get_totals_segments( $totals_query, $table_name );
|
||||
$totals = (object) $this->cast_numbers( $totals[0] );
|
||||
|
||||
$this->interval_query->add_sql_clause( 'select', $this->get_sql_clause( 'select' ) . ' AS time_interval' );
|
||||
$this->interval_query->add_sql_clause( 'left_join', $coupon_join );
|
||||
$this->interval_query->add_sql_clause( 'where_time', $where_time );
|
||||
$db_intervals = $wpdb->get_col(
|
||||
$this->interval_query->get_query_statement()
|
||||
); // phpcs:ignore cache ok, DB call ok, , unprepared SQL ok.
|
||||
|
||||
$db_interval_count = count( $db_intervals );
|
||||
$expected_interval_count = TimeInterval::intervals_between( $query_args['after'], $query_args['before'], $query_args['interval'] );
|
||||
$total_pages = (int) ceil( $expected_interval_count / $params['per_page'] );
|
||||
|
||||
if ( $query_args['page'] < 1 || $query_args['page'] > $total_pages ) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
$this->update_intervals_sql_params( $query_args, $db_interval_count, $expected_interval_count, $table_name );
|
||||
$this->interval_query->add_sql_clause( 'order_by', $this->get_sql_clause( 'order_by' ) );
|
||||
$this->interval_query->add_sql_clause( 'limit', $this->get_sql_clause( 'limit' ) );
|
||||
$this->interval_query->add_sql_clause( 'select', ", MAX({$table_name}.date_created) AS datetime_anchor" );
|
||||
if ( '' !== $selections ) {
|
||||
$this->interval_query->add_sql_clause( 'select', ', ' . $selections );
|
||||
}
|
||||
$intervals = $wpdb->get_results(
|
||||
$this->interval_query->get_query_statement(),
|
||||
ARRAY_A
|
||||
); // phpcs:ignore cache ok, DB call ok, unprepared SQL ok.
|
||||
|
||||
if ( null === $intervals ) {
|
||||
return new \WP_Error( 'woocommerce_analytics_revenue_result_failed', __( 'Sorry, fetching revenue data failed.', 'woocommerce' ) );
|
||||
}
|
||||
|
||||
if ( isset( $intervals[0] ) ) {
|
||||
$unique_coupons = $this->get_unique_coupon_count( $intervals_query['from_clause'], $intervals_query['where_time_clause'], $intervals_query['where_clause'], true );
|
||||
$intervals[0]['coupons_count'] = $unique_coupons;
|
||||
}
|
||||
|
||||
$data = (object) array(
|
||||
'totals' => $totals,
|
||||
'intervals' => $intervals,
|
||||
'total' => $expected_interval_count,
|
||||
'pages' => $total_pages,
|
||||
'page_no' => (int) $query_args['page'],
|
||||
);
|
||||
|
||||
if ( TimeInterval::intervals_missing( $expected_interval_count, $db_interval_count, $params['per_page'], $query_args['page'], $query_args['order'], $query_args['orderby'], count( $intervals ) ) ) {
|
||||
$this->fill_in_missing_intervals( $db_intervals, $query_args['adj_after'], $query_args['adj_before'], $query_args['interval'], $data );
|
||||
$this->sort_intervals( $data, $query_args['orderby'], $query_args['order'] );
|
||||
$this->remove_extra_records( $data, $query_args['page'], $params['per_page'], $db_interval_count, $expected_interval_count, $query_args['orderby'], $query_args['order'] );
|
||||
} else {
|
||||
$this->update_interval_boundary_dates( $query_args['after'], $query_args['before'], $query_args['interval'], $data->intervals );
|
||||
}
|
||||
$segmenter->add_intervals_segments( $data, $intervals_query, $table_name );
|
||||
$this->create_interval_subtotals( $data->intervals );
|
||||
|
||||
$this->set_cached_data( $cache_key, $data );
|
||||
// Additional filtering for Orders report.
|
||||
$this->orders_stats_sql_filter( $query_args );
|
||||
$this->total_query->add_sql_clause( 'select', $selections );
|
||||
$this->total_query->add_sql_clause( 'left_join', $coupon_join );
|
||||
$this->total_query->add_sql_clause( 'where_time', $where_time );
|
||||
$totals = $wpdb->get_results(
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- cache ok, DB call ok, unprepared SQL ok.
|
||||
$this->total_query->get_query_statement(),
|
||||
ARRAY_A
|
||||
);
|
||||
if ( null === $totals ) {
|
||||
return new \WP_Error( 'woocommerce_analytics_revenue_result_failed', __( 'Sorry, fetching revenue data failed.', 'woocommerce' ) );
|
||||
}
|
||||
|
||||
// phpcs:ignore Generic.Commenting.Todo.TaskFound
|
||||
// @todo Remove these assignements when refactoring segmenter classes to use query objects.
|
||||
$totals_query = array(
|
||||
'from_clause' => $this->total_query->get_sql_clause( 'join' ),
|
||||
'where_time_clause' => $where_time,
|
||||
'where_clause' => $this->total_query->get_sql_clause( 'where' ),
|
||||
);
|
||||
$intervals_query = array(
|
||||
'select_clause' => $this->get_sql_clause( 'select' ),
|
||||
'from_clause' => $this->interval_query->get_sql_clause( 'join' ),
|
||||
'where_time_clause' => $where_time,
|
||||
'where_clause' => $this->interval_query->get_sql_clause( 'where' ),
|
||||
'limit' => $this->get_sql_clause( 'limit' ),
|
||||
);
|
||||
|
||||
$unique_products = $this->get_unique_product_count( $totals_query['from_clause'], $totals_query['where_time_clause'], $totals_query['where_clause'] );
|
||||
$totals[0]['products'] = $unique_products;
|
||||
$segmenter = new Segmenter( $query_args, $this->report_columns );
|
||||
$unique_coupons = $this->get_unique_coupon_count( $totals_query['from_clause'], $totals_query['where_time_clause'], $totals_query['where_clause'] );
|
||||
$totals[0]['coupons_count'] = $unique_coupons;
|
||||
$totals[0]['segments'] = $segmenter->get_totals_segments( $totals_query, $table_name );
|
||||
$totals = (object) $this->cast_numbers( $totals[0] );
|
||||
|
||||
$this->interval_query->add_sql_clause( 'select', $this->get_sql_clause( 'select' ) . ' AS time_interval' );
|
||||
$this->interval_query->add_sql_clause( 'left_join', $coupon_join );
|
||||
$this->interval_query->add_sql_clause( 'where_time', $where_time );
|
||||
$db_intervals = $wpdb->get_col(
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- cache ok, DB call ok, , unprepared SQL ok.
|
||||
$this->interval_query->get_query_statement()
|
||||
);
|
||||
|
||||
$db_interval_count = count( $db_intervals );
|
||||
|
||||
$this->update_intervals_sql_params( $query_args, $db_interval_count, $expected_interval_count, $table_name );
|
||||
$this->interval_query->add_sql_clause( 'order_by', $this->get_sql_clause( 'order_by' ) );
|
||||
$this->interval_query->add_sql_clause( 'limit', $this->get_sql_clause( 'limit' ) );
|
||||
$this->interval_query->add_sql_clause( 'select', ", MAX({$table_name}.date_created) AS datetime_anchor" );
|
||||
if ( '' !== $selections ) {
|
||||
$this->interval_query->add_sql_clause( 'select', ', ' . $selections );
|
||||
}
|
||||
$intervals = $wpdb->get_results(
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- cache ok, DB call ok, , unprepared SQL ok.
|
||||
$this->interval_query->get_query_statement(),
|
||||
ARRAY_A
|
||||
);
|
||||
|
||||
if ( null === $intervals ) {
|
||||
return new \WP_Error( 'woocommerce_analytics_revenue_result_failed', __( 'Sorry, fetching revenue data failed.', 'woocommerce' ) );
|
||||
}
|
||||
|
||||
if ( isset( $intervals[0] ) ) {
|
||||
$unique_coupons = $this->get_unique_coupon_count( $intervals_query['from_clause'], $intervals_query['where_time_clause'], $intervals_query['where_clause'], true );
|
||||
$intervals[0]['coupons_count'] = $unique_coupons;
|
||||
}
|
||||
|
||||
$data->totals = $totals;
|
||||
$data->intervals = $intervals;
|
||||
|
||||
if ( TimeInterval::intervals_missing( $expected_interval_count, $db_interval_count, $params['per_page'], $query_args['page'], $query_args['order'], $query_args['orderby'], count( $intervals ) ) ) {
|
||||
$this->fill_in_missing_intervals( $db_intervals, $query_args['adj_after'], $query_args['adj_before'], $query_args['interval'], $data );
|
||||
$this->sort_intervals( $data, $query_args['orderby'], $query_args['order'] );
|
||||
$this->remove_extra_records( $data, $query_args['page'], $params['per_page'], $db_interval_count, $expected_interval_count, $query_args['orderby'], $query_args['order'] );
|
||||
} else {
|
||||
$this->update_interval_boundary_dates( $query_args['after'], $query_args['before'], $query_args['interval'], $data->intervals );
|
||||
}
|
||||
$segmenter->add_intervals_segments( $data, $intervals_query, $table_name );
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
@@ -729,18 +728,4 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize query objects.
|
||||
*/
|
||||
protected function initialize_queries() {
|
||||
$this->clear_all_clauses();
|
||||
unset( $this->subquery );
|
||||
$this->total_query = new SqlQuery( $this->context . '_total' );
|
||||
$this->total_query->add_sql_clause( 'from', self::get_db_table_name() );
|
||||
|
||||
$this->interval_query = new SqlQuery( $this->context . '_interval' );
|
||||
$this->interval_query->add_sql_clause( 'from', self::get_db_table_name() );
|
||||
$this->interval_query->add_sql_clause( 'group_by', 'time_interval' );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,14 +17,23 @@
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\API\Reports\Orders\Stats;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
use Automattic\WooCommerce\Admin\API\Reports\GenericQuery;
|
||||
|
||||
use Automattic\WooCommerce\Admin\API\Reports\Query as ReportsQuery;
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* API\Reports\Orders\Stats\Query
|
||||
*/
|
||||
class Query extends ReportsQuery {
|
||||
class Query extends GenericQuery {
|
||||
|
||||
/**
|
||||
* Specific query name.
|
||||
* Will be used to load the `report-{name}` data store,
|
||||
* and to call `woocommerce_analytics_{snake_case(name)}_*` filters.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $name = 'orders-stats';
|
||||
|
||||
/**
|
||||
* Valid fields for Orders report.
|
||||
@@ -45,17 +54,4 @@ class Query extends ReportsQuery {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get revenue data based on the current query vars.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_data() {
|
||||
$args = apply_filters( 'woocommerce_analytics_orders_stats_query_args', $this->get_query_vars() );
|
||||
|
||||
$data_store = \WC_Data_Store::load( 'report-orders-stats' );
|
||||
$results = $data_store->get_data( $args );
|
||||
return apply_filters( 'woocommerce_analytics_orders_stats_select_query', $results, $args );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -452,10 +452,10 @@ class Controller extends GenericController {
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare a report object for serialization.
|
||||
* Prepare a report data item for serialization.
|
||||
*
|
||||
* @param array $stat_data Report data.
|
||||
* @param WP_REST_Request $request Request object.
|
||||
* @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 ) {
|
||||
@@ -527,8 +527,13 @@ class Controller extends GenericController {
|
||||
*/
|
||||
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.
|
||||
$index = array_search( $chart, $data['general']->visits->fields, true );
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -9,8 +9,9 @@ namespace Automattic\WooCommerce\Admin\API\Reports\Products;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
use Automattic\WooCommerce\Admin\API\Reports\GenericController;
|
||||
use Automattic\WooCommerce\Admin\API\Reports\ExportableInterface;
|
||||
use Automattic\WooCommerce\Admin\API\Reports\GenericController;
|
||||
use Automattic\WooCommerce\Admin\API\Reports\GenericQuery;
|
||||
use WP_REST_Request;
|
||||
use WP_REST_Response;
|
||||
|
||||
@@ -41,51 +42,22 @@ class Controller extends GenericController implements ExportableInterface {
|
||||
);
|
||||
|
||||
/**
|
||||
* Get items.
|
||||
* Get data from `'products'` Query.
|
||||
*
|
||||
* @param WP_REST_Request $request Request data.
|
||||
* @override GenericController::get_datastore_data()
|
||||
*
|
||||
* @return array|WP_Error
|
||||
* @param array $query_args Query arguments.
|
||||
* @return mixed Results from the data store.
|
||||
*/
|
||||
public function get_items( $request ) {
|
||||
$args = array();
|
||||
$registered = array_keys( $this->get_collection_params() );
|
||||
foreach ( $registered as $param_name ) {
|
||||
if ( isset( $request[ $param_name ] ) ) {
|
||||
if ( isset( $this->param_mapping[ $param_name ] ) ) {
|
||||
$args[ $this->param_mapping[ $param_name ] ] = $request[ $param_name ];
|
||||
} else {
|
||||
$args[ $param_name ] = $request[ $param_name ];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$reports = new 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 );
|
||||
if ( isset( $item->data['extended_info']['name'] ) ) {
|
||||
$item->data['extended_info']['name'] = wp_strip_all_tags( $item->data['extended_info']['name'] );
|
||||
}
|
||||
$data[] = $this->prepare_response_for_collection( $item );
|
||||
}
|
||||
|
||||
return $this->add_pagination_headers(
|
||||
$request,
|
||||
$data,
|
||||
(int) $products_data->total,
|
||||
(int) $products_data->page_no,
|
||||
(int) $products_data->pages
|
||||
);
|
||||
protected function get_datastore_data( $query_args = array() ) {
|
||||
$query = new GenericQuery( $query_args, 'products' );
|
||||
return $query->get_data();
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare a report object for serialization.
|
||||
* Prepare a report data item for serialization.
|
||||
*
|
||||
* @param Array $report Report data.
|
||||
* @param Array $report Report data item as returned from Data Store.
|
||||
* @param WP_REST_Request $request Request object.
|
||||
* @return WP_REST_Response
|
||||
*/
|
||||
@@ -101,8 +73,36 @@ class Controller extends GenericController implements ExportableInterface {
|
||||
* @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.
|
||||
*
|
||||
* @since 6.5.0
|
||||
*/
|
||||
return apply_filters( 'woocommerce_rest_prepare_report_products', $response, $report, $request );
|
||||
$filtered_response = apply_filters( 'woocommerce_rest_prepare_report_products', $response, $report, $request );
|
||||
if ( isset( $filtered_response->data['extended_info']['name'] ) ) {
|
||||
$filtered_response->data['extended_info']['name'] = wp_strip_all_tags( $filtered_response->data['extended_info']['name'] );
|
||||
}
|
||||
return $filtered_response;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Maps query arguments from the REST request.
|
||||
*
|
||||
* @param array $request Request array.
|
||||
* @return array
|
||||
*/
|
||||
protected function prepare_reports_query( $request ) {
|
||||
$args = array();
|
||||
$registered = array_keys( $this->get_collection_params() );
|
||||
foreach ( $registered as $param_name ) {
|
||||
if ( isset( $request[ $param_name ] ) ) {
|
||||
if ( isset( $this->param_mapping[ $param_name ] ) ) {
|
||||
$args[ $this->param_mapping[ $param_name ] ] = $request[ $param_name ];
|
||||
} else {
|
||||
$args[ $param_name ] = $request[ $param_name ];
|
||||
}
|
||||
}
|
||||
}
|
||||
return $args;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -21,6 +21,8 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||
/**
|
||||
* Table used to get the data.
|
||||
*
|
||||
* @override ReportsDataStore::$table_name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected static $table_name = 'wc_order_product_lookup';
|
||||
@@ -28,6 +30,8 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||
/**
|
||||
* Cache identifier.
|
||||
*
|
||||
* @override ReportsDataStore::$cache_key
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $cache_key = 'products';
|
||||
@@ -35,6 +39,8 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||
/**
|
||||
* Mapping columns to data type to return correct response types.
|
||||
*
|
||||
* @override ReportsDataStore::$column_types
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $column_types = array(
|
||||
@@ -79,12 +85,16 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||
/**
|
||||
* Data store context used to pass to filters.
|
||||
*
|
||||
* @override ReportsDataStore::$context
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $context = 'products';
|
||||
|
||||
/**
|
||||
* Assign report columns once full table name has been assigned.
|
||||
*
|
||||
* @override ReportsDataStore::assign_report_columns()
|
||||
*/
|
||||
protected function assign_report_columns() {
|
||||
$table_name = self::get_db_table_name();
|
||||
@@ -175,6 +185,8 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||
/**
|
||||
* Maps ordering specified by the user to columns in the database/fields in the data.
|
||||
*
|
||||
* @override ReportsDataStore::normalize_order_by()
|
||||
*
|
||||
* @param string $order_by Sorting criterion.
|
||||
* @return string
|
||||
*/
|
||||
@@ -256,122 +268,137 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||
/**
|
||||
* Returns the report data based on parameters supplied by the user.
|
||||
*
|
||||
* @override ReportsDataStore::get_data()
|
||||
*
|
||||
* @param array $query_args Query parameters.
|
||||
* @return stdClass|WP_Error Data.
|
||||
*/
|
||||
public function get_data( $query_args ) {
|
||||
$data = parent::get_data( $query_args );
|
||||
|
||||
/*
|
||||
* Do not cache extended info -- this is required to get the latest stock data.
|
||||
* `include_extended_info` checks only `extended_info` key,
|
||||
* so we don't need to bother about normalizing timestamps.
|
||||
*/
|
||||
$defaults = $this->get_default_query_vars();
|
||||
$query_args = wp_parse_args( $query_args, $defaults );
|
||||
$this->include_extended_info( $data->data, $query_args );
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default query arguments to be used by get_data().
|
||||
* These defaults are only partially applied when used via REST API, as that has its own defaults.
|
||||
*
|
||||
* @override ReportsDataStore::get_default_query_vars()
|
||||
*
|
||||
* @return array Query parameters.
|
||||
*/
|
||||
public function get_default_query_vars() {
|
||||
$defaults = parent::get_default_query_vars();
|
||||
$defaults['category_includes'] = array();
|
||||
$defaults['product_includes'] = array();
|
||||
$defaults['extended_info'] = false;
|
||||
|
||||
return $defaults;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the report data based on normalized parameters.
|
||||
* Will be called by `get_data` if there is no data in cache.
|
||||
*
|
||||
* @override ReportsDataStore::get_noncached_data()
|
||||
*
|
||||
* @see get_data
|
||||
* @param array $query_args Query parameters.
|
||||
* @return stdClass|WP_Error Data object `{ totals: *, intervals: array, total: int, pages: int, page_no: int }`, or error.
|
||||
*/
|
||||
public function get_noncached_data( $query_args ) {
|
||||
global $wpdb;
|
||||
|
||||
$table_name = self::get_db_table_name();
|
||||
|
||||
// These defaults are only partially applied when used via REST API, as that has its own defaults.
|
||||
$defaults = array(
|
||||
'per_page' => get_option( 'posts_per_page' ),
|
||||
'page' => 1,
|
||||
'order' => 'DESC',
|
||||
'orderby' => 'date',
|
||||
'before' => TimeInterval::default_before(),
|
||||
'after' => TimeInterval::default_after(),
|
||||
'fields' => '*',
|
||||
'category_includes' => array(),
|
||||
'product_includes' => array(),
|
||||
'extended_info' => false,
|
||||
$this->initialize_queries();
|
||||
|
||||
$data = (object) array(
|
||||
'data' => array(),
|
||||
'total' => 0,
|
||||
'pages' => 0,
|
||||
'page_no' => 0,
|
||||
);
|
||||
$query_args = wp_parse_args( $query_args, $defaults );
|
||||
$this->normalize_timezones( $query_args, $defaults );
|
||||
|
||||
/*
|
||||
* We need to get the cache key here because
|
||||
* parent::update_intervals_sql_params() modifies $query_args.
|
||||
*/
|
||||
$cache_key = $this->get_cache_key( $query_args );
|
||||
$data = $this->get_cached_data( $cache_key );
|
||||
$selections = $this->selected_columns( $query_args );
|
||||
$included_products = $this->get_included_products_array( $query_args );
|
||||
$params = $this->get_limit_params( $query_args );
|
||||
$this->add_sql_query_params( $query_args );
|
||||
|
||||
if ( false === $data ) {
|
||||
$this->initialize_queries();
|
||||
if ( count( $included_products ) > 0 ) {
|
||||
$filtered_products = array_diff( $included_products, array( '-1' ) );
|
||||
$total_results = count( $filtered_products );
|
||||
$total_pages = (int) ceil( $total_results / $params['per_page'] );
|
||||
|
||||
$data = (object) array(
|
||||
'data' => array(),
|
||||
'total' => 0,
|
||||
'pages' => 0,
|
||||
'page_no' => 0,
|
||||
);
|
||||
|
||||
$selections = $this->selected_columns( $query_args );
|
||||
$included_products = $this->get_included_products_array( $query_args );
|
||||
$params = $this->get_limit_params( $query_args );
|
||||
$this->add_sql_query_params( $query_args );
|
||||
|
||||
if ( count( $included_products ) > 0 ) {
|
||||
$filtered_products = array_diff( $included_products, array( '-1' ) );
|
||||
$total_results = count( $filtered_products );
|
||||
$total_pages = (int) ceil( $total_results / $params['per_page'] );
|
||||
|
||||
if ( 'date' === $query_args['orderby'] ) {
|
||||
$selections .= ", {$table_name}.date_created";
|
||||
}
|
||||
|
||||
$fields = $this->get_fields( $query_args );
|
||||
$join_selections = $this->format_join_selections( $fields, array( 'product_id' ) );
|
||||
$ids_table = $this->get_ids_table( $included_products, 'product_id' );
|
||||
|
||||
$this->subquery->clear_sql_clause( 'select' );
|
||||
$this->subquery->add_sql_clause( 'select', $selections );
|
||||
$this->add_sql_clause( 'select', $join_selections );
|
||||
$this->add_sql_clause( 'from', '(' );
|
||||
$this->add_sql_clause( 'from', $this->subquery->get_query_statement() );
|
||||
$this->add_sql_clause( 'from', ") AS {$table_name}" );
|
||||
$this->add_sql_clause(
|
||||
'right_join',
|
||||
"RIGHT JOIN ( {$ids_table} ) AS default_results
|
||||
ON default_results.product_id = {$table_name}.product_id"
|
||||
);
|
||||
$this->add_sql_clause( 'where', 'AND default_results.product_id != -1' );
|
||||
|
||||
$products_query = $this->get_query_statement();
|
||||
} else {
|
||||
$count_query = "SELECT COUNT(*) FROM (
|
||||
{$this->subquery->get_query_statement()}
|
||||
) AS tt";
|
||||
$db_records_count = (int) $wpdb->get_var(
|
||||
$count_query // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
);
|
||||
|
||||
$total_results = $db_records_count;
|
||||
$total_pages = (int) ceil( $db_records_count / $params['per_page'] );
|
||||
|
||||
if ( ( $query_args['page'] < 1 || $query_args['page'] > $total_pages ) ) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
$this->subquery->clear_sql_clause( 'select' );
|
||||
$this->subquery->add_sql_clause( 'select', $selections );
|
||||
$this->subquery->add_sql_clause( 'order_by', $this->get_sql_clause( 'order_by' ) );
|
||||
$this->subquery->add_sql_clause( 'limit', $this->get_sql_clause( 'limit' ) );
|
||||
$products_query = $this->subquery->get_query_statement();
|
||||
if ( 'date' === $query_args['orderby'] ) {
|
||||
$selections .= ", {$table_name}.date_created";
|
||||
}
|
||||
|
||||
$product_data = $wpdb->get_results(
|
||||
$products_query, // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
ARRAY_A
|
||||
$fields = $this->get_fields( $query_args );
|
||||
$join_selections = $this->format_join_selections( $fields, array( 'product_id' ) );
|
||||
$ids_table = $this->get_ids_table( $included_products, 'product_id' );
|
||||
|
||||
$this->subquery->clear_sql_clause( 'select' );
|
||||
$this->subquery->add_sql_clause( 'select', $selections );
|
||||
$this->add_sql_clause( 'select', $join_selections );
|
||||
$this->add_sql_clause( 'from', '(' );
|
||||
$this->add_sql_clause( 'from', $this->subquery->get_query_statement() );
|
||||
$this->add_sql_clause( 'from', ") AS {$table_name}" );
|
||||
$this->add_sql_clause(
|
||||
'right_join',
|
||||
"RIGHT JOIN ( {$ids_table} ) AS default_results
|
||||
ON default_results.product_id = {$table_name}.product_id"
|
||||
);
|
||||
$this->add_sql_clause( 'where', 'AND default_results.product_id != -1' );
|
||||
|
||||
$products_query = $this->get_query_statement();
|
||||
} else {
|
||||
$count_query = "SELECT COUNT(*) FROM (
|
||||
{$this->subquery->get_query_statement()}
|
||||
) AS tt";
|
||||
$db_records_count = (int) $wpdb->get_var(
|
||||
$count_query // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
);
|
||||
|
||||
if ( null === $product_data ) {
|
||||
$total_results = $db_records_count;
|
||||
$total_pages = (int) ceil( $db_records_count / $params['per_page'] );
|
||||
|
||||
if ( ( $query_args['page'] < 1 || $query_args['page'] > $total_pages ) ) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
$product_data = array_map( array( $this, 'cast_numbers' ), $product_data );
|
||||
$data = (object) array(
|
||||
'data' => $product_data,
|
||||
'total' => $total_results,
|
||||
'pages' => $total_pages,
|
||||
'page_no' => (int) $query_args['page'],
|
||||
);
|
||||
|
||||
$this->set_cached_data( $cache_key, $data );
|
||||
$this->subquery->clear_sql_clause( 'select' );
|
||||
$this->subquery->add_sql_clause( 'select', $selections );
|
||||
$this->subquery->add_sql_clause( 'order_by', $this->get_sql_clause( 'order_by' ) );
|
||||
$this->subquery->add_sql_clause( 'limit', $this->get_sql_clause( 'limit' ) );
|
||||
$products_query = $this->subquery->get_query_statement();
|
||||
}
|
||||
|
||||
$this->include_extended_info( $data->data, $query_args );
|
||||
$product_data = $wpdb->get_results(
|
||||
$products_query, // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
ARRAY_A
|
||||
);
|
||||
|
||||
if ( null === $product_data ) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
$product_data = array_map( array( $this, 'cast_numbers' ), $product_data );
|
||||
$data = (object) array(
|
||||
'data' => $product_data,
|
||||
'total' => $total_results,
|
||||
'pages' => $total_pages,
|
||||
'page_no' => (int) $query_args['page'],
|
||||
);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
@@ -22,12 +22,16 @@ use Automattic\WooCommerce\Admin\API\Reports\Query as ReportsQuery;
|
||||
|
||||
/**
|
||||
* API\Reports\Products\Query
|
||||
*
|
||||
* @deprecated 9.3.0 Products\Query class is deprecated, please use GenericQuery or \WC_Object_Query instead.
|
||||
*/
|
||||
class Query extends ReportsQuery {
|
||||
|
||||
/**
|
||||
* Valid fields for Products report.
|
||||
*
|
||||
* @deprecated 9.3.0 Products\Query class is deprecated, please use GenericQuery or \WC_Object_Query instead.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_default_query_vars() {
|
||||
@@ -37,6 +41,8 @@ class Query extends ReportsQuery {
|
||||
/**
|
||||
* Get product data based on the current query vars.
|
||||
*
|
||||
* @deprecated 9.3.0 Products\Query class is deprecated, please use GenericQuery or \WC_Object_Query instead.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_data() {
|
||||
|
||||
@@ -9,8 +9,8 @@ namespace Automattic\WooCommerce\Admin\API\Reports\Products\Stats;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
use Automattic\WooCommerce\Admin\API\Reports\GenericQuery;
|
||||
use Automattic\WooCommerce\Admin\API\Reports\GenericStatsController;
|
||||
use Automattic\WooCommerce\Admin\API\Reports\ParameterException;
|
||||
use WP_REST_Request;
|
||||
use WP_REST_Response;
|
||||
|
||||
@@ -48,12 +48,25 @@ class Controller extends GenericStatsController {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all reports.
|
||||
* Get data from `'products-stats'` Query.
|
||||
*
|
||||
* @param WP_REST_Request $request Request data.
|
||||
* @return array|WP_Error
|
||||
* @override GenericController::get_datastore_data()
|
||||
*
|
||||
* @param array $query_args Query arguments.
|
||||
* @return mixed Results from the data store.
|
||||
*/
|
||||
public function get_items( $request ) {
|
||||
protected function get_datastore_data( $query_args = array() ) {
|
||||
$query = new GenericQuery( $query_args, 'products-stats' );
|
||||
return $query->get_data();
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps query arguments from the REST request, to be fed to Query.
|
||||
*
|
||||
* @param \WP_REST_Request $request Full request object.
|
||||
* @return array Simplified array of params.
|
||||
*/
|
||||
protected function prepare_reports_query( $request ) {
|
||||
$query_args = array(
|
||||
'fields' => array(
|
||||
'items_sold',
|
||||
@@ -75,36 +88,13 @@ class Controller extends GenericStatsController {
|
||||
}
|
||||
}
|
||||
|
||||
$query = new Query( $query_args );
|
||||
try {
|
||||
$report_data = $query->get_data();
|
||||
} catch ( ParameterException $e ) {
|
||||
return new \WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) );
|
||||
}
|
||||
|
||||
$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 );
|
||||
}
|
||||
|
||||
return $this->add_pagination_headers(
|
||||
$request,
|
||||
$out_data,
|
||||
(int) $report_data->total,
|
||||
(int) $report_data->page_no,
|
||||
(int) $report_data->pages
|
||||
);
|
||||
return $query_args;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare a report object for serialization.
|
||||
* Prepare a report data item for serialization.
|
||||
*
|
||||
* @param array $report Report data.
|
||||
* @param array $report Report data item as returned from Data Store.
|
||||
* @param WP_REST_Request $request Request object.
|
||||
* @return WP_REST_Response
|
||||
*/
|
||||
@@ -255,15 +245,6 @@ class Controller extends GenericStatsController {
|
||||
),
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
);
|
||||
$params['fields'] = array(
|
||||
'description' => __( 'Limit stats fields to the specified items.', 'woocommerce' ),
|
||||
'type' => 'array',
|
||||
'sanitize_callback' => 'wp_parse_slug_list',
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
'items' => array(
|
||||
'type' => 'string',
|
||||
),
|
||||
);
|
||||
|
||||
return $params;
|
||||
}
|
||||
|
||||
@@ -8,18 +8,22 @@ namespace Automattic\WooCommerce\Admin\API\Reports\Products\Stats;
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
use Automattic\WooCommerce\Admin\API\Reports\Products\DataStore as ProductsDataStore;
|
||||
use Automattic\WooCommerce\Admin\API\Reports\DataStore as ReportsDataStore;
|
||||
use Automattic\WooCommerce\Admin\API\Reports\DataStoreInterface;
|
||||
use Automattic\WooCommerce\Admin\API\Reports\TimeInterval;
|
||||
use Automattic\WooCommerce\Admin\API\Reports\SqlQuery;
|
||||
use Automattic\WooCommerce\Admin\API\Reports\StatsDataStoreTrait;
|
||||
|
||||
/**
|
||||
* API\Reports\Products\Stats\DataStore.
|
||||
*/
|
||||
class DataStore extends ProductsDataStore implements DataStoreInterface {
|
||||
use StatsDataStoreTrait;
|
||||
|
||||
/**
|
||||
* Mapping columns to data type to return correct response types.
|
||||
*
|
||||
* @override ProductsDataStore::$column_types
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $column_types = array(
|
||||
@@ -36,6 +40,8 @@ class DataStore extends ProductsDataStore implements DataStoreInterface {
|
||||
/**
|
||||
* Cache identifier.
|
||||
*
|
||||
* @override ProductsDataStore::$cache_key
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $cache_key = 'products_stats';
|
||||
@@ -43,12 +49,16 @@ class DataStore extends ProductsDataStore implements DataStoreInterface {
|
||||
/**
|
||||
* Data store context used to pass to filters.
|
||||
*
|
||||
* @override ProductsDataStore::$context
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $context = 'products_stats';
|
||||
|
||||
/**
|
||||
* Assign report columns once full table name has been assigned.
|
||||
*
|
||||
* @override ProductsDataStore::assign_report_columns()
|
||||
*/
|
||||
protected function assign_report_columns() {
|
||||
$table_name = self::get_db_table_name();
|
||||
@@ -99,138 +109,141 @@ class DataStore extends ProductsDataStore implements DataStoreInterface {
|
||||
$this->interval_query->add_sql_clause( 'select', $this->get_sql_clause( 'select' ) . ' AS time_interval' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default query arguments to be used by get_data().
|
||||
* These defaults are only partially applied when used via REST API, as that has its own defaults.
|
||||
*
|
||||
* @override ProductsDataStore::get_default_query_vars()
|
||||
*
|
||||
* @return array Query parameters.
|
||||
*/
|
||||
public function get_default_query_vars() {
|
||||
$defaults = parent::get_default_query_vars();
|
||||
$defaults['interval'] = 'week';
|
||||
unset( $defaults['extended_info'] );
|
||||
|
||||
return $defaults;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the report data based on parameters supplied by the user.
|
||||
*
|
||||
* @since 3.5.0
|
||||
* @override ProductsDataStore::get_data()
|
||||
*
|
||||
* @param array $query_args Query parameters.
|
||||
* @return stdClass|WP_Error Data.
|
||||
*/
|
||||
public function get_data( $query_args ) {
|
||||
// Do not include extended info like `ProductsDataStore` does.
|
||||
return ReportsDataStore::get_data( $query_args );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the report data based on normalized parameters.
|
||||
* Will be called by `get_data` if there is no data in cache.
|
||||
*
|
||||
* @override ProductsDataStore::get_noncached_data()
|
||||
*
|
||||
* @see get_data
|
||||
* @see get_noncached_stats_data
|
||||
* @param array $query_args Query parameters.
|
||||
* @param array $params Query limit parameters.
|
||||
* @param stdClass $data Reference to the data object to fill.
|
||||
* @param int $expected_interval_count Number of expected intervals.
|
||||
* @return stdClass|WP_Error Data object `{ totals: *, intervals: array, total: int, pages: int, page_no: int }`, or error.
|
||||
*/
|
||||
public function get_noncached_stats_data( $query_args, $params, &$data, $expected_interval_count ) {
|
||||
global $wpdb;
|
||||
|
||||
$table_name = self::get_db_table_name();
|
||||
|
||||
// These defaults are only partially applied when used via REST API, as that has its own defaults.
|
||||
$defaults = array(
|
||||
'per_page' => get_option( 'posts_per_page' ),
|
||||
'page' => 1,
|
||||
'order' => 'DESC',
|
||||
'orderby' => 'date',
|
||||
'before' => TimeInterval::default_before(),
|
||||
'after' => TimeInterval::default_after(),
|
||||
'fields' => '*',
|
||||
'category_includes' => array(),
|
||||
'interval' => 'week',
|
||||
'product_includes' => array(),
|
||||
$this->initialize_queries();
|
||||
|
||||
$selections = $this->selected_columns( $query_args );
|
||||
|
||||
$this->update_sql_query_params( $query_args );
|
||||
$this->get_limit_sql_params( $query_args );
|
||||
$this->interval_query->add_sql_clause( 'where_time', $this->get_sql_clause( 'where_time' ) );
|
||||
|
||||
$db_intervals = $wpdb->get_col(
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- cache ok, DB call ok, unprepared SQL ok.
|
||||
$this->interval_query->get_query_statement()
|
||||
);
|
||||
$query_args = wp_parse_args( $query_args, $defaults );
|
||||
$this->normalize_timezones( $query_args, $defaults );
|
||||
|
||||
/*
|
||||
* We need to get the cache key here because
|
||||
* parent::update_intervals_sql_params() modifies $query_args.
|
||||
*/
|
||||
$cache_key = $this->get_cache_key( $query_args );
|
||||
$data = $this->get_cached_data( $cache_key );
|
||||
$db_interval_count = count( $db_intervals );
|
||||
|
||||
if ( false === $data ) {
|
||||
$this->initialize_queries();
|
||||
$intervals = array();
|
||||
$this->update_intervals_sql_params( $query_args, $db_interval_count, $expected_interval_count, $table_name );
|
||||
$this->total_query->add_sql_clause( 'select', $selections );
|
||||
$this->total_query->add_sql_clause( 'where_time', $this->get_sql_clause( 'where_time' ) );
|
||||
|
||||
$selections = $this->selected_columns( $query_args );
|
||||
$params = $this->get_limit_params( $query_args );
|
||||
$totals = $wpdb->get_results(
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- cache ok, DB call ok, unprepared SQL ok.
|
||||
$this->total_query->get_query_statement(),
|
||||
ARRAY_A
|
||||
);
|
||||
|
||||
$this->update_sql_query_params( $query_args );
|
||||
$this->get_limit_sql_params( $query_args );
|
||||
$this->interval_query->add_sql_clause( 'where_time', $this->get_sql_clause( 'where_time' ) );
|
||||
// phpcs:ignore Generic.Commenting.Todo.TaskFound
|
||||
// @todo remove these assignements when refactoring segmenter classes to use query objects.
|
||||
$totals_query = array(
|
||||
'from_clause' => $this->total_query->get_sql_clause( 'join' ),
|
||||
'where_time_clause' => $this->total_query->get_sql_clause( 'where_time' ),
|
||||
'where_clause' => $this->total_query->get_sql_clause( 'where' ),
|
||||
);
|
||||
$intervals_query = array(
|
||||
'select_clause' => $this->get_sql_clause( 'select' ),
|
||||
'from_clause' => $this->interval_query->get_sql_clause( 'join' ),
|
||||
'where_time_clause' => $this->interval_query->get_sql_clause( 'where_time' ),
|
||||
'where_clause' => $this->interval_query->get_sql_clause( 'where' ),
|
||||
'order_by' => $this->get_sql_clause( 'order_by' ),
|
||||
'limit' => $this->get_sql_clause( 'limit' ),
|
||||
);
|
||||
$segmenter = new Segmenter( $query_args, $this->report_columns );
|
||||
$totals[0]['segments'] = $segmenter->get_totals_segments( $totals_query, $table_name );
|
||||
|
||||
$db_intervals = $wpdb->get_col(
|
||||
$this->interval_query->get_query_statement()
|
||||
); // WPCS: cache ok, DB call ok, unprepared SQL ok.
|
||||
|
||||
$db_interval_count = count( $db_intervals );
|
||||
$expected_interval_count = TimeInterval::intervals_between( $query_args['after'], $query_args['before'], $query_args['interval'] );
|
||||
$total_pages = (int) ceil( $expected_interval_count / $params['per_page'] );
|
||||
if ( $query_args['page'] < 1 || $query_args['page'] > $total_pages ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$intervals = array();
|
||||
$this->update_intervals_sql_params( $query_args, $db_interval_count, $expected_interval_count, $table_name );
|
||||
$this->total_query->add_sql_clause( 'select', $selections );
|
||||
$this->total_query->add_sql_clause( 'where_time', $this->get_sql_clause( 'where_time' ) );
|
||||
|
||||
$totals = $wpdb->get_results(
|
||||
$this->total_query->get_query_statement(),
|
||||
ARRAY_A
|
||||
); // WPCS: cache ok, DB call ok, unprepared SQL ok.
|
||||
|
||||
// @todo remove these assignements when refactoring segmenter classes to use query objects.
|
||||
$totals_query = array(
|
||||
'from_clause' => $this->total_query->get_sql_clause( 'join' ),
|
||||
'where_time_clause' => $this->total_query->get_sql_clause( 'where_time' ),
|
||||
'where_clause' => $this->total_query->get_sql_clause( 'where' ),
|
||||
);
|
||||
$intervals_query = array(
|
||||
'select_clause' => $this->get_sql_clause( 'select' ),
|
||||
'from_clause' => $this->interval_query->get_sql_clause( 'join' ),
|
||||
'where_time_clause' => $this->interval_query->get_sql_clause( 'where_time' ),
|
||||
'where_clause' => $this->interval_query->get_sql_clause( 'where' ),
|
||||
'order_by' => $this->get_sql_clause( 'order_by' ),
|
||||
'limit' => $this->get_sql_clause( 'limit' ),
|
||||
);
|
||||
$segmenter = new Segmenter( $query_args, $this->report_columns );
|
||||
$totals[0]['segments'] = $segmenter->get_totals_segments( $totals_query, $table_name );
|
||||
|
||||
if ( null === $totals ) {
|
||||
return new \WP_Error( 'woocommerce_analytics_products_stats_result_failed', __( 'Sorry, fetching revenue data failed.', 'woocommerce' ) );
|
||||
}
|
||||
|
||||
$this->interval_query->add_sql_clause( 'order_by', $this->get_sql_clause( 'order_by' ) );
|
||||
$this->interval_query->add_sql_clause( 'limit', $this->get_sql_clause( 'limit' ) );
|
||||
$this->interval_query->add_sql_clause( 'select', ", MAX({$table_name}.date_created) AS datetime_anchor" );
|
||||
if ( '' !== $selections ) {
|
||||
$this->interval_query->add_sql_clause( 'select', ', ' . $selections );
|
||||
}
|
||||
|
||||
$intervals = $wpdb->get_results(
|
||||
$this->interval_query->get_query_statement(),
|
||||
ARRAY_A
|
||||
); // WPCS: cache ok, DB call ok, unprepared SQL ok.
|
||||
|
||||
if ( null === $intervals ) {
|
||||
return new \WP_Error( 'woocommerce_analytics_products_stats_result_failed', __( 'Sorry, fetching revenue data failed.', 'woocommerce' ) );
|
||||
}
|
||||
|
||||
$totals = (object) $this->cast_numbers( $totals[0] );
|
||||
|
||||
$data = (object) array(
|
||||
'totals' => $totals,
|
||||
'intervals' => $intervals,
|
||||
'total' => $expected_interval_count,
|
||||
'pages' => $total_pages,
|
||||
'page_no' => (int) $query_args['page'],
|
||||
);
|
||||
|
||||
if ( TimeInterval::intervals_missing( $expected_interval_count, $db_interval_count, $params['per_page'], $query_args['page'], $query_args['order'], $query_args['orderby'], count( $intervals ) ) ) {
|
||||
$this->fill_in_missing_intervals( $db_intervals, $query_args['adj_after'], $query_args['adj_before'], $query_args['interval'], $data );
|
||||
$this->sort_intervals( $data, $query_args['orderby'], $query_args['order'] );
|
||||
$this->remove_extra_records( $data, $query_args['page'], $params['per_page'], $db_interval_count, $expected_interval_count, $query_args['orderby'], $query_args['order'] );
|
||||
} else {
|
||||
$this->update_interval_boundary_dates( $query_args['after'], $query_args['before'], $query_args['interval'], $data->intervals );
|
||||
}
|
||||
$segmenter->add_intervals_segments( $data, $intervals_query, $table_name );
|
||||
$this->create_interval_subtotals( $data->intervals );
|
||||
|
||||
$this->set_cached_data( $cache_key, $data );
|
||||
if ( null === $totals ) {
|
||||
return new \WP_Error( 'woocommerce_analytics_products_stats_result_failed', __( 'Sorry, fetching revenue data failed.', 'woocommerce' ) );
|
||||
}
|
||||
|
||||
$this->interval_query->add_sql_clause( 'order_by', $this->get_sql_clause( 'order_by' ) );
|
||||
$this->interval_query->add_sql_clause( 'limit', $this->get_sql_clause( 'limit' ) );
|
||||
$this->interval_query->add_sql_clause( 'select', ", MAX({$table_name}.date_created) AS datetime_anchor" );
|
||||
if ( '' !== $selections ) {
|
||||
$this->interval_query->add_sql_clause( 'select', ', ' . $selections );
|
||||
}
|
||||
|
||||
$intervals = $wpdb->get_results(
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- cache ok, DB call ok, unprepared SQL ok.
|
||||
$this->interval_query->get_query_statement(),
|
||||
ARRAY_A
|
||||
);
|
||||
|
||||
if ( null === $intervals ) {
|
||||
return new \WP_Error( 'woocommerce_analytics_products_stats_result_failed', __( 'Sorry, fetching revenue data failed.', 'woocommerce' ) );
|
||||
}
|
||||
|
||||
$totals = (object) $this->cast_numbers( $totals[0] );
|
||||
|
||||
$data->totals = $totals;
|
||||
$data->intervals = $intervals;
|
||||
|
||||
if ( TimeInterval::intervals_missing( $expected_interval_count, $db_interval_count, $params['per_page'], $query_args['page'], $query_args['order'], $query_args['orderby'], count( $intervals ) ) ) {
|
||||
$this->fill_in_missing_intervals( $db_intervals, $query_args['adj_after'], $query_args['adj_before'], $query_args['interval'], $data );
|
||||
$this->sort_intervals( $data, $query_args['orderby'], $query_args['order'] );
|
||||
$this->remove_extra_records( $data, $query_args['page'], $params['per_page'], $db_interval_count, $expected_interval_count, $query_args['orderby'], $query_args['order'] );
|
||||
} else {
|
||||
$this->update_interval_boundary_dates( $query_args['after'], $query_args['before'], $query_args['interval'], $data->intervals );
|
||||
}
|
||||
$segmenter->add_intervals_segments( $data, $intervals_query, $table_name );
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes order_by clause to match to SQL query.
|
||||
*
|
||||
* @override ProductsDataStore::normalize_order_by()
|
||||
*
|
||||
* @param string $order_by Order by option requeste by user.
|
||||
* @return string
|
||||
*/
|
||||
@@ -241,18 +254,4 @@ class DataStore extends ProductsDataStore implements DataStoreInterface {
|
||||
|
||||
return $order_by;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize query objects.
|
||||
*/
|
||||
protected function initialize_queries() {
|
||||
$this->clear_all_clauses();
|
||||
unset( $this->subquery );
|
||||
$this->total_query = new SqlQuery( $this->context . '_total' );
|
||||
$this->total_query->add_sql_clause( 'from', self::get_db_table_name() );
|
||||
|
||||
$this->interval_query = new SqlQuery( $this->context . '_interval' );
|
||||
$this->interval_query->add_sql_clause( 'from', self::get_db_table_name() );
|
||||
$this->interval_query->add_sql_clause( 'group_by', 'time_interval' );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,12 +22,16 @@ use Automattic\WooCommerce\Admin\API\Reports\Query as ReportsQuery;
|
||||
|
||||
/**
|
||||
* API\Reports\Products\Stats\Query
|
||||
*
|
||||
* @deprecated 9.3.0 Products\Stats\Query class is deprecated, please use GenericQuery or \WC_Object_Query instead.
|
||||
*/
|
||||
class Query extends ReportsQuery {
|
||||
|
||||
/**
|
||||
* Valid fields for Products report.
|
||||
*
|
||||
* @deprecated 9.3.0 Products\Stats\Query class is deprecated, please use GenericQuery or \WC_Object_Query instead.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_default_query_vars() {
|
||||
@@ -37,6 +41,8 @@ class Query extends ReportsQuery {
|
||||
/**
|
||||
* Get product data based on the current query vars.
|
||||
*
|
||||
* @deprecated 9.3.0 Products\Stats\Query class is deprecated, please use GenericQuery or \WC_Object_Query instead.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_data() {
|
||||
|
||||
@@ -9,12 +9,15 @@ defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* Admin\API\Reports\Query
|
||||
*
|
||||
* @deprecated 9.3.0 Query class is deprecated, please use GenericQuery or \WC_Object_Query instead.
|
||||
*/
|
||||
abstract class Query extends \WC_Object_Query {
|
||||
|
||||
/**
|
||||
* Get report data matching the current query vars.
|
||||
*
|
||||
* @deprecated 9.3.0 Query class is deprecated, please use GenericQuery or \WC_Object_Query instead.
|
||||
*
|
||||
* @return array|object of WC_Product objects
|
||||
*/
|
||||
public function get_data() {
|
||||
|
||||
@@ -16,12 +16,15 @@ namespace Automattic\WooCommerce\Admin\API\Reports\Revenue;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
use Automattic\WooCommerce\Admin\API\Reports\Query as ReportsQuery;
|
||||
|
||||
/**
|
||||
* API\Reports\Revenue\Query
|
||||
*
|
||||
* This query uses inconsistent names:
|
||||
* - `report-revenue-stats` data store
|
||||
* - `woocommerce_analytics_revenue_*` filters
|
||||
* So, for backward compatibility, we cannot use GenericQuery.
|
||||
*/
|
||||
class Query extends ReportsQuery {
|
||||
class Query extends \WC_Object_Query {
|
||||
|
||||
/**
|
||||
* Valid fields for Revenue report.
|
||||
|
||||
@@ -13,7 +13,6 @@ use Automattic\WooCommerce\Admin\API\Reports\GenericStatsController;
|
||||
use Automattic\WooCommerce\Admin\API\Reports\Revenue\Query as RevenueQuery;
|
||||
use Automattic\WooCommerce\Admin\API\Reports\ExportableInterface;
|
||||
use Automattic\WooCommerce\Admin\API\Reports\ExportableTraits;
|
||||
use Automattic\WooCommerce\Admin\API\Reports\ParameterException;
|
||||
use WP_REST_Request;
|
||||
use WP_REST_Response;
|
||||
|
||||
@@ -60,37 +59,16 @@ class Controller extends GenericStatsController implements ExportableInterface {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all reports.
|
||||
* Get data from RevenueQuery.
|
||||
*
|
||||
* @param WP_REST_Request $request Request data.
|
||||
* @return WP_REST_Response|WP_Error
|
||||
* @override GenericController::get_datastore_data()
|
||||
*
|
||||
* @param array $query_args Query arguments.
|
||||
* @return mixed Results from the data store.
|
||||
*/
|
||||
public function get_items( $request ) {
|
||||
$query_args = $this->prepare_reports_query( $request );
|
||||
$reports_revenue = new RevenueQuery( $query_args );
|
||||
try {
|
||||
$report_data = $reports_revenue->get_data();
|
||||
} catch ( ParameterException $e ) {
|
||||
return new \WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) );
|
||||
}
|
||||
|
||||
$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 );
|
||||
}
|
||||
|
||||
return $this->add_pagination_headers(
|
||||
$request,
|
||||
$out_data,
|
||||
(int) $report_data->total,
|
||||
(int) $report_data->page_no,
|
||||
(int) $report_data->pages
|
||||
);
|
||||
protected function get_datastore_data( $query_args = array() ) {
|
||||
$query = new RevenueQuery( $query_args );
|
||||
return $query->get_data();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -112,9 +90,9 @@ class Controller extends GenericStatsController implements ExportableInterface {
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare a report object for serialization.
|
||||
* Prepare a report data item for serialization.
|
||||
*
|
||||
* @param array $report Report data.
|
||||
* @param array $report Report data item as returned from Data Store.
|
||||
* @param WP_REST_Request $request Request object.
|
||||
* @return WP_REST_Response
|
||||
*/
|
||||
@@ -279,6 +257,7 @@ class Controller extends GenericStatsController implements ExportableInterface {
|
||||
),
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
);
|
||||
unset( $params['fields'] );
|
||||
|
||||
return $params;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
<?php
|
||||
declare( strict_types = 1);
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\API\Reports;
|
||||
|
||||
// Exit if accessed directly.
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
use Automattic\WooCommerce\Admin\API\Reports\SqlQuery;
|
||||
|
||||
/**
|
||||
* Trait to contain *stats-specific methods for data stores.
|
||||
*
|
||||
* It does preliminary intervals & page calculations
|
||||
* and prepares intervals & totals data structure by implementing the `get_noncached_data()` method.
|
||||
* So, this time, you'll need to prepare `get_noncached_stats_data()` which will be called only if
|
||||
* the requested page is within the date range.
|
||||
*
|
||||
* The trait also exposes the `initialize_queries()` method to initialize the interval and total queries.
|
||||
*
|
||||
* Example:
|
||||
* <pre><code class="language-php">class MyStatsDataStore extends DataStore implements DataStoreInterface {
|
||||
* // Use the trait.
|
||||
* use StatsDataStoreTrait;
|
||||
* // Provide all the necessary properties and methods for a regular DataStore.
|
||||
* // ...
|
||||
* /**
|
||||
* * Return your results with the help of the interval & total methods and queries.
|
||||
* * @return stdClass|WP_Error $data filled with your results.
|
||||
* */
|
||||
* public function get_noncached_stats_data( $query_args, $params, &$data, $expected_interval_count ) {
|
||||
* $this->initialize_queries();
|
||||
* // Do your magic ...
|
||||
* // ... with a help of things like:
|
||||
* $this->update_intervals_sql_params( $query_args, $db_interval_count, $expected_interval_count, $table_name );
|
||||
* $this->total_query->add_sql_clause( 'where_time', $this->get_sql_clause( 'where_time' ) );
|
||||
*
|
||||
* $totals = $wpdb->get_results(
|
||||
* $this->total_query->get_query_statement(),
|
||||
* ARRAY_A
|
||||
* );
|
||||
*
|
||||
* $intervals = $wpdb->get_results(
|
||||
* $this->interval_query->get_query_statement(),
|
||||
* ARRAY_A
|
||||
* );
|
||||
*
|
||||
* $data->totals = (object) $this->cast_numbers( $totals[0] );
|
||||
* $data->intervals = $intervals;
|
||||
*
|
||||
* if ( TimeInterval::intervals_missing( $expected_interval_count, $db_interval_count, $params['per_page'], $query_args['page'], $query_args['order'], $query_args['orderby'], count( $intervals ) ) ) {
|
||||
* $this->fill_in_missing_intervals( $db_intervals, $query_args['adj_after'], $query_args['adj_before'], $query_args['interval'], $data );
|
||||
* $this->sort_intervals( $data, $query_args['orderby'], $query_args['order'] );
|
||||
* $this->remove_extra_records( $data, $query_args['page'], $params['per_page'], $db_interval_count, $expected_interval_count, $query_args['orderby'], $query_args['order'] );
|
||||
* } else {
|
||||
* $this->update_interval_boundary_dates( $query_args['after'], $query_args['before'], $query_args['interval'], $data->intervals );
|
||||
* }
|
||||
*
|
||||
* return $data;
|
||||
* }
|
||||
* }
|
||||
* </code></pre>
|
||||
*
|
||||
* @see DataStore
|
||||
*/
|
||||
trait StatsDataStoreTrait {
|
||||
/**
|
||||
* Initialize query objects.
|
||||
*/
|
||||
protected function initialize_queries() {
|
||||
$this->clear_all_clauses();
|
||||
unset( $this->subquery );
|
||||
$table_name = self::get_db_table_name();
|
||||
|
||||
$this->total_query = new SqlQuery( $this->context . '_total' );
|
||||
$this->total_query->add_sql_clause( 'from', $table_name );
|
||||
|
||||
$this->interval_query = new SqlQuery( $this->context . '_interval' );
|
||||
$this->interval_query->add_sql_clause( 'from', $table_name );
|
||||
$this->interval_query->add_sql_clause( 'group_by', 'time_interval' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the stats report data based on normalized parameters.
|
||||
* Prepares the basic intervals and object structure
|
||||
* Will be called by `get_data` if there is no data in cache.
|
||||
* Will call `get_noncached_stats_data` to fetch the actual data.
|
||||
*
|
||||
* @see get_data
|
||||
* @param array $query_args Query parameters.
|
||||
* @return stdClass|WP_Error Data object, or error.
|
||||
*/
|
||||
public function get_noncached_data( $query_args ) {
|
||||
$params = $this->get_limit_params( $query_args );
|
||||
$expected_interval_count = TimeInterval::intervals_between( $query_args['after'], $query_args['before'], $query_args['interval'] );
|
||||
$total_pages = (int) ceil( $expected_interval_count / $params['per_page'] );
|
||||
|
||||
// Default, empty data object.
|
||||
$data = (object) array(
|
||||
'totals' => null,
|
||||
'intervals' => array(),
|
||||
'total' => $expected_interval_count,
|
||||
'pages' => $total_pages,
|
||||
'page_no' => (int) $query_args['page'],
|
||||
);
|
||||
// If the requested page is out off range, return the deault empty object.
|
||||
if ( $query_args['page'] >= 1 && $query_args['page'] <= $total_pages ) {
|
||||
// Fetch the actual data.
|
||||
$data = $this->get_noncached_stats_data( $query_args, $params, $data, $expected_interval_count );
|
||||
|
||||
if ( ! is_wp_error( $data ) && is_array( $data->intervals ) ) {
|
||||
$this->create_interval_subtotals( $data->intervals );
|
||||
}
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
@@ -276,9 +276,9 @@ class Controller extends GenericController implements ExportableInterface {
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare a report object for serialization.
|
||||
* Prepare a report data item for serialization.
|
||||
*
|
||||
* @param WC_Product $product Report data.
|
||||
* @param WC_Product $product Report data item as returned from Data Store.
|
||||
* @param WP_REST_Request $request Request object.
|
||||
* @return WP_REST_Response
|
||||
*/
|
||||
|
||||
@@ -47,9 +47,9 @@ class Controller extends \WC_REST_Reports_Controller {
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare a report object for serialization.
|
||||
* Prepare a report data item for serialization.
|
||||
*
|
||||
* @param WC_Product $report Report data.
|
||||
* @param WC_Product $report Report data item as returned from Data Store.
|
||||
* @param WP_REST_Request $request Request object.
|
||||
* @return WP_REST_Response
|
||||
*/
|
||||
|
||||
@@ -18,6 +18,8 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||
/**
|
||||
* Get stock counts for the whole store.
|
||||
*
|
||||
* @override ReportsDataStore::get_data()
|
||||
*
|
||||
* @param array $query Not used for the stock stats data store, but needed for the interface.
|
||||
* @return array Array of counts.
|
||||
*/
|
||||
|
||||
@@ -10,12 +10,11 @@ namespace Automattic\WooCommerce\Admin\API\Reports\Stock\Stats;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
use Automattic\WooCommerce\Admin\API\Reports\Query as ReportsQuery;
|
||||
|
||||
/**
|
||||
* API\Reports\Stock\Stats\Query
|
||||
* This query takes no arguments, so we do not inherit from GenericQuery.
|
||||
*/
|
||||
class Query extends ReportsQuery {
|
||||
class Query extends \WC_Object_Query {
|
||||
|
||||
/**
|
||||
* Get product data based on the current query vars.
|
||||
|
||||
@@ -9,9 +9,10 @@ namespace Automattic\WooCommerce\Admin\API\Reports\Taxes;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
use Automattic\WooCommerce\Admin\API\Reports\GenericController;
|
||||
use Automattic\WooCommerce\Admin\API\Reports\ExportableInterface;
|
||||
use Automattic\WooCommerce\Admin\API\Reports\ExportableTraits;
|
||||
use Automattic\WooCommerce\Admin\API\Reports\GenericController;
|
||||
use Automattic\WooCommerce\Admin\API\Reports\GenericQuery;
|
||||
use WP_REST_Request;
|
||||
use WP_REST_Response;
|
||||
|
||||
@@ -34,6 +35,19 @@ class Controller extends GenericController implements ExportableInterface {
|
||||
*/
|
||||
protected $rest_base = 'reports/taxes';
|
||||
|
||||
/**
|
||||
* Get data from `'taxes'` Query.
|
||||
*
|
||||
* @override GenericController::get_datastore_data()
|
||||
*
|
||||
* @param array $query_args Query arguments.
|
||||
* @return mixed Results from the data store.
|
||||
*/
|
||||
protected function get_datastore_data( $query_args = array() ) {
|
||||
$query = new GenericQuery( $query_args, 'taxes' );
|
||||
return $query->get_data();
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps query arguments from the REST request.
|
||||
*
|
||||
@@ -55,41 +69,17 @@ class Controller extends GenericController implements ExportableInterface {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all reports.
|
||||
* Prepare a report data item for serialization.
|
||||
*
|
||||
* @param WP_REST_Request $request Request data.
|
||||
* @return array|WP_Error
|
||||
*/
|
||||
public function get_items( $request ) {
|
||||
$query_args = $this->prepare_reports_query( $request );
|
||||
$taxes_query = new Query( $query_args );
|
||||
$report_data = $taxes_query->get_data();
|
||||
|
||||
$data = array();
|
||||
|
||||
foreach ( $report_data->data as $tax_data ) {
|
||||
$item = $this->prepare_item_for_response( (object) $tax_data, $request );
|
||||
$data[] = $this->prepare_response_for_collection( $item );
|
||||
}
|
||||
|
||||
return $this->add_pagination_headers(
|
||||
$request,
|
||||
$data,
|
||||
(int) $report_data->total,
|
||||
(int) $report_data->page_no,
|
||||
(int) $report_data->pages
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare a report object for serialization.
|
||||
*
|
||||
* @param stdClass $report Report data.
|
||||
* @param mixed $report 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( $report, $request ) {
|
||||
$response = parent::prepare_item_for_response( $report, $request );
|
||||
|
||||
// Map to `object` for backwards compatibility.
|
||||
$report = (object) $report;
|
||||
$response->add_links( $this->prepare_links( $report ) );
|
||||
|
||||
/**
|
||||
|
||||
@@ -21,6 +21,8 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||
/**
|
||||
* Table used to get the data.
|
||||
*
|
||||
* @override ReportsDataStore::$table_name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected static $table_name = 'wc_order_tax_lookup';
|
||||
@@ -28,6 +30,8 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||
/**
|
||||
* Cache identifier.
|
||||
*
|
||||
* @override ReportsDataStore::$cache_key
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $cache_key = 'taxes';
|
||||
@@ -35,6 +39,8 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||
/**
|
||||
* Mapping columns to data type to return correct response types.
|
||||
*
|
||||
* @override ReportsDataStore::$column_types
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $column_types = array(
|
||||
@@ -53,12 +59,16 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||
/**
|
||||
* Data store context used to pass to filters.
|
||||
*
|
||||
* @override ReportsDataStore::$context
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $context = 'taxes';
|
||||
|
||||
/**
|
||||
* Assign report columns once full table name has been assigned.
|
||||
*
|
||||
* @override ReportsDataStore::assign_report_columns()
|
||||
*/
|
||||
protected function assign_report_columns() {
|
||||
global $wpdb;
|
||||
@@ -138,100 +148,97 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the report data based on parameters supplied by the user.
|
||||
* Get the default query arguments to be used by get_data().
|
||||
* These defaults are only partially applied when used via REST API, as that has its own defaults.
|
||||
*
|
||||
* @param array $query_args Query parameters.
|
||||
* @return stdClass|WP_Error Data.
|
||||
* @override ReportsDataStore::get_default_query_vars()
|
||||
*
|
||||
* @return array Query parameters.
|
||||
*/
|
||||
public function get_data( $query_args ) {
|
||||
public function get_default_query_vars() {
|
||||
$defaults = parent::get_default_query_vars();
|
||||
$defaults['orderby'] = 'tax_rate_id';
|
||||
$defaults['taxes'] = array();
|
||||
|
||||
return $defaults;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the report data based on normalized parameters.
|
||||
* Will be called by `get_data` if there is no data in cache.
|
||||
*
|
||||
* @override ReportsDataStore::get_noncached_data()
|
||||
*
|
||||
* @see get_data
|
||||
* @param array $query_args Query parameters.
|
||||
* @return stdClass|WP_Error Data object `{ totals: *, intervals: array, total: int, pages: int, page_no: int }`, or error.
|
||||
*/
|
||||
public function get_noncached_data( $query_args ) {
|
||||
global $wpdb;
|
||||
|
||||
// These defaults are only partially applied when used via REST API, as that has its own defaults.
|
||||
$defaults = array(
|
||||
'per_page' => get_option( 'posts_per_page' ),
|
||||
'page' => 1,
|
||||
'order' => 'DESC',
|
||||
'orderby' => 'tax_rate_id',
|
||||
'before' => TimeInterval::default_before(),
|
||||
'after' => TimeInterval::default_after(),
|
||||
'fields' => '*',
|
||||
'taxes' => array(),
|
||||
$this->initialize_queries();
|
||||
|
||||
$data = (object) array(
|
||||
'data' => array(),
|
||||
'total' => 0,
|
||||
'pages' => 0,
|
||||
'page_no' => 0,
|
||||
);
|
||||
$query_args = wp_parse_args( $query_args, $defaults );
|
||||
$this->normalize_timezones( $query_args, $defaults );
|
||||
|
||||
/*
|
||||
* We need to get the cache key here because
|
||||
* parent::update_intervals_sql_params() modifies $query_args.
|
||||
*/
|
||||
$cache_key = $this->get_cache_key( $query_args );
|
||||
$data = $this->get_cached_data( $cache_key );
|
||||
$this->add_sql_query_params( $query_args );
|
||||
$params = $this->get_limit_params( $query_args );
|
||||
|
||||
if ( false === $data ) {
|
||||
$this->initialize_queries();
|
||||
|
||||
$data = (object) array(
|
||||
'data' => array(),
|
||||
'total' => 0,
|
||||
'pages' => 0,
|
||||
'page_no' => 0,
|
||||
if ( isset( $query_args['taxes'] ) && is_array( $query_args['taxes'] ) && ! empty( $query_args['taxes'] ) ) {
|
||||
$total_results = count( $query_args['taxes'] );
|
||||
$total_pages = (int) ceil( $total_results / $params['per_page'] );
|
||||
} else {
|
||||
$db_records_count = (int) $wpdb->get_var(
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- cache ok, DB call ok, unprepared SQL ok.
|
||||
"SELECT COUNT(*) FROM ( {$this->subquery->get_query_statement()} ) AS tt"
|
||||
);
|
||||
|
||||
$this->add_sql_query_params( $query_args );
|
||||
$params = $this->get_limit_params( $query_args );
|
||||
$total_results = $db_records_count;
|
||||
$total_pages = (int) ceil( $db_records_count / $params['per_page'] );
|
||||
|
||||
if ( isset( $query_args['taxes'] ) && is_array( $query_args['taxes'] ) && ! empty( $query_args['taxes'] ) ) {
|
||||
$total_results = count( $query_args['taxes'] );
|
||||
$total_pages = (int) ceil( $total_results / $params['per_page'] );
|
||||
} else {
|
||||
$db_records_count = (int) $wpdb->get_var(
|
||||
"SELECT COUNT(*) FROM (
|
||||
{$this->subquery->get_query_statement()}
|
||||
) AS tt"
|
||||
); // WPCS: cache ok, DB call ok, unprepared SQL ok.
|
||||
|
||||
$total_results = $db_records_count;
|
||||
$total_pages = (int) ceil( $db_records_count / $params['per_page'] );
|
||||
|
||||
if ( $query_args['page'] < 1 || $query_args['page'] > $total_pages ) {
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
|
||||
$this->subquery->clear_sql_clause( 'select' );
|
||||
$this->subquery->add_sql_clause( 'select', $this->selected_columns( $query_args ) );
|
||||
$this->subquery->add_sql_clause( 'group_by', ", {$wpdb->prefix}woocommerce_order_items.order_item_name, {$wpdb->prefix}woocommerce_order_itemmeta.meta_value" );
|
||||
$this->subquery->add_sql_clause( 'order_by', $this->get_sql_clause( 'order_by' ) );
|
||||
$this->subquery->add_sql_clause( 'limit', $this->get_sql_clause( 'limit' ) );
|
||||
|
||||
$taxes_query = $this->subquery->get_query_statement();
|
||||
|
||||
$tax_data = $wpdb->get_results(
|
||||
$taxes_query,
|
||||
ARRAY_A
|
||||
); // WPCS: cache ok, DB call ok, unprepared SQL ok.
|
||||
|
||||
if ( null === $tax_data ) {
|
||||
if ( $query_args['page'] < 1 || $query_args['page'] > $total_pages ) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
$tax_data = array_map( array( $this, 'cast_numbers' ), $tax_data );
|
||||
$data = (object) array(
|
||||
'data' => $tax_data,
|
||||
'total' => $total_results,
|
||||
'pages' => $total_pages,
|
||||
'page_no' => (int) $query_args['page'],
|
||||
);
|
||||
|
||||
$this->set_cached_data( $cache_key, $data );
|
||||
}
|
||||
|
||||
$this->subquery->clear_sql_clause( 'select' );
|
||||
$this->subquery->add_sql_clause( 'select', $this->selected_columns( $query_args ) );
|
||||
$this->subquery->add_sql_clause( 'group_by', ", {$wpdb->prefix}woocommerce_order_items.order_item_name, {$wpdb->prefix}woocommerce_order_itemmeta.meta_value" );
|
||||
$this->subquery->add_sql_clause( 'order_by', $this->get_sql_clause( 'order_by' ) );
|
||||
$this->subquery->add_sql_clause( 'limit', $this->get_sql_clause( 'limit' ) );
|
||||
|
||||
$taxes_query = $this->subquery->get_query_statement();
|
||||
|
||||
$tax_data = $wpdb->get_results(
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- cache ok, DB call ok, unprepared SQL ok.
|
||||
$taxes_query,
|
||||
ARRAY_A
|
||||
);
|
||||
|
||||
if ( null === $tax_data ) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
$tax_data = array_map( array( $this, 'cast_numbers' ), $tax_data );
|
||||
$data = (object) array(
|
||||
'data' => $tax_data,
|
||||
'total' => $total_results,
|
||||
'pages' => $total_pages,
|
||||
'page_no' => (int) $query_args['page'],
|
||||
);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps ordering specified by the user to columns in the database/fields in the data.
|
||||
*
|
||||
* @override ReportsDataStore::normalize_order_by()
|
||||
*
|
||||
* @param string $order_by Sorting criterion.
|
||||
* @return string
|
||||
*/
|
||||
|
||||
@@ -21,12 +21,16 @@ use Automattic\WooCommerce\Admin\API\Reports\Query as ReportsQuery;
|
||||
|
||||
/**
|
||||
* API\Reports\Taxes\Query
|
||||
*
|
||||
* @deprecated 9.3.0 Taxes\Query class is deprecated, please use GenericQuery or \WC_Object_Query instead.
|
||||
*/
|
||||
class Query extends ReportsQuery {
|
||||
|
||||
/**
|
||||
* Valid fields for Taxes report.
|
||||
*
|
||||
* @deprecated 9.3.0 Taxes\Query class is deprecated, please use GenericQuery or \WC_Object_Query instead.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_default_query_vars() {
|
||||
@@ -36,6 +40,8 @@ class Query extends ReportsQuery {
|
||||
/**
|
||||
* Get product data based on the current query vars.
|
||||
*
|
||||
* @deprecated 9.3.0 Taxes\Query class is deprecated, please use GenericQuery or \WC_Object_Query instead.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_data() {
|
||||
|
||||
@@ -9,6 +9,7 @@ namespace Automattic\WooCommerce\Admin\API\Reports\Taxes\Stats;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
use Automattic\WooCommerce\Admin\API\Reports\GenericQuery;
|
||||
use Automattic\WooCommerce\Admin\API\Reports\GenericStatsController;
|
||||
use WP_REST_Request;
|
||||
use WP_REST_Response;
|
||||
@@ -83,47 +84,30 @@ class Controller extends GenericStatsController {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all reports.
|
||||
* Get data from `'taxes-stats'` Query.
|
||||
*
|
||||
* @param WP_REST_Request $request Request data.
|
||||
* @return array|WP_Error
|
||||
* @override GenericController::get_datastore_data()
|
||||
*
|
||||
* @param array $query_args Query arguments.
|
||||
* @return mixed Results from the data store.
|
||||
*/
|
||||
public function get_items( $request ) {
|
||||
$query_args = $this->prepare_reports_query( $request );
|
||||
$taxes_query = new Query( $query_args );
|
||||
$report_data = $taxes_query->get_data();
|
||||
|
||||
$out_data = array(
|
||||
'totals' => get_object_vars( $report_data->totals ),
|
||||
'intervals' => array(),
|
||||
);
|
||||
|
||||
foreach ( $report_data->intervals as $interval_data ) {
|
||||
$item = $this->prepare_item_for_response( (object) $interval_data, $request );
|
||||
$out_data['intervals'][] = $this->prepare_response_for_collection( $item );
|
||||
}
|
||||
|
||||
return $this->add_pagination_headers(
|
||||
$request,
|
||||
$out_data,
|
||||
(int) $report_data->total,
|
||||
(int) $report_data->page_no,
|
||||
(int) $report_data->pages
|
||||
);
|
||||
protected function get_datastore_data( $query_args = array() ) {
|
||||
$query = new GenericQuery( $query_args, 'taxes-stats' );
|
||||
return $query->get_data();
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare a report object for serialization.
|
||||
* Prepare a report data item for serialization.
|
||||
*
|
||||
* @param stdClass $report Report data.
|
||||
* @param mixed $report 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( $report, $request ) {
|
||||
$data = get_object_vars( $report );
|
||||
|
||||
$response = parent::prepare_item_for_response( $data, $request );
|
||||
$response = parent::prepare_item_for_response( $report, $request );
|
||||
|
||||
// Map to `object` for backwards compatibility.
|
||||
$report = (object) $report;
|
||||
/**
|
||||
* Filter a report returned from the API.
|
||||
*
|
||||
@@ -226,15 +210,6 @@ class Controller extends GenericStatsController {
|
||||
),
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
);
|
||||
$params['fields'] = array(
|
||||
'description' => __( 'Limit stats fields to the specified items.', 'woocommerce' ),
|
||||
'type' => 'array',
|
||||
'sanitize_callback' => 'wp_parse_slug_list',
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
'items' => array(
|
||||
'type' => 'string',
|
||||
),
|
||||
);
|
||||
|
||||
return $params;
|
||||
}
|
||||
|
||||
@@ -10,16 +10,19 @@ defined( 'ABSPATH' ) || exit;
|
||||
use Automattic\WooCommerce\Admin\API\Reports\DataStore as ReportsDataStore;
|
||||
use Automattic\WooCommerce\Admin\API\Reports\DataStoreInterface;
|
||||
use Automattic\WooCommerce\Admin\API\Reports\TimeInterval;
|
||||
use Automattic\WooCommerce\Admin\API\Reports\SqlQuery;
|
||||
use Automattic\WooCommerce\Admin\API\Reports\StatsDataStoreTrait;
|
||||
|
||||
/**
|
||||
* API\Reports\Taxes\Stats\DataStore.
|
||||
*/
|
||||
class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||
use StatsDataStoreTrait;
|
||||
|
||||
/**
|
||||
* Table used to get the data.
|
||||
*
|
||||
* @override ReportsDataStore::$table_name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected static $table_name = 'wc_order_tax_lookup';
|
||||
@@ -27,6 +30,8 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||
/**
|
||||
* Cache identifier.
|
||||
*
|
||||
* @override ReportsDataStore::$cache_key
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $cache_key = 'taxes_stats';
|
||||
@@ -34,6 +39,8 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||
/**
|
||||
* Mapping columns to data type to return correct response types.
|
||||
*
|
||||
* @override ReportsDataStore::$column_types
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $column_types = array(
|
||||
@@ -47,12 +54,16 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||
/**
|
||||
* Data store context used to pass to filters.
|
||||
*
|
||||
* @override ReportsDataStore::$context
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $context = 'taxes_stats';
|
||||
|
||||
/**
|
||||
* Assign report columns once full table name has been assigned.
|
||||
*
|
||||
* @override ReportsDataStore::assign_report_columns()
|
||||
*/
|
||||
protected function assign_report_columns() {
|
||||
$table_name = self::get_db_table_name();
|
||||
@@ -107,12 +118,12 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||
public static function get_taxes( $args ) {
|
||||
global $wpdb;
|
||||
$query = "
|
||||
SELECT
|
||||
tax_rate_id,
|
||||
tax_rate_country,
|
||||
tax_rate_state,
|
||||
tax_rate_name,
|
||||
tax_rate_priority
|
||||
SELECT
|
||||
tax_rate_id,
|
||||
tax_rate_country,
|
||||
tax_rate_state,
|
||||
tax_rate_name,
|
||||
tax_rate_priority
|
||||
FROM {$wpdb->prefix}woocommerce_tax_rates
|
||||
";
|
||||
if ( ! empty( $args['include'] ) ) {
|
||||
@@ -126,146 +137,116 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the report data based on parameters supplied by the user.
|
||||
* Get the default query arguments to be used by get_data().
|
||||
* These defaults are only partially applied when used via REST API, as that has its own defaults.
|
||||
*
|
||||
* @param array $query_args Query parameters.
|
||||
* @return stdClass|WP_Error Data.
|
||||
* @override ReportsDataStore::get_default_query_vars()
|
||||
*
|
||||
* @return array Query parameters.
|
||||
*/
|
||||
public function get_data( $query_args ) {
|
||||
public function get_default_query_vars() {
|
||||
$defaults = parent::get_default_query_vars();
|
||||
$defaults['orderby'] = 'tax_rate_id';
|
||||
$defaults['taxes'] = array();
|
||||
|
||||
return $defaults;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the report data based on normalized parameters.
|
||||
* Will be called by `get_data` if there is no data in cache.
|
||||
*
|
||||
* @override ReportsDataStore::get_noncached_data()
|
||||
*
|
||||
* @see get_data
|
||||
* @see get_noncached_stats_data
|
||||
* @param array $query_args Query parameters.
|
||||
* @param array $params Query limit parameters.
|
||||
* @param stdClass $data Reference to the data object to fill.
|
||||
* @param int $expected_interval_count Number of expected intervals.
|
||||
* @return stdClass|WP_Error Data object `{ totals: *, intervals: array, total: int, pages: int, page_no: int }`, or error.
|
||||
*/
|
||||
public function get_noncached_stats_data( $query_args, $params, &$data, $expected_interval_count ) {
|
||||
global $wpdb;
|
||||
|
||||
$table_name = self::get_db_table_name();
|
||||
|
||||
// These defaults are only partially applied when used via REST API, as that has its own defaults.
|
||||
$defaults = array(
|
||||
'per_page' => get_option( 'posts_per_page' ),
|
||||
'page' => 1,
|
||||
'order' => 'DESC',
|
||||
'orderby' => 'tax_rate_id',
|
||||
'before' => TimeInterval::default_before(),
|
||||
'after' => TimeInterval::default_after(),
|
||||
'fields' => '*',
|
||||
'taxes' => array(),
|
||||
$this->initialize_queries();
|
||||
|
||||
$selections = $this->selected_columns( $query_args );
|
||||
$order_stats_join = "JOIN {$wpdb->prefix}wc_order_stats ON {$table_name}.order_id = {$wpdb->prefix}wc_order_stats.order_id";
|
||||
$this->update_sql_query_params( $query_args );
|
||||
$this->interval_query->add_sql_clause( 'join', $order_stats_join );
|
||||
|
||||
$db_intervals = $wpdb->get_col(
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- cache ok, DB call ok, unprepared SQL ok.
|
||||
$this->interval_query->get_query_statement()
|
||||
);
|
||||
$query_args = wp_parse_args( $query_args, $defaults );
|
||||
$this->normalize_timezones( $query_args, $defaults );
|
||||
$db_interval_count = count( $db_intervals );
|
||||
|
||||
/*
|
||||
* We need to get the cache key here because
|
||||
* parent::update_intervals_sql_params() modifies $query_args.
|
||||
*/
|
||||
$cache_key = $this->get_cache_key( $query_args );
|
||||
$data = $this->get_cached_data( $cache_key );
|
||||
$this->total_query->add_sql_clause( 'select', $selections );
|
||||
$this->total_query->add_sql_clause( 'join', $order_stats_join );
|
||||
$this->total_query->add_sql_clause( 'where_time', $this->get_sql_clause( 'where_time' ) );
|
||||
|
||||
if ( false === $data ) {
|
||||
$this->initialize_queries();
|
||||
$totals = $wpdb->get_results(
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- cache ok, DB call ok, unprepared SQL ok.
|
||||
$this->total_query->get_query_statement(),
|
||||
ARRAY_A
|
||||
);
|
||||
|
||||
$data = (object) array(
|
||||
'totals' => (object) array(),
|
||||
'intervals' => (object) array(),
|
||||
'total' => 0,
|
||||
'pages' => 0,
|
||||
'page_no' => 0,
|
||||
);
|
||||
|
||||
$selections = $this->selected_columns( $query_args );
|
||||
$params = $this->get_limit_params( $query_args );
|
||||
$order_stats_join = "JOIN {$wpdb->prefix}wc_order_stats ON {$table_name}.order_id = {$wpdb->prefix}wc_order_stats.order_id";
|
||||
$this->update_sql_query_params( $query_args );
|
||||
$this->interval_query->add_sql_clause( 'join', $order_stats_join );
|
||||
|
||||
$db_intervals = $wpdb->get_col(
|
||||
$this->interval_query->get_query_statement()
|
||||
); // WPCS: cache ok, DB call ok, unprepared SQL ok.
|
||||
$db_interval_count = count( $db_intervals );
|
||||
$expected_interval_count = TimeInterval::intervals_between( $query_args['after'], $query_args['before'], $query_args['interval'] );
|
||||
$total_pages = (int) ceil( $expected_interval_count / $params['per_page'] );
|
||||
|
||||
if ( $query_args['page'] < 1 || $query_args['page'] > $total_pages ) {
|
||||
return $data;
|
||||
}
|
||||
$this->total_query->add_sql_clause( 'select', $selections );
|
||||
$this->total_query->add_sql_clause( 'join', $order_stats_join );
|
||||
$this->total_query->add_sql_clause( 'where_time', $this->get_sql_clause( 'where_time' ) );
|
||||
|
||||
$totals = $wpdb->get_results(
|
||||
$this->total_query->get_query_statement(),
|
||||
ARRAY_A
|
||||
); // WPCS: cache ok, DB call ok, unprepared SQL ok.
|
||||
|
||||
if ( null === $totals ) {
|
||||
return new \WP_Error( 'woocommerce_analytics_taxes_stats_result_failed', __( 'Sorry, fetching revenue data failed.', 'woocommerce' ) );
|
||||
}
|
||||
|
||||
// @todo remove these assignements when refactoring segmenter classes to use query objects.
|
||||
$totals_query = array(
|
||||
'from_clause' => $this->total_query->get_sql_clause( 'join' ),
|
||||
'where_time_clause' => $this->total_query->get_sql_clause( 'where_time' ),
|
||||
'where_clause' => $this->total_query->get_sql_clause( 'where' ),
|
||||
);
|
||||
$intervals_query = array(
|
||||
'select_clause' => $this->get_sql_clause( 'select' ),
|
||||
'from_clause' => $this->interval_query->get_sql_clause( 'join' ),
|
||||
'where_time_clause' => $this->interval_query->get_sql_clause( 'where_time' ),
|
||||
'where_clause' => $this->interval_query->get_sql_clause( 'where' ),
|
||||
);
|
||||
$segmenter = new Segmenter( $query_args, $this->report_columns );
|
||||
$totals[0]['segments'] = $segmenter->get_totals_segments( $totals_query, $table_name );
|
||||
|
||||
$this->update_intervals_sql_params( $query_args, $db_interval_count, $expected_interval_count, $table_name );
|
||||
|
||||
if ( '' !== $selections ) {
|
||||
$this->interval_query->add_sql_clause( 'select', ', ' . $selections );
|
||||
}
|
||||
|
||||
$this->interval_query->add_sql_clause( 'select', ", MAX({$table_name}.date_created) AS datetime_anchor" );
|
||||
$this->interval_query->add_sql_clause( 'order_by', $this->get_sql_clause( 'order_by' ) );
|
||||
$this->interval_query->add_sql_clause( 'limit', $this->get_sql_clause( 'limit' ) );
|
||||
|
||||
$intervals = $wpdb->get_results(
|
||||
$this->interval_query->get_query_statement(),
|
||||
ARRAY_A
|
||||
); // WPCS: cache ok, DB call ok, unprepared SQL ok.
|
||||
|
||||
if ( null === $intervals ) {
|
||||
return new \WP_Error( 'woocommerce_analytics_taxes_stats_result_failed', __( 'Sorry, fetching tax data failed.', 'woocommerce' ) );
|
||||
}
|
||||
|
||||
$totals = (object) $this->cast_numbers( $totals[0] );
|
||||
|
||||
$data = (object) array(
|
||||
'totals' => $totals,
|
||||
'intervals' => $intervals,
|
||||
'total' => $expected_interval_count,
|
||||
'pages' => $total_pages,
|
||||
'page_no' => (int) $query_args['page'],
|
||||
);
|
||||
|
||||
if ( TimeInterval::intervals_missing( $expected_interval_count, $db_interval_count, $params['per_page'], $query_args['page'], $query_args['order'], $query_args['orderby'], count( $intervals ) ) ) {
|
||||
$this->fill_in_missing_intervals( $db_intervals, $query_args['adj_after'], $query_args['adj_before'], $query_args['interval'], $data );
|
||||
$this->sort_intervals( $data, $query_args['orderby'], $query_args['order'] );
|
||||
$this->remove_extra_records( $data, $query_args['page'], $params['per_page'], $db_interval_count, $expected_interval_count, $query_args['orderby'], $query_args['order'] );
|
||||
} else {
|
||||
$this->update_interval_boundary_dates( $query_args['after'], $query_args['before'], $query_args['interval'], $data->intervals );
|
||||
}
|
||||
$segmenter->add_intervals_segments( $data, $intervals_query, $table_name );
|
||||
$this->create_interval_subtotals( $data->intervals );
|
||||
$this->set_cached_data( $cache_key, $data );
|
||||
if ( null === $totals ) {
|
||||
return new \WP_Error( 'woocommerce_analytics_taxes_stats_result_failed', __( 'Sorry, fetching revenue data failed.', 'woocommerce' ) );
|
||||
}
|
||||
|
||||
// phpcs:ignore Generic.Commenting.Todo.TaskFound
|
||||
// @todo remove these assignements when refactoring segmenter classes to use query objects.
|
||||
$totals_query = array(
|
||||
'from_clause' => $this->total_query->get_sql_clause( 'join' ),
|
||||
'where_time_clause' => $this->total_query->get_sql_clause( 'where_time' ),
|
||||
'where_clause' => $this->total_query->get_sql_clause( 'where' ),
|
||||
);
|
||||
$intervals_query = array(
|
||||
'select_clause' => $this->get_sql_clause( 'select' ),
|
||||
'from_clause' => $this->interval_query->get_sql_clause( 'join' ),
|
||||
'where_time_clause' => $this->interval_query->get_sql_clause( 'where_time' ),
|
||||
'where_clause' => $this->interval_query->get_sql_clause( 'where' ),
|
||||
);
|
||||
$segmenter = new Segmenter( $query_args, $this->report_columns );
|
||||
$totals[0]['segments'] = $segmenter->get_totals_segments( $totals_query, $table_name );
|
||||
|
||||
$this->update_intervals_sql_params( $query_args, $db_interval_count, $expected_interval_count, $table_name );
|
||||
|
||||
if ( '' !== $selections ) {
|
||||
$this->interval_query->add_sql_clause( 'select', ', ' . $selections );
|
||||
}
|
||||
|
||||
$this->interval_query->add_sql_clause( 'select', ", MAX({$table_name}.date_created) AS datetime_anchor" );
|
||||
$this->interval_query->add_sql_clause( 'order_by', $this->get_sql_clause( 'order_by' ) );
|
||||
$this->interval_query->add_sql_clause( 'limit', $this->get_sql_clause( 'limit' ) );
|
||||
|
||||
$intervals = $wpdb->get_results(
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- cache ok, DB call ok, unprepared SQL ok.
|
||||
$this->interval_query->get_query_statement(),
|
||||
ARRAY_A
|
||||
);
|
||||
|
||||
if ( null === $intervals ) {
|
||||
return new \WP_Error( 'woocommerce_analytics_taxes_stats_result_failed', __( 'Sorry, fetching tax data failed.', 'woocommerce' ) );
|
||||
}
|
||||
|
||||
$totals = (object) $this->cast_numbers( $totals[0] );
|
||||
|
||||
$data->totals = $totals;
|
||||
$data->intervals = $intervals;
|
||||
|
||||
if ( TimeInterval::intervals_missing( $expected_interval_count, $db_interval_count, $params['per_page'], $query_args['page'], $query_args['order'], $query_args['orderby'], count( $intervals ) ) ) {
|
||||
$this->fill_in_missing_intervals( $db_intervals, $query_args['adj_after'], $query_args['adj_before'], $query_args['interval'], $data );
|
||||
$this->sort_intervals( $data, $query_args['orderby'], $query_args['order'] );
|
||||
$this->remove_extra_records( $data, $query_args['page'], $params['per_page'], $db_interval_count, $expected_interval_count, $query_args['orderby'], $query_args['order'] );
|
||||
} else {
|
||||
$this->update_interval_boundary_dates( $query_args['after'], $query_args['before'], $query_args['interval'], $data->intervals );
|
||||
}
|
||||
$segmenter->add_intervals_segments( $data, $intervals_query, $table_name );
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize query objects.
|
||||
*/
|
||||
protected function initialize_queries() {
|
||||
$this->clear_all_clauses();
|
||||
unset( $this->subquery );
|
||||
$this->total_query = new SqlQuery( $this->context . '_total' );
|
||||
$this->total_query->add_sql_clause( 'from', self::get_db_table_name() );
|
||||
|
||||
$this->interval_query = new SqlQuery( $this->context . '_interval' );
|
||||
$this->interval_query->add_sql_clause( 'from', self::get_db_table_name() );
|
||||
$this->interval_query->add_sql_clause( 'group_by', 'time_interval' );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,12 +22,16 @@ use Automattic\WooCommerce\Admin\API\Reports\Query as ReportsQuery;
|
||||
|
||||
/**
|
||||
* API\Reports\Taxes\Stats\Query
|
||||
*
|
||||
* @deprecated 9.3.0 Taxes\Stats\Query class is deprecated, please use GenericQuery or \WC_Object_Query instead.
|
||||
*/
|
||||
class Query extends ReportsQuery {
|
||||
|
||||
/**
|
||||
* Valid fields for Taxes report.
|
||||
*
|
||||
* @deprecated 9.3.0 Taxes\Stats\Query class is deprecated, please use GenericQuery or \WC_Object_Query instead.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_default_query_vars() {
|
||||
@@ -37,6 +41,8 @@ class Query extends ReportsQuery {
|
||||
/**
|
||||
* Get tax stats data based on the current query vars.
|
||||
*
|
||||
* @deprecated 9.3.0 Taxes\Stats\Query class is deprecated, please use GenericQuery or \WC_Object_Query instead.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_data() {
|
||||
|
||||
@@ -9,17 +9,24 @@ namespace Automattic\WooCommerce\Admin\API\Reports\Variations;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
use Automattic\WooCommerce\Admin\API\Reports\Controller as ReportsController;
|
||||
use Automattic\WooCommerce\Admin\API\Reports\ExportableInterface;
|
||||
use Automattic\WooCommerce\Admin\API\Reports\ExportableTraits;
|
||||
use Automattic\WooCommerce\Admin\API\Reports\GenericController;
|
||||
use Automattic\WooCommerce\Admin\API\Reports\GenericQuery;
|
||||
use Automattic\WooCommerce\Admin\API\Reports\OrderAwareControllerTrait;
|
||||
|
||||
|
||||
/**
|
||||
* REST API Reports products controller class.
|
||||
*
|
||||
* @internal
|
||||
* @extends ReportsController
|
||||
* @extends GenericController
|
||||
*/
|
||||
class Controller extends ReportsController implements ExportableInterface {
|
||||
class Controller extends GenericController implements ExportableInterface {
|
||||
|
||||
// The controller does not use this trait. It's here for API backward compatibility.
|
||||
use OrderAwareControllerTrait;
|
||||
|
||||
/**
|
||||
* Exportable traits.
|
||||
*/
|
||||
@@ -43,13 +50,52 @@ class Controller extends ReportsController implements ExportableInterface {
|
||||
);
|
||||
|
||||
/**
|
||||
* Get items.
|
||||
* Get data from `'variations'` Query.
|
||||
*
|
||||
* @param WP_REST_Request $request Request data.
|
||||
* @override GenericController::get_datastore_data()
|
||||
*
|
||||
* @return array|WP_Error
|
||||
* @param array $query_args Query arguments.
|
||||
* @return mixed Results from the data store.
|
||||
*/
|
||||
public function get_items( $request ) {
|
||||
protected function get_datastore_data( $query_args = array() ) {
|
||||
$query = new GenericQuery( $query_args, 'variations' );
|
||||
return $query->get_data();
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare a report data item for serialization.
|
||||
*
|
||||
* @param array $report 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( $report, $request ) {
|
||||
// Wrap the data in a response object.
|
||||
$response = parent::prepare_item_for_response( $report, $request );
|
||||
|
||||
$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.
|
||||
*
|
||||
* @since 6.5.0
|
||||
*
|
||||
* @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_variations', $response, $report, $request );
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps query arguments from the REST request.
|
||||
*
|
||||
* @param array $request Request array.
|
||||
* @return array
|
||||
*/
|
||||
protected function prepare_reports_query( $request ) {
|
||||
$args = array();
|
||||
/**
|
||||
* Experimental: Filter the list of parameters provided when querying data from the data store.
|
||||
@@ -57,6 +103,8 @@ class Controller extends ReportsController implements ExportableInterface {
|
||||
* @ignore
|
||||
*
|
||||
* @param array $collection_params List of parameters.
|
||||
*
|
||||
* @since 6.5.0
|
||||
*/
|
||||
$collection_params = apply_filters(
|
||||
'experimental_woocommerce_analytics_variations_collection_params',
|
||||
@@ -72,54 +120,7 @@ class Controller extends ReportsController implements ExportableInterface {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$reports = new 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 );
|
||||
}
|
||||
|
||||
return $this->add_pagination_headers(
|
||||
$request,
|
||||
$data,
|
||||
(int) $products_data->total,
|
||||
(int) $products_data->page_no,
|
||||
(int) $products_data->pages
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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_variations', $response, $report, $request );
|
||||
return $args;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -244,38 +245,15 @@ class Controller extends ReportsController implements ExportableInterface {
|
||||
* @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 = parent::get_collection_params();
|
||||
$params['orderby']['enum'] = array(
|
||||
'date',
|
||||
'net_revenue',
|
||||
'orders_count',
|
||||
'items_sold',
|
||||
'sku',
|
||||
);
|
||||
$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['match'] = array(
|
||||
$params['match'] = array(
|
||||
'description' => __( 'Indicates whether all the conditions should be true for the resulting set, or if any one of them is sufficient. Match affects the following parameters: status_is, status_is_not, product_includes, product_excludes, coupon_includes, coupon_excludes, customer, categories', 'woocommerce' ),
|
||||
'type' => 'string',
|
||||
'default' => 'all',
|
||||
@@ -285,27 +263,7 @@ class Controller extends ReportsController implements ExportableInterface {
|
||||
),
|
||||
'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',
|
||||
'items_sold',
|
||||
'sku',
|
||||
),
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
);
|
||||
$params['product_includes'] = array(
|
||||
$params['product_includes'] = array(
|
||||
'description' => __( 'Limit result set to items that have the specified parent product(s).', 'woocommerce' ),
|
||||
'type' => 'array',
|
||||
'items' => array(
|
||||
@@ -315,7 +273,7 @@ class Controller extends ReportsController implements ExportableInterface {
|
||||
'sanitize_callback' => 'wp_parse_id_list',
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
);
|
||||
$params['product_excludes'] = array(
|
||||
$params['product_excludes'] = array(
|
||||
'description' => __( 'Limit result set to items that don\'t have the specified parent product(s).', 'woocommerce' ),
|
||||
'type' => 'array',
|
||||
'items' => array(
|
||||
@@ -325,7 +283,7 @@ class Controller extends ReportsController implements ExportableInterface {
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
'sanitize_callback' => 'wp_parse_id_list',
|
||||
);
|
||||
$params['variations'] = array(
|
||||
$params['variations'] = array(
|
||||
'description' => __( 'Limit result to items with specified variation ids.', 'woocommerce' ),
|
||||
'type' => 'array',
|
||||
'sanitize_callback' => 'wp_parse_id_list',
|
||||
@@ -334,14 +292,14 @@ class Controller extends ReportsController implements ExportableInterface {
|
||||
'type' => 'integer',
|
||||
),
|
||||
);
|
||||
$params['extended_info'] = array(
|
||||
$params['extended_info'] = array(
|
||||
'description' => __( 'Add additional piece of info about each variation to the report.', 'woocommerce' ),
|
||||
'type' => 'boolean',
|
||||
'default' => false,
|
||||
'sanitize_callback' => 'wc_string_to_bool',
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
);
|
||||
$params['attribute_is'] = array(
|
||||
$params['attribute_is'] = array(
|
||||
'description' => __( 'Limit result set to variations that include the specified attributes.', 'woocommerce' ),
|
||||
'type' => 'array',
|
||||
'items' => array(
|
||||
@@ -350,7 +308,7 @@ class Controller extends ReportsController implements ExportableInterface {
|
||||
'default' => array(),
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
);
|
||||
$params['attribute_is_not'] = array(
|
||||
$params['attribute_is_not'] = array(
|
||||
'description' => __( 'Limit result set to variations that don\'t include the specified attributes.', 'woocommerce' ),
|
||||
'type' => 'array',
|
||||
'items' => array(
|
||||
@@ -359,7 +317,7 @@ class Controller extends ReportsController implements ExportableInterface {
|
||||
'default' => array(),
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
);
|
||||
$params['category_includes'] = array(
|
||||
$params['category_includes'] = array(
|
||||
'description' => __( 'Limit result set to variations in the specified categories.', 'woocommerce' ),
|
||||
'type' => 'array',
|
||||
'sanitize_callback' => 'wp_parse_id_list',
|
||||
@@ -368,7 +326,7 @@ class Controller extends ReportsController implements ExportableInterface {
|
||||
'type' => 'integer',
|
||||
),
|
||||
);
|
||||
$params['category_excludes'] = array(
|
||||
$params['category_excludes'] = array(
|
||||
'description' => __( 'Limit result set to variations not in the specified categories.', 'woocommerce' ),
|
||||
'type' => 'array',
|
||||
'sanitize_callback' => 'wp_parse_id_list',
|
||||
@@ -377,13 +335,7 @@ class Controller extends ReportsController implements ExportableInterface {
|
||||
'type' => 'integer',
|
||||
),
|
||||
);
|
||||
$params['force_cache_refresh'] = array(
|
||||
'description' => __( 'Force retrieval of fresh data instead of from the cache.', 'woocommerce' ),
|
||||
'type' => 'boolean',
|
||||
'sanitize_callback' => 'wp_validate_boolean',
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
);
|
||||
$params['products'] = array(
|
||||
$params['products'] = array(
|
||||
'description' => __( 'Limit result to items with specified product ids.', 'woocommerce' ),
|
||||
'type' => 'array',
|
||||
'sanitize_callback' => 'wp_parse_id_list',
|
||||
|
||||
@@ -9,7 +9,6 @@ defined( 'ABSPATH' ) || exit;
|
||||
|
||||
use Automattic\WooCommerce\Admin\API\Reports\DataStore as ReportsDataStore;
|
||||
use Automattic\WooCommerce\Admin\API\Reports\DataStoreInterface;
|
||||
use Automattic\WooCommerce\Admin\API\Reports\TimeInterval;
|
||||
use Automattic\WooCommerce\Admin\API\Reports\SqlQuery;
|
||||
|
||||
/**
|
||||
@@ -20,6 +19,8 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||
/**
|
||||
* Table used to get the data.
|
||||
*
|
||||
* @override ReportsDataStore::$table_name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected static $table_name = 'wc_order_product_lookup';
|
||||
@@ -27,6 +28,8 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||
/**
|
||||
* Cache identifier.
|
||||
*
|
||||
* @override ReportsDataStore::$cache_key
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $cache_key = 'variations';
|
||||
@@ -34,6 +37,8 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||
/**
|
||||
* Mapping columns to data type to return correct response types.
|
||||
*
|
||||
* @override ReportsDataStore::$column_types
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $column_types = array(
|
||||
@@ -70,12 +75,16 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||
/**
|
||||
* Data store context used to pass to filters.
|
||||
*
|
||||
* @override ReportsDataStore::$context
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $context = 'variations';
|
||||
|
||||
/**
|
||||
* Assign report columns once full table name has been assigned.
|
||||
*
|
||||
* @override ReportsDataStore::assign_report_columns()
|
||||
*/
|
||||
protected function assign_report_columns() {
|
||||
$table_name = self::get_db_table_name();
|
||||
@@ -209,6 +218,8 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||
/**
|
||||
* Maps ordering specified by the user to columns in the database/fields in the data.
|
||||
*
|
||||
* @override ReportsDataStore::normalize_order_by()
|
||||
*
|
||||
* @param string $order_by Sorting criterion.
|
||||
*
|
||||
* @return string
|
||||
@@ -372,146 +383,139 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the report data based on parameters supplied by the user.
|
||||
* Get the default query arguments to be used by get_data().
|
||||
* These defaults are only partially applied when used via REST API, as that has its own defaults.
|
||||
*
|
||||
* @param array $query_args Query parameters.
|
||||
* @override ReportsDataStore::get_default_query_vars()
|
||||
*
|
||||
* @return stdClass|WP_Error Data.
|
||||
* @return array Query parameters.
|
||||
*/
|
||||
public function get_data( $query_args ) {
|
||||
public function get_default_query_vars() {
|
||||
$defaults = parent::get_default_query_vars();
|
||||
$defaults['product_includes'] = array();
|
||||
$defaults['variation_includes'] = array();
|
||||
$defaults['extended_info'] = false;
|
||||
|
||||
return $defaults;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the report data based on normalized parameters.
|
||||
* Will be called by `get_data` if there is no data in cache.
|
||||
*
|
||||
* @override ReportsDataStore::get_noncached_data()
|
||||
*
|
||||
* @see get_data
|
||||
* @param array $query_args Query parameters.
|
||||
* @return stdClass|WP_Error Data object `{ totals: *, intervals: array, total: int, pages: int, page_no: int }`, or error.
|
||||
*/
|
||||
public function get_noncached_data( $query_args ) {
|
||||
global $wpdb;
|
||||
|
||||
$table_name = self::get_db_table_name();
|
||||
|
||||
// These defaults are only partially applied when used via REST API, as that has its own defaults.
|
||||
$defaults = array(
|
||||
'per_page' => get_option( 'posts_per_page' ),
|
||||
'page' => 1,
|
||||
'order' => 'DESC',
|
||||
'orderby' => 'date',
|
||||
'before' => TimeInterval::default_before(),
|
||||
'after' => TimeInterval::default_after(),
|
||||
'fields' => '*',
|
||||
'product_includes' => array(),
|
||||
'variation_includes' => array(),
|
||||
'extended_info' => false,
|
||||
$this->initialize_queries();
|
||||
|
||||
$data = (object) array(
|
||||
'data' => array(),
|
||||
'total' => 0,
|
||||
'pages' => 0,
|
||||
'page_no' => 0,
|
||||
);
|
||||
$query_args = wp_parse_args( $query_args, $defaults );
|
||||
$this->normalize_timezones( $query_args, $defaults );
|
||||
|
||||
/*
|
||||
* We need to get the cache key here because
|
||||
* parent::update_intervals_sql_params() modifies $query_args.
|
||||
*/
|
||||
$cache_key = $this->get_cache_key( $query_args );
|
||||
$data = $this->get_cached_data( $cache_key );
|
||||
$selections = $this->selected_columns( $query_args );
|
||||
$included_variations =
|
||||
( isset( $query_args['variation_includes'] ) && is_array( $query_args['variation_includes'] ) )
|
||||
? $query_args['variation_includes']
|
||||
: array();
|
||||
$params = $this->get_limit_params( $query_args );
|
||||
$this->add_sql_query_params( $query_args );
|
||||
|
||||
if ( false === $data ) {
|
||||
$this->initialize_queries();
|
||||
if ( count( $included_variations ) > 0 ) {
|
||||
$total_results = count( $included_variations );
|
||||
$total_pages = (int) ceil( $total_results / $params['per_page'] );
|
||||
|
||||
$data = (object) array(
|
||||
'data' => array(),
|
||||
'total' => 0,
|
||||
'pages' => 0,
|
||||
'page_no' => 0,
|
||||
);
|
||||
$this->subquery->clear_sql_clause( 'select' );
|
||||
$this->subquery->add_sql_clause( 'select', $selections );
|
||||
|
||||
$selections = $this->selected_columns( $query_args );
|
||||
$included_variations =
|
||||
( isset( $query_args['variation_includes'] ) && is_array( $query_args['variation_includes'] ) )
|
||||
? $query_args['variation_includes']
|
||||
: array();
|
||||
$params = $this->get_limit_params( $query_args );
|
||||
$this->add_sql_query_params( $query_args );
|
||||
|
||||
if ( count( $included_variations ) > 0 ) {
|
||||
$total_results = count( $included_variations );
|
||||
$total_pages = (int) ceil( $total_results / $params['per_page'] );
|
||||
|
||||
$this->subquery->clear_sql_clause( 'select' );
|
||||
$this->subquery->add_sql_clause( 'select', $selections );
|
||||
|
||||
if ( 'date' === $query_args['orderby'] ) {
|
||||
$this->subquery->add_sql_clause( 'select', ", {$table_name}.date_created" );
|
||||
}
|
||||
|
||||
$fields = $this->get_fields( $query_args );
|
||||
$join_selections = $this->format_join_selections( $fields, array( 'variation_id' ) );
|
||||
$ids_table = $this->get_ids_table( $included_variations, 'variation_id' );
|
||||
|
||||
$this->add_sql_clause( 'select', $join_selections );
|
||||
$this->add_sql_clause( 'from', '(' );
|
||||
$this->add_sql_clause( 'from', $this->subquery->get_query_statement() );
|
||||
$this->add_sql_clause( 'from', ") AS {$table_name}" );
|
||||
$this->add_sql_clause(
|
||||
'right_join',
|
||||
"RIGHT JOIN ( {$ids_table} ) AS default_results
|
||||
ON default_results.variation_id = {$table_name}.variation_id"
|
||||
);
|
||||
|
||||
$variations_query = $this->get_query_statement();
|
||||
} else {
|
||||
|
||||
$this->subquery->clear_sql_clause( 'select' );
|
||||
$this->subquery->add_sql_clause( 'select', $selections );
|
||||
|
||||
/**
|
||||
* Experimental: Filter the Variations SQL query allowing extensions to add additional SQL clauses.
|
||||
*
|
||||
* @since 7.4.0
|
||||
* @param array $query_args Query parameters.
|
||||
* @param SqlQuery $subquery Variations query class.
|
||||
*/
|
||||
apply_filters( 'experimental_woocommerce_analytics_variations_additional_clauses', $query_args, $this->subquery );
|
||||
|
||||
/* phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared */
|
||||
$db_records_count = (int) $wpdb->get_var(
|
||||
"SELECT COUNT(*) FROM (
|
||||
{$this->subquery->get_query_statement()}
|
||||
) AS tt"
|
||||
);
|
||||
/* phpcs:enable */
|
||||
|
||||
$total_results = $db_records_count;
|
||||
$total_pages = (int) ceil( $db_records_count / $params['per_page'] );
|
||||
|
||||
if ( $query_args['page'] < 1 || $query_args['page'] > $total_pages ) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
$this->subquery->add_sql_clause( 'order_by', $this->get_sql_clause( 'order_by' ) );
|
||||
$this->subquery->add_sql_clause( 'limit', $this->get_sql_clause( 'limit' ) );
|
||||
$variations_query = $this->subquery->get_query_statement();
|
||||
if ( 'date' === $query_args['orderby'] ) {
|
||||
$this->subquery->add_sql_clause( 'select', ", {$table_name}.date_created" );
|
||||
}
|
||||
|
||||
/* phpcs:disable WordPress.DB.PreparedSQL.NotPrepared */
|
||||
$product_data = $wpdb->get_results(
|
||||
$variations_query,
|
||||
ARRAY_A
|
||||
$fields = $this->get_fields( $query_args );
|
||||
$join_selections = $this->format_join_selections( $fields, array( 'variation_id' ) );
|
||||
$ids_table = $this->get_ids_table( $included_variations, 'variation_id' );
|
||||
|
||||
$this->add_sql_clause( 'select', $join_selections );
|
||||
$this->add_sql_clause( 'from', '(' );
|
||||
$this->add_sql_clause( 'from', $this->subquery->get_query_statement() );
|
||||
$this->add_sql_clause( 'from', ") AS {$table_name}" );
|
||||
$this->add_sql_clause(
|
||||
'right_join',
|
||||
"RIGHT JOIN ( {$ids_table} ) AS default_results
|
||||
ON default_results.variation_id = {$table_name}.variation_id"
|
||||
);
|
||||
|
||||
$variations_query = $this->get_query_statement();
|
||||
} else {
|
||||
|
||||
$this->subquery->clear_sql_clause( 'select' );
|
||||
$this->subquery->add_sql_clause( 'select', $selections );
|
||||
|
||||
/**
|
||||
* Experimental: Filter the Variations SQL query allowing extensions to add additional SQL clauses.
|
||||
*
|
||||
* @since 7.4.0
|
||||
* @param array $query_args Query parameters.
|
||||
* @param SqlQuery $subquery Variations query class.
|
||||
*/
|
||||
apply_filters( 'experimental_woocommerce_analytics_variations_additional_clauses', $query_args, $this->subquery );
|
||||
|
||||
/* phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared */
|
||||
$db_records_count = (int) $wpdb->get_var(
|
||||
"SELECT COUNT(*) FROM (
|
||||
{$this->subquery->get_query_statement()}
|
||||
) AS tt"
|
||||
);
|
||||
/* phpcs:enable */
|
||||
|
||||
if ( null === $product_data ) {
|
||||
$total_results = $db_records_count;
|
||||
$total_pages = (int) ceil( $db_records_count / $params['per_page'] );
|
||||
|
||||
if ( $query_args['page'] < 1 || $query_args['page'] > $total_pages ) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
$this->include_extended_info( $product_data, $query_args );
|
||||
|
||||
if ( $query_args['extended_info'] ) {
|
||||
$this->fill_deleted_product_name( $product_data );
|
||||
}
|
||||
|
||||
$product_data = array_map( array( $this, 'cast_numbers' ), $product_data );
|
||||
$data = (object) array(
|
||||
'data' => $product_data,
|
||||
'total' => $total_results,
|
||||
'pages' => $total_pages,
|
||||
'page_no' => (int) $query_args['page'],
|
||||
);
|
||||
|
||||
$this->set_cached_data( $cache_key, $data );
|
||||
$this->subquery->add_sql_clause( 'order_by', $this->get_sql_clause( 'order_by' ) );
|
||||
$this->subquery->add_sql_clause( 'limit', $this->get_sql_clause( 'limit' ) );
|
||||
$variations_query = $this->subquery->get_query_statement();
|
||||
}
|
||||
|
||||
/* phpcs:disable WordPress.DB.PreparedSQL.NotPrepared */
|
||||
$product_data = $wpdb->get_results(
|
||||
$variations_query,
|
||||
ARRAY_A
|
||||
);
|
||||
/* phpcs:enable */
|
||||
|
||||
if ( null === $product_data ) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
$this->include_extended_info( $product_data, $query_args );
|
||||
|
||||
if ( $query_args['extended_info'] ) {
|
||||
$this->fill_deleted_product_name( $product_data );
|
||||
}
|
||||
|
||||
$product_data = array_map( array( $this, 'cast_numbers' ), $product_data );
|
||||
$data = (object) array(
|
||||
'data' => $product_data,
|
||||
'total' => $total_results,
|
||||
'pages' => $total_pages,
|
||||
'page_no' => (int) $query_args['page'],
|
||||
);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
|
||||
@@ -22,12 +22,16 @@ use Automattic\WooCommerce\Admin\API\Reports\Query as ReportsQuery;
|
||||
|
||||
/**
|
||||
* API\Reports\Variations\Query
|
||||
*
|
||||
* @deprecated 9.3.0 Variations\Query class is deprecated, please use GenericQuery or \WC_Object_Query instead.
|
||||
*/
|
||||
class Query extends ReportsQuery {
|
||||
|
||||
/**
|
||||
* Valid fields for Products report.
|
||||
*
|
||||
* @deprecated 9.3.0 Variations\Query class is deprecated, please use GenericQuery or \WC_Object_Query instead.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_default_query_vars() {
|
||||
@@ -37,6 +41,8 @@ class Query extends ReportsQuery {
|
||||
/**
|
||||
* Get product data based on the current query vars.
|
||||
*
|
||||
* @deprecated 9.3.0 Variations\Query class is deprecated, please use GenericQuery or \WC_Object_Query instead.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_data() {
|
||||
|
||||
@@ -9,8 +9,8 @@ namespace Automattic\WooCommerce\Admin\API\Reports\Variations\Stats;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
use Automattic\WooCommerce\Admin\API\Reports\GenericQuery;
|
||||
use Automattic\WooCommerce\Admin\API\Reports\GenericStatsController;
|
||||
use Automattic\WooCommerce\Admin\API\Reports\ParameterException;
|
||||
use WP_REST_Request;
|
||||
use WP_REST_Response;
|
||||
|
||||
@@ -46,12 +46,25 @@ class Controller extends GenericStatsController {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all reports.
|
||||
* Get data from `'variations-stats'` Query.
|
||||
*
|
||||
* @param WP_REST_Request $request Request data.
|
||||
* @return array|WP_Error
|
||||
* @override GenericController::get_datastore_data()
|
||||
*
|
||||
* @param array $query_args Query arguments.
|
||||
* @return mixed Results from the data store.
|
||||
*/
|
||||
public function get_items( $request ) {
|
||||
protected function get_datastore_data( $query_args = array() ) {
|
||||
$query = new GenericQuery( $query_args, 'variations-stats' );
|
||||
return $query->get_data();
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps query arguments from the REST request, to be fed to Query.
|
||||
*
|
||||
* @param \WP_REST_Request $request Full request object.
|
||||
* @return array Simplified array of params.
|
||||
*/
|
||||
protected function prepare_reports_query( $request ) {
|
||||
$query_args = array(
|
||||
'fields' => array(
|
||||
'items_sold',
|
||||
@@ -79,36 +92,13 @@ class Controller extends GenericStatsController {
|
||||
}
|
||||
}
|
||||
|
||||
$query = new Query( $query_args );
|
||||
try {
|
||||
$report_data = $query->get_data();
|
||||
} catch ( ParameterException $e ) {
|
||||
return new \WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) );
|
||||
}
|
||||
|
||||
$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 );
|
||||
}
|
||||
|
||||
return $this->add_pagination_headers(
|
||||
$request,
|
||||
$out_data,
|
||||
(int) $report_data->total,
|
||||
(int) $report_data->page_no,
|
||||
(int) $report_data->pages
|
||||
);
|
||||
return $query_args;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare a report object for serialization.
|
||||
* Prepare a report data item for serialization.
|
||||
*
|
||||
* @param array $report Report data.
|
||||
* @param array $report Report data item as returned from Data Store.
|
||||
* @param WP_REST_Request $request Request object.
|
||||
* @return WP_REST_Response
|
||||
*/
|
||||
@@ -288,15 +278,6 @@ class Controller extends GenericStatsController {
|
||||
),
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
);
|
||||
$params['fields'] = array(
|
||||
'description' => __( 'Limit stats fields to the specified items.', 'woocommerce' ),
|
||||
'type' => 'array',
|
||||
'sanitize_callback' => 'wp_parse_slug_list',
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
'items' => array(
|
||||
'type' => 'string',
|
||||
),
|
||||
);
|
||||
$params['attribute_is'] = array(
|
||||
'description' => __( 'Limit result set to orders that include products with the specified attributes.', 'woocommerce' ),
|
||||
'type' => 'array',
|
||||
|
||||
@@ -10,16 +10,19 @@ defined( 'ABSPATH' ) || exit;
|
||||
use Automattic\WooCommerce\Admin\API\Reports\Variations\DataStore as VariationsDataStore;
|
||||
use Automattic\WooCommerce\Admin\API\Reports\DataStoreInterface;
|
||||
use Automattic\WooCommerce\Admin\API\Reports\TimeInterval;
|
||||
use Automattic\WooCommerce\Admin\API\Reports\SqlQuery;
|
||||
use Automattic\WooCommerce\Admin\API\Reports\StatsDataStoreTrait;
|
||||
|
||||
/**
|
||||
* API\Reports\Variations\Stats\DataStore.
|
||||
*/
|
||||
class DataStore extends VariationsDataStore implements DataStoreInterface {
|
||||
use StatsDataStoreTrait;
|
||||
|
||||
/**
|
||||
* Mapping columns to data type to return correct response types.
|
||||
*
|
||||
* @override VariationsDataStore::$column_types
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $column_types = array(
|
||||
@@ -32,6 +35,8 @@ class DataStore extends VariationsDataStore implements DataStoreInterface {
|
||||
/**
|
||||
* Cache identifier.
|
||||
*
|
||||
* @override VariationsDataStore::$cache_key
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $cache_key = 'variations_stats';
|
||||
@@ -39,12 +44,16 @@ class DataStore extends VariationsDataStore implements DataStoreInterface {
|
||||
/**
|
||||
* Data store context used to pass to filters.
|
||||
*
|
||||
* @override VariationsDataStore::$context
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $context = 'variations_stats';
|
||||
|
||||
/**
|
||||
* Assign report columns once full table name has been assigned.
|
||||
*
|
||||
* @override VariationsDataStore::assign_report_columns()
|
||||
*/
|
||||
protected function assign_report_columns() {
|
||||
$table_name = self::get_db_table_name();
|
||||
@@ -133,144 +142,131 @@ class DataStore extends VariationsDataStore implements DataStoreInterface {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the report data based on parameters supplied by the user.
|
||||
* Get the default query arguments to be used by get_data().
|
||||
* These defaults are only partially applied when used via REST API, as that has its own defaults.
|
||||
*
|
||||
* @since 3.5.0
|
||||
* @param array $query_args Query parameters.
|
||||
* @return stdClass|WP_Error Data.
|
||||
* @override VariationsDataStore::get_default_query_vars()
|
||||
*
|
||||
* @return array Query parameters.
|
||||
*/
|
||||
public function get_data( $query_args ) {
|
||||
public function get_default_query_vars() {
|
||||
$defaults = parent::get_default_query_vars();
|
||||
$defaults['category_includes'] = array();
|
||||
$defaults['interval'] = 'week';
|
||||
unset( $defaults['extended_info'] );
|
||||
|
||||
return $defaults;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the report data based on normalized parameters.
|
||||
* Will be called by `get_data` if there is no data in cache.
|
||||
*
|
||||
* @override VariationsDataStore::get_noncached_stats_data()
|
||||
*
|
||||
* @see get_data
|
||||
* @see get_noncached_stats_data
|
||||
* @param array $query_args Query parameters.
|
||||
* @param array $params Query limit parameters.
|
||||
* @param stdClass $data Reference to the data object to fill.
|
||||
* @param int $expected_interval_count Number of expected intervals.
|
||||
* @return stdClass|WP_Error Data object `{ totals: *, intervals: array, total: int, pages: int, page_no: int }`, or error.
|
||||
*/
|
||||
public function get_noncached_stats_data( $query_args, $params, &$data, $expected_interval_count ) {
|
||||
global $wpdb;
|
||||
|
||||
$table_name = self::get_db_table_name();
|
||||
|
||||
// These defaults are only partially applied when used via REST API, as that has its own defaults.
|
||||
$defaults = array(
|
||||
'per_page' => get_option( 'posts_per_page' ),
|
||||
'page' => 1,
|
||||
'order' => 'DESC',
|
||||
'orderby' => 'date',
|
||||
'before' => TimeInterval::default_before(),
|
||||
'after' => TimeInterval::default_after(),
|
||||
'fields' => '*',
|
||||
'category_includes' => array(),
|
||||
'interval' => 'week',
|
||||
'product_includes' => array(),
|
||||
'variation_includes' => array(),
|
||||
$this->initialize_queries();
|
||||
|
||||
$selections = $this->selected_columns( $query_args );
|
||||
|
||||
$this->update_sql_query_params( $query_args );
|
||||
$this->get_limit_sql_params( $query_args );
|
||||
$this->interval_query->add_sql_clause( 'where_time', $this->get_sql_clause( 'where_time' ) );
|
||||
|
||||
/* phpcs:disable WordPress.DB.PreparedSQL.NotPrepared */
|
||||
$db_intervals = $wpdb->get_col(
|
||||
$this->interval_query->get_query_statement()
|
||||
);
|
||||
$query_args = wp_parse_args( $query_args, $defaults );
|
||||
$this->normalize_timezones( $query_args, $defaults );
|
||||
/* phpcs:enable */
|
||||
|
||||
/*
|
||||
* We need to get the cache key here because
|
||||
* parent::update_intervals_sql_params() modifies $query_args.
|
||||
*/
|
||||
$cache_key = $this->get_cache_key( $query_args );
|
||||
$data = $this->get_cached_data( $cache_key );
|
||||
$db_interval_count = count( $db_intervals );
|
||||
|
||||
if ( false === $data ) {
|
||||
$this->initialize_queries();
|
||||
$intervals = array();
|
||||
$this->update_intervals_sql_params( $query_args, $db_interval_count, $expected_interval_count, $table_name );
|
||||
$this->total_query->add_sql_clause( 'select', $selections );
|
||||
$this->total_query->add_sql_clause( 'where_time', $this->get_sql_clause( 'where_time' ) );
|
||||
|
||||
$selections = $this->selected_columns( $query_args );
|
||||
$params = $this->get_limit_params( $query_args );
|
||||
/* phpcs:disable WordPress.DB.PreparedSQL.NotPrepared */
|
||||
$totals = $wpdb->get_results(
|
||||
$this->total_query->get_query_statement(),
|
||||
ARRAY_A
|
||||
);
|
||||
/* phpcs:enable */
|
||||
|
||||
$this->update_sql_query_params( $query_args );
|
||||
$this->get_limit_sql_params( $query_args );
|
||||
$this->interval_query->add_sql_clause( 'where_time', $this->get_sql_clause( 'where_time' ) );
|
||||
// phpcs:ignore Generic.Commenting.Todo.TaskFound
|
||||
// @todo remove these assignements when refactoring segmenter classes to use query objects.
|
||||
$totals_query = array(
|
||||
'from_clause' => $this->total_query->get_sql_clause( 'join' ),
|
||||
'where_time_clause' => $this->total_query->get_sql_clause( 'where_time' ),
|
||||
'where_clause' => $this->total_query->get_sql_clause( 'where' ),
|
||||
);
|
||||
$intervals_query = array(
|
||||
'select_clause' => $this->get_sql_clause( 'select' ),
|
||||
'from_clause' => $this->interval_query->get_sql_clause( 'join' ),
|
||||
'where_time_clause' => $this->interval_query->get_sql_clause( 'where_time' ),
|
||||
'where_clause' => $this->interval_query->get_sql_clause( 'where' ),
|
||||
'order_by' => $this->get_sql_clause( 'order_by' ),
|
||||
'limit' => $this->get_sql_clause( 'limit' ),
|
||||
);
|
||||
$segmenter = new Segmenter( $query_args, $this->report_columns );
|
||||
$totals[0]['segments'] = $segmenter->get_totals_segments( $totals_query, $table_name );
|
||||
|
||||
/* phpcs:disable WordPress.DB.PreparedSQL.NotPrepared */
|
||||
$db_intervals = $wpdb->get_col(
|
||||
$this->interval_query->get_query_statement()
|
||||
);
|
||||
/* phpcs:enable */
|
||||
|
||||
$db_interval_count = count( $db_intervals );
|
||||
$expected_interval_count = TimeInterval::intervals_between( $query_args['after'], $query_args['before'], $query_args['interval'] );
|
||||
$total_pages = (int) ceil( $expected_interval_count / $params['per_page'] );
|
||||
if ( $query_args['page'] < 1 || $query_args['page'] > $total_pages ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$intervals = array();
|
||||
$this->update_intervals_sql_params( $query_args, $db_interval_count, $expected_interval_count, $table_name );
|
||||
$this->total_query->add_sql_clause( 'select', $selections );
|
||||
$this->total_query->add_sql_clause( 'where_time', $this->get_sql_clause( 'where_time' ) );
|
||||
|
||||
/* phpcs:disable WordPress.DB.PreparedSQL.NotPrepared */
|
||||
$totals = $wpdb->get_results(
|
||||
$this->total_query->get_query_statement(),
|
||||
ARRAY_A
|
||||
);
|
||||
/* phpcs:enable */
|
||||
|
||||
// @todo remove these assignements when refactoring segmenter classes to use query objects.
|
||||
$totals_query = array(
|
||||
'from_clause' => $this->total_query->get_sql_clause( 'join' ),
|
||||
'where_time_clause' => $this->total_query->get_sql_clause( 'where_time' ),
|
||||
'where_clause' => $this->total_query->get_sql_clause( 'where' ),
|
||||
);
|
||||
$intervals_query = array(
|
||||
'select_clause' => $this->get_sql_clause( 'select' ),
|
||||
'from_clause' => $this->interval_query->get_sql_clause( 'join' ),
|
||||
'where_time_clause' => $this->interval_query->get_sql_clause( 'where_time' ),
|
||||
'where_clause' => $this->interval_query->get_sql_clause( 'where' ),
|
||||
'order_by' => $this->get_sql_clause( 'order_by' ),
|
||||
'limit' => $this->get_sql_clause( 'limit' ),
|
||||
);
|
||||
$segmenter = new Segmenter( $query_args, $this->report_columns );
|
||||
$totals[0]['segments'] = $segmenter->get_totals_segments( $totals_query, $table_name );
|
||||
|
||||
if ( null === $totals ) {
|
||||
return new \WP_Error( 'woocommerce_analytics_variations_stats_result_failed', __( 'Sorry, fetching revenue data failed.', 'woocommerce' ) );
|
||||
}
|
||||
|
||||
$this->interval_query->add_sql_clause( 'order_by', $this->get_sql_clause( 'order_by' ) );
|
||||
$this->interval_query->add_sql_clause( 'limit', $this->get_sql_clause( 'limit' ) );
|
||||
$this->interval_query->add_sql_clause( 'select', ", MAX({$table_name}.date_created) AS datetime_anchor" );
|
||||
if ( '' !== $selections ) {
|
||||
$this->interval_query->add_sql_clause( 'select', ', ' . $selections );
|
||||
}
|
||||
|
||||
/* phpcs:disable WordPress.DB.PreparedSQL.NotPrepared */
|
||||
$intervals = $wpdb->get_results(
|
||||
$this->interval_query->get_query_statement(),
|
||||
ARRAY_A
|
||||
);
|
||||
/* phpcs:enable */
|
||||
|
||||
if ( null === $intervals ) {
|
||||
return new \WP_Error( 'woocommerce_analytics_variations_stats_result_failed', __( 'Sorry, fetching revenue data failed.', 'woocommerce' ) );
|
||||
}
|
||||
|
||||
$totals = (object) $this->cast_numbers( $totals[0] );
|
||||
|
||||
$data = (object) array(
|
||||
'totals' => $totals,
|
||||
'intervals' => $intervals,
|
||||
'total' => $expected_interval_count,
|
||||
'pages' => $total_pages,
|
||||
'page_no' => (int) $query_args['page'],
|
||||
);
|
||||
|
||||
if ( TimeInterval::intervals_missing( $expected_interval_count, $db_interval_count, $params['per_page'], $query_args['page'], $query_args['order'], $query_args['orderby'], count( $intervals ) ) ) {
|
||||
$this->fill_in_missing_intervals( $db_intervals, $query_args['adj_after'], $query_args['adj_before'], $query_args['interval'], $data );
|
||||
$this->sort_intervals( $data, $query_args['orderby'], $query_args['order'] );
|
||||
$this->remove_extra_records( $data, $query_args['page'], $params['per_page'], $db_interval_count, $expected_interval_count, $query_args['orderby'], $query_args['order'] );
|
||||
} else {
|
||||
$this->update_interval_boundary_dates( $query_args['after'], $query_args['before'], $query_args['interval'], $data->intervals );
|
||||
}
|
||||
$segmenter->add_intervals_segments( $data, $intervals_query, $table_name );
|
||||
$this->create_interval_subtotals( $data->intervals );
|
||||
|
||||
$this->set_cached_data( $cache_key, $data );
|
||||
if ( null === $totals ) {
|
||||
return new \WP_Error( 'woocommerce_analytics_variations_stats_result_failed', __( 'Sorry, fetching revenue data failed.', 'woocommerce' ) );
|
||||
}
|
||||
|
||||
$this->interval_query->add_sql_clause( 'order_by', $this->get_sql_clause( 'order_by' ) );
|
||||
$this->interval_query->add_sql_clause( 'limit', $this->get_sql_clause( 'limit' ) );
|
||||
$this->interval_query->add_sql_clause( 'select', ", MAX({$table_name}.date_created) AS datetime_anchor" );
|
||||
if ( '' !== $selections ) {
|
||||
$this->interval_query->add_sql_clause( 'select', ', ' . $selections );
|
||||
}
|
||||
|
||||
/* phpcs:disable WordPress.DB.PreparedSQL.NotPrepared */
|
||||
$intervals = $wpdb->get_results(
|
||||
$this->interval_query->get_query_statement(),
|
||||
ARRAY_A
|
||||
);
|
||||
/* phpcs:enable */
|
||||
|
||||
if ( null === $intervals ) {
|
||||
return new \WP_Error( 'woocommerce_analytics_variations_stats_result_failed', __( 'Sorry, fetching revenue data failed.', 'woocommerce' ) );
|
||||
}
|
||||
|
||||
$totals = (object) $this->cast_numbers( $totals[0] );
|
||||
|
||||
$data->totals = $totals;
|
||||
$data->intervals = $intervals;
|
||||
|
||||
if ( TimeInterval::intervals_missing( $expected_interval_count, $db_interval_count, $params['per_page'], $query_args['page'], $query_args['order'], $query_args['orderby'], count( $intervals ) ) ) {
|
||||
$this->fill_in_missing_intervals( $db_intervals, $query_args['adj_after'], $query_args['adj_before'], $query_args['interval'], $data );
|
||||
$this->sort_intervals( $data, $query_args['orderby'], $query_args['order'] );
|
||||
$this->remove_extra_records( $data, $query_args['page'], $params['per_page'], $db_interval_count, $expected_interval_count, $query_args['orderby'], $query_args['order'] );
|
||||
} else {
|
||||
$this->update_interval_boundary_dates( $query_args['after'], $query_args['before'], $query_args['interval'], $data->intervals );
|
||||
}
|
||||
$segmenter->add_intervals_segments( $data, $intervals_query, $table_name );
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes order_by clause to match to SQL query.
|
||||
*
|
||||
* @override VariationsDataStore::normalize_order_by()
|
||||
*
|
||||
* @param string $order_by Order by option requeste by user.
|
||||
* @return string
|
||||
*/
|
||||
@@ -281,18 +277,4 @@ class DataStore extends VariationsDataStore implements DataStoreInterface {
|
||||
|
||||
return $order_by;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize query objects.
|
||||
*/
|
||||
protected function initialize_queries() {
|
||||
$this->clear_all_clauses();
|
||||
unset( $this->subquery );
|
||||
$this->total_query = new SqlQuery( $this->context . '_total' );
|
||||
$this->total_query->add_sql_clause( 'from', self::get_db_table_name() );
|
||||
|
||||
$this->interval_query = new SqlQuery( $this->context . '_interval' );
|
||||
$this->interval_query->add_sql_clause( 'from', self::get_db_table_name() );
|
||||
$this->interval_query->add_sql_clause( 'group_by', 'time_interval' );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,12 +22,16 @@ use Automattic\WooCommerce\Admin\API\Reports\Query as ReportsQuery;
|
||||
|
||||
/**
|
||||
* API\Reports\Variations\Stats\Query
|
||||
*
|
||||
* @deprecated 9.3.0 Variations\Stats\Query class is deprecated, please use GenericQuery or \WC_Object_Query instead.
|
||||
*/
|
||||
class Query extends ReportsQuery {
|
||||
|
||||
/**
|
||||
* Valid fields for Products report.
|
||||
*
|
||||
* @deprecated 9.3.0 Variations\Stats\Query class is deprecated, please use GenericQuery or \WC_Object_Query instead.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_default_query_vars() {
|
||||
@@ -37,6 +41,8 @@ class Query extends ReportsQuery {
|
||||
/**
|
||||
* Get variations data based on the current query vars.
|
||||
*
|
||||
* @deprecated 9.3.0 Variations\Stats\Query class is deprecated, please use GenericQuery or \WC_Object_Query instead.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_data() {
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
declare( strict_types = 1);
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\Features\Blueprint\Exporters;
|
||||
|
||||
use Automattic\WooCommerce\Blueprint\Exporters\StepExporter;
|
||||
use Automattic\WooCommerce\Blueprint\Exporters\HasAlias;
|
||||
use Automattic\WooCommerce\Blueprint\Steps\SetSiteOptions;
|
||||
use Automattic\WooCommerce\Blueprint\UseWPFunctions;
|
||||
|
||||
/**
|
||||
* ExportWCCoreProfilerOptions class
|
||||
*/
|
||||
class ExportWCCoreProfilerOptions implements StepExporter, HasAlias {
|
||||
use UseWPFunctions;
|
||||
|
||||
/**
|
||||
* Export the step
|
||||
*
|
||||
* @return SetSiteOptions
|
||||
*/
|
||||
public function export() {
|
||||
$step = new SetSiteOptions(
|
||||
array(
|
||||
'blogname' => $this->wp_get_option( 'blogname' ),
|
||||
'woocommerce_allow_tracking' => $this->wp_get_option( 'woocommerce_allow_tracking' ),
|
||||
'woocommerce_onboarding_profile' => $this->wp_get_option( 'woocommerce_onboarding_profile', array() ),
|
||||
'woocommerce_default_country' => $this->wp_get_option( 'woocommerce_default_country' ),
|
||||
)
|
||||
);
|
||||
$step->set_meta_values(
|
||||
array(
|
||||
'plugin' => 'woocommerce',
|
||||
'alias' => $this->get_alias(),
|
||||
)
|
||||
);
|
||||
|
||||
return $step;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the step name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_step_name() {
|
||||
return 'setSiteOptions';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the alias
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_alias() {
|
||||
return 'setWCCoreProfilerOptions';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
declare( strict_types = 1);
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\Features\Blueprint\Exporters;
|
||||
|
||||
use Automattic\WooCommerce\Admin\Features\Blueprint\Steps\SetWCPaymentGateways;
|
||||
use Automattic\WooCommerce\Blueprint\Exporters\StepExporter;
|
||||
use Automattic\WooCommerce\Blueprint\Steps\Step;
|
||||
|
||||
/**
|
||||
* ExportWCPaymentGateways class
|
||||
*/
|
||||
class ExportWCPaymentGateways implements StepExporter {
|
||||
/**
|
||||
* Payment gateway IDs to exclude from export
|
||||
*
|
||||
* @var array|string[] Payment gateway IDs to exclude from export
|
||||
*/
|
||||
protected array $exclude_ids = array( 'pre_install_woocommerce_payments_promotion' );
|
||||
|
||||
/**
|
||||
* Export the step
|
||||
*
|
||||
* @return Step
|
||||
*/
|
||||
public function export(): Step {
|
||||
$step = new SetWCPaymentGateways();
|
||||
$this->maybe_hide_wcpay_gateways();
|
||||
foreach ( $this->get_wc_payment_gateways() as $id => $payment_gateway ) {
|
||||
if ( in_array( $id, $this->exclude_ids, true ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$step->add_payment_gateway(
|
||||
$id,
|
||||
$payment_gateway->get_title(),
|
||||
$payment_gateway->get_description(),
|
||||
$payment_gateway->is_available() ? 'yes' : 'no'
|
||||
);
|
||||
}
|
||||
|
||||
return $step;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the payment gateways resgietered in WooCommerce
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_wc_payment_gateways() {
|
||||
return WC()->payment_gateways->payment_gateways();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the step name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_step_name() {
|
||||
return SetWCPaymentGateways::get_step_name();
|
||||
}
|
||||
|
||||
/**
|
||||
* Maybe hide WooCommerce Payments gateways
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function maybe_hide_wcpay_gateways() {
|
||||
if ( class_exists( 'WC_Payments' ) ) {
|
||||
\WC_Payments::hide_gateways_on_settings_page();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,224 @@
|
||||
<?php
|
||||
|
||||
declare( strict_types = 1);
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\Features\Blueprint\Exporters;
|
||||
|
||||
use Automattic\WooCommerce\Blueprint\Exporters\HasAlias;
|
||||
use Automattic\WooCommerce\Blueprint\Exporters\StepExporter;
|
||||
use Automattic\WooCommerce\Blueprint\Steps\SetSiteOptions;
|
||||
use Automattic\WooCommerce\Blueprint\UseWPFunctions;
|
||||
use Automattic\WooCommerce\Blueprint\Util;
|
||||
use WC_Admin_Settings;
|
||||
use WC_Settings_Page;
|
||||
|
||||
/**
|
||||
* Class ExportWCSettings
|
||||
*
|
||||
* This class exports WooCommerce settings and implements the StepExporter and HasAlias interfaces.
|
||||
*
|
||||
* @package Automattic\WooCommerce\Admin\Features\Blueprint\Exporters
|
||||
*/
|
||||
class ExportWCSettings implements StepExporter, HasAlias {
|
||||
use UseWPFunctions;
|
||||
|
||||
/**
|
||||
* Array of WC_Settings_Page objects.
|
||||
*
|
||||
* @var WC_Settings_Page[]
|
||||
*/
|
||||
private array $setting_pages;
|
||||
|
||||
/**
|
||||
* Array of page IDs to exclude from export.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private array $exclude_pages = array( 'integration', 'site-visibility' );
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param array $setting_pages Optional array of setting pages.
|
||||
*/
|
||||
public function __construct( array $setting_pages = array() ) {
|
||||
if ( empty( $setting_pages ) ) {
|
||||
$setting_pages = WC_Admin_Settings::get_settings_pages();
|
||||
}
|
||||
$this->setting_pages = $setting_pages;
|
||||
$this->wp_add_filter( 'wooblueprint_export_settings', array( $this, 'add_site_visibility_settings' ), 10, 3 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Export WooCommerce settings.
|
||||
*
|
||||
* @return SetSiteOptions
|
||||
*/
|
||||
public function export() {
|
||||
$pages = array();
|
||||
$options = array();
|
||||
$option_info = array();
|
||||
|
||||
foreach ( $this->setting_pages as $page ) {
|
||||
$id = $page->get_id();
|
||||
if ( in_array( $id, $this->exclude_pages, true ) ) {
|
||||
continue;
|
||||
}
|
||||
$pages[ $id ] = $this->get_page_info( $page );
|
||||
foreach ( $pages[ $id ]['options'] as $option ) {
|
||||
$options[ $option['id'] ] = $option['value'];
|
||||
$option_info[ $option['id'] ] = array(
|
||||
'location' => $option['location'],
|
||||
'title' => $option['title'],
|
||||
);
|
||||
}
|
||||
unset( $pages[ $id ]['options'] );
|
||||
}
|
||||
|
||||
$filtered = $this->wp_apply_filters( 'wooblueprint_export_settings', $options, $pages, $option_info );
|
||||
|
||||
$step = new SetSiteOptions( $filtered['options'] );
|
||||
$step->set_meta_values(
|
||||
array(
|
||||
'plugin' => 'woocommerce',
|
||||
'pages' => $filtered['pages'],
|
||||
'info' => $option_info,
|
||||
'alias' => $this->get_alias(),
|
||||
)
|
||||
);
|
||||
|
||||
return $step;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get information about a settings page.
|
||||
*
|
||||
* @param WC_Settings_Page $page The settings page.
|
||||
* @return array
|
||||
*/
|
||||
protected function get_page_info( WC_Settings_Page $page ) {
|
||||
$info = array(
|
||||
'label' => $page->get_label(),
|
||||
'sections' => array(),
|
||||
);
|
||||
|
||||
foreach ( $page->get_sections() as $id => $section ) {
|
||||
$section_id = Util::camel_to_snake( strtolower( $section ) );
|
||||
$info['sections'][ $section_id ] = array(
|
||||
'label' => $section,
|
||||
'subsections' => array(),
|
||||
);
|
||||
|
||||
$settings = $page->get_settings_for_section( $id );
|
||||
|
||||
// Get subsections.
|
||||
$subsections = array_filter(
|
||||
$settings,
|
||||
function ( $setting ) {
|
||||
return isset( $setting['type'] ) && 'title' === $setting['type'] && isset( $setting['title'] );
|
||||
}
|
||||
);
|
||||
|
||||
foreach ( $subsections as $subsection ) {
|
||||
if ( ! isset( $subsection['id'] ) ) {
|
||||
$subsection['id'] = Util::camel_to_snake( strtolower( $subsection['title'] ) );
|
||||
}
|
||||
|
||||
$info['sections'][ $section_id ]['subsections'][ $subsection['id'] ] = array(
|
||||
'label' => $subsection['title'],
|
||||
);
|
||||
}
|
||||
|
||||
// Get options.
|
||||
$info['options'] = $this->get_page_section_settings( $settings, $page->get_id(), $section_id );
|
||||
}
|
||||
return $info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get settings for a specific page section.
|
||||
*
|
||||
* @param array $settings The settings.
|
||||
* @param string $page The page ID.
|
||||
* @param string $section The section ID.
|
||||
* @return array
|
||||
*/
|
||||
private function get_page_section_settings( $settings, $page, $section = '' ) {
|
||||
$current_title = '';
|
||||
$data = array();
|
||||
foreach ( $settings as $setting ) {
|
||||
if ( 'sectionend' === $setting['type'] || 'slotfill_placeholder' === $setting['type'] || ! isset( $setting['id'] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( 'title' === $setting['type'] ) {
|
||||
$current_title = Util::camel_to_snake( strtolower( $setting['title'] ) );
|
||||
} else {
|
||||
$location = $page . '.' . $section;
|
||||
if ( $current_title ) {
|
||||
$location .= '.' . $current_title;
|
||||
}
|
||||
|
||||
$data[] = array(
|
||||
'id' => $setting['id'],
|
||||
'value' => $this->wp_get_option( $setting['id'], $setting['default'] ?? null ),
|
||||
'title' => $setting['title'] ?? $setting['desc'] ?? '',
|
||||
'location' => $location,
|
||||
);
|
||||
}
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add site visibility settings.
|
||||
*
|
||||
* @param array $options The options array.
|
||||
* @param array $pages The pages array.
|
||||
* @param array $option_info The option information array.
|
||||
* @return array
|
||||
*/
|
||||
public function add_site_visibility_settings( array $options, array $pages, array $option_info ) {
|
||||
$pages['site_visibility'] = array(
|
||||
'label' => 'Site Visibility',
|
||||
'sections' => array(
|
||||
'general' => array(
|
||||
'label' => 'General',
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
$options['woocommerce_coming_soon'] = $this->wp_get_option( 'woocommerce_coming_soon' );
|
||||
$options['woocommerce_store_pages_only'] = $this->wp_get_option( 'woocommerce_store_pages_only' );
|
||||
|
||||
$option_info['woocommerce_coming_soon'] = array(
|
||||
'location' => 'site_visibility.general',
|
||||
'title' => 'Coming soon',
|
||||
);
|
||||
|
||||
$option_info['woocommerce_store_pages_only'] = array(
|
||||
'location' => 'site_visibility.general',
|
||||
'title' => 'Apply to store pages only',
|
||||
);
|
||||
|
||||
return compact( 'options', 'pages', 'option_info' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of the step.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_step_name() {
|
||||
return 'setSiteOptions';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the alias for this exporter.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_alias() {
|
||||
return 'setWCSettings';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
<?php
|
||||
|
||||
declare( strict_types = 1);
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\Features\Blueprint\Exporters;
|
||||
|
||||
use Automattic\WooCommerce\Admin\Features\Blueprint\Steps\SetWCShipping;
|
||||
use Automattic\WooCommerce\Blueprint\Exporters\StepExporter;
|
||||
use Automattic\WooCommerce\Blueprint\Util;
|
||||
|
||||
/**
|
||||
* Class ExportWCShipping
|
||||
*
|
||||
* This class exports WooCommerce shipping settings and implements the StepExporter interface.
|
||||
*
|
||||
* @package Automattic\WooCommerce\Admin\Features\Blueprint\Exporters
|
||||
*/
|
||||
class ExportWCShipping implements StepExporter {
|
||||
/**
|
||||
* Export WooCommerce shipping settings.
|
||||
*
|
||||
* @return SetWCShipping
|
||||
*/
|
||||
public function export() {
|
||||
global $wpdb;
|
||||
|
||||
// Fetch shipping classes from the database.
|
||||
$classes = $wpdb->get_results(
|
||||
"
|
||||
SELECT *
|
||||
FROM {$wpdb->prefix}term_taxonomy
|
||||
WHERE taxonomy = 'product_shipping_class'
|
||||
"
|
||||
);
|
||||
|
||||
$term_ids = array();
|
||||
|
||||
// Collect term IDs.
|
||||
foreach ( $classes as $term ) {
|
||||
$term_ids[] = (int) $term->term_id;
|
||||
}
|
||||
|
||||
$term_ids = implode( ', ', $term_ids );
|
||||
|
||||
// Fetch terms based on term IDs.
|
||||
if ( ! empty( $term_ids ) ) {
|
||||
$terms = $wpdb->get_results(
|
||||
$wpdb->prepare(
|
||||
"
|
||||
SELECT *
|
||||
FROM {$wpdb->prefix}terms
|
||||
WHERE term_id IN (%s)
|
||||
",
|
||||
$term_ids
|
||||
)
|
||||
);
|
||||
} else {
|
||||
$terms = array();
|
||||
}
|
||||
|
||||
// Fetch local pickup settings.
|
||||
$local_pickup = array(
|
||||
'general' => get_option( 'woocommerce_pickup_location_settings', array() ),
|
||||
'locations' => get_option( 'pickup_location_pickup_locations', array() ),
|
||||
);
|
||||
|
||||
if ( empty( $local_pickup['general'] ) ) {
|
||||
$local_pickup['general'] = new \stdClass();
|
||||
}
|
||||
|
||||
// Fetch shipping zones from the database.
|
||||
$zones = $wpdb->get_results(
|
||||
"
|
||||
SELECT *
|
||||
FROM {$wpdb->prefix}woocommerce_shipping_zones
|
||||
"
|
||||
);
|
||||
|
||||
// Fetch shipping zone methods from the database.
|
||||
$methods = $wpdb->get_results(
|
||||
"
|
||||
SELECT *
|
||||
FROM {$wpdb->prefix}woocommerce_shipping_zone_methods
|
||||
"
|
||||
);
|
||||
|
||||
// Fetch shipping method options.
|
||||
// Each method has a corresponding option in the options table.
|
||||
$method_options = $wpdb->get_results(
|
||||
"
|
||||
SELECT *
|
||||
FROM {$wpdb->prefix}options
|
||||
WHERE option_name LIKE 'woocommerce_flat_rate_%_settings'
|
||||
or option_name LIKE 'woocommerce_free_shipping_%_settings'
|
||||
",
|
||||
ARRAY_A
|
||||
);
|
||||
|
||||
$method_options = Util::index_array(
|
||||
$method_options,
|
||||
function ( $key, $option ) {
|
||||
return $option['option_name'];
|
||||
}
|
||||
);
|
||||
|
||||
foreach ( $methods as $method ) {
|
||||
$key_name = 'woocommerce_' . $method->method_id . '_' . $method->instance_id . '_settings';
|
||||
if ( isset( $method_options[ $key_name ] ) ) {
|
||||
$method->settings = array(
|
||||
'option_name' => $key_name,
|
||||
'option_value' => maybe_unserialize( $method_options[ $key_name ]['option_value'] ),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$methods_by_zone_id = array();
|
||||
|
||||
// Organize methods by zone ID.
|
||||
foreach ( $methods as $method ) {
|
||||
if ( ! isset( $methods_by_zone_id[ $method->zone_id ] ) ) {
|
||||
$methods_by_zone_id[ $method->zone_id ] = array();
|
||||
}
|
||||
$methods_by_zone_id[ $method->zone_id ][] = $method->method_id;
|
||||
}
|
||||
|
||||
// Fetch shipping zone locations from the database.
|
||||
$locations = $wpdb->get_results(
|
||||
"
|
||||
SELECT *
|
||||
FROM {$wpdb->prefix}woocommerce_shipping_zone_locations
|
||||
"
|
||||
);
|
||||
|
||||
$locations_by_zone_id = array();
|
||||
|
||||
// Organize locations by zone ID.
|
||||
foreach ( $locations as $location ) {
|
||||
if ( ! isset( $locations_by_zone_id[ $location->zone_id ] ) ) {
|
||||
$locations_by_zone_id[ $location->zone_id ] = array();
|
||||
}
|
||||
$locations_by_zone_id[ $location->zone_id ][] = $location->location_id;
|
||||
}
|
||||
|
||||
// Create a new SetWCShipping step with the fetched data.
|
||||
$step = new SetWCShipping( $methods, $locations, $zones, $terms, $classes, $local_pickup );
|
||||
$step->set_meta_values(
|
||||
array(
|
||||
'plugin' => 'woocommerce',
|
||||
)
|
||||
);
|
||||
|
||||
return $step;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of the step.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_step_name() {
|
||||
return SetWCShipping::get_step_name();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
declare( strict_types = 1);
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\Features\Blueprint\Exporters;
|
||||
|
||||
use Automattic\WooCommerce\Blueprint\Exporters\ExportsStep;
|
||||
use Automattic\WooCommerce\Blueprint\Exporters\HasAlias;
|
||||
use Automattic\WooCommerce\Blueprint\Exporters\StepExporter;
|
||||
use Automattic\WooCommerce\Blueprint\Steps\SetSiteOptions;
|
||||
use Automattic\WooCommerce\Blueprint\UseWPFunctions;
|
||||
|
||||
/**
|
||||
* Class ExportWCTaskOptions
|
||||
*
|
||||
* This class exports WooCommerce task options and implements the StepExporter and HasAlias interfaces.
|
||||
*
|
||||
* @package Automattic\WooCommerce\Admin\Features\Blueprint\Exporters
|
||||
*/
|
||||
class ExportWCTaskOptions implements StepExporter, HasAlias {
|
||||
use UseWPFunctions;
|
||||
|
||||
/**
|
||||
* Export WooCommerce task options.
|
||||
*
|
||||
* @return SetSiteOptions
|
||||
*/
|
||||
public function export() {
|
||||
$step = new SetSiteOptions(
|
||||
array(
|
||||
'woocommerce_admin_customize_store_completed' => $this->wp_get_option( 'woocommerce_admin_customize_store_completed', 'no' ),
|
||||
'woocommerce_task_list_tracked_completed_actions' => $this->wp_get_option( 'woocommerce_task_list_tracked_completed_actions', array() ),
|
||||
)
|
||||
);
|
||||
|
||||
$step->set_meta_values(
|
||||
array(
|
||||
'plugin' => 'woocommerce',
|
||||
'alias' => $this->get_alias(),
|
||||
)
|
||||
);
|
||||
|
||||
return $step;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of the step.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_step_name() {
|
||||
return 'setOptions';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the alias for this exporter.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_alias() {
|
||||
return 'setWCTaskOptions';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
declare( strict_types = 1);
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\Features\Blueprint\Exporters;
|
||||
|
||||
use Automattic\WooCommerce\Admin\Features\Blueprint\Steps\SetWCTaxRates;
|
||||
use Automattic\WooCommerce\Blueprint\Exporters\StepExporter;
|
||||
|
||||
/**
|
||||
* Class ExportWCTaxRates
|
||||
*
|
||||
* This class exports WooCommerce tax rates and implements the StepExporter interface.
|
||||
*
|
||||
* @package Automattic\WooCommerce\Admin\Features\Blueprint\Exporters
|
||||
*/
|
||||
class ExportWCTaxRates implements StepExporter {
|
||||
|
||||
/**
|
||||
* Export WooCommerce tax rates.
|
||||
*
|
||||
* @return SetWCTaxRates
|
||||
*/
|
||||
public function export() {
|
||||
global $wpdb;
|
||||
|
||||
// Fetch tax rates from the database.
|
||||
$rates = $wpdb->get_results(
|
||||
"
|
||||
SELECT *
|
||||
FROM {$wpdb->prefix}woocommerce_tax_rates as tax_rates
|
||||
",
|
||||
ARRAY_A
|
||||
);
|
||||
|
||||
// Fetch tax rate locations from the database.
|
||||
$locations = $wpdb->get_results(
|
||||
"
|
||||
SELECT *
|
||||
FROM {$wpdb->prefix}woocommerce_tax_rate_locations as locations
|
||||
",
|
||||
ARRAY_A
|
||||
);
|
||||
|
||||
// Create a new SetWCTaxRates step with the fetched data.
|
||||
$step = new SetWCTaxRates( $rates, $locations );
|
||||
$step->set_meta_values(
|
||||
array(
|
||||
'plugin' => 'woocommerce',
|
||||
)
|
||||
);
|
||||
|
||||
return $step;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of the step.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_step_name() {
|
||||
return 'setWCTaxRates';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
declare( strict_types = 1);
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\Features\Blueprint\Importers;
|
||||
|
||||
use Automattic\WooCommerce\Admin\Features\Blueprint\Steps\SetWCPaymentGateways;
|
||||
use Automattic\WooCommerce\Blueprint\StepProcessor;
|
||||
use Automattic\WooCommerce\Blueprint\StepProcessorResult;
|
||||
use Automattic\WooCommerce\Blueprint\UseWPFunctions;
|
||||
|
||||
/**
|
||||
* Class ImportSetWCPaymentGateways
|
||||
*
|
||||
* This class imports WooCommerce payment gateways settings and implements the StepProcessor interface.
|
||||
*
|
||||
* @package Automattic\WooCommerce\Admin\Features\Blueprint\Importers
|
||||
*/
|
||||
class ImportSetWCPaymentGateways implements StepProcessor {
|
||||
use UseWPFunctions;
|
||||
|
||||
/**
|
||||
* Process the import of WooCommerce payment gateways settings.
|
||||
*
|
||||
* @param object $schema The schema object containing import details.
|
||||
* @return StepProcessorResult
|
||||
*/
|
||||
public function process( $schema ): StepProcessorResult {
|
||||
$result = StepProcessorResult::success( SetWCPaymentGateways::get_step_name() );
|
||||
$payment_gateways = $this->get_wc_payment_gateways();
|
||||
$fields = array( 'title', 'description', 'enabled' );
|
||||
|
||||
foreach ( $schema->payment_gateways as $id => $payment_gateway_data ) {
|
||||
if ( ! isset( $payment_gateways[ $id ] ) ) {
|
||||
$result->add_info( "Skipping {$id}. The payment gateway is not available" );
|
||||
continue;
|
||||
}
|
||||
|
||||
$payment_gateway = $payment_gateways[ $id ];
|
||||
|
||||
// Refer to https://github.com/woocommerce/woocommerce/blob/trunk/plugins/woocommerce/includes/class-wc-ajax.php#L3564.
|
||||
foreach ( $fields as $field ) {
|
||||
if ( isset( $payment_gateway_data->{$field} ) ) {
|
||||
$payment_gateway->update_option( $field, $payment_gateway_data->{$field} );
|
||||
}
|
||||
}
|
||||
$result->add_info( "{$id} has been updated." );
|
||||
$this->wp_do_action( 'woocommerce_update_options' );
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the payment gateways resgietered in WooCommerce
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_wc_payment_gateways() {
|
||||
return WC()->payment_gateways->payment_gateways();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the class name for the step.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_step_class(): string {
|
||||
return SetWCPaymentGateways::class;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
<?php
|
||||
|
||||
declare( strict_types = 1);
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\Features\Blueprint\Importers;
|
||||
|
||||
use Automattic\WooCommerce\Admin\Features\Blueprint\Steps\SetWCShipping;
|
||||
use Automattic\WooCommerce\Blueprint\StepProcessor;
|
||||
use Automattic\WooCommerce\Blueprint\StepProcessorResult;
|
||||
use Automattic\WooCommerce\Blueprint\UseWPFunctions;
|
||||
use WC_Tax;
|
||||
|
||||
/**
|
||||
* Class ImportSetWCShipping
|
||||
*
|
||||
* This class imports WooCommerce shipping settings and implements the StepProcessor interface.
|
||||
*
|
||||
* @package Automattic\WooCommerce\Admin\Features\Blueprint\Importers
|
||||
*/
|
||||
class ImportSetWCShipping implements StepProcessor {
|
||||
use UseWPFunctions;
|
||||
|
||||
/**
|
||||
* Process the import of WooCommerce shipping settings.
|
||||
*
|
||||
* @param object $schema The schema object containing import details.
|
||||
* @return StepProcessorResult
|
||||
*/
|
||||
public function process( $schema ): StepProcessorResult {
|
||||
$result = StepProcessorResult::success( SetWCShipping::get_step_name() );
|
||||
|
||||
$fields = array(
|
||||
'terms' => array( 'terms', array( '%d', '%s', '%s', '%d' ) ),
|
||||
'classes' => array( 'term_taxonomy', array( '%d', '%d', '%s', '%s', '%d', '%d' ) ),
|
||||
'shipping_zones' => array( 'woocommerce_shipping_zones', array( '%d', '%s', '%d' ) ),
|
||||
'shipping_methods' => array( 'woocommerce_shipping_zone_methods', array( '%d', '%d', '%s', '%d', '%d' ) ),
|
||||
'shipping_locations' => array( 'woocommerce_shipping_zone_locations', array( '%d', '%d', '%s', '%s' ) ),
|
||||
);
|
||||
|
||||
foreach ( $fields as $name => $data ) {
|
||||
if ( isset( $schema->values->{$name} ) ) {
|
||||
$filter_method = 'filter_' . $name . '_data';
|
||||
if ( method_exists( $this, $filter_method ) ) {
|
||||
$insert_values = $this->$filter_method( $schema->values->{$name} );
|
||||
} else {
|
||||
$insert_values = $schema->values->{$name};
|
||||
}
|
||||
|
||||
$this->insert( $data[0], $data[1], $insert_values );
|
||||
// check if function with process_$name exist and call it.
|
||||
$method = 'post_process_' . $name;
|
||||
if ( method_exists( $this, $method ) ) {
|
||||
$this->$method( $schema->values->{$name} );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( isset( $schema->values->local_pickup ) ) {
|
||||
$this->add_local_pickup( $schema->values->local_pickup );
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter shipping methods data.
|
||||
*
|
||||
* @param array $methods The shipping methods.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
protected function filter_shipping_methods_data( $methods ) {
|
||||
return array_map(
|
||||
function ( $method ) {
|
||||
unset( $method->settings );
|
||||
return $method;
|
||||
},
|
||||
$methods
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Post process shipping methods.
|
||||
*
|
||||
* @param array $methods The shipping methods.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function post_process_shipping_methods( $methods ) {
|
||||
foreach ( $methods as $method ) {
|
||||
if ( isset( $method->settings ) ) {
|
||||
update_option( $method->option_name, $method->option_value );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert data into the specified table.
|
||||
*
|
||||
* @param string $table The table name.
|
||||
* @param array $format The data format.
|
||||
* @param array $rows The rows to insert.
|
||||
* @global \wpdb $wpdb WordPress database abstraction object.
|
||||
* @return array The IDs of the inserted rows.
|
||||
*/
|
||||
protected function insert( $table, $format, $rows ) {
|
||||
global $wpdb;
|
||||
$inserted_ids = array();
|
||||
$table = $wpdb->prefix . $table;
|
||||
$format = implode( ', ', $format );
|
||||
foreach ( $rows as $row ) {
|
||||
$row = (array) $row;
|
||||
$columns = implode( ', ', array_keys( $row ) );
|
||||
// phpcs:ignore
|
||||
$sql = $wpdb->prepare( "REPLACE INTO $table ($columns) VALUES ($format)", $row );
|
||||
// phpcs:ignore
|
||||
$wpdb->query( $sql );
|
||||
}
|
||||
return $inserted_ids;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add local pickup settings.
|
||||
*
|
||||
* @param object $local_pickup The local pickup settings.
|
||||
*/
|
||||
private function add_local_pickup( $local_pickup ) {
|
||||
if ( isset( $local_pickup->general ) ) {
|
||||
$this->wp_update_option( 'woocommerce_pickup_location_settings', (array) $local_pickup->general );
|
||||
}
|
||||
|
||||
if ( isset( $local_pickup->locations ) ) {
|
||||
$local_pickup->locations = json_decode( wp_json_encode( $local_pickup->locations ), true );
|
||||
$this->wp_update_option( 'pickup_location_pickup_locations', $local_pickup->locations );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the class name for the step.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_step_class(): string {
|
||||
return SetWCShipping::class;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
<?php
|
||||
|
||||
declare( strict_types = 1);
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\Features\Blueprint\Importers;
|
||||
|
||||
use Automattic\WooCommerce\Admin\Features\Blueprint\Steps\SetWCTaxRates;
|
||||
use Automattic\WooCommerce\Blueprint\StepProcessor;
|
||||
use Automattic\WooCommerce\Blueprint\StepProcessorResult;
|
||||
use WC_Tax;
|
||||
|
||||
/**
|
||||
* Class ImportSetWCTaxRates
|
||||
*
|
||||
* This class imports WooCommerce tax rates and implements the StepProcessor interface.
|
||||
*
|
||||
* @package Automattic\WooCommerce\Admin\Features\Blueprint\Importers
|
||||
*/
|
||||
class ImportSetWCTaxRates implements StepProcessor {
|
||||
/**
|
||||
* The result of the step processing.
|
||||
*
|
||||
* @var StepProcessorResult $result The result of the step processing.
|
||||
*/
|
||||
private StepProcessorResult $result;
|
||||
|
||||
/**
|
||||
* Process the import of WooCommerce tax rates.
|
||||
*
|
||||
* @param object $schema The schema object containing import details.
|
||||
* @return StepProcessorResult
|
||||
*/
|
||||
public function process( $schema ): StepProcessorResult {
|
||||
$this->result = StepProcessorResult::success( SetWCTaxRates::get_step_name() );
|
||||
|
||||
foreach ( $schema->values->rates as $rate ) {
|
||||
$this->add_rate( $rate );
|
||||
}
|
||||
|
||||
foreach ( $schema->values->locations as $location ) {
|
||||
$this->add_location( $location );
|
||||
}
|
||||
|
||||
return $this->result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a tax rate exists in the database.
|
||||
*
|
||||
* @param int $id The tax rate ID.
|
||||
* @global \wpdb $wpdb WordPress database abstraction object.
|
||||
* @return array|null The tax rate row if found, null otherwise.
|
||||
*/
|
||||
protected function exist( $id ) {
|
||||
global $wpdb;
|
||||
return $wpdb->get_row(
|
||||
$wpdb->prepare(
|
||||
"
|
||||
SELECT *
|
||||
FROM {$wpdb->prefix}woocommerce_tax_rates
|
||||
WHERE tax_rate_id = %d
|
||||
",
|
||||
$id
|
||||
),
|
||||
ARRAY_A
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a tax rate to the database.
|
||||
*
|
||||
* @param object $rate The tax rate object.
|
||||
* @return int|false The tax rate ID if successfully added, false otherwise.
|
||||
*/
|
||||
protected function add_rate( $rate ) {
|
||||
$tax_rate = (array) $rate;
|
||||
|
||||
if ( $this->exist( $tax_rate['tax_rate_id'] ) ) {
|
||||
$this->result->add_info( "Tax rate with I.D {$tax_rate['tax_rate_id']} already exists. Skipped creating it." );
|
||||
return false;
|
||||
}
|
||||
|
||||
$tax_rate_id = WC_Tax::_insert_tax_rate( $tax_rate );
|
||||
|
||||
if ( isset( $rate->postcode ) ) {
|
||||
$postcode = array_map( 'wc_clean', explode( ';', $rate->postcode ) );
|
||||
$postcode = array_map( 'wc_normalize_postcode', $postcode );
|
||||
WC_Tax::_update_tax_rate_postcodes( $tax_rate_id, $postcode );
|
||||
}
|
||||
if ( isset( $rate->city ) ) {
|
||||
$cities = explode( ';', $rate->city );
|
||||
WC_Tax::_update_tax_rate_cities( $tax_rate_id, array_map( 'wc_clean', array_map( 'wp_unslash', $cities ) ) );
|
||||
}
|
||||
|
||||
return $tax_rate_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a tax rate location to the database.
|
||||
*
|
||||
* @param object $location The location object.
|
||||
* @global \wpdb $wpdb WordPress database abstraction object.
|
||||
*/
|
||||
public function add_location( $location ) {
|
||||
global $wpdb;
|
||||
$location = (array) $location;
|
||||
$columns = implode( ',', array_keys( $location ) );
|
||||
$format = implode( ',', array( '%d', '%s', '%d', '%s' ) );
|
||||
$table = $wpdb->prefix . 'woocommerce_tax_rate_locations';
|
||||
// phpcs:ignore
|
||||
$sql = $wpdb->prepare( "REPLACE INTO $table ($columns) VALUES ($format)", $location );
|
||||
// phpcs:ignore
|
||||
$wpdb->query( $sql );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the class name for the step.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_step_class(): string {
|
||||
return SetWCTaxRates::class;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
<?php
|
||||
|
||||
declare( strict_types = 1 );
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\Features\Blueprint;
|
||||
|
||||
use Automattic\WooCommerce\Admin\Features\Blueprint\Exporters\ExportWCCoreProfilerOptions;
|
||||
use Automattic\WooCommerce\Admin\Features\Blueprint\Exporters\ExportWCPaymentGateways;
|
||||
use Automattic\WooCommerce\Admin\Features\Blueprint\Exporters\ExportWCSettings;
|
||||
use Automattic\WooCommerce\Admin\Features\Blueprint\Exporters\ExportWCShipping;
|
||||
use Automattic\WooCommerce\Admin\Features\Blueprint\Exporters\ExportWCTaskOptions;
|
||||
use Automattic\WooCommerce\Admin\Features\Blueprint\Exporters\ExportWCTaxRates;
|
||||
use Automattic\WooCommerce\Admin\Features\Blueprint\Importers\ImportSetWCPaymentGateways;
|
||||
use Automattic\WooCommerce\Admin\Features\Blueprint\Importers\ImportSetWCShipping;
|
||||
use Automattic\WooCommerce\Admin\Features\Blueprint\Importers\ImportSetWCTaxRates;
|
||||
use Automattic\WooCommerce\Admin\PageController;
|
||||
use Automattic\WooCommerce\Blueprint\Exporters\StepExporter;
|
||||
use Automattic\WooCommerce\Blueprint\StepProcessor;
|
||||
|
||||
/**
|
||||
* Class Init
|
||||
*
|
||||
* This class initializes the Blueprint feature for WooCommerce.
|
||||
*/
|
||||
class Init {
|
||||
/**
|
||||
* Init constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
add_action( 'rest_api_init', array( $this, 'init_rest_api' ) );
|
||||
add_filter( 'woocommerce_admin_shared_settings', array( $this, 'add_upload_nonce_to_settings' ) );
|
||||
|
||||
add_filter(
|
||||
'wooblueprint_export_landingpage',
|
||||
function () {
|
||||
return 'admin.php?page=wc-admin';
|
||||
}
|
||||
);
|
||||
|
||||
add_filter( 'wooblueprint_exporters', array( $this, 'add_woo_exporters' ) );
|
||||
add_filter( 'wooblueprint_importers', array( $this, 'add_woo_importers' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register REST API routes.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function init_rest_api() {
|
||||
( new RestApi() )->register_routes();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add upload nonce to global JS settings.
|
||||
*
|
||||
* The value can be accessed at wcSettings.admin.blueprint_upload_nonce
|
||||
*
|
||||
* @param array $settings Global JS settings.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function add_upload_nonce_to_settings( array $settings ) {
|
||||
if ( ! is_admin() ) {
|
||||
return $settings;
|
||||
}
|
||||
|
||||
$page_id = PageController::get_instance()->get_current_screen_id();
|
||||
if ( 'woocommerce_page_wc-admin' === $page_id ) {
|
||||
$settings['blueprint_upload_nonce'] = wp_create_nonce( 'blueprint_upload_nonce' );
|
||||
return $settings;
|
||||
}
|
||||
|
||||
return $settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add Woo Specific Exporters.
|
||||
*
|
||||
* @param StepExporter[] $exporters Array of step exporters.
|
||||
*
|
||||
* @return StepExporter[]
|
||||
*/
|
||||
public function add_woo_exporters( array $exporters ) {
|
||||
return array_merge(
|
||||
$exporters,
|
||||
array(
|
||||
new ExportWCCoreProfilerOptions(),
|
||||
new ExportWCSettings(),
|
||||
new ExportWCPaymentGateways(),
|
||||
new ExportWCShipping(),
|
||||
new ExportWCTaskOptions(),
|
||||
new ExportWCTaxRates(),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add Woo Specific Importers.
|
||||
*
|
||||
* @param StepProcessor[] $importers Array of step processors.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function add_woo_importers( array $importers ) {
|
||||
return array_merge(
|
||||
$importers,
|
||||
array(
|
||||
new ImportSetWCPaymentGateways(),
|
||||
new ImportSetWCShipping(),
|
||||
new ImportSetWCTaxRates(),
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,213 @@
|
||||
<?php
|
||||
|
||||
declare( strict_types = 1 );
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\Features\Blueprint;
|
||||
|
||||
use Automattic\WooCommerce\Blueprint\ExportSchema;
|
||||
use Automattic\WooCommerce\Blueprint\ImportSchema;
|
||||
use Automattic\WooCommerce\Blueprint\JsonResultFormatter;
|
||||
use Automattic\WooCommerce\Blueprint\ZipExportedSchema;
|
||||
|
||||
/**
|
||||
* Class RestApi
|
||||
*
|
||||
* This class handles the REST API endpoints for importing and exporting WooCommerce Blueprints.
|
||||
*
|
||||
* @package Automattic\WooCommerce\Admin\Features\Blueprint
|
||||
*/
|
||||
class RestApi {
|
||||
/**
|
||||
* Endpoint namespace.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $namespace = 'blueprint';
|
||||
|
||||
/**
|
||||
* Register routes.
|
||||
*
|
||||
* @since 9.3.0
|
||||
*/
|
||||
public function register_routes() {
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/import',
|
||||
array(
|
||||
array(
|
||||
'methods' => \WP_REST_Server::CREATABLE,
|
||||
'callback' => array( $this, 'import' ),
|
||||
'permission_callback' => array( $this, 'check_permission' ),
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/export',
|
||||
array(
|
||||
array(
|
||||
'methods' => \WP_REST_Server::CREATABLE,
|
||||
'callback' => array( $this, 'export' ),
|
||||
'permission_callback' => array( $this, 'check_permission' ),
|
||||
'args' => array(
|
||||
'steps' => array(
|
||||
'description' => __( 'A list of plugins to install', 'woocommerce' ),
|
||||
'type' => 'array',
|
||||
'items' => 'string',
|
||||
'default' => array(),
|
||||
'sanitize_callback' => function ( $value ) {
|
||||
return array_map(
|
||||
function ( $value ) {
|
||||
return sanitize_text_field( $value );
|
||||
},
|
||||
$value
|
||||
);
|
||||
},
|
||||
'required' => false,
|
||||
),
|
||||
'export_as_zip' => array(
|
||||
'description' => __( 'Export as a zip file', 'woocommerce' ),
|
||||
'type' => 'boolean',
|
||||
'default' => false,
|
||||
'required' => false,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the current user has permission to perform the request.
|
||||
*
|
||||
* @return bool|\WP_Error
|
||||
*/
|
||||
public function check_permission() {
|
||||
if ( ! current_user_can( 'install_plugins' ) ) {
|
||||
return new \WP_Error( 'woocommerce_rest_cannot_view', __( 'Sorry, you cannot list resources.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the export request.
|
||||
*
|
||||
* @param \WP_REST_Request $request The request object.
|
||||
* @return \WP_HTTP_Response The response object.
|
||||
*/
|
||||
public function export( $request ) {
|
||||
$steps = $request->get_param( 'steps' );
|
||||
$export_as_zip = $request->get_param( 'export_as_zip' );
|
||||
$exporter = new ExportSchema();
|
||||
|
||||
$data = $exporter->export( $steps, $export_as_zip );
|
||||
|
||||
if ( $export_as_zip ) {
|
||||
$zip = new ZipExportedSchema( $data );
|
||||
$data = $zip->zip();
|
||||
$data = site_url( str_replace( ABSPATH, '', $data ) );
|
||||
}
|
||||
|
||||
return new \WP_HTTP_Response(
|
||||
array(
|
||||
'data' => $data,
|
||||
'type' => $export_as_zip ? 'zip' : 'json',
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the import request.
|
||||
*
|
||||
* @return \WP_HTTP_Response The response object.
|
||||
* @throws \InvalidArgumentException If the import fails.
|
||||
*/
|
||||
public function import() {
|
||||
|
||||
// Check for nonce to prevent CSRF.
|
||||
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash
|
||||
if ( ! isset( $_POST['blueprint_upload_nonce'] ) || ! \wp_verify_nonce( $_POST['blueprint_upload_nonce'], 'blueprint_upload_nonce' ) ) {
|
||||
return new \WP_HTTP_Response(
|
||||
array(
|
||||
'status' => 'error',
|
||||
'message' => __( 'Invalid nonce', 'woocommerce' ),
|
||||
),
|
||||
400
|
||||
);
|
||||
}
|
||||
|
||||
// phpcs:ignore
|
||||
if ( ! empty( $_FILES['file'] ) && $_FILES['file']['error'] === UPLOAD_ERR_OK ) {
|
||||
// phpcs:ignore
|
||||
$uploaded_file = $_FILES['file']['tmp_name'];
|
||||
// phpcs:ignore
|
||||
$mime_type = $_FILES['file']['type'];
|
||||
|
||||
if ( 'application/json' !== $mime_type && 'application/zip' !== $mime_type ) {
|
||||
return new \WP_HTTP_Response(
|
||||
array(
|
||||
'status' => 'error',
|
||||
'message' => __( 'Invalid file type', 'woocommerce' ),
|
||||
),
|
||||
400
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
// phpcs:ignore
|
||||
if ( $mime_type === 'application/zip' ) {
|
||||
// phpcs:ignore
|
||||
if ( ! function_exists( 'wp_handle_upload' ) ) {
|
||||
require_once ABSPATH . 'wp-admin/includes/file.php';
|
||||
}
|
||||
|
||||
$movefile = \wp_handle_upload( $_FILES['file'], array( 'test_form' => false ) );
|
||||
|
||||
if ( $movefile && ! isset( $movefile['error'] ) ) {
|
||||
$blueprint = ImportSchema::create_from_zip( $movefile['file'] );
|
||||
} else {
|
||||
throw new InvalidArgumentException( $movefile['error'] );
|
||||
}
|
||||
} else {
|
||||
$blueprint = ImportSchema::create_from_json( $uploaded_file );
|
||||
}
|
||||
} catch ( \Exception $e ) {
|
||||
return new \WP_HTTP_Response(
|
||||
array(
|
||||
'status' => 'error',
|
||||
'message' => $e->getMessage(),
|
||||
),
|
||||
400
|
||||
);
|
||||
}
|
||||
|
||||
$results = $blueprint->import();
|
||||
$result_formatter = new JsonResultFormatter( $results );
|
||||
$redirect = $blueprint->get_schema()->landingPage ?? null;
|
||||
$redirect_url = $redirect->url ?? 'admin.php?page=wc-admin';
|
||||
|
||||
$is_success = $result_formatter->is_success() ? 'success' : 'error';
|
||||
|
||||
return new \WP_HTTP_Response(
|
||||
array(
|
||||
'status' => $is_success,
|
||||
'message' => 'error' === $is_success ? __( 'There was an error while processing your schema', 'woocommerce' ) : 'success',
|
||||
'data' => array(
|
||||
'redirect' => admin_url( $redirect_url ),
|
||||
'result' => $result_formatter->format(),
|
||||
),
|
||||
),
|
||||
200
|
||||
);
|
||||
}
|
||||
|
||||
return new \WP_HTTP_Response(
|
||||
array(
|
||||
'status' => 'error',
|
||||
'message' => __( 'No file uploaded', 'woocommerce' ),
|
||||
),
|
||||
400
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
<?php
|
||||
|
||||
declare( strict_types = 1);
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\Features\Blueprint\Steps;
|
||||
|
||||
use Automattic\WooCommerce\Blueprint\Steps\Step;
|
||||
|
||||
/**
|
||||
* Class SetWCPaymentGateways
|
||||
*
|
||||
* This class sets WooCommerce payment gateways and extends the Step class.
|
||||
*
|
||||
* @package Automattic\WooCommerce\Admin\Features\Blueprint\Steps
|
||||
*/
|
||||
class SetWCPaymentGateways extends Step {
|
||||
|
||||
/**
|
||||
* Payment gateways.
|
||||
*
|
||||
* @var array $payment_gateways Array of payment gateways.
|
||||
*/
|
||||
protected array $payment_gateways = array();
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param array $payment_gateways Optional array of payment gateways.
|
||||
*/
|
||||
public function __construct( array $payment_gateways = array() ) {
|
||||
$this->payment_gateways = $payment_gateways;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a payment gateway.
|
||||
*
|
||||
* @param string $id The ID of the payment gateway.
|
||||
* @param string $title The title of the payment gateway.
|
||||
* @param string $description The description of the payment gateway.
|
||||
* @param string $enabled Whether the payment gateway is enabled ('yes' or 'no').
|
||||
*/
|
||||
public function add_payment_gateway( $id, $title, $description, $enabled ) {
|
||||
$this->payment_gateways[ $id ] = array(
|
||||
'title' => $title,
|
||||
'description' => $description,
|
||||
'enabled' => $enabled,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of the step.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_step_name(): string {
|
||||
return 'setWCPaymentGateways';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the schema for the step.
|
||||
*
|
||||
* @param int $version Optional version number of the schema.
|
||||
* @return array The schema array.
|
||||
*/
|
||||
public static function get_schema( $version = 1 ): array {
|
||||
return array(
|
||||
'type' => 'object',
|
||||
'properties' => array(
|
||||
'step' => array(
|
||||
'type' => 'string',
|
||||
'enum' => array( 'setWCPaymentGateways' ),
|
||||
),
|
||||
'payment_gateways' => array(
|
||||
'type' => 'object',
|
||||
'patternProperties' => array(
|
||||
'^[a-zA-Z0-9_]+$' => array(
|
||||
'type' => 'object',
|
||||
'properties' => array(
|
||||
'title' => array(
|
||||
'type' => 'string',
|
||||
),
|
||||
'description' => array(
|
||||
'type' => 'string',
|
||||
),
|
||||
'enabled' => array(
|
||||
'type' => 'string',
|
||||
'enum' => array( 'yes', 'no' ),
|
||||
),
|
||||
),
|
||||
'required' => array( 'title', 'description', 'enabled' ),
|
||||
),
|
||||
),
|
||||
'additionalProperties' => false,
|
||||
),
|
||||
),
|
||||
'required' => array( 'step', 'payment_gateways' ),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the JSON array for the step.
|
||||
*
|
||||
* @return array The JSON array.
|
||||
*/
|
||||
public function prepare_json_array(): array {
|
||||
return array(
|
||||
'step' => static::get_step_name(),
|
||||
'payment_gateways' => $this->payment_gateways,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,232 @@
|
||||
<?php
|
||||
|
||||
declare( strict_types = 1);
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\Features\Blueprint\Steps;
|
||||
|
||||
use Automattic\WooCommerce\Blueprint\Steps\Step;
|
||||
|
||||
/**
|
||||
* Class SetWCShipping
|
||||
*
|
||||
* This class sets WooCommerce shipping settings and extends the Step class.
|
||||
*
|
||||
* @package Automattic\WooCommerce\Admin\Features\Blueprint\Steps
|
||||
*/
|
||||
class SetWCShipping extends Step {
|
||||
|
||||
/**
|
||||
* Shipping methods.
|
||||
*
|
||||
* @var array $methods Shipping methods.
|
||||
*/
|
||||
private array $methods;
|
||||
|
||||
/**
|
||||
* Shipping locations.
|
||||
*
|
||||
* @var array $locations Shipping locations.
|
||||
*/
|
||||
private array $locations;
|
||||
|
||||
/**
|
||||
* Shipping zones.
|
||||
*
|
||||
* @var array $zones Shipping zones.
|
||||
*/
|
||||
private array $zones;
|
||||
|
||||
/**
|
||||
* Shipping terms.
|
||||
*
|
||||
* @var array $terms Shipping terms.
|
||||
*/
|
||||
private array $terms;
|
||||
|
||||
/**
|
||||
* Shipping classes.
|
||||
*
|
||||
* @var array $classes Shipping classes.
|
||||
*/
|
||||
private array $classes;
|
||||
|
||||
/**
|
||||
* Local pickup settings.
|
||||
*
|
||||
* @var array $local_pickup Local pickup settings.
|
||||
*/
|
||||
private array $local_pickup;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param array $methods Shipping methods.
|
||||
* @param array $locations Shipping locations.
|
||||
* @param array $zones Shipping zones.
|
||||
* @param array $terms Shipping terms.
|
||||
* @param array $classes Shipping classes.
|
||||
* @param array $local_pickup Local pickup settings.
|
||||
*/
|
||||
public function __construct( array $methods, array $locations, array $zones, array $terms, array $classes, array $local_pickup ) {
|
||||
$this->methods = $methods;
|
||||
$this->locations = $locations;
|
||||
$this->zones = $zones;
|
||||
$this->terms = $terms;
|
||||
$this->classes = $classes;
|
||||
$this->local_pickup = $local_pickup;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the JSON array for the step.
|
||||
*
|
||||
* @return array The JSON array.
|
||||
*/
|
||||
public function prepare_json_array(): array {
|
||||
return array(
|
||||
'step' => static::get_step_name(),
|
||||
'values' => array(
|
||||
'shipping_methods' => $this->methods,
|
||||
'shipping_locations' => $this->locations,
|
||||
'shipping_zones' => $this->zones,
|
||||
'terms' => $this->terms,
|
||||
'classes' => $this->classes,
|
||||
'local_pickup' => $this->local_pickup,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of the step.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_step_name(): string {
|
||||
return 'setWCShipping';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the schema for the step.
|
||||
*
|
||||
* @param int $version Optional version number of the schema.
|
||||
* @return array The schema array.
|
||||
*/
|
||||
public static function get_schema( $version = 1 ): array {
|
||||
return array(
|
||||
'type' => 'object',
|
||||
'properties' => array(
|
||||
'step' => array(
|
||||
'type' => 'string',
|
||||
'enum' => array( static::get_step_name() ),
|
||||
),
|
||||
'values' => array(
|
||||
'type' => 'object',
|
||||
'properties' => array(
|
||||
'classes' => array(
|
||||
'type' => 'array',
|
||||
'items' => array(
|
||||
'type' => 'object',
|
||||
'properties' => array(
|
||||
'term_taxonomy_id' => array( 'type' => 'string' ),
|
||||
'term_id' => array( 'type' => 'string' ),
|
||||
'taxonomy' => array( 'type' => 'string' ),
|
||||
'description' => array( 'type' => 'string' ),
|
||||
'parent' => array( 'type' => 'string' ),
|
||||
'count' => array( 'type' => 'string' ),
|
||||
),
|
||||
'required' => array( 'term_taxonomy_id', 'term_id', 'taxonomy', 'description', 'parent', 'count' ),
|
||||
),
|
||||
),
|
||||
'terms' => array(
|
||||
'type' => 'array',
|
||||
'items' => array(
|
||||
'type' => 'object',
|
||||
'properties' => array(
|
||||
'term_id' => array( 'type' => 'string' ),
|
||||
'name' => array( 'type' => 'string' ),
|
||||
'slug' => array( 'type' => 'string' ),
|
||||
'term_group' => array( 'type' => 'string' ),
|
||||
),
|
||||
'required' => array( 'term_id', 'name', 'slug', 'term_group' ),
|
||||
),
|
||||
),
|
||||
'local_pickup' => array(
|
||||
'type' => 'object',
|
||||
'properties' => array(
|
||||
'general' => array(
|
||||
'type' => 'object',
|
||||
'properties' => array(
|
||||
'enabled' => array( 'type' => 'string' ),
|
||||
'title' => array( 'type' => 'string' ),
|
||||
'tax_status' => array( 'type' => 'string' ),
|
||||
'cost' => array( 'type' => 'string' ),
|
||||
),
|
||||
),
|
||||
'locations' => array(
|
||||
'type' => 'array',
|
||||
'items' => array(
|
||||
'type' => 'object',
|
||||
'properties' => array(
|
||||
'name' => array( 'type' => 'string' ),
|
||||
'address' => array(
|
||||
'type' => 'object',
|
||||
'properties' => array(
|
||||
'address_1' => array( 'type' => 'string' ),
|
||||
'city' => array( 'type' => 'string' ),
|
||||
'state' => array( 'type' => 'string' ),
|
||||
'postcode' => array( 'type' => 'string' ),
|
||||
'country' => array( 'type' => 'string' ),
|
||||
),
|
||||
),
|
||||
'details' => array( 'type' => 'string' ),
|
||||
'enabled' => array( 'type' => 'boolean' ),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
'shipping_methods' => array(
|
||||
'type' => 'array',
|
||||
'items' => array(
|
||||
'type' => 'object',
|
||||
'properties' => array(
|
||||
'zone_id' => array( 'type' => 'string' ),
|
||||
'instance_id' => array( 'type' => 'string' ),
|
||||
'method_id' => array( 'type' => 'string' ),
|
||||
'method_order' => array( 'type' => 'string' ),
|
||||
'is_enabled' => array( 'type' => 'string' ),
|
||||
),
|
||||
'required' => array( 'zone_id', 'instance_id', 'method_id', 'method_order', 'is_enabled' ),
|
||||
),
|
||||
),
|
||||
'shipping_locations' => array(
|
||||
'type' => 'array',
|
||||
'items' => array(
|
||||
'type' => 'object',
|
||||
'properties' => array(
|
||||
'location_id' => array( 'type' => 'string' ),
|
||||
'zone_id' => array( 'type' => 'string' ),
|
||||
'location_code' => array( 'type' => 'string' ),
|
||||
'location_type' => array( 'type' => 'string' ),
|
||||
),
|
||||
'required' => array( 'location_id', 'zone_id', 'location_code', 'location_type' ),
|
||||
),
|
||||
),
|
||||
'shipping_zones' => array(
|
||||
'type' => 'array',
|
||||
'items' => array(
|
||||
'type' => 'object',
|
||||
'properties' => array(
|
||||
'zone_id' => array( 'type' => 'string' ),
|
||||
'zone_name' => array( 'type' => 'string' ),
|
||||
'zone_order' => array( 'type' => 'string' ),
|
||||
),
|
||||
'required' => array( 'zone_id', 'zone_name', 'zone_order' ),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
'required' => array( 'step', 'values' ),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
<?php
|
||||
|
||||
declare( strict_types = 1);
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\Features\Blueprint\Steps;
|
||||
|
||||
use Automattic\WooCommerce\Blueprint\Steps\Step;
|
||||
|
||||
/**
|
||||
* Class SetWCTaxRates
|
||||
*
|
||||
* This class sets WooCommerce tax rates and extends the Step class.
|
||||
*
|
||||
* @package Automattic\WooCommerce\Admin\Features\Blueprint\Steps
|
||||
*/
|
||||
class SetWCTaxRates extends Step {
|
||||
|
||||
/**
|
||||
* Tax rates.
|
||||
*
|
||||
* @var array $rates Tax rates.
|
||||
*/
|
||||
private array $rates;
|
||||
|
||||
/**
|
||||
* Tax rate locations.
|
||||
*
|
||||
* @var array $locations Tax rate locations.
|
||||
*/
|
||||
private array $locations;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param array $rates Tax rates.
|
||||
* @param array $locations Tax rate locations.
|
||||
*/
|
||||
public function __construct( array $rates, array $locations ) {
|
||||
$this->rates = $rates;
|
||||
$this->locations = $locations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the JSON array for the step.
|
||||
*
|
||||
* @return array The JSON array.
|
||||
*/
|
||||
public function prepare_json_array(): array {
|
||||
return array(
|
||||
'step' => static::get_step_name(),
|
||||
'values' => array(
|
||||
'rates' => $this->rates,
|
||||
'locations' => $this->locations,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of the step.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_step_name(): string {
|
||||
return 'setWCTaxRates';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the schema for the step.
|
||||
*
|
||||
* @param int $version Optional version number of the schema.
|
||||
* @return array The schema array.
|
||||
*/
|
||||
public static function get_schema( $version = 1 ): array {
|
||||
return array(
|
||||
'type' => 'object',
|
||||
'properties' => array(
|
||||
'step' => array(
|
||||
'type' => 'string',
|
||||
'enum' => array( static::get_step_name() ),
|
||||
),
|
||||
'values' => array(
|
||||
'type' => 'object',
|
||||
'properties' => array(
|
||||
'rates' => array(
|
||||
'type' => 'array',
|
||||
'items' => array(
|
||||
'type' => 'object',
|
||||
'properties' => array(
|
||||
'tax_rate_id' => array( 'type' => 'string' ),
|
||||
'tax_rate_country' => array( 'type' => 'string' ),
|
||||
'tax_rate_state' => array( 'type' => 'string' ),
|
||||
'tax_rate' => array( 'type' => 'string' ),
|
||||
'tax_rate_name' => array( 'type' => 'string' ),
|
||||
'tax_rate_priority' => array( 'type' => 'string' ),
|
||||
'tax_rate_compound' => array( 'type' => 'string' ),
|
||||
'tax_rate_shipping' => array( 'type' => 'string' ),
|
||||
'tax_rate_order' => array( 'type' => 'string' ),
|
||||
'tax_rate_class' => array( 'type' => 'string' ),
|
||||
),
|
||||
'required' => array(
|
||||
'tax_rate_id',
|
||||
'tax_rate_country',
|
||||
'tax_rate_state',
|
||||
'tax_rate',
|
||||
'tax_rate_name',
|
||||
'tax_rate_priority',
|
||||
'tax_rate_compound',
|
||||
'tax_rate_shipping',
|
||||
'tax_rate_order',
|
||||
'tax_rate_class',
|
||||
),
|
||||
),
|
||||
),
|
||||
'locations' => array(
|
||||
'type' => 'array',
|
||||
'items' => array(
|
||||
'type' => 'object',
|
||||
'properties' => array(
|
||||
'location_id' => array( 'type' => 'string' ),
|
||||
'location_code' => array( 'type' => 'string' ),
|
||||
'tax_rate_id' => array( 'type' => 'string' ),
|
||||
'location_type' => array( 'type' => 'string' ),
|
||||
),
|
||||
'required' => array( 'location_id', 'location_code', 'tax_rate_id', 'location_type' ),
|
||||
),
|
||||
),
|
||||
),
|
||||
'required' => array( 'rates' ),
|
||||
),
|
||||
),
|
||||
'required' => array( 'step', 'values' ),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -5,12 +5,13 @@ namespace Automattic\WooCommerce\Admin\Features;
|
||||
use Automattic\WooCommerce\Admin\PageController;
|
||||
use Automattic\WooCommerce\Blocks\Utils\BlockTemplateUtils;
|
||||
use Automattic\WooCommerce\Admin\WCAdminHelper;
|
||||
use Automattic\WooCommerce\Internal\Admin\WCAdminUser;
|
||||
|
||||
/**
|
||||
* Takes care of Launch Your Store related actions.
|
||||
*/
|
||||
class LaunchYourStore {
|
||||
const BANNER_DISMISS_USER_META_KEY = 'woocommerce_coming_soon_banner_dismissed';
|
||||
const BANNER_DISMISS_USER_META_KEY = 'coming_soon_banner_dismissed';
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
@@ -21,6 +22,7 @@ class LaunchYourStore {
|
||||
add_action( 'init', array( $this, 'register_launch_your_store_user_meta_fields' ) );
|
||||
add_filter( 'woocommerce_tracks_event_properties', array( $this, 'append_coming_soon_global_tracks' ), 10, 2 );
|
||||
add_action( 'wp_login', array( $this, 'reset_woocommerce_coming_soon_banner_dismissed' ), 10, 2 );
|
||||
add_filter( 'woocommerce_admin_get_user_data_fields', array( $this, 'add_user_data_fields' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -160,7 +162,10 @@ class LaunchYourStore {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( get_user_meta( $current_user_id, self::BANNER_DISMISS_USER_META_KEY, true ) === 'yes' ) {
|
||||
$has_dismissed_banner = WCAdminUser::get_user_data_field( $current_user_id, self::BANNER_DISMISS_USER_META_KEY )
|
||||
// Remove this check in WC 9.4.
|
||||
|| get_user_meta( $current_user_id, 'woocommerce_' . self::BANNER_DISMISS_USER_META_KEY, true ) === 'yes';
|
||||
if ( $has_dismissed_banner ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -198,6 +203,8 @@ class LaunchYourStore {
|
||||
|
||||
/**
|
||||
* Register user meta fields for Launch Your Store.
|
||||
*
|
||||
* This should be removed in WC 9.4.
|
||||
*/
|
||||
public function register_launch_your_store_user_meta_fields() {
|
||||
if ( ! $this->is_manager_or_admin() ) {
|
||||
@@ -217,7 +224,7 @@ class LaunchYourStore {
|
||||
|
||||
register_meta(
|
||||
'user',
|
||||
self::BANNER_DISMISS_USER_META_KEY,
|
||||
'woocommerce_coming_soon_banner_dismissed',
|
||||
array(
|
||||
'type' => 'string',
|
||||
'description' => 'Indicate whether the user has dismissed the coming soon notice or not.',
|
||||
@@ -227,6 +234,22 @@ class LaunchYourStore {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register user meta fields for Launch Your Store.
|
||||
*
|
||||
* @param array $user_data_fields user data fields.
|
||||
* @return array
|
||||
*/
|
||||
public function add_user_data_fields( $user_data_fields ) {
|
||||
return array_merge(
|
||||
$user_data_fields,
|
||||
array(
|
||||
'launch_your_store_tour_hidden',
|
||||
self::BANNER_DISMISS_USER_META_KEY,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset 'woocommerce_coming_soon_banner_dismissed' user meta to 'no'.
|
||||
*
|
||||
@@ -236,9 +259,9 @@ class LaunchYourStore {
|
||||
* @param object $user user object.
|
||||
*/
|
||||
public function reset_woocommerce_coming_soon_banner_dismissed( $user_login, $user ) {
|
||||
$existing_meta = get_user_meta( $user->ID, self::BANNER_DISMISS_USER_META_KEY, true );
|
||||
$existing_meta = WCAdminUser::get_user_data_field( $user->ID, self::BANNER_DISMISS_USER_META_KEY );
|
||||
if ( 'yes' === $existing_meta ) {
|
||||
update_user_meta( $user->ID, self::BANNER_DISMISS_USER_META_KEY, 'no' );
|
||||
WCAdminUser::update_user_data_field( $user->ID, self::BANNER_DISMISS_USER_META_KEY, 'no' );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
/**
|
||||
* WooCommerce Navigation Core Menu
|
||||
*
|
||||
* @deprecated 9.3.0 Navigation is no longer a feature and its classes will be removed in WooCommerce 9.4.
|
||||
* @package Woocommerce Admin
|
||||
*/
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
/**
|
||||
* WooCommerce Navigation Favorite
|
||||
*
|
||||
* @deprecated 9.3.0 Navigation is no longer a feature and its classes will be removed in WooCommerce 9.4.
|
||||
* @package Woocommerce Navigation
|
||||
*/
|
||||
|
||||
|
||||
@@ -2,17 +2,17 @@
|
||||
/**
|
||||
* Navigation Experience
|
||||
*
|
||||
* @deprecated 9.3.0 Navigation is no longer a feature and its classes will be removed in WooCommerce 9.4.
|
||||
* @package Woocommerce Admin
|
||||
*/
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\Features\Navigation;
|
||||
|
||||
use Automattic\WooCommerce\Internal\Admin\Survey;
|
||||
use Automattic\WooCommerce\Admin\Features\Features;
|
||||
use Automattic\WooCommerce\Admin\Features\Navigation\Screen;
|
||||
use Automattic\WooCommerce\Admin\Features\Navigation\Menu;
|
||||
use Automattic\WooCommerce\Admin\Features\Navigation\CoreMenu;
|
||||
use Automattic\WooCommerce\Internal\Admin\WCAdminAssets;
|
||||
use WC_Tracks;
|
||||
|
||||
/**
|
||||
* Contains logic for the Navigation
|
||||
@@ -23,115 +23,27 @@ class Init {
|
||||
*/
|
||||
const TOGGLE_OPTION_NAME = 'woocommerce_navigation_enabled';
|
||||
|
||||
/**
|
||||
* Determines if the feature has been toggled on or off.
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
protected static $is_updated = false;
|
||||
|
||||
/**
|
||||
* Hook into WooCommerce.
|
||||
*/
|
||||
public function __construct() {
|
||||
add_action( 'update_option_' . self::TOGGLE_OPTION_NAME, array( $this, 'reload_page_on_toggle' ), 10, 2 );
|
||||
add_action( 'woocommerce_settings_saved', array( $this, 'maybe_reload_page' ) );
|
||||
add_action( 'admin_enqueue_scripts', array( $this, 'maybe_enqueue_opt_out_scripts' ) );
|
||||
|
||||
if ( Features::is_enabled( 'navigation' ) ) {
|
||||
Menu::instance()->init();
|
||||
CoreMenu::instance()->init();
|
||||
Screen::instance()->init();
|
||||
// Disable the option to turn off the feature.
|
||||
update_option( self::TOGGLE_OPTION_NAME, 'no' );
|
||||
|
||||
if ( class_exists( 'WC_Tracks' ) ) {
|
||||
WC_Tracks::record_event( 'deprecated_navigation_in_use' );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the feature toggle to the features settings.
|
||||
* Create a deprecation notice.
|
||||
*
|
||||
* @deprecated 7.0 The WooCommerce Admin features are now handled by the WooCommerce features engine (see the FeaturesController class).
|
||||
*
|
||||
* @param array $features Feature sections.
|
||||
* @return array
|
||||
* @param string $fcn The function that is deprecated.
|
||||
*/
|
||||
public static function add_feature_toggle( $features ) {
|
||||
return $features;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if sufficient versions are present to support Navigation feature
|
||||
*/
|
||||
public function is_nav_compatible() {
|
||||
include_once ABSPATH . 'wp-admin/includes/plugin.php';
|
||||
|
||||
$gutenberg_minimum_version = '9.0.0'; // https://github.com/WordPress/gutenberg/releases/tag/v9.0.0.
|
||||
$wp_minimum_version = '5.6';
|
||||
$has_gutenberg = is_plugin_active( 'gutenberg/gutenberg.php' );
|
||||
$gutenberg_version = $has_gutenberg ? get_plugin_data( WP_PLUGIN_DIR . '/gutenberg/gutenberg.php' )['Version'] : false;
|
||||
|
||||
if ( $gutenberg_version && version_compare( $gutenberg_version, $gutenberg_minimum_version, '>=' ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Get unmodified $wp_version.
|
||||
include ABSPATH . WPINC . '/version.php';
|
||||
|
||||
// Strip '-src' from the version string. Messes up version_compare().
|
||||
$wp_version = str_replace( '-src', '', $wp_version );
|
||||
|
||||
if ( version_compare( $wp_version, $wp_minimum_version, '>=' ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reloads the page when the option is toggled to make sure all nav features are loaded.
|
||||
*
|
||||
* @param string $old_value Old value.
|
||||
* @param string $value New value.
|
||||
*/
|
||||
public static function reload_page_on_toggle( $old_value, $value ) {
|
||||
if ( $old_value === $value ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( 'yes' !== $value ) {
|
||||
update_option( 'woocommerce_navigation_show_opt_out', 'yes' );
|
||||
}
|
||||
|
||||
self::$is_updated = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reload the page if the setting has been updated.
|
||||
*/
|
||||
public static function maybe_reload_page() {
|
||||
if ( ! isset( $_SERVER['REQUEST_URI'] ) || ! self::$is_updated ) {
|
||||
return;
|
||||
}
|
||||
|
||||
wp_safe_redirect( wp_unslash( $_SERVER['REQUEST_URI'] ) );
|
||||
exit();
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue the opt out scripts.
|
||||
*/
|
||||
public function maybe_enqueue_opt_out_scripts() {
|
||||
if ( get_option( 'woocommerce_navigation_show_opt_out', 'no' ) !== 'yes' ) {
|
||||
return;
|
||||
}
|
||||
|
||||
WCAdminAssets::register_style( 'navigation-opt-out', 'style', array( 'wp-components' ) );
|
||||
WCAdminAssets::register_script( 'wp-admin-scripts', 'navigation-opt-out', true );
|
||||
wp_localize_script(
|
||||
'wc-admin-navigation-opt-out',
|
||||
'surveyData',
|
||||
array(
|
||||
'url' => Survey::get_url( '/new-navigation-opt-out' ),
|
||||
)
|
||||
);
|
||||
delete_option( 'woocommerce_navigation_show_opt_out' );
|
||||
public static function deprecation_notice( $fcn ) {
|
||||
// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
|
||||
error_log( 'Automattic\WooCommerce\Admin\Features\Navigation\\' . $fcn . ' is deprecated since 9.3 with no alternative. Navigation classes will be removed in WooCommerce 9.4' );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
/**
|
||||
* WooCommerce Navigation Menu
|
||||
*
|
||||
* @deprecated 9.3.0 Navigation is no longer a feature and its classes will be removed in WooCommerce 9.4.
|
||||
* @package Woocommerce Navigation
|
||||
*/
|
||||
|
||||
@@ -10,6 +11,7 @@ namespace Automattic\WooCommerce\Admin\Features\Navigation;
|
||||
use Automattic\WooCommerce\Admin\Features\Navigation\Favorites;
|
||||
use Automattic\WooCommerce\Admin\Features\Navigation\Screen;
|
||||
use Automattic\WooCommerce\Admin\Features\Navigation\CoreMenu;
|
||||
use Automattic\WooCommerce\Admin\Features\Navigation\Init;
|
||||
|
||||
/**
|
||||
* Contains logic for the WooCommerce Navigation menu.
|
||||
@@ -95,172 +97,33 @@ class Menu {
|
||||
|
||||
/**
|
||||
* Init.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function init() {
|
||||
add_action( 'admin_menu', array( $this, 'add_core_items' ), 100 );
|
||||
add_filter( 'admin_enqueue_scripts', array( $this, 'enqueue_data' ), 20 );
|
||||
|
||||
add_filter( 'admin_menu', array( $this, 'migrate_core_child_items' ), PHP_INT_MAX - 1 );
|
||||
add_filter( 'admin_menu', array( $this, 'migrate_menu_items' ), PHP_INT_MAX - 2 );
|
||||
}
|
||||
final public function init() {}
|
||||
|
||||
/**
|
||||
* Convert a WordPress menu callback to a URL.
|
||||
*
|
||||
* @param string $callback Menu callback.
|
||||
* @return string
|
||||
*/
|
||||
public static function get_callback_url( $callback ) {
|
||||
// Return the full URL.
|
||||
if ( strpos( $callback, 'http' ) === 0 ) {
|
||||
return $callback;
|
||||
}
|
||||
|
||||
$pos = strpos( $callback, '?' );
|
||||
$file = $pos > 0 ? substr( $callback, 0, $pos ) : $callback;
|
||||
if ( file_exists( ABSPATH . "/wp-admin/$file" ) ) {
|
||||
return $callback;
|
||||
}
|
||||
return 'admin.php?page=' . $callback;
|
||||
}
|
||||
public static function get_callback_url() {}
|
||||
|
||||
/**
|
||||
* Get the parent key if one exists.
|
||||
*
|
||||
* @param string $callback Callback or URL.
|
||||
* @return string|null
|
||||
*/
|
||||
public static function get_parent_key( $callback ) {
|
||||
global $submenu;
|
||||
|
||||
if ( ! $submenu ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// This is already a parent item.
|
||||
if ( isset( $submenu[ $callback ] ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
foreach ( $submenu as $key => $menu ) {
|
||||
foreach ( $menu as $item ) {
|
||||
if ( $item[ self::CALLBACK ] === $callback ) {
|
||||
return $key;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
public static function get_parent_key() {}
|
||||
|
||||
/**
|
||||
* Adds a top level menu item to the navigation.
|
||||
*
|
||||
* @param array $args Array containing the necessary arguments.
|
||||
* $args = array(
|
||||
* 'id' => (string) The unique ID of the menu item. Required.
|
||||
* 'title' => (string) Title of the menu item. Required.
|
||||
* 'url' => (string) URL or callback to be used. Required.
|
||||
* 'order' => (int) Menu item order.
|
||||
* 'migrate' => (bool) Whether or not to hide the item in the wp admin menu.
|
||||
* 'menuId' => (string) The ID of the menu to add the category to.
|
||||
* ).
|
||||
*/
|
||||
private static function add_category( $args ) {
|
||||
if ( ! isset( $args['id'] ) || isset( self::$menu_items[ $args['id'] ] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$defaults = array(
|
||||
'id' => '',
|
||||
'title' => '',
|
||||
'order' => 100,
|
||||
'migrate' => true,
|
||||
'menuId' => 'primary',
|
||||
'isCategory' => true,
|
||||
);
|
||||
$menu_item = wp_parse_args( $args, $defaults );
|
||||
$menu_item['title'] = wp_strip_all_tags( wp_specialchars_decode( $menu_item['title'] ) );
|
||||
unset( $menu_item['url'] );
|
||||
unset( $menu_item['capability'] );
|
||||
|
||||
if ( ! isset( $menu_item['parent'] ) ) {
|
||||
$menu_item['parent'] = 'woocommerce';
|
||||
$menu_item['backButtonLabel'] = __(
|
||||
'WooCommerce Home',
|
||||
'woocommerce'
|
||||
);
|
||||
}
|
||||
|
||||
self::$menu_items[ $menu_item['id'] ] = $menu_item;
|
||||
self::$categories[ $menu_item['id'] ] = array();
|
||||
self::$categories[ $menu_item['parent'] ][] = $menu_item['id'];
|
||||
|
||||
if ( isset( $args['url'] ) ) {
|
||||
self::$callbacks[ $args['url'] ] = $menu_item['migrate'];
|
||||
}
|
||||
private static function add_category() {
|
||||
Init::deprecation_notice( 'Menu::add_category' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a child menu item to the navigation.
|
||||
*
|
||||
* @param array $args Array containing the necessary arguments.
|
||||
* $args = array(
|
||||
* 'id' => (string) The unique ID of the menu item. Required.
|
||||
* 'title' => (string) Title of the menu item. Required.
|
||||
* 'parent' => (string) Parent menu item ID.
|
||||
* 'capability' => (string) Capability to view this menu item.
|
||||
* 'url' => (string) URL or callback to be used. Required.
|
||||
* 'order' => (int) Menu item order.
|
||||
* 'migrate' => (bool) Whether or not to hide the item in the wp admin menu.
|
||||
* 'menuId' => (string) The ID of the menu to add the item to.
|
||||
* 'matchExpression' => (string) A regular expression used to identify if the menu item is active.
|
||||
* ).
|
||||
*/
|
||||
private static function add_item( $args ) {
|
||||
if ( ! isset( $args['id'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( isset( self::$menu_items[ $args['id'] ] ) ) {
|
||||
wc_doing_it_wrong(
|
||||
__METHOD__,
|
||||
sprintf(
|
||||
/* translators: 1: Duplicate menu item path. */
|
||||
esc_html__( 'You have attempted to register a duplicate item with WooCommerce Navigation: %1$s', 'woocommerce' ),
|
||||
'`' . $args['id'] . '`'
|
||||
),
|
||||
'6.5.0'
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$defaults = array(
|
||||
'id' => '',
|
||||
'title' => '',
|
||||
'capability' => 'manage_woocommerce',
|
||||
'url' => '',
|
||||
'order' => 100,
|
||||
'migrate' => true,
|
||||
'menuId' => 'primary',
|
||||
);
|
||||
$menu_item = wp_parse_args( $args, $defaults );
|
||||
$menu_item['title'] = wp_strip_all_tags( wp_specialchars_decode( $menu_item['title'] ) );
|
||||
$menu_item['url'] = self::get_callback_url( $menu_item['url'] );
|
||||
|
||||
if ( ! isset( $menu_item['parent'] ) ) {
|
||||
$menu_item['parent'] = 'woocommerce';
|
||||
}
|
||||
|
||||
$menu_item['menuId'] = self::get_item_menu_id( $menu_item );
|
||||
|
||||
self::$menu_items[ $menu_item['id'] ] = $menu_item;
|
||||
self::$categories[ $menu_item['parent'] ][] = $menu_item['id'];
|
||||
|
||||
if ( isset( $args['url'] ) ) {
|
||||
self::$callbacks[ $args['url'] ] = $menu_item['migrate'];
|
||||
}
|
||||
private static function add_item() {
|
||||
Init::deprecation_notice( 'Menu::add_item' );
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -287,112 +150,25 @@ class Menu {
|
||||
|
||||
/**
|
||||
* Adds a plugin category.
|
||||
*
|
||||
* @param array $args Array containing the necessary arguments.
|
||||
* $args = array(
|
||||
* 'id' => (string) The unique ID of the menu item. Required.
|
||||
* 'title' => (string) Title of the menu item. Required.
|
||||
* 'url' => (string) URL or callback to be used. Required.
|
||||
* 'migrate' => (bool) Whether or not to hide the item in the wp admin menu.
|
||||
* 'order' => (int) Menu item order.
|
||||
* ).
|
||||
*/
|
||||
public static function add_plugin_category( $args ) {
|
||||
$category_args = array_merge(
|
||||
$args,
|
||||
array(
|
||||
'menuId' => 'plugins',
|
||||
)
|
||||
);
|
||||
|
||||
if ( ! isset( $category_args['parent'] ) ) {
|
||||
unset( $category_args['order'] );
|
||||
}
|
||||
|
||||
$menu_id = self::get_item_menu_id( $category_args );
|
||||
if ( ! in_array( $menu_id, array( 'plugins', 'favorites' ), true ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$category_args['menuId'] = $menu_id;
|
||||
|
||||
self::add_category( $category_args );
|
||||
public static function add_plugin_category() {
|
||||
Init::deprecation_notice( 'Menu::add_plugin_category' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a plugin item.
|
||||
*
|
||||
* @param array $args Array containing the necessary arguments.
|
||||
* $args = array(
|
||||
* 'id' => (string) The unique ID of the menu item. Required.
|
||||
* 'title' => (string) Title of the menu item. Required.
|
||||
* 'parent' => (string) Parent menu item ID.
|
||||
* 'capability' => (string) Capability to view this menu item.
|
||||
* 'url' => (string) URL or callback to be used. Required.
|
||||
* 'migrate' => (bool) Whether or not to hide the item in the wp admin menu.
|
||||
* 'order' => (int) Menu item order.
|
||||
* 'matchExpression' => (string) A regular expression used to identify if the menu item is active.
|
||||
* ).
|
||||
*/
|
||||
public static function add_plugin_item( $args ) {
|
||||
if ( ! isset( $args['parent'] ) ) {
|
||||
unset( $args['order'] );
|
||||
}
|
||||
|
||||
$item_args = array_merge(
|
||||
$args,
|
||||
array(
|
||||
'menuId' => 'plugins',
|
||||
)
|
||||
);
|
||||
|
||||
$menu_id = self::get_item_menu_id( $item_args );
|
||||
|
||||
if ( 'plugins' !== $menu_id ) {
|
||||
return;
|
||||
}
|
||||
|
||||
self::add_item( $item_args );
|
||||
public static function add_plugin_item() {
|
||||
Init::deprecation_notice( 'Menu::add_plugin_item' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a plugin setting item.
|
||||
*
|
||||
* @param array $args Array containing the necessary arguments.
|
||||
* $args = array(
|
||||
* 'id' => (string) The unique ID of the menu item. Required.
|
||||
* 'title' => (string) Title of the menu item. Required.
|
||||
* 'capability' => (string) Capability to view this menu item.
|
||||
* 'url' => (string) URL or callback to be used. Required.
|
||||
* 'migrate' => (bool) Whether or not to hide the item in the wp admin menu.
|
||||
* ).
|
||||
*/
|
||||
public static function add_setting_item( $args ) {
|
||||
unset( $args['order'] );
|
||||
|
||||
if ( isset( $args['parent'] ) || isset( $args['menuId'] ) ) {
|
||||
error_log( // phpcs:ignore
|
||||
sprintf(
|
||||
/* translators: 1: Duplicate menu item path. */
|
||||
esc_html__( 'The item ID %1$s attempted to register using an invalid option. The arguments `menuId` and `parent` are not allowed for add_setting_item()', 'woocommerce' ),
|
||||
'`' . $args['id'] . '`'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$item_args = array_merge(
|
||||
$args,
|
||||
array(
|
||||
'menuId' => 'secondary',
|
||||
'parent' => 'woocommerce-settings',
|
||||
)
|
||||
);
|
||||
|
||||
self::add_item( $item_args );
|
||||
public static function add_setting_item() {
|
||||
Init::deprecation_notice( 'Menu::add_setting_item' );
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Get menu item templates for a given post type.
|
||||
*
|
||||
|
||||
@@ -2,12 +2,14 @@
|
||||
/**
|
||||
* WooCommerce Navigation Screen
|
||||
*
|
||||
* @deprecated 9.3.0 Navigation is no longer a feature and its classes will be removed in WooCommerce 9.4.
|
||||
* @package Woocommerce Navigation
|
||||
*/
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\Features\Navigation;
|
||||
|
||||
use Automattic\WooCommerce\Admin\Features\Navigation\Menu;
|
||||
use Automattic\WooCommerce\Admin\Features\Navigation\Init;
|
||||
|
||||
/**
|
||||
* Contains logic for the WooCommerce Navigation menu.
|
||||
@@ -85,6 +87,8 @@ class Screen {
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_woocommerce_page() {
|
||||
Init::deprecation_notice( 'Screen::is_woocommerce_page' );
|
||||
|
||||
global $pagenow;
|
||||
|
||||
// Get taxonomy if on a taxonomy screen.
|
||||
@@ -218,23 +222,15 @@ class Screen {
|
||||
|
||||
/**
|
||||
* Register post type for use in WooCommerce Navigation screens.
|
||||
*
|
||||
* @param string $post_type Post type to add.
|
||||
*/
|
||||
public static function register_post_type( $post_type ) {
|
||||
if ( ! in_array( $post_type, self::$post_types, true ) ) {
|
||||
self::$post_types[] = $post_type;
|
||||
}
|
||||
public static function register_post_type() {
|
||||
Init::deprecation_notice( 'Screen::register_post_type' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register taxonomy for use in WooCommerce Navigation screens.
|
||||
*
|
||||
* @param string $taxonomy Taxonomy to add.
|
||||
*/
|
||||
public static function register_taxonomy( $taxonomy ) {
|
||||
if ( ! in_array( $taxonomy, self::$taxonomies, true ) ) {
|
||||
self::$taxonomies[] = $taxonomy;
|
||||
}
|
||||
public static function register_taxonomy() {
|
||||
Init::deprecation_notice( 'Screen::register_taxonomy' );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,7 +117,6 @@ class TaskLists {
|
||||
'Payments',
|
||||
'Tax',
|
||||
'Shipping',
|
||||
'Marketing',
|
||||
'LaunchYourStore',
|
||||
);
|
||||
|
||||
@@ -165,6 +164,7 @@ class TaskLists {
|
||||
),
|
||||
),
|
||||
'tasks' => array(
|
||||
'Marketing',
|
||||
'ExtendStore',
|
||||
'AdditionalPayments',
|
||||
'GetMobileApp',
|
||||
@@ -297,7 +297,6 @@ class TaskLists {
|
||||
$task_list->add_task( $task );
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -318,8 +317,8 @@ class TaskLists {
|
||||
public static function get_lists_by_ids( $ids ) {
|
||||
return array_filter(
|
||||
self::$lists,
|
||||
function( $list ) use ( $ids ) {
|
||||
return in_array( $list->get_list_id(), $ids, true );
|
||||
function ( $task_list ) use ( $ids ) {
|
||||
return in_array( $task_list->get_list_id(), $ids, true );
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -404,25 +403,31 @@ class TaskLists {
|
||||
/**
|
||||
* Return number of setup tasks remaining
|
||||
*
|
||||
* @return number
|
||||
* This is not updated immediately when a task is completed, but rather when task is marked as complete in the database to reduce performance impact.
|
||||
*
|
||||
* @return int|null
|
||||
*/
|
||||
public static function setup_tasks_remaining() {
|
||||
$setup_list = self::get_list( 'setup' );
|
||||
|
||||
if ( ! $setup_list || $setup_list->is_hidden() || $setup_list->has_previously_completed() || $setup_list->is_complete() ) {
|
||||
if ( ! $setup_list || $setup_list->is_hidden() || $setup_list->has_previously_completed() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$remaining_tasks = array_values(
|
||||
$viewable_tasks = $setup_list->get_viewable_tasks();
|
||||
$completed_tasks = get_option( Task::COMPLETED_OPTION, array() );
|
||||
if ( ! is_array( $completed_tasks ) ) {
|
||||
$completed_tasks = array();
|
||||
}
|
||||
|
||||
return count(
|
||||
array_filter(
|
||||
$setup_list->get_viewable_tasks(),
|
||||
function( $task ) {
|
||||
return ! $task->is_complete();
|
||||
$viewable_tasks,
|
||||
function ( $task ) use ( $completed_tasks ) {
|
||||
return ! in_array( $task->get_id(), $completed_tasks, true );
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
return count( $remaining_tasks );
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -443,7 +448,6 @@ class TaskLists {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -188,7 +188,7 @@ class AdditionalPayments extends Payments {
|
||||
*/
|
||||
private static function get_suggestion_gateways( $filter_by = 'category_additional' ) {
|
||||
$country = wc_get_base_location()['country'];
|
||||
$plugin_suggestions = Init::get_suggestions();
|
||||
$plugin_suggestions = Init::get_cached_or_default_suggestions();
|
||||
$plugin_suggestions = array_filter(
|
||||
$plugin_suggestions,
|
||||
function( $plugin ) use ( $country, $filter_by ) {
|
||||
|
||||
@@ -56,79 +56,40 @@ class Marketing extends Task {
|
||||
return __( '2 minutes', 'woocommerce' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Task completion.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_complete() {
|
||||
if ( null === $this->is_complete_result ) {
|
||||
$this->is_complete_result = self::has_installed_extensions();
|
||||
}
|
||||
|
||||
return $this->is_complete_result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Task visibility.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function can_view() {
|
||||
return Features::is_enabled( 'remote-free-extensions' ) && count( self::get_plugins() ) > 0;
|
||||
return Features::is_enabled( 'remote-free-extensions' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the marketing plugins.
|
||||
*
|
||||
* @deprecated 9.3.0 Removed to improve performance.
|
||||
* @return array
|
||||
*/
|
||||
public static function get_plugins() {
|
||||
$bundles = RemoteFreeExtensions::get_extensions(
|
||||
array(
|
||||
'task-list/reach',
|
||||
'task-list/grow',
|
||||
)
|
||||
);
|
||||
|
||||
return array_reduce(
|
||||
$bundles,
|
||||
function( $plugins, $bundle ) {
|
||||
$visible = array();
|
||||
foreach ( $bundle['plugins'] as $plugin ) {
|
||||
if ( $plugin->is_visible ) {
|
||||
$visible[] = $plugin;
|
||||
}
|
||||
}
|
||||
return array_merge( $plugins, $visible );
|
||||
},
|
||||
array()
|
||||
wc_deprecated_function(
|
||||
__METHOD__,
|
||||
'9.3.0'
|
||||
);
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the store has installed marketing extensions.
|
||||
*
|
||||
* @deprecated 9.3.0 Removed to improve performance.
|
||||
* @return bool
|
||||
*/
|
||||
public static function has_installed_extensions() {
|
||||
$plugins = self::get_plugins();
|
||||
$remaining = array();
|
||||
$installed = array();
|
||||
|
||||
foreach ( $plugins as $plugin ) {
|
||||
if ( ! $plugin->is_installed ) {
|
||||
$remaining[] = $plugin;
|
||||
} else {
|
||||
$installed[] = $plugin;
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure the task has been actioned and a marketing extension has been installed.
|
||||
if ( count( $installed ) > 0 && Task::is_task_actioned( 'marketing' ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
wc_deprecated_function(
|
||||
__METHOD__,
|
||||
'9.3.0'
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ use Automattic\WooCommerce\Admin\Features\OnboardingTasks\Task;
|
||||
use Automattic\WooCommerce\Admin\PluginsHelper;
|
||||
use Automattic\WooCommerce\Admin\Features\PaymentGatewaySuggestions\Init as Suggestions;
|
||||
use Automattic\WooCommerce\Internal\Admin\WCPayPromotion\Init as WCPayPromotionInit;
|
||||
use Automattic\WooCommerce\Admin\Features\PaymentGatewaySuggestions\DefaultPaymentGateways;
|
||||
|
||||
/**
|
||||
* WooCommercePayments Task
|
||||
@@ -179,11 +180,11 @@ class WooCommercePayments extends Task {
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_supported() {
|
||||
$suggestions = Suggestions::get_suggestions();
|
||||
$suggestions = Suggestions::get_suggestions( DefaultPaymentGateways::get_all() );
|
||||
$suggestion_plugins = array_merge(
|
||||
...array_filter(
|
||||
array_column( $suggestions, 'plugins' ),
|
||||
function( $plugins ) {
|
||||
function ( $plugins ) {
|
||||
return is_array( $plugins );
|
||||
}
|
||||
)
|
||||
|
||||
@@ -7,8 +7,6 @@ namespace Automattic\WooCommerce\Admin\Features\PaymentGatewaySuggestions;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
use Automattic\WooCommerce\Admin\Features\PaymentGatewaySuggestions\DefaultPaymentGateways;
|
||||
use Automattic\WooCommerce\Admin\Features\PaymentGatewaySuggestions\PaymentGatewaysController;
|
||||
use Automattic\WooCommerce\Admin\RemoteSpecs\RemoteSpecsEngine;
|
||||
|
||||
/**
|
||||
@@ -63,6 +61,31 @@ class Init extends RemoteSpecsEngine {
|
||||
return $specs_to_return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets either cached or default suggestions.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_cached_or_default_suggestions() {
|
||||
$specs = 'no' === get_option( 'woocommerce_show_marketplace_suggestions', 'yes' )
|
||||
? DefaultPaymentGateways::get_all()
|
||||
: PaymentGatewaySuggestionsDataSourcePoller::get_instance()->get_cached_specs();
|
||||
|
||||
if ( ! is_array( $specs ) || 0 === count( $specs ) ) {
|
||||
$specs = DefaultPaymentGateways::get_all();
|
||||
}
|
||||
/**
|
||||
* Allows filtering of payment gateway suggestion specs
|
||||
*
|
||||
* @since 6.4.0
|
||||
*
|
||||
* @param array Gateway specs.
|
||||
*/
|
||||
$specs = apply_filters( 'woocommerce_admin_payment_gateway_suggestion_specs', $specs );
|
||||
$results = EvaluateSuggestion::evaluate_specs( $specs );
|
||||
return $results['suggestions'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the specs transient.
|
||||
*/
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\Features\PaymentGatewaySuggestions;
|
||||
|
||||
use Automattic\WooCommerce\Admin\DataSourcePoller;
|
||||
use Automattic\WooCommerce\Admin\RemoteSpecs\DataSourcePoller;
|
||||
|
||||
/**
|
||||
* Specs data source poller class for payment gateway suggestions.
|
||||
|
||||
@@ -574,6 +574,6 @@ class PageController {
|
||||
* TODO: See usage in `admin.php`. This needs refactored and implemented properly in core.
|
||||
*/
|
||||
public static function is_embed_page() {
|
||||
return wc_admin_is_connected_page() || ( ! self::is_admin_page() && class_exists( 'Automattic\WooCommerce\Admin\Features\Navigation\Screen' ) && Screen::is_woocommerce_page() );
|
||||
return wc_admin_is_connected_page();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ use Automatic_Upgrader_Skin;
|
||||
use Automattic\WooCommerce\Admin\PluginsInstallLoggers\AsyncPluginsInstallLogger;
|
||||
use Automattic\WooCommerce\Admin\PluginsInstallLoggers\PluginsInstallLogger;
|
||||
use Automattic\WooCommerce\Internal\Admin\WCAdminAssets;
|
||||
use Automattic\WooCommerce\Utilities\PluginUtil;
|
||||
use Plugin_Upgrader;
|
||||
use WC_Helper;
|
||||
use WC_Helper_Updater;
|
||||
@@ -80,7 +81,7 @@ class PluginsHelper {
|
||||
*
|
||||
* @param string $slug Plugin slug to get path for.
|
||||
*
|
||||
* @return string|false
|
||||
* @return string|false The plugin path or false if the plugin is not installed.
|
||||
*/
|
||||
public static function get_plugin_path_from_slug( $slug ) {
|
||||
$plugins = get_plugins();
|
||||
@@ -137,16 +138,25 @@ class PluginsHelper {
|
||||
/**
|
||||
* Get an array of active plugin slugs.
|
||||
*
|
||||
* @return array
|
||||
* The list will include both network active and site active plugins.
|
||||
*
|
||||
* @return array The list of active plugin slugs.
|
||||
*/
|
||||
public static function get_active_plugin_slugs() {
|
||||
return array_map(
|
||||
function ( $plugin_path ) {
|
||||
$path_parts = explode( '/', $plugin_path );
|
||||
return array_unique(
|
||||
array_map(
|
||||
function ( $absolute_path ) {
|
||||
// Make the path relative to the plugins directory.
|
||||
$plugin_path = str_replace( WP_PLUGIN_DIR . '/', '', $absolute_path );
|
||||
|
||||
return $path_parts[0];
|
||||
},
|
||||
get_option( 'active_plugins', array() )
|
||||
// Split the path to get the plugin slug (aka the directory name).
|
||||
$path_parts = explode( '/', $plugin_path );
|
||||
|
||||
return $path_parts[0];
|
||||
},
|
||||
// Use this method as it is the most bulletproof way to get the active plugins.
|
||||
wc_get_container()->get( PluginUtil::class )->get_all_active_valid_plugins()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -173,7 +183,7 @@ class PluginsHelper {
|
||||
public static function is_plugin_active( $plugin ) {
|
||||
$plugin_path = self::get_plugin_path_from_slug( $plugin );
|
||||
|
||||
return $plugin_path ? in_array( $plugin_path, get_option( 'active_plugins', array() ), true ) : false;
|
||||
return $plugin_path && \is_plugin_active( $plugin_path );
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -829,7 +839,7 @@ class PluginsHelper {
|
||||
$subscriptions,
|
||||
function ( $sub ) {
|
||||
return ( ! empty( $sub['local']['installed'] ) && ! empty( $sub['product_key'] ) )
|
||||
&& $sub['active']
|
||||
&& ( $sub['active'] || empty( $sub['connections'] ) ) // Active on current site or not connected to any sites.
|
||||
&& $sub['expiring']
|
||||
&& ! $sub['autorenew'];
|
||||
},
|
||||
@@ -907,7 +917,7 @@ class PluginsHelper {
|
||||
$subscriptions,
|
||||
function ( $sub ) {
|
||||
return ( ! empty( $sub['local']['installed'] ) && ! empty( $sub['product_key'] ) )
|
||||
&& $sub['active']
|
||||
&& ( $sub['active'] || empty( $sub['connections'] ) ) // Active on current site or not connected to any sites.
|
||||
&& $sub['expired']
|
||||
&& ! $sub['lifetime'];
|
||||
},
|
||||
|
||||
@@ -106,9 +106,9 @@ abstract class DataSourcePoller {
|
||||
public function get_specs_from_data_sources() {
|
||||
$locale = get_user_locale();
|
||||
$specs_group = get_transient( $this->args['transient_name'] ) ?? array();
|
||||
$specs = isset( $specs_group[ $locale ] ) ? $specs_group[ $locale ] : array();
|
||||
$specs = isset( $specs_group[ $locale ] ) ? $specs_group[ $locale ] : null;
|
||||
|
||||
if ( ! is_array( $specs ) || empty( $specs ) ) {
|
||||
if ( ! is_array( $specs ) ) {
|
||||
$this->read_specs_from_data_sources();
|
||||
$specs_group = get_transient( $this->args['transient_name'] );
|
||||
$specs = isset( $specs_group[ $locale ] ) ? $specs_group[ $locale ] : array();
|
||||
@@ -126,6 +126,29 @@ abstract class DataSourcePoller {
|
||||
return false !== $specs ? $specs : array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets specs from cache if it exists.
|
||||
*
|
||||
* @return array list of specs.
|
||||
*/
|
||||
public function get_cached_specs() {
|
||||
$locale = get_user_locale();
|
||||
$specs_group = get_transient( $this->args['transient_name'] ) ?? array();
|
||||
$specs = isset( $specs_group[ $locale ] ) ? $specs_group[ $locale ] : null;
|
||||
|
||||
/**
|
||||
* Filter specs.
|
||||
*
|
||||
* @param array $specs List of specs.
|
||||
* @param string $this->id Spec identifier.
|
||||
*
|
||||
* @since 8.8.0
|
||||
*/
|
||||
$specs = apply_filters( self::FILTER_NAME_SPECS, $specs, $this->id );
|
||||
|
||||
return false !== $specs ? $specs : array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the data sources for specs and persists those specs.
|
||||
*
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user