Add working availability form with PHP email sending
- send.php handles form submission and sends email via mail() - Honeypot field for bot protection - IP-based rate limiting (60s cooldown) - Query logging to data/kyselyt.log - Frontend JS with loading state and success/error feedback - data/ directory protected with .htaccess Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
1
data/.htaccess
Normal file
1
data/.htaccess
Normal file
@@ -0,0 +1 @@
|
|||||||
|
Deny from all
|
||||||
46
index.html
46
index.html
@@ -198,6 +198,9 @@
|
|||||||
<p>Täytä lomake ja selvitämme valokuidun saatavuuden osoitteessasi. Vastaamme yleensä 1–2 arkipäivän kuluessa. Kysely ei velvoita mihinkään.</p>
|
<p>Täytä lomake ja selvitämme valokuidun saatavuuden osoitteessasi. Vastaamme yleensä 1–2 arkipäivän kuluessa. Kysely ei velvoita mihinkään.</p>
|
||||||
</div>
|
</div>
|
||||||
<form class="form" id="availability-form">
|
<form class="form" id="availability-form">
|
||||||
|
<div style="position:absolute;left:-9999px;top:-9999px;">
|
||||||
|
<input type="text" name="website" tabindex="-1" autocomplete="off">
|
||||||
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="company">Yrityksen nimi</label>
|
<label for="company">Yrityksen nimi</label>
|
||||||
<input type="text" id="company" name="company" required>
|
<input type="text" id="company" name="company" required>
|
||||||
@@ -259,5 +262,48 @@
|
|||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Mobiilinavigaatio
|
||||||
|
document.querySelector('.nav-toggle')?.addEventListener('click', function() {
|
||||||
|
document.querySelector('.nav')?.classList.toggle('open');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Saatavuuskysely-lomake
|
||||||
|
document.getElementById('availability-form')?.addEventListener('submit', async function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
const btn = this.querySelector('button[type="submit"]');
|
||||||
|
const originalText = btn.textContent;
|
||||||
|
btn.disabled = true;
|
||||||
|
btn.textContent = 'Lähetetään...';
|
||||||
|
|
||||||
|
try {
|
||||||
|
const formData = new FormData(this);
|
||||||
|
const response = await fetch('send.php', {
|
||||||
|
method: 'POST',
|
||||||
|
body: formData
|
||||||
|
});
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (data.success) {
|
||||||
|
btn.textContent = 'Lähetetty!';
|
||||||
|
btn.style.background = '#27ae60';
|
||||||
|
this.reset();
|
||||||
|
setTimeout(() => {
|
||||||
|
btn.textContent = originalText;
|
||||||
|
btn.style.background = '';
|
||||||
|
btn.disabled = false;
|
||||||
|
}, 4000);
|
||||||
|
} else {
|
||||||
|
alert(data.error || 'Jokin meni pieleen. Yritä uudelleen.');
|
||||||
|
btn.textContent = originalText;
|
||||||
|
btn.disabled = false;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
alert('Yhteysvirhe. Tarkista verkkoyhteytesi ja yritä uudelleen.');
|
||||||
|
btn.textContent = originalText;
|
||||||
|
btn.disabled = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
100
send.php
Normal file
100
send.php
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
<?php
|
||||||
|
header('Content-Type: application/json; charset=utf-8');
|
||||||
|
|
||||||
|
// Vain POST-pyynnöt
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||||
|
http_response_code(405);
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Virheellinen pyyntö']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rate limiting (yksinkertainen, IP-pohjainen)
|
||||||
|
$rateLimitDir = __DIR__ . '/data';
|
||||||
|
if (!is_dir($rateLimitDir)) {
|
||||||
|
mkdir($rateLimitDir, 0755, true);
|
||||||
|
}
|
||||||
|
$ip = $_SERVER['REMOTE_ADDR'] ?? 'unknown';
|
||||||
|
$rateLimitFile = $rateLimitDir . '/ratelimit_' . md5($ip) . '.txt';
|
||||||
|
$now = time();
|
||||||
|
if (file_exists($rateLimitFile)) {
|
||||||
|
$lastSent = (int) file_get_contents($rateLimitFile);
|
||||||
|
if ($now - $lastSent < 60) {
|
||||||
|
http_response_code(429);
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Odota hetki ennen uutta lähetystä']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Honeypot-kenttä (botisuoja)
|
||||||
|
if (!empty($_POST['website'])) {
|
||||||
|
http_response_code(200);
|
||||||
|
echo json_encode(['success' => true]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Kentät
|
||||||
|
$company = trim($_POST['company'] ?? '');
|
||||||
|
$email = trim($_POST['email'] ?? '');
|
||||||
|
$address = trim($_POST['address'] ?? '');
|
||||||
|
$city = trim($_POST['city'] ?? '');
|
||||||
|
$message = trim($_POST['message'] ?? '');
|
||||||
|
|
||||||
|
// Validointi
|
||||||
|
if (empty($company) || empty($email) || empty($address) || empty($city)) {
|
||||||
|
http_response_code(400);
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Täytä kaikki pakolliset kentät']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
||||||
|
http_response_code(400);
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Tarkista sähköpostiosoite']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sanitointi
|
||||||
|
$company = htmlspecialchars($company, ENT_QUOTES, 'UTF-8');
|
||||||
|
$email = htmlspecialchars($email, ENT_QUOTES, 'UTF-8');
|
||||||
|
$address = htmlspecialchars($address, ENT_QUOTES, 'UTF-8');
|
||||||
|
$city = htmlspecialchars($city, ENT_QUOTES, 'UTF-8');
|
||||||
|
$message = htmlspecialchars($message, ENT_QUOTES, 'UTF-8');
|
||||||
|
|
||||||
|
// Sähköpostin sisältö
|
||||||
|
$to = 'asiakaspalvelu@cuitunet.fi';
|
||||||
|
$subject = "Saatavuuskysely: $company – $address, $city";
|
||||||
|
|
||||||
|
$body = "Uusi saatavuuskysely cuitunet.fi-sivustolta\n";
|
||||||
|
$body .= "=========================================\n\n";
|
||||||
|
$body .= "Yritys: $company\n";
|
||||||
|
$body .= "Sähköposti: $email\n";
|
||||||
|
$body .= "Osoite: $address\n";
|
||||||
|
$body .= "Kaupunki: $city\n";
|
||||||
|
if (!empty($message)) {
|
||||||
|
$body .= "Lisätiedot: $message\n";
|
||||||
|
}
|
||||||
|
$body .= "\n-----------------------------------------\n";
|
||||||
|
$body .= "Lähetetty: " . date('d.m.Y H:i') . "\n";
|
||||||
|
$body .= "IP: $ip\n";
|
||||||
|
|
||||||
|
$headers = "From: sivusto@cuitunet.fi\r\n";
|
||||||
|
$headers .= "Reply-To: $email\r\n";
|
||||||
|
$headers .= "Content-Type: text/plain; charset=UTF-8\r\n";
|
||||||
|
$headers .= "X-Mailer: Cuitunet-Web\r\n";
|
||||||
|
|
||||||
|
// Lähetys
|
||||||
|
$sent = mail($to, $subject, $body, $headers);
|
||||||
|
|
||||||
|
if ($sent) {
|
||||||
|
// Tallenna rate limit
|
||||||
|
file_put_contents($rateLimitFile, $now);
|
||||||
|
|
||||||
|
// Tallenna kopio kyselyistä
|
||||||
|
$logFile = $rateLimitDir . '/kyselyt.log';
|
||||||
|
$logEntry = date('Y-m-d H:i:s') . " | $company | $email | $address, $city | $message\n";
|
||||||
|
file_put_contents($logFile, $logEntry, FILE_APPEND | LOCK_EX);
|
||||||
|
|
||||||
|
echo json_encode(['success' => true]);
|
||||||
|
} else {
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Viestin lähetys epäonnistui. Yritä myöhemmin uudelleen.']);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user