полис страхования '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");