- Replace hardcoded BOT_TOKEN, CHAT_ID, TURNSTILE_SECRET, smtp_pass with require_once from /opt/www/sag24.ru/contact-config.php (outside webroot) - Convert email from plain text to HTML (text/html Content-Type) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
173 lines
7.8 KiB
PHP
173 lines
7.8 KiB
PHP
<?php
|
||
header('Content-Type: application/json');
|
||
header('Access-Control-Allow-Origin: https://sag24.ru');
|
||
header('Access-Control-Allow-Methods: POST');
|
||
header('Access-Control-Allow-Headers: Content-Type');
|
||
|
||
// Load secrets from outside webroot (not in git)
|
||
$configFile = dirname(__DIR__, 2) . '/contact-config.php';
|
||
if (file_exists($configFile)) {
|
||
require_once $configFile;
|
||
} else {
|
||
// Fallback values for local development (override via contact-config.php on server)
|
||
$BOT_TOKEN = getenv('TELEGRAM_BOT_TOKEN') ?: '';
|
||
$CHAT_ID = getenv('TELEGRAM_CHAT_ID') ?: '';
|
||
$TURNSTILE_SECRET = getenv('TURNSTILE_SECRET_KEY') ?: '';
|
||
$smtp_pass = getenv('SMTP_PASS') ?: '';
|
||
}
|
||
|
||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||
http_response_code(405);
|
||
echo json_encode(['error' => 'Method not allowed']);
|
||
exit;
|
||
}
|
||
|
||
// ─── Rate limiting (5 requests per minute per IP) ────────────────────────────
|
||
$ip = $_SERVER['HTTP_X_FORWARDED_FOR']
|
||
? explode(',', $_SERVER['HTTP_X_FORWARDED_FOR'])[0]
|
||
: ($_SERVER['REMOTE_ADDR'] ?? 'unknown');
|
||
$ip = trim($ip);
|
||
$rate_file = sys_get_temp_dir() . '/rl_' . md5($ip) . '.json';
|
||
$now = time();
|
||
$window = 60;
|
||
$max_req = 5;
|
||
$rate_data = file_exists($rate_file) ? json_decode(file_get_contents($rate_file), true) : [];
|
||
$rate_data = array_filter($rate_data ?? [], fn($t) => $t > $now - $window);
|
||
if (count($rate_data) >= $max_req) {
|
||
http_response_code(429);
|
||
echo json_encode(['error' => 'Too many requests. Please try again later.']);
|
||
exit;
|
||
}
|
||
$rate_data[] = $now;
|
||
file_put_contents($rate_file, json_encode(array_values($rate_data)));
|
||
|
||
$data = json_decode(file_get_contents('php://input'), true);
|
||
if (!$data) {
|
||
http_response_code(400);
|
||
echo json_encode(['error' => 'Invalid JSON']);
|
||
exit;
|
||
}
|
||
|
||
$name = htmlspecialchars(trim($data['name'] ?? ''), ENT_QUOTES);
|
||
$company = htmlspecialchars(trim($data['company'] ?? ''), ENT_QUOTES);
|
||
$phone = htmlspecialchars(trim($data['phone'] ?? ''), ENT_QUOTES);
|
||
$email = htmlspecialchars(trim($data['email'] ?? ''), ENT_QUOTES);
|
||
$message = htmlspecialchars(trim($data['message'] ?? ''), ENT_QUOTES);
|
||
$turnstileToken = trim($data['turnstileToken'] ?? '');
|
||
|
||
if (!$name || !$message) {
|
||
http_response_code(400);
|
||
echo json_encode(['error' => 'Missing required fields']);
|
||
exit;
|
||
}
|
||
|
||
// ─── Validation ───────────────────────────────────────────────────────────────
|
||
if ($email && !filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
||
http_response_code(400);
|
||
echo json_encode(['error' => 'Invalid email format']);
|
||
exit;
|
||
}
|
||
if (mb_strlen($name) > 100) { http_response_code(400); echo json_encode(['error' => 'Name too long']); exit; }
|
||
if (mb_strlen($company) > 200) { http_response_code(400); echo json_encode(['error' => 'Company too long']); exit; }
|
||
if (mb_strlen($email) > 254) { http_response_code(400); echo json_encode(['error' => 'Email too long']); exit; }
|
||
if (mb_strlen($phone) > 30) { http_response_code(400); echo json_encode(['error' => 'Phone too long']); exit; }
|
||
if (mb_strlen($message) > 5000) { http_response_code(400); echo json_encode(['error' => 'Message too long']); exit; }
|
||
|
||
// ─── Cloudflare Turnstile verification ───────────────────────────────────────
|
||
if ($TURNSTILE_SECRET) {
|
||
if (!$turnstileToken) {
|
||
http_response_code(400);
|
||
echo json_encode(['error' => 'Bot verification required.']);
|
||
exit;
|
||
}
|
||
$ch = curl_init('https://challenges.cloudflare.com/turnstile/v0/siteverify');
|
||
curl_setopt_array($ch, [
|
||
CURLOPT_POST => true,
|
||
CURLOPT_POSTFIELDS => http_build_query(['secret' => $TURNSTILE_SECRET, 'response' => $turnstileToken, 'remoteip' => $ip]),
|
||
CURLOPT_RETURNTRANSFER => true,
|
||
CURLOPT_TIMEOUT => 10,
|
||
]);
|
||
$tsResult = json_decode(curl_exec($ch), true);
|
||
curl_close($ch);
|
||
if (($tsResult['success'] ?? false) !== true) {
|
||
http_response_code(400);
|
||
echo json_encode(['error' => 'Bot verification failed. Please try again.']);
|
||
exit;
|
||
}
|
||
}
|
||
|
||
// ─── Telegram (best-effort, not fatal) ───────────────────────────────────────
|
||
|
||
$userAgent = $_SERVER['HTTP_USER_AGENT'] ?? 'unknown';
|
||
$referer = $_SERVER['HTTP_REFERER'] ?? '';
|
||
$country = $_SERVER['HTTP_CF_IPCOUNTRY'] ?? '';
|
||
|
||
$text = "🔔 <b>Заявка с sag24.ru</b>\n\n";
|
||
$text .= "👤 <b>Имя:</b> {$name}\n";
|
||
if ($company) $text .= "🏢 <b>Компания:</b> {$company}\n";
|
||
if ($email) $text .= "📧 <b>Email:</b> {$email}\n";
|
||
if ($phone) $text .= "📱 <b>Телефон:</b> {$phone}\n";
|
||
$text .= "\n💬 <b>Сообщение:</b>\n{$message}\n";
|
||
$text .= "\n📊 <b>Метаданные:</b>\n";
|
||
$text .= "🌍 IP: {$ip}\n";
|
||
if ($country) $text .= "🌐 Страна: {$country}\n";
|
||
if ($referer) $text .= "🔗 Источник: {$referer}\n";
|
||
$text .= "⏰ " . date('d.m.Y H:i', time() + 3 * 3600) . " MSK";
|
||
|
||
$ch = curl_init("https://tg-relay.it-resheniya-2018.workers.dev/bot{$BOT_TOKEN}/sendMessage");
|
||
curl_setopt_array($ch, [
|
||
CURLOPT_POST => true,
|
||
CURLOPT_POSTFIELDS => json_encode(['chat_id' => $CHAT_ID, 'text' => $text, 'parse_mode' => 'HTML']),
|
||
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
|
||
CURLOPT_RETURNTRANSFER => true,
|
||
CURLOPT_TIMEOUT => 10,
|
||
]);
|
||
curl_exec($ch);
|
||
curl_close($ch);
|
||
|
||
// ─── Email via SMTP (noreply@sag24.ru → info@sag24.ru) ───────────────────────
|
||
$smtp_host = 'mx.hhivp.com';
|
||
$smtp_port = 587;
|
||
$smtp_user = 'noreply@sag24.ru';
|
||
$mail_to = 'info@sag24.ru';
|
||
|
||
$subj_enc = "=?UTF-8?B?" . base64_encode("Заявка с sag24.ru от {$name}") . "?=";
|
||
$html_body = "<h2>Заявка с sag24.ru</h2>";
|
||
$html_body .= "<p><strong>Имя:</strong> {$name}</p>";
|
||
if ($company) $html_body .= "<p><strong>Компания:</strong> {$company}</p>";
|
||
if ($email) $html_body .= "<p><strong>Email:</strong> <a href=\"mailto:{$email}\">{$email}</a></p>";
|
||
if ($phone) $html_body .= "<p><strong>Телефон:</strong> {$phone}</p>";
|
||
$msg_html = nl2br($message);
|
||
$html_body .= "<p><strong>Сообщение:</strong><br>{$msg_html}</p>";
|
||
$html_body .= "<hr><p><small>IP: {$ip}";
|
||
if ($country) $html_body .= " | Страна: {$country}";
|
||
$html_body .= " | " . date('d.m.Y H:i', time() + 3 * 3600) . " MSK</small></p>";
|
||
|
||
try {
|
||
$smtp = fsockopen($smtp_host, $smtp_port, $errno, $errstr, 10);
|
||
if ($smtp) {
|
||
fgets($smtp, 512);
|
||
fwrite($smtp, "EHLO sag24.ru\r\n"); $caps = '';
|
||
while ($line = fgets($smtp, 512)) { $caps .= $line; if ($line[3] == ' ') break; }
|
||
fwrite($smtp, "STARTTLS\r\n"); fgets($smtp, 512);
|
||
stream_socket_enable_crypto($smtp, true, STREAM_CRYPTO_METHOD_TLS_CLIENT);
|
||
fwrite($smtp, "EHLO sag24.ru\r\n");
|
||
while ($line = fgets($smtp, 512)) { if ($line[3] == ' ') break; }
|
||
fwrite($smtp, "AUTH LOGIN\r\n"); fgets($smtp, 512);
|
||
fwrite($smtp, base64_encode($smtp_user) . "\r\n"); fgets($smtp, 512);
|
||
fwrite($smtp, base64_encode($smtp_pass) . "\r\n"); fgets($smtp, 512);
|
||
fwrite($smtp, "MAIL FROM:<{$smtp_user}>\r\n"); fgets($smtp, 512);
|
||
fwrite($smtp, "RCPT TO:<{$mail_to}>\r\n"); fgets($smtp, 512);
|
||
fwrite($smtp, "DATA\r\n"); fgets($smtp, 512);
|
||
fwrite($smtp, "From: noreply@sag24.ru\r\nTo: {$mail_to}\r\nSubject: {$subj_enc}\r\n");
|
||
fwrite($smtp, "MIME-Version: 1.0\r\nContent-Type: text/html; charset=UTF-8\r\n\r\n");
|
||
fwrite($smtp, $html_body . "\r\n.\r\n"); fgets($smtp, 512);
|
||
fwrite($smtp, "QUIT\r\n");
|
||
fclose($smtp);
|
||
}
|
||
} catch (Throwable $e) {
|
||
// email failure is non-fatal
|
||
}
|
||
|
||
echo json_encode(['success' => true]);
|