auto-patch 638-dev-dev01-2024-05-14T20_44_36

This commit is contained in:
root
2024-05-14 20:44:36 +00:00
parent a941559057
commit 5dbb0b284e
1812 changed files with 29671 additions and 14588 deletions

View File

@@ -51,7 +51,6 @@ class Init {
add_filter( 'woocommerce_rest_prepare_shop_order_object', array( __CLASS__, 'add_currency_symbol_to_order_response' ) );
include_once WC_ABSPATH . 'includes/admin/class-wc-admin-upload-downloadable-product.php';
}
/**
@@ -105,6 +104,10 @@ class Init {
$product_form_controllers[] = 'Automattic\WooCommerce\Admin\API\ProductForm';
}
if ( Features::is_enabled( 'launch-your-store' ) ) {
$controllers[] = 'Automattic\WooCommerce\Admin\API\LaunchYourStore';
}
if ( Features::is_enabled( 'analytics' ) ) {
$analytics_controllers = array(
'Automattic\WooCommerce\Admin\API\Customers',
@@ -134,8 +137,7 @@ class Init {
// The performance indicators controller must be registered last, after other /stats endpoints have been registered.
$analytics_controllers[] = 'Automattic\WooCommerce\Admin\API\Reports\PerformanceIndicators\Controller';
$controllers = array_merge( $controllers, $analytics_controllers, $product_form_controllers );
$controllers = array_merge( $controllers, $analytics_controllers, $product_form_controllers );
}
/**
@@ -190,8 +192,8 @@ class Init {
* object in REST API responses. For use in formatAmount().
*
* @internal
* @param {WP_REST_Response} $response REST response object.
* @returns {WP_REST_Response}
* @param WP_REST_Response $response REST response object.
* @returns WP_REST_Response
*/
public static function add_currency_symbol_to_order_response( $response ) {
$response_data = $response->get_data();

View File

@@ -0,0 +1,98 @@
<?php
/**
* REST API Launch Your Store Controller
*
* Handles requests to /launch-your-store/*
*/
namespace Automattic\WooCommerce\Admin\API;
use Automattic\WooCommerce\Admin\WCAdminHelper;
defined( 'ABSPATH' ) || exit;
/**
* Launch Your Store controller.
*
* @internal
*/
class LaunchYourStore {
/**
* Endpoint namespace.
*
* @var string
*/
protected $namespace = 'wc-admin';
/**
* Route base.
*
* @var string
*/
protected $rest_base = 'launch-your-store';
/**
* Register routes.
*/
public function register_routes() {
register_rest_route(
$this->namespace,
'/' . $this->rest_base . '/initialize-coming-soon',
array(
array(
'methods' => 'POST',
'callback' => array( $this, 'initialize_coming_soon' ),
'permission_callback' => array( $this, 'must_be_shop_manager_or_admin' ),
),
)
);
}
/**
* User must be either shop_manager or administrator.
*
* @return bool
*/
public function must_be_shop_manager_or_admin() {
// phpcs:ignore
if ( ! current_user_can( 'manage_woocommerce' ) && ! current_user_can( 'administrator' ) ) {
return false;
}
return true;
}
/**
* Initializes options for coming soon. Does not override if options exist.
*
* @return bool|void
*/
public function initialize_coming_soon() {
$current_user_id = get_current_user_id();
// Abort if we don't have a user id for some reason.
if ( ! $current_user_id ) {
return;
}
$coming_soon = 'yes';
$store_pages_only = WCAdminHelper::is_site_fresh() ? 'no' : 'yes';
$private_link = 'no';
$share_key = wp_generate_password( 32, false );
add_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 );
wc_admin_record_tracks_event(
'launch_your_store_initialize_coming_soon',
array(
'coming_soon' => $coming_soon,
'store_pages_only' => $store_pages_only,
'private_link' => $private_link,
)
);
return true;
}
}

View File

@@ -132,6 +132,57 @@ class MarketingCampaigns extends WC_REST_Controller {
return $response;
}
/**
* Get formatted price based on Price type.
*
* This uses plugins/woocommerce/i18n/currency-info.php and plugins/woocommerce/i18n/locale-info.php to get option object based on $price->currency.
*
* Example:
*
* - When $price->currency is 'USD' and $price->value is '1000', it should return '$1000.00'.
* - When $price->currency is 'JPY' and $price->value is '1000', it should return '¥1,000'.
* - When $price->currency is 'AED' and $price->value is '1000', it should return '5.000,00 د.إ'.
*
* @param Price $price Price object.
* @return String formatted price.
*/
private function get_formatted_price( $price ) {
// Get $num_decimals to be passed to wc_price.
$locale_info_all = include WC()->plugin_path() . '/i18n/locale-info.php';
$locale_index = array_search( $price->get_currency(), array_column( $locale_info_all, 'currency_code' ), true );
$locale = array_values( $locale_info_all )[ $locale_index ];
$num_decimals = $locale['num_decimals'];
// Get $currency_info based on user locale or default locale.
$currency_locales = $locale['locales'];
$user_locale = get_user_locale();
$currency_info = $currency_locales[ $user_locale ] ?? $currency_locales['default'];
// Get $price_format to be passed to wc_price.
$currency_pos = $currency_info['currency_pos'];
$currency_formats = array(
'left' => '%1$s%2$s',
'right' => '%2$s%1$s',
'left_space' => '%1$s&nbsp;%2$s',
'right_space' => '%2$s&nbsp;%1$s',
);
$price_format = $currency_formats[ $currency_pos ] ?? $currency_formats['left'];
$price_value = wc_format_decimal( $price->get_value() );
$price_formatted = wc_price(
$price_value,
array(
'currency' => $price->get_currency(),
'decimal_separator' => $currency_info['decimal_sep'],
'thousand_separator' => $currency_info['thousand_sep'],
'decimals' => $num_decimals,
'price_format' => $price_format,
)
);
return html_entity_decode( wp_strip_all_tags( $price_formatted ) );
}
/**
* Prepares the item for the REST response.
*
@@ -150,15 +201,17 @@ class MarketingCampaigns extends WC_REST_Controller {
if ( $item->get_cost() instanceof Price ) {
$data['cost'] = array(
'value' => wc_format_decimal( $item->get_cost()->get_value() ),
'currency' => $item->get_cost()->get_currency(),
'value' => wc_format_decimal( $item->get_cost()->get_value() ),
'currency' => $item->get_cost()->get_currency(),
'formatted' => $this->get_formatted_price( $item->get_cost() ),
);
}
if ( $item->get_sales() instanceof Price ) {
$data['sales'] = array(
'value' => wc_format_decimal( $item->get_sales()->get_value() ),
'currency' => $item->get_sales()->get_currency(),
'value' => wc_format_decimal( $item->get_sales()->get_value() ),
'currency' => $item->get_sales()->get_currency(),
'formatted' => $this->get_formatted_price( $item->get_sales() ),
);
}
@@ -257,6 +310,4 @@ class MarketingCampaigns extends WC_REST_Controller {
return $params;
}
}

View File

@@ -82,7 +82,7 @@ class OnboardingFreeExtensions extends WC_REST_Data_Controller {
/**
* Allows removing Jetpack suggestions from WooCommerce Admin when false.
*
* In this instance it is removed from the list of extensions suggested in the Onboarding Profiler. This list is first retrieved from the Woo.com API, then if a plugin with the 'jetpack' slug is found, it is removed.
* In this instance it is removed from the list of extensions suggested in the Onboarding Profiler. This list is first retrieved from the WooCommerce.com API, then if a plugin with the 'jetpack' slug is found, it is removed.
*
* @since 7.8
*/

View File

@@ -409,14 +409,14 @@ class OnboardingProfile extends \WC_REST_Data_Controller {
),
'wccom_connected' => array(
'type' => 'boolean',
'description' => __( 'Whether or not the store was connected to Woo.com during the extension flow.', 'woocommerce' ),
'description' => __( 'Whether or not the store was connected to WooCommerce.com during the extension flow.', 'woocommerce' ),
'context' => array( 'view' ),
'readonly' => true,
'validate_callback' => 'rest_validate_request_arg',
),
'is_agree_marketing' => array(
'type' => 'boolean',
'description' => __( 'Whether or not this store agreed to receiving marketing contents from Woo.com.', 'woocommerce' ),
'description' => __( 'Whether or not this store agreed to receiving marketing contents from WooCommerce.com.', 'woocommerce' ),
'context' => array( 'view' ),
'readonly' => true,
'validate_callback' => 'rest_validate_request_arg',

View File

@@ -740,14 +740,6 @@ class OnboardingTasks extends \WC_REST_Data_Controller {
$lists = is_array( $task_list_ids ) && count( $task_list_ids ) > 0 ? TaskLists::get_lists_by_ids( $task_list_ids ) : TaskLists::get_lists();
// We have no use for hidden lists, it's expensive to compute individual tasks completion.
$lists = array_filter(
$lists,
function( $list ) {
return ! $list->is_hidden();
}
);
$json = array_map(
function( $list ) {
return $list->sort_tasks()->get_json();

View File

@@ -249,9 +249,135 @@ class OnboardingThemes extends \WC_REST_Data_Controller {
);
}
$in_app_purchase_params = \WC_Admin_Addons::get_in_app_purchase_url_params();
$in_app_purchase_params['wccom-back'] = rawurlencode( '/wp-admin/admin.php?page=wc-admin&path=/customize-store' );
$core_themes = array(
array(
'name' => 'Twenty Twenty-Four',
'price' => 'Free',
'color_palettes' => array(
array(
'title' => 'Black and white',
'primary' => '#FEFBF3',
'secondary' => '#7F7E7A',
),
array(
'title' => 'Brown Sugar',
'primary' => '#EFEBE0',
'secondary' => '#AC6239',
),
array(
'title' => 'Midnight',
'primary' => '#161514',
'secondary' => '#AFADA7',
),
array(
'title' => 'Olive',
'primary' => '#FEFBF3',
'secondary' => '#7F7E7A',
),
),
'total_palettes' => 0,
'slug' => 'twentytwentyfour',
'thumbnail_url' => 'https://i0.wp.com/themes.svn.wordpress.org/twentytwentyfour/1.0/screenshot.png',
'link_url' => 'https://wordpress.org/themes/twentytwentyfour/',
),
array(
'name' => 'Highline',
'price' => '$79/year',
'color_palettes' => array(
array(
'title' => 'Primary',
'primary' => '#211f1d',
'secondary' => '#211f1d',
),
array(
'title' => 'Additional color',
'primary' => '#aaaaaa',
'secondary' => '#aaaaaa',
),
array(
'title' => 'Accent Background',
'primary' => '#b04b3c',
'secondary' => '#b04b3c',
),
array(
'title' => 'Secondary Background',
'primary' => '#dabfa1',
'secondary' => '#dabfa1',
),
),
'total_palettes' => 9,
'slug' => 'highline',
'thumbnail_url' => 'https://woocommerce.com/wp-content/uploads/2023/12/Featured-image-538x403-1.png',
'link_url' => add_query_arg( $in_app_purchase_params, 'https://woocommerce.com/products/highline/' ),
),
array(
'name' => 'Luminate',
'price' => '$79/year',
'color_palettes' => array(
array(
'title' => 'Primary',
'primary' => '#242a2e',
'secondary' => '#242a2e',
),
array(
'title' => 'Lite',
'primary' => '#f6f5f2',
'secondary' => '#f6f5f2',
),
array(
'title' => 'Grey',
'primary' => '#a5a5a5',
'secondary' => '#a5a5a5',
),
array(
'title' => 'Lite Grey',
'primary' => '#e4e4e1',
'secondary' => '#e4e4e1',
),
),
'total_palettes' => 5,
'slug' => 'luminate',
'thumbnail_url' => 'https://woocommerce.com/wp-content/uploads/2022/07/Featured-image-538x403-2.png',
'link_url' => add_query_arg( $in_app_purchase_params, 'https://woocommerce.com/products/luminate/' ),
),
array(
'name' => 'Nokul',
'price' => '$79/year',
'color_palettes' => array(
array(
'title' => 'Foreground and background',
'primary' => '#000000',
'secondary' => '#f1eee2',
),
array(
'title' => 'Foreground and secondary',
'primary' => '#000000',
'secondary' => '#999999',
),
array(
'title' => 'Foreground and accent',
'primary' => '#000000',
'secondary' => '#d82f16',
),
array(
'title' => 'Primary and background',
'primary' => '#d9d0bf',
'secondary' => '#f1eee2',
),
),
'total_palettes' => 6,
'slug' => 'nokul',
'thumbnail_url' => 'https://woocommerce.com/wp-content/uploads/2022/11/Product-logo.jpg',
'link_url' => add_query_arg( $in_app_purchase_params, 'https://woocommerce.com/products/nokul/' ),
),
);
// To be implemented: 1. Fetch themes from the marketplace API. 2. Convert prices to the requested currency.
// These are Dotcom themes.
$themes = array(
$default_themes = array(
array(
'name' => 'Tsubaki',
'price' => 'Free',
@@ -269,7 +395,6 @@ class OnboardingThemes extends \WC_REST_Data_Controller {
'slug' => 'tazza',
'thumbnail_url' => 'https://i0.wp.com/s2.wp.com/wp-content/themes/premium/tazza/screenshot.png',
'link_url' => 'https://wordpress.com/theme/tazza/',
'total_palettes' => 0,
),
array(
'name' => 'Amulet',
@@ -333,6 +458,9 @@ class OnboardingThemes extends \WC_REST_Data_Controller {
),
);
$ai_connection_enabled = get_option( 'woocommerce_blocks_allow_ai_connection' );
$themes = $ai_connection_enabled ? $default_themes : $core_themes;
// To be implemented: Filter themes based on industry.
if ( $industry ) {
$filtered_themes = array_filter(

View File

@@ -205,6 +205,7 @@ class Options extends \WC_REST_Data_Controller {
'woocommerce_product_tour_modal_hidden',
'woocommerce_block_product_tour_shown',
'woocommerce_revenue_report_date_tour_shown',
'woocommerce_show_prepublish_checks_enabled',
'woocommerce_date_type',
'date_format',
'time_format',
@@ -219,6 +220,12 @@ class Options extends \WC_REST_Data_Controller {
'woocommerce_admin_customize_store_completed',
'woocommerce_admin_customize_store_completed_theme_id',
'woocommerce_admin_customize_store_survey_completed',
'woocommerce_admin_launch_your_store_survey_completed',
'woocommerce_coming_soon',
'woocommerce_store_pages_only',
'woocommerce_private_link',
'woocommerce_share_key',
'woocommerce_show_lys_tour',
// WC Test helper options.
'wc-admin-test-helper-rest-api-filters',
'wc_admin_helper_feature_values',

View File

@@ -420,12 +420,12 @@ class Plugins extends \WC_REST_Data_Controller {
/**
* Kicks off the WCCOM Connect process.
*
* @return WP_Error|array Connection URL for Woo.com
* @return WP_Error|array Connection URL for WooCommerce.com
*/
public function request_wccom_connect() {
include_once WC_ABSPATH . 'includes/admin/helper/class-wc-helper-api.php';
if ( ! class_exists( 'WC_Helper_API' ) ) {
return new \WP_Error( 'woocommerce_rest_helper_not_active', __( 'There was an error loading the Woo.com Helper API.', 'woocommerce' ), 404 );
return new \WP_Error( 'woocommerce_rest_helper_not_active', __( 'There was an error loading the WooCommerce.com Helper API.', 'woocommerce' ), 404 );
}
$redirect_uri = wc_admin_url( '&task=connect&wccom-connected=1' );
@@ -442,12 +442,12 @@ class Plugins extends \WC_REST_Data_Controller {
$code = wp_remote_retrieve_response_code( $request );
if ( 200 !== $code ) {
return new \WP_Error( 'woocommerce_rest_helper_connect', __( 'There was an error connecting to Woo.com. Please try again.', 'woocommerce' ), 500 );
return new \WP_Error( 'woocommerce_rest_helper_connect', __( 'There was an error connecting to WooCommerce.com. Please try again.', 'woocommerce' ), 500 );
}
$secret = json_decode( wp_remote_retrieve_body( $request ) );
if ( empty( $secret ) ) {
return new \WP_Error( 'woocommerce_rest_helper_connect', __( 'There was an error connecting to Woo.com. Please try again.', 'woocommerce' ), 500 );
return new \WP_Error( 'woocommerce_rest_helper_connect', __( 'There was an error connecting to WooCommerce.com. Please try again.', 'woocommerce' ), 500 );
}
do_action( 'woocommerce_helper_connect_start' );
@@ -477,7 +477,7 @@ class Plugins extends \WC_REST_Data_Controller {
}
/**
* Finishes connecting to Woo.com.
* Finishes connecting to WooCommerce.com.
*
* @param object $rest_request Request details.
* @return WP_Error|array Contains success status.
@@ -488,7 +488,7 @@ class Plugins extends \WC_REST_Data_Controller {
include_once WC_ABSPATH . 'includes/admin/helper/class-wc-helper-updater.php';
include_once WC_ABSPATH . 'includes/admin/helper/class-wc-helper-options.php';
if ( ! class_exists( 'WC_Helper_API' ) ) {
return new \WP_Error( 'woocommerce_rest_helper_not_active', __( 'There was an error loading the Woo.com Helper API.', 'woocommerce' ), 404 );
return new \WP_Error( 'woocommerce_rest_helper_not_active', __( 'There was an error loading the WooCommerce.com Helper API.', 'woocommerce' ), 404 );
}
// Obtain an access token.
@@ -504,12 +504,12 @@ class Plugins extends \WC_REST_Data_Controller {
$code = wp_remote_retrieve_response_code( $request );
if ( 200 !== $code ) {
return new \WP_Error( 'woocommerce_rest_helper_connect', __( 'There was an error connecting to Woo.com. Please try again.', 'woocommerce' ), 500 );
return new \WP_Error( 'woocommerce_rest_helper_connect', __( 'There was an error connecting to WooCommerce.com. Please try again.', 'woocommerce' ), 500 );
}
$access_token = json_decode( wp_remote_retrieve_body( $request ), true );
if ( ! $access_token ) {
return new \WP_Error( 'woocommerce_rest_helper_connect', __( 'There was an error connecting to Woo.com. Please try again.', 'woocommerce' ), 500 );
return new \WP_Error( 'woocommerce_rest_helper_connect', __( 'There was an error connecting to WooCommerce.com. Please try again.', 'woocommerce' ), 500 );
}
\WC_Helper_Options::update(
@@ -525,7 +525,7 @@ class Plugins extends \WC_REST_Data_Controller {
if ( ! \WC_Helper::_flush_authentication_cache() ) {
\WC_Helper_Options::update( 'auth', array() );
return new \WP_Error( 'woocommerce_rest_helper_connect', __( 'There was an error connecting to Woo.com. Please try again.', 'woocommerce' ), 500 );
return new \WP_Error( 'woocommerce_rest_helper_connect', __( 'There was an error connecting to WooCommerce.com. Please try again.', 'woocommerce' ), 500 );
}
delete_transient( '_woocommerce_helper_subscriptions' );
@@ -592,25 +592,22 @@ class Plugins extends \WC_REST_Data_Controller {
}
/**
* Returns a URL that can be used to by WCPay to verify business details with Stripe.
* Returns a URL that can be used to by WCPay to verify business details.
*
* @return WP_Error|array Connect URL.
*/
public function connect_wcpay() {
if ( ! class_exists( 'WC_Payments_Account' ) ) {
if ( ! class_exists( 'WC_Payments' ) ) {
return new \WP_Error( 'woocommerce_rest_helper_connect', __( 'There was an error communicating with the WooPayments plugin.', 'woocommerce' ), 500 );
}
$args = WooCommercePayments::is_account_partially_onboarded() ? [
'wcpay-login' => '1',
'_wpnonce' => wp_create_nonce( 'wcpay-login' ),
] : [
'wcpay-connect' => 'WCADMIN_PAYMENT_TASK',
'_wpnonce' => wp_create_nonce( 'wcpay-connect' ),
];
// Redirect to the WooPayments overview page if the merchant started onboarding (aka WooPayments is already connected).
// Redirect to the WooPayments connect page if they haven't started onboarding.
$path = WooCommercePayments::is_connected() ? '/payments/overview' : '/payments/connect';
// Point to the WooPayments Connect page rather than straight to the onboarding flow.
return( array(
'connectUrl' => add_query_arg( $args, admin_url() ),
'connectUrl' => add_query_arg( 'from', 'WCADMIN_PAYMENT_TASK', admin_url( 'admin.php?page=wc-admin&path=' . $path ) ),
) );
}

View File

@@ -1345,6 +1345,7 @@ class DataStore extends SqlQuery {
continue;
}
$term_id = '';
// If the tuple is numeric, assume these are IDs.
if ( is_numeric( $attribute_term[0] ) && is_numeric( $attribute_term[1] ) ) {
$attribute_id = intval( $attribute_term[0] );
@@ -1374,6 +1375,10 @@ class DataStore extends SqlQuery {
// Assume these are a custom attribute slug/value pair.
$meta_key = esc_sql( $attribute_term[0] );
$meta_value = esc_sql( $attribute_term[1] );
$attr_term = get_term_by( 'slug', $meta_value, $meta_key );
if ( false !== $attr_term ) {
$term_id = $attr_term->term_id;
}
}
$join_alias = 'orderitemmeta1';
@@ -1391,8 +1396,23 @@ class DataStore extends SqlQuery {
$sql_clauses['join'][] = "JOIN {$wpdb->prefix}woocommerce_order_itemmeta as {$join_alias} ON {$join_alias}.order_item_id = {$table_to_join_on}.order_item_id";
}
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
$sql_clauses['where'][] = $wpdb->prepare( "( {$join_alias}.meta_key = %s AND {$join_alias}.meta_value {$comparator} %s )", $meta_key, $meta_value );
$in_comparator = '=' === $comparator ? 'in' : 'not in';
// Add subquery for products ordered using attributes not used in variations.
$term_attribute_subquery = "select product_id from {$wpdb->prefix}wc_product_attributes_lookup where is_variation_attribute=0 and term_id = %s";
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
// phpcs:disable WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber
$sql_clauses['where'][] = $wpdb->prepare(
"
( ( {$join_alias}.meta_key = %s AND {$join_alias}.meta_value {$comparator} %s ) or (
{$wpdb->prefix}wc_order_product_lookup.variation_id = 0 and {$wpdb->prefix}wc_order_product_lookup.product_id {$in_comparator} ({$term_attribute_subquery})
) )",
$meta_key,
$meta_value,
$term_id,
);
// phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
// phpcs:enable WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber
}
}

View File

@@ -211,24 +211,30 @@ class Controller extends ReportsController implements ExportableInterface {
'readonly' => true,
),
'extended_info' => array(
'products' => array(
'products' => array(
'type' => 'array',
'readonly' => true,
'context' => array( 'view', 'edit' ),
'description' => __( 'List of order product IDs, names, quantities.', 'woocommerce' ),
),
'coupons' => array(
'coupons' => array(
'type' => 'array',
'readonly' => true,
'context' => array( 'view', 'edit' ),
'description' => __( 'List of order coupons.', 'woocommerce' ),
),
'customer' => array(
'customer' => array(
'type' => 'object',
'readonly' => true,
'context' => array( 'view', 'edit' ),
'description' => __( 'Order customer information.', 'woocommerce' ),
),
'attribution' => array(
'type' => 'object',
'readonly' => true,
'context' => array( 'view', 'edit' ),
'description' => __( 'Order attribution information.', 'woocommerce' ),
),
),
),
);
@@ -526,6 +532,7 @@ class Controller extends ReportsController implements ExportableInterface {
'num_items_sold' => __( 'Items sold', 'woocommerce' ),
'coupons' => __( 'Coupon(s)', 'woocommerce' ),
'net_total' => __( 'N. Revenue', 'woocommerce' ),
'attribution' => __( 'Attribution', 'woocommerce' ),
);
/**
@@ -558,6 +565,7 @@ class Controller extends ReportsController implements ExportableInterface {
'num_items_sold' => $item['num_items_sold'],
'coupons' => isset( $item['extended_info']['coupons'] ) ? $this->get_coupons( $item['extended_info']['coupons'] ) : null,
'net_total' => $item['net_total'],
'attribution' => $item['extended_info']['attribution']['origin'],
);
/**

View File

@@ -7,6 +7,9 @@ namespace Automattic\WooCommerce\Admin\API\Reports\Orders;
defined( 'ABSPATH' ) || exit;
use Automattic\WooCommerce\Internal\Traits\OrderAttributionMeta;
use Automattic\WooCommerce\Internal\DataStores\Orders\OrdersTableDataStore;
use Automattic\WooCommerce\Utilities\OrderUtil;
use Automattic\WooCommerce\Admin\API\Reports\DataStore as ReportsDataStore;
use Automattic\WooCommerce\Admin\API\Reports\DataStoreInterface;
use Automattic\WooCommerce\Admin\API\Reports\SqlQuery;
@@ -18,6 +21,7 @@ use Automattic\WooCommerce\Admin\API\Reports\TimeInterval;
* API\Reports\Orders\DataStore.
*/
class DataStore extends ReportsDataStore implements DataStoreInterface {
use OrderAttributionMeta;
/**
* Dynamically sets the date column name based on configuration
@@ -338,13 +342,14 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
* @param array $query_args Query parameters.
*/
protected function include_extended_info( &$orders_data, $query_args ) {
$mapped_orders = $this->map_array_by_key( $orders_data, 'order_id' );
$related_orders = $this->get_orders_with_parent_id( $mapped_orders );
$order_ids = array_merge( array_keys( $mapped_orders ), array_keys( $related_orders ) );
$products = $this->get_products_by_order_ids( $order_ids );
$coupons = $this->get_coupons_by_order_ids( array_keys( $mapped_orders ) );
$customers = $this->get_customers_by_orders( $orders_data );
$mapped_customers = $this->map_array_by_key( $customers, 'customer_id' );
$mapped_orders = $this->map_array_by_key( $orders_data, 'order_id' );
$related_orders = $this->get_orders_with_parent_id( $mapped_orders );
$order_ids = array_merge( array_keys( $mapped_orders ), array_keys( $related_orders ) );
$products = $this->get_products_by_order_ids( $order_ids );
$coupons = $this->get_coupons_by_order_ids( array_keys( $mapped_orders ) );
$order_attributions = $this->get_order_attributions_by_order_ids( array_keys( $mapped_orders ) );
$customers = $this->get_customers_by_orders( $orders_data );
$mapped_customers = $this->map_array_by_key( $customers, 'customer_id' );
$mapped_data = array();
foreach ( $products as $product ) {
@@ -384,7 +389,7 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
foreach ( $coupons as $coupon ) {
if ( ! isset( $mapped_data[ $coupon['order_id'] ] ) ) {
$mapped_data[ $product['order_id'] ]['coupons'] = array();
$mapped_data[ $coupon['order_id'] ]['coupons'] = array();
}
$mapped_data[ $coupon['order_id'] ]['coupons'][] = array(
@@ -394,15 +399,22 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
}
foreach ( $orders_data as $key => $order_data ) {
$defaults = array(
'products' => array(),
'coupons' => array(),
'customer' => array(),
$defaults = array(
'products' => array(),
'coupons' => array(),
'customer' => array(),
'attribution' => array(),
);
$orders_data[ $key ]['extended_info'] = isset( $mapped_data[ $order_data['order_id'] ] ) ? array_merge( $defaults, $mapped_data[ $order_data['order_id'] ] ) : $defaults;
$order_id = $order_data['order_id'];
$orders_data[ $key ]['extended_info'] = isset( $mapped_data[ $order_id ] ) ? array_merge( $defaults, $mapped_data[ $order_id ] ) : $defaults;
if ( $order_data['customer_id'] && isset( $mapped_customers[ $order_data['customer_id'] ] ) ) {
$orders_data[ $key ]['extended_info']['customer'] = $mapped_customers[ $order_data['customer_id'] ];
}
$source_type = $order_attributions[ $order_id ]['_wc_order_attribution_source_type'] ?? '';
$utm_source = $order_attributions[ $order_id ]['_wc_order_attribution_utm_source'] ?? '';
$orders_data[ $key ]['extended_info']['attribution']['origin'] = $this->get_origin_label( $source_type, $utm_source );
}
}
@@ -534,6 +546,52 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
return $coupons;
}
/**
* Get order attributions data from order IDs.
*
* @param array $order_ids Array of order IDs.
* @return array
*/
protected function get_order_attributions_by_order_ids( $order_ids ) {
global $wpdb;
$order_meta_table = OrdersTableDataStore::get_meta_table_name();
$included_order_ids = implode( ',', array_map( 'absint', $order_ids ) );
if ( OrderUtil::custom_orders_table_usage_is_enabled() ) {
/* phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared */
$order_attributions_meta = $wpdb->get_results(
"SELECT order_id, meta_key, meta_value
FROM $order_meta_table
WHERE order_id IN ({$included_order_ids})
AND meta_key IN ( '_wc_order_attribution_source_type', '_wc_order_attribution_utm_source' )
",
ARRAY_A
);
/* phpcs:enable */
} else {
/* phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared */
$order_attributions_meta = $wpdb->get_results(
"SELECT post_id as order_id, meta_key, meta_value
FROM $wpdb->postmeta
WHERE post_id IN ({$included_order_ids})
AND meta_key IN ( '_wc_order_attribution_source_type', '_wc_order_attribution_utm_source' )
",
ARRAY_A
);
/* phpcs:enable */
}
$order_attributions = array();
foreach ( $order_attributions_meta as $meta ) {
if ( ! isset( $order_attributions[ $meta['order_id'] ] ) ) {
$order_attributions[ $meta['order_id'] ] = array();
}
$order_attributions[ $meta['order_id'] ][ $meta['meta_key'] ] = $meta['meta_value'];
}
return $order_attributions;
}
/**
* Get all statuses that have been synced.
*

View File

@@ -165,7 +165,7 @@ class Segmenter extends ReportsSegmenter {
$segments = $this->get_product_related_segments( $type, $segmenting_selections, $segmenting_from, $segmenting_where, $segmenting_groupby, $segmenting_dimension_name, $table_name, $query_params, $unique_orders_table );
} elseif ( 'variation' === $this->query_args['segmentby'] ) {
if ( ! isset( $this->query_args['product_includes'] ) || ! is_array( count( $this->query_args['product_includes'] ) ) || count( $this->query_args['product_includes'] ) !== 1 ) {
if ( ! isset( $this->query_args['product_includes'] ) || ! is_array( $this->query_args['product_includes'] ) || count( $this->query_args['product_includes'] ) !== 1 ) {
throw new ParameterException( 'wc_admin_reports_invalid_segmenting_variation', __( 'product_includes parameter need to specify exactly one product when segmenting by variation.', 'woocommerce' ) );
}