false, 'error' => 'method_not_allowed'], 405); } $csrf = (string)($_POST['csrf'] ?? ''); if (!csrf_check($csrf)) { json_out(['ok' => false, 'error' => 'Invalid session, refresh and try again.'], 400); } // Honeypot to deter bots if (!empty($_POST['website'])) { json_out(['ok' => true]); } $last = (int)($_SESSION['contact_last'] ?? 0); if ($last && (time() - $last) < 20) { json_out(['ok' => false, 'error' => 'Please wait a moment before sending again.'], 429); } $name = trim((string)($_POST['name'] ?? '')); $email = trim((string)($_POST['email'] ?? '')); $message = trim((string)($_POST['message'] ?? '')); if ($name === '' || mb_strlen($name) > 80) { json_out(['ok' => false, 'error' => 'Invalid name.'], 400); } if (!filter_var($email, FILTER_VALIDATE_EMAIL) || mb_strlen($email) > 120) { json_out(['ok' => false, 'error' => 'Invalid email.'], 400); } if ($message === '' || mb_strlen($message) > 2000) { json_out(['ok' => false, 'error' => 'Invalid message length.'], 400); } $name = str_replace(["\r", "\n"], ' ', $name); $email = str_replace(["\r", "\n"], '', $email); // Ensure table exists try { pdo()->exec(" CREATE TABLE IF NOT EXISTS contact_requests ( id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, name VARCHAR(80) NOT NULL, email VARCHAR(120) NOT NULL, message TEXT NOT NULL, status ENUM('new','read','archived') NOT NULL DEFAULT 'new', created_at DATETIME NOT NULL, ip VARCHAR(45) NULL, user_agent VARCHAR(255) NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci "); } catch (Throwable $e) { json_out(['ok' => false, 'error' => 'Server error (contact table).'], 500); } $ipRaw = (string)($_SERVER['REMOTE_ADDR'] ?? ''); $ip = substr($ipRaw !== '' ? $ipRaw : '127.0.0.1', 0, 45); $ua = substr((string)($_SERVER['HTTP_USER_AGENT'] ?? ''), 0, 255); $now = date('Y-m-d H:i:s'); $id = null; try { $st = pdo()->prepare("INSERT INTO contact_requests (name, email, message, status, created_at, ip, user_agent) VALUES (?, ?, ?, 'new', ?, ?, ?)"); $st->execute([$name, $email, $message, $now, $ip, $ua]); $id = (int)pdo()->lastInsertId(); } catch (Throwable $e) { json_out(['ok' => false, 'error' => 'Server error (save failed).'], 500); } // Optional email notification if configured $delivered = false; $to = getenv('CONTACT_TO') ?: ''; if ($to !== '') { $subject = "Portfolio contact from {$name}"; $body = "Name: {$name}\n" . "Email: {$email}\n\n" . "Message:\n{$message}\n"; $headers = [ 'From: Portfolio ', 'Reply-To: ' . $email, 'Content-Type: text/plain; charset=utf-8' ]; $delivered = @mail($to, $subject, $body, implode("\r\n", $headers)) === true; } $_SESSION['contact_last'] = time(); json_out([ 'ok' => true, 'id' => $id, 'delivered' => $delivered, 'ip' => $ip, ]);