Files
ptp/forms/forms_helper.php
T
Kirik ac334495b9 Make drone serial number field mandatory in trip form
- Added required validation for bvs_number field in send_plan.php
- Updated form label to show asterisk (*) indicating required field
- Implemented file attachment support in email notifications (multipart MIME)
- Files are now temporarily saved and automatically deleted after email is sent
2025-11-20 00:41:48 +01:00

445 lines
17 KiB
PHP
Raw 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
/**
* Вспомогательные функции для обработки форм
* Поддержка настроек из .env файла
*/
/**
* Загрузка переменных из .env файла
*/
function load_env_file($file_path = '../.env') {
if (!file_exists($file_path)) {
return false;
}
$lines = file($file_path, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
foreach ($lines as $line) {
// Пропускаем комментарии
if (strpos(trim($line), '#') === 0) {
continue;
}
// Убираем inline комментарии (всё после #)
if (strpos($line, '#') !== false) {
$line = substr($line, 0, strpos($line, '#'));
}
// Проверяем что есть знак =
if (strpos($line, '=') === false) {
continue;
}
list($name, $value) = explode('=', $line, 2);
$name = trim($name);
$value = trim($value); // ВАЖНО: убирает пробелы до и после значения
if (!array_key_exists($name, $_SERVER) && !array_key_exists($name, $_ENV)) {
putenv(sprintf('%s=%s', $name, $value));
$_ENV[$name] = $value;
$_SERVER[$name] = $value;
}
}
return true;
}
/**
* Получение настроек форм из .env
*/
function get_forms_settings() {
return [
'send_email' => getenv('FORMS_SEND_EMAIL') === 'true',
'send_telegram' => getenv('FORMS_SEND_TELEGRAM') === 'true',
'save_json' => getenv('FORMS_SAVE_JSON') === 'true',
'notifications' => getenv('FORMS_NOTIFICATIONS') === 'true',
'telegram_bot_token' => getenv('TELEGRAM_BOT_TOKEN'),
'telegram_chat_id' => getenv('TELEGRAM_ADMIN_CHAT_ID'),
'encryption_key' => getenv('FORMS_ENCRYPTION_KEY') ?: 'default_key_change_me'
];
}
/**
* Простое шифрование данных для безопасного хранения
* Совместимо с Python Crypto.Cipher.AES + unpad
*/
function encrypt_data($data, $key) {
$json = json_encode($data, JSON_UNESCAPED_UNICODE);
$iv = random_bytes(16);
// openssl_encrypt с параметром 0 сам добавляет PKCS7 padding и возвращает base64
$encrypted_b64 = openssl_encrypt($json, 'AES-256-CBC', hash('sha256', $key), 0, $iv);
// Декодируем base64 чтобы получить бинарные данные
$encrypted_raw = base64_decode($encrypted_b64);
// Добавляем IV в начало и кодируем все вместе
return base64_encode($iv . $encrypted_raw);
}
/**
* Расшифровка данных
* Совместимо с Python Crypto.Cipher.AES + unpad
*/
function decrypt_data($encrypted_data, $key) {
$data = base64_decode($encrypted_data);
$iv = substr($data, 0, 16);
$encrypted_raw = substr($data, 16);
// Кодируем обратно в base64 для openssl_decrypt
$encrypted_b64 = base64_encode($encrypted_raw);
// openssl_decrypt с параметром 0 сам убирает PKCS7 padding
$decrypted = openssl_decrypt($encrypted_b64, 'AES-256-CBC', hash('sha256', $key), 0, $iv);
return json_decode($decrypted, true);
}
/**
* Сохранение заявки в зашифрованный JSON файл
*/
function save_application_to_json($form_data, $form_type) {
$settings = get_forms_settings();
if (!$settings['save_json']) {
return true; // Сохранение отключено
}
// Создаем безопасную директорию для хранения (вне веб-доступа)
$secure_dir = '/var/secure/forms/';
if (!is_dir($secure_dir)) {
mkdir($secure_dir, 0700, true);
}
// Подготавливаем данные для сохранения
$application = [
'id' => uniqid('app_'),
'type' => $form_type,
'timestamp' => date('Y-m-d H:i:s'),
'data' => $form_data,
'status' => 'new'
];
try {
// Шифруем данные
$encrypted_data = encrypt_data($application, $settings['encryption_key']);
// Сохраняем в файл с временной меткой
$filename = $secure_dir . $form_type . '_' . date('Y-m-d') . '.json';
// Загружаем существующие данные или создаем новый массив
$existing_data = [];
if (file_exists($filename)) {
$file_content = file_get_contents($filename);
if ($file_content) {
$existing_data = json_decode($file_content, true) ?: [];
}
}
// Добавляем новую заявку
$existing_data[] = [
'id' => $application['id'],
'timestamp' => $application['timestamp'],
'encrypted_data' => $encrypted_data
];
// Сохраняем обновленный файл
file_put_contents($filename, json_encode($existing_data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT));
chmod($filename, 0600); // Только владелец может читать
return $application['id'];
} catch (Exception $e) {
error_log("Ошибка сохранения заявки: " . $e->getMessage());
return false;
}
}
/**
* Отправка уведомления в Telegram
*/
function send_telegram_notification($form_data, $form_type) {
$settings = get_forms_settings();
if (!$settings['send_telegram'] || !$settings['notifications']) {
return true; // Отправка отключена
}
if (empty($settings['telegram_bot_token']) || empty($settings['telegram_chat_id'])) {
error_log("Telegram настройки не настроены");
return false;
}
// Подготавливаем сообщение
$message = "🔔 *Новая заявка*\n\n";
$message .= "📝 *Тип:* " . ($form_type === 'plan' ? 'Заявка на поездку' : 'Вопрос') . "\n";
$message .= "👤 *Имя:* " . $form_data['name'] . "\n";
$message .= "📧 *Email:* " . $form_data['email'] . "\n";
if ($form_type === 'plan') {
$message .= "📱 *Телефон:* " . ($form_data['phone'] ?: 'не указан') . "\n";
$message .= "✈️ *Поездка:* " . ($form_data['trip_period'] ?: 'не выбрана') . "\n";
$message .= "🚁 *БВС/Направление:* " . ($form_data['bvs_number'] ?: 'не указано') . "\n";
} else {
if (!empty($form_data['telegram'])) {
$message .= "📱 *Telegram:* " . $form_data['telegram'] . "\n";
}
if (!empty($form_data['phone'])) {
$message .= "☎️ *Телефон:* " . $form_data['phone'] . "\n";
}
$message .= "💬 *Тема:* " . $form_data['subject'] . "\n";
$message .= "📝 *Сообщение:* " . substr($form_data['message'], 0, 200) . "...\n";
}
$message .= "\n⏰ *Время:* " . date('Y-m-d H:i:s');
// Отправляем сообщение
$url = "https://api.telegram.org/bot" . $settings['telegram_bot_token'] . "/sendMessage";
$data = [
'chat_id' => $settings['telegram_chat_id'],
'text' => $message,
'parse_mode' => 'Markdown'
];
$context = stream_context_create([
'http' => [
'method' => 'POST',
'header' => 'Content-Type: application/x-www-form-urlencoded',
'content' => http_build_query($data)
]
]);
try {
$result = file_get_contents($url, false, $context);
$response = json_decode($result, true);
if ($response && $response['ok']) {
return true;
} else {
error_log("Ошибка отправки в Telegram: " . $result);
return false;
}
} catch (Exception $e) {
error_log("Исключение при отправке в Telegram: " . $e->getMessage());
return false;
}
}
/**
* Отправка уведомления на Email через msmtp с поддержкой файлов
*/
function send_email_notification($form_data, $form_type) {
$settings = get_forms_settings();
if (!$settings['send_email']) {
return true; // Отправка email отключена
}
// Получаем admin email из .env
$admin_email = getenv('ADMIN_EMAIL') ?: 'admin@example.com';
$from_email = getenv('SMTP_FROM_EMAIL') ?: getenv('SMTP_USERNAME') ?: 'noreply@yourdomain.com';
$from_name = getenv('SMTP_FROM_NAME') ?: 'PTP Robot';
// Формируем тему письма
$subject = ($form_type === 'plan')
? '🎫 Новая заявка на поездку - ' . $form_data['name']
: '❓ Новый вопрос - ' . ($form_data['subject'] ?? 'Без темы');
// Формируем тело письма
$message = "Новая заявка с сайта\n\n";
$message .= "Тип: " . ($form_type === 'plan' ? 'Заявка на поездку' : 'Вопрос') . "\n";
$message .= "Время: " . date('Y-m-d H:i:s') . "\n\n";
$message .= "=== ДАННЫЕ ЗАЯВИТЕЛЯ ===\n";
$message .= "Имя: " . $form_data['name'] . "\n";
$message .= "Email: " . $form_data['email'] . "\n";
if ($form_type === 'plan') {
$message .= "Телефон: " . ($form_data['phone'] ?: 'не указан') . "\n";
$message .= "Telegram: " . ($form_data['telegram'] ?: 'не указан') . "\n";
$message .= "Поездка: " . ($form_data['trip_period'] ?: 'не выбрана') . "\n";
$message .= "БВС/Направление: " . ($form_data['bvs_number'] ?: 'не указано') . "\n";
} else {
if (!empty($form_data['telegram'])) {
$message .= "Telegram: " . $form_data['telegram'] . "\n";
}
if (!empty($form_data['phone'])) {
$message .= "Телефон: " . $form_data['phone'] . "\n";
}
$message .= "Тема: " . $form_data['subject'] . "\n";
$message .= "\n=== СООБЩЕНИЕ ===\n";
$message .= $form_data['message'] . "\n";
}
// Проверяем наличие файла для отправки
$attachment_file = $form_data['bvs_file_path'] ?? null;
$has_attachment = $attachment_file && file_exists($attachment_file);
// Создаем boundary для multipart письма если есть файл
$boundary = "----=_Part_" . md5(time() . rand());
if ($has_attachment) {
// Заголовки письма с файлом (multipart)
$headers = "From: $from_name <$from_email>\r\n";
$headers .= "Reply-To: " . $form_data['email'] . "\r\n";
$headers .= "MIME-Version: 1.0\r\n";
$headers .= "Content-Type: multipart/mixed; boundary=\"$boundary\"\r\n";
$headers .= "X-Mailer: PHP/" . phpversion();
// Формируем multipart тело письма
$multipart_message = "--$boundary\r\n";
$multipart_message .= "Content-Type: text/plain; charset=UTF-8\r\n";
$multipart_message .= "Content-Transfer-Encoding: 8bit\r\n\r\n";
$multipart_message .= $message . "\r\n";
// Добавляем файл
$file_content = file_get_contents($attachment_file);
$file_name = basename($attachment_file);
// Извлекаем оригинальное имя файла (после bvs_)
if (preg_match('/bvs_\w+_(.+)$/', $file_name, $matches)) {
$file_name = $matches[1];
}
$multipart_message .= "--$boundary\r\n";
$multipart_message .= "Content-Type: application/pdf; name=\"$file_name\"\r\n";
$multipart_message .= "Content-Transfer-Encoding: base64\r\n";
$multipart_message .= "Content-Disposition: attachment; filename=\"$file_name\"\r\n\r\n";
$multipart_message .= chunk_split(base64_encode($file_content)) . "\r\n";
$multipart_message .= "--$boundary--\r\n";
$email_message = $multipart_message;
} else {
// Заголовки письма без файла
$headers = "From: $from_name <$from_email>\r\n";
$headers .= "Reply-To: " . $form_data['email'] . "\r\n";
$headers .= "Content-Type: text/plain; charset=UTF-8\r\n";
$headers .= "X-Mailer: PHP/" . phpversion();
$email_message = $message;
}
// Отправка письма
try {
error_log("=== Attempting mail() ===");
error_log("To: $admin_email | From: $from_email | Subject: $subject | Has attachment: " . ($has_attachment ? 'YES' : 'NO'));
$result = mail($admin_email, $subject, $email_message, $headers);
error_log("mail() returned: " . ($result ? 'TRUE' : 'FALSE'));
if ($result) {
error_log("Email отправлен успешно на: $admin_email");
// Удаляем временный файл после отправки
if ($has_attachment && file_exists($attachment_file)) {
unlink($attachment_file);
error_log("Временный файл удален: $attachment_file");
}
return true;
} else {
error_log("Ошибка отправки email на: $admin_email");
return false;
}
} catch (Exception $e) {
error_log("Исключение при отправке email: " . $e->getMessage());
return false;
}
}
/**
* Получение списка всех заявок (для Telegram бота)
*/
function get_all_applications() {
$settings = get_forms_settings();
$secure_dir = '/var/secure/forms/';
if (!is_dir($secure_dir)) {
return [];
}
$applications = [];
$files = glob($secure_dir . '*.json');
foreach ($files as $file) {
$file_content = file_get_contents($file);
if (!$file_content) continue;
$file_data = json_decode($file_content, true);
if (!$file_data) continue;
foreach ($file_data as $item) {
try {
$decrypted = decrypt_data($item['encrypted_data'], $settings['encryption_key']);
$applications[] = $decrypted;
} catch (Exception $e) {
error_log("Ошибка расшифровки заявки: " . $e->getMessage());
}
}
}
// Сортируем по времени (новые сначала)
usort($applications, function($a, $b) {
return strtotime($b['timestamp']) - strtotime($a['timestamp']);
});
return $applications;
}
/**
* Отметить заявку как рассмотренную
*/
function mark_application_as_reviewed($app_id) {
$settings = get_forms_settings();
$secure_dir = '/var/secure/forms/';
$files = glob($secure_dir . '*.json');
foreach ($files as $file) {
$file_content = file_get_contents($file);
if (!$file_content) continue;
$file_data = json_decode($file_content, true);
if (!$file_data) continue;
$updated = false;
foreach ($file_data as &$item) {
try {
$decrypted = decrypt_data($item['encrypted_data'], $settings['encryption_key']);
if ($decrypted['id'] === $app_id) {
$decrypted['status'] = 'reviewed';
$item['encrypted_data'] = encrypt_data($decrypted, $settings['encryption_key']);
$updated = true;
break;
}
} catch (Exception $e) {
continue;
}
}
if ($updated) {
file_put_contents($file, json_encode($file_data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT));
return true;
}
}
return false;
}
/**
* Очистка старых заявок (старше 30 дней)
*/
function cleanup_old_applications() {
$secure_dir = '/var/secure/forms/';
if (!is_dir($secure_dir)) return;
$files = glob($secure_dir . '*.json');
$cutoff_date = strtotime('-30 days');
foreach ($files as $file) {
// Извлекаем дату из имени файла
if (preg_match('/(\d{4}-\d{2}-\d{2})\.json$/', $file, $matches)) {
$file_date = strtotime($matches[1]);
if ($file_date < $cutoff_date) {
unlink($file);
error_log("Удален старый файл заявок: " . basename($file));
}
}
}
}
// Автоматическая очистка старых файлов (вызывается при каждом обращении к формам)
if (rand(1, 100) === 1) { // 1% вероятность
cleanup_old_applications();
}
?>