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


Правила форума

Внимание!!! Если не можете скачать, пожалуйста ознакомьтесь с условиями получения доступа с файлам форума. Правила форума


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


Сообщений в теме: 9

#1 badisoft

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

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

Отправлено 25 января 2015 - 18:17

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

После обновления высылается письмо с результатами обновления по всем валютам 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);
	}
?>


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

#2 badisoft

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

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

Отправлено 25 января 2015 - 23:07

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

#3 makki

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

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

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

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

#4 badisoft

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

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

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

Просмотр сообщенияmakki сказал:

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

#5 badisoft

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

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

Отправлено 22 марта 2015 - 13:30

Цитата

Как работает дополнение?
Проще некуда. Странный вопрос для кода в 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/
http://cpu.badisoft.ru (тестовый сайт), http://badisoft.ru (модули)

#6 makki

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

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

Отправлено 22 марта 2015 - 20:57

Просмотр сообщенияbadisoft (22 марта 2015 - 13:30) писал:

Цитата

Как работает дополнение?
Проще некуда. Странный вопрос для кода в 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);
	    }
?>


#7 badisoft

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

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

Отправлено 22 марта 2015 - 22:48

Цитата

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

#8 Robby

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

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

Отправлено 20 апреля 2016 - 14:39

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

Просмотр сообщенияmakki сказал:

под Национальный банк Украины (НБУ)
Отличия:
работает с любой валютой в качестве основной, а не только с гривной в качестве основной валюты;
изменена логика работы;
исправлены некоторые недочеты связанные с округлением возвращаемого курса, из-за чего можно было получить курс равный 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);
			}
}
?>



#9 badisoft

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

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

Отправлено 20 апреля 2016 - 15:34

Я теперь для "раз-в-сутки", "раз-в-час" и прочих подобных действий "по расписанию" использую не основную ветку исполнения клиентского запроса (чтобы не отнимать лишнее время у клиентского запроса), а добавляю в 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 минуты, то получим данные из вчерашнего файла :).
Зачем вообще это ветвление - локальный/новый файл, если запрос по определению выполняется ОДИН раз в сутки?
http://cpu.badisoft.ru (тестовый сайт), http://badisoft.ru (модули)

#10 badisoft

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

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

Отправлено 14 июня 2018 - 14:33

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

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


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