297 lines
9.0 KiB
PHP
Executable File
297 lines
9.0 KiB
PHP
Executable File
<?php
|
|
/**
|
|
* MSPE Contact Form API
|
|
*/
|
|
|
|
require_once 'config.php';
|
|
|
|
$method = $_SERVER['REQUEST_METHOD'];
|
|
$id = $_GET['id'] ?? null;
|
|
|
|
switch ($method) {
|
|
case 'GET':
|
|
requireAuth();
|
|
if ($id) {
|
|
getMessage($id);
|
|
} else {
|
|
getMessages();
|
|
}
|
|
break;
|
|
|
|
case 'POST':
|
|
submitMessage();
|
|
break;
|
|
|
|
case 'PUT':
|
|
requireAuth();
|
|
if (!$id) {
|
|
jsonResponse(['success' => false, 'message' => 'Message ID required'], 400);
|
|
}
|
|
updateMessage($id);
|
|
break;
|
|
|
|
case 'DELETE':
|
|
requireAuth();
|
|
if (!$id) {
|
|
jsonResponse(['success' => false, 'message' => 'Message ID required'], 400);
|
|
}
|
|
deleteMessage($id);
|
|
break;
|
|
|
|
default:
|
|
jsonResponse(['success' => false, 'message' => 'Method not allowed'], 405);
|
|
}
|
|
|
|
function getMessages() {
|
|
global $db;
|
|
|
|
$messages = $db->getAll('messages');
|
|
|
|
// Apply filters
|
|
$status = $_GET['status'] ?? null;
|
|
$limit = (int)($_GET['limit'] ?? 20);
|
|
$offset = (int)($_GET['offset'] ?? 0);
|
|
|
|
if ($status) {
|
|
$messages = array_filter($messages, function($m) use ($status) {
|
|
return $m['status'] === $status;
|
|
});
|
|
}
|
|
|
|
// Sort by date (newest first)
|
|
usort($messages, function($a, $b) {
|
|
return strtotime($b['created_at']) - strtotime($a['created_at']);
|
|
});
|
|
|
|
$total = count($messages);
|
|
$unread = count(array_filter($messages, function($m) {
|
|
return ($m['status'] ?? 'unread') === 'unread';
|
|
}));
|
|
|
|
$messages = array_slice(array_values($messages), $offset, $limit);
|
|
|
|
jsonResponse([
|
|
'success' => true,
|
|
'data' => $messages,
|
|
'total' => $total,
|
|
'unread' => $unread,
|
|
'limit' => $limit,
|
|
'offset' => $offset
|
|
]);
|
|
}
|
|
|
|
function getMessage($id) {
|
|
global $db;
|
|
|
|
$message = $db->get('messages', $id);
|
|
|
|
if (!$message) {
|
|
jsonResponse(['success' => false, 'message' => 'Message not found'], 404);
|
|
}
|
|
|
|
// Mark as read
|
|
if (($message['status'] ?? 'unread') === 'unread') {
|
|
$db->update('messages', $id, ['status' => 'read']);
|
|
$message['status'] = 'read';
|
|
}
|
|
|
|
jsonResponse([
|
|
'success' => true,
|
|
'data' => $message
|
|
]);
|
|
}
|
|
|
|
function submitMessage() {
|
|
global $db;
|
|
|
|
requireSameOriginRequest();
|
|
|
|
// Rate limiting: max 5 submissions per IP per hour
|
|
$ip = $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
|
|
$oneHourAgo = date('Y-m-d H:i:s', strtotime('-1 hour'));
|
|
$now = date('Y-m-d H:i:s');
|
|
|
|
$recentCount = $db->countByDateRange('messages', 'created_at', $oneHourAgo, $now, [
|
|
'ip_address' => $ip
|
|
]);
|
|
|
|
if ($recentCount >= 5) {
|
|
jsonResponse(['success' => false, 'message' => 'Too many submissions. Please try again later.'], 429);
|
|
}
|
|
|
|
$data = getRequestBody();
|
|
|
|
// ── Cloudflare Turnstile verification (active only when TURNSTILE_SECRET_KEY is set) ──
|
|
$turnstileSecret = env('TURNSTILE_SECRET_KEY', '');
|
|
if ($turnstileSecret !== '') {
|
|
$turnstileToken = $data['cf-turnstile-response'] ?? '';
|
|
if (empty($turnstileToken)) {
|
|
jsonResponse(['success' => false, 'message' => 'Human verification is required.'], 400);
|
|
}
|
|
if (function_exists('curl_init')) {
|
|
$ch = curl_init('https://challenges.cloudflare.com/turnstile/v0/siteverify');
|
|
curl_setopt_array($ch, [
|
|
CURLOPT_POST => true,
|
|
CURLOPT_RETURNTRANSFER => true,
|
|
CURLOPT_POSTFIELDS => http_build_query([
|
|
'secret' => $turnstileSecret,
|
|
'response' => $turnstileToken,
|
|
'remoteip' => $_SERVER['REMOTE_ADDR'] ?? ''
|
|
])
|
|
]);
|
|
$tvResult = json_decode(curl_exec($ch), true);
|
|
curl_close($ch);
|
|
if (!($tvResult['success'] ?? false)) {
|
|
jsonResponse(['success' => false, 'message' => 'Human verification failed. Please try again.'], 400);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Validate required fields
|
|
$required = ['first_name', 'last_name', 'email', 'message'];
|
|
foreach ($required as $field) {
|
|
if (empty($data[$field])) {
|
|
jsonResponse(['success' => false, 'message' => ucfirst(str_replace('_', ' ', $field)) . ' is required'], 400);
|
|
}
|
|
}
|
|
|
|
// Validate email
|
|
$email = trim((string)($data['email'] ?? ''));
|
|
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
|
jsonResponse(['success' => false, 'message' => 'Invalid email address'], 400);
|
|
}
|
|
|
|
// Validate phone format if provided (basic check)
|
|
if (!empty($data['phone'])) {
|
|
$phone = preg_replace('/[^\d+\-() ]/', '', (string)$data['phone']);
|
|
if (strlen($phone) < 5) {
|
|
jsonResponse(['success' => false, 'message' => 'Invalid phone number'], 400);
|
|
}
|
|
}
|
|
|
|
// Honeypot check (if implemented in form)
|
|
if (!empty($data['website'])) {
|
|
// Likely a bot
|
|
jsonResponse(['success' => true, 'message' => 'Message sent successfully']);
|
|
}
|
|
|
|
// Prepare message data
|
|
$messageData = [
|
|
'first_name' => sanitize($data['first_name']),
|
|
'last_name' => sanitize($data['last_name']),
|
|
'email' => sanitize($data['email']),
|
|
'phone' => sanitize($data['phone'] ?? ''),
|
|
'company' => sanitize($data['company'] ?? ''),
|
|
'service' => sanitize($data['service'] ?? ''),
|
|
'budget' => sanitize($data['budget'] ?? ''),
|
|
'message' => sanitize($data['message']),
|
|
'newsletter' => $data['newsletter'] ?? false,
|
|
'status' => 'unread',
|
|
'ip_address' => $_SERVER['REMOTE_ADDR'] ?? '',
|
|
'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? ''
|
|
];
|
|
|
|
$message = $db->insert('messages', $messageData);
|
|
|
|
// Send email notification (optional)
|
|
sendNotificationEmail($messageData);
|
|
|
|
// Handle newsletter subscription
|
|
if (!empty($data['newsletter'])) {
|
|
subscribeNewsletter($data['email'], $data['first_name'] . ' ' . $data['last_name']);
|
|
}
|
|
|
|
jsonResponse([
|
|
'success' => true,
|
|
'message' => 'Thank you for your message! We\'ll be in touch soon.'
|
|
], 201);
|
|
}
|
|
|
|
function updateMessage($id) {
|
|
global $db;
|
|
|
|
$existing = $db->get('messages', $id);
|
|
if (!$existing) {
|
|
jsonResponse(['success' => false, 'message' => 'Message not found'], 404);
|
|
}
|
|
|
|
$data = getRequestBody();
|
|
$allowed = ['status', 'notes'];
|
|
|
|
$updateData = [];
|
|
foreach ($allowed as $field) {
|
|
if (isset($data[$field])) {
|
|
$updateData[$field] = $data[$field];
|
|
}
|
|
}
|
|
|
|
$message = $db->update('messages', $id, $updateData);
|
|
|
|
jsonResponse([
|
|
'success' => true,
|
|
'message' => 'Message updated successfully',
|
|
'data' => $message
|
|
]);
|
|
}
|
|
|
|
function deleteMessage($id) {
|
|
global $db;
|
|
|
|
$existing = $db->get('messages', $id);
|
|
if (!$existing) {
|
|
jsonResponse(['success' => false, 'message' => 'Message not found'], 404);
|
|
}
|
|
|
|
$db->delete('messages', $id);
|
|
|
|
jsonResponse([
|
|
'success' => true,
|
|
'message' => 'Message deleted successfully'
|
|
]);
|
|
}
|
|
|
|
function sendNotificationEmail($data) {
|
|
$to = getSetting('admin_email', getSetting('contact_email', ADMIN_EMAIL));
|
|
$subject = 'New Contact Form Submission - ' . SITE_NAME;
|
|
|
|
$plain = "You have received a new message from your website contact form.\n\n";
|
|
$plain .= "Name: {$data['first_name']} {$data['last_name']}\n";
|
|
$plain .= "Email: {$data['email']}\n";
|
|
$plain .= "Phone: {$data['phone']}\n";
|
|
$plain .= "Company: {$data['company']}\n";
|
|
$plain .= "Service Interest: {$data['service']}\n";
|
|
$plain .= "Budget: {$data['budget']}\n\n";
|
|
$plain .= "Message:\n{$data['message']}\n";
|
|
|
|
$html = '<h2>New Contact Form Submission</h2>'
|
|
. '<p><strong>Name:</strong> ' . htmlspecialchars($data['first_name'] . ' ' . $data['last_name']) . '</p>'
|
|
. '<p><strong>Email:</strong> ' . htmlspecialchars($data['email']) . '</p>'
|
|
. '<p><strong>Phone:</strong> ' . htmlspecialchars($data['phone']) . '</p>'
|
|
. '<p><strong>Company:</strong> ' . htmlspecialchars($data['company']) . '</p>'
|
|
. '<p><strong>Service Interest:</strong> ' . htmlspecialchars($data['service']) . '</p>'
|
|
. '<p><strong>Budget:</strong> ' . htmlspecialchars($data['budget']) . '</p>'
|
|
. '<p><strong>Message:</strong><br>' . nl2br(htmlspecialchars($data['message'])) . '</p>';
|
|
|
|
$result = sendEmail($to, $subject, $html, $plain, $data['email']);
|
|
|
|
if (!$result['success']) {
|
|
error_log('MSPE contact email failed: ' . ($result['message'] ?? 'unknown'));
|
|
}
|
|
}
|
|
|
|
function subscribeNewsletter($email, $name) {
|
|
global $db;
|
|
|
|
// Check if already subscribed
|
|
$existing = $db->query('subscribers', ['email' => $email]);
|
|
if (!empty($existing)) {
|
|
return;
|
|
}
|
|
|
|
$db->insert('subscribers', [
|
|
'email' => $email,
|
|
'name' => $name,
|
|
'status' => 'active'
|
|
]);
|
|
}
|