Files
erv-ticket-dev/submit.php
Fedor 2c516362df feat: Secure SMS verification with Redis (Predis)
- Added Predis library for Redis connection (no PHP extension required)
- Server-side SMS code generation and storage in Redis
- Rate limiting and brute-force protection
- Integration with n8n webhook for SMS sending
- Environment variables moved to .env file
- Fixed policy verification endpoint
- Added file-based fallback if Redis unavailable
2026-01-15 15:40:13 +03:00

346 lines
17 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
// Увеличиваем лимиты для больших файлов
set_time_limit(300); // 5 минут
ini_set('max_execution_time', 300);
ini_set('memory_limit', '512M');
ini_set('post_max_size', '100M');
ini_set('upload_max_filesize', '50M');
// Логирование для отладки - В САМОМ НАЧАЛЕ
$log_file = __DIR__ . '/logs/submit.log';
$log_dir = dirname($log_file);
if (!is_dir($log_dir)) {
mkdir($log_dir, 0755, true);
}
function log_message($message) {
global $log_file;
$timestamp = date('Y-m-d H:i:s');
file_put_contents($log_file, "[$timestamp] $message\n", FILE_APPEND);
}
log_message("=== submit.php ВЫЗВАН ===");
log_message("REQUEST_METHOD: " . ($_SERVER['REQUEST_METHOD'] ?? 'не определен'));
log_message("REQUEST_URI: " . ($_SERVER['REQUEST_URI'] ?? 'не определен'));
// Отправляем заголовки сразу, чтобы избежать ошибок
if (!headers_sent()) {
header('Content-Type: application/json; charset=utf-8');
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: POST, OPTIONS');
header('Access-Control-Allow-Headers: Content-Type');
}
// Обработка preflight запроса
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
log_message("OPTIONS запрос - завершаем");
http_response_code(200);
exit;
}
log_message("=== Начало обработки формы ===");
log_message("POST data keys: " . (empty($_POST) ? 'пусто' : implode(', ', array_keys($_POST))));
log_message("FILES data keys: " . (empty($_FILES) ? 'пусто' : implode(', ', array_keys($_FILES))));
try {
// URL вебхука n8n
$webhook_url = 'https://n8n.clientright.pro/webhook/aviaticketold';
// Получаем данные из POST
$form_data = [];
// Обрабатываем поля формы из appends[]
if (isset($_POST['appends']) && is_array($_POST['appends'])) {
foreach ($_POST['appends'] as $append_json) {
$append = json_decode($append_json, true);
if ($append && isset($append['ws_name'])) {
$form_data[$append['ws_name']] = $append['field_val'];
// Логируем поле source для отладки
if ($append['ws_name'] === 'source') {
log_message("Поле source получено: '" . $append['field_val'] . "'");
}
}
}
}
// Добавляем дополнительные поля
if (isset($_POST['lastname'])) {
$form_data['lastname'] = $_POST['lastname'];
}
if (isset($_POST['sub_dir'])) {
$form_data['sub_dir'] = $_POST['sub_dir'];
}
// Маппинг технических имен полей на понятные названия
// Используем name атрибуты из формы
$field_names_map = [
// name="polis" -> полис страхования
'polis' => 'insurance_policy',
// name="file_15_18" -> документы законного представителя несовершеннолетнего
'file_15_18' => 'legal_representative_documents',
// name="delay_docs" -> подтверждающие документы (посадочный талон, билет)
'delay_docs' => 'supporting_documents',
// name="cancel_confirmation" -> подтверждение уведомления об отмене рейса
'cancel_confirmation' => 'cancellation_notification',
// name="other_docs" -> документ удостоверяющий личность
'other_docs' => 'identity_document'
];
// Также маппинг для crmname (если используется)
$crmname_map = [
'cf_1885' => 'polis', // Номер полиса -> полис
// Добавим другие по мере необходимости
];
// Группируем файлы по полям с понятными названиями
$files_by_field = [];
// Обрабатываем загруженные файлы напрямую из $_FILES
// Файлы приходят в формате: field_name-0, field_name-1 и т.д.
if (!empty($_FILES)) {
foreach ($_FILES as $field_name => $file_data) {
// Проверяем, что это файл (не массив файлов)
if (is_array($file_data['tmp_name'])) {
continue;
}
// Пропускаем пустые файлы
if (empty($file_data['tmp_name']) || !is_uploaded_file($file_data['tmp_name'])) {
continue;
}
// Извлекаем имя поля и индекс из field_name-0
if (strpos($field_name, '-') !== false) {
$parts = explode('-', $field_name);
$actual_field_name = $parts[0];
$file_index = (int)end($parts);
} else {
$actual_field_name = $field_name;
$file_index = 0;
}
// Преобразуем техническое имя в понятное
$readable_field_name = $field_names_map[$actual_field_name] ?? $actual_field_name;
$field_description = $field_descriptions[$readable_field_name] ?? $readable_field_name;
// Группируем файлы по полям
if (!isset($files_by_field[$readable_field_name])) {
$files_by_field[$readable_field_name] = [
'original_field' => $actual_field_name, // Сохраняем оригинальное имя для справки
'description' => $field_description, // Понятное описание
'files' => []
];
}
$files_by_field[$readable_field_name]['files'][] = [
'index' => $file_index,
'name' => $file_data['name'],
'size' => $file_data['size'],
'type' => $file_data['type'],
'tmp_name' => $file_data['tmp_name']
];
log_message("Обработан файл: {$file_data['name']} ({$file_data['size']} байт) для поля '$field_description' ($readable_field_name, оригинал: $actual_field_name)");
}
}
// Формируем данные для отправки (multipart/form-data)
$post_data = [];
// Добавляем данные формы как JSON
$post_data['form_data'] = json_encode($form_data, JSON_UNESCAPED_UNICODE);
// Добавляем метаданные
$post_data['timestamp'] = date('Y-m-d H:i:s');
$post_data['ip'] = $_SERVER['REMOTE_ADDR'] ?? '';
$post_data['user_agent'] = $_SERVER['HTTP_USER_AGENT'] ?? '';
// Добавляем файлы, сгруппированные по полям
// Формат: files[field_name][0], files[field_name][1] и т.д.
foreach ($files_by_field as $field_name => $field_data) {
$files = $field_data['files']; // Получаем массив файлов из структуры
foreach ($files as $index => $file_info) {
$file_key = "files[{$field_name}][{$index}]";
$post_data[$file_key] = new CURLFile(
$file_info['tmp_name'],
$file_info['type'],
$file_info['name']
);
log_message("Добавлен файл в post_data: $file_key - {$file_info['name']} ({$file_info['size']} байт)");
}
// Добавляем метаданные для поля (количество файлов, общий размер)
$post_data["files_meta[{$field_name}][description]"] = $field_data['description'];
$post_data["files_meta[{$field_name}][original_field]"] = $field_data['original_field'];
$post_data["files_meta[{$field_name}][count]"] = count($files);
$post_data["files_meta[{$field_name}][total_size]"] = array_sum(array_column($files, 'size'));
}
log_message("Всего файлов добавлено в post_data: " . count(array_filter($post_data, function($v) { return $v instanceof CURLFile; })));
// Логируем структуру (без файлов)
$log_data = [
'form_data' => $form_data,
'files_structure' => array_map(function($field_data) {
return [
'description' => $field_data['description'],
'original_field' => $field_data['original_field'],
'files_count' => count($field_data['files']),
'files' => array_map(function($f) {
return ['name' => $f['name'], 'size' => $f['size'], 'type' => $f['type']];
}, $field_data['files'])
];
}, $files_by_field),
'timestamp' => $post_data['timestamp']
];
log_message("Данные для отправки: " . json_encode($log_data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT));
// Отправляем на вебхук n8n как multipart/form-data
$ch = curl_init($webhook_url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
curl_setopt($ch, CURLOPT_TIMEOUT, 300); // 5 минут для больших файлов
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 30);
curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
// Для больших файлов отключаем буферизацию
curl_setopt($ch, CURLOPT_BUFFERSIZE, 65536); // 64KB буфер
// Отключаем Expect header для предотвращения HTTP 100 Continue
// Это важно для больших файлов - без этого сервер может запросить подтверждение
$headers = array('Expect:');
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
// Логируем информацию о запросе
$total_size = 0;
foreach ($post_data as $key => $value) {
if ($value instanceof CURLFile) {
$total_size += filesize($value->getFilename());
}
}
log_message("Начинаем отправку на n8n. URL: $webhook_url");
log_message("Общий размер файлов: " . round($total_size / 1024 / 1024, 2) . " MB");
log_message("Количество полей в запросе: " . count($post_data));
// Включаем verbose режим для отладки (в лог не пишем, только для отладки)
$start_time = microtime(true);
$response = curl_exec($ch);
$end_time = microtime(true);
$duration = round($end_time - $start_time, 2);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$curl_error = curl_error($ch);
$curl_errno = curl_errno($ch);
$curl_info = curl_getinfo($ch);
curl_close($ch);
log_message("Время выполнения CURL: {$duration} сек");
log_message("HTTP код ответа: $http_code");
log_message("Размер отправленных данных: " . ($curl_info['size_upload'] ?? 0) . " байт (" . round(($curl_info['size_upload'] ?? 0) / 1024 / 1024, 2) . " MB)");
log_message("Размер полученного ответа: " . ($curl_info['size_download'] ?? 0) . " байт");
log_message("Скорость загрузки: " . round(($curl_info['speed_upload'] ?? 0) / 1024, 2) . " KB/s");
log_message("Ответ от n8n: " . substr($response, 0, 500)); // Первые 500 символов
log_message("CURL ошибка: " . ($curl_error ?: 'нет') . ", код ошибки: $curl_errno");
// Проверяем, были ли данные отправлены
if (($curl_info['size_upload'] ?? 0) > 0) {
log_message("✓ Данные были отправлены на сервер (" . round(($curl_info['size_upload'] ?? 0) / 1024 / 1024, 2) . " MB)");
} else {
log_message("✗ Данные НЕ были отправлены на сервер (size_upload = 0)");
}
// Обрабатываем ошибки CURL
// CURLE_ABORTED_BY_CALLBACK (42) может возникать при больших файлах, но данные могут быть отправлены
$is_aborted_callback = false;
if ($curl_error) {
log_message("Ошибка CURL: $curl_error (код: $curl_errno)");
// CURLE_ABORTED_BY_CALLBACK = 42
if ($curl_errno == 42) {
$is_aborted_callback = true;
log_message("Предупреждение: CURL callback прерван (возможно из-за размера файлов или таймаута)");
log_message("Для больших файлов это может быть нормальным - данные могли быть отправлены");
} else if (empty($response) && $http_code == 0) {
throw new Exception("Ошибка отправки данных: $curl_error");
}
}
// Проверяем HTTP код ответа
if ($http_code >= 200 && $http_code < 300) {
// Успешный ответ
$response_data = json_decode($response, true);
if ($response_data === null) {
$response_data = $response; // Если ответ не JSON
}
log_message("Успешная отправка");
echo json_encode([
'success' => true,
'message' => 'Данные успешно отправлены',
'response' => $response_data
], JSON_UNESCAPED_UNICODE);
} else if ($http_code == 0) {
// HTTP код 0 - может быть из-за таймаута или больших файлов
if ($is_aborted_callback) {
// CURLE_ABORTED_BY_CALLBACK - данные могли быть отправлены, но ответ не получен
log_message("HTTP код 0 с CURLE_ABORTED_BY_CALLBACK - считаем успешным (данные могли быть отправлены)");
echo json_encode([
'success' => true,
'message' => 'Данные отправлены (ответ не получен из-за размера файлов, но данные могли быть доставлены)',
'warning' => 'Ответ от сервера не получен, но данные могли быть успешно отправлены'
], JSON_UNESCAPED_UNICODE);
} else if (!empty($response)) {
// HTTP код 0, но есть ответ - возможно, данные отправились
log_message("HTTP код 0, но есть ответ - считаем успешным");
echo json_encode([
'success' => true,
'message' => 'Данные отправлены (HTTP код не получен, но ответ есть)',
'response' => $response
], JSON_UNESCAPED_UNICODE);
} else {
log_message("Ошибка HTTP: $http_code, ответ пуст");
throw new Exception("Ошибка сервера: HTTP $http_code, ответ пуст");
}
} else {
log_message("Ошибка HTTP: $http_code");
throw new Exception("Ошибка сервера: HTTP $http_code");
}
} catch (Exception $e) {
log_message("Исключение: " . $e->getMessage());
log_message("Трассировка: " . $e->getTraceAsString());
// Устанавливаем код ответа только если заголовки еще не отправлены
if (!headers_sent()) {
http_response_code(500);
}
echo json_encode([
'success' => false,
'message' => 'Ошибка обработки запроса',
'error' => $e->getMessage()
], JSON_UNESCAPED_UNICODE);
} catch (Error $e) {
// Обработка фатальных ошибок PHP 7+
log_message("Фатальная ошибка: " . $e->getMessage());
log_message("Трассировка: " . $e->getTraceAsString());
if (!headers_sent()) {
http_response_code(500);
}
echo json_encode([
'success' => false,
'message' => 'Критическая ошибка обработки запроса',
'error' => $e->getMessage()
], JSON_UNESCAPED_UNICODE);
}
log_message("=== Конец обработки формы ===\n");