Обзор

Функции

  • postcalc_arr_from_txt
  • postcalc_autocomplete
  • postcalc_get_default_ops
  • postcalc_get_stat_arr
  • postcalc_request
  • Обзор
  • Функция
  • Загрузка
  1: <?php
  2: require_once 'postcalc_light_config.php';
  3: /**
  4:  * Основная функция опроса сервера Postcalc.RU
  5:  * 
  6:  * Настройки хранятся в конфигурационном файле файле postcalc_light_config.php.<br>
  7:  * 
  8:  * Принимает следующие данные: отправитель, получатель, вес, оценка, страна. <br>
  9:  *  
 10:  * 1). Проверяет эти данные, при ошибке возвращает строку с сообщением об ошибке.<br>
 11:  * 
 12:  * 2). В цикле опрашивает сервера проекта Postcalc.RU (переменная servers
 13:  * конфигурационного файла).<br>
 14:  * 
 15:  * 3). В случае успеха возвращает массив с полученными от сервера данными, 
 16:  * при ошибке - строку с сообщением об ошибке.<br>
 17:  * 
 18:  * 4). Использует кэширование: в случае успеха записывает ответ в каталог cache_dir, 
 19:  * хранит ответ в течение cache_valid секунд. <br>
 20:  * 
 21:  * <code>
 22:  * $Response=postcalc_request('101000', 'Александровка, Алтайский край, Локтевский район', 505.1, 1000, 'RU');
 23:  * 
 24:  * if (is_array($Response)) {
 25:  *      echo $Response['Отправления']['ПростаяБандероль']['Тариф'];
 26:  *      } else {
 27:  *      echo "Ошибка: $Response";
 28:  * }
 29:  * </code>
 30:  * 
 31:  * @uses postcalc_get_default_ops() Используется при валидации отправителя и получателя.
 32:  * @uses postcalc_arr_from_txt() Используется при валидации страны.
 33:  * 
 34:  * @param string $From Отправитель. Либо 6-значный индекс ОПС, который проверяется по файлу postcalc_light_post_indexes.txt
 35:  * или таблице postcalc_light_post_indexes, либо наименование населенного пункта, которое проверяется по файлу
 36:  * postcalc_light_cities.txt или таблице postcalc_light_cities.
 37:  *  
 38:  * @param string $To Получатель. Либо 6-значный индекс ОПС, который проверяется по файлу postcalc_light_post_indexes.txt
 39:  * или таблице postcalc_light_post_indexes, либо наименование населенного пункта, которое проверяется по файлу
 40:  * postcalc_light_cities.txt или таблице postcalc_light_cities.
 41:  * 
 42:  * @param float $Weight Вес в граммах, от 1 до 100000.
 43:  * 
 44:  * @param float $Valuation Оценка почтового отправления в рублях, от 0 до 100000.
 45:  * 
 46:  * @param string $Country Двухбуквенный код страны, проверяется по файлу postcalc_light_countries.txt или
 47:  * таблице postcalc_light_countries. Если отличается от RU, поле $To игнорируется.
 48:  * 
 49:  * @return array|string В случае успеха возвращает массив с данными, полученными от сервера Postcalc.RU.
 50:  *  При ошибке возвращает строку с сообщением об ошибке.
 51:  * 
 52:  * @since 10.05.2014
 53:  * 
 54:  * @author Postcalc.RU <postcalc@mail.ru>
 55:  * 
 56:  * @version 2.0
 57:  * 
 58:  *
 59:  * 
 60:  */
 61: function postcalc_request($From, $To, $Weight, $Valuation=0, $Country='RU')
 62: {
 63:     global $arrPostcalcConfig;
 64:     extract($arrPostcalcConfig, EXTR_PREFIX_ALL, 'config');
 65:     // Обязательно! Проверяем данные - больше всего ошибочных запросов из-за неверных значений веса и оценки,
 66:     // из-за пропущенного поля "Куда".
 67:     if ( !is_numeric($Weight) || !($Weight > 0 && $Weight <= 100000) ) 
 68:                 return "Bec в граммах - число от 1 до 100000, десятичный знак - точка!";
 69:     if ( !is_numeric($Valuation) || !($Valuation >= 0 && $Valuation <= 100000) ) 
 70:                 return "Оценка в рублях - число от 0 до 100000, десятичный знак - точка!";
 71:     
 72:     // Отдельная функция проверяет правильность полей Откуда и Куда
 73:     //$From = mb_convert_case($From, MB_CASE_TITLE, $config_cs);
 74:     $PindexFrom = postcalc_get_default_ops($From);
 75:     if ( !$PindexFrom ) 
 76:             return "Поле 'Откуда': '$From' - не является допустимым индексом, названием региона или центра региона!";
 77:     
 78:     
 79:     //$To = mb_convert_case($To, MB_CASE_TITLE, $config_cs);
 80:     $PindexTo = postcalc_get_default_ops($To);
 81:     if ( !$PindexTo ) 
 82:             return "Поле 'Куда': '$To' - не является допустимым индексом, названием региона или центра региона!";
 83:     // Если установлен флаг $config_city_as_pindex, то в запрос подставляем почтовый индекс по умолчанию.
 84:     // Если флаг $config_city_as_pindex не установлен, переводим название нас.пункта в "процентную" кодировку.
 85:     $From = ( $config_city_as_pindex ) ? $PindexFrom : rawurlencode($From);  
 86:     $To = ( $config_city_as_pindex ) ? $PindexTo : rawurlencode($To);
 87:     
 88:     $Country = mb_convert_case($Country, MB_CASE_TITLE, $config_cs);
 89:     if ( !postcalc_arr_from_txt('postcalc_light_countries.txt', $Country, 1) ) 
 90:             return "Код страны '$Country' не найден в базе стран postcalc_light_countries.txt!";
 91: 
 92:     // Формируем запрос со всеми необходимыми переменными. 
 93:     $QueryString  = "st=$config_st&ml=$config_ml";
 94:     $QueryString .= "&f=$From&t=$To&w=$Weight&v=$Valuation&c=$Country";
 95:     $QueryString .= "&o=php&sw=PostcalcLight_2.0&cs=$config_cs";
 96:     if ( $config_d != 'now' ) $QueryString .= "&d=$config_d";
 97:     if ( $config_ib != 'f' ) $QueryString .= "&ib=$config_ib";
 98:     if ( $config_r != 0.01 ) $QueryString .= "&r=$config_r";
 99:     if ( $config_pr > 0 ) $QueryString .= "&pr=$config_pr";    
100:     if ( $config_pk > 0 ) $QueryString .= "&pk=$config_pk";  
101:      
102:     // Название файла - префикс postcalc_ плюс хэш строки запроса
103:     $CacheFile = "$config_cache_dir/postcalc_".md5($QueryString).'.txt';
104:     // Сборка мусора. Удаляем все файлы, которые подходят под маску, старше POSTCALC_CACHE_VALID секунд 
105:     $arrCacheFiles = glob( "$config_cache_dir/postcalc_*.txt" );
106:     $Now = time();
107:     foreach ( $arrCacheFiles as $fileObj ) 
108:         if ( $Now-filemtime($fileObj) > $config_cache_valid ) unlink( $fileObj );
109:     
110:     // Если существует файл кэша для данной строки запроса, просто зачитываем его
111:     if ( file_exists($CacheFile) ) {
112:         return  unserialize( file_get_contents($CacheFile) ); 
113:     } else {
114:          // Формируем опции запроса. Это _необязательно_, однако упрощает контроль и отладку
115:         $arrOptions = array('http' =>
116:           array( 'header'  => 'Accept-Encoding: gzip',
117:                  'timeout' => $config_timeout, 
118:                  'user_agent' => 'PostcalcLight_2.0 '.phpversion() 
119:                )
120:         );
121:         $TS=microtime(1);
122:         // Опрашиваем в цикле сервера Postcalc.RU, пока не получаем ответ
123:         $ConnectOK=0;
124:         foreach ( $config_servers as $Server ) {
125:             // Запрос к серверу. Сохраняем ответ в переменной $Response. 
126:             // При ошибке соединения опрашиваем следующий сервер в цепочке.
127:             if ( !$Response = file_get_contents("http://$Server/?$QueryString", false , stream_context_create($arrOptions)) ) {
128:                   // === ОБРАБОТКА ОШИБОК СОЕДИНЕНИЯ                  
129:                   // Журнал ошибок соединения, поля разделены табуляцией: 
130:                   // метка времени, сервер, истекшее время с начала сессии (т.е. всех запросов), краткое сообщение об ошибке, полное сообщение об ошибке
131:                   if ( $config_error_log && count(error_get_last()) ) {
132:                         $ErrorLog = "$config_cache_dir/postcalc_error_" .date('Y-m') .'.log';
133:                         $arrError = error_get_last(); 
134:                         $PHPErrorMessage = $arrError['message'];
135:                         // Отрезаем конец сообщения PHP, где сообщается причина проблемы
136:                         $ErrMessage = substr( $PHPErrorMessage, strrpos( $PHPErrorMessage,':')+2 );
137:                         $fp_log = fopen($ErrorLog,'a');
138:                         fwrite($fp_log, date('Y-m-d H:i:s') ."\t$Server\t" .number_format((microtime(1)-$TS),3) ."\t$ErrMessage\t$PHPErrorMessage\n");
139:                         fclose($fp_log);
140:                         if ( $config_error_log_send > 0 ) {
141:                             $fp_log = fopen($ErrorLog,'r');
142:                             // Последовательно идем по логу и сохраняем в переменной $MailMessage фрагмент не более $config_error_log_send строк
143:                             $MailMessage = '';  $send_log=false;  $line_counter = 0;
144:                             while ( ($line = fgets($fp_log)) !== false) {
145:                                 $line_counter++;
146:                                 if ( $send_log ) {
147:                                     $MailMessage = '';
148:                                     $send_log=false;
149:                                 }
150:                                 $MailMessage .= $line;
151:                                 // Если в $MailMessage оказалось ровно $config_error_log_send строк, сбрасываем счетчик строк и устанавливаем флаг $send_log.
152:                                 // Если следующее чтение вернуло конец файла, цикл будет прерван и фрагмент лога отослан по почте.  
153:                                 // Иначе фрагмент лога будет сброшен, как и флаг $send_log 
154:                                 if ( $line_counter % $config_error_log_send === 0 ) {
155:                                     $line_counter = 0;
156:                                     $send_log=true;
157:                                 }
158:                             }
159:                             fclose($fp_log);
160:                             if ( $send_log ) {
161:                                     $MailMessage="$_SERVER[SERVER_ADDR] [$_SERVER[SERVER_ADDR]]: ошибки соединения в скрипте $_SERVER[SCRIPT_FILENAME].\n"
162:                                             . "Подробности см. в http://$_SERVER[HTTP_HOST]".dirname($_SERVER['REQUEST_URI'])."/postcalc_light_stat.php\n"
163:                                             . "Последние строки ($config_error_log_send) из журнала ошибок:\n\n"
164:                                             . $MailMessage;
165:                                     mail($config_ml, 
166:                                          "$_SERVER[SERVER_ADDR] [$_SERVER[SERVER_ADDR]]: connection errors in postcalc_light_lib",
167:                                          $MailMessage,
168:                                          "Content-Transfer-Encoding: 8bit\nContent-Type: text/plain; charset=$config_cs\n");
169:                             }
170:                         }
171:                   }
172:                   // === КОНЕЦ ОБРАБОТКИ ОШИБОК СОЕДИНЕНИЯ
173:                   continue;
174:             }
175:                 $ConnectOK = 1;      
176:                 break;
177:         }
178:         if ( !$ConnectOK )  return 'Не удалось соединиться ни с одним из следующих серверов postcalc.ru: '.implode(',',$config_servers).'. Проверьте соединение с Интернетом.';
179:         
180:         $ResponseSize = strlen( $Response );
181:         
182:         // Если поток сжат, разархивируем его
183:         if ( substr($Response, 0, 3) == "\x1f\x8b\x08" ) 
184:                 $Response = gzinflate( substr($Response, 10, -8) );
185: 
186:         // Переводим ответ сервера в массив PHP
187:         if ( !$arrResponse = unserialize($Response) ) 
188:                 return "Получены странные данные. Ответ сервера:\n$Response";
189: 
190:         // Обработка возможной ошибки
191:         if ( $arrResponse['Status'] != 'OK' ) 
192:                 return "Сервер вернул ошибку: $arrResponse[Status]!";
193:                
194:         // Журнал успешных соединений, поля разделены табуляцией: 
195:         // метка времени, сервер, затраченное время, размер ответа, строка запроса
196:         if ( $config_log ) {
197:             $fp_log = fopen("$config_cache_dir/postcalc_light_" .date('Y-m') .'.log','a');
198:             fwrite($fp_log,date('Y-m-d H:i:s')."\t$Server\t" .number_format((microtime(1)-$TS),3) ."\t$ResponseSize\t$QueryString\n");
199:             fclose($fp_log);
200:         }
201:         // Успешный ответ пишем в кэш
202:         file_put_contents($CacheFile, $Response);
203:         
204:     return $arrResponse;
205:     }
206:  
207: }
208: 
209: /**
210:  * Функция проверки правильности отправителя или получателя. Принимает либо 6-значный индекс,
211:  * либо название населенного пункта из файла postcalc_light_cities.txt или таблицы postcalc_light_cities. 
212:  * Например: 'Москва', 'Абагур (Новокузнецк)', 'Абрамцево, Московская область, Сергиево-Посадский район'.
213:  * 
214:  * Возвращает 6-значный индекс ОПС, если не найдено - false. 
215:  *  
216:  * Если передан 6-значный индекс, проверка идет по текстовому файлу postcalc_light_post_indexes.txt
217:  * или таблице postcalc_light_post_indexes, где находятся все почтовые индексы России в формате 
218:  * индекс ОПС - название ОПС из "эталонного справочника Почты России".
219:  * 
220:  * <code>
221:  * $From = 'Сергиев Посад';
222:  * 
223:  * $postIndex = postcalc_get_default_ops($From);
224:  * 
225:  * if ( !$postIndex ) echo "'$From' не является допустимым индексом, названием региона или центра региона!";
226:  * </code>
227:  * 
228:  * @param string $FromTo Проверяемое значение 
229:  * @return string  При ошибке возвращает false, иначе - шестизначный индекс ОПС.
230:  * 
231:  * @uses postcalc_arr_from_txt() Запрашивает массив, созданный из текстового файла.
232:  */
233: function postcalc_get_default_ops( $FromTo )
234: {
235:     if ( !$FromTo ) return false;
236:      
237:     if ( preg_match('/^[1-9][0-9]{5}$/',$FromTo) ) {
238:         // Это 6-значный индекс. 
239:         $isPindex = true;
240:         $arr = postcalc_arr_from_txt('postcalc_light_post_indexes.txt', $FromTo);
241:     } else {
242:         // Это любое другое сочетание букв и цифр
243:         $isPindex = false;
244:         $arr = postcalc_arr_from_txt('postcalc_light_cities.txt', $FromTo);
245:     }
246:     // Ищем точное совпадение $FromTo и ключа в массиве. 
247:     if ( isset($arr[$FromTo]) ) 
248:         return ($isPindex) ? $FromTo : $arr[$FromTo];
249:    
250:     return false;
251: }
252: /**
253:  * Функция генерирует массив PHP либо из текстового файла с данными,
254:  * либо из таблицы MySQL. 
255:  * 
256:  * В первом случае открывает файл $src_txt, в котором находятся данные в формате:
257:  * [ключ]\t[значение]\n. 
258:  * 
259:  * Во втором случае обращается к таблице MySQL. Ее название совпадает с названием 
260:  * текстового файла без расширения .txt.
261:  * 
262:  * Возвращает массив. Параметр search - совпадение с началом ключа. Если пустая 
263:  * строка (по умолчанию) - возвращает все строки. 
264:  * 
265:  * @param string $src_txt Название файла с данными (включая расширение .txt).
266:  * @param string $search Совпадение с началом ключа. Если пустая строка - возвращает полную таблицу.
267:  * @param integer $limit Возвращать не более $limit элементов (для Autocomplete)
268:  * @return array Массив, если совпадений нет - пустой массив
269:  * 
270:  */
271: function postcalc_arr_from_txt($src_txt, $search = '', $limit = 0){
272:      global $arrPostcalcConfig;
273:      extract($arrPostcalcConfig, EXTR_PREFIX_ALL, 'config');
274:      $arr=array();
275:      // === Источник - таблица mysql
276:      if ( $config_source == 'mysql' ) {
277:          $mysql = new MySQLi($config_mysql_host, $config_mysql_user, $config_mysql_pass, $config_mysql_db);
278:          $TableName = basename($src_txt, '.txt');
279:           // Небольшой хак, чтобы установить имя ключевого поля.
280:              if ( $TableName == 'postcalc_light_cities' ) { 
281:                  $KeyField = 'city';
282:                  $OrderField = 'city';
283:              } elseif ( $TableName == 'postcalc_light_post_indexes'  ) {
284:                  $KeyField = 'pindex';
285:                  $OrderField = 'pindex';
286:              } elseif ( $TableName == 'postcalc_light_countries' ) {
287:                  $KeyField = 'iso2';
288:                  $OrderField = 'country';
289:              }
290:          $Limit = ( $limit ) ? "LIMIT $limit" : '';
291:          $Where = ( $search ) ? "WHERE $KeyField LIKE '$search%'" : '' ;
292:          $Order = "ORDER BY $OrderField";
293:          echo "SELECT * FROM $TableName $Where $Limit\n";
294:          // Устанавливаем правильный набор символов для запроса MySQL
295:          if ( stripos($config_cs, 'utf') !== false ) { 
296:              $MySQL_Charset = 'UTF8';
297:          } else if ( stripos($config_cs, '1251') !== false ) {
298:              $MySQL_Charset = 'cp1251';
299:          }
300:          $stmt = $mysql->query("SET NAMES $MySQL_Charset");
301:          $stmt = $mysql->query("SELECT * FROM $TableName $Where $Order $Limit");
302:          while ( $row = $stmt->fetch_row() ) 
303:              $arr[$row[0]] = $row[1];
304:          $mysql->close();
305:          return $arr;
306:      }
307:      // === Источник - текстовый файл.
308:      $src_idx = basename($src_txt, 'txt'). 'idx';
309:      $src_txt = $config_txt_dir. '/' .$src_txt;
310:      $src_idx = $config_txt_dir. '/' .$src_idx;
311:      $search =  mb_convert_case($search, MB_CASE_LOWER, $config_cs);
312:      
313:      // == Если нет файла индекса или фильтр отсутствует, идем полным перебором 
314:      if ( !file_exists($src_idx) || $search == '' ) {
315:          $fp = fopen($src_txt, 'r');
316:          $counter = 0;
317:          while ( ( $line = fgets($fp) ) !== false ) {
318:              list($key, $value) = explode("\t", $line);
319:              if (  $search == '' || 
320:                   ( $search != '' && mb_stripos($key, $search, 0, $config_cs) === 0 ) 
321:                 ) {
322:                $arr[$key] = trim($value);
323:                if ($limit > 0 && ++$counter >= $limit) break;
324:              } 
325:          }
326:          fclose($fp);
327:          return $arr;
328:      } 
329:      // == Индексный файл есть.
330:      $string_idx = file_get_contents($src_idx);
331:      // Начало совпадения
332:      $pos = mb_strpos(
333:                        $string_idx,
334:                        // Берем два первых символа
335:                       "\n".mb_substr($search, 0, 2, $config_cs), 
336:                        0,  
337:                        $config_cs
338:              );
339:      $idx_len = mb_strlen($string_idx, $config_cs);
340:      // Конец строки с совпадением
341:      $pos_line_end = mb_strpos($string_idx, "\n", $pos + 1, $config_cs);
342:      $s = mb_substr($string_idx, $pos + 1, $pos_line_end - $pos - 1, $config_cs);
343:      // Получили сдвиг в оригинальном файле.
344:      list($tmp, $offset) = explode("\t", $s);
345:      // Теперь длина. 
346:      if ( $idx_len == $pos_line_end + 1 ) {
347:         // Если это последняя строка в файле индекса, то будем брать фрагмент до конца файла данных.
348:         // Берем любое большое число.
349:         $len = 1000000;
350:      } else {
351:         $pos = $pos_line_end + 1;
352:         $pos_line_end = mb_strpos($string_idx, "\n", $pos + 1, $config_cs);
353:         $s = mb_substr($string_idx, $pos + 1, $pos_line_end - $pos, $config_cs);
354:         list($tmp, $offset2) = explode("\t", $s);
355:         $len = $offset2 - $offset;
356:      }
357:      $fp = fopen($src_txt, 'r');    
358:      fseek($fp, $offset);
359:      $chunk = fread($fp, $len);
360:      fclose($fp);
361:      // Теперь делаем массив.
362:      $arr_tmp = explode("\n", trim($chunk));
363:      $counter = 0;
364:      foreach ($arr_tmp as $no => $line ) {
365:           list($key, $value) = explode("\t", $line);
366:           if (  $search == '' || 
367:                   ( $search != '' && mb_stripos($key, $search, 0, $config_cs) === 0 ) 
368:                 ) {
369:                $arr[$key] = trim($value);
370:                if ($limit > 0 && ++$counter >= $limit) break;
371:              } 
372:          
373:      }
374:      
375:      return $arr;
376: }
377: 
378: /**
379:  * Вспомогательная функция, генерирует из массива содержимое списка <select> для веб-страницы.
380:  * 
381:  * Создает список стран, Россия в списке выделена:
382:  * <code>
383:  * postcalc_make_select(postcalc_arr_from_txt('postcalc_light_countries.txt'),'RU');
384:  * </code>
385:  * 
386:  * @ignore
387:  * @param array $arrList Ключи массива становятся value в тэге <option>, значения массива становятся видимыми элементами списка.
388:  * @param string $defaultValue Это значение будет выделено (атрибут selected).
389:  * @return string Готовый список для вставки на веб-странице между тэгами <select> и </select>.
390:  */
391: function postcalc_make_select($arrList,$defaultValue)
392: {
393: $Out='';
394:    foreach ($arrList as $value=>$label) {
395:         $Out.= "<option value='$value'";
396:         $Out.= ($value == $defaultValue) ? ' selected' : ''; 
397:         $Out.= ">$label</option>\n";
398:     }
399: return $Out;
400: }
401: /**
402:  * Автодополнение для полей "Откуда" и "Куда" на веб-странице. Работает с виджетом jQuery Autocomplete.
403:  * 
404:  * Внимание! Входные данные ожидаются всегда в кодировке UTF-8. 
405:  * jQuery Autocomplete эту кодировку обеспечивает автоматически, в остальных случаях можно применять
406:  * функцию javascript encodeURIComponent().
407:  * 
408:  * Возвращает массив JSON для непосредственного использования в виджете jQuery Autocomplete в кодировке UTF-8.
409:  * 
410:  *
411:  * @param string $post_index Начало почтового индекса или населенного пункта.
412:  * @param integer $limit Максимальное число элементов в списке.
413:  * @return mixed Объект JSON для непосредственного использования в виджете jQuery Autocomplete.
414:  * 
415:  * @uses postcalc_arr_from_txt() Запрашивает функцию postcalc_arr_from_txt() для получения массива, сгенерированного из текстового файла.
416:  */
417: function postcalc_autocomplete($post_index, $limit = 10)
418: {
419:     global $arrPostcalcConfig;
420:     $Charset = $arrPostcalcConfig['cs'];
421:     $arr = array();
422:     // Не менее 3 начальных символов должны быть цифрами
423:     if ( preg_match("/\d{3,}/", $post_index) ) {
424:         $arr_indexes = postcalc_arr_from_txt('postcalc_light_post_indexes.txt', $post_index, $limit);
425:         foreach ($arr_indexes as $pindex => $opsname) 
426:             $arr[] = array( 
427:                 'label' => $pindex.' '.mb_convert_encoding($opsname, 'UTF-8', $Charset),
428:                 'value' => $pindex
429:                 );
430:       } else {
431:         // Все данные с веб-страницы поступают в UTF-8
432:         $post_index = mb_convert_case($post_index, MB_CASE_TITLE, 'UTF-8');
433:         // Преобразуем в текущую кодировку библиотеки
434:         $post_index = mb_convert_encoding($post_index,$Charset, 'UTF-8' );
435:         $arr_cities = postcalc_arr_from_txt('postcalc_light_cities.txt', $post_index, $limit);
436:         
437:         
438:         foreach ($arr_cities as $city => $default_ops) {
439:              $arr[]=array(
440:                     'label' =>  mb_convert_encoding($city, 'UTF-8', $Charset),
441:                     'value' =>  mb_convert_encoding($city, 'UTF-8', $Charset)
442:              );
443:         }
444:         
445:     }
446:     
447:   return json_encode($arr);
448: }
449: 
450: /**
451:  * Функция генерирует из журналов соединений массив PHP. Используется в postcalc_light_stat.php. 
452:  * Возвращаемый массив может быть использован и для самостоятельного анализа.
453:  * 
454:  * Открывает в цикле все файлы, которые расположены в cache_dir и имеют название вида postcalc_light_YYYY-MM.log,
455:  * возвращает массив, где данные сгруппированы по дням: ключ массива - дата в формате YYYY-MM-DD, 
456:  * значения: число обращений за сутки num_requests, среднее время запроса time_elapsed, средний размер ответа size.
457:  *  
458:  * @global array $arrPostcalcConfig
459:  * @return array
460:  */
461: function postcalc_get_stat_arr() {
462:    global $arrPostcalcConfig;
463:    $postcalc_config_cache_dir =  $arrPostcalcConfig['cache_dir'];
464:    $arrStat=array();
465: foreach (glob("$postcalc_config_cache_dir/postcalc_light_*.log") as $logfile ) {
466:     $fp_log=fopen($logfile,'r');
467:     while ( $logline = fgets($fp_log)) {
468:         list($date_time,$server,$time_elapsed,$size,$query_string)=explode("\t",$logline);
469:         $date = substr($date_time, 0, 10);
470:         if ( isset($arrStat[$date]) ) {
471:             $arrStat[$date]['time_elapsed'] += $time_elapsed;
472:             $arrStat[$date]['size'] += $size;
473:             $arrStat[$date]['num_requests']++;
474:         } else {
475:             $arrStat[$date]['time_elapsed'] = $time_elapsed;
476:             $arrStat[$date]['size'] = $size;
477:             $arrStat[$date]['num_requests'] = 1;
478:             $arrStat[$date]['errors'] = 0;
479:         }
480:     }
481:     fclose($fp_log);
482: }
483: // Дополняем статистикой по ошибкам
484: foreach (glob("$postcalc_config_cache_dir/postcalc_error_*.log") as $logfile ) {
485:     $fp_log=fopen($logfile,'r');
486:     while ( $logline = fgets($fp_log)) {
487:         list($date_time,$server,$time_elapsed,$error_short,$error_full)=explode("\t",$logline);
488:         $date = substr($date_time, 0, 10);
489:         if ( isset($arrStat[$date]['errors']) ) {
490:             $arrStat[$date]['errors'] += 1;
491:         } else {
492:             $arrStat[$date]['errors'] = 1;
493:         }
494:     }
495:     fclose($fp_log);
496: }
497: 
498: // Теперь проходимся по всему массиву и вычисляем среднее арифметическое 
499: // для size (округляем до целого) и time_elapsed (оставляем 3 знака после запятой).
500: foreach ( $arrStat as $date => $arr_values ) {
501:     if ( isset($arrStat[$date]['time_elapsed']) )
502:         $arrStat[$date]['time_elapsed'] = number_format(($arrStat[$date]['time_elapsed']/$arrStat[$date]['num_requests']),3,'.','');
503:     if ( isset($arrStat[$date]['size']) )
504:         $arrStat[$date]['size'] = round($arrStat[$date]['size']/$arrStat[$date]['num_requests'], 0);
505: }
506: 
507: return $arrStat;
508: }
509: 
Библиотека и клиент PostcalcLight документация по API сгенерирована ApiGen 2.8.0