2025-06-07 10:53:32 +01:00
|
|
|
<?php
|
|
|
|
|
// Quote Form Handler with Enhanced Security
|
2025-08-09 06:07:27 +00:00
|
|
|
ini_set('session.cookie_samesite', 'Lax');
|
|
|
|
|
ini_set('session.cookie_httponly', '1');
|
2026-02-04 03:17:55 +00:00
|
|
|
ini_set('session.cookie_secure', '1');
|
2025-06-07 10:53:32 +01:00
|
|
|
session_start();
|
|
|
|
|
|
2026-02-04 03:17:55 +00:00
|
|
|
// Logging
|
|
|
|
|
$logDir = __DIR__ . '/logs';
|
|
|
|
|
if (!is_dir($logDir)) {
|
|
|
|
|
mkdir($logDir, 0755, true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function logDebug($message) {
|
|
|
|
|
global $logDir;
|
|
|
|
|
$entry = date('Y-m-d H:i:s') . " - " . $message . "\n";
|
|
|
|
|
file_put_contents($logDir . '/quote-debug.log', $entry, FILE_APPEND | LOCK_EX);
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-07 10:53:32 +01:00
|
|
|
// Security headers
|
|
|
|
|
|
2025-06-17 18:43:06 +01:00
|
|
|
// CSRF Protection
|
2026-02-04 03:17:55 +00:00
|
|
|
function validateCSRFToken($token) {
|
2025-06-17 18:43:06 +01:00
|
|
|
if (!isset($_SESSION['csrf_token'])) {
|
2026-02-04 03:17:55 +00:00
|
|
|
logDebug("CSRF: No session token exists");
|
|
|
|
|
return false;
|
2025-06-17 18:43:06 +01:00
|
|
|
}
|
2026-02-04 03:17:55 +00:00
|
|
|
$match = hash_equals($_SESSION['csrf_token'], $token);
|
|
|
|
|
if (!$match) {
|
|
|
|
|
logDebug("CSRF: Token mismatch - Session: " . substr($_SESSION['csrf_token'], 0, 16) . "... POST: " . substr($token, 0, 16) . "...");
|
|
|
|
|
}
|
|
|
|
|
return $match;
|
2025-06-17 18:43:06 +01:00
|
|
|
}
|
|
|
|
|
|
2025-06-07 10:53:32 +01:00
|
|
|
// Rate limiting
|
|
|
|
|
function checkRateLimit() {
|
|
|
|
|
$ip = $_SERVER['REMOTE_ADDR'];
|
|
|
|
|
$key = 'quote_' . md5($ip);
|
|
|
|
|
|
|
|
|
|
if (!isset($_SESSION[$key])) {
|
|
|
|
|
$_SESSION[$key] = ['count' => 0, 'time' => time()];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$data = $_SESSION[$key];
|
|
|
|
|
|
|
|
|
|
if (time() - $data['time'] > 3600) {
|
|
|
|
|
$_SESSION[$key] = ['count' => 0, 'time' => time()];
|
|
|
|
|
$data = $_SESSION[$key];
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-04 03:17:55 +00:00
|
|
|
return $data['count'] < 5; // Allow 5 submissions per hour
|
2025-06-07 10:53:32 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Input validation
|
|
|
|
|
function validateInput($data, $type = 'text') {
|
|
|
|
|
$data = trim($data);
|
|
|
|
|
$data = stripslashes($data);
|
|
|
|
|
$data = htmlspecialchars($data, ENT_QUOTES, 'UTF-8');
|
|
|
|
|
|
|
|
|
|
switch ($type) {
|
|
|
|
|
case 'email':
|
|
|
|
|
return filter_var($data, FILTER_VALIDATE_EMAIL) ? $data : false;
|
|
|
|
|
case 'text':
|
2026-02-04 03:17:55 +00:00
|
|
|
return strlen($data) > 0 ? $data : '';
|
2025-06-07 10:53:32 +01:00
|
|
|
default:
|
|
|
|
|
return $data;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-09 06:07:27 +00:00
|
|
|
// Check if request is AJAX
|
|
|
|
|
function isAjaxRequest() {
|
|
|
|
|
return !empty($_SERVER['HTTP_X_REQUESTED_WITH']) &&
|
|
|
|
|
strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest';
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-07 10:53:32 +01:00
|
|
|
// Response function
|
2026-02-04 03:17:55 +00:00
|
|
|
function sendResponse($success, $message) {
|
2025-08-09 06:07:27 +00:00
|
|
|
if (isAjaxRequest()) {
|
|
|
|
|
header('Content-Type: application/json');
|
2026-02-04 03:17:55 +00:00
|
|
|
echo json_encode(['success' => $success, 'message' => $message]);
|
2025-08-09 06:07:27 +00:00
|
|
|
exit;
|
2025-06-07 10:53:32 +01:00
|
|
|
}
|
|
|
|
|
|
2026-02-04 03:17:55 +00:00
|
|
|
// HTML fallback
|
|
|
|
|
$title = $success ? 'Quote Request Sent' : 'Error';
|
|
|
|
|
$class = $success ? 'success' : 'error';
|
|
|
|
|
echo "<!DOCTYPE html><html><head><title>$title</title>
|
|
|
|
|
<style>body{font-family:sans-serif;padding:40px;text-align:center;}
|
|
|
|
|
.msg{padding:20px;border-radius:8px;max-width:500px;margin:0 auto;}
|
|
|
|
|
.success{background:#d4edda;color:#155724;border:1px solid #c3e6cb;}
|
|
|
|
|
.error{background:#f8d7da;color:#721c24;border:1px solid #f5c6cb;}
|
|
|
|
|
a{color:#007bff;}</style></head>
|
|
|
|
|
<body><div class='msg $class'><h2>$title</h2><p>" . htmlspecialchars($message) . "</p>
|
|
|
|
|
<p><a href='/quote'>Back to form</a> | <a href='/'>Home</a></p></div></body></html>";
|
2025-06-07 10:53:32 +01:00
|
|
|
exit;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Handle POST requests only
|
|
|
|
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
|
|
|
|
sendResponse(false, 'Invalid request method');
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-04 03:17:55 +00:00
|
|
|
logDebug("Form submission from " . $_SERVER['REMOTE_ADDR'] . " - Session ID: " . session_id());
|
2025-08-09 06:07:27 +00:00
|
|
|
|
2026-02-04 03:17:55 +00:00
|
|
|
// CSRF Validation
|
|
|
|
|
$csrfToken = $_POST['csrf_token'] ?? '';
|
|
|
|
|
if (empty($csrfToken)) {
|
|
|
|
|
logDebug("CSRF: No token in POST data");
|
2025-06-17 18:43:06 +01:00
|
|
|
sendResponse(false, 'Security validation failed. Please refresh the page and try again.');
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-04 03:17:55 +00:00
|
|
|
if (!validateCSRFToken($csrfToken)) {
|
|
|
|
|
sendResponse(false, 'Security validation failed. Please refresh the page and try again.');
|
2025-06-17 18:43:06 +01:00
|
|
|
}
|
|
|
|
|
|
2026-02-04 03:17:55 +00:00
|
|
|
logDebug("CSRF validation passed");
|
2025-06-17 18:43:06 +01:00
|
|
|
|
2026-02-04 03:17:55 +00:00
|
|
|
// Rate limiting
|
2025-06-07 10:53:32 +01:00
|
|
|
if (!checkRateLimit()) {
|
2026-02-04 03:17:55 +00:00
|
|
|
logDebug("Rate limit exceeded for " . $_SERVER['REMOTE_ADDR']);
|
2025-06-07 10:53:32 +01:00
|
|
|
sendResponse(false, 'Too many requests. Please try again later.');
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-04 03:17:55 +00:00
|
|
|
// reCAPTCHA (if enabled)
|
|
|
|
|
require_once __DIR__ . '/.recaptcha-config.php';
|
|
|
|
|
if (RECAPTCHA_ENABLED) {
|
|
|
|
|
$recaptchaResponse = $_POST['recaptcha_response'] ?? '';
|
|
|
|
|
if (empty($recaptchaResponse)) {
|
|
|
|
|
sendResponse(false, 'Security verification failed. Please try again.');
|
2025-08-09 06:12:52 +00:00
|
|
|
}
|
|
|
|
|
|
2026-02-04 03:17:55 +00:00
|
|
|
$verifyData = [
|
|
|
|
|
'secret' => RECAPTCHA_SECRET_KEY,
|
|
|
|
|
'response' => $recaptchaResponse,
|
2025-08-09 06:12:52 +00:00
|
|
|
'remoteip' => $_SERVER['REMOTE_ADDR']
|
|
|
|
|
];
|
|
|
|
|
|
2026-02-04 03:17:55 +00:00
|
|
|
$result = @file_get_contents('https://www.google.com/recaptcha/api/siteverify', false,
|
|
|
|
|
stream_context_create(['http' => [
|
2025-08-09 06:12:52 +00:00
|
|
|
'method' => 'POST',
|
2026-02-04 03:17:55 +00:00
|
|
|
'header' => 'Content-type: application/x-www-form-urlencoded',
|
|
|
|
|
'content' => http_build_query($verifyData)
|
|
|
|
|
]]));
|
2025-08-09 06:12:52 +00:00
|
|
|
|
2026-02-04 03:17:55 +00:00
|
|
|
if ($result) {
|
|
|
|
|
$resultJson = json_decode($result, true);
|
|
|
|
|
if (!$resultJson['success'] || ($resultJson['score'] ?? 1) < RECAPTCHA_THRESHOLD) {
|
|
|
|
|
logDebug("reCAPTCHA failed: " . print_r($resultJson, true));
|
|
|
|
|
sendResponse(false, 'Security verification failed. Please try again.');
|
|
|
|
|
}
|
2025-08-09 06:12:52 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-04 03:17:55 +00:00
|
|
|
// Honeypot check
|
|
|
|
|
if (!empty($_POST['website'])) {
|
|
|
|
|
logDebug("Honeypot triggered");
|
|
|
|
|
sendResponse(false, 'Spam detected');
|
2025-08-09 06:12:52 +00:00
|
|
|
}
|
|
|
|
|
|
2026-02-04 03:17:55 +00:00
|
|
|
// Bot detection - user agent check
|
|
|
|
|
$userAgent = $_SERVER['HTTP_USER_AGENT'] ?? '';
|
|
|
|
|
$suspiciousAgents = ['curl', 'wget', 'python-requests', 'scrapy'];
|
|
|
|
|
foreach ($suspiciousAgents as $agent) {
|
|
|
|
|
if (stripos($userAgent, $agent) !== false) {
|
|
|
|
|
logDebug("Suspicious user agent: $userAgent");
|
|
|
|
|
sendResponse(false, 'Automated submissions not allowed');
|
|
|
|
|
}
|
2025-06-17 18:43:06 +01:00
|
|
|
}
|
|
|
|
|
|
2026-02-04 03:17:55 +00:00
|
|
|
// Extract and validate form data
|
|
|
|
|
$service_type = validateInput($_POST['service_type'] ?? '');
|
|
|
|
|
$scale = validateInput($_POST['scale'] ?? '');
|
|
|
|
|
$name = validateInput($_POST['name'] ?? '');
|
2025-06-07 10:53:32 +01:00
|
|
|
$email = validateInput($_POST['email'] ?? '', 'email');
|
2026-02-04 03:17:55 +00:00
|
|
|
$company = validateInput($_POST['company'] ?? '');
|
|
|
|
|
$timeline = validateInput($_POST['timeline'] ?? '');
|
|
|
|
|
$data_sources = validateInput($_POST['data_sources'] ?? '');
|
|
|
|
|
$requirements = validateInput($_POST['requirements'] ?? '');
|
2025-06-07 10:53:32 +01:00
|
|
|
|
|
|
|
|
// Validation
|
|
|
|
|
$errors = [];
|
2026-02-04 03:17:55 +00:00
|
|
|
if (empty($service_type)) $errors[] = 'Please select a service';
|
|
|
|
|
if (empty($scale)) $errors[] = 'Please select a scale';
|
|
|
|
|
if (empty($name) || strlen($name) < 2) $errors[] = 'Please enter a valid name';
|
|
|
|
|
if (!$email) $errors[] = 'Please enter a valid email address';
|
|
|
|
|
if (empty($timeline)) $errors[] = 'Please select a timeline';
|
2025-06-07 10:53:32 +01:00
|
|
|
|
|
|
|
|
if (!empty($errors)) {
|
2026-02-04 03:17:55 +00:00
|
|
|
logDebug("Validation errors: " . implode(', ', $errors));
|
2025-06-07 10:53:32 +01:00
|
|
|
sendResponse(false, implode('. ', $errors));
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-04 03:17:55 +00:00
|
|
|
// Spam content check
|
|
|
|
|
$spamKeywords = ['viagra', 'cialis', 'casino', 'lottery', 'bitcoin', 'forex', 'pharmacy', 'click here', 'act now'];
|
2025-06-17 18:43:06 +01:00
|
|
|
$contentToCheck = strtolower($requirements . ' ' . $name . ' ' . $company . ' ' . $data_sources);
|
|
|
|
|
foreach ($spamKeywords as $keyword) {
|
|
|
|
|
if (strpos($contentToCheck, $keyword) !== false) {
|
2026-02-04 03:17:55 +00:00
|
|
|
logDebug("Spam keyword detected: $keyword");
|
2025-06-17 18:43:06 +01:00
|
|
|
sendResponse(false, 'Invalid content detected');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-07 10:53:32 +01:00
|
|
|
// Update rate limit counter
|
|
|
|
|
$ip = $_SERVER['REMOTE_ADDR'];
|
|
|
|
|
$key = 'quote_' . md5($ip);
|
|
|
|
|
$_SESSION[$key]['count']++;
|
|
|
|
|
|
2026-02-04 03:17:55 +00:00
|
|
|
// Prepare friendly labels
|
|
|
|
|
$serviceLabels = [
|
|
|
|
|
'web-scraping' => 'Web Scraping',
|
|
|
|
|
'data-cleaning' => 'Data Cleaning',
|
|
|
|
|
'api-development' => 'API Development',
|
|
|
|
|
'automation' => 'Automation',
|
|
|
|
|
'custom' => 'Custom Solution',
|
|
|
|
|
'other' => 'Other'
|
2025-06-07 10:53:32 +01:00
|
|
|
];
|
|
|
|
|
|
2026-02-04 03:17:55 +00:00
|
|
|
$scaleLabels = [
|
|
|
|
|
'small' => 'Small (under 1,000 records)',
|
|
|
|
|
'medium' => 'Medium (1,000 - 50,000 records)',
|
|
|
|
|
'large' => 'Large (50,000 - 500,000 records)',
|
|
|
|
|
'enterprise' => 'Enterprise (500,000+ records)',
|
|
|
|
|
'unsure' => 'Not sure yet'
|
2025-06-07 10:53:32 +01:00
|
|
|
];
|
|
|
|
|
|
2026-02-04 03:17:55 +00:00
|
|
|
$timelineLabels = [
|
|
|
|
|
'asap' => 'ASAP',
|
|
|
|
|
'2weeks' => 'Within 2 weeks',
|
|
|
|
|
'1month' => 'Within a month',
|
|
|
|
|
'flexible' => 'Flexible / No rush'
|
2025-06-07 10:53:32 +01:00
|
|
|
];
|
|
|
|
|
|
2026-02-04 03:17:55 +00:00
|
|
|
// Build email
|
2026-03-21 09:48:46 +00:00
|
|
|
$to = 'info@ukaiautomation.co.uk';
|
2026-02-04 03:17:55 +00:00
|
|
|
$subject = 'New Quote Request - ' . ($serviceLabels[$service_type] ?? $service_type);
|
|
|
|
|
|
|
|
|
|
$emailHTML = '<!DOCTYPE html><html><head><meta charset="UTF-8"></head><body style="font-family:Arial,sans-serif;line-height:1.6;">
|
|
|
|
|
<div style="max-width:600px;margin:0 auto;padding:20px;">
|
2026-03-21 09:48:46 +00:00
|
|
|
<div style="background:linear-gradient(135deg,#7c3aed,#6d28d9);color:white;padding:20px;border-radius:8px 8px 0 0;text-align:center;">
|
2026-02-04 03:17:55 +00:00
|
|
|
<h1 style="margin:0;">New Quote Request</h1>
|
2026-03-21 09:48:46 +00:00
|
|
|
<p style="margin:5px 0 0;">UK AI Automation</p>
|
2026-02-04 03:17:55 +00:00
|
|
|
</div>
|
|
|
|
|
<div style="background:#f9f9f9;padding:20px;border-radius:0 0 8px 8px;">
|
|
|
|
|
|
|
|
|
|
<div style="background:white;padding:15px;border-radius:8px;margin-bottom:15px;border-left:4px solid #28a745;">
|
2026-03-21 09:48:46 +00:00
|
|
|
<h3 style="margin:0 0 10px;color:#7c3aed;">Contact Details</h3>
|
2026-02-04 03:17:55 +00:00
|
|
|
<p><strong>Name:</strong> ' . $name . '</p>
|
|
|
|
|
<p><strong>Email:</strong> ' . $email . '</p>
|
|
|
|
|
<p><strong>Company:</strong> ' . ($company ?: 'Not provided') . '</p>
|
|
|
|
|
</div>
|
|
|
|
|
|
2026-03-21 09:48:46 +00:00
|
|
|
<div style="background:white;padding:15px;border-radius:8px;margin-bottom:15px;border-left:4px solid #6d28d9;">
|
|
|
|
|
<h3 style="margin:0 0 10px;color:#7c3aed;">Project Details</h3>
|
2026-02-04 03:17:55 +00:00
|
|
|
<p><strong>Service:</strong> ' . ($serviceLabels[$service_type] ?? $service_type) . '</p>
|
|
|
|
|
<p><strong>Scale:</strong> ' . ($scaleLabels[$scale] ?? $scale) . '</p>
|
|
|
|
|
<p><strong>Timeline:</strong> ' . ($timelineLabels[$timeline] ?? $timeline) . '</p>
|
|
|
|
|
<p><strong>Target Sources:</strong> ' . ($data_sources ?: 'Not specified') . '</p>
|
|
|
|
|
</div>';
|
|
|
|
|
|
|
|
|
|
if (!empty($requirements)) {
|
|
|
|
|
$emailHTML .= '<div style="background:white;padding:15px;border-radius:8px;margin-bottom:15px;border-left:4px solid #ffc107;">
|
2026-03-21 09:48:46 +00:00
|
|
|
<h3 style="margin:0 0 10px;color:#7c3aed;">Requirements</h3>
|
2026-02-04 03:17:55 +00:00
|
|
|
<p>' . nl2br($requirements) . '</p>
|
|
|
|
|
</div>';
|
2025-06-07 10:53:32 +01:00
|
|
|
}
|
|
|
|
|
|
2026-02-04 03:17:55 +00:00
|
|
|
$emailHTML .= '<div style="background:#e9ecef;padding:10px;border-radius:8px;font-size:12px;color:#666;">
|
|
|
|
|
<p><strong>Submitted:</strong> ' . date('Y-m-d H:i:s') . ' UTC</p>
|
|
|
|
|
<p><strong>IP:</strong> ' . $_SERVER['REMOTE_ADDR'] . '</p>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
</div></div></body></html>';
|
|
|
|
|
|
2025-06-07 10:53:32 +01:00
|
|
|
$headers = "MIME-Version: 1.0\r\n";
|
|
|
|
|
$headers .= "Content-Type: text/html; charset=UTF-8\r\n";
|
2026-03-21 09:48:46 +00:00
|
|
|
$headers .= "From: \"UK AI Automation\" <noreply@ukaiautomation.co.uk>\r\n";
|
2025-06-07 10:53:32 +01:00
|
|
|
$headers .= "Reply-To: " . $email . "\r\n";
|
|
|
|
|
$headers .= "X-Priority: " . ($timeline === 'asap' ? '1' : '3') . "\r\n";
|
|
|
|
|
|
|
|
|
|
// Send email
|
2026-02-04 03:17:55 +00:00
|
|
|
$emailSent = @mail($to, $subject, $emailHTML, $headers);
|
|
|
|
|
|
|
|
|
|
if ($emailSent) {
|
|
|
|
|
logDebug("SUCCESS: Quote from $email ($name) - Service: $service_type");
|
2025-06-07 10:53:32 +01:00
|
|
|
|
2026-02-04 03:17:55 +00:00
|
|
|
// Log to file
|
|
|
|
|
$logEntry = date('Y-m-d H:i:s') . " | $name | $email | $service_type | $scale | $timeline\n";
|
|
|
|
|
file_put_contents($logDir . '/quote-requests.log', $logEntry, FILE_APPEND | LOCK_EX);
|
2025-06-07 10:53:32 +01:00
|
|
|
|
2026-02-04 03:17:55 +00:00
|
|
|
sendResponse(true, 'Thank you for your quote request! We will send you a detailed proposal within 24 hours.');
|
|
|
|
|
} else {
|
|
|
|
|
$error = error_get_last();
|
|
|
|
|
logDebug("FAILED: Email send failed - " . ($error['message'] ?? 'Unknown error'));
|
2026-03-21 09:48:46 +00:00
|
|
|
sendResponse(false, 'There was an error sending your request. Please try again or contact us at info@ukaiautomation.co.uk');
|
2025-06-07 10:53:32 +01:00
|
|
|
}
|
2026-02-04 03:17:55 +00:00
|
|
|
?>
|