You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
457 lines
13 KiB
457 lines
13 KiB
<?php
|
|
|
|
use WCML\MultiCurrency\ExchangeRateServices\Service;
|
|
use WPML\FP\Obj;
|
|
|
|
/**
|
|
* Class WCML_Exchange_Rates
|
|
*/
|
|
class WCML_Exchange_Rates {
|
|
|
|
/** @var woocommerce_wpml */
|
|
private $woocommerce_wpml;
|
|
/** @var array */
|
|
private $services = [];
|
|
/** @var array */
|
|
private $settings;
|
|
/** @var WP_Locale|mixed */
|
|
private $wp_locale;
|
|
|
|
const CRONJOB_EVENT = 'wcml_exchange_rates_update';
|
|
const DIGITS_AFTER_DECIMAL_POINT = 6;
|
|
const KEY_RATES_UPDATED_FLAG = 'wcml_exchange_rates_manually_updated';
|
|
|
|
/**
|
|
* @param woocommerce_wpml $woocommerce_wpml
|
|
* @param WP_Locale|mixed $wp_locale
|
|
*/
|
|
public function __construct( woocommerce_wpml $woocommerce_wpml, $wp_locale ) {
|
|
$this->woocommerce_wpml = $woocommerce_wpml;
|
|
$this->wp_locale = $wp_locale;
|
|
}
|
|
|
|
/**
|
|
* Please use `make( WCML_Exchange_Rates::class )` to get the instance of this class.
|
|
*
|
|
* @return WCML_Exchange_Rates
|
|
*/
|
|
public static function create() {
|
|
/**
|
|
* @var woocommerce_wpml $woocommerce_wpml
|
|
* @var WP_Locale|mixed $wp_locale
|
|
*/
|
|
global $woocommerce_wpml, $wp_locale;
|
|
|
|
return new self( $woocommerce_wpml, $wp_locale );
|
|
}
|
|
|
|
public function add_actions() {
|
|
if ( is_admin() ) {
|
|
add_action( 'wcml_saved_mc_options', [ $this, 'update_exchange_rate_options' ] ); // before init
|
|
}
|
|
add_action( 'init', [ $this, 'init' ] );
|
|
}
|
|
|
|
public function init() {
|
|
if ( $this->woocommerce_wpml->multi_currency->get_currencies() ) {
|
|
if ( is_admin() ) {
|
|
add_action( 'wp_ajax_wcml_update_exchange_rates', [ $this, 'update_exchange_rates_ajax' ] );
|
|
}
|
|
add_filter( 'cron_schedules', [ $this, 'cron_schedules' ] );
|
|
/* @phpstan-ignore-next-line */
|
|
add_action( self::CRONJOB_EVENT, [ $this, 'update_exchange_rates' ] );
|
|
}
|
|
}
|
|
|
|
public function initialize_settings() {
|
|
if ( ! isset( $this->woocommerce_wpml->settings['multi_currency']['exchange_rates'] ) ) {
|
|
$this->settings = [
|
|
'automatic' => 0,
|
|
'service' => 'currencylayer',
|
|
'lifting_charge' => 0,
|
|
'schedule' => 'manual',
|
|
'week_day' => 1,
|
|
'month_day' => 1,
|
|
];
|
|
$this->save_settings();
|
|
} else {
|
|
$this->settings =& $this->woocommerce_wpml->settings['multi_currency']['exchange_rates'];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @return array
|
|
*/
|
|
public function get_services() {
|
|
return $this->services;
|
|
}
|
|
|
|
/**
|
|
* @param string $service_id
|
|
* @param Service $service
|
|
*/
|
|
public function add_service( $service_id, $service ) {
|
|
$this->services[ $service_id ] = $service;
|
|
}
|
|
|
|
/**
|
|
* @return array
|
|
*/
|
|
public function get_settings() {
|
|
return $this->settings;
|
|
}
|
|
|
|
/**
|
|
* @param string $key
|
|
*
|
|
* @return mixed|null
|
|
*/
|
|
public function get_setting( $key ) {
|
|
return isset( $this->settings[ $key ] ) ? $this->settings[ $key ] : null;
|
|
}
|
|
|
|
public function save_settings() {
|
|
$this->woocommerce_wpml->settings['multi_currency']['exchange_rates'] = $this->settings;
|
|
$this->woocommerce_wpml->update_settings();
|
|
}
|
|
|
|
/**
|
|
* @param string $key
|
|
* @param mixed $value
|
|
*/
|
|
public function save_setting( $key, $value ) {
|
|
$this->settings[ $key ] = $value;
|
|
$this->save_settings();
|
|
}
|
|
|
|
public function update_exchange_rates_ajax() {
|
|
$response = [];
|
|
if ( wp_create_nonce( 'update-exchange-rates' ) === $_POST['wcml_nonce'] ) {
|
|
try {
|
|
$rates = $this->update_exchange_rates();
|
|
$response['success'] = 1;
|
|
$response['last_updated'] = date_i18n( 'F j, Y g:i a', $this->settings['last_updated'] );
|
|
$response['rates'] = $rates;
|
|
} catch ( Exception $e ) {
|
|
$response['success'] = 0;
|
|
$response['error'] = $e->getMessage();
|
|
$response['service'] = $this->settings['service'];
|
|
}
|
|
} else {
|
|
$response['success'] = 0;
|
|
$response['error'] = 'Invalid nonce';
|
|
}
|
|
wp_send_json( $response );
|
|
}
|
|
|
|
/**
|
|
* @return array
|
|
* @throws Exception
|
|
*/
|
|
public function update_exchange_rates() {
|
|
$currencies = $this->woocommerce_wpml->multi_currency->get_currency_codes();
|
|
$rates = $this->fetch_exchange_rates_from_active_service( $currencies );
|
|
|
|
foreach ( $rates as $to => $rate ) {
|
|
if ( $rate && is_numeric( $rate ) ) {
|
|
$this->save_exchage_rate( $to, $rate );
|
|
}
|
|
}
|
|
|
|
$this->settings['last_updated'] = current_time( 'timestamp' );
|
|
$this->save_settings();
|
|
|
|
return $rates;
|
|
}
|
|
|
|
/**
|
|
* @param array $currencies
|
|
*
|
|
* @return array
|
|
* @throws Exception
|
|
*/
|
|
public function fetch_exchange_rates_from_active_service( $currencies ) {
|
|
if ( ! isset( $this->services[ $this->settings['service'] ] ) ) {
|
|
throw new Exception( 'The exchange rate service "' . $this->settings['service'] . '" is not defined.' );
|
|
}
|
|
|
|
/** @var Service $service */
|
|
$service = $this->get_current_service();
|
|
|
|
$default_currency = wcml_get_woocommerce_currency_option();
|
|
$secondary_currencies = array_diff( $currencies, [ $default_currency ] );
|
|
|
|
try {
|
|
$rates = $service->getRates( $default_currency, $secondary_currencies );
|
|
} catch ( Exception $e ) {
|
|
if ( defined( 'WP_DEBUG_LOG' ) && WP_DEBUG_LOG ) {
|
|
error_log( 'Exchange rates update error (' . $this->settings['service'] . '): ' . $e->getMessage() );
|
|
}
|
|
throw new Exception( $e->getMessage() );
|
|
}
|
|
|
|
$this->apply_lifting_charge( $rates );
|
|
|
|
return $rates;
|
|
}
|
|
|
|
public function apply_lifting_charge( &$rates ) {
|
|
foreach ( $rates as $k => $rate ) {
|
|
$rates[ $k ] = round( $rate * ( 1 + $this->settings['lifting_charge'] / 100 ), self::DIGITS_AFTER_DECIMAL_POINT );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param string $currency
|
|
* @param string $rate
|
|
*/
|
|
private function save_exchage_rate( $currency, $rate ) {
|
|
$this->woocommerce_wpml->settings['currency_options'][ $currency ]['previous_rate'] =
|
|
$this->woocommerce_wpml->settings['currency_options'][ $currency ]['rate'];
|
|
$this->woocommerce_wpml->settings['currency_options'][ $currency ]['rate'] = $rate;
|
|
$this->woocommerce_wpml->update_settings();
|
|
}
|
|
|
|
/**
|
|
* @param string $currency
|
|
*
|
|
* @return mixed
|
|
*/
|
|
public function get_currency_rate( $currency ) {
|
|
return $this->woocommerce_wpml->settings['currency_options'][ $currency ]['rate'];
|
|
}
|
|
|
|
/**
|
|
* @param array $post_data
|
|
*/
|
|
public function update_exchange_rate_options( $post_data ) {
|
|
|
|
if ( isset( $post_data['exchange-rates-automatic'] ) && $post_data['exchange-rates-automatic'] ) {
|
|
$active_service_changed = false;
|
|
$active_key_changed = false;
|
|
$active_service_id = Obj::prop( 'service', $this->settings );
|
|
$active_service = Obj::prop( $active_service_id, $this->services );
|
|
|
|
$this->settings['automatic'] = (int) $post_data['exchange-rates-automatic'];
|
|
|
|
if ( isset( $post_data['exchange-rates-service'] ) ) {
|
|
|
|
// clear errors for replaced service
|
|
if ( isset( $this->services[ $this->settings['service'] ] ) && $post_data['exchange-rates-service'] !== $this->settings['service'] ) {
|
|
$this->services[ $this->settings['service'] ]->clearLastError();
|
|
}
|
|
|
|
$this->settings['service'] = sanitize_text_field( $post_data['exchange-rates-service'] );
|
|
$active_service_changed = $active_service_id !== $this->settings['service'];
|
|
}
|
|
|
|
if ( isset( $post_data['services'] ) ) {
|
|
$active_service_key = $active_service ? $active_service->getSetting( 'api-key' ) : '';
|
|
|
|
foreach ( $post_data['services'] as $service_id => $service_data ) {
|
|
if ( isset( $service_data['api-key'] ) ) {
|
|
$this->services[ $service_id ]->saveSetting( 'api-key', $service_data['api-key'] );
|
|
|
|
if ( $service_id === $active_service_id ) {
|
|
$active_key_changed = $active_service_key !== $service_data['api-key'];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
$this->settings['lifting_charge'] = is_numeric( $post_data['lifting_charge'] ) ? $post_data['lifting_charge'] : 0;
|
|
|
|
if ( isset( $post_data['update-schedule'] ) ) {
|
|
$this->settings['schedule'] = sanitize_text_field( $post_data['update-schedule'] );
|
|
}
|
|
|
|
if ( isset( $post_data['update-time'] ) ) {
|
|
$this->settings['time'] = sanitize_text_field( $post_data['update-time'] );
|
|
}
|
|
|
|
if ( isset( $post_data['update-weekly-day'] ) ) {
|
|
$this->settings['week_day'] = sanitize_text_field( $post_data['update-weekly-day'] );
|
|
}
|
|
|
|
if ( isset( $post_data['update-monthly-day'] ) ) {
|
|
$this->settings['month_day'] = sanitize_text_field( $post_data['update-monthly-day'] );
|
|
}
|
|
|
|
if ( $this->settings['schedule'] === 'manual' ) {
|
|
$this->delete_update_cronjob();
|
|
} else {
|
|
$this->enable_update_cronjob();
|
|
}
|
|
|
|
if ( $active_key_changed || $active_service_changed ) {
|
|
$currentService = $this->get_current_service();
|
|
|
|
if ( $currentService ) {
|
|
$currentService->resetConnectionCache();
|
|
}
|
|
|
|
add_action( 'init', [ $this, 'update_rates_on_service_or_key_changed' ], 5 );
|
|
}
|
|
} else {
|
|
$this->settings['automatic'] = 0;
|
|
$this->delete_update_cronjob();
|
|
}
|
|
|
|
$this->save_settings();
|
|
}
|
|
|
|
public function update_rates_on_service_or_key_changed() {
|
|
try {
|
|
$this->update_exchange_rates();
|
|
$this->woocommerce_wpml->get_multi_currency()->init_currencies(); // Re-init currencies.
|
|
wp_cache_add( self::KEY_RATES_UPDATED_FLAG, true );
|
|
add_action( 'shutdown', function() {
|
|
wp_cache_delete( WCML_Exchange_Rates::KEY_RATES_UPDATED_FLAG );
|
|
} );
|
|
} catch ( \Exception $e ) {} // Exception is handled inside `update_exchange_rates`.
|
|
}
|
|
|
|
public function enable_update_cronjob() {
|
|
$schedule = wp_get_schedule( self::CRONJOB_EVENT );
|
|
|
|
if ( $schedule !== $this->settings['schedule'] ) {
|
|
$this->delete_update_cronjob();
|
|
}
|
|
|
|
if ( 'monthly' === $this->settings['schedule'] ) {
|
|
$time_offset = $this->get_monthly_schedule_time_offset();
|
|
$schedule = 'wcml_' . $this->settings['schedule'] . '_on_' . $this->settings['month_day'];
|
|
} elseif ( 'weekly' === $this->settings['schedule'] ) {
|
|
$time_offset = $this->get_weekly_schedule_time_offset();
|
|
$schedule = 'wcml_' . $this->settings['schedule'] . '_on_' . $this->settings['week_day'];
|
|
|
|
} else {
|
|
$time_offset = time();
|
|
$schedule = $this->settings['schedule'];
|
|
}
|
|
|
|
if ( ! wp_next_scheduled( self::CRONJOB_EVENT ) ) {
|
|
wp_schedule_event( $time_offset, $schedule, self::CRONJOB_EVENT );
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* @return int
|
|
*/
|
|
private function get_monthly_schedule_time_offset() {
|
|
$current_day = date( 'j' );
|
|
$days_in_current_month = cal_days_in_month( CAL_GREGORIAN, date( 'n' ), date( 'Y' ) );
|
|
|
|
if ( $this->settings['month_day'] >= $current_day && $this->settings['month_day'] <= $days_in_current_month ) {
|
|
$days = $this->settings['month_day'] - $current_day;
|
|
} else {
|
|
$days = $days_in_current_month - $current_day + $this->settings['month_day'];
|
|
}
|
|
|
|
$time_offset = time() + $days * DAY_IN_SECONDS;
|
|
|
|
return $time_offset;
|
|
}
|
|
|
|
/**
|
|
* @return int
|
|
*/
|
|
private function get_weekly_schedule_time_offset() {
|
|
$current_day = date( 'w' );
|
|
if ( $this->settings['week_day'] >= $current_day ) {
|
|
$days = $this->settings['week_day'] - $current_day;
|
|
} else {
|
|
$days = 7 - $current_day + $this->settings['week_day'];
|
|
}
|
|
|
|
$time_offset = time() + $days * DAY_IN_SECONDS;
|
|
|
|
return $time_offset;
|
|
}
|
|
|
|
public function delete_update_cronjob() {
|
|
wp_clear_scheduled_hook( self::CRONJOB_EVENT );
|
|
}
|
|
|
|
/**
|
|
* @param array $schedules
|
|
*
|
|
* @return array
|
|
*/
|
|
public function cron_schedules( $schedules ) {
|
|
|
|
if ( 'monthly' === $this->settings['schedule'] ) {
|
|
|
|
$month_day = $this->get_month_day_formatted();
|
|
$current_month = date( 'n' );
|
|
$days_in_current_month = cal_days_in_month( CAL_GREGORIAN, $current_month, date( 'Y' ) );
|
|
if ( $this->settings['month_day'] <= $days_in_current_month && $this->settings['month_day'] >= date( 'j' ) ) {
|
|
$interval = DAY_IN_SECONDS * $days_in_current_month;
|
|
} else {
|
|
$month_number = 12 === (int) $current_month ? 1 : $current_month + 1;
|
|
$year_number = 12 === (int) $current_month ? date( 'Y' ) + 1 : date( 'Y' );
|
|
$interval = DAY_IN_SECONDS * cal_days_in_month( CAL_GREGORIAN, $month_number, $year_number );
|
|
}
|
|
|
|
$schedules[ 'wcml_monthly_on_' . $this->settings['month_day'] ] = [
|
|
'interval' => $interval,
|
|
/* translators: %s is the month day */
|
|
'display' => sprintf( __( 'Monthly on the %s', 'woocommerce-multilingual' ), $month_day ),
|
|
];
|
|
|
|
} elseif ( 'weekly' === $this->settings['schedule'] ) {
|
|
|
|
$week_day = $this->wp_locale->get_weekday( $this->settings['week_day'] );
|
|
$schedules[ 'wcml_weekly_on_' . $this->settings['week_day'] ] = [
|
|
'interval' => WEEK_IN_SECONDS,
|
|
/* translators: %s is the week day */
|
|
'display' => sprintf( __( 'Weekly on %s', 'woocommerce-multilingual' ), $week_day ),
|
|
];
|
|
|
|
}
|
|
|
|
return $schedules;
|
|
}
|
|
|
|
private function get_month_day_formatted() {
|
|
$month_day = $this->settings['month_day'];
|
|
switch ( $month_day ) {
|
|
case 1:
|
|
$month_day .= 'st';
|
|
break;
|
|
case 2:
|
|
$month_day .= 'nd';
|
|
break;
|
|
case 3:
|
|
$month_day .= 'rd';
|
|
break;
|
|
default:
|
|
$month_day .= 'th';
|
|
break;
|
|
}
|
|
return $month_day;
|
|
}
|
|
|
|
/**
|
|
* @return Service|null
|
|
*/
|
|
private function get_current_service() {
|
|
return Obj::prop( Obj::prop( 'service', (array) $this->settings ), (array) $this->services );
|
|
}
|
|
|
|
/**
|
|
* Check if a service is defined and has a key if needed.
|
|
*
|
|
* @return bool
|
|
*/
|
|
public function is_current_service_actionable() {
|
|
$current_service = $this->get_current_service();
|
|
|
|
return $current_service
|
|
&& (
|
|
! $current_service->isKeyRequired()
|
|
|| $current_service->getSetting( 'api-key' )
|
|
);
|
|
}
|
|
}
|
|
|