; } /** * Convert an amount to the store's default currency in order to store in the stats table. * * @param float $amount The amount to convert into the store's default currency. * @param float $exchange_rate The exchange rate to use for the conversion. * * @return float The converted amount. */ private function convert_amount( float $amount, float $exchange_rate ): float { return $amount * $exchange_rate; } /** * Check whether the order stats table is referenced in the clauses, to work out whether * to add the JOIN columns for Multi-Currency. * * @param array $clauses The array containing the clauses used. * * @return boolean Whether the order stats table is referenced. */ private function is_order_stats_table_used_in_clauses( array $clauses ): bool { global $wpdb; foreach ( $clauses as $clause ) { if ( strpos( $clause, "{$wpdb->prefix}wc_order_stats" ) !== false ) { return true; } } return false; } /** * There are some queries which are made in the analytics which are actually sub-queries * which are used to join on an individual item/coupon/tax code. In these cases, rather than * the context being the expected format e.g. product_stats_total, it will simply be 'product'. * In these cases, we don't want to add the join columns or select them. * * @param string $context The context the query was made in. * * @return boolean */ private function is_supported_context( string $context ): bool { $unsupported_contexts = [ 'products', 'coupons', 'taxes', 'variations', 'categories' ]; if ( in_array( $context, $unsupported_contexts, true ) ) { return false; } return true; } /** * Generate a case when statement using the provided variables. * * @param string $variable The SQL variable we want to check for NULL. * @param string $then The THEN clause. * @param string $else The ELSE clause. * * @return string */ private function generate_case_when( string $variable, string $then, string $else ): string { return "CASE WHEN {$variable} IS NOT NULL THEN {$then} ELSE {$else} END"; } /** * Perform an SQL query to determine whether Multi Currency has ever been used on this store, * by checking how many orders are in the database where an exchange currency rate has been stored. * * @return bool */ private function has_multi_currency_orders() { global $wpdb; // Using full SQL instead of variables to keep WPCS happy. if ( $this->is_cot_enabled() ) { $result = $wpdb->get_var( "SELECT EXISTS( SELECT 1 FROM {$wpdb->prefix}wc_orders_meta WHERE meta_key = '_wcpay_multi_currency_order_exchange_rate' LIMIT 1) AS count;" ); } else { $result = $wpdb->get_var( "SELECT EXISTS( SELECT 1 FROM {$wpdb->postmeta} WHERE meta_key = '_wcpay_multi_currency_order_exchange_rate' LIMIT 1) AS count;" ); } return intval( $result ) === 1; } /** * Get the SQL replacements variable. * * @return array */ private function get_sql_replacements(): array { return $this->sql_replacements; } /** * Check the passed in query params to see if currency has been passed in. * Will return null if no currency variable was passed in, otherwise will * return the currency. * * @return array */ private function get_customer_currency_args_from_request(): array { $args = [ 'currency_is' => [], 'currency_is_not' => [], 'currency' => null, ]; /* phpcs:disable WordPress.Security.NonceVerification */ if ( isset( $_GET['currency_is'] ) && is_array( $_GET['currency_is'] ) ) { $args['currency_is'] = array_map( 'sanitize_text_field', wp_unslash( $_GET['currency_is'] ) ); } if ( isset( $_GET['currency_is_not'] ) ) { $args['currency_is_not'] = array_map( 'sanitize_text_field', wp_unslash( $_GET['currency_is_not'] ) ); } if ( isset( $_GET['currency'] ) ) { $args['currency'] = sanitize_text_field( wp_unslash( $_GET['currency'] ) ); } /* phpcs:enable WordPress.Security.NonceVerification */ return $args; } /** * Set the SQL replacements variable. * * @return void */ private function set_sql_replacements() { $default_currency = 'wcpay_multicurrency_default_currency_meta.meta_value'; $exchange_rate = 'wcpay_multicurrency_exchange_rate_meta.meta_value'; $stripe_exchange_rate = 'wcpay_multicurrency_stripe_exchange_rate_meta.meta_value'; $discount_amount = $this->generate_case_when( $default_currency, $this->generate_case_when( $stripe_exchange_rate, "ROUND(discount_amount * {$stripe_exchange_rate}, 2)", "ROUND(discount_amount * (1 / {$exchange_rate} ), 2)" ), 'discount_amount' ); $product_net_revenue = $this->generate_case_when( $default_currency, $this->generate_case_when( $stripe_exchange_rate, "ROUND(product_net_revenue * {$stripe_exchange_rate}, 2)", "ROUND(product_net_revenue * (1 / {$exchange_rate} ), 2)" ), 'product_net_revenue' ); $product_gross_revenue = $this->generate_case_when( $default_currency, $this->generate_case_when( $stripe_exchange_rate, "ROUND(product_gross_revenue * {$stripe_exchange_rate}, 2)", "ROUND(product_gross_revenue * (1 / {$exchange_rate} ), 2)" ), 'product_gross_revenue' ); $this->sql_replacements = [ 'generic' => [ 'discount_amount' => $discount_amount, 'product_net_revenue' => $product_net_revenue, 'product_gross_revenue' => $product_gross_revenue, ], 'orders' => [ 'discount_amount' => $discount_amount, ], 'products' => [ 'product_net_revenue' => $product_net_revenue, 'product_gross_revenue' => $product_gross_revenue, ], 'variations' => [ 'product_net_revenue' => $product_net_revenue, 'product_gross_revenue' => $product_gross_revenue, ], 'categories' => [ 'product_net_revenue' => $product_net_revenue, 'product_gross_revenue' => $product_gross_revenue, ], 'taxes' => [ 'SUM(total_tax)' => 'SUM(' . $this->generate_case_when( $default_currency, $this->generate_case_when( $stripe_exchange_rate, "ROUND(total_tax * {$stripe_exchange_rate}, 2)", "ROUND(total_tax * (1 / {$exchange_rate} ), 2)" ), 'total_tax' ) . ')', 'SUM(order_tax)' => 'SUM(' . $this->generate_case_when( $default_currency, $this->generate_case_when( $stripe_exchange_rate, "ROUND(order_tax * {$stripe_exchange_rate}, 2)", "ROUND(order_tax * (1 / {$exchange_rate} ), 2)" ), 'order_tax' ) . ')', 'SUM(shipping_tax)' => 'SUM(' . $this->generate_case_when( $default_currency, $this->generate_case_when( $stripe_exchange_rate, "ROUND(shipping_tax * {$stripe_exchange_rate}, 2)", "ROUND(shipping_tax * (1 / {$exchange_rate} ), 2)" ), 'shipping_tax' ) . ')', ], 'coupons' => [ 'discount_amount' => $discount_amount, ], ]; } /** * Checks whether Custom Order Tables are enabled. * * @return bool */ private function is_cot_enabled(): bool { return class_exists( OrderUtil::class ) && OrderUtil::custom_orders_table_usage_is_enabled(); } }