| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123 |
- <?php
- declare(strict_types=1);
- header('Content-Type: application/json; charset=utf-8');
- header('Cache-Control: no-store');
- function respond(int $status, bool $ok, string $message): never
- {
- http_response_code($status);
- echo json_encode(['ok' => $ok, 'message' => $message], JSON_UNESCAPED_SLASHES);
- exit;
- }
- function loadContactEnvironment(): array
- {
- $values = [];
- foreach (['/etc/desktop-resume/contact.env', '/vagrant/.env'] as $path) {
- if (!is_readable($path)) {
- continue;
- }
- foreach (file($path, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) ?: [] as $line) {
- $line = trim($line);
- if ($line === '' || str_starts_with($line, '#') || !str_contains($line, '=')) {
- continue;
- }
- [$key, $value] = array_map('trim', explode('=', $line, 2));
- $values[$key] = trim($value, "\"'");
- }
- }
- return $values;
- }
- if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
- respond(405, false, 'Please submit the contact form.');
- }
- $contentType = $_SERVER['CONTENT_TYPE'] ?? '';
- if (str_contains($contentType, 'application/json')) {
- $input = json_decode(file_get_contents('php://input') ?: '{}', true);
- $input = is_array($input) ? $input : [];
- } else {
- $input = $_POST;
- }
- if (trim((string) ($input['website'] ?? '')) !== '') {
- respond(200, true, 'Thanks—your ticket is in the queue.');
- }
- $startedAt = (int) ($input['started_at'] ?? 0);
- if ($startedAt <= 0 || time() - $startedAt < 3 || time() - $startedAt > 86400) {
- respond(400, false, 'Please refresh the page and try again.');
- }
- $name = trim((string) ($input['name'] ?? ''));
- $email = trim((string) ($input['email'] ?? ''));
- $topic = trim((string) ($input['topic'] ?? ''));
- $message = trim((string) ($input['message'] ?? ''));
- $allowedTopics = ['Junior support opportunity', 'Interview conversation', 'Networking', 'Website feedback'];
- if ($name === '' || mb_strlen($name) > 80) {
- respond(422, false, 'Please enter your name.');
- }
- if (!filter_var($email, FILTER_VALIDATE_EMAIL) || mb_strlen($email) > 160) {
- respond(422, false, 'Please enter a valid email address.');
- }
- if (!in_array($topic, $allowedTopics, true)) {
- respond(422, false, 'Please choose a ticket type.');
- }
- if (mb_strlen($message) < 20 || mb_strlen($message) > 3000) {
- respond(422, false, 'Please include a little more detail (20–3,000 characters).');
- }
- $rateKey = hash('sha256', ($_SERVER['REMOTE_ADDR'] ?? 'unknown') . '|desktop-contact');
- $rateFile = sys_get_temp_dir() . '/contact-' . $rateKey;
- $lastSent = is_file($rateFile) ? (int) file_get_contents($rateFile) : 0;
- if ($lastSent > time() - 60) {
- respond(429, false, 'That ticket just came through. Please wait a minute before sending another.');
- }
- $environment = loadContactEnvironment();
- $apiKey = getenv('RESEND_API_KEY') ?: ($environment['RESEND_API_KEY'] ?? $environment['RESEND_KEY'] ?? '');
- $from = getenv('RESEND_FROM') ?: ($environment['RESEND_FROM'] ?? 'Desktop Support Portfolio <contact@jadenportfolio.com>');
- if ($apiKey === '') {
- error_log('Contact form: RESEND_API_KEY is not configured.');
- respond(503, false, 'The message desk is temporarily offline. Please email me directly instead.');
- }
- $safeName = htmlspecialchars($name, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
- $safeEmail = htmlspecialchars($email, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
- $safeTopic = htmlspecialchars($topic, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
- $safeMessage = nl2br(htmlspecialchars($message, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'));
- $ticket = 'WEB-' . strtoupper(substr(hash('sha256', $email . microtime()), 0, 8));
- $payload = [
- 'from' => $from,
- 'to' => ['praesidium909@gmail.com'],
- 'reply_to' => $email,
- 'subject' => "[$ticket] $topic — $name",
- 'html' => "<div style=\"font-family:Arial,sans-serif;max-width:640px;margin:auto;color:#172033\"><p style=\"color:#64748b\">New portfolio support ticket · <strong>$ticket</strong></p><h1 style=\"font-size:24px\">$safeTopic</h1><table style=\"width:100%;border-collapse:collapse;margin:24px 0\"><tr><td style=\"padding:10px;border-bottom:1px solid #e2e8f0;color:#64748b\">From</td><td style=\"padding:10px;border-bottom:1px solid #e2e8f0\"><strong>$safeName</strong></td></tr><tr><td style=\"padding:10px;border-bottom:1px solid #e2e8f0;color:#64748b\">Email</td><td style=\"padding:10px;border-bottom:1px solid #e2e8f0\">$safeEmail</td></tr></table><div style=\"background:#f8fafc;border-left:4px solid #0ea5e9;padding:20px;line-height:1.6\">$safeMessage</div><p style=\"color:#64748b;font-size:13px;margin-top:24px\">Reply directly to this email to respond.</p></div>",
- 'text' => "Ticket: $ticket\nType: $topic\nFrom: $name <$email>\n\n$message",
- ];
- $curl = curl_init('https://api.resend.com/emails');
- curl_setopt_array($curl, [
- CURLOPT_POST => true,
- CURLOPT_RETURNTRANSFER => true,
- CURLOPT_TIMEOUT => 15,
- CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $apiKey, 'Content-Type: application/json'],
- CURLOPT_POSTFIELDS => json_encode($payload, JSON_UNESCAPED_SLASHES),
- ]);
- $response = curl_exec($curl);
- $status = (int) curl_getinfo($curl, CURLINFO_HTTP_CODE);
- $error = curl_error($curl);
- curl_close($curl);
- if ($response === false || $status < 200 || $status >= 300) {
- error_log("Contact form: Resend failed with HTTP $status: $error");
- respond(502, false, 'The ticket could not be sent right now. Please use the email link instead.');
- }
- file_put_contents($rateFile, (string) time(), LOCK_EX);
- respond(200, true, "Ticket $ticket received. I’ll be in touch soon!");
|