Файловый менеджер - Редактировать - /home/harasnat/www/labour/wp-content/plugins/woocommerce-payments/includes/multi-currency/MultiCurrency.php
Назад
<?php /** * Class MultiCurrency * * @package WooCommerce\Payments\MultiCurrency */ namespace WCPay\MultiCurrency; use WCPay\MultiCurrency\Exceptions\InvalidCurrencyException; use WCPay\MultiCurrency\Exceptions\InvalidCurrencyRateException; use WCPay\MultiCurrency\Interfaces\MultiCurrencyAccountInterface; use WCPay\MultiCurrency\Interfaces\MultiCurrencyApiClientInterface; use WCPay\MultiCurrency\Interfaces\MultiCurrencyCacheInterface; use WCPay\MultiCurrency\Interfaces\MultiCurrencyLocalizationInterface; use WCPay\MultiCurrency\Interfaces\MultiCurrencySettingsInterface; use WCPay\MultiCurrency\Logger; use WCPay\MultiCurrency\Notes\NoteMultiCurrencyAvailable; use WCPay\MultiCurrency\Utils; use WC_Payments_Features; defined( 'ABSPATH' ) || exit; /** * Class that controls Multi-Currency functionality. */ class MultiCurrency { const CURRENCY_SESSION_KEY = 'wcpay_currency'; const CURRENCY_META_KEY = 'wcpay_currency'; const FILTER_PREFIX = 'wcpay_multi_currency_'; const CUSTOMER_CURRENCIES_KEY = 'wcpay_multi_currency_stored_customer_currencies'; /** * The plugin's ID. * * @var string */ public $id = 'wcpay_multi_currency'; /** * Static flag to show if the currencies initialization has been completed * * @var bool */ protected static $is_initialized = false; /** * Compatibility instance. * * @var Compatibility */ protected $compatibility; /** * Geolocation instance. * * @var Geolocation */ protected $geolocation; /** * The Currency Switcher Widget instance. * * @var null|CurrencySwitcherWidget */ protected $currency_switcher_widget; /** * Gutenberg Block implementation of the Currency Switcher Widget instance. * * @var CurrencySwitcherBlock */ protected $currency_switcher_block; /** * Utils instance. * * @var Utils */ protected $utils; /** * FrontendPrices instance. * * @var FrontendPrices */ protected $frontend_prices; /** * FrontendCurrencies instance. * * @var FrontendCurrencies */ protected $frontend_currencies; /** * StorefrontIntegration instance. * * @var StorefrontIntegration */ protected $storefront_integration; /** * The available currencies. * * @var Currency[]|null */ protected $available_currencies; /** * The default currency. * * @var Currency|null */ protected $default_currency; /** * The enabled currencies. * * @var Currency[]|null */ protected $enabled_currencies; /** * Instance of MultiCurrencySettingsInterface. * * @var MultiCurrencySettingsInterface */ private $settings_service; /** * Client for making requests to the API * * @var MultiCurrencyApiClientInterface */ private $payments_api_client; /** * Instance of MultiCurrencyAccountInterface. * * @var MultiCurrencyAccountInterface */ private $payments_account; /** * Instance of MultiCurrencyLocalizationInterface. * * @var MultiCurrencyLocalizationInterface */ private $localization_service; /** * Instance of MultiCurrencyCacheInterface. * * @var MultiCurrencyCacheInterface */ private $cache; /** * Tracking instance. * * @var Tracking */ protected $tracking; /** * Simulation variables array. * * @var array */ protected $simulation_params = []; /** * Class constructor. * * @param MultiCurrencySettingsInterface $settings_service Settings service. * @param MultiCurrencyApiClientInterface $payments_api_client Payments API client. * @param MultiCurrencyAccountInterface $payments_account Payments Account instance. * @param MultiCurrencyLocalizationInterface $localization_service Localization Service instance. * @param MultiCurrencyCacheInterface $cache Cache instance. * @param Utils|null $utils Optional Utils instance. */ public function __construct( MultiCurrencySettingsInterface $settings_service, MultiCurrencyApiClientInterface $payments_api_client, MultiCurrencyAccountInterface $payments_account, MultiCurrencyLocalizationInterface $localization_service, MultiCurrencyCacheInterface $cache, ?Utils $utils = null ) { $this->settings_service = $settings_service; $this->payments_api_client = $payments_api_client; $this->payments_account = $payments_account; $this->localization_service = $localization_service; $this->cache = $cache; // If a Utils instance is not passed as argument, initialize it. This allows to mock it in tests. $this->utils = $utils ?? new Utils(); $this->geolocation = new Geolocation( $this->localization_service ); $this->compatibility = new Compatibility( $this, $this->utils ); $this->currency_switcher_block = new CurrencySwitcherBlock( $this, $this->compatibility ); } /** * Backwards compatibility for the old `instance()` static method. * * We need to use this as some plugins still call `MultiCurrency::instance()` directly. * * @return null|MultiCurrency - Main instance. */ public static function instance() { if ( function_exists( 'WC_Payments_Multi_Currency' ) ) { return WC_Payments_Multi_Currency(); } } /** * Initializes this class' WP hooks. * * @return void */ public function init_hooks() { if ( is_admin() && current_user_can( 'manage_woocommerce' ) ) { add_filter( 'woocommerce_get_settings_pages', [ $this, 'init_settings_pages' ] ); // Enqueue the scripts after the main WC_Payments_Admin does. add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_admin_scripts' ], 20 ); } add_action( 'init', [ $this, 'init' ] ); add_action( 'rest_api_init', [ $this, 'init_rest_api' ] ); add_action( 'widgets_init', [ $this, 'init_widgets' ] ); $is_frontend_request = ! is_admin() && ! defined( 'DOING_CRON' ) && ! Utils::is_admin_api_request(); if ( $is_frontend_request || Utils::is_store_api_request() ) { // Make sure that this runs after the main init function. add_action( 'init', [ $this, 'update_selected_currency_by_url' ], 11 ); add_action( 'init', [ $this, 'update_selected_currency_by_geolocation' ], 12 ); add_action( 'init', [ $this, 'possible_simulation_activation' ], 13 ); add_action( 'woocommerce_created_customer', [ $this, 'set_new_customer_currency_meta' ] ); } if ( ! Utils::is_store_batch_request() && ! Utils::is_store_api_request() && WC()->is_rest_api_request() ) { if ( isset( $_GET['currency'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification $get_currency_from_query_param = function () { $currency = sanitize_text_field( wp_unslash( $_GET['currency'] ) ); // phpcs:ignore WordPress.Security.NonceVerification return strtoupper( $currency ); }; add_filter( self::FILTER_PREFIX . 'override_selected_currency', $get_currency_from_query_param ); } else { // If the request is a REST API request, ensure we default to the store currency and leave price as-is. add_filter( self::FILTER_PREFIX . 'should_return_store_currency', '__return_true' ); add_filter( self::FILTER_PREFIX . 'should_convert_product_price', '__return_false' ); $get_default_currency_code = function () { return $this->get_default_currency()->get_code(); }; add_filter( self::FILTER_PREFIX . 'override_selected_currency', $get_default_currency_code ); } } add_filter( 'wcpay_payment_fields_js_config', [ $this, 'add_props_to_wcpay_js_config' ] ); $this->currency_switcher_block->init_hooks(); } /** * Called after the WooCommerce session has been initialized. Initialises the available currencies, * default currency and enabled currencies for the Multi-Currency plugin. * * @return void */ public function init() { $store_currency_updated = $this->check_store_currency_for_change(); // If the store currency has been updated, clear the cache to make sure we fetch fresh rates from the server. if ( $store_currency_updated ) { $this->clear_cache(); } $this->initialize_available_currencies(); $this->set_default_currency(); $this->initialize_enabled_currencies(); // If the store currency has been updated, we need to update the notice that will display any manual currencies. if ( $store_currency_updated ) { $this->update_manual_rate_currencies_notice_option(); } $admin_notices = new AdminNotices(); $user_settings = new UserSettings( $this ); new Analytics( $this, $this->settings_service ); $this->frontend_prices = new FrontendPrices( $this, $this->compatibility ); $this->frontend_currencies = new FrontendCurrencies( $this, $this->localization_service, $this->utils, $this->compatibility ); $this->tracking = new Tracking( $this ); // Init all the hooks. $admin_notices->init_hooks(); $user_settings->init_hooks(); $this->frontend_prices->init_hooks(); $this->frontend_currencies->init_hooks(); $this->tracking->init_hooks(); add_action( 'woocommerce_order_refunded', [ $this, 'add_order_meta_on_refund' ], 50, 2 ); // Check to make sure there are enabled currencies, then for Storefront being active, and then load the integration. $theme = wp_get_theme(); if ( 'storefront' === $theme->get_stylesheet() || 'storefront' === $theme->get_template() ) { $this->storefront_integration = new StorefrontIntegration( $this ); } if ( is_admin() ) { add_action( 'admin_init', [ $this, 'add_woo_admin_notes' ] ); } // Update the customer currencies option after an order status change. add_action( 'woocommerce_order_status_changed', [ $this, 'maybe_update_customer_currencies_option' ] ); static::$is_initialized = true; } /** * Initialize the REST API controller. * * @return void */ public function init_rest_api() { // Ensures we are not initializing our REST during `rest_preload_api_request`. // When constructors signature changes, in manual update scenarios we were run into fatals. // Those fatals are not critical, but it causes hickups in release process as catches unnecessary attention. if ( function_exists( 'get_current_screen' ) && get_current_screen() ) { return; } $api_controller = new RestController( $this ); $api_controller->register_routes(); } /** * Initialize the legacy widgets. * * @return void */ public function init_widgets() { // Register the legacy widget. $this->currency_switcher_widget = new CurrencySwitcherWidget( $this, $this->compatibility ); register_widget( $this->currency_switcher_widget ); } /** * Initialize the Settings Pages. * * @param array $settings_pages The settings pages. * * @return array The new settings pages. */ public function init_settings_pages( $settings_pages ): array { // We don't need to check if the payment provider is connected for the // Settings page generation on the incoming CLI and async job calls. if ( ( defined( 'WP_CLI' ) && WP_CLI ) || ( defined( 'WPCOM_JOBS' ) && WPCOM_JOBS ) ) { return $settings_pages; } // Due to autoloader limitations, we shouldn't initiate MCCY settings if the plugin was just upgraded: // https://github.com/Automattic/woocommerce-payments/issues/9676. if ( did_action( 'upgrader_process_complete' ) ) { return $settings_pages; } if ( $this->payments_account->is_provider_connected() ) { $settings = new Settings( $this ); $settings->init_hooks(); $settings_pages[] = $settings; } else { $settings_onboard_cta = new SettingsOnboardCta( $this, $this->payments_account ); $settings_onboard_cta->init_hooks(); $settings_pages[] = $settings_onboard_cta; } return $settings_pages; } /** * Load the admin assets. * * @return void */ public function enqueue_admin_scripts() { global $current_tab; // Enqueue the settings JS and CSS only on the WCPay multi-currency settings page. if ( 'wcpay_multi_currency' !== $current_tab ) { return; } $this->register_admin_scripts(); wp_enqueue_script( 'WCPAY_MULTI_CURRENCY_SETTINGS' ); wp_enqueue_style( 'WCPAY_MULTI_CURRENCY_SETTINGS' ); } /** * Add multi-currency specific props to the WCPay JS config. * * @param array $config The JS config that will be loaded on the frontend. * * @return array The updated JS config. */ public function add_props_to_wcpay_js_config( $config ) { $config['isMultiCurrencyEnabled'] = true; return $config; } /** * Wipes the cached currency data option, forcing to re-fetch the data from WPCOM. * * @return void */ public function clear_cache() { Logger::debug( 'Clearing the cache to force new rates to be fetched from the server.' ); $this->cache->delete( MultiCurrencyCacheInterface::CURRENCIES_KEY ); } /** * Gets and caches the data for the currency rates from the server. * Will be returned as an array with two keys: * - 'currencies' (the currencies) * - 'updated' (when this data was fetched from the API). * * @return ?array */ public function get_cached_currencies() { // If connection to server cannot be established, payment provider is not connected, or the account is rejected, // return any data we have cached (expired or not) or null. if ( ! $this->payments_api_client->is_server_connected() || ! $this->payments_account->is_provider_connected() || $this->payments_account->is_account_rejected() ) { $cached_data = $this->cache->get( MultiCurrencyCacheInterface::CURRENCIES_KEY, true ); return $cached_data ?? null; } return $this->cache->get_or_add( MultiCurrencyCacheInterface::CURRENCIES_KEY, function () { try { $currency_data = $this->payments_api_client->get_currency_rates( strtolower( get_woocommerce_currency() ) ); return [ 'currencies' => $currency_data, 'updated' => time(), ]; } catch ( \Exception $e ) { return null; } }, function ( $data ) { return is_array( $data ) && isset( $data['currencies'], $data['updated'] ); } ); } /** * Returns the Compatibility instance. * * @return Compatibility */ public function get_compatibility() { return $this->compatibility; } /** * Returns the Currency Switcher Widget instance. * * @return CurrencySwitcherWidget|null */ public function get_currency_switcher_widget() { return $this->currency_switcher_widget; } /** * Returns the FrontendPrices instance. * * @return FrontendPrices */ public function get_frontend_prices(): FrontendPrices { return $this->frontend_prices; } /** * Returns the FrontendCurrencies instance. * * @return FrontendCurrencies */ public function get_frontend_currencies(): FrontendCurrencies { return $this->frontend_currencies; } /** * Returns the StorefrontIntegration instance. * * @return StorefrontIntegration|null */ public function get_storefront_integration() { return $this->storefront_integration; } /** * Generates the switcher widget markup. * * @param array $instance The widget's instance settings. * @param array $args The widget's arguments. * * @return string The widget markup. */ public function get_switcher_widget_markup( array $instance = [], array $args = [] ): string { /** * The spl_object_hash function is used here due to we register the widget with an instance of the widget and * not the class name of the widget. WordPress core takes the instance and passes it through spl_object_hash * to get a hash and adds that as the widget's name in the $wp_widget_factory->widgets[] array. In order to * call the_widget, you need to have the name of the widget, so we get the instance and hash to use. */ ob_start(); $currency_switcher_widget = $this->get_currency_switcher_widget(); if ( ! is_object( $currency_switcher_widget ) ) { Logger::notice( sprintf( 'Invalid widget markup. Widget instance must be type object, %s given.', gettype( $currency_switcher_widget ) ) ); return ob_get_clean(); } the_widget( spl_object_hash( $currency_switcher_widget ), apply_filters( self::FILTER_PREFIX . 'theme_widget_instance', $instance ), apply_filters( self::FILTER_PREFIX . 'theme_widget_args', $args ) ); return ob_get_clean(); } /** * Returns the store's current available, enabled, and default currencies. * * @return array */ public function get_store_currencies(): array { return [ 'available' => $this->get_available_currencies(), 'enabled' => $this->get_enabled_currencies(), 'default' => $this->get_default_currency(), ]; } /** * Gets the currency settings for a single currency. * * @param string $currency_code The currency code to get settings for. * * @return array The currency's settings. * * @throws InvalidCurrencyException */ public function get_single_currency_settings( string $currency_code ): array { // Confirm the currency code is valid before trying to get the settings. if ( ! array_key_exists( strtoupper( $currency_code ), $this->get_available_currencies() ) ) { $this->log_and_throw_invalid_currency_exception( __FUNCTION__, $currency_code ); } $currency_code = strtolower( $currency_code ); return [ 'exchange_rate_type' => get_option( 'wcpay_multi_currency_exchange_rate_' . $currency_code, 'automatic' ), 'manual_rate' => get_option( 'wcpay_multi_currency_manual_rate_' . $currency_code, null ), 'price_rounding' => get_option( 'wcpay_multi_currency_price_rounding_' . $currency_code, null ), 'price_charm' => get_option( 'wcpay_multi_currency_price_charm_' . $currency_code, null ), ]; } /** * Updates the currency settings for a single currency. * * @param string $currency_code The single currency code to be updated. * @param string $exchange_rate_type The exchange rate type setting. * @param float $price_rounding The price rounding setting. * @param float $price_charm The price charm setting. * @param ?float $manual_rate The manual rate setting, or null. * * @return void * * @throws InvalidCurrencyException * @throws InvalidCurrencyRateException */ public function update_single_currency_settings( string $currency_code, string $exchange_rate_type, float $price_rounding, float $price_charm, $manual_rate = null ) { // Confirm the currency code is valid before trying to update the settings. if ( ! array_key_exists( strtoupper( $currency_code ), $this->get_available_currencies() ) ) { $this->log_and_throw_invalid_currency_exception( __FUNCTION__, $currency_code ); } $currency_code = strtolower( $currency_code ); if ( 'manual' === $exchange_rate_type && ! is_null( $manual_rate ) ) { if ( ! is_numeric( $manual_rate ) || 0 >= $manual_rate ) { $message = 'Invalid manual currency rate passed to update_single_currency_settings: ' . $manual_rate; Logger::error( $message ); throw new InvalidCurrencyRateException( esc_html( $message ), 500 ); } update_option( 'wcpay_multi_currency_manual_rate_' . $currency_code, $manual_rate ); } update_option( 'wcpay_multi_currency_price_rounding_' . $currency_code, $price_rounding ); update_option( 'wcpay_multi_currency_price_charm_' . $currency_code, $price_charm ); if ( in_array( $exchange_rate_type, [ 'automatic', 'manual' ], true ) ) { update_option( 'wcpay_multi_currency_exchange_rate_' . $currency_code, esc_attr( $exchange_rate_type ) ); } } /** * Updates the customer currencies option. * * @param int $order_id The order ID. * * @return void */ public function maybe_update_customer_currencies_option( $order_id ) { $order = wc_get_order( $order_id ); if ( ! $order ) { return; } $currency = strtoupper( $order->get_currency() ); $currencies = self::get_all_customer_currencies(); // Skip if the currency is already in the list. if ( in_array( $currency, $currencies, true ) ) { return; } $currencies[] = $currency; update_option( self::CUSTOMER_CURRENCIES_KEY, $currencies ); } /** * Gets the currencies available. Initializes it if needed. * * @return Currency[] Array of Currency objects. */ public function get_available_currencies(): array { if ( null === $this->available_currencies ) { $this->init(); } return $this->available_currencies ?? []; } /** * Gets the store base currency. Initializes it if needed. * * @return Currency The store base currency. */ public function get_default_currency(): Currency { if ( null === $this->default_currency ) { $this->init(); } return $this->default_currency ?? new Currency( $this->localization_service, get_woocommerce_currency() ); } /** * Gets the currently enabled currencies. Initializes it if needed. * * @return Currency[] Array of Currency objects. */ public function get_enabled_currencies(): array { if ( null === $this->enabled_currencies ) { $this->init(); } return $this->enabled_currencies ?? []; } /** * Sets the enabled currencies for the store. * * @param string[] $currencies Array of currency codes to be enabled. * * @return void * * @throws InvalidCurrencyException */ public function set_enabled_currencies( $currencies = [] ) { // If curriencies is not an array, or if there are no currencies, just exit. if ( ! is_array( $currencies ) || 0 === count( $currencies ) ) { return; } // Confirm the currencies submitted are available/valid currencies. $invalid_currencies = array_diff( $currencies, array_keys( $this->get_available_currencies() ) ); if ( 0 < count( $invalid_currencies ) ) { $this->log_and_throw_invalid_currency_exception( __FUNCTION__, implode( ', ', $invalid_currencies ) ); } // Get the currencies that were removed before they are updated. $removed_currencies = array_diff( array_keys( $this->get_enabled_currencies() ), $currencies ); // Update the enabled currencies and reinitialize. update_option( $this->id . '_enabled_currencies', $currencies ); $this->initialize_enabled_currencies(); Logger::debug( 'Enabled currencies updated: ' . var_export( $currencies, true ) // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_var_export ); // Now remove the removed currencies settings. if ( 0 < count( $removed_currencies ) ) { $this->remove_currencies_settings( $removed_currencies ); } } /** * Gets the user selected currency, or `$default_currency` if is not set. * * @return Currency */ public function get_selected_currency(): Currency { $multi_currency_code = $this->compatibility->override_selected_currency(); $currency_code = $multi_currency_code ? $multi_currency_code : $this->get_stored_currency_code(); return $this->get_enabled_currencies()[ $currency_code ] ?? $this->get_default_currency(); } /** * Update the selected currency from a currency code. * * @param string $currency_code Three letter currency code. * @param bool $persist_change Set true to store the change in the session cookie if it doesn't exist yet. * * @return void */ public function update_selected_currency( string $currency_code, bool $persist_change = true ) { $code = strtoupper( $currency_code ); $user_id = get_current_user_id(); $currency = $this->get_enabled_currencies()[ $code ] ?? null; if ( null === $currency ) { return; } // We discard the cache for the front-end. $this->frontend_currencies->selected_currency_changed(); // initializing the session (useful for Store API), // so that the selected currency (set as query string parameter) can be correctly set. if ( ! isset( WC()->session ) ) { WC()->initialize_session(); } if ( $this->get_stored_currency_code() !== $code && $persist_change ) { $this->frontend_currencies->clear_url_price_params(); } if ( 0 === $user_id && WC()->session ) { WC()->session->set( self::CURRENCY_SESSION_KEY, $currency->get_code() ); // Set the session cookie if is not yet to persist the selected currency. if ( ! WC()->session->has_session() && ! headers_sent() && $persist_change ) { $this->utils->set_customer_session_cookie( true ); } } elseif ( $user_id ) { update_user_meta( $user_id, self::CURRENCY_META_KEY, $currency->get_code() ); } // Recalculate cart when currency changes. if ( did_action( 'wp_loaded' ) ) { $this->recalculate_cart(); } else { add_action( 'wp_loaded', [ $this, 'recalculate_cart' ] ); } } /** * Update the selected currency from url param `currency`. * * @return void */ public function update_selected_currency_by_url() { if ( ! isset( $_GET['currency'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification return; } $this->update_selected_currency( sanitize_text_field( wp_unslash( $_GET['currency'] ) ) ); // phpcs:ignore WordPress.Security.NonceVerification } /** * Update the selected currency from the user's geolocation country. * * @return void */ public function update_selected_currency_by_geolocation() { // We only want to automatically set the currency if the option is enabled and it shouldn't be disabled for any reason. if ( ! $this->is_using_auto_currency_switching() || $this->compatibility->should_disable_currency_switching() ) { return; } // Display notice, prevent duplicates in simulation. if ( ! has_action( 'wp_footer', [ $this, 'display_geolocation_currency_update_notice' ] ) ) { add_action( 'wp_footer', [ $this, 'display_geolocation_currency_update_notice' ] ); } // Update currency only if it's already not set. if ( $this->get_stored_currency_code() ) { return; } $currency = $this->geolocation->get_currency_by_customer_location(); if ( empty( $this->get_enabled_currencies()[ $currency ] ) ) { return; } $this->update_selected_currency( $currency, false ); } /** * Gets the configured value for apply charm pricing only to products. * * @return mixed The configured value. */ public function get_apply_charm_only_to_products() { return apply_filters( self::FILTER_PREFIX . 'apply_charm_only_to_products', true ); } /** * Gets the converted price using the current currency with the rounding and charm pricing settings. * * @param mixed $price The price to be converted. * @param string $type The type of price being converted. One of 'product', 'shipping', 'tax', 'coupon', or 'exchange_rate'. * * @return float The converted price. */ public function get_price( $price, string $type ): float { $supported_types = [ 'product', 'shipping', 'tax', 'coupon', 'exchange_rate' ]; $currency = $this->get_selected_currency(); if ( ! in_array( $type, $supported_types, true ) || $currency->get_is_default() ) { return (float) $price; } $converted_price = ( (float) $price ) * $currency->get_rate(); if ( 'tax' === $type || 'coupon' === $type || 'exchange_rate' === $type ) { // We must make sure the price is rounded properly before returning it, otherwise we // may end up with inconsistent prices in the cart. $num_decimals = absint( $this->localization_service->get_currency_format( $currency->get_code() )['num_decimals'] ); return round( $converted_price, $num_decimals ); } $charm_compatible_types = [ 'product', 'shipping' ]; $apply_charm_pricing = $this->get_apply_charm_only_to_products() ? 'product' === $type : in_array( $type, $charm_compatible_types, true ); return $this->get_adjusted_price( $converted_price, $apply_charm_pricing, $currency ); } /** * Gets a raw converted amount based on the amount and currency codes passed. * This is a helper method for external conversions, if needed. * * @param float $amount The amount to be converted. * @param string $to_currency The 3 letter currency code to convert the amount to. * @param string $from_currency The 3 letter currency code to convert the amount from. * * @return float The converted amount. * * @throws InvalidCurrencyException * @throws InvalidCurrencyRateException */ public function get_raw_conversion( float $amount, string $to_currency, string $from_currency = '' ): float { $enabled_currencies = $this->get_enabled_currencies(); // If the from_currency is not set, use the store currency. if ( '' === $from_currency ) { $from_currency = $this->get_default_currency()->get_code(); } // We throw an exception if either of the currencies are not enabled. $to_currency = strtoupper( $to_currency ); $from_currency = strtoupper( $from_currency ); foreach ( [ $to_currency, $from_currency ] as $code ) { if ( ! isset( $enabled_currencies[ $code ] ) ) { $this->log_and_throw_invalid_currency_exception( __FUNCTION__, $code ); } } // Get the rates. $to_currency_rate = $enabled_currencies[ $to_currency ]->get_rate(); $from_currency_rate = $enabled_currencies[ $from_currency ]->get_rate(); // Throw an exception in case from_currency_rate is less than or equal to 0. if ( 0 >= $from_currency_rate ) { $message = 'Invalid rate for from_currency in get_raw_conversion: ' . $from_currency_rate; Logger::error( $message ); throw new InvalidCurrencyRateException( esc_html( $message ), 500 ); } $amount = $amount * ( $to_currency_rate / $from_currency_rate ); return (float) $amount; } /** * Recalculates WooCommerce cart totals. * * @return void */ public function recalculate_cart() { if ( WC()->cart ) { WC()->cart->calculate_totals(); } } /** * When an order is refunded, a new psuedo order is created to represent the refund. * We want to check if the original order was a multi-currency order, and if so, copy the meta data * to the new order. * * @param int $order_id The order ID. * @param int $refund_id The refund order ID. */ public function add_order_meta_on_refund( $order_id, $refund_id ) { $default_currency = $this->get_default_currency(); $order = wc_get_order( $order_id ); $refund = wc_get_order( $refund_id ); // Do not add exchange rate if order was made in the store's default currency. if ( ! $order || ! $refund || $default_currency->get_code() === $order->get_currency() ) { return; } $order_exchange_rate = $order->get_meta( '_wcpay_multi_currency_order_exchange_rate', true ); $stripe_exchange_rate = $order->get_meta( '_wcpay_multi_currency_stripe_exchange_rate', true ); $order_default_currency = $order->get_meta( '_wcpay_multi_currency_order_default_currency', true ); $refund->update_meta_data( '_wcpay_multi_currency_order_exchange_rate', $order_exchange_rate ); $refund->update_meta_data( '_wcpay_multi_currency_order_default_currency', $order_default_currency ); if ( $stripe_exchange_rate ) { $refund->update_meta_data( '_wcpay_multi_currency_stripe_exchange_rate', $stripe_exchange_rate ); } $refund->save_meta_data(); } /** * Displays a notice on the frontend informing the customer of the * automatic currency switch. */ public function display_geolocation_currency_update_notice() { $current_currency = $this->get_selected_currency(); $store_currency = get_option( 'woocommerce_currency' ); $country = $this->geolocation->get_country_by_customer_location(); $geolocated_currency = $this->geolocation->get_currency_by_customer_location(); $currencies = get_woocommerce_currencies(); // Don't run next checks if simulation is enabled. if ( ! $this->is_simulation_enabled() ) { // Do not display notice if using the store's default currency. if ( $store_currency === $current_currency->get_code() ) { return; } // Do not display notice for other currencies than geolocated. if ( $current_currency->get_code() !== $geolocated_currency ) { return; } } $message = sprintf( /* translators: %1 User's country, %2 Selected currency name, %3 Default store currency name, %4 Link to switch currency */ __( 'We noticed you\'re visiting from %1$s. We\'ve updated our prices to %2$s for your shopping convenience. <a href="%4$s">Use %3$s instead.</a>', 'woocommerce-payments' ), apply_filters( self::FILTER_PREFIX . 'override_notice_country', WC()->countries->countries[ $country ] ), apply_filters( self::FILTER_PREFIX . 'override_notice_currency_name', $current_currency->get_name() ), esc_html( $currencies[ $store_currency ] ), esc_url( '?currency=' . $store_currency ) ); $notice_id = md5( $message ); echo '<p class="woocommerce-store-notice demo_store" data-notice-id="' . esc_attr( $notice_id . 2 ) . '" style="display:none;">'; // No need to escape here as the contents of $message is already escaped. // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped echo $message; echo ' <a href="#" class="woocommerce-store-notice__dismiss-link">' . esc_html__( 'Dismiss', 'woocommerce-payments' ) . '</a></p>'; } /** * Sets a new customer's currency meta to what's in their session. * This is needed for when a new user/customer is created during the checkout process. * * @param int $customer_id The user/customer id. * * @return void */ public function set_new_customer_currency_meta( $customer_id ) { $code = 0 !== $customer_id && WC()->session ? WC()->session->get( self::CURRENCY_SESSION_KEY ) : false; if ( $code ) { update_user_meta( $customer_id, self::CURRENCY_META_KEY, $code ); } } /** * Adds Multi-Currency notes to the WC-Admin inbox. * * @return void */ public function add_woo_admin_notes() { // Do not try to add notes on ajax requests to improve their performance. if ( wp_doing_ajax() ) { return; } if ( defined( 'WC_VERSION' ) && version_compare( WC_VERSION, '4.4.0', '>=' ) ) { NoteMultiCurrencyAvailable::set_account( $this->payments_account ); NoteMultiCurrencyAvailable::possibly_add_note(); } } /** * Removes Multi-Currency notes from the WC-Admin inbox. * * @return void */ public static function remove_woo_admin_notes() { if ( defined( 'WC_VERSION' ) && version_compare( WC_VERSION, '4.4.0', '>=' ) ) { NoteMultiCurrencyAvailable::possibly_delete_note(); } } /** * Checks if the merchant has enabled automatic currency switching and geolocation. * * @return bool */ public function is_using_auto_currency_switching(): bool { return 'yes' === get_option( $this->id . '_enable_auto_currency', 'no' ); } /** * Checks if the merchant has enabled the currency switcher widget. * * @return bool */ public function is_using_storefront_switcher(): bool { return 'yes' === get_option( $this->id . '_enable_storefront_switcher', 'no' ); } /** * Gets the store settings. * * @return array The store settings. */ public function get_settings() { return [ $this->id . '_enable_auto_currency' => $this->is_using_auto_currency_switching(), $this->id . '_enable_storefront_switcher' => $this->is_using_storefront_switcher(), 'site_theme' => wp_get_theme()->get( 'Name' ), 'date_format' => esc_attr( get_option( 'date_format', 'F j, Y' ) ), 'time_format' => esc_attr( get_option( 'time_format', 'g:i a' ) ), 'store_url' => esc_attr( get_page_uri( wc_get_page_id( 'shop' ) ) ), ]; } /** * Updates the store settings * * @param array $params Update requested values. * * @return void */ public function update_settings( $params ) { $updateable_options = [ 'wcpay_multi_currency_enable_auto_currency', 'wcpay_multi_currency_enable_storefront_switcher', ]; foreach ( $updateable_options as $key ) { if ( isset( $params[ $key ] ) ) { update_option( $key, sanitize_text_field( $params[ $key ] ) ); } } } /** * Load script with all required dependencies. * * @param string $handler Script handler. * @param string $script Script name relative to the plugin root. * @param array $additional_dependencies Additional dependencies. * * @return void */ public function register_script_with_dependencies( string $handler, string $script, array $additional_dependencies = [] ) { $script_file = $script . '.js'; $script_src_url = plugins_url( $script_file, $this->settings_service->get_plugin_file_path() ); $script_asset_path = plugin_dir_path( $this->settings_service->get_plugin_file_path() ) . $script . '.asset.php'; $script_asset = file_exists( $script_asset_path ) ? require $script_asset_path : [ 'dependencies' => [] ]; // nosemgrep: audit.php.lang.security.file.inclusion-arg -- server generated path is used. $all_dependencies = array_merge( $script_asset['dependencies'], $additional_dependencies ); wp_register_script( $handler, $script_src_url, $all_dependencies, $this->get_file_version( $script_file ), true ); } /** * Get the file modified time as a cache buster if we're in dev mode. * * @param string $file Local path to the file. * * @return string */ public function get_file_version( $file ) { $plugin_path = plugin_dir_path( $this->settings_service->get_plugin_file_path() ); if ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG && file_exists( $plugin_path . $file ) ) { return (string) filemtime( $plugin_path . trim( $file, '/' ) ); } return $this->settings_service->get_plugin_version(); } /** * Validates the given currency code. * * @param string $currency_code The currency code to check validity. * * @return string|false Returns back the currency code in uppercase letters if it's valid, or `false` if not. */ public function validate_currency_code( $currency_code ) { return array_key_exists( strtoupper( $currency_code ), $this->available_currencies ) ? strtoupper( $currency_code ) : false; } /** * Get simulation params from querystring and activate when needed * * @return void */ public function possible_simulation_activation() { // This is required in the MC onboarding simulation iframe. $this->simulation_params = $this->get_multi_currency_onboarding_simulation_variables(); if ( ! $this->is_simulation_enabled() ) { return; } // Modify the page links to deliver required params in the simulation. $this->add_simulation_params_to_preview_urls(); $this->simulate_client_currency(); } /** * Returns whether the simulation querystring param is set and active * * @return bool Whether the simulation is enabled or not */ public function is_simulation_enabled() { return 0 < count( $this->simulation_params ); } /** * Gets the Multi-Currency onboarding preview overrides from the querystring. * * @return array Override variables */ public function get_multi_currency_onboarding_simulation_variables() { $parameters = $_GET; // phpcs:ignore WordPress.Security.NonceVerification // Check if we are in a preview session, don't interfere with the main session. if ( ! isset( $parameters['is_mc_onboarding_simulation'] ) || ! (bool) $parameters['is_mc_onboarding_simulation'] ) { // Check if the page referer has the variables. $server = $_SERVER; // phpcs:ignore WordPress.Security.NonceVerification // Check if we are coming from a simulation session (if we don't have the necessary query strings). if ( isset( $server['HTTP_REFERER'] ) && 0 < strpos( $server['HTTP_REFERER'], 'is_mc_onboarding_simulation' ) ) { wp_parse_str( wp_parse_url( $server['HTTP_REFERER'], PHP_URL_QUERY ), $parameters ); if ( ! isset( $parameters['is_mc_onboarding_simulation'] ) || ! (bool) $parameters['is_mc_onboarding_simulation'] ) { return []; } } else { return []; } } // Define variables which can be overridden inside the preview session, with their sanitization methods. $possible_variables = [ 'enable_storefront_switcher' => 'wp_validate_boolean', 'enable_auto_currency' => 'wp_validate_boolean', ]; // Define the defaults if the parameter is missing in the request. $defaults = [ 'enable_storefront_switcher' => false, 'enable_auto_currency' => false, ]; // Prepare the params array. $values = []; // Walk through the querystring parameter possibilities, and prepare the params. foreach ( $possible_variables as $possible_variable => $sanitization_callback ) { // phpcs:disable WordPress.Security.NonceVerification if ( isset( $parameters[ $possible_variable ] ) ) { $values[ $possible_variable ] = $sanitization_callback( $parameters[ $possible_variable ] ); } else { // Append the default, the param is missing in the querystring. $values [ $possible_variable ] = $defaults[ $possible_variable ]; } } return $values; } /** * Checks if the currently displayed page is the WooCommerce Payments * settings page for the Multi-Currency settings. * * @return bool */ public function is_multi_currency_settings_page(): bool { global $current_screen, $current_tab; return ( is_admin() && $current_tab && $current_screen && 'wcpay_multi_currency' === $current_tab && 'woocommerce_page_wc-settings' === $current_screen->base ); } /** * Get all the currencies that have been used in the store. * * @return array */ public function get_all_customer_currencies(): array { global $wpdb; $currencies = get_option( self::CUSTOMER_CURRENCIES_KEY ); if ( self::is_customer_currencies_data_valid( $currencies ) ) { return array_map( 'strtoupper', $currencies ); } $currencies = $this->get_available_currencies(); $query_union = []; if ( class_exists( 'Automattic\WooCommerce\Utilities\OrderUtil' ) && \Automattic\WooCommerce\Utilities\OrderUtil::custom_orders_table_usage_is_enabled() ) { foreach ( $currencies as $currency ) { $query_union[] = $wpdb->prepare( "SELECT %s AS currency_code, EXISTS(SELECT currency FROM {$wpdb->prefix}wc_orders WHERE currency=%s LIMIT 1) AS exists_in_orders", $currency->code, $currency->code ); } } else { foreach ( $currencies as $currency ) { $query_union[] = $wpdb->prepare( "SELECT %s AS currency_code, EXISTS(SELECT meta_value FROM {$wpdb->postmeta} WHERE meta_key=%s AND meta_value=%s LIMIT 1) AS exists_in_orders", $currency->code, '_order_currency', $currency->code ); } } $sub_query = implode( ' UNION ALL ', $query_union ); $query = "SELECT currency_code FROM ( $sub_query ) as subquery WHERE subquery.exists_in_orders=1 ORDER BY currency_code ASC"; $currencies = $wpdb->get_col( $query ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared if ( self::is_customer_currencies_data_valid( $currencies ) ) { update_option( self::CUSTOMER_CURRENCIES_KEY, $currencies ); return array_map( 'strtoupper', $currencies ); } return []; } /** * Checks if there are additional currencies enabled beyond the store's default one. * * @return bool */ public function has_additional_currencies_enabled(): bool { $enabled_currencies = $this->get_enabled_currencies(); return count( $enabled_currencies ) > 1; } /** * Returns if the currency initializations are completed. * * @return bool If the initializations have been completed. */ public function is_initialized(): bool { return static::$is_initialized; } /** * Adjusts the given amount for the currently selected currency. * * Applies charm pricing if specified, and adjusts the amount according to * the selected currency's conversion rate. * * @param float $amount The original amount to adjust. * @param bool $apply_charm_pricing Optional. Whether to apply charm pricing to the adjusted amount. Default true. * @return float The amount adjusted for the selected currency. */ public function adjust_amount_for_selected_currency( $amount, $apply_charm_pricing = true ) { return $this->get_adjusted_price( $amount, $apply_charm_pricing, $this->get_selected_currency() ); } /** * Returns the amount with the backend format. * * @param float $amount The amount to format. * @param array $args The arguments to pass to wc_price. * * @return string The formatted amount. */ public function get_backend_formatted_wc_price( float $amount, array $args = [] ): string { return wc_price( $amount, $args ); } /** * Gets the price after adjusting it with the rounding and charm settings. * * @param float $price The price to be adjusted. * @param bool $apply_charm_pricing Whether charm pricing should be applied. * @param Currency $currency The currency to be used when adjusting. * * @return float The adjusted price. */ protected function get_adjusted_price( $price, $apply_charm_pricing, $currency ): float { $rounding = (float) $currency->get_rounding(); // If rounding is configured to be `0.00` we still need to round to the nearest lowest // currency denomination. // Otherwise we ceil the price to the configured rounding option. // NOTE: We don't round if currency rounding is > 0.00 because in those cases we want to // ceil the amount. For example: if $price = 1.251 and currency rounding = 0.25 we // want that amount ceiled to 1.50. If we round( 1.251 ) to 1.25 before ceiling the // price to the nearest 0.25 amount the final amount will be 1.25, which is incorrect. if ( 0.00 === $rounding ) { $num_decimals = absint( $this->localization_service->get_currency_format( $currency->get_code() )['num_decimals'] ); $price = round( $price, $num_decimals ); } else { $price = $this->ceil_price( $price, $rounding ); } if ( $apply_charm_pricing ) { $price += (float) $currency->get_charm(); } // Do not return negative prices (possible because of $currency->get_charm()). return max( 0, $price ); } /** * Ceils the price to the next number based on the rounding value. * * @param float $price The price to be ceiled. * @param float $rounding The rounding option. * * @return float The ceiled price. */ protected function ceil_price( float $price, float $rounding ): float { if ( 0.00 === $rounding ) { return $price; } return ceil( $price / $rounding ) * $rounding; } /** * Sets up the available currencies, which are alphabetical by name. * * @return void */ private function initialize_available_currencies() { // Add default store currency with a rate of 1.0. $woocommerce_currency = get_woocommerce_currency(); $this->available_currencies[ $woocommerce_currency ] = new Currency( $this->localization_service, $woocommerce_currency, 1.0 ); $available_currencies = []; $currencies = $this->get_account_available_currencies(); if ( ! empty( $currencies ) ) { $cache_data = $this->get_cached_currencies(); foreach ( $currencies as $currency_code ) { $currency_rate = $cache_data['currencies'][ $currency_code ] ?? 1.0; $update_time = $cache_data['updated'] ?? null; $new_currency = new Currency( $this->localization_service, $currency_code, $currency_rate, $update_time ); // Add this to our list of available currencies. $available_currencies[ $new_currency->get_name() ] = $new_currency; } } ksort( $available_currencies ); foreach ( $available_currencies as $currency ) { $this->available_currencies[ $currency->get_code() ] = $currency; } } /** * Sets up the enabled currencies. * * @return void */ private function initialize_enabled_currencies() { $available_currencies = $this->get_available_currencies(); $enabled_currency_codes = get_option( $this->id . '_enabled_currencies', [] ); $enabled_currency_codes = is_array( $enabled_currency_codes ) ? $enabled_currency_codes : []; $default_code = $this->get_default_currency()->get_code(); $default = []; $enabled_currency_codes[] = $default_code; // This allows to keep the alphabetical sorting by name. $enabled_currencies = array_filter( $available_currencies, function ( $currency ) use ( $enabled_currency_codes ) { return in_array( $currency->get_code(), $enabled_currency_codes, true ); } ); $this->enabled_currencies = []; foreach ( $enabled_currencies as $enabled_currency ) { // Get the charm and rounding for each enabled currency and add the currencies to the object property. $currency = clone $enabled_currency; $charm = get_option( $this->id . '_price_charm_' . $currency->get_id(), 0.00 ); $rounding = get_option( $this->id . '_price_rounding_' . $currency->get_id(), $currency->get_is_zero_decimal() ? '100' : '1.00' ); $currency->set_charm( $charm ); $currency->set_rounding( $rounding ); // If the currency is set to be manual, set the rate to the stored manual rate. $type = get_option( $this->id . '_exchange_rate_' . $currency->get_id(), 'automatic' ); if ( 'manual' === $type ) { $manual_rate = get_option( $this->id . '_manual_rate_' . $currency->get_id(), $currency->get_rate() ); $currency->set_rate( $manual_rate ); } $this->enabled_currencies[ $currency->get_code() ] = $currency; } // Set default currency to the top of the list. $default[ $default_code ] = $this->enabled_currencies[ $default_code ]; unset( $this->enabled_currencies[ $default_code ] ); $this->enabled_currencies = array_merge( $default, $this->enabled_currencies ); } /** * Sets the default currency. * * @return void */ private function set_default_currency() { $available_currencies = $this->get_available_currencies(); $this->default_currency = $available_currencies[ get_woocommerce_currency() ] ?? null; } /** * Returns the currency code stored for the user or in the session. * * @return string|null Currency code. */ private function get_stored_currency_code() { $user_id = get_current_user_id(); if ( $user_id ) { return get_user_meta( $user_id, self::CURRENCY_META_KEY, true ); } WC()->initialize_session(); $currency_code = WC()->session->get( self::CURRENCY_SESSION_KEY ); return is_string( $currency_code ) ? $currency_code : null; } /** * Checks to see if the store currency has changed. If it has, this will * also update the option containing the store currency. * * @return bool */ private function check_store_currency_for_change(): bool { $last_known_currency = get_option( $this->id . '_store_currency', false ); $woocommerce_currency = get_woocommerce_currency(); // If the last known currency was not set, update the option to set it and return false. if ( ! $last_known_currency ) { update_option( $this->id . '_store_currency', $woocommerce_currency ); return false; } if ( $last_known_currency !== $woocommerce_currency ) { update_option( $this->id . '_store_currency', $woocommerce_currency ); return true; } return false; } /** * Called when the store currency has changed. Puts any manual rate currencies into an option for a notice to display. * * @return void */ private function update_manual_rate_currencies_notice_option() { $enabled_currencies = $this->get_enabled_currencies(); $manual_currencies = []; // Check enabled currencies for manual rates. foreach ( $enabled_currencies as $currency ) { $rate_type = get_option( $this->id . '_exchange_rate_' . $currency->get_id(), false ); if ( 'manual' === $rate_type ) { $manual_currencies[] = $currency->get_name(); } } if ( 0 < count( $manual_currencies ) ) { update_option( $this->id . '_show_store_currency_changed_notice', $manual_currencies ); } } /** * Accepts an array of currencies that should have their settings removed. * * @param array $currencies Array of Currency objects or 3 letter currency codes. * * @return void */ private function remove_currencies_settings( array $currencies ) { foreach ( $currencies as $currency ) { $this->remove_currency_settings( $currency ); } } /** * Will remove a currency's settings if it is not enabled. * * @param mixed $currency Currency object or 3 letter currency code. * * @return void */ private function remove_currency_settings( $currency ) { $code = is_a( $currency, Currency::class ) ? $currency->get_code() : strtoupper( $currency ); // Bail if the currency code passed is not 3 characters, or if the currency is presently enabled. if ( 3 !== strlen( $code ) || isset( $this->get_enabled_currencies()[ $code ] ) ) { return; } $settings = [ 'price_charm', 'price_rounding', 'manual_rate', 'exchange_rate', ]; // Go through each setting and remove them. foreach ( $settings as $setting ) { delete_option( $this->id . '_' . $setting . '_' . strtolower( $code ) ); } } /** * Returns the currencies enabled for the payment provider account that are * also available in WC. * * Can be filtered with the 'wcpay_multi_currency_available_currencies' hook. * * @return array Array with the available currencies' codes. */ private function get_account_available_currencies(): array { // If the payment provider is not connected, return an empty array. This prevents using MC without being connected to the payment provider. if ( ! $this->payments_account->is_provider_connected() ) { return []; } $wc_currencies = array_keys( get_woocommerce_currencies() ); $account_currencies = $wc_currencies; $account = $this->payments_account->get_cached_account_data(); $supported_currencies = $this->payments_account->get_account_customer_supported_currencies(); if ( $account && ! empty( $supported_currencies ) ) { $account_currencies = array_map( 'strtoupper', $supported_currencies ); } /** * Filter the available currencies for WooCommerce Multi-Currency. * * This filter can be used to modify the currencies available for WC Pay * Multi-Currency. Currencies have to be added in uppercase and should * also be available in `get_woocommerce_currencies` for them to work. * * @since 2.8.0 * * @param array $available_currencies Current available currencies. Calculated based on * WC Pay's account currencies and WC currencies. */ return apply_filters( self::FILTER_PREFIX . 'available_currencies', array_intersect( $account_currencies, $wc_currencies ) ); } /** * Register the CSS and JS admin scripts. * * @return void */ private function register_admin_scripts() { $this->register_script_with_dependencies( 'WCPAY_MULTI_CURRENCY_SETTINGS', 'dist/multi-currency', [ 'WCPAY_ADMIN_SETTINGS', 'wp-components' ] ); wp_register_style( 'WCPAY_MULTI_CURRENCY_SETTINGS', plugins_url( 'dist/multi-currency.css', $this->settings_service->get_plugin_file_path() ), [ 'wc-components', 'WCPAY_ADMIN_SETTINGS' ], $this->get_file_version( 'dist/multi-currency.css' ), 'all' ); } /** * Enables simulation of client browser currency. * * @return void */ private function simulate_client_currency() { if ( ! $this->simulation_params['enable_auto_currency'] ) { return; } $countries = $this->payments_account->get_supported_countries(); $predefined_simulation_currencies = [ 'USD' => $countries['US'], 'GBP' => $countries['GB'], ]; $simulation_currency = 'USD' === get_option( 'woocommerce_currency', 'USD' ) ? 'GBP' : 'USD'; $simulation_currency_name = $this->available_currencies[ $simulation_currency ]->get_name(); $simulation_country = $predefined_simulation_currencies[ $simulation_currency ]; // Simulate client currency from geolocation. add_filter( 'wcpay_multi_currency_override_notice_currency_name', function ( $selected_currency_name ) use ( $simulation_currency_name ) { return $simulation_currency_name; } ); // Simulate client country from geolocation. add_filter( 'wcpay_multi_currency_override_notice_country', function ( $selected_country ) use ( $simulation_country ) { return $simulation_country; } ); // Always display the notice on simulation screen, prevent duplicate hooks. if ( ! has_action( 'wp_footer', [ $this, 'display_geolocation_currency_update_notice' ] ) ) { add_action( 'wp_footer', [ $this, 'display_geolocation_currency_update_notice' ] ); } // Skip recalculating the cart to prevent infinite loop in simulation. remove_action( 'wp_loaded', [ $this, 'recalculate_cart' ] ); } /** * Adds the required querystring parameters to all urls in preview pages. * * @return void */ private function add_simulation_params_to_preview_urls() { $params = $this->simulation_params; add_filter( 'wp_footer', function () use ( $params ) { ?> <script type="text/javascript" id="wcpay_multi_currency-simulation-script"> // Add simulation overrides to all links. document.querySelectorAll('a').forEach((link) => { const parsedURL = new URL(link.href); if ( false === parsedURL.searchParams.has( 'is_mc_onboarding_simulation' ) ) { parsedURL.searchParams.set('is_mc_onboarding_simulation', true); parsedURL.searchParams.set('enable_auto_currency', <?php echo esc_attr( $params['enable_auto_currency'] ? 'true' : 'false' ); ?>); parsedURL.searchParams.set('enable_storefront_switcher', <?php echo esc_attr( $params['enable_storefront_switcher'] ? 'true' : 'false' ); ?>); link.href = parsedURL.toString(); } }); // Unhide the store notice in simulation mode. document.addEventListener('DOMContentLoaded', () => { const noticeElement = document.querySelector('.woocommerce-store-notice.demo_store') if( noticeElement ) { const noticeId = noticeElement.getAttribute('data-notice-id'); cookieStore.delete( 'store_notice' + noticeId ); } }); </script> <?php } ); } /** * Logs a message and throws InvalidCurrencyException. * * @param string $method The method that's actually throwing the exception. * @param string $currency_code The currency code that was invalid. * @param int $code The exception code. * * @throws InvalidCurrencyException */ private function log_and_throw_invalid_currency_exception( $method, $currency_code, $code = 500 ) { $message = 'Invalid currency passed to ' . $method . ': ' . $currency_code; Logger::error( $message ); throw new InvalidCurrencyException( esc_html( $message ), esc_html( $code ) ); } /** * Checks if the customer currencies data is valid. * * @param mixed $currencies The currencies to check. * * @return bool */ private function is_customer_currencies_data_valid( $currencies ) { return ! empty( $currencies ) && is_array( $currencies ); } }
| ver. 1.4 |
Github
|
.
| PHP 8.1.33 | Генерация страницы: 0 |
proxy
|
phpinfo
|
Настройка