commit 02a110e93a216183fc2d2b902adb18ba4a6ab3f7 Author: Flatlogic Bot Date: Fri Sep 19 13:06:01 2025 +0000 Initial version diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e427ff3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +node_modules/ +*/node_modules/ +*/build/ diff --git a/.htaccess b/.htaccess new file mode 100644 index 0000000..e2bbc23 --- /dev/null +++ b/.htaccess @@ -0,0 +1,18 @@ +DirectoryIndex index.php index.html +Options -Indexes +Options -MultiViews + +RewriteEngine On + +# 0) Serve existing files/directories as-is +RewriteCond %{REQUEST_FILENAME} -f [OR] +RewriteCond %{REQUEST_FILENAME} -d +RewriteRule ^ - [L] + +# 1) Internal map: /page or /page/ -> /page.php (if such PHP file exists) +RewriteCond %{REQUEST_FILENAME}.php -f +RewriteRule ^(.+?)/?$ $1.php [L] + +# 2) Optional: strip trailing slash for non-directories (keeps .php links working) +RewriteCond %{REQUEST_FILENAME} !-d +RewriteRule ^(.+)/$ $1 [R=301,L] diff --git a/.perm_test_apache b/.perm_test_apache new file mode 100644 index 0000000..e69de29 diff --git a/.perm_test_exec b/.perm_test_exec new file mode 100644 index 0000000..e69de29 diff --git a/assets/css/custom.css b/assets/css/custom.css new file mode 100644 index 0000000..6247e42 --- /dev/null +++ b/assets/css/custom.css @@ -0,0 +1,118 @@ + +/* + Custom Styles for Personal Portfolio +*/ + +:root { + --primary-color: #3B82F6; + --secondary-color: #10B981; + --background-color: #F9FAFB; + --surface-color: #FFFFFF; + --text-color: #1F2937; + --light-text-color: #6B7280; + --border-radius: 0.5rem; + --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05); + --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1); +} + +body { + font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; + background-color: var(--background-color); + color: var(--text-color); +} + +.navbar { + transition: background-color 0.3s ease-in-out; +} + +.navbar-scrolled { + background-color: var(--surface-color); + box-shadow: var(--shadow-md); +} + +.hero { + color: white; + padding: 8rem 0; + position: relative; + text-align: center; +} + +.hero::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-image: linear-gradient(to right, rgba(59, 130, 246, 0.8), rgba(16, 185, 129, 0.8)), url('../images/hero-kittens-45170.jpg'); + background-size: cover; + background-position: center; + z-index: -1; +} + +.hero h1 { + font-size: 3.5rem; + font-weight: 700; +} + +.hero p { + font-size: 1.25rem; +} + +.btn-primary { + background-color: var(--primary-color); + border-color: var(--primary-color); + padding: 0.75rem 1.5rem; + border-radius: var(--border-radius); + font-weight: 600; +} + +.btn-primary:hover { + opacity: 0.9; + background-color: var(--primary-color); + border-color: var(--primary-color); +} + +.section { + padding: 5rem 0; +} + +.section-title { + text-align: center; + margin-bottom: 3rem; + font-size: 2.5rem; + font-weight: 700; +} + +.portfolio-card { + border: none; + border-radius: var(--border-radius); + box-shadow: var(--shadow-md); + overflow: hidden; + transition: transform 0.3s ease, box-shadow 0.3s ease; +} + +.portfolio-card:hover { + transform: translateY(-5px); + box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1); +} + +.form-control:focus { + box-shadow: 0 0 0 0.25rem rgba(59, 130, 246, 0.25); + border-color: var(--primary-color); +} + +footer { + background-color: var(--surface-color); + padding: 2rem 0; + border-top: 1px solid #e5e7eb; +} + +#contact-alert { + position: fixed; + top: 1rem; + right: 1rem; + z-index: 1050; + display: none; + min-width: 250px; +} diff --git a/assets/images/hero-kittens-45170.jpg b/assets/images/hero-kittens-45170.jpg new file mode 100644 index 0000000..ee824de Binary files /dev/null and b/assets/images/hero-kittens-45170.jpg differ diff --git a/assets/images/kitten-4450241.jpg b/assets/images/kitten-4450241.jpg new file mode 100644 index 0000000..f76974d Binary files /dev/null and b/assets/images/kitten-4450241.jpg differ diff --git a/assets/images/kitten-7778872.jpg b/assets/images/kitten-7778872.jpg new file mode 100644 index 0000000..0adc9d0 Binary files /dev/null and b/assets/images/kitten-7778872.jpg differ diff --git a/assets/images/kitten-7778873.jpg b/assets/images/kitten-7778873.jpg new file mode 100644 index 0000000..9184168 Binary files /dev/null and b/assets/images/kitten-7778873.jpg differ diff --git a/assets/images/pexels/355952.jpg b/assets/images/pexels/355952.jpg new file mode 100644 index 0000000..d6c9e22 Binary files /dev/null and b/assets/images/pexels/355952.jpg differ diff --git a/assets/images/pexels/546819.jpg b/assets/images/pexels/546819.jpg new file mode 100644 index 0000000..ea4f106 Binary files /dev/null and b/assets/images/pexels/546819.jpg differ diff --git a/assets/images/pexels/577585.jpg b/assets/images/pexels/577585.jpg new file mode 100644 index 0000000..43151ea Binary files /dev/null and b/assets/images/pexels/577585.jpg differ diff --git a/assets/js/main.js b/assets/js/main.js new file mode 100644 index 0000000..e6d5f79 --- /dev/null +++ b/assets/js/main.js @@ -0,0 +1,75 @@ + +document.addEventListener('DOMContentLoaded', function () { + + // Navbar scroll effect + const navbar = document.querySelector('.navbar'); + if (navbar) { + window.addEventListener('scroll', () => { + if (window.scrollY > 50) { + navbar.classList.add('navbar-scrolled'); + } else { + navbar.classList.remove('navbar-scrolled'); + } + }); + } + + // Contact form submission + const contactForm = document.getElementById('contactForm'); + if (contactForm) { + contactForm.addEventListener('submit', function (e) { + e.preventDefault(); + + const name = document.getElementById('name').value.trim(); + const email = document.getElementById('email').value.trim(); + const message = document.getElementById('message').value.trim(); + const alertEl = document.getElementById('contact-alert'); + const submitButton = contactForm.querySelector('button[type="submit"]'); + + if (!name || !email || !message) { + showAlert('Please fill in all fields.', 'danger'); + return; + } + + if (!/^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$/.test(email)) { + showAlert('Please enter a valid email address.', 'danger'); + return; + } + + const formData = new FormData(this); + submitButton.disabled = true; + submitButton.innerHTML = ' Sending...'; + + fetch('contact.php', { + method: 'POST', + body: formData + }) + .then(response => response.json()) + .then(data => { + if (data.success) { + showAlert('Message sent successfully! I will get back to you soon.', 'success'); + contactForm.reset(); + } else { + showAlert(data.error || 'An error occurred. Please try again.', 'danger'); + } + }) + .catch(() => { + showAlert('A network error occurred. Please try again.', 'danger'); + }) + .finally(() => { + submitButton.disabled = false; + submitButton.innerHTML = 'Send Message'; + }); + }); + } + + function showAlert(message, type) { + const alertEl = document.getElementById('contact-alert'); + alertEl.innerHTML = message; + alertEl.className = `alert alert-${type}`; + alertEl.style.display = 'block'; + + setTimeout(() => { + alertEl.style.display = 'none'; + }, 5000); + } +}); diff --git a/contact.php b/contact.php new file mode 100644 index 0000000..b774dcf --- /dev/null +++ b/contact.php @@ -0,0 +1,43 @@ + false, 'error' => 'An unknown error occurred.']; + +if ($_SERVER['REQUEST_METHOD'] === 'POST') { + $name = trim($_POST['name'] ?? ''); + $email = trim($_POST['email'] ?? ''); + $message = trim($_POST['message'] ?? ''); + + if (empty($name) || empty($email) || empty($message)) { + $response['error'] = 'Please fill out all fields.'; + } elseif (!filter_var($email, FILTER_VALIDATE_EMAIL)) { + $response['error'] = 'Please provide a valid email address.'; + } else { + $subject = 'New Contact Form Submission from ' . htmlspecialchars($name); + + // Using MailService to send the contact message. + // The user's email is automatically set as the Reply-To address. + // The recipient is determined by the MailService configuration (defaults to MAIL_TO in .env). + $result = MailService::sendContactMessage($name, $email, $message, null, $subject); + + if (!empty($result['success'])) { + $response['success'] = true; + unset($response['error']); + } else { + // For security, we don't expose detailed mailer errors to the client. + // The error is logged on the server for debugging. + error_log('MailService Error: ' . ($result['error'] ?? 'Unknown error while sending email.')); + $response['error'] = 'Sorry, there was an issue sending your message. Please try again later.'; + } + } +} else { + http_response_code(405); // Method Not Allowed + $response['error'] = 'Invalid request method.'; +} + +echo json_encode($response); diff --git a/db/config.php b/db/config.php new file mode 100644 index 0000000..807c63d --- /dev/null +++ b/db/config.php @@ -0,0 +1,17 @@ + PDO::ERRMODE_EXCEPTION, + PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, + ]); + } + return $pdo; +} diff --git a/includes/pexels.php b/includes/pexels.php new file mode 100644 index 0000000..156d6fd --- /dev/null +++ b/includes/pexels.php @@ -0,0 +1,55 @@ + 0 ? $k : 'Vc99rnmOhHhJAbgGQoKLZtsaIVfkeownoQNbTj78VemUjKh08ZYRbf18'; +} + +function pexels_get($url) { + $ch = curl_init(); + curl_setopt_array($ch, [ + CURLOPT_URL => $url, + CURLOPT_RETURNTRANSFER => true, + CURLOPT_HTTPHEADER => [ 'Authorization: '. pexels_key() ], + CURLOPT_TIMEOUT => 15, + ]); + $resp = curl_exec($ch); + $code = curl_getinfo($ch, CURLINFO_HTTP_CODE); + curl_close($ch); + if ($code >= 200 && $code < 300 && $resp) { + return json_decode($resp, true); + } + return null; +} + +function download_to($srcUrl, $destPath) { + if (!is_dir(dirname($destPath))) { + if (!mkdir(dirname($destPath), 0775, true)) { + return false; + } + } + + $ch = curl_init($srcUrl); + $fp = fopen($destPath, 'wb'); + if (!$fp) { + curl_close($ch); + return false; + } + + curl_setopt($ch, CURLOPT_FILE, $fp); + curl_setopt($ch, CURLOPT_HEADER, 0); + curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); + curl_setopt($ch, CURLOPT_TIMEOUT, 60); + $result = curl_exec($ch); + $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE); + curl_close($ch); + fclose($fp); + + if ($result && $http_code >= 200 && $http_code < 300) { + return $destPath; + } + + if (file_exists($destPath)) { + unlink($destPath); + } + return false; +} diff --git a/index.php b/index.php new file mode 100644 index 0000000..b51a6a1 --- /dev/null +++ b/index.php @@ -0,0 +1,147 @@ + + + + + + John Doe - Personal Portfolio + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

Welcome to My Digital Space

+

I build modern, responsive, and beautiful web applications.

+ View My Work +
+
+ +
+
+
+

About Me

+
+
+

I am a passionate web developer with a love for clean code and user-centric design. With a background in computer science and several years of experience, I specialize in creating dynamic and engaging websites. When I'm not coding, I enjoy hiking and photography.

+
+
+
+
+ +
+
+

Portfolio

+
+
+
+ A computer screen with code on it. +
+
Project One
+

A brief description of the project, its purpose, and the technologies used. This is a placeholder.

+
+
+
+
+
+ A person's hands typing on a laptop. +
+
Project Two
+

A brief description of the project, its purpose, and the technologies used. This is a placeholder.

+
+
+
+
+
+ Code on a screen with a blurry background. +
+
Project Three
+

A brief description of the project, its purpose, and the technologies used. This is a placeholder.

+
+
+
+
+
+
+ +
+
+

Get In Touch

+
+
+
+
+ + +
+
+ + +
+
+ + +
+
+ +
+
+
+ For testing purposes, emails may be sent to a default address. Please configure your own SMTP settings in the .env file for production use. +
+
+
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/mail/MailService.php b/mail/MailService.php new file mode 100644 index 0000000..d801067 --- /dev/null +++ b/mail/MailService.php @@ -0,0 +1,235 @@ + false, 'error' => 'PHPMailer not available' ]; + } + + $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 = $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']); + } + + // 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)' ]; + } + + 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); } + + // 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']; + } + + $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; + } + + // 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() ]; + } + } + + 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); + } +} diff --git a/mail/config.php b/mail/config.php new file mode 100644 index 0000000..626cca1 --- /dev/null +++ b/mail/config.php @@ -0,0 +1,76 @@ + config array for MailService. + +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, +];