Files
ukaiautomation/quote-handler.php

297 lines
10 KiB
PHP

<?php
// Quote Form Handler with Enhanced Security
ini_set('session.cookie_samesite', 'Lax');
ini_set('session.cookie_httponly', '1');
ini_set('session.cookie_secure', '1');
session_start();
// 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);
}
// Security headers
// CSRF Protection
function validateCSRFToken($token) {
if (!isset($_SESSION['csrf_token'])) {
logDebug("CSRF: No session token exists");
return false;
}
$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;
}
// 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];
}
return $data['count'] < 5; // Allow 5 submissions per hour
}
// 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':
return strlen($data) > 0 ? $data : '';
default:
return $data;
}
}
// Check if request is AJAX
function isAjaxRequest() {
return !empty($_SERVER['HTTP_X_REQUESTED_WITH']) &&
strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest';
}
// Response function
function sendResponse($success, $message) {
if (isAjaxRequest()) {
header('Content-Type: application/json');
echo json_encode(['success' => $success, 'message' => $message]);
exit;
}
// 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>";
exit;
}
// Handle POST requests only
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
sendResponse(false, 'Invalid request method');
}
logDebug("Form submission from " . $_SERVER['REMOTE_ADDR'] . " - Session ID: " . session_id());
// CSRF Validation
$csrfToken = $_POST['csrf_token'] ?? '';
if (empty($csrfToken)) {
logDebug("CSRF: No token in POST data");
sendResponse(false, 'Security validation failed. Please refresh the page and try again.');
}
if (!validateCSRFToken($csrfToken)) {
sendResponse(false, 'Security validation failed. Please refresh the page and try again.');
}
logDebug("CSRF validation passed");
// Rate limiting
if (!checkRateLimit()) {
logDebug("Rate limit exceeded for " . $_SERVER['REMOTE_ADDR']);
sendResponse(false, 'Too many requests. Please try again later.');
}
// 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.');
}
$verifyData = [
'secret' => RECAPTCHA_SECRET_KEY,
'response' => $recaptchaResponse,
'remoteip' => $_SERVER['REMOTE_ADDR']
];
$result = @file_get_contents('https://www.google.com/recaptcha/api/siteverify', false,
stream_context_create(['http' => [
'method' => 'POST',
'header' => 'Content-type: application/x-www-form-urlencoded',
'content' => http_build_query($verifyData)
]]));
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.');
}
}
}
// Honeypot check
if (!empty($_POST['website'])) {
logDebug("Honeypot triggered");
sendResponse(false, 'Spam detected');
}
// 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');
}
}
// Extract and validate form data
$service_type = validateInput($_POST['service_type'] ?? '');
$scale = validateInput($_POST['scale'] ?? '');
$name = validateInput($_POST['name'] ?? '');
$email = validateInput($_POST['email'] ?? '', 'email');
$company = validateInput($_POST['company'] ?? '');
$timeline = validateInput($_POST['timeline'] ?? '');
$data_sources = validateInput($_POST['data_sources'] ?? '');
$requirements = validateInput($_POST['requirements'] ?? '');
// Validation
$errors = [];
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';
if (!empty($errors)) {
logDebug("Validation errors: " . implode(', ', $errors));
sendResponse(false, implode('. ', $errors));
}
// Spam content check
$spamKeywords = ['viagra', 'cialis', 'casino', 'lottery', 'bitcoin', 'forex', 'pharmacy', 'click here', 'act now'];
$contentToCheck = strtolower($requirements . ' ' . $name . ' ' . $company . ' ' . $data_sources);
foreach ($spamKeywords as $keyword) {
if (strpos($contentToCheck, $keyword) !== false) {
logDebug("Spam keyword detected: $keyword");
sendResponse(false, 'Invalid content detected');
}
}
// Update rate limit counter
$ip = $_SERVER['REMOTE_ADDR'];
$key = 'quote_' . md5($ip);
$_SESSION[$key]['count']++;
// Prepare friendly labels
$serviceLabels = [
'web-scraping' => 'Web Scraping',
'data-cleaning' => 'Data Cleaning',
'api-development' => 'API Development',
'automation' => 'Automation',
'custom' => 'Custom Solution',
'other' => 'Other'
];
$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'
];
$timelineLabels = [
'asap' => 'ASAP',
'2weeks' => 'Within 2 weeks',
'1month' => 'Within a month',
'flexible' => 'Flexible / No rush'
];
// Build email
$to = 'info@ukaiautomation.co.uk';
$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;">
<div style="background:linear-gradient(135deg,#7c3aed,#6d28d9);color:white;padding:20px;border-radius:8px 8px 0 0;text-align:center;">
<h1 style="margin:0;">New Quote Request</h1>
<p style="margin:5px 0 0;">UK AI Automation</p>
</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;">
<h3 style="margin:0 0 10px;color:#7c3aed;">Contact Details</h3>
<p><strong>Name:</strong> ' . $name . '</p>
<p><strong>Email:</strong> ' . $email . '</p>
<p><strong>Company:</strong> ' . ($company ?: 'Not provided') . '</p>
</div>
<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>
<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;">
<h3 style="margin:0 0 10px;color:#7c3aed;">Requirements</h3>
<p>' . nl2br($requirements) . '</p>
</div>';
}
$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>';
$headers = "MIME-Version: 1.0\r\n";
$headers .= "Content-Type: text/html; charset=UTF-8\r\n";
$headers .= "From: \"UK AI Automation\" <noreply@ukaiautomation.co.uk>\r\n";
$headers .= "Reply-To: " . $email . "\r\n";
$headers .= "X-Priority: " . ($timeline === 'asap' ? '1' : '3') . "\r\n";
// Send email
$emailSent = @mail($to, $subject, $emailHTML, $headers);
if ($emailSent) {
logDebug("SUCCESS: Quote from $email ($name) - Service: $service_type");
// 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);
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'));
sendResponse(false, 'There was an error sending your request. Please try again or contact us at info@ukaiautomation.co.uk');
}
?>