Перейти к содержимому


Автообновление курсов валют с сайта ЦБ РФ


  • Вы не можете ответить в тему
Сообщений в теме: 9

#1 badisoft

    Продвинутый пользователь

  • Assistent vsupport.club
  • PipPipPip
  • 5075 сообщений
Репутация: 786
Мастер

Отправлено 25 January 2015 - 06:17 PM

Дополнение для автоматического ежесуточного обновления курса валют с сайта Центробанка России (ЦБРФ).

После обновления высылается письмо с результатами обновления по всем валютам ShopCMS:
1. Валюта XXX обновлена. Новый курс 12.3456. Старый курс 12.2345.
2. Валюта YYY не обновлена. Новый курс отличается от старого больше, чем на NN%.
3. Валюта ZZZ не обновлена. Не найдена в результате запроса.

Нет особой сложности в переделке дополнения и под любой другой банк.
Хорошо идет в комплекте с модулем MaestrO "Мультивалютные товары" - получаем ежедневное обновление цен в дефолтовой валюте (например, рублевых цен) для товаров, учет которых задан в другой валюте (например, в долларах).

Установка:

1. Сохраняем код в файл core/includes/autoupdate_currency.php
(имя файла должно начинаться на букву "а")


<?php
if(!defined('CONF_AUTOUPDATE_CURRENCY_DATE'))
db_query("INSERT ".SETTINGS_TABLE." SET
settings_groupID=9999,
settings_constant_name='CONF_AUTOUPDATE_CURRENCY_DATE',
settings_value='01.01.2001',
settings_title='Дата обновления курсов валют',
settings_description='Дата обновления курсов валют. Оно выполняется один раз в сутки.',
settings_html_function='setting_TEXT_BOX(0,',
sort_order=1");

$date = date('d.m.Y');
$prev_date = settingGetSetting('CONF_AUTOUPDATE_CURRENCY_DATE');
if ($prev_date['settings_value'] !== $date)
{
$addon = 1.03; // добавка к курсу. Курс в ShopCMS будет на 3% больше, чем курс ЦБРФ.
$max_delta = 0.1; // граница изменения курса. Если новый курс отличается от старого более, чем на 10% - обновления не будет. На всякий случай.
$CRLF = "\n";
$new_values = array();
$text = '';
$html = file_get_contents("http://www.cbr.ru/currency_base/daily.aspx?date_req=$date");

foreach (currGetAllCurrencies() as $curr)
{
if (preg_match('/<tr><td>\d{3}<\/td>.{2}<td>'.$curr['currency_iso_3'].'<\/td>.{2}<td>(\d{1,5})<\/td>.{2}<td>.+?<\/td>.{2}<td>(\d{2,3},\d{4})<\/td><\/tr>/s',$html,$matches))
{
$today = (float)str_replace(',','.',$matches[2])/$matches[1]*$addon;
$fromCMS = currGetCurrencyByID($curr['CID']);
$old = 1.0/(float)$fromCMS['currency_value'];
if (abs($today - $old)/$old < $max_delta)
{
$text .= "Курс ".$curr['currency_iso_3']." обновлен. Новый курс $today. Старый курс $old.$CRLF";
$new_values[$curr['CID']] = 1.0/$today;
}
else $text .= "Курс ".$curr['currency_iso_3']." не обновлен. Новый курс ($today) отличается от старого ($old) на ".round(100.0*($today - $old)/$old,2)."%$CRLF";
}
else $text .= "Курс ".$curr['currency_iso_3']." не обновлен. Не найден в результате запроса.$CRLF";
}

foreach ($new_values as $key => $val)
db_query( "UPDATE ".CURRENCY_TYPES_TABLE." SET currency_value=$val WHERE CID=$key");

# BEGIN MultiCurrencyProducts
#configUpdateProductsPrice();
# END MultiCurrencyProducts

_setSettingOptionValue('CONF_AUTOUPDATE_CURRENCY_DATE', $date );
xMailTxtHTML(CONF_GENERAL_EMAIL, "$date - завершено обновление курсов валют", $text);
}
?>

  • 0
http://cpu.badisoft.ru (тестовый сайт), http://badisoft.ru (модули)

#2 badisoft

    Продвинутый пользователь

  • Assistent vsupport.club
  • PipPipPip
  • 5075 сообщений
Репутация: 786
Мастер

Отправлено 25 January 2015 - 11:07 PM

update. Кавычки забыл в одном месте.
  • 0
http://cpu.badisoft.ru (тестовый сайт), http://badisoft.ru (модули)

#3 makki

    Продвинутый пользователь

  • Assistent vsupport.club
  • PipPipPip
  • 148 сообщений
Репутация: 7
Начинающий

Отправлено 21 March 2015 - 10:34 AM

Как работает дополнение? Установил. Ничего не поменялось
  • 0

#4 badisoft

    Продвинутый пользователь

  • Assistent vsupport.club
  • PipPipPip
  • 5075 сообщений
Репутация: 786
Мастер

Отправлено 21 March 2015 - 11:35 AM

Как работает дополнение? Установил. Ничего не поменялось

Если не пришло даже письмо (см. последнюю строку дополнения), то Вы явно установили его куда-то не туда :).
  • 0
http://cpu.badisoft.ru (тестовый сайт), http://badisoft.ru (модули)

#5 badisoft

    Продвинутый пользователь

  • Assistent vsupport.club
  • PipPipPip
  • 5075 сообщений
Репутация: 786
Мастер

Отправлено 22 March 2015 - 01:30 PM

Как работает дополнение?

Проще некуда. Странный вопрос для кода в 50 строк.
1. Создается константа (сеттинг), в которой хранится дата обновления. Изначально туда прописывается 01.01.2001.
2. При каждом выполнении проверяется, сегодняшняя ли дата в этой константе. Если не сегодняшняя, то выполняем обновление, прописываем сегодняшнюю и отсылаем емейл админу о новых курсах. Таким образом обновление происходит раз в сутки при первом обращении к сайту.
3. При обновлении на сайте ЦБРФ по соответствующему регулярному выражению ищутся курсы для валют, ISO3-наименования которых есть в таблице валют. Если находятся и удовлетворяют проверке, то старый курс заменяется новым.

Получаемый email выглядит так (это сегодняшний):
=============
Курс USD обновлен. Новый курс 61.8351. Старый курс 61.8353. Курс ЦБ 60.0341.
Курс EUR обновлен. Новый курс 65.9719. Старый курс 65.9718. Курс ЦБ 64.0504.
Курс RUR не обновлен. Не найден в результате запроса.
Курс UAH обновлен. Новый курс 2.639. Старый курс 2.639. Курс ЦБ 2.56211.
=============

PS. Если есть возможность запускать код по cron-у (или любой другой шедулер, позволяющий выполнить скрипт в заданное время), то изворот с setting-константой не нужен, т.к. можно будет выполнить обновление курсов один раз в сутки в заданное время используя ?do=autoupdate_currency по аналогии с запуском других скриптов из core/include/processor/
  • 0
http://cpu.badisoft.ru (тестовый сайт), http://badisoft.ru (модули)

#6 makki

    Продвинутый пользователь

  • Assistent vsupport.club
  • PipPipPip
  • 148 сообщений
Репутация: 7
Начинающий

Отправлено 22 March 2015 - 08:57 PM

Как работает дополнение?

Проще некуда. Странный вопрос для кода в 50 строк.
1. Создается константа (сеттинг), в которой хранится дата обновления. Изначально туда прописывается 01.01.2001.
2. При каждом выполнении проверяется, сегодняшняя ли дата в этой константе. Если не сегодняшняя, то выполняем обновление, прописываем сегодняшнюю и отсылаем емейл админу о новых курсах. Таким образом обновление происходит раз в сутки при первом обращении к сайту.
3. При обновлении на сайте ЦБРФ по соответствующему регулярному выражению ищутся курсы для валют, ISO3-наименования которых есть в таблице валют. Если находятся и удовлетворяют проверке, то старый курс заменяется новым.

Получаемый email выглядит так (это сегодняшний):
=============
Курс USD обновлен. Новый курс 61.8351. Старый курс 61.8353. Курс ЦБ 60.0341.
Курс EUR обновлен. Новый курс 65.9719. Старый курс 65.9718. Курс ЦБ 64.0504.
Курс RUR не обновлен. Не найден в результате запроса.
Курс UAH обновлен. Новый курс 2.639. Старый курс 2.639. Курс ЦБ 2.56211.
=============

PS. Если есть возможность запускать код по cron-у (или любой другой шедулер, позволяющий выполнить скрипт в заданное время), то изворот с setting-константой не нужен, т.к. можно будет выполнить обновление курсов один раз в сутки в заданное время используя ?do=autoupdate_currency по аналогии с запуском других скриптов из core/include/processor/


Спасибо большое за нужное дополнение и доступное объяснение.
Переделал себе под Национальный банк Украины (НБУ). Выкладываю сюда, может кому будет нужно.

Установка.
1. Сохраняем код в файл core/classes/class.exchangerate.php
<?php
class ExchangeRate {
    public $exchange_url =
		    'http://bank-ua.com/export/currrate.xml';
    public $xml;
    function __construct(){
	    return $this->xml =
			    simplexml_load_file($this->exchange_url);
    }
    function getExchangeRateByChar3($char3){
	    if ($this->xml!==FALSE) {
		    foreach($this->xml->children() as $item){
			    $row = simplexml_load_string($item->asXML());		   
			    $v = $row->xpath('//char3[. ="' . $char3 . '"]');
			    if($v[0]){
				    $result = $item;
				    break;
			    }
		    }
	    }
	    return $result;
    }
}
?>

2. Сохраняем код в файл core/includes/autoupdate_currency.php
<?php
if(!defined('CONF_AUTOUPDATE_CURRENCY_DATE'))
	    db_query("INSERT ".SETTINGS_TABLE." SET
						  settings_groupID=9999,
						  settings_constant_name='CONF_AUTOUPDATE_CURRENCY_DATE',
						  settings_value='01.01.2001',
						  settings_title='Дата обновления курсов валют',
						  settings_description='Дата обновления курсов валют. Оно выполняется один раз в сутки.',
						  settings_html_function='setting_TEXT_BOX(0,',
						  sort_order=1");
$date = date('d.m.Y');
$prev_date = settingGetSetting('CONF_AUTOUPDATE_CURRENCY_DATE');
if ($prev_date['settings_value'] !== $date)
	    {
	    $addon = 1.03; // добавка к курсу. Курс в ShopCMS будет на 3% больше.
	    $max_delta = 0.1; // граница изменения курса. Если новый курс отличается от старого более, чем на 10% - обновления не будет. На всякий случай.
	    $CRLF = "\n";
	    $new_values = array();
	    $text = '';	 
	    include_once ("core/classes/class.exchangerate.php");
	    $er = new ExchangeRate();
	    foreach (currGetAllCurrencies() as $curr)
			    {
			 $data = $er->getExchangeRateByChar3($curr['currency_iso_3']);												   
			    if ($data)
					    {
					    $today = $data->rate / $data->size * $addon;
					    $fromCMS = currGetCurrencyByID($curr['CID']);
					    $old = 1.0/(float)$fromCMS['currency_value'];
					    if (abs($today - $old)/$old < $max_delta)
							    {
							    $text .= "Курс ".$curr['currency_iso_3']." обновлен. Новый курс $today. Старый курс $old.$CRLF";
							    $new_values[$curr['CID']] = 1.0/$today;
							    }
					    else $text .= "Курс ".$curr['currency_iso_3']." не обновлен. Новый курс ($today) отличается от старого ($old) на ".round(100.0*($today - $old)/$old,2)."%$CRLF";
					    }
			    else $text .= "Курс ".$curr['currency_iso_3']." не обновлен. Не найден в результате запроса.$CRLF";
			    }
	    foreach ($new_values as $key => $val)
			    db_query( "UPDATE ".CURRENCY_TYPES_TABLE." SET currency_value=$val WHERE CID=$key");
# BEGIN MultiCurrencyProducts
#configUpdateProductsPrice();
# END MultiCurrencyProducts
	    _setSettingOptionValue('CONF_AUTOUPDATE_CURRENCY_DATE', $date );
	    xMailTxtHTML(CONF_GENERAL_EMAIL, "$date - завершено обновление курсов валют", $text);
	    }
?>

  • 0

#7 badisoft

    Продвинутый пользователь

  • Assistent vsupport.club
  • PipPipPip
  • 5075 сообщений
Репутация: 786
Мастер

Отправлено 22 March 2015 - 10:48 PM

settings_groupID=9999

Вот это излишне. Это я сдуру :).
Достаточно settings_groupID=1, как для всех остальных невидимых сеттингов.
  • 0
http://cpu.badisoft.ru (тестовый сайт), http://badisoft.ru (модули)

#8 Robby

    Продвинутый пользователь

  • Assistent vsupport.club
  • PipPipPip
  • 162 сообщений
Репутация: 75
Продвинутый

Отправлено 20 April 2016 - 02:39 PM

Переделал решение от

под Национальный банк Украины (НБУ)

Отличия:
работает с любой валютой в качестве основной, а не только с гривной в качестве основной валюты;
изменена логика работы;
исправлены некоторые недочеты связанные с округлением возвращаемого курса, из-за чего можно было получить курс равный 0;
добавлено кэширование, результат запроса сохраняется в файл

Установка.
1. Сохраняем php код в файл core/classes/class.exchangerate.php

<?php
class ExchangeRate
{
// URL, файл в формате XML
public $exchange_url = 'http://bank-ua.com/export/currrate.xml';

private $xml;
private $currency = 'core/cache/currency.xml';

function __construct()
{
$this->getXML();
return $this->xml = simplexml_load_file($this->currency);
}

private function getXML()
{
// используем локальный файл если он существует и если дата его создания меньше 24 часов от текущей даты 86400 = 60*60*24
if (!file_exists($this->currency) or (time() - filemtime($this->currency) > 86400))
file_put_contents($this->currency, file_get_contents($this->exchange_url));
}

public function getExchangeRateByChar3($char3)
{
if ($this->xml !== FALSE) {
foreach ($this->xml->children() as $item) {
$row = simplexml_load_string($item->asXML());
$v = $row->xpath('//char3[. ="' . $char3 . '"]');
if ($v[0]) {
$result = $item;
break;
}
}
}
return $result;
}

private function getExchangeVal($char3)
{
if ($this->xml !== FALSE) {
// в XML-данных нет ошибки, продолжаем...
foreach ($this->xml as $currency) {
if ($currency->char3 == $char3) {
$curs = (float) $currency->rate / $currency->size;
break;
}
}
}
return $curs; // возвращает курс для валюты, код которой передан в качестве аргумента функции
}

public function getExchange($val, $base_val, $increase = 1, $round = 6)
{
if ($val != $base_val) {
$base_valuta = $this->getExchangeVal($base_val); // курс базовой валюты
if (!$base_valuta) {
$base_valuta = 1;
}
//если гривна не основная валюта
if ($val == 'UAH') {
return round($base_valuta * $increase, $round);
} else {
$valuta = $this->getExchangeVal($val); // курс текущей валюты
return round($base_valuta / $valuta * $increase, $round);
}
} else {
return 1; // если текущая валюта равна основной, то возвращаем курс равный 1
}

}

}
?>


2. Сохраняем код в файл core/includes/autoupdate_currency.php

<?php
include_once ("core/classes/class.exchangerate.php");
$addon = 1.03; // Надбавка к курсу. Курс в ShopCMS будет на 3% больше.
$max_delta = 0.1; // Граница изменения курса. Если новый курс отличается от старого более, чем на 10% - обновления не будет. На всякий случай.
$round = 4; // Кол-во разрядов для округления
$CRLF = "\n"; // перенос строки
$new_values = array(); // массив с валютами, без основной валюты
$text = ''; // текст сообщения о результатах обновления
$curr_primary = ''; // код основной валюты магазина
$date = date('d.m.Y'); // текущая дата
$er = new ExchangeRate(); // создаем новый экземпляр класса
foreach (currGetAllCurrencies() as $curr) // последовательно перебираем все валюты магазина
{
$fromCMS = currGetCurrencyByID($curr['CID']); // получаем свойства текущей валюты
$old = (float)$fromCMS['currency_value']; // старое значение курса текущей валюты
if ($curr['currency_value'] == '1') // если основная валюта
{
$curr_primary = $curr['currency_iso_3']; // сохраняем код основной валюты
}
else {
$new_values[$curr['CID']] = array('currency_iso_3' => $curr['currency_iso_3'], 'old_curr' => $old); // добавляем текущую валюту в массив, ключи: код валюты, старое значение курса валюты
}

} // конец foreach перебора валют

if(!defined('CONF_AUTOUPDATE_CURRENCY_DATE')) {
// первичная установка курсов, если константа отсутствует - получаем курсы валют и устанавливаем из значения в магазине
foreach ($new_values as $key => $value)
{
$new_curr = $er->getExchange($value['currency_iso_3'], $curr_primary, $addon, $round);
if ($new_curr) {
db_query( "UPDATE ".CURRENCY_TYPES_TABLE." SET currency_value=$new_curr WHERE CID=$key");
$text .= "Курс {$value['currency_iso_3']} обновлен. Новый курс $new_curr. Старый курс {$value['old_curr']} $CRLF";
}
else {
$text .= "Курс {$value['currency_iso_3']} не обновлен. Не найден в результате запроса.$CRLF";
}
}
db_query("INSERT ".SETTINGS_TABLE." SET
settings_groupID=1,
settings_constant_name='CONF_AUTOUPDATE_CURRENCY_DATE',
settings_value='" . $date . "',
settings_title='Дата обновления курсов валют',
settings_description='Дата обновления курсов валют. Оно выполняется один раз в сутки.',
settings_html_function='setting_TEXT_BOX(0,',
sort_order=1");

xMailTxtHTML(CONF_GENERAL_EMAIL, "$date установка курсов валют", $text);
}
else {
$prev_date = settingGetSetting('CONF_AUTOUPDATE_CURRENCY_DATE');
if ($prev_date['settings_value'] !== $date)
{
foreach ($new_values as $key => $value)
{
$new_curr = $er->getExchange($value['currency_iso_3'], $curr_primary, $addon, $round);
if ($new_curr) {
if ( abs($new_curr - $value['old_curr'])/$value['old_curr'] < $max_delta ) {
db_query( "UPDATE ".CURRENCY_TYPES_TABLE." SET currency_value=$new_curr WHERE CID=$key");
$text .= "Курс {$value['currency_iso_3']} обновлен. Новый курс $new_curr. Старый курс {$value['old_curr']} $CRLF";
}
else {
$text .= "Курс {$value['currency_iso_3']} не обновлен. Новый курс ($new_curr) отличается от старого ({$value['old_curr']}) на " . round(100.0*($new_curr - $value['old_curr'])/$value['old_curr'], 2) . "%$CRLF";
}
}
else {
$text .= "Курс {$value['currency_iso_3']} не обновлен. Не найден в результате запроса.$CRLF";
}
} //конец foreach

_setSettingOptionValue('CONF_AUTOUPDATE_CURRENCY_DATE', $date );
xMailTxtHTML(CONF_GENERAL_EMAIL, "$date обновление курсов валют", $text);
}
}
?>

  • 0

#9 badisoft

    Продвинутый пользователь

  • Assistent vsupport.club
  • PipPipPip
  • 5075 сообщений
Репутация: 786
Мастер

Отправлено 20 April 2016 - 03:34 PM

Я теперь для "раз-в-сутки", "раз-в-час" и прочих подобных действий "по расписанию" использую не основную ветку исполнения клиентского запроса (чтобы не отнимать лишнее время у клиентского запроса), а добавляю в index.tpl.html в самый конец строки типа:

{if $sincro_autoupdate}
<script type='text/javascript'>JsHttpRequest.query('index.php','do=sincro_autoupdate',null,true);</script>
{/if}

При этом из браузера клиента в самом конце загрузки посылается AJAX-запрос с соответствующими параметрами и "действие по расписанию" выполняется уже отдельным потоком, не мешая запросу клиента. В данном случае выполнится /core/includes/processor/sincro_autoupdate.php. Под задачу автоапдейта валюты оно не особо и надо, не те затраты времени, а вот под ежесуточный бэкап базы вполне актуально.


// используем локальный файл если он существует и если дата его создания меньше 24 часов от текущей даты 86400 = 60*60*24

И если вчера первый в эти сутки клиент зашел в 0 часов 5 минут, а сегодня в 0 часов 3 минуты, то получим данные из вчерашнего файла :).
Зачем вообще это ветвление - локальный/новый файл, если запрос по определению выполняется ОДИН раз в сутки?
  • 0
http://cpu.badisoft.ru (тестовый сайт), http://badisoft.ru (модули)

#10 badisoft

    Продвинутый пользователь

  • Assistent vsupport.club
  • PipPipPip
  • 5075 сообщений
Репутация: 786
Мастер

Отправлено 14 June 2018 - 02:33 PM

update: чуть изменился html-код страницы центробанка, откуда парсятся курсы валют.

Прикрепленные файлы


  • 0
http://cpu.badisoft.ru (тестовый сайт), http://badisoft.ru (модули)