PluginProbe ʕ •ᴥ•ʔ
WPML Multilingual & Multicurrency for WooCommerce / 5.5.7
WPML Multilingual & Multicurrency for WooCommerce v5.5.7
5.5.7 5.3.8 5.3.9 5.4.3 5.4.4 5.4.5 5.5.1 5.5.1.1 5.5.2.2 5.5.2.3 5.5.3 5.5.3.1 5.5.4 5.5.5 5.5.6 trunk 0.9 1.0 1.1 1.2 1.3 1.4 1.5 2.0 2.2 2.3 2.3.1 2.3.2 3.0 3.0.1 3.1 3.2 3.2.1 3.2.2 3.3 3.3.1 3.3.2 3.3.3 3.3.4 3.4 3.4.1 3.4.2 3.4.3 3.5 3.5.1 3.5.2 3.5.3 3.5.4 3.5.5 3.6 3.6.1 3.6.10 3.6.11 3.6.2 3.6.3 3.6.4 3.6.5 3.6.5.1 3.6.6 3.6.7 3.6.8 3.6.9 3.7 3.7.1 3.7.10 3.7.11 3.7.12 3.7.13 3.7.14 3.7.15 3.7.16 3.7.2 3.7.3 3.7.4 3.7.5 3.7.6 3.7.7 3.7.8 3.7.9 3.8.0 3.8.1 3.8.2 3.8.3 3.8.4 3.8.5 3.8.6 3.9.0 3.9.1 3.9.1.1 3.9.2 3.9.3 3.9.4 3.9.5 4.0.0 4.0.1 4.0.2 4.0.3 4.0.4 4.1.0 4.1.1 4.1.2 4.1.3 4.1.4 4.10.0 4.10.1 4.10.2 4.10.3 4.10.4 4.11.2 4.11.3.2 4.11.5 4.11.6 4.12.1 4.12.4 4.12.5 4.12.6 4.2.0 4.2.0.1 4.2.1 4.2.1.1 4.2.10 4.2.2 4.2.3 4.2.4 4.2.5 4.2.6 4.2.7 4.2.7.1 4.2.8 4.2.8.1 4.2.9 4.3.0 4.3.1 4.3.2 4.3.2.1 4.3.3 4.3.4 4.3.5 4.3.6 4.3.7 4.4.0 4.4.1 4.4.2 4.4.2.1 4.5.0 4.6.0 4.6.1 4.6.2 4.6.2.1 4.6.3 4.6.5 4.6.6 4.6.7 4.7.0 4.7.1 4.7.2 4.7.3 4.7.4 4.7.5 4.7.6 4.7.7 4.7.8 4.7.9 4.8.0 4.9.0 4.9.1 5.0.1 5.0.2 5.1.1 5.1.2 5.1.3 5.2.0 5.2.1 5.3.2 5.3.3.1 5.3.4 5.3.5 5.3.6 5.3.7
woocommerce-multilingual / classes / multi-currency / class-wcml-exchange-rates.php
woocommerce-multilingual / classes / multi-currency Last commit date
exchange-rate-services 4 weeks ago geolocation 4 weeks ago payment-gateways 4 weeks ago class-wcml-exchange-rates.php 4 weeks ago
class-wcml-exchange-rates.php
379 lines
1 <?php
2
3 use WCML\MultiCurrency\ExchangeRateServices\Service;
4 use WPML\FP\Obj;
5
6 class WCML_Exchange_Rates {
7
8 private $woocommerce_wpml;
9 private $services = [];
10 private $settings;
11 private $wp_locale;
12
13 const CRONJOB_EVENT = 'wcml_exchange_rates_update';
14 const DIGITS_AFTER_DECIMAL_POINT = 6;
15 const KEY_RATES_UPDATED_FLAG = 'wcml_exchange_rates_manually_updated';
16
17 public function __construct( woocommerce_wpml $woocommerce_wpml, $wp_locale ) {
18 $this->woocommerce_wpml = $woocommerce_wpml;
19 $this->wp_locale = $wp_locale;
20 }
21
22 public static function create() {
23 global $woocommerce_wpml, $wp_locale;
24
25 return new self( $woocommerce_wpml, $wp_locale );
26 }
27
28 public function add_actions() {
29 if ( is_admin() ) {
30 add_action( 'wcml_saved_mc_options', [ $this, 'update_exchange_rate_options' ] );
31 }
32 add_action( 'init', [ $this, 'init' ] );
33 }
34
35 public function init() {
36 if ( $this->woocommerce_wpml->multi_currency->get_currencies() ) {
37 if ( is_admin() ) {
38 add_action( 'wp_ajax_wcml_update_exchange_rates', [ $this, 'update_exchange_rates_ajax' ] );
39 }
40 add_filter( 'cron_schedules', [ $this, 'cron_schedules' ] );
41 add_action( self::CRONJOB_EVENT, [ $this, 'action_update_exchange_rates' ] );
42 }
43 }
44
45 public function initialize_settings() {
46 if ( ! isset( $this->woocommerce_wpml->settings['multi_currency']['exchange_rates'] ) ) {
47 $this->settings = [
48 'automatic' => 0,
49 'service' => 'currencylayer',
50 'lifting_charge' => 0,
51 'schedule' => 'manual',
52 'week_day' => 1,
53 'month_day' => 1,
54 ];
55 $this->save_settings();
56 } else {
57 $this->settings =& $this->woocommerce_wpml->settings['multi_currency']['exchange_rates'];
58 }
59 }
60
61 public function get_services() {
62 return $this->services;
63 }
64
65 public function add_service( $service_id, $service ) {
66 $this->services[ $service_id ] = $service;
67 }
68
69 public function get_settings() {
70 return $this->settings;
71 }
72
73 public function get_setting( $key ) {
74 return $this->settings[ $key ] ?? null;
75 }
76
77 public function save_settings() {
78 $this->woocommerce_wpml->settings['multi_currency']['exchange_rates'] = $this->settings;
79 $this->woocommerce_wpml->update_settings();
80 }
81
82 public function save_setting( $key, $value ) {
83 $this->settings[ $key ] = $value;
84 $this->save_settings();
85 }
86
87 public function update_exchange_rates_ajax() {
88 $response = [];
89 if ( wp_create_nonce( 'update-exchange-rates' ) === $_POST['wcml_nonce'] ) {
90 try {
91 $rates = $this->update_exchange_rates();
92 $response['success'] = 1;
93 $response['last_updated'] = date_i18n( 'F j, Y g:i a', $this->settings['last_updated'] );
94 $response['rates'] = $rates;
95 } catch ( Exception $e ) {
96 $response['success'] = 0;
97 $response['error'] = $e->getMessage();
98 $response['service'] = $this->settings['service'];
99 }
100 } else {
101 $response['success'] = 0;
102 $response['error'] = 'Invalid nonce';
103 }
104 wp_send_json( $response );
105 }
106
107 public function update_exchange_rates() {
108 $currencies = $this->woocommerce_wpml->multi_currency->get_currency_codes();
109 $rates = $this->fetch_exchange_rates_from_active_service( $currencies );
110
111 foreach ( $rates as $to => $rate ) {
112 if ( $rate && is_numeric( $rate ) ) {
113 $this->save_exchage_rate( $to, $rate );
114 }
115 }
116
117 $this->settings['last_updated'] = current_time( 'timestamp' );
118 $this->save_settings();
119
120 return $rates;
121 }
122
123 public function action_update_exchange_rates() {
124 $this->update_exchange_rates();
125 }
126
127 public function fetch_exchange_rates_from_active_service( $currencies ) {
128 if ( ! isset( $this->services[ $this->settings['service'] ] ) ) {
129 throw new Exception( 'The exchange rate service "' . $this->settings['service'] . '" is not defined.' );
130 }
131
132 $service = $this->get_current_service();
133
134 $default_currency = wcml_get_woocommerce_currency_option();
135 $secondary_currencies = array_diff( $currencies, [ $default_currency ] );
136
137 try {
138 $rates = $service->getRates( $default_currency, $secondary_currencies );
139 } catch ( Exception $e ) {
140 if ( defined( 'WP_DEBUG_LOG' ) && WP_DEBUG_LOG ) {
141 error_log( 'Exchange rates update error (' . $this->settings['service'] . '): ' . $e->getMessage() );
142 }
143 throw new Exception( $e->getMessage() );
144 }
145
146 $this->apply_lifting_charge( $rates );
147
148 return $rates;
149 }
150
151 public function apply_lifting_charge( &$rates ) {
152 foreach ( $rates as $k => $rate ) {
153 $rates[ $k ] = round( $rate * ( 1 + $this->settings['lifting_charge'] / 100 ), self::DIGITS_AFTER_DECIMAL_POINT );
154 }
155 }
156
157 private function save_exchage_rate( $currency, $rate ) {
158 $this->woocommerce_wpml->settings['currency_options'][ $currency ]['previous_rate'] =
159 $this->woocommerce_wpml->settings['currency_options'][ $currency ]['rate'];
160 $this->woocommerce_wpml->settings['currency_options'][ $currency ]['rate'] = $rate;
161 $this->woocommerce_wpml->update_settings();
162 }
163
164 public function get_currency_rate( $currency ) {
165 return $this->woocommerce_wpml->settings['currency_options'][ $currency ]['rate'];
166 }
167
168 public function update_exchange_rate_options( $post_data ) {
169
170 if ( isset( $post_data['exchange-rates-automatic'] ) && $post_data['exchange-rates-automatic'] ) {
171 $active_service_changed = false;
172 $active_key_changed = false;
173 $active_service_id = Obj::prop( 'service', $this->settings );
174 $active_service = Obj::prop( $active_service_id, $this->services );
175
176 $this->settings['automatic'] = (int) $post_data['exchange-rates-automatic'];
177
178 if ( isset( $post_data['exchange-rates-service'] ) ) {
179
180 if ( isset( $this->services[ $this->settings['service'] ] ) && $post_data['exchange-rates-service'] !== $this->settings['service'] ) {
181 $this->services[ $this->settings['service'] ]->clearLastError();
182 }
183
184 $this->settings['service'] = sanitize_text_field( $post_data['exchange-rates-service'] );
185 $active_service_changed = $active_service_id !== $this->settings['service'];
186 }
187
188 if ( isset( $post_data['services'] ) ) {
189 $active_service_key = $active_service ? $active_service->getSetting( 'api-key' ) : '';
190
191 foreach ( $post_data['services'] as $service_id => $service_data ) {
192 if ( isset( $service_data['api-key'] ) ) {
193 $this->services[ $service_id ]->saveSetting( 'api-key', $service_data['api-key'] );
194
195 if ( $service_id === $active_service_id ) {
196 $active_key_changed = $active_service_key !== $service_data['api-key'];
197 }
198 }
199 }
200 }
201
202 $this->settings['lifting_charge'] = is_numeric( $post_data['lifting_charge'] ) ? $post_data['lifting_charge'] : 0;
203
204 if ( isset( $post_data['update-schedule'] ) ) {
205 $this->settings['schedule'] = sanitize_text_field( $post_data['update-schedule'] );
206 }
207
208 if ( isset( $post_data['update-time'] ) ) {
209 $this->settings['time'] = sanitize_text_field( $post_data['update-time'] );
210 }
211
212 if ( isset( $post_data['update-weekly-day'] ) ) {
213 $this->settings['week_day'] = sanitize_text_field( $post_data['update-weekly-day'] );
214 }
215
216 if ( isset( $post_data['update-monthly-day'] ) ) {
217 $this->settings['month_day'] = sanitize_text_field( $post_data['update-monthly-day'] );
218 }
219
220 if ( $this->settings['schedule'] === 'manual' ) {
221 $this->delete_update_cronjob();
222 } else {
223 $this->enable_update_cronjob();
224 }
225
226 if ( $active_key_changed || $active_service_changed ) {
227 $currentService = $this->get_current_service();
228
229 if ( $currentService ) {
230 $currentService->resetConnectionCache();
231 }
232
233 add_action( 'init', [ $this, 'update_rates_on_service_or_key_changed' ], 5 );
234 }
235 } else {
236 $this->settings['automatic'] = 0;
237 $this->delete_update_cronjob();
238 }
239
240 $this->save_settings();
241 }
242
243 public function update_rates_on_service_or_key_changed() {
244 try {
245 $this->update_exchange_rates();
246 $this->woocommerce_wpml->get_multi_currency()->init_currencies();
247 wp_cache_add( self::KEY_RATES_UPDATED_FLAG, true );
248 add_action( 'shutdown', function() {
249 wp_cache_delete( WCML_Exchange_Rates::KEY_RATES_UPDATED_FLAG );
250 } );
251 } catch ( \Exception $e ) {}
252 }
253
254 public function enable_update_cronjob() {
255 $schedule = wp_get_schedule( self::CRONJOB_EVENT );
256
257 if ( $schedule !== $this->settings['schedule'] ) {
258 $this->delete_update_cronjob();
259 }
260
261 if ( 'monthly' === $this->settings['schedule'] ) {
262 $time_offset = $this->get_monthly_schedule_time_offset();
263 $schedule = 'wcml_' . $this->settings['schedule'] . '_on_' . $this->settings['month_day'];
264 } elseif ( 'weekly' === $this->settings['schedule'] ) {
265 $time_offset = $this->get_weekly_schedule_time_offset();
266 $schedule = 'wcml_' . $this->settings['schedule'] . '_on_' . $this->settings['week_day'];
267
268 } else {
269 $time_offset = time();
270 $schedule = $this->settings['schedule'];
271 }
272
273 if ( ! wp_next_scheduled( self::CRONJOB_EVENT ) ) {
274 wp_schedule_event( $time_offset, $schedule, self::CRONJOB_EVENT );
275 }
276
277 }
278
279 private function get_monthly_schedule_time_offset() {
280 $current_day = date( 'j' );
281 $days_in_current_month = cal_days_in_month( CAL_GREGORIAN, (int) date( 'n' ), (int) date( 'Y' ) );
282
283 if ( $this->settings['month_day'] >= $current_day && $this->settings['month_day'] <= $days_in_current_month ) {
284 $days = $this->settings['month_day'] - $current_day;
285 } else {
286 $days = $days_in_current_month - $current_day + $this->settings['month_day'];
287 }
288
289 $time_offset = time() + $days * DAY_IN_SECONDS;
290
291 return $time_offset;
292 }
293
294 private function get_weekly_schedule_time_offset() {
295 $current_day = date( 'w' );
296 if ( $this->settings['week_day'] >= $current_day ) {
297 $days = $this->settings['week_day'] - $current_day;
298 } else {
299 $days = 7 - $current_day + $this->settings['week_day'];
300 }
301
302 $time_offset = time() + $days * DAY_IN_SECONDS;
303
304 return $time_offset;
305 }
306
307 public function delete_update_cronjob() {
308 wp_clear_scheduled_hook( self::CRONJOB_EVENT );
309 }
310
311 public function cron_schedules( $schedules ) {
312
313 if ( 'monthly' === $this->settings['schedule'] ) {
314
315 $month_day = $this->get_month_day_formatted();
316 $current_month = date( 'n' );
317 $days_in_current_month = cal_days_in_month( CAL_GREGORIAN, $current_month, date( 'Y' ) );
318 if ( $this->settings['month_day'] <= $days_in_current_month && $this->settings['month_day'] >= date( 'j' ) ) {
319 $interval = DAY_IN_SECONDS * $days_in_current_month;
320 } else {
321 $month_number = 12 === (int) $current_month ? 1 : $current_month + 1;
322 $year_number = 12 === (int) $current_month ? date( 'Y' ) + 1 : date( 'Y' );
323 $interval = DAY_IN_SECONDS * cal_days_in_month( CAL_GREGORIAN, $month_number, $year_number );
324 }
325
326 $schedules[ 'wcml_monthly_on_' . $this->settings['month_day'] ] = [
327 'interval' => $interval,
328 /* translators: %s is the month day */
329 'display' => sprintf( __( 'Monthly on the %s', 'woocommerce-multilingual' ), $month_day ),
330 ];
331
332 } elseif ( 'weekly' === $this->settings['schedule'] ) {
333
334 $week_day = $this->wp_locale->get_weekday( $this->settings['week_day'] );
335 $schedules[ 'wcml_weekly_on_' . $this->settings['week_day'] ] = [
336 'interval' => WEEK_IN_SECONDS,
337 /* translators: %s is the week day */
338 'display' => sprintf( __( 'Weekly on %s', 'woocommerce-multilingual' ), $week_day ),
339 ];
340
341 }
342
343 return $schedules;
344 }
345
346 private function get_month_day_formatted() {
347 $month_day = $this->settings['month_day'];
348 switch ( $month_day ) {
349 case 1:
350 $month_day .= 'st';
351 break;
352 case 2:
353 $month_day .= 'nd';
354 break;
355 case 3:
356 $month_day .= 'rd';
357 break;
358 default:
359 $month_day .= 'th';
360 break;
361 }
362 return $month_day;
363 }
364
365 private function get_current_service() {
366 return Obj::prop( Obj::prop( 'service', (array) $this->settings ), (array) $this->services );
367 }
368
369 public function is_current_service_actionable() {
370 $current_service = $this->get_current_service();
371
372 return $current_service
373 && (
374 ! $current_service->isKeyRequired()
375 || $current_service->getSetting( 'api-key' )
376 );
377 }
378 }
379