Add HPOS compat queries for tracker. (#38293)

This commit is contained in:
Vedanshu Jain 2023-06-09 10:19:01 +05:30 committed by GitHub
commit b04376b501
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 261 additions and 88 deletions

View File

@ -0,0 +1,4 @@
Significance: patch
Type: dev
Add HPOS compat queries for tracker.

View File

@ -11,15 +11,19 @@
*/ */
use Automattic\Jetpack\Constants; use Automattic\Jetpack\Constants;
use Automattic\WooCommerce\Internal\DataStores\Orders\OrdersTableDataStore;
use Automattic\WooCommerce\Internal\Utilities\BlocksUtil; use Automattic\WooCommerce\Internal\Utilities\BlocksUtil;
use Automattic\WooCommerce\Utilities\OrderUtil;
defined( 'ABSPATH' ) || exit; defined( 'ABSPATH' ) || exit;
// phpcs:disable Squiz.Classes.ClassFileName.NoMatch, Squiz.Classes.ValidClassName.NotCamelCaps -- Backwards compatibility.
/** /**
* WooCommerce Tracker Class * WooCommerce Tracker Class
*/ */
class WC_Tracker { class WC_Tracker {
// phpcs:enable
/** /**
* URL to the WooThemes Tracker API endpoint. * URL to the WooThemes Tracker API endpoint.
* *
@ -30,7 +34,7 @@ class WC_Tracker {
/** /**
* Hook into cron event. * Hook into cron event.
*/ */
public static function init() { public static function init() { // phpcs:ignore WooCommerce.Functions.InternalInjectionMethod.MissingFinal, WooCommerce.Functions.InternalInjectionMethod.MissingInternalTag -- Not an injection.
add_action( 'woocommerce_tracker_send_event', array( __CLASS__, 'send_tracking_data' ) ); add_action( 'woocommerce_tracker_send_event', array( __CLASS__, 'send_tracking_data' ) );
} }
@ -45,10 +49,15 @@ class WC_Tracker {
return; return;
} }
/**
* Filter whether to send tracking data or not.
*
* @since 2.3.0
*/
if ( ! apply_filters( 'woocommerce_tracker_send_override', $override ) ) { if ( ! apply_filters( 'woocommerce_tracker_send_override', $override ) ) {
// Send a maximum of once per week by default. // Send a maximum of once per week by default.
$last_send = self::get_last_send_time(); $last_send = self::get_last_send_time();
if ( $last_send && $last_send > apply_filters( 'woocommerce_tracker_last_send_interval', strtotime( '-1 week' ) ) ) { if ( $last_send && $last_send > apply_filters( 'woocommerce_tracker_last_send_interval', strtotime( '-1 week' ) ) ) { // phpcs:ignore
return; return;
} }
} else { } else {
@ -84,6 +93,11 @@ class WC_Tracker {
* @return int|bool * @return int|bool
*/ */
private static function get_last_send_time() { private static function get_last_send_time() {
/**
* Filter the last time tracking data was sent.
*
* @since 2.3.0
*/
return apply_filters( 'woocommerce_tracker_last_send_time', get_option( 'woocommerce_tracker_last_send', false ) ); return apply_filters( 'woocommerce_tracker_last_send_time', get_option( 'woocommerce_tracker_last_send', false ) );
} }
@ -118,7 +132,12 @@ class WC_Tracker {
$data = array(); $data = array();
// General site info. // General site info.
$data['url'] = home_url(); $data['url'] = home_url();
/**
* Filter the admin email that's sent with data.
*
* @since 2.3.0
*/
$data['email'] = apply_filters( 'woocommerce_tracker_admin_email', get_option( 'admin_email' ) ); $data['email'] = apply_filters( 'woocommerce_tracker_admin_email', get_option( 'admin_email' ) );
$data['theme'] = self::get_theme_info(); $data['theme'] = self::get_theme_info();
@ -172,12 +191,21 @@ class WC_Tracker {
$data['mini_cart_block'] = self::get_mini_cart_info(); $data['mini_cart_block'] = self::get_mini_cart_info();
} }
// WooCommerce Admin info. /**
* Filter whether to disable admin tracking.
*
* @since 5.2.0
*/
$data['wc_admin_disabled'] = apply_filters( 'woocommerce_admin_disabled', false ) ? 'yes' : 'no'; $data['wc_admin_disabled'] = apply_filters( 'woocommerce_admin_disabled', false ) ? 'yes' : 'no';
// Mobile info. // Mobile info.
$data['wc_mobile_usage'] = self::get_woocommerce_mobile_usage(); $data['wc_mobile_usage'] = self::get_woocommerce_mobile_usage();
/**
* Filter the data that's sent with the tracker.
*
* @since 2.3.0
*/
return apply_filters( 'woocommerce_tracker_data', $data ); return apply_filters( 'woocommerce_tracker_data', $data );
} }
@ -212,6 +240,7 @@ class WC_Tracker {
$memory = wc_let_to_num( WP_MEMORY_LIMIT ); $memory = wc_let_to_num( WP_MEMORY_LIMIT );
if ( function_exists( 'memory_get_usage' ) ) { if ( function_exists( 'memory_get_usage' ) ) {
// phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged -- False positive.
$system_memory = wc_let_to_num( @ini_get( 'memory_limit' ) ); $system_memory = wc_let_to_num( @ini_get( 'memory_limit' ) );
$memory = max( $memory, $system_memory ); $memory = max( $memory, $system_memory );
} }
@ -287,20 +316,20 @@ class WC_Tracker {
foreach ( $plugins as $k => $v ) { foreach ( $plugins as $k => $v ) {
// Take care of formatting the data how we want it. // Take care of formatting the data how we want it.
$formatted = array(); $formatted = array();
$formatted['name'] = strip_tags( $v['Name'] ); $formatted['name'] = wp_strip_all_tags( $v['Name'] );
if ( isset( $v['Version'] ) ) { if ( isset( $v['Version'] ) ) {
$formatted['version'] = strip_tags( $v['Version'] ); $formatted['version'] = wp_strip_all_tags( $v['Version'] );
} }
if ( isset( $v['Author'] ) ) { if ( isset( $v['Author'] ) ) {
$formatted['author'] = strip_tags( $v['Author'] ); $formatted['author'] = wp_strip_all_tags( $v['Author'] );
} }
if ( isset( $v['Network'] ) ) { if ( isset( $v['Network'] ) ) {
$formatted['network'] = strip_tags( $v['Network'] ); $formatted['network'] = wp_strip_all_tags( $v['Network'] );
} }
if ( isset( $v['PluginURI'] ) ) { if ( isset( $v['PluginURI'] ) ) {
$formatted['plugin_uri'] = strip_tags( $v['PluginURI'] ); $formatted['plugin_uri'] = wp_strip_all_tags( $v['PluginURI'] );
} }
if ( in_array( $k, $active_plugins_keys ) ) { if ( in_array( $k, $active_plugins_keys, true ) ) {
// Remove active plugins from list so we can show active and inactive separately. // Remove active plugins from list so we can show active and inactive separately.
unset( $plugins[ $k ] ); unset( $plugins[ $k ] );
$active_plugins[ $k ] = $formatted; $active_plugins[ $k ] = $formatted;
@ -381,10 +410,9 @@ class WC_Tracker {
* @return array * @return array
*/ */
private static function get_order_counts() { private static function get_order_counts() {
$order_count = array(); $order_count = array();
$order_count_data = wp_count_posts( 'shop_order' );
foreach ( wc_get_order_statuses() as $status_slug => $status_name ) { foreach ( wc_get_order_statuses() as $status_slug => $status_name ) {
$order_count[ $status_slug ] = $order_count_data->{ $status_slug }; $order_count[ $status_slug ] = wc_orders_count( $status_slug );
} }
return $order_count; return $order_count;
} }
@ -413,33 +441,59 @@ class WC_Tracker {
private static function get_order_totals() { private static function get_order_totals() {
global $wpdb; global $wpdb;
$gross_total = $wpdb->get_var( $orders_table = OrdersTableDataStore::get_orders_table_name();
if ( OrderUtil::custom_orders_table_usage_is_enabled() ) {
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
$gross_total = $wpdb->get_var(
"
SELECT SUM(total_amount) AS 'gross_total'
FROM $orders_table
WHERE status in ('wc-completed', 'wc-refunded');
" "
SELECT );
SUM( order_meta.meta_value ) AS 'gross_total' // phpcs:enable
FROM {$wpdb->prefix}posts AS orders } else {
LEFT JOIN {$wpdb->prefix}postmeta AS order_meta ON order_meta.post_id = orders.ID $gross_total = $wpdb->get_var(
WHERE order_meta.meta_key = '_order_total' "
AND orders.post_status in ( 'wc-completed', 'wc-refunded' ) SELECT
GROUP BY order_meta.meta_key SUM( order_meta.meta_value ) AS 'gross_total'
" FROM {$wpdb->prefix}posts AS orders
); LEFT JOIN {$wpdb->prefix}postmeta AS order_meta ON order_meta.post_id = orders.ID
WHERE order_meta.meta_key = '_order_total'
AND orders.post_status in ( 'wc-completed', 'wc-refunded' )
GROUP BY order_meta.meta_key
"
);
}
if ( is_null( $gross_total ) ) { if ( is_null( $gross_total ) ) {
$gross_total = 0; $gross_total = 0;
} }
$processing_gross_total = $wpdb->get_var( if ( OrderUtil::custom_orders_table_usage_is_enabled() ) {
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
$processing_gross_total = $wpdb->get_var(
"
SELECT SUM(total_amount) AS 'gross_total'
FROM $orders_table
WHERE status = 'wc-processing';
" "
SELECT );
SUM( order_meta.meta_value ) AS 'gross_total' // phpcs:enable
FROM {$wpdb->prefix}posts AS orders } else {
LEFT JOIN {$wpdb->prefix}postmeta AS order_meta ON order_meta.post_id = orders.ID $processing_gross_total = $wpdb->get_var(
WHERE order_meta.meta_key = '_order_total' "
AND orders.post_status = 'wc-processing' SELECT
GROUP BY order_meta.meta_key SUM( order_meta.meta_value ) AS 'gross_total'
" FROM {$wpdb->prefix}posts AS orders
); LEFT JOIN {$wpdb->prefix}postmeta AS order_meta ON order_meta.post_id = orders.ID
WHERE order_meta.meta_key = '_order_total'
AND orders.post_status = 'wc-processing'
GROUP BY order_meta.meta_key
"
);
}
if ( is_null( $processing_gross_total ) ) { if ( is_null( $processing_gross_total ) ) {
$processing_gross_total = 0; $processing_gross_total = 0;
@ -459,16 +513,31 @@ class WC_Tracker {
private static function get_order_dates() { private static function get_order_dates() {
global $wpdb; global $wpdb;
$min_max = $wpdb->get_row( $orders_table = OrdersTableDataStore::get_orders_table_name();
" if ( OrderUtil::custom_orders_table_usage_is_enabled() ) {
SELECT // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
MIN( post_date_gmt ) as 'first', MAX( post_date_gmt ) as 'last' $min_max = $wpdb->get_row(
FROM {$wpdb->prefix}posts "
WHERE post_type = 'shop_order' SELECT
AND post_status = 'wc-completed' MIN( date_created_gmt ) as 'first', MAX( date_created_gmt ) as 'last'
", FROM $orders_table
ARRAY_A WHERE status = 'wc-completed';
); ",
ARRAY_A
);
// phpcs:enable
} else {
$min_max = $wpdb->get_row(
"
SELECT
MIN( post_date_gmt ) as 'first', MAX( post_date_gmt ) as 'last'
FROM {$wpdb->prefix}posts
WHERE post_type = 'shop_order'
AND post_status = 'wc-completed'
",
ARRAY_A
);
}
if ( is_null( $min_max ) ) { if ( is_null( $min_max ) ) {
$min_max = array( $min_max = array(
@ -477,16 +546,30 @@ class WC_Tracker {
); );
} }
$processing_min_max = $wpdb->get_row( if ( OrderUtil::custom_orders_table_usage_is_enabled() ) {
" // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
SELECT $processing_min_max = $wpdb->get_row(
MIN( post_date_gmt ) as 'processing_first', MAX( post_date_gmt ) as 'processing_last' "
FROM {$wpdb->prefix}posts SELECT
WHERE post_type = 'shop_order' MIN( date_created_gmt ) as 'processing_first', MAX( date_created_gmt ) as 'processing_last'
AND post_status = 'wc-processing' FROM $orders_table
", WHERE status = 'wc-processing';
ARRAY_A ",
); ARRAY_A
);
// phpcs:enable
} else {
$processing_min_max = $wpdb->get_row(
"
SELECT
MIN( post_date_gmt ) as 'processing_first', MAX( post_date_gmt ) as 'processing_last'
FROM {$wpdb->prefix}posts
WHERE post_type = 'shop_order'
AND post_status = 'wc-processing'
",
ARRAY_A
);
}
if ( is_null( $processing_min_max ) ) { if ( is_null( $processing_min_max ) ) {
$processing_min_max = array( $processing_min_max = array(
@ -569,28 +652,42 @@ class WC_Tracker {
private static function get_orders_by_gateway() { private static function get_orders_by_gateway() {
global $wpdb; global $wpdb;
$orders_and_gateway_details = $wpdb->get_results( if ( OrderUtil::custom_orders_table_usage_is_enabled() ) {
" $orders_table = OrdersTableDataStore::get_orders_table_name();
SELECT // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
gateway, currency, SUM(total) AS totals, COUNT(order_id) AS counts $orders_and_gateway_details = $wpdb->get_results(
FROM ( "
SELECT payment_method AS gateway, currency AS currency, SUM( total_amount ) AS totals, count( id ) AS counts
FROM $orders_table
WHERE status IN ( 'wc-completed', 'wc-processing', 'wc-refunded' )
GROUP BY gateway, currency;
"
);
// phpcs:enable
} else {
$orders_and_gateway_details = $wpdb->get_results(
"
SELECT SELECT
orders.id AS order_id, gateway, currency, SUM(total) AS totals, COUNT(order_id) AS counts
MAX(CASE WHEN meta_key = '_payment_method' THEN meta_value END) gateway, FROM (
MAX(CASE WHEN meta_key = '_order_total' THEN meta_value END) total, SELECT
MAX(CASE WHEN meta_key = '_order_currency' THEN meta_value END) currency orders.id AS order_id,
FROM MAX(CASE WHEN meta_key = '_payment_method' THEN meta_value END) gateway,
{$wpdb->prefix}posts orders MAX(CASE WHEN meta_key = '_order_total' THEN meta_value END) total,
LEFT JOIN MAX(CASE WHEN meta_key = '_order_currency' THEN meta_value END) currency
{$wpdb->prefix}postmeta order_meta ON order_meta.post_id = orders.id FROM
WHERE orders.post_type = 'shop_order' {$wpdb->prefix}posts orders
AND orders.post_status in ( 'wc-completed', 'wc-processing', 'wc-refunded' ) LEFT JOIN
AND meta_key in( '_payment_method','_order_total','_order_currency') {$wpdb->prefix}postmeta order_meta ON order_meta.post_id = orders.id
GROUP BY orders.id WHERE orders.post_type = 'shop_order'
) order_gateways AND orders.post_status in ( 'wc-completed', 'wc-processing', 'wc-refunded' )
GROUP BY gateway, currency AND meta_key in( '_payment_method','_order_total','_order_currency')
" GROUP BY orders.id
); ) order_gateways
GROUP BY gateway, currency
"
);
}
$orders_by_gateway_currency = array(); $orders_by_gateway_currency = array();
@ -656,20 +753,33 @@ class WC_Tracker {
private static function get_orders_origins() { private static function get_orders_origins() {
global $wpdb; global $wpdb;
$orders_origin = $wpdb->get_results( if ( OrderUtil::custom_orders_table_usage_is_enabled() ) {
$op_table_name = OrdersTableDataStore::get_operational_data_table_name();
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
$orders_origin = $wpdb->get_results(
"
SELECT created_via as origin, COUNT( order_id ) as count
FROM $op_table_name
GROUP BY created_via;
"
);
// phpcs:enable
} else {
$orders_origin = $wpdb->get_results(
"
SELECT
meta_value as origin, COUNT( DISTINCT ( orders.id ) ) as count
FROM
$wpdb->posts orders
LEFT JOIN
$wpdb->postmeta order_meta ON order_meta.post_id = orders.id
WHERE
meta_key = '_created_via'
GROUP BY
meta_value;
" "
SELECT );
meta_value as origin, COUNT( DISTINCT ( orders.id ) ) as count }
FROM
$wpdb->posts orders
LEFT JOIN
$wpdb->postmeta order_meta ON order_meta.post_id = orders.id
WHERE
meta_key = '_created_via'
GROUP BY
meta_value;
"
);
// The associative array that is created as the result of array_reduce is passed to extract_group_key() // The associative array that is created as the result of array_reduce is passed to extract_group_key()
// This function has the logic that will remove specific identifiers that may sometimes be part of an origin. // This function has the logic that will remove specific identifiers that may sometimes be part of an origin.
@ -836,7 +946,12 @@ class WC_Tracker {
* @return array * @return array
*/ */
private static function get_all_template_overrides() { private static function get_all_template_overrides() {
$override_data = array(); $override_data = array();
/**
* Filter the paths to scan for template overrides.
*
* @since 2.3.0
*/
$template_paths = apply_filters( 'woocommerce_template_overrides_scan_paths', array( 'WooCommerce' => WC()->plugin_path() . '/templates/' ) ); $template_paths = apply_filters( 'woocommerce_template_overrides_scan_paths', array( 'WooCommerce' => WC()->plugin_path() . '/templates/' ) );
$scanned_files = array(); $scanned_files = array();

View File

@ -5,10 +5,13 @@
* @package WooCommerce\Tests\WC_Tracker. * @package WooCommerce\Tests\WC_Tracker.
*/ */
// phpcs:disable Squiz.Classes.ClassFileName.NoMatch, Squiz.Classes.ValidClassName.NotCamelCaps -- Backward compatibility.
/** /**
* Class WC_Tracker_Test * Class WC_Tracker_Test
*/ */
class WC_Tracker_Test extends \WC_Unit_Test_Case { class WC_Tracker_Test extends \WC_Unit_Test_Case {
// phpcs:enable
/** /**
* Test the tracking of wc_admin being disabled via filter. * Test the tracking of wc_admin being disabled via filter.
*/ */
@ -64,4 +67,55 @@ class WC_Tracker_Test extends \WC_Unit_Test_Case {
$this->assertArrayHasKey( 'wc_admin_disabled', $tracking_data ); $this->assertArrayHasKey( 'wc_admin_disabled', $tracking_data );
$this->assertEquals( 'no', $tracking_data['wc_admin_disabled'] ); $this->assertEquals( 'no', $tracking_data['wc_admin_disabled'] );
} }
/**
* @testDox Test orders tracking data.
*/
public function test_get_tracking_data_orders() {
$dummy_product = WC_Helper_Product::create_simple_product();
$status_entries = array( 'wc-processing', 'wc-completed', 'wc-refunded', 'wc-pending' );
$created_via_entries = array( 'api', 'checkout', 'admin' );
$payment_method_entries = array( 'paypal', 'stripe', 'cod' );
$order_count = count( $status_entries ) * count( $created_via_entries ) * count( $payment_method_entries );
foreach ( $status_entries as $status_entry ) {
foreach ( $created_via_entries as $created_via_entry ) {
foreach ( $payment_method_entries as $payment_method_entry ) {
$order = wc_create_order(
array(
'status' => $status_entry,
'created_via' => $created_via_entry,
'payment_method' => $payment_method_entry,
)
);
$order->add_product( $dummy_product );
$order->save();
$order->calculate_totals();
}
}
}
$order_data = WC_Tracker::get_tracking_data()['orders'];
foreach ( $status_entries as $status_entry ) {
$this->assertEquals( $order_count / count( $status_entries ), $order_data[ $status_entry ] );
}
// Gross revenue is for wc-completed and wc-refunded status, so we calculate expected revenue per status, multiply by 2, and then multiply by 10 to account for the 10 USD per status.
$this->assertEquals( ( $order_count / count( $status_entries ) ) * 2 * 10, $order_data['gross'] );
// Gross revenue is for wc-pending status, so we calculate expected revenue per status, multiply by 1, and then multiply by 10 to account for the 10 USD per status.
$this->assertEquals( ( $order_count / count( $status_entries ) ) * 1 * 10, $order_data['processing_gross'] );
// Order count per gateway is calculated for three status (completed, processing and refunded) so we multiply order count by 3 and then divide by the number of status entries.
$this->assertEquals( ( $order_count * 3 / count( $status_entries ) ), $order_data['gateway__USD_count'] );
// Order revenue per gateway is calculated for three status (completed, processing and refunded) so we multiply order count by 3, then by 10 to account for 10 USD per order and then divide by the number of status entries.
$this->assertEquals( ( $order_count * 3 * 10 / count( $status_entries ) ), $order_data['gateway__USD_total'] );
foreach ( $created_via_entries as $created_via_entry ) {
$this->assertEquals( ( $order_count / count( $created_via_entries ) ), $order_data['created_via'][ $created_via_entry ] );
}
}
} }