Files
sag24-website/public/api/contact.php
striker c155ff366c feat: add Turnstile anti-bot, email field, and metadata to contact form
- Add Cloudflare Turnstile widget support (site key configurable in config.js,
  secret key in contact.php — both empty until widget created at dash.cloudflare.com)
- Add email input field to contact form (parity with hhivp)
- Add company length validation (200 chars) to contact.php
- Add IP, country (CF-IPCountry header), and referer metadata to Telegram notifications
- Add company and email fields to SMTP email body
- Turnstile script loaded in index.html, widget rendered conditionally when TURNSTILE_SITE_KEY is set

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-15 14:20:23 +03:00

163 lines
7.3 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
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');
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 ───────────────────────────────────────
$TURNSTILE_SECRET = ''; // TODO: Create widget at dash.cloudflare.com → Turnstile → Add site (sag24.ru)
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) ───────────────────────────────────────
$BOT_TOKEN = '8138813013:AAElH2L5NspRLSdiFjDz6Qf32n4G24P_cj8';
$CHAT_ID = '-5230603582';
$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';
$smtp_pass = '9hsnDyBAk5&S4#lE';
$mail_to = 'info@sag24.ru';
$subject = "=?UTF-8?B?" . base64_encode("Заявка с sag24.ru от {$name}") . "?=";
$body = "Имя: {$name}\r\n";
if ($company) $body .= "Компания: {$company}\r\n";
if ($email) $body .= "Email: {$email}\r\n";
if ($phone) $body .= "Телефон: {$phone}\r\n";
$body .= "\r\nСообщение:\r\n{$message}\r\n";
$body .= "\r\nIP: {$ip}";
if ($country) $body .= " | Страна: {$country}";
$body .= "\r\n" . date('d.m.Y H:i', time() + 3 * 3600) . " MSK";
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: {$subject}\r\n");
fwrite($smtp, "MIME-Version: 1.0\r\nContent-Type: text/plain; charset=UTF-8\r\n\r\n");
fwrite($smtp, $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]);