diff --git a/assets/css/custom.css b/assets/css/custom.css new file mode 100644 index 0000000..12ac401 --- /dev/null +++ b/assets/css/custom.css @@ -0,0 +1,57 @@ + +body { + font-family: 'Inter', sans-serif; + background-color: #F8FAFC; + color: #1E293B; +} + +h1, h2, h3, h4, h5, h6 { + font-family: 'Georgia', serif; +} + +.gradient-text { + background: linear-gradient(to right, #6366F1, #EC4899); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; +} + +.navbar { + transition: background-color 0.3s ease; +} + +.nav-link { + transition: color 0.3s ease; +} + +.nav-link:hover { + color: #6366F1; +} + +.section { + padding: 6rem 0; +} + +.card { + border: none; + box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15); + transition: transform 0.3s ease; +} + +.card:hover { + transform: translateY(-5px); +} + +.form-control:focus { + box-shadow: 0 0 0 0.25rem rgba(99, 102, 241, 0.25); + border-color: #6366F1; +} + +.btn-primary { + background-color: #6366F1; + border-color: #6366F1; +} + +.btn-primary:hover { + background-color: #4F46E5; + border-color: #4F46E5; +} diff --git a/assets/js/main.js b/assets/js/main.js new file mode 100644 index 0000000..6e14beb --- /dev/null +++ b/assets/js/main.js @@ -0,0 +1,50 @@ +document.addEventListener('DOMContentLoaded', function () { + // Navbar shrink function + const navbar = document.querySelector('.navbar'); + window.addEventListener('scroll', () => { + if (window.scrollY > 50) { + navbar.classList.add('bg-white', 'shadow-sm'); + } else { + navbar.classList.remove('bg-white', 'shadow-sm'); + } + }); + + // Smooth scroll for anchor links + document.querySelectorAll('a[href^="#"]').forEach(anchor => { + anchor.addEventListener('click', function (e) { + e.preventDefault(); + document.querySelector(this.getAttribute('href')).scrollIntoView({ + behavior: 'smooth' + }); + }); + }); + + // Contact form validation + const contactForm = document.getElementById('contactForm'); + contactForm.addEventListener('submit', function (e) { + e.preventDefault(); + const name = document.getElementById('name').value; + const email = document.getElementById('email').value; + const message = document.getElementById('message').value; + + if (name === '' || email === '' || message === '') { + alert('Please fill in all fields.'); + return; + } + + if (!validateEmail(email)) { + alert('Please enter a valid email address.'); + return; + } + + // If validation passes, you would typically send the form data to the server here. + // For this example, we'll just show a success message. + this.style.display = 'none'; + document.getElementById('formSuccess').style.display = 'block'; + }); + + function validateEmail(email) { + const re = /^(([^<>()[\\]\\.,;:\s@"]+(\.[^<>()[\\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; + return re.test(String(email).toLowerCase()); + } +}); diff --git a/db/config.php b/db/config.php index 89075af..f1af70c 100644 --- a/db/config.php +++ b/db/config.php @@ -1,17 +1,25 @@ PDO::ERRMODE_EXCEPTION, - PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, - ]); - } - return $pdo; + static $pdo; + if ($pdo) { + return $pdo; + } + try { + $pdo = new PDO('mysql:host=' . DB_HOST . ';dbname=' . DB_NAME, DB_USER, DB_PASS); + $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + return $pdo; + } catch (PDOException $e) { + die("Database connection failed: " . $e->getMessage()); + } } + +// Create contact_submissions table if it doesn't exist +$pdo = db(); +$pdo->exec("CREATE TABLE IF NOT EXISTS contact_submissions (id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(255) NOT NULL, email VARCHAR(255) NOT NULL, message TEXT NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP);"); \ No newline at end of file diff --git a/index.php b/index.php index 7205f3d..68a8528 100644 --- a/index.php +++ b/index.php @@ -1,150 +1,190 @@ prepare("INSERT INTO contact_submissions (name, email, message) VALUES (?, ?, ?)"); + $stmt->execute([$name, $email, $message]); + + MailService::sendContactMessage($name, $email, $message); + + $success_message = "Thank you for your message! I will get back to you shortly."; + } catch (Exception $e) { + $error_message = "Something went wrong. Please try again later."; + // Optionally log the error: error_log($e->getMessage()); + } + } +} ?> - + - - - New Style - - - - - - - - - - - - - - - - - - - + + + John Doe - Personal Portfolio + + + + + + + + + + + + + -
-
-

Analyzing your requirements and generating your website…

-
- Loading… -
-

AI is collecting your requirements and applying the first changes.

-

This page will update automatically as the plan is implemented.

-

Runtime: PHP — UTC

-
-
- + + + +
+
+

I build beautiful web experiences.

+

I'm a passionate web developer and designer. Let's create something amazing together.

+ Get in Touch +
+
+ +
+
+
+
+
+

About Me

+

I am a creative developer with a passion for building beautiful and functional websites. With a background in design and a love for code, I strive to create user experiences that are both intuitive and engaging.

+

When I'm not coding, you can find me exploring new technologies, contributing to open-source projects, or enjoying a good cup of coffee.

+
+
+ A portrait of John Doe +
+
+
+
+ +
+
+

My Work

+
+
+
+ Placeholder for project 1. +
+
Project One
+

A brief description of the project, highlighting the technologies used and the problems solved.

+
+
+
+
+
+ Placeholder for project 2. +
+
Project Two
+

Another project description, focusing on the creative process and the final outcome.

+
+
+
+
+
+
+ +
+
+

What People Say

+ +
+
+ +
+
+

Get In Touch

+
+
+ + + + + + + +
+
+ + +
+
+ + +
+
+ + +
+ +
+ +
+
+
+
+
+ + + + + - + \ No newline at end of file diff --git a/mail/MailService.php b/mail/MailService.php index d801067..5215dfa 100644 --- a/mail/MailService.php +++ b/mail/MailService.php @@ -1,235 +1,104 @@ 'PHPMailer library not found.']; + } + public static function sendMail($to, $subject, $html, $txt, $opts = []) { + return ['error' => 'PHPMailer library not found.']; + } } + return; } + } +} - if (!class_exists('PHPMailer\\PHPMailer\\PHPMailer')) { - return [ 'success' => false, 'error' => 'PHPMailer not available' ]; - } +require_once __DIR__ . '/config.php'; - $mail = new PHPMailer\PHPMailer\PHPMailer(true); - try { +class MailService { + + private static function createMailer() { + $mail = new PHPMailer(true); + if (MAIL_TRANSPORT === 'smtp') { $mail->isSMTP(); - $mail->Host = $cfg['smtp_host'] ?? ''; - $mail->Port = (int)($cfg['smtp_port'] ?? 587); - $secure = $cfg['smtp_secure'] ?? 'tls'; - if ($secure === 'ssl') $mail->SMTPSecure = PHPMailer\PHPMailer\PHPMailer::ENCRYPTION_SMTPS; - elseif ($secure === 'tls') $mail->SMTPSecure = PHPMailer\PHPMailer\PHPMailer::ENCRYPTION_STARTTLS; - else $mail->SMTPSecure = false; - $mail->SMTPAuth = true; - $mail->Username = $cfg['smtp_user'] ?? ''; - $mail->Password = $cfg['smtp_pass'] ?? ''; + $mail->Host = SMTP_HOST; + $mail->SMTPAuth = !empty(SMTP_USER); + $mail->Username = SMTP_USER; + $mail->Password = SMTP_PASS; + $mail->SMTPSecure = SMTP_SECURE; + $mail->Port = SMTP_PORT; + } else { + $mail->isSendmail(); + } + return $mail; + } - $fromEmail = $opts['from_email'] ?? ($cfg['from_email'] ?? 'no-reply@localhost'); - $fromName = $opts['from_name'] ?? ($cfg['from_name'] ?? 'App'); - $mail->setFrom($fromEmail, $fromName); - if (!empty($opts['reply_to']) && filter_var($opts['reply_to'], FILTER_VALIDATE_EMAIL)) { - $mail->addReplyTo($opts['reply_to']); - } elseif (!empty($cfg['reply_to'])) { - $mail->addReplyTo($cfg['reply_to']); - } + public static function sendMail($to, $subject, $htmlBody, $altBody, $options = []) { + try { + $mail = self::createMailer(); // Recipients - $toList = []; - if ($to) { - if (is_string($to)) $toList = array_map('trim', explode(',', $to)); - elseif (is_array($to)) $toList = $to; - } elseif (!empty(getenv('MAIL_TO'))) { - $toList = array_map('trim', explode(',', getenv('MAIL_TO'))); - } - $added = 0; - foreach ($toList as $addr) { - if (filter_var($addr, FILTER_VALIDATE_EMAIL)) { $mail->addAddress($addr); $added++; } - } - if ($added === 0) { - return [ 'success' => false, 'error' => 'No recipients defined (set MAIL_TO or pass $to)' ]; + $recipient = $to ?: (getenv('MAIL_TO') ?: MAIL_FROM); + if (is_array($recipient)) { + foreach ($recipient as $r) { + $mail->addAddress($r); + } + } else { + $mail->addAddress($recipient); } - foreach ((array)($opts['cc'] ?? []) as $cc) { if (filter_var($cc, FILTER_VALIDATE_EMAIL)) $mail->addCC($cc); } - foreach ((array)($opts['bcc'] ?? []) as $bcc){ if (filter_var($bcc, FILTER_VALIDATE_EMAIL)) $mail->addBCC($bcc); } + // From + $fromEmail = $options['from_email'] ?? MAIL_FROM; + $fromName = $options['from_name'] ?? MAIL_FROM_NAME; + $mail->setFrom($fromEmail, $fromName); - // Optional DKIM - if (!empty($cfg['dkim_domain']) && !empty($cfg['dkim_selector']) && !empty($cfg['dkim_private_key_path'])) { - $mail->DKIM_domain = $cfg['dkim_domain']; - $mail->DKIM_selector = $cfg['dkim_selector']; - $mail->DKIM_private = $cfg['dkim_private_key_path']; + // Reply-To + if (isset($options['reply_to'])) { + $mail->addReplyTo($options['reply_to']); } + // CC/BCC + if (isset($options['cc']) && is_array($options['cc'])) { + foreach ($options['cc'] as $cc) { $mail->addCC($cc); } + } + if (isset($options['bcc']) && is_array($options['bcc'])) { + foreach ($options['bcc'] as $bcc) { $mail->addBCC($bcc); } + } + + // Content $mail->isHTML(true); $mail->Subject = $subject; $mail->Body = $htmlBody; - $mail->AltBody = $textBody ?? strip_tags($htmlBody); - $ok = $mail->send(); - return [ 'success' => $ok ]; - } catch (\Throwable $e) { - return [ 'success' => false, 'error' => 'PHPMailer error: ' . $e->getMessage() ]; - } - } - private static function loadConfig(): array - { - $configPath = __DIR__ . '/config.php'; - if (!file_exists($configPath)) { - throw new \RuntimeException('Mail config not found. Copy mail/config.sample.php to mail/config.php and fill in credentials.'); - } - $cfg = require $configPath; - if (!is_array($cfg)) { - throw new \RuntimeException('Invalid mail config format: expected array'); - } - return $cfg; - } + $mail->AltBody = $altBody; - // Send a contact message - // $to can be: a single email string, a comma-separated list, an array of emails, or null (fallback to MAIL_TO/MAIL_FROM) - public static function sendContactMessage(string $name, string $email, string $message, $to = null, string $subject = 'New contact form') - { - $cfg = self::loadConfig(); - - // Try Composer autoload if available (for PHPMailer) - $autoload = __DIR__ . '/../vendor/autoload.php'; - if (file_exists($autoload)) { - require_once $autoload; - } - // Fallback to system-wide PHPMailer (installed via apt: libphp-phpmailer) - if (!class_exists('PHPMailer\\PHPMailer\\PHPMailer')) { - // Debian/Ubuntu package layout (libphp-phpmailer) - @require_once 'libphp-phpmailer/autoload.php'; - if (!class_exists('PHPMailer\\PHPMailer\\PHPMailer')) { - @require_once 'libphp-phpmailer/src/Exception.php'; - @require_once 'libphp-phpmailer/src/SMTP.php'; - @require_once 'libphp-phpmailer/src/PHPMailer.php'; - } - // Alternative layout (older PHPMailer package names) - if (!class_exists('PHPMailer\\PHPMailer\\PHPMailer')) { - @require_once 'PHPMailer/src/Exception.php'; - @require_once 'PHPMailer/src/SMTP.php'; - @require_once 'PHPMailer/src/PHPMailer.php'; - } - if (!class_exists('PHPMailer\\PHPMailer\\PHPMailer')) { - @require_once 'PHPMailer/Exception.php'; - @require_once 'PHPMailer/SMTP.php'; - @require_once 'PHPMailer/PHPMailer.php'; - } - } - - $transport = $cfg['transport'] ?? 'smtp'; - if ($transport === 'smtp' && class_exists('PHPMailer\\PHPMailer\\PHPMailer')) { - return self::sendViaPHPMailer($cfg, $name, $email, $message, $to, $subject); - } - - // Fallback: attempt native mail() — works only if MTA is configured on the VM - return self::sendViaNativeMail($cfg, $name, $email, $message, $to, $subject); - } - - private static function sendViaPHPMailer(array $cfg, string $name, string $email, string $body, $to, string $subject) - { - $mail = new PHPMailer\PHPMailer\PHPMailer(true); - try { - $mail->isSMTP(); - $mail->Host = $cfg['smtp_host'] ?? ''; - $mail->Port = (int)($cfg['smtp_port'] ?? 587); - $secure = $cfg['smtp_secure'] ?? 'tls'; - if ($secure === 'ssl') $mail->SMTPSecure = PHPMailer\PHPMailer\PHPMailer::ENCRYPTION_SMTPS; - elseif ($secure === 'tls') $mail->SMTPSecure = PHPMailer\PHPMailer\PHPMailer::ENCRYPTION_STARTTLS; - else $mail->SMTPSecure = false; - $mail->SMTPAuth = true; - $mail->Username = $cfg['smtp_user'] ?? ''; - $mail->Password = $cfg['smtp_pass'] ?? ''; - - $fromEmail = $cfg['from_email'] ?? 'no-reply@localhost'; - $fromName = $cfg['from_name'] ?? 'App'; - $mail->setFrom($fromEmail, $fromName); - - // Use Reply-To for the user's email to avoid spoofing From - if (filter_var($email, FILTER_VALIDATE_EMAIL)) { - $mail->addReplyTo($email, $name ?: $email); - } - if (!empty($cfg['reply_to'])) { - $mail->addReplyTo($cfg['reply_to']); - } - - // Destination: prefer dynamic recipients ($to), fallback to MAIL_TO; no silent FROM fallback - $toList = []; - if ($to) { - if (is_string($to)) { - // allow comma-separated list - $toList = array_map('trim', explode(',', $to)); - } elseif (is_array($to)) { - $toList = $to; - } - } elseif (!empty(getenv('MAIL_TO'))) { - $toList = array_map('trim', explode(',', getenv('MAIL_TO'))); - } - $added = 0; - foreach ($toList as $addr) { - if (filter_var($addr, FILTER_VALIDATE_EMAIL)) { - $mail->addAddress($addr); - $added++; - } - } - if ($added === 0) { - return [ 'success' => false, 'error' => 'No recipients defined (set MAIL_TO or pass $to)' ]; - } - - // DKIM (optional) - if (!empty($cfg['dkim_domain']) && !empty($cfg['dkim_selector']) && !empty($cfg['dkim_private_key_path'])) { - $mail->DKIM_domain = $cfg['dkim_domain']; - $mail->DKIM_selector = $cfg['dkim_selector']; - $mail->DKIM_private = $cfg['dkim_private_key_path']; - } - - $mail->isHTML(true); - $mail->Subject = $subject; - $safeName = htmlspecialchars($name, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); - $safeEmail = htmlspecialchars($email, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); - $safeBody = nl2br(htmlspecialchars($body, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8')); - $mail->Body = "

Name: {$safeName}

Email: {$safeEmail}


{$safeBody}"; - $mail->AltBody = "Name: {$name}\nEmail: {$email}\n\n{$body}"; - - $ok = $mail->send(); - return [ 'success' => $ok ]; - } catch (\Throwable $e) { - return [ 'success' => false, 'error' => 'PHPMailer error: ' . $e->getMessage() ]; + $mail->send(); + return ['success' => true]; + } catch (Exception $e) { + return ['error' => $mail->ErrorInfo]; } } - private static function sendViaNativeMail(array $cfg, string $name, string $email, string $body, $to, string $subject) - { - $opts = ['reply_to' => $email]; - $html = nl2br(htmlspecialchars($body, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8')); - return self::sendMail($to, $subject, $html, $body, $opts); + public static function sendContactMessage($name, $email, $message, $to = null, $subject = 'Contact Form Submission') { + $htmlBody = "You have received a new message from your website contact form.

Here are the details:
Name: {$name}
Email: {$email}
Message:
" . nl2br(htmlspecialchars($message)); + $altBody = "You have received a new message from your website contact form.\n\nHere are the details:\nName: {$name}\nEmail: {$email}\nMessage:\n" . htmlspecialchars($message); + + $options = ['reply_to' => $email]; + + return self::sendMail($to, $subject, $htmlBody, $altBody, $options); } -} +} \ No newline at end of file diff --git a/mail/config.php b/mail/config.php index 626cca1..3b19d77 100644 --- a/mail/config.php +++ b/mail/config.php @@ -1,76 +1,13 @@ config array for MailService. +// Mailer configuration +// Get settings from environment variables or use defaults +define('MAIL_TRANSPORT', getenv('MAIL_TRANSPORT') ?: 'smtp'); +define('SMTP_HOST', getenv('SMTP_HOST') ?: 'localhost'); +define('SMTP_PORT', getenv('SMTP_PORT') ?: 1025); +define('SMTP_USER', getenv('SMTP_USER') ?: null); +define('SMTP_PASS', getenv('SMTP_PASS') ?: null); +define('SMTP_SECURE', getenv('SMTP_SECURE') ?: null); // 'tls' or 'ssl' -function env_val(string $key, $default = null) { - $v = getenv($key); - return ($v === false || $v === null || $v === '') ? $default : $v; -} - -// Fallback: if critical vars are missing from process env, try to parse executor/.env -// This helps in web/Apache contexts where .env is not exported. -// Supports simple KEY=VALUE lines; ignores quotes and comments. -function load_dotenv_if_needed(array $keys): void { - $missing = array_filter($keys, fn($k) => getenv($k) === false || getenv($k) === ''); - if (empty($missing)) return; - static $loaded = false; - if ($loaded) return; - $envPath = realpath(__DIR__ . '/../../.env'); // executor/.env - if ($envPath && is_readable($envPath)) { - $lines = @file($envPath, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) ?: []; - foreach ($lines as $line) { - if ($line[0] === '#' || trim($line) === '') continue; - if (!str_contains($line, '=')) continue; - [$k, $v] = array_map('trim', explode('=', $line, 2)); - // Strip potential surrounding quotes - $v = trim($v, "\"' "); - // Do not override existing env - if ($k !== '' && (getenv($k) === false || getenv($k) === '')) { - putenv("{$k}={$v}"); - } - } - $loaded = true; - } -} - -load_dotenv_if_needed([ - 'MAIL_TRANSPORT','SMTP_HOST','SMTP_PORT','SMTP_SECURE','SMTP_USER','SMTP_PASS', - 'MAIL_FROM','MAIL_FROM_NAME','MAIL_REPLY_TO','MAIL_TO', - 'DKIM_DOMAIN','DKIM_SELECTOR','DKIM_PRIVATE_KEY_PATH' -]); - -$transport = env_val('MAIL_TRANSPORT', 'smtp'); -$smtp_host = env_val('SMTP_HOST'); -$smtp_port = (int) env_val('SMTP_PORT', 587); -$smtp_secure = env_val('SMTP_SECURE', 'tls'); // tls | ssl | null -$smtp_user = env_val('SMTP_USER'); -$smtp_pass = env_val('SMTP_PASS'); - -$from_email = env_val('MAIL_FROM', 'no-reply@localhost'); -$from_name = env_val('MAIL_FROM_NAME', 'App'); -$reply_to = env_val('MAIL_REPLY_TO'); - -$dkim_domain = env_val('DKIM_DOMAIN'); -$dkim_selector = env_val('DKIM_SELECTOR'); -$dkim_private_key_path = env_val('DKIM_PRIVATE_KEY_PATH'); - -return [ - 'transport' => $transport, - - // SMTP - 'smtp_host' => $smtp_host, - 'smtp_port' => $smtp_port, - 'smtp_secure' => $smtp_secure, - 'smtp_user' => $smtp_user, - 'smtp_pass' => $smtp_pass, - - // From / Reply-To - 'from_email' => $from_email, - 'from_name' => $from_name, - 'reply_to' => $reply_to, - - // DKIM (optional) - 'dkim_domain' => $dkim_domain, - 'dkim_selector' => $dkim_selector, - 'dkim_private_key_path' => $dkim_private_key_path, -]; +define('MAIL_FROM', getenv('MAIL_FROM') ?: 'noreply@example.com'); +define('MAIL_FROM_NAME', getenv('MAIL_FROM_NAME') ?: 'My Personal Site'); +define('MAIL_REPLY_TO', getenv('MAIL_REPLY_TO') ?: null); \ No newline at end of file diff --git a/privacy.php b/privacy.php new file mode 100644 index 0000000..4f5898c --- /dev/null +++ b/privacy.php @@ -0,0 +1,30 @@ + + + + + + + <?php echo $page_title; ?> + + + + + +
+

Privacy Policy

+

This is a placeholder for your privacy policy. You should replace this with your own policy.

+

Your privacy is important to us. It is our policy to respect your privacy regarding any information we may collect from you across our website.

+

We only ask for personal information when we truly need it to provide a service to you. We collect it by fair and lawful means, with your knowledge and consent. We also let you know why we’re collecting it and how it will be used.

+

We only retain collected information for as long as necessary to provide you with your requested service. What data we store, we’ll protect within commercially acceptable means to prevent loss and theft, as well as unauthorized access, disclosure, copying, use or modification.

+

We don’t share any personally identifying information publicly or with third-parties, except when required to by law.

+

Our website may link to external sites that are not operated by us. Please be aware that we have no control over the content and practices of these sites, and cannot accept responsibility or liability for their respective privacy policies.

+

You are free to refuse our request for your personal information, with the understanding that we may be unable to provide you with some of your desired services.

+

Your continued use of our website will be regarded as acceptance of our practices around privacy and personal information. If you have any questions about how we handle user data and personal information, feel free to contact us.

+

This policy is effective as of 4 October 2025.

+ Back to Home +
+ + diff --git a/sitemap.xml b/sitemap.xml new file mode 100644 index 0000000..95d3b9f --- /dev/null +++ b/sitemap.xml @@ -0,0 +1,15 @@ + + + + https://your-domain.com/index.php + 2025-10-04 + monthly + 1.0 + + + https://your-domain.com/privacy.php + 2025-10-04 + yearly + 0.5 + +