implemented step 18 by Gemini

- Contact formular and emailer script
This commit is contained in:
2026-06-15 04:49:26 +02:00
parent 029d7a232a
commit 269cc5f5d5
6 changed files with 254 additions and 7 deletions

View File

@ -71,18 +71,44 @@ class Renderer
}
$projectExportDir = $this->exportsPath . DIRECTORY_SEPARATOR . $projectId;
$assetsDir = $projectExportDir . DIRECTORY_SEPARATOR . 'assets' . DIRECTORY_SEPARATOR . 'css';
$assetsDir = $projectExportDir . DIRECTORY_SEPARATOR . 'assets';
$cssDir = $assetsDir . DIRECTORY_SEPARATOR . 'css';
$jsDir = $assetsDir . DIRECTORY_SEPARATOR . 'js';
// 1. Create directory structure
if (!is_dir($assetsDir)) {
mkdir($assetsDir, 0777, true);
}
if (!is_dir($cssDir)) mkdir($cssDir, 0777, true);
if (!is_dir($jsDir)) mkdir($jsDir, 0777, true);
// 2. Copy static assets
$siteCssSource = $this->templatesPath . DIRECTORY_SEPARATOR . 'css' . DIRECTORY_SEPARATOR . 'site.css';
copy($siteCssSource, $assetsDir . DIRECTORY_SEPARATOR . 'site.css');
copy($this->templatesPath . '/css/site.css', $cssDir . '/site.css');
copy($this->templatesPath . '/js/site.js', $jsDir . '/site.js');
// 3. Prepare relative paths for assets
// 3. Handle Contact Form Logic
$formConfig = $projectData['wizard_data']['modules']['contact_form'] ?? [];
if (!empty($formConfig['enabled'])) {
// Copy handler
copy($this->templatesPath . '/emailer.php', $projectExportDir . '/ajax.php');
// Create config for handler
$siteConfig = [
'site_name' => $projectData['wizard_data']['identity']['business_name'],
'form_mode' => $formConfig['mode'] ?? 'local',
'smtp' => $formConfig['smtp'] ?? null,
'local_password_hash' => $formConfig['local_viewer']['password_hash'] ?? null
];
file_put_contents($projectExportDir . '/config.json', json_encode($siteConfig, JSON_PRETTY_PRINT));
if ($siteConfig['form_mode'] === 'local') {
// Copy viewer
copy($this->templatesPath . '/form-viewer.php', $projectExportDir . '/form-viewer.php');
// Create messages dir
$msgDir = $projectExportDir . '/messages';
if (!is_dir($msgDir)) mkdir($msgDir, 0777, true);
file_put_contents($msgDir . '/.htaccess', "Deny from all");
}
}
// 4. Prepare relative paths for assets
if (!empty($projectData['wizard_data']['assets']['logo'])) {
$projectData['wizard_data']['assets']['logo'] = $this->makePathRelative($projectData['wizard_data']['assets']['logo'], $projectId);
}

View File

@ -82,5 +82,6 @@ $assets = $project['wizard_data']['assets'] ?? [];
</div>
</footer>
<script src="assets/js/site.js"></script>
</body>
</html>

82
src/Templates/emailer.php Normal file
View File

@ -0,0 +1,82 @@
<?php
/**
* Universal Form Handler for WebWizard exported sites.
* Handles both SMTP mailing and local file storage.
*/
header('Content-Type: application/json; charset=utf-8');
$config = [];
if (file_exists('config.json')) {
$config = json_decode(file_get_contents('config.json'), true);
}
function sendResponse(bool $success, string $message) {
echo json_encode(['success' => $success, 'message' => $message]);
exit;
}
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
sendResponse(false, 'Method not allowed.');
}
// Honeypot spam protection
if (!empty($_POST['website'])) {
sendResponse(true, 'Spam detected.'); // Fake success for bots
}
$name = trim($_POST['name'] ?? '');
$email = trim($_POST['email'] ?? '');
$message = trim($_POST['message'] ?? '');
if (!$name || !$email || !$message) {
sendResponse(false, 'Prosím vyplňte všetky povinné polia.');
}
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
sendResponse(false, 'Neplatná emailová adresa.');
}
$mode = $config['form_mode'] ?? 'local';
if ($mode === 'local') {
// Save to messages directory
$msgDir = 'messages';
if (!is_dir($msgDir)) {
mkdir($msgDir, 0777, true);
}
$msgId = date('Ymd-His') . '-' . bin2hex(random_bytes(4));
$msgData = [
'id' => $msgId,
'timestamp' => date('c'),
'sender_name' => $name,
'sender_email' => $email,
'message' => $message
];
if (file_put_contents($msgDir . '/' . $msgId . '.json', json_encode($msgData, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE))) {
sendResponse(true, 'Vaša správa bola úspešne uložená.');
} else {
sendResponse(false, 'Nepodarilo sa uložiť správu.');
}
} else {
// SMTP / Mail mode
$to = $config['smtp']['recipient'] ?? '';
if (!$to) {
sendResponse(false, 'Cieľový email nie je nakonfigurovaný.');
}
$subject = "Nová správa z webu: " . ($config['site_name'] ?? 'WebWizard');
$body = "Meno: $name\nEmail: $email\n\nSpráva:\n$message";
$headers = "From: webwizard@{$_SERVER['HTTP_HOST']}\r\n" .
"Reply-To: $email\r\n" .
"X-Mailer: PHP/" . phpversion();
// Using standard mail() as fallback for MVP if no complex SMTP is provided
if (mail($to, $subject, $body, $headers)) {
sendResponse(true, 'Vaša správa bola úspešne odoslaná.');
} else {
sendResponse(false, 'Nepodarilo sa odoslať email.');
}
}

View File

@ -0,0 +1,95 @@
<?php
/**
* Local Message Viewer for WebWizard exported sites.
*/
session_start();
$config = [];
if (file_exists('config.json')) {
$config = json_decode(file_get_contents('config.json'), true);
}
$passwordHash = $config['local_password_hash'] ?? null;
// Handle Logout
if (isset($_GET['logout'])) {
session_destroy();
header('Location: form-viewer.php');
exit;
}
// Handle Login
$error = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['password'])) {
if ($passwordHash && password_verify($_POST['password'], $passwordHash)) {
$_SESSION['ww_authorized'] = true;
} else {
$error = 'Nesprávne heslo.';
}
}
$isAuthorized = $_SESSION['ww_authorized'] ?? false;
?>
<!DOCTYPE html>
<html lang="sk">
<head>
<meta charset="UTF-8">
<title>Prehliadač správ - WebWizard</title>
<style>
body { font-family: sans-serif; background: #f4f4f4; padding: 2rem; color: #333; }
.container { max-width: 800px; margin: 0 auto; background: #fff; padding: 2rem; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }
h1 { border-bottom: 2px solid #eee; padding-bottom: 1rem; }
.msg-item { border: 1px solid #eee; padding: 1rem; margin-bottom: 1rem; border-radius: 4px; }
.msg-header { display: flex; justify-content: space-between; font-size: 0.85rem; color: #666; margin-bottom: 0.5rem; }
.msg-sender { font-weight: bold; color: #2563eb; }
.login-form { text-align: center; padding: 2rem 0; }
input[type="password"] { padding: 0.75rem; width: 250px; border: 1px solid #ddd; border-radius: 4px; }
button { padding: 0.75rem 1.5rem; background: #2563eb; color: #fff; border: none; border-radius: 4px; cursor: pointer; }
.error { color: #dc2626; margin-bottom: 1rem; }
.no-messages { text-align: center; color: #666; padding: 2rem; }
.logout-link { float: right; font-size: 0.9rem; color: #666; }
</style>
</head>
<body>
<div class="container">
<?php if (!$isAuthorized): ?>
<div class="login-form">
<h1>Prihlásenie</h1>
<?php if ($error): ?><div class="error"><?= $error ?></div><?php endif; ?>
<form method="POST">
<input type="password" name="password" placeholder="Heslo k správam" required autofocus>
<button type="submit">Vstúpiť</button>
</form>
</div>
<?php else: ?>
<a href="?logout=1" class="logout-link">Odhlásiť sa</a>
<h1>Prijaté správy</h1>
<?php
$files = glob('messages/*.json');
rsort($files); // Newest first
if (empty($files)): ?>
<p class="no-messages">Zatiaľ ste neprijali žiadne správy.</p>
<?php else:
foreach ($files as $file):
$data = json_decode(file_get_contents($file), true);
if ($data): ?>
<div class="msg-item">
<div class="msg-header">
<span><?= htmlspecialchars($data['timestamp']) ?></span>
<span>ID: <?= htmlspecialchars($data['id']) ?></span>
</div>
<div>Od: <span class="msg-sender"><?= htmlspecialchars($data['sender_name']) ?></span> (&lt;<?= htmlspecialchars($data['sender_email']) ?>&gt;)</div>
<div style="margin-top: 1rem; white-space: pre-wrap;"><?= htmlspecialchars($data['message']) ?></div>
</div>
<?php endif;
endforeach;
endif; ?>
<?php endif; ?>
</div>
</body>
</html>

40
src/Templates/js/site.js Normal file
View File

@ -0,0 +1,40 @@
/**
* WebWizard Site Scripts
*/
document.addEventListener('DOMContentLoaded', () => {
const contactForm = document.getElementById('site-contact-form');
if (contactForm) {
contactForm.addEventListener('submit', async (e) => {
e.preventDefault();
const formData = new FormData(contactForm);
const submitBtn = contactForm.querySelector('button[type="submit"]');
const originalBtnText = submitBtn.textContent;
submitBtn.disabled = true;
submitBtn.textContent = 'Odosielam...';
try {
const response = await fetch(contactForm.action, {
method: 'POST',
body: formData
});
const result = await response.json();
alert(result.message);
if (result.success) {
contactForm.reset();
}
} catch (error) {
console.error('Form submission error:', error);
alert('Vyskytla sa chyba pri odosielaní formulára. Skúste to prosím neskôr.');
} finally {
submitBtn.disabled = false;
submitBtn.textContent = originalBtnText;
}
});
}
});

View File

@ -33,6 +33,9 @@ $formEnabled = $project['wizard_data']['modules']['contact_form']['enabled'] ??
<div class="contact-form">
<h3>Napíšte nám</h3>
<form action="ajax.php" method="POST" id="site-contact-form">
<!-- Honeypot -->
<input type="text" name="website" style="display:none !important" tabindex="-1" autocomplete="off">
<div class="form-group">
<input type="text" name="name" placeholder="Vaše meno" required>
</div>