Files

207 lines
6.6 KiB
PHP

<?php
declare(strict_types=1);
header("Content-Type: application/json; charset=UTF-8");
if ($_SERVER["REQUEST_METHOD"] !== "POST") {
http_response_code(405);
echo json_encode([
"ok" => false,
"message" => "Метод не поддерживается",
], JSON_UNESCAPED_UNICODE);
exit;
}
function respondError(string $message, int $code = 400): void
{
http_response_code($code);
echo json_encode([
"ok" => false,
"message" => $message,
], JSON_UNESCAPED_UNICODE);
exit;
}
function sanitizeInput(string $value): string
{
$clean = trim($value);
return preg_replace("/[\\x00-\\x1F\\x7F]/u", "", $clean) ?? "";
}
function respondSuccess(): void
{
echo json_encode([
"ok" => true,
], JSON_UNESCAPED_UNICODE);
exit;
}
function loadRateLimitState(string $filePath): array
{
if (!is_file($filePath)) {
return [];
}
$raw = file_get_contents($filePath);
if ($raw === false || $raw === "") {
return [];
}
$data = json_decode($raw, true);
return is_array($data) ? $data : [];
}
function storeRateLimitState(string $filePath, array $state): void
{
$encoded = json_encode($state, JSON_UNESCAPED_UNICODE);
if ($encoded === false) {
return;
}
@file_put_contents($filePath, $encoded, LOCK_EX);
}
function isRateLimited(string $ip, int $nowTs): bool
{
$limitWindowSeconds = 10 * 60; // 10 минут
$limitAttempts = 8;
$rateFile = rtrim(sys_get_temp_dir(), DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . "frieze_spb_rate_limit.json";
$state = loadRateLimitState($rateFile);
$freshState = [];
foreach ($state as $storedIp => $timestamps) {
if (!is_array($timestamps)) {
continue;
}
$validTs = [];
foreach ($timestamps as $ts) {
$intTs = (int)$ts;
if ($intTs >= $nowTs - $limitWindowSeconds) {
$validTs[] = $intTs;
}
}
if ($validTs) {
$freshState[$storedIp] = $validTs;
}
}
$currentIpHits = $freshState[$ip] ?? [];
if (count($currentIpHits) >= $limitAttempts) {
storeRateLimitState($rateFile, $freshState);
return true;
}
$currentIpHits[] = $nowTs;
$freshState[$ip] = $currentIpHits;
storeRateLimitState($rateFile, $freshState);
return false;
}
$name = sanitizeInput((string)($_POST["name"] ?? ""));
$phoneRaw = sanitizeInput((string)($_POST["phone"] ?? ""));
$consent = (string)($_POST["consent"] ?? "");
$honeypot = sanitizeInput((string)($_POST["contact_fax"] ?? ($_POST["website"] ?? "")));
$startedAtRaw = sanitizeInput((string)($_POST["started_at"] ?? ""));
if ($honeypot !== "") {
respondError("Не удалось отправить заявку. Пожалуйста, попробуйте еще раз.", 400);
}
$nowTs = time();
$formMinFillSeconds = 0.4;
$startedAtMs = (int)$startedAtRaw;
if ($startedAtMs > 0) {
$elapsedSeconds = ((int)floor(microtime(true) * 1000) - $startedAtMs) / 1000;
if ($elapsedSeconds < $formMinFillSeconds) {
respondError("Слишком быстрая отправка формы. Попробуйте еще раз.", 429);
}
}
$clientIp = (string)($_SERVER["REMOTE_ADDR"] ?? "unknown");
if (isRateLimited($clientIp, $nowTs)) {
respondError("Слишком много заявок за короткое время. Повторите попытку позже.", 429);
}
if ($name === "") {
respondError("Укажите имя");
}
// Нормализуем имя: убираем лишние пробелы внутри.
$name = preg_replace("/\\s+/u", " ", $name) ?? $name;
if (mb_strlen($name) < 2 || mb_strlen($name) > 80) {
respondError("Имя должно быть от 2 до 80 символов");
}
if (!preg_match("/^[\\p{L}\\s-]+$/u", $name)) {
respondError("Имя может содержать только буквы, пробелы и дефис");
}
$phoneDigits = preg_replace("/\\D+/", "", $phoneRaw) ?? "";
if (strlen($phoneDigits) !== 11 || strpos($phoneDigits, "7") !== 0) {
respondError("Укажите корректный телефон");
}
if ($consent !== "1") {
respondError("Необходимо согласие на обработку данных");
}
// Укажите email в файле submit-config.php (предпочтительно для REG.RU)
// или в переменной окружения FORM_RECIPIENT_EMAIL.
$recipientEmail = "";
$senderEmail = "";
$configFile = __DIR__ . "/submit-config.php";
if (is_file($configFile)) {
require $configFile;
}
if ($recipientEmail === "") {
$recipientEmail = getenv("FORM_RECIPIENT_EMAIL") ?: "you@example.com";
}
if (substr($recipientEmail, -11) === "@example.com") {
respondError("Не настроен email получателя заявок. Укажите адрес в submit-config.php.", 500);
}
$host = strtolower((string)($_SERVER["HTTP_HOST"] ?? "frieze-spb.ru"));
$host = preg_replace("/:\\d+$/", "", $host) ?? "frieze-spb.ru";
$host = preg_replace("/[^a-z0-9\\.-]/", "", $host) ?? "frieze-spb.ru";
if ($host === "") {
$host = "frieze-spb.ru";
}
$defaultSenderEmail = "noreply@" . $host;
$fromEmail = $senderEmail !== "" ? $senderEmail : $defaultSenderEmail;
if (!filter_var($fromEmail, FILTER_VALIDATE_EMAIL)) {
$fromEmail = $defaultSenderEmail;
}
$subject = "Новая заявка с сайта frieze-spb.ru";
$encodedSubject = "=?UTF-8?B?" . base64_encode($subject) . "?=";
$messageLines = [
"Новая заявка с сайта",
"",
"Имя: " . $name,
"Телефон: " . $phoneRaw,
"Телефон (цифры): +" . $phoneDigits,
"Дата/время: " . date("d.m.Y H:i:s"),
"IP: " . (string)($_SERVER["REMOTE_ADDR"] ?? "не определен"),
"User-Agent: " . (string)($_SERVER["HTTP_USER_AGENT"] ?? "не определен"),
];
$body = implode("\r\n", $messageLines);
$headers = [
"MIME-Version: 1.0",
"Content-Type: text/plain; charset=UTF-8",
"From: Frieze Site <" . $fromEmail . ">",
"Reply-To: " . $recipientEmail,
"X-Mailer: PHP/" . PHP_VERSION,
];
$sent = mail($recipientEmail, $encodedSubject, $body, implode("\r\n", $headers), "-f" . $fromEmail);
if (!$sent) {
respondError("Не удалось отправить заявку. Попробуйте позже или позвоните по телефону.", 500);
}
respondSuccess();