Skip to content

Commit

Permalink
Continue optimization work for Frontend Currencies. (#3466)
Browse files Browse the repository at this point in the history
* Optimisation

Add some cache that should speed all queries up.

* Cache get_selected_currency_code()

It should fix some tests

* Added FrontendCurrencies::selected_currency_changed

Allow to disable the cache in case front end changes

* Fix tests

Fix tests

Fix tests

Fix tests

Fix tests

Fix tests

Some fix

* Continue optimization work for Frontend Currencies.

* Continue optimization work for Frontend Currencies.

* Refactor should_use_order_currency to only call is_call_in_backtrace when needed, update tests accordingly.

* Making code in should_use_order_currency read better.

* Only init compatibility classes if there's more than one currency in use.

* Add Utils method to check page query_vars. Add compatibility_classes property in Compatibility to allow for testing. Add/update tests.

* Fix type declaration in docblock.

Co-authored-by: millerf <[email protected]>
  • Loading branch information
jessepearson and millerf authored Dec 13, 2021
1 parent 685b09c commit 1518a12
Show file tree
Hide file tree
Showing 7 changed files with 200 additions and 39 deletions.
32 changes: 25 additions & 7 deletions includes/multi-currency/Compatibility.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,13 @@
*/
class Compatibility extends BaseCompatibility {

/**
* Compatibility classes.
*
* @var array
*/
protected $compatibility_classes = [];

/**
* Init the class.
*
Expand All @@ -46,13 +53,24 @@ protected function init() {
* @return void
*/
public function init_compatibility_classes() {
$compatibility_classes[] = new WooCommerceBookings( $this->multi_currency, $this->utils, $this->multi_currency->get_frontend_currencies() );
$compatibility_classes[] = new WooCommerceFedEx( $this->multi_currency, $this->utils );
$compatibility_classes[] = new WooCommercePreOrders( $this->multi_currency, $this->utils );
$compatibility_classes[] = new WooCommerceProductAddOns( $this->multi_currency, $this->utils );
$compatibility_classes[] = new WooCommerceSubscriptions( $this->multi_currency, $this->utils );
$compatibility_classes[] = new WooCommerceUPS( $this->multi_currency, $this->utils );
$compatibility_classes[] = new WooCommerceDeposits( $this->multi_currency, $this->utils );
if ( 1 < count( $this->multi_currency->get_enabled_currencies() ) ) {
$this->compatibility_classes[] = new WooCommerceBookings( $this->multi_currency, $this->utils, $this->multi_currency->get_frontend_currencies() );
$this->compatibility_classes[] = new WooCommerceFedEx( $this->multi_currency, $this->utils );
$this->compatibility_classes[] = new WooCommercePreOrders( $this->multi_currency, $this->utils );
$this->compatibility_classes[] = new WooCommerceProductAddOns( $this->multi_currency, $this->utils );
$this->compatibility_classes[] = new WooCommerceSubscriptions( $this->multi_currency, $this->utils );
$this->compatibility_classes[] = new WooCommerceUPS( $this->multi_currency, $this->utils );
$this->compatibility_classes[] = new WooCommerceDeposits( $this->multi_currency, $this->utils );
}
}

/**
* Returns the compatibility classes.
*
* @return array
*/
public function get_compatibility_classes(): array {
return $this->compatibility_classes;
}

/**
Expand Down
117 changes: 97 additions & 20 deletions includes/multi-currency/FrontendCurrencies.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,14 +51,41 @@ class FrontendCurrencies {
*/
protected $currency_format = [];


/**
* Order currency code.
*
* @var string|null
*/
protected $order_currency;

/**
* WOO currency cache.
*
* @var string
*/
private $woocommerce_currency;

/**
* Price Decimal Separator cache.
*
* @var array
*/
private $price_decimal_separators = [];

/**
* Selected Currency Code cache.
*
* @var string
*/
private $selected_currency_code;

/**
* Store Currency cache.
*
* @var Currency
*/
private $store_currency;

/**
* Constructor.
*
Expand Down Expand Up @@ -89,13 +116,28 @@ public function __construct( MultiCurrency $multi_currency, WC_Payments_Localiza
add_filter( 'woocommerce_shipping_method_add_rate_args', [ $this, 'fix_price_decimals_for_shipping_rates' ], 50, 2 );
}

/**
* The selected currency changed. We discard some cache.
*
* @return void
*/
public function selected_currency_changed() {
$this->selected_currency_code = null;
$this->price_decimal_separators = [];
$this->woocommerce_currency = null;
$this->store_currency = null;
}

/**
* Gets the store currency.
*
* @return Currency The store currency wrapped as a Currency object
*/
public function get_store_currency() {
return new Currency( get_option( 'woocommerce_currency' ) );
public function get_store_currency(): Currency {
if ( empty( $this->store_currency ) ) {
$this->store_currency = $this->multi_currency->get_default_currency();
}
return $this->store_currency;
}

/**
Expand All @@ -105,9 +147,13 @@ public function get_store_currency() {
*/
public function get_woocommerce_currency(): string {
if ( $this->compatibility->should_return_store_currency() ) {
return $this->multi_currency->get_default_currency()->get_code();
return $this->get_store_currency()->get_code();
}

if ( empty( $this->woocommerce_currency ) ) {
$this->woocommerce_currency = $this->get_selected_currency_code();
}
return $this->multi_currency->get_selected_currency()->get_code();
return $this->woocommerce_currency;
}

/**
Expand All @@ -133,11 +179,19 @@ public function get_price_decimals( $decimals ): int {
* @return string The decimal separator.
*/
public function get_price_decimal_separator( $separator ): string {
$currency_code = $this->get_currency_code();
if ( $currency_code !== $this->get_store_currency()->get_code() ) {
return $this->localization_service->get_currency_format( $currency_code )['decimal_sep'];
$currency_code = $this->get_currency_code();
$store_currency_code = $this->get_store_currency()->get_code();

if ( $currency_code === $store_currency_code ) {
$currency_code = $store_currency_code;
$this->price_decimal_separators[ $currency_code ] = $separator;
}
return $separator;

if ( empty( $this->price_decimal_separators[ $currency_code ] ) ) {
$this->price_decimal_separators[ $currency_code ] = $this->localization_service->get_currency_format( $currency_code )['decimal_sep'];
}

return $this->price_decimal_separators[ $currency_code ];
}

/**
Expand Down Expand Up @@ -200,7 +254,7 @@ public function add_currency_to_cart_hash( $hash ): string {
*
* @param mixed $arg Either WC_Order or the id of an order are expected, but can be empty.
*
* @return int The order id or what was passed as $arg.
* @return int|mixed The order id or what was passed as $arg.
*/
public function init_order_currency( $arg ) {
if ( null !== $this->order_currency ) {
Expand Down Expand Up @@ -250,24 +304,47 @@ public function fix_price_decimals_for_shipping_rates( array $args, $method ): a
* @return string|null Three letter currency code.
*/
private function get_currency_code() {
if ( $this->should_override_currency_code() ) {
if ( $this->should_use_order_currency() ) {
return $this->order_currency;
}
return $this->multi_currency->get_selected_currency()->get_code();

$this->selected_currency_code = $this->get_selected_currency_code();

return $this->selected_currency_code;
}

/**
* Helper function to "cache" the selected currency.
*
* @return string
*/
private function get_selected_currency_code(): string {
if ( empty( $this->selected_currency_code ) ) {
$this->selected_currency_code = $this->multi_currency->get_selected_currency()->get_code();
}
return $this->selected_currency_code;
}

/**
* Checks whether currency code used for formatting should be overridden.
*
* @return bool
*/
private function should_override_currency_code(): bool {
return $this->utils->is_call_in_backtrace(
[
'WC_Shortcode_My_Account::view_order',
'WC_Shortcode_Checkout::order_received',
'WC_Shortcode_Checkout::order_pay',
]
);
private function should_use_order_currency(): bool {
$pages = [ 'my-account', 'checkout' ];
$vars = [ 'order-received', 'order-pay', 'order-received', 'orders' ];

if ( $this->utils->is_page_with_vars( $pages, $vars ) ) {
return $this->utils->is_call_in_backtrace(
[
'WC_Shortcode_My_Account::view_order',
'WC_Shortcode_Checkout::order_received',
'WC_Shortcode_Checkout::order_pay',
'WC_Order->get_formatted_order_total',
]
);
}

return false;
}
}
3 changes: 3 additions & 0 deletions includes/multi-currency/MultiCurrency.php
Original file line number Diff line number Diff line change
Expand Up @@ -629,6 +629,9 @@ public function update_selected_currency( string $currency_code ) {
$user_id = get_current_user_id();
$currency = $this->get_enabled_currencies()[ $code ] ?? null;

// We discard the cache for the front-end.
$this->frontend_currencies->selected_currency_changed();

if ( null === $currency ) {
return;
}
Expand Down
21 changes: 21 additions & 0 deletions includes/multi-currency/Utils.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,27 @@ public function is_call_in_backtrace( array $calls ): bool {
return false;
}

/**
* Checks the query_vars array for a particular pagename and variable to be set.
*
* @param array $pages Array of the pagenames to check for.
* @param array $vars Array of the vars to check for.
*
* @return bool True if found, false if not.
*/
public function is_page_with_vars( array $pages, array $vars ): bool {
global $wp;

if ( $wp->query_vars && isset( $wp->query_vars['pagename'] ) && in_array( $wp->query_vars['pagename'], $pages, true ) ) {
foreach ( $vars as $var ) {
if ( isset( $wp->query_vars[ $var ] ) ) {
return true;
}
}
}
return false;
}

/**
* Checks if is a REST API request and the HTTP referer matches admin url.
*
Expand Down
18 changes: 18 additions & 0 deletions tests/unit/multi-currency/test-class-compatibility.php
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,24 @@ public function setUp() {
$this->compatibility = new Compatibility( $this->mock_multi_currency, $this->mock_utils );
}

public function test_init_compatibility_classes_does_not_add_classes_if_one_enabled_currencies() {
$this->mock_multi_currency
->method( 'get_enabled_currencies' )
->willReturn( [ 'USD' ] );

$this->compatibility->init_compatibility_classes();
$this->assertEquals( 0, count( $this->compatibility->get_compatibility_classes() ) );
}

public function test_init_compatibility_classes_adds_classes_if_enabled_currencies() {
$this->mock_multi_currency
->method( 'get_enabled_currencies' )
->willReturn( [ 'USD', 'EUR' ] );

$this->compatibility->init_compatibility_classes();
$this->assertGreaterThan( 0, count( $this->compatibility->get_compatibility_classes() ) );
}

public function test_should_convert_coupon_amount_return_true_on_null_coupon() {
$this->assertTrue( $this->compatibility->should_convert_coupon_amount( null ) );
}
Expand Down
35 changes: 23 additions & 12 deletions tests/unit/multi-currency/test-class-frontend-currencies.php
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ public function setUp() {
$this->mock_utils = $this->createMock( Utils::class );
$this->mock_order = WC_Helper_Order::create_order();

$this->mock_multi_currency
->method( 'get_default_currency' )
->willReturn( new Currency( 'USD' ) );

$this->frontend_currencies = new FrontendCurrencies( $this->mock_multi_currency, $this->mock_localization_service, $this->mock_utils, $this->mock_compatibility );
}

Expand Down Expand Up @@ -94,24 +98,14 @@ public function woocommerce_filter_provider() {
}

public function test_get_woocommerce_currency_returns_selected_currency() {
$store_currency = new Currency( 'USD' );
$selected_currency = new Currency( 'EUR' );
$this->mock_multi_currency->method( 'get_default_currency' )->willReturn( $store_currency );
$this->mock_multi_currency->method( 'get_selected_currency' )->willReturn( $selected_currency );

$this->mock_multi_currency->method( 'get_selected_currency' )->willReturn( $selected_currency );
$this->mock_multi_currency->method( 'get_selected_currency' )->willReturn( new Currency( 'EUR' ) );
$this->mock_compatibility->method( 'should_return_store_currency' )->willReturn( false );

$this->assertSame( 'EUR', $this->frontend_currencies->get_woocommerce_currency() );
}

public function test_get_woocommerce_currency_returns_store_currency() {
$store_currency = new Currency( 'USD' );
$selected_currency = new Currency( 'EUR' );
$this->mock_multi_currency->method( 'get_default_currency' )->willReturn( $store_currency );
$this->mock_multi_currency->method( 'get_selected_currency' )->willReturn( $selected_currency );

$this->mock_multi_currency->method( 'get_selected_currency' )->willReturn( $selected_currency );
$this->mock_multi_currency->method( 'get_selected_currency' )->willReturn( new Currency( 'EUR' ) );
$this->mock_compatibility->method( 'should_return_store_currency' )->willReturn( true );

$this->assertSame( 'USD', $this->frontend_currencies->get_woocommerce_currency() );
Expand All @@ -129,6 +123,10 @@ public function test_get_price_decimals_returns_num_decimals_for_order_currency(
->expects( $this->once() )
->method( 'is_call_in_backtrace' )
->willReturn( true );
$this->mock_utils
->expects( $this->once() )
->method( 'is_page_with_vars' )
->willReturn( true );
$this->mock_multi_currency->method( 'get_selected_currency' )->willReturn( new Currency( 'USD' ) );
$this->mock_localization_service->method( 'get_currency_format' )->with( 'EUR' )->willReturn( [ 'num_decimals' => 3 ] );

Expand Down Expand Up @@ -157,6 +155,10 @@ public function test_get_price_decimal_separator_returns_decimal_sep_for_order_c
->expects( $this->once() )
->method( 'is_call_in_backtrace' )
->willReturn( true );
$this->mock_utils
->expects( $this->once() )
->method( 'is_page_with_vars' )
->willReturn( true );
$this->mock_multi_currency->method( 'get_selected_currency' )->willReturn( new Currency( 'USD' ) );
$this->mock_localization_service->method( 'get_currency_format' )->with( 'EUR' )->willReturn( [ 'decimal_sep' => '.' ] );

Expand Down Expand Up @@ -185,6 +187,10 @@ public function test_get_price_thousand_separator_returns_thousand_sep_for_order
->expects( $this->once() )
->method( 'is_call_in_backtrace' )
->willReturn( true );
$this->mock_utils
->expects( $this->once() )
->method( 'is_page_with_vars' )
->willReturn( true );
$this->mock_multi_currency->method( 'get_selected_currency' )->willReturn( new Currency( 'USD' ) );
$this->mock_localization_service->method( 'get_currency_format' )->with( 'EUR' )->willReturn( [ 'thousand_sep' => ',' ] );

Expand Down Expand Up @@ -213,10 +219,15 @@ public function test_get_woocommerce_price_format_returns_format_for_order_curre
->expects( $this->once() )
->method( 'is_call_in_backtrace' )
->willReturn( true );
$this->mock_utils
->expects( $this->once() )
->method( 'is_page_with_vars' )
->willReturn( true );
$this->mock_multi_currency->method( 'get_selected_currency' )->willReturn( new Currency( 'USD' ) );
$this->mock_localization_service->method( 'get_currency_format' )->with( 'EUR' )->willReturn( [ 'currency_pos' => 'left' ] );

$this->mock_order->set_currency( 'EUR' );
$this->frontend_currencies->selected_currency_changed();
$this->frontend_currencies->init_order_currency( $this->mock_order );

$this->assertEquals( '%1$s%2$s', $this->frontend_currencies->get_woocommerce_price_format( '%2$s%1$s' ) );
Expand Down
13 changes: 13 additions & 0 deletions tests/unit/multi-currency/test-class-utils.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,19 @@ public function test_is_call_in_backtrace_return_true() {
$this->assertTrue( $this->utils->is_call_in_backtrace( [ 'WCPay_Multi_Currency_Utils_Tests->test_is_call_in_backtrace_return_true' ] ) );
}

public function test_is_page_with_vars_return_false() {
$this->assertFalse( $this->utils->is_page_with_vars( [ 'test' ], [ 'test' ] ) );
}

public function test_is_page_with_vars_return_true() {
global $wp;
$wp->query_vars = [
'pagename' => 'checkout',
'order-received' => '42',
];
$this->assertTrue( $this->utils->is_page_with_vars( [ 'checkout' ], [ 'order-received' ] ) );
}

public function test_is_admin_api_request_returns_false() {
$_SERVER['HTTP_REFERER'] = 'http://example.org/';
$_SERVER['REQUEST_URI'] = trailingslashit( rest_get_url_prefix() );
Expand Down

0 comments on commit 1518a12

Please sign in to comment.