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.
 
 
 
 
 

235 lines
5.0 KiB

<?php
namespace WCML\MultiCurrency\ExchangeRateServices;
use WPML\FP\Obj;
/**
* Class Service
*/
abstract class Service {
/** @var array */
private $settings;
/**
* @return string
*/
abstract public function getId();
/**
* @return string
*/
abstract public function getName();
/**
* @return string
*/
abstract public function getUrl();
/**
* @return string
*/
abstract public function getApiUrl();
/**
* @return bool
*/
abstract public function isKeyRequired();
/**
* @return void
*/
public function resetConnectionCache() {
}
/**
* @param string $from Base currency.
* @param array $tos Target currencies.
*
* @return mixed
* @throws \Exception Thrown where there are connection problems.
*/
public function getRates( $from, $tos ) {
$this->clearLastError();
$response = $this->makeRequest( $from, $tos );
if ( is_wp_error( $response ) ) {
$http_error = implode( "\n", $response->get_error_messages() );
$this->saveLastError( $http_error );
throw new \Exception( $http_error );
}
$data = json_decode( $response['body'] );
if ( $this->isInvalidResponse( $data ) ) {
$error = self::get_formatted_error( $data );
$this->saveLastError( $error );
throw new \Exception( $error );
}
return $this->extractRates( $data, $from, $tos );
}
/**
* @param string $from The base currency code.
* @param array $tos The target currency codes.
*
* @return array|\WP_Error
*/
protected function makeRequest( $from, $tos ) {
if ( $this->isKeyRequired() ) {
$url = sprintf( $this->getApiUrl(), $this->getApiKey(), $from, implode( ',', $tos ) );
} else {
$url = sprintf( $this->getApiUrl(), $from, implode( ',', $tos ) );
}
return wp_safe_remote_get( $url, [ 'headers' => $this->getRequestHeaders() ] );
}
/**
* @return array
*/
protected function getRequestHeaders() {
return [];
}
/**
* @param object $decodedData
*
* @return bool
*/
protected function isInvalidResponse( $decodedData ) {
return empty( $decodedData->rates );
}
/**
* @param object $validData
* @param string $from
* @param array $tos
*
* @return array
*/
protected function extractRates( $validData, $from, $tos ) {
$rates = [];
foreach ( $validData->rates as $to => $rate ) {
$rates[ $to ] = round( $rate, \WCML_Exchange_Rates::DIGITS_AFTER_DECIMAL_POINT );
}
return $rates;
}
/**
* Each service has its own response signature,
* and I also noticed that it does not always
* respect their own doc.
*
* So the idea is to just catch all possible information
* and return it as raw output.
*
* Example: "error_code: 104 - error_message: ..."
*
* @param array|\stdClass $response
*
* @return string
*/
public static function get_formatted_error( $response ) {
// $getFromPath :: array -> string|null
$getFromPath = function( $path ) use ( $response ) {
try {
$value = Obj::path( $path, $response );
return is_string( $value ) || is_int( $value ) ? $value : null;
} catch ( \Exception $e ) {
return null;
}
};
$formattedError = wpml_collect( [
// Codes or types
'error' => $getFromPath( [ 'error' ] ),
'error_code' => $getFromPath( [ 'error', 'code' ] ),
'error_type' => $getFromPath( [ 'error', 'type' ] ),
// Descriptions or messages
'error_info' => $getFromPath( [ 'error', 'info' ] ),
'error_message' => $getFromPath( [ 'error', 'message' ] ),
'message' => $getFromPath( [ 'message' ] ),
'description' => $getFromPath( [ 'description' ] ),
] )->filter()
->map( function( $value, $key ) {
return "$key: $value";
} )
->implode( ' - ' );
return $formattedError
? strip_tags( $formattedError )
: esc_html__( 'Cannot get exchange rates. Connection failed.', 'woocommerce-multilingual' );
}
/**
* @return array
*/
public function getSettings() {
if ( null === $this->settings ) {
$this->settings = get_option( 'wcml_exchange_rate_service_' . $this->getId(), [] );
}
return $this->settings;
}
private function saveSettings() {
update_option( 'wcml_exchange_rate_service_' . $this->getId(), $this->getSettings() );
}
/**
* @param string $key
*
* @return mixed|null
*/
public function getSetting( $key ) {
return Obj::prop( $key, $this->getSettings() );
}
/**
* @param string $key
* @param mixed $value
*/
public function saveSetting( $key, $value ) {
$this->getSettings();
$this->settings[ $key ] = $value;
$this->saveSettings();
}
/**
* @param string $error_message
*/
public function saveLastError( $error_message ) {
$this->saveSetting(
'last_error',
[
'text' => $error_message,
'time' => date_i18n( 'F j, Y g:i a', false, true ),
]
);
}
public function clearLastError() {
$this->saveSetting( 'last_error', false );
}
/**
* @return mixed
*/
public function getLastError() {
return $this->getSetting( 'last_error' );
}
/**
* @return string|null
*/
protected function getApiKey() {
return $this->getSetting( 'api-key' );
}
}