Skip to content

Instantly share code, notes, and snippets.

@ilyautkin
Created November 27, 2020 09:59
Show Gist options
  • Save ilyautkin/377ba8c5c27b01f5efdd41aa207efba4 to your computer and use it in GitHub Desktop.
Save ilyautkin/377ba8c5c27b01f5efdd41aa207efba4 to your computer and use it in GitHub Desktop.
Changed libs for msRussianPost with key
<?php
require_once 'postcalc_light_config.php';
global $modx;
function postcalc_request($From, $To, $Weight, $Valuation = 0, $Country = 'RU')
{
$key = ''; //Ваш ключ
$arrPostcalcConfig = unserialize(POSTCALC_CONF);
extract($arrPostcalcConfig, EXTR_PREFIX_ALL, 'config');
if (!is_numeric($Weight) || !($Weight > 0 && $Weight <= 100000))
return "Bec в граммах - число от 1 до 100000, десятичный знак - точка!";
if (!is_numeric($Valuation) || !($Valuation >= 0 && $Valuation <= 100000))
return "Оценка в рублях - число от 0 до 100000, десятичный знак - точка!";
// Отдельная функция проверяет правильность полей Откуда и Куда
$PindexFrom = postcalc_get_default_ops($From);
if (!$PindexFrom)
return "Поле 'Откуда': '$From' - не является допустимым индексом, названием региона или центра региона!";
if ($Country === 'RU') {
$PindexTo = postcalc_get_default_ops($To);
if (!$PindexTo)
return "Поле 'Куда': '$To' - не является допустимым индексом, названием региона или центра региона!";
}
// Если установлен флаг $config_city_as_pindex, то в запрос подставляем почтовый индекс по умолчанию.
// Если флаг $config_city_as_pindex не установлен, переводим название нас.пункта в "процентную" кодировку.
$From = ($config_city_as_pindex) ? $PindexFrom : rawurlencode($From);
if ($Country === 'RU') {
$To = ($config_city_as_pindex) ? $PindexTo : rawurlencode($To);
}
if (!postcalc_arr_from_txt('postcalc_light_countries.txt', $Country, 1))
return "Код страны '$Country' не найден в базе стран postcalc_light_countries.txt!";
// Формируем запрос со всеми необходимыми переменными.
$QueryString = "st=$config_st&ml=$config_ml&key=$key&p=bv,pv,co,p1,em,is,isa,ip,ipa,iem";
$QueryString .= "&f=$From&t=$To&w=$Weight&v=$Valuation&c=$Country";
$QueryString .= "&o=php&sw=PostcalcLight_2.3&cs=$config_cs";
if ($config_d != 'now') $QueryString .= "&d=$config_d";
if ($config_ib != 'f') $QueryString .= "&ib=$config_ib";
if ($config_r != 0.01) $QueryString .= "&r=$config_r";
if ($config_pr > 0) $QueryString .= "&pr=$config_pr";
if ($config_pk > 0) $QueryString .= "&pk=$config_pk";
// Если существует файл кэша для данной строки запроса, просто зачитываем его
if (false) {
return unserialize(file_get_contents($CacheFile));
} else {
$TS = microtime(1);
// Опрашиваем в цикле сервера Postcalc.RU, пока не получаем ответ
$ConnectOK = 0;
foreach ($config_servers as $Server) {
// Запрос к серверу.
$Request = "http://$Server/?$QueryString";
$Response = postcalc_http_request($Request, $config_timeout);
// При ошибке соединения опрашиваем следующий сервер в цепочке.
if ($Response === false) {
// === ОБРАБОТКА ОШИБОК СОЕДИНЕНИЯ
// Журнал ошибок соединения, поля разделены табуляцией:
// метка времени, сервер, истекшее время с начала сессии (т.е. всех запросов), краткое сообщение об ошибке, полное сообщение об ошибке
if ($config_error_log && count(error_get_last())) {
$ErrorLog = "$config_cache_dir/postcalc_error_" . date('Y-m') . '.log';
$arrError = error_get_last();
$PHPErrorMessage = $arrError['message'];
// Отрезаем конец сообщения PHP, где сообщается причина проблемы
$ErrMessage = substr($PHPErrorMessage, strrpos($PHPErrorMessage, ':') + 2);
$fp_log = fopen($ErrorLog, 'a');
fwrite($fp_log, date('Y-m-d H:i:s') . "\t$Server\t" . number_format((microtime(1) - $TS), 3) . "\t$ErrMessage\t$PHPErrorMessage\n");
fclose($fp_log);
if ($config_error_log_send > 0) {
$fp_log = fopen($ErrorLog, 'r');
// Последовательно идем по логу и сохраняем в переменной $MailMessage фрагмент не более $config_error_log_send строк
$MailMessage = '';
$send_log = false;
$line_counter = 0;
while (($line = fgets($fp_log)) !== false) {
$line_counter++;
if ($send_log) {
$MailMessage = '';
$send_log = false;
}
$MailMessage .= $line;
// Если в $MailMessage оказалось ровно $config_error_log_send строк, сбрасываем счетчик строк и устанавливаем флаг $send_log.
// Если следующее чтение вернуло конец файла, цикл будет прерван и фрагмент лога отослан по почте.
// Иначе фрагмент лога будет сброшен, как и флаг $send_log
if ($line_counter % $config_error_log_send === 0) {
$line_counter = 0;
$send_log = true;
}
}
fclose($fp_log);
if ($send_log) {
$MailMessage = "$_SERVER[SERVER_ADDR] [$_SERVER[SERVER_ADDR]]: ошибки соединения в скрипте $_SERVER[SCRIPT_FILENAME].\n"
. "Подробности см. в http://$_SERVER[HTTP_HOST]" . dirname($_SERVER['REQUEST_URI']) . "/postcalc_light_stat.php\n"
. "Последние строки ($config_error_log_send) из журнала ошибок:\n\n"
. $MailMessage;
mail($config_ml,
"$_SERVER[SERVER_ADDR] [$_SERVER[SERVER_ADDR]]: connection errors in postcalc_light_lib",
$MailMessage,
"Content-Transfer-Encoding: 8bit\nContent-Type: text/plain; charset=$config_cs\n");
}
}
}
// === КОНЕЦ ОБРАБОТКИ ОШИБОК СОЕДИНЕНИЯ
continue;
}
$ConnectOK = 1;
break;
}
if (!$ConnectOK) return 'Не удалось соединиться ни с одним из следующих серверов postcalc.ru: ' . implode(',', $config_servers) . '. Проверьте соединение с Интернетом.';
$ResponseSize = strlen($Response);
// Если поток сжат, разархивируем его
if (substr($Response, 0, 3) == "\x1f\x8b\x08")
$Response = gzinflate(substr($Response, 10, -8));
// Переводим ответ сервера в массив PHP
if (!$arrResponse = unserialize($Response))
return "Получены странные данные. Ответ сервера:\n$Response";
// Обработка возможной ошибки
if ($arrResponse['Status'] != 'OK')
return "Сервер вернул ошибку: $arrResponse[Status]!";
// Журнал успешных соединений, поля разделены табуляцией:
// метка времени, сервер, затраченное время, размер ответа, строка запроса
if ($config_log) {
$fp_log = fopen("$config_cache_dir/postcalc_light_" . date('Y-m') . '.log', 'a');
fwrite($fp_log, date('Y-m-d H:i:s') . "\t$Server\t" . number_format((microtime(1) - $TS), 3) . "\t$ResponseSize\t$QueryString\n");
fclose($fp_log);
}
return $arrResponse;
}
}
function postcalc_http_request($Request, $TimeOut)
{
$UserAgent = 'PostcalcLight_2.4 ' . phpversion();
// Получаем значение для специального индекса 000000, который содержит версию БД
$arr = postcalc_arr_from_txt('postcalc_light_post_indexes.txt', '000000', 1);
if ($arr) {
$UserAgent .= ' DB ' . current($arr);
}
// Если установлен пакет curl, используем именно его, т.к. file_get_contents делает лишний цикл запрос-ответ.
if (function_exists('curl_init')) {
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $Request);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $TimeOut);
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Connection: close', 'Accept-Encoding: gzip'));
curl_setopt($ch, CURLOPT_USERAGENT, $UserAgent);
$Response = curl_exec($ch);
curl_close($ch);
} else {
// Формируем опции запроса. Это _необязательно_, однако упрощает контроль и отладку
$arrOptions = array('http' =>
array('header' => array('Connection: close', 'Accept-Encoding: gzip',
'timeout' => $TimeOut,
'user_agent' => $UserAgent
)
));
// Подавляем вывод сообщений и сохраняем ответ в переменной $Response.
$Response = @file_get_contents($Request, false, stream_context_create($arrOptions));
}
return $Response;
}
/**
* Функция проверки правильности отправителя или получателя. Принимает либо 6-значный индекс,
* либо название населенного пункта из файла postcalc_light_cities.txt или таблицы postcalc_light_cities.
* Например: 'Москва', 'Абагур (Новокузнецк)', 'Абрамцево, Московская область, Сергиево-Посадский район'.
*
* Возвращает 6-значный индекс ОПС, если не найдено - false.
*
* Если передан 6-значный индекс, проверка идет по текстовому файлу postcalc_light_post_indexes.txt
* или таблице postcalc_light_post_indexes, где находятся все почтовые индексы России в формате
* индекс ОПС - название ОПС из "эталонного справочника Почты России".
*
* <code>
* $From = 'Сергиев Посад';
*
* $postIndex = postcalc_get_default_ops($From);
*
* if ( !$postIndex ) echo "'$From' не является допустимым индексом, названием региона или центра региона!";
* </code>
*
* @param string $FromTo Проверяемое значение
* @return string При ошибке возвращает false, иначе - шестизначный индекс ОПС.
*
* @uses postcalc_arr_from_txt() Запрашивает массив, созданный из текстового файла.
*/
function postcalc_get_default_ops($FromTo)
{
if (!$FromTo) return false;
// Особый ключ из таблицы почтовых индексов - 000000. Значение - дата и номер обновления таблицы почтовых индексов.
if ($FromTo == '000000') return false;
if (preg_match('/^[1-9][0-9]{5}$/', $FromTo)) {
// Это 6-значный индекс.
$isPindex = true;
$arr = postcalc_arr_from_txt('postcalc_light_post_indexes.txt', $FromTo);
} else {
// Это любое другое сочетание букв и цифр
$isPindex = false;
$arr = postcalc_arr_from_txt('postcalc_light_cities.txt', $FromTo);
}
// Ищем точное совпадение $FromTo и ключа в массиве.
if (isset($arr[$FromTo]))
return ($isPindex) ? $FromTo : $arr[$FromTo];
return false;
}
/**
* Функция генерирует массив PHP либо из текстового файла с данными,
* либо из таблицы MySQL.
*
* В первом случае открывает файл $src_txt, в котором находятся данные в формате:
* [ключ]\t[значение]\n.
*
* Во втором случае обращается к таблице MySQL. Ее название совпадает с названием
* текстового файла без расширения .txt.
*
* Возвращает массив. Параметр search - совпадение с началом ключа. Если пустая
* строка (по умолчанию) - возвращает все строки.
*
* @param string $src_txt Название файла с данными (включая расширение .txt).
* @param string $search Совпадение с началом ключа. Если пустая строка - возвращает полную таблицу.
* @param integer $limit Возвращать не более $limit элементов (для Autocomplete)
* @return array Массив, если совпадений нет - пустой массив
*
*/
function postcalc_arr_from_txt($src_txt, $search = '', $limit = 0)
{
$arrPostcalcConfig = unserialize(POSTCALC_CONF);
extract($arrPostcalcConfig, EXTR_PREFIX_ALL, 'config');
$arr = array();
// === Источник - таблица mysql
if ($config_source == 'mysql') {
$mysqli = new MySQLi($config_mysql_host, $config_mysql_user, $config_mysql_pass, $config_mysql_db);
// Устанавливаем правильный набор символов для запроса MySQL
if (stripos($config_cs, 'utf') !== false) {
$MySQL_Charset = 'UTF8';
} else if (stripos($config_cs, '1251') !== false) {
$MySQL_Charset = 'cp1251';
}
$stmt = $mysqli->set_charset($MySQL_Charset);
// Экранируем текст, который может поступить из Интернета
$search = $mysqli->real_escape_string($search);
$TableName = basename($src_txt, '.txt');
// Небольшой хак, чтобы установить имя ключевого поля.
if ($TableName == 'postcalc_light_cities') {
$KeyField = 'city';
$OrderField = 'city';
} elseif ($TableName == 'postcalc_light_post_indexes') {
$KeyField = 'pindex';
$OrderField = 'pindex';
} elseif ($TableName == 'postcalc_light_countries') {
$KeyField = 'iso2';
$OrderField = 'country';
}
$Limit = ($limit) ? "LIMIT $limit" : '';
$Where = ($search) ? "WHERE $KeyField LIKE '$search%'" : '';
$Order = "ORDER BY $OrderField";
$stmt = $mysqli->query("SELECT * FROM $TableName $Where $Order $Limit");
while ($row = $stmt->fetch_row())
$arr[$row[0]] = $row[1];
$mysqli->close();
return $arr;
}
// === Источник - текстовый файл.
$src_idx = basename($src_txt, 'txt') . 'idx';
$src_txt = $config_txt_dir . '/' . $src_txt;
$src_idx = $config_txt_dir . '/' . $src_idx;
$search = mb_convert_case($search, MB_CASE_LOWER, $config_cs);
// == Если нет файла индекса или фильтр отсутствует, идем полным перебором
if (!file_exists($src_idx) || $search == '') {
$fp = fopen($src_txt, 'r');
$counter = 0;
while (($line = fgets($fp)) !== false) {
list($key, $value) = explode("\t", $line);
if ($search == '' ||
($search != '' && mb_stripos($key, $search, 0, $config_cs) === 0)
) {
$arr[$key] = trim($value);
if ($limit > 0 && ++$counter >= $limit) break;
}
}
fclose($fp);
return $arr;
}
// == Индексный файл есть.
$string_idx = file_get_contents($src_idx);
// Начало совпадения
$pos = mb_strpos(
$string_idx,
// Берем два первых символа
"\n" . mb_substr($search, 0, 2, $config_cs),
0,
$config_cs
);
$idx_len = mb_strlen($string_idx, $config_cs);
// Конец строки с совпадением
$pos_line_end = mb_strpos($string_idx, "\n", $pos + 1, $config_cs);
$s = mb_substr($string_idx, $pos + 1, $pos_line_end - $pos - 1, $config_cs);
// Получили сдвиг в оригинальном файле.
list($tmp, $offset) = explode("\t", $s);
// Теперь длина.
if ($idx_len == $pos_line_end + 1) {
// Если это последняя строка в файле индекса, то будем брать фрагмент до конца файла данных.
// Берем любое большое число.
$len = 1000000;
} else {
$pos = $pos_line_end + 1;
$pos_line_end = mb_strpos($string_idx, "\n", $pos + 1, $config_cs);
$s = mb_substr($string_idx, $pos + 1, $pos_line_end - $pos, $config_cs);
list($tmp, $offset2) = explode("\t", $s);
$len = $offset2 - $offset;
}
$fp = fopen($src_txt, 'r');
fseek($fp, $offset);
$chunk = fread($fp, $len);
fclose($fp);
// Теперь делаем массив.
$arr_tmp = explode("\n", trim($chunk));
$counter = 0;
foreach ($arr_tmp as $no => $line) {
list($key, $value) = explode("\t", $line);
if ($search == '' ||
($search != '' && mb_stripos($key, $search, 0, $config_cs) === 0)
) {
$arr[$key] = trim($value);
if ($limit > 0 && ++$counter >= $limit) break;
}
}
return $arr;
}
/**
* Вспомогательная функция, генерирует из массива содержимое списка <select> для веб-страницы.
*
* Создает список стран, Россия в списке выделена:
* <code>
* postcalc_make_select(postcalc_arr_from_txt('postcalc_light_countries.txt'),'RU');
* </code>
*
* @ignore
* @param array $arrList Ключи массива становятся value в тэге <option>, значения массива становятся видимыми элементами списка.
* @param string $defaultValue Это значение будет выделено (атрибут selected).
* @return string Готовый список для вставки на веб-странице между тэгами <select> и </select>.
*/
function postcalc_make_select($arrList, $defaultValue)
{
$Out = '';
foreach ($arrList as $value => $label) {
$Out .= "<option value='$value'";
$Out .= ($value == $defaultValue) ? ' selected' : '';
$Out .= ">$label</option>\n";
}
return $Out;
}
/**
* Автодополнение для полей "Откуда" и "Куда" на веб-странице. Работает с виджетом jQuery Autocomplete.
*
* Внимание! Входные данные ожидаются всегда в кодировке UTF-8.
* jQuery Autocomplete эту кодировку обеспечивает автоматически, в остальных случаях можно применять
* функцию javascript encodeURIComponent().
*
* Возвращает массив JSON для непосредственного использования в виджете jQuery Autocomplete в кодировке UTF-8.
*
*
* @param string $post_index Начало почтового индекса или населенного пункта.
* @param integer $limit Максимальное число элементов в списке.
* @return mixed Объект JSON для непосредственного использования в виджете jQuery Autocomplete.
*
* @uses postcalc_arr_from_txt() Запрашивает функцию postcalc_arr_from_txt() для получения массива, сгенерированного из текстового файла.
*/
function postcalc_autocomplete($post_index, $limit = 10)
{
$arrPostcalcConfig = unserialize(POSTCALC_CONF);
$Charset = $arrPostcalcConfig['cs'];
$arr = array();
// Не менее 3 начальных символов должны быть цифрами
if (preg_match("/\d{3,}/", $post_index)) {
$arr_indexes = postcalc_arr_from_txt('postcalc_light_post_indexes.txt', $post_index, $limit);
foreach ($arr_indexes as $pindex => $opsname)
$arr[] = array(
'label' => $pindex . ' ' . mb_convert_encoding($opsname, 'UTF-8', $Charset),
// Почтовый индекс - как строка для правильного преобразования в json_encode().
'value' => (string)$pindex
);
} else {
// Все данные с веб-страницы поступают в UTF-8
$post_index = mb_convert_case($post_index, MB_CASE_TITLE, 'UTF-8');
// Преобразуем в текущую кодировку библиотеки
$post_index = mb_convert_encoding($post_index, $Charset, 'UTF-8');
$arr_cities = postcalc_arr_from_txt('postcalc_light_cities.txt', $post_index, $limit);
foreach ($arr_cities as $city => $default_ops) {
$arr[] = array(
'label' => mb_convert_encoding($city, 'UTF-8', $Charset),
'value' => mb_convert_encoding($city, 'UTF-8', $Charset)
);
}
}
return json_encode($arr);
}
/**
* Функция генерирует из журналов соединений массив PHP. Используется в postcalc_light_stat.php.
* Возвращаемый массив может быть использован и для самостоятельного анализа.
*
* Открывает в цикле все файлы, которые расположены в cache_dir и имеют название вида postcalc_light_YYYY-MM.log,
* возвращает массив, где данные сгруппированы по дням: ключ массива - дата в формате YYYY-MM-DD,
* значения: число обращений за сутки num_requests, среднее время запроса time_elapsed, средний размер ответа size.
*
* @global array $arrPostcalcConfig
* @return array
*/
function postcalc_get_stat_arr()
{
$arrPostcalcConfig = unserialize(POSTCALC_CONF);
$postcalc_config_cache_dir = $arrPostcalcConfig['cache_dir'];
$arrStat = array();
foreach (glob("$postcalc_config_cache_dir/postcalc_light_*.log") as $logfile) {
$fp_log = fopen($logfile, 'r');
while ($logline = fgets($fp_log)) {
list($date_time, $server, $time_elapsed, $size, $query_string) = explode("\t", $logline);
$date = substr($date_time, 0, 10);
if (isset($arrStat[$date])) {
$arrStat[$date]['time_elapsed'] += $time_elapsed;
$arrStat[$date]['size'] += $size;
$arrStat[$date]['num_requests']++;
} else {
$arrStat[$date]['time_elapsed'] = $time_elapsed;
$arrStat[$date]['size'] = $size;
$arrStat[$date]['num_requests'] = 1;
$arrStat[$date]['errors'] = 0;
}
}
fclose($fp_log);
}
// Дополняем статистикой по ошибкам
foreach (glob("$postcalc_config_cache_dir/postcalc_error_*.log") as $logfile) {
$fp_log = fopen($logfile, 'r');
while ($logline = fgets($fp_log)) {
list($date_time, $server, $time_elapsed, $error_short, $error_full) = explode("\t", $logline);
$date = substr($date_time, 0, 10);
if (isset($arrStat[$date]['errors'])) {
$arrStat[$date]['errors'] += 1;
} else {
$arrStat[$date]['errors'] = 1;
}
}
fclose($fp_log);
}
// Теперь проходимся по всему массиву и вычисляем среднее арифметическое
// для size (округляем до целого) и time_elapsed (оставляем 3 знака после запятой).
foreach ($arrStat as $date => $arr_values) {
if (isset($arrStat[$date]['time_elapsed']))
$arrStat[$date]['time_elapsed'] = number_format(($arrStat[$date]['time_elapsed'] / $arrStat[$date]['num_requests']), 3, '.', '');
if (isset($arrStat[$date]['size']))
$arrStat[$date]['size'] = round($arrStat[$date]['size'] / $arrStat[$date]['num_requests'], 0);
}
return $arrStat;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment