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();