smtp config

This commit is contained in:
Flatlogic Bot 2026-02-27 06:54:46 +00:00
parent fa60b1d6db
commit 9b31c32aba
9 changed files with 984 additions and 602 deletions

View File

@ -4,9 +4,11 @@ require_once __DIR__ . "/../db/config.php";
require_permission("settings_view");
require_once __DIR__ . '/../db/config.php';
require_once __DIR__ . '/../includes/WablasService.php';
require_once __DIR__ . '/../mail/MailService.php';
$pdo = db();
$wablasTestResult = null;
$smtpTestResult = null;
$message = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
@ -61,6 +63,30 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
}
}
}
// SMTP
if ($provider === 'smtp') {
$keys = ['host', 'port', 'secure', 'username', 'password', 'from_email', 'from_name'];
foreach ($keys as $k) {
$val = $_POST[$k] ?? '';
$stmt = $pdo->prepare("INSERT INTO integration_settings (provider, setting_key, setting_value) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE setting_value = VALUES(setting_value)");
$stmt->execute(['smtp', $k, $val]);
}
if ($action === 'save') {
header("Location: integrations.php?msg=saved");
exit;
} elseif ($action === 'test') {
$testEmail = $_POST['test_email'] ?? '';
if (!empty($testEmail)) {
// We need to use the new values immediately for testing
// MailService usually loads from config, which we will update next
// For now, let's just use the provided values if we can or wait until config is updated.
// Actually, let's update config first then test.
$smtpTestResult = MailService::sendMail($testEmail, "SMTP Test Message", "Your SMTP configuration is working correctly!", "Your SMTP configuration is working correctly!");
}
}
}
}
// Fetch current settings
@ -86,6 +112,15 @@ $wablasSecKey = getSetting($allSettings, 'wablas', 'secret_key');
$wablasTemplate = getSetting($allSettings, 'wablas', 'order_template');
$wablasEnabled = getSetting($allSettings, 'wablas', 'is_enabled');
// SMTP Settings
$smtpHost = getSetting($allSettings, 'smtp', 'host');
$smtpPort = getSetting($allSettings, 'smtp', 'port') ?: '587';
$smtpSecure = getSetting($allSettings, 'smtp', 'secure') ?: 'tls';
$smtpUser = getSetting($allSettings, 'smtp', 'username');
$smtpPass = getSetting($allSettings, 'smtp', 'password');
$smtpFromEmail = getSetting($allSettings, 'smtp', 'from_email');
$smtpFromName = getSetting($allSettings, 'smtp', 'from_name');
// Default template if empty
if (empty($wablasTemplate)) {
$wablasTemplate = "Dear *{customer_name}*,
@ -127,7 +162,76 @@ require_once __DIR__ . '/includes/header.php';
</div>
<?php endif; ?>
<?php if ($smtpTestResult): ?>
<div class="alert alert-<?= $smtpTestResult['success'] ? 'success' : 'danger' ?> alert-dismissible fade show" role="alert">
<strong>SMTP Test Result:</strong> <?= $smtpTestResult['success'] ? 'Success! Test email sent.' : 'Error: ' . htmlspecialchars($smtpTestResult['error']) ?>
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
<?php endif; ?>
<div class="row">
<!-- SMTP Settings -->
<div class="col-md-12 mb-4">
<div class="card shadow">
<div class="card-header py-3 d-flex flex-row align-items-center justify-content-between">
<h6 class="m-0 fw-bold text-primary">SMTP Configuration</h6>
</div>
<div class="card-body">
<form method="POST">
<input type="hidden" name="provider" value="smtp">
<div class="row">
<div class="col-md-6 mb-3">
<label class="form-label">SMTP Host</label>
<input type="text" class="form-control" name="host" placeholder="smtp.gmail.com" value="<?= htmlspecialchars($smtpHost) ?>" <?= !has_permission('settings_add') ? 'readonly' : '' ?>>
</div>
<div class="col-md-3 mb-3">
<label class="form-label">SMTP Port</label>
<input type="number" class="form-control" name="port" value="<?= htmlspecialchars($smtpPort) ?>" <?= !has_permission('settings_add') ? 'readonly' : '' ?>>
</div>
<div class="col-md-3 mb-3">
<label class="form-label">Encryption</label>
<select class="form-select" name="secure" <?= !has_permission('settings_add') ? 'disabled' : '' ?>>
<option value="tls" <?= $smtpSecure == 'tls' ? 'selected' : '' ?>>TLS</option>
<option value="ssl" <?= $smtpSecure == 'ssl' ? 'selected' : '' ?>>SSL</option>
<option value="none" <?= $smtpSecure == 'none' ? 'selected' : '' ?>>None</option>
</select>
</div>
<div class="col-md-6 mb-3">
<label class="form-label">SMTP Username</label>
<input type="text" class="form-control" name="username" value="<?= htmlspecialchars($smtpUser) ?>" <?= !has_permission('settings_add') ? 'readonly' : '' ?>>
</div>
<div class="col-md-6 mb-3">
<label class="form-label">SMTP Password</label>
<input type="password" class="form-control" name="password" value="<?= htmlspecialchars($smtpPass) ?>" <?= !has_permission('settings_add') ? 'readonly' : '' ?>>
</div>
<div class="col-md-6 mb-3">
<label class="form-label">From Email</label>
<input type="email" class="form-control" name="from_email" placeholder="noreply@yourdomain.com" value="<?= htmlspecialchars($smtpFromEmail) ?>" <?= !has_permission('settings_add') ? 'readonly' : '' ?>>
</div>
<div class="col-md-6 mb-3">
<label class="form-label">From Name</label>
<input type="text" class="form-control" name="from_name" placeholder="Business Name" value="<?= htmlspecialchars($smtpFromName) ?>" <?= !has_permission('settings_add') ? 'readonly' : '' ?>>
</div>
</div>
<?php if (has_permission('settings_add')): ?>
<div class="mb-3 border-top pt-3">
<label class="form-label text-muted small">Test SMTP Connection</label>
<div class="input-group">
<input type="email" class="form-control" name="test_email" placeholder="receiver@example.com" value="<?= htmlspecialchars($_POST['test_email'] ?? '') ?>">
<button type="submit" name="action" value="test" class="btn btn-info text-white">Save & Send Test Email</button>
</div>
</div>
<div class="d-flex justify-content-end">
<button type="submit" name="action" value="save" class="btn btn-primary">Save SMTP Settings</button>
</div>
<?php endif; ?>
</form>
</div>
</div>
</div>
<!-- Thawani -->
<div class="col-md-6 mb-4">
<div class="card shadow h-100">
@ -174,7 +278,6 @@ require_once __DIR__ . '/includes/header.php';
<form method="POST" id="wablas_form">
<input type="hidden" name="provider" value="wablas">
<!-- Also keep a hidden input to send '0' if checkbox is unchecked (handled in PHP POST block too) -->
<div class="mb-3">
<label class="form-label">Domain</label>
<input type="text" class="form-control" name="domain" placeholder="https://..." value="<?= htmlspecialchars($wablasDom) ?>" <?= !has_permission('settings_add') ? 'readonly' : '' ?>>
@ -206,7 +309,6 @@ require_once __DIR__ . '/includes/header.php';
<input type="text" class="form-control" name="test_phone" placeholder="e.g. 62812345678" value="<?= htmlspecialchars($_POST['test_phone'] ?? '') ?>">
<button type="submit" name="action" value="test" class="btn btn-info text-white">Test & Send Message</button>
</div>
<small class="text-muted">Enter a phone number to send a real test message.</small>
</div>
<div class="d-flex justify-content-end">
@ -220,4 +322,4 @@ require_once __DIR__ . '/includes/header.php';
</div>
</div>
<?php require_once __DIR__ . '/includes/header.php'; ?>
<?php require_once __DIR__ . '/includes/footer.php'; ?>

View File

@ -56,7 +56,7 @@ if (isset($_GET['delete'])) {
}
// Fetch Outlets for Filter
$outlets = $pdo->query("SELECT id, name FROM outlets ORDER BY name")->fetchAll(PDO::FETCH_ASSOC);
$outlets = $pdo->query("SELECT id, name FROM outlets WHERE is_deleted = 0 ORDER BY name")->fetchAll(PDO::FETCH_ASSOC);
// Build Query with Filters
$params = [];
@ -78,11 +78,16 @@ if (!empty($_GET['end_date'])) {
$params[':end_date'] = $_GET['end_date'];
}
// Filter: Search (Order No)
// Filter: Search (Order No / Customer Name)
if (!empty($_GET['search'])) {
if (is_numeric($_GET['search'])) {
$where[] = "o.id = :search";
$params[':search'] = $_GET['search'];
$searchTerm = $_GET['search'];
if (is_numeric($searchTerm)) {
$where[] = "(o.id = :search_exact OR o.customer_name LIKE :search_like)";
$params[':search_exact'] = $searchTerm;
$params[':search_like'] = "%$searchTerm%";
} else {
$where[] = "o.customer_name LIKE :search";
$params[':search'] = "%$searchTerm%";
}
}
@ -246,7 +251,7 @@ include 'includes/header.php';
</div>
<div class="col-md-3">
<label class="form-label small fw-bold text-muted">Search</label>
<input type="text" name="search" class="form-control" placeholder="Order No (ID)" value="<?= htmlspecialchars($_GET['search'] ?? '') ?>">
<input type="text" name="search" class="form-control" placeholder="Order No / Customer" value="<?= htmlspecialchars($_GET['search'] ?? '') ?>">
</div>
<div class="col-md-3">
<div class="d-flex gap-2">

View File

@ -16,7 +16,7 @@ ALTER TABLE `tables` ADD COLUMN IF NOT EXISTS `is_deleted` TINYINT(1) DEFAULT 0;
-- But usually, if they have 'name', they need to move it to 'table_number'.
-- If name exists, this will work. If not, it will fail, which is okay for this specific fix.
UPDATE `tables` SET `table_number` = `name` WHERE `table_number` IS NULL OR `table_number` = '';
-- UPDATE `tables` SET `table_number` = `name` WHERE `table_number` IS NULL OR `table_number` = '';
-- Fix areas foreign key if it was wrong in their initial setup
ALTER TABLE `areas` DROP FOREIGN KEY IF EXISTS `areas_ibfk_1`;

View File

@ -0,0 +1,2 @@
ALTER TABLE users ADD COLUMN reset_token VARCHAR(255) DEFAULT NULL;
ALTER TABLE users ADD COLUMN reset_token_expiry DATETIME DEFAULT NULL;

122
forgot_password.php Normal file
View File

@ -0,0 +1,122 @@
<?php
require_once __DIR__ . '/db/config.php';
require_once __DIR__ . '/includes/functions.php';
require_once __DIR__ . '/mail/MailService.php';
init_session();
$baseUrl = get_base_url();
$settings = get_company_settings();
$error = '';
$success = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$email = $_POST['email'] ?? '';
if (empty($email)) {
$error = 'Please enter your email address.';
} else {
$pdo = db();
$stmt = $pdo->prepare("SELECT id, username, full_name FROM users WHERE email = ? AND is_deleted = 0 LIMIT 1");
$stmt->execute([$email]);
$user = $stmt->fetch(PDO::FETCH_ASSOC);
if ($user) {
$token = bin2hex(random_bytes(32));
$expiry = date('Y-m-d H:i:s', strtotime('+1 hour'));
$stmt = $pdo->prepare("UPDATE users SET reset_token = ?, reset_token_expiry = ? WHERE id = ?");
$stmt->execute([$token, $expiry, $user['id']]);
$resetLink = $baseUrl . "reset_password.php?token=" . $token;
$subject = "Password Reset Request - " . $settings['company_name'];
$messageHtml = "
<h2>Password Reset Request</h2>
<p>Hello " . htmlspecialchars($user['full_name'] ?: $user['username']) . ",</p>
<p>We received a request to reset your password. Click the button below to set a new password:</p>
<p><a href='$resetLink' style='background: #0d6efd; color: white; padding: 10px 20px; text-decoration: none; border-radius: 5px; display: inline-block;'>Reset Password</a></p>
<p>If you did not request this, please ignore this email.</p>
<p>This link will expire in 1 hour.</p>
";
$messageTxt = "Hello, click here to reset your password: $resetLink. This link will expire in 1 hour.";
$res = MailService::sendMail($email, $subject, $messageHtml, $messageTxt);
if (!empty($res['success'])) {
$success = 'Password reset instructions have been sent to your email.';
} else {
$error = 'Failed to send reset email. Please contact administrator.';
// error_log($res['error']);
}
} else {
// We show success anyway for security reasons to prevent email enumeration
$success = 'If that email exists in our system, you will receive reset instructions shortly.';
}
}
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Forgot Password - <?= htmlspecialchars($settings['company_name']) ?></title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css">
<link rel="stylesheet" href="<?= $baseUrl ?>assets/css/custom.css">
<style>
body {
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
height: 100vh;
display: flex;
align-items: center;
justify-content: center;
}
.login-card {
width: 100%;
max-width: 400px;
border-radius: 1.5rem;
box-shadow: 0 1rem 3rem rgba(0,0,0,0.1);
}
</style>
</head>
<body>
<div class="card login-card border-0">
<div class="card-body p-5">
<div class="text-center mb-4">
<h3 class="fw-bold">Forgot Password</h3>
<p class="text-muted small">Enter your email address to receive a reset link</p>
</div>
<?php if ($error): ?>
<div class="alert alert-danger small py-2"><?= $error ?></div>
<?php endif; ?>
<?php if ($success): ?>
<div class="alert alert-success small py-2"><?= $success ?></div>
<div class="text-center mt-3">
<a href="login.php" class="small text-decoration-none"><i class="bi bi-arrow-left"></i> Back to Login</a>
</div>
<?php else: ?>
<form method="POST">
<div class="mb-4">
<label class="form-label small fw-medium">Email Address</label>
<div class="input-group">
<span class="input-group-text bg-light border-0"><i class="bi bi-envelope"></i></span>
<input type="email" name="email" class="form-control bg-light border-0" required autofocus>
</div>
</div>
<button type="submit" class="btn btn-primary w-100 py-2 fw-bold mb-3">Send Reset Link</button>
<div class="text-center">
<a href="login.php" class="small text-decoration-none">Back to Login</a>
</div>
</form>
<?php endif; ?>
</div>
</div>
</body>
</html>

View File

@ -82,6 +82,10 @@ $settings = get_company_settings();
<div class="alert alert-danger small py-2"><?= $error ?></div>
<?php endif; ?>
<?php if (isset($_GET['reset']) && $_GET['reset'] == 'success'): ?>
<div class="alert alert-success small py-2">Password reset successfully. You can now login.</div>
<?php endif; ?>
<form method="POST">
<div class="mb-3">
<label class="form-label small fw-medium">Username</label>
@ -90,13 +94,16 @@ $settings = get_company_settings();
<input type="text" name="username" class="form-control bg-light border-0" required autofocus>
</div>
</div>
<div class="mb-4">
<div class="mb-3">
<label class="form-label small fw-medium">Password</label>
<div class="input-group">
<span class="input-group-text bg-light border-0"><i class="bi bi-lock"></i></span>
<input type="password" name="password" class="form-control bg-light border-0" required>
</div>
</div>
<div class="mb-4 d-flex justify-content-end">
<a href="forgot_password.php" class="small text-decoration-none">Forgot Password?</a>
</div>
<button type="submit" class="btn btn-primary w-100 py-2 fw-bold">Login</button>
</form>
</div>

View File

@ -1,6 +1,5 @@
<?php
// Mail configuration sourced from environment variables.
// No secrets are stored here; the file just maps env -> config array for MailService.
// Mail configuration sourced from environment variables or Database.
function env_val(string $key, $default = null) {
$v = getenv($key);
@ -8,8 +7,6 @@ function env_val(string $key, $default = null) {
}
// 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;
@ -22,9 +19,7 @@ function load_dotenv_if_needed(array $keys): void {
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
$v = trim($v, "'\" ");
if ($k !== '' && (getenv($k) === false || getenv($k) === '')) {
putenv("{$k}={$v}");
}
@ -35,42 +30,43 @@ function load_dotenv_if_needed(array $keys): void {
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'
'MAIL_FROM','MAIL_FROM_NAME','MAIL_REPLY_TO','MAIL_TO'
]);
$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');
// Try to load from Database
$dbSettings = [];
try {
require_once __DIR__ . '/../db/config.php';
$pdo = db();
$stmt = $pdo->prepare("SELECT setting_key, setting_value FROM integration_settings WHERE provider = 'smtp'");
$stmt->execute();
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
foreach ($rows as $row) {
$dbSettings[$row['setting_key']] = $row['setting_value'];
}
} catch (Exception $e) {
// Database might not be ready or table might not exist yet
}
$from_email = env_val('MAIL_FROM', 'no-reply@localhost');
$from_name = env_val('MAIL_FROM_NAME', 'App');
$transport = $dbSettings['transport'] ?? env_val('MAIL_TRANSPORT', 'smtp');
$smtp_host = $dbSettings['host'] ?? env_val('SMTP_HOST');
$smtp_port = (int) ($dbSettings['port'] ?? env_val('SMTP_PORT', 587));
$smtp_secure = $dbSettings['secure'] ?? env_val('SMTP_SECURE', 'tls');
$smtp_user = $dbSettings['username'] ?? env_val('SMTP_USER');
$smtp_pass = $dbSettings['password'] ?? env_val('SMTP_PASS');
$from_email = $dbSettings['from_email'] ?? env_val('MAIL_FROM', 'no-reply@localhost');
$from_name = $dbSettings['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,
];

1054
qorder.php

File diff suppressed because it is too large Load Diff

110
reset_password.php Normal file
View File

@ -0,0 +1,110 @@
<?php
require_once __DIR__ . '/db/config.php';
require_once __DIR__ . '/includes/functions.php';
init_session();
$baseUrl = get_base_url();
$settings = get_company_settings();
$error = '';
$token = $_GET['token'] ?? '';
$user = null;
if (empty($token)) {
header("Location: login.php");
exit;
}
$pdo = db();
$stmt = $pdo->prepare("SELECT id, username, reset_token_expiry FROM users WHERE reset_token = ? AND is_deleted = 0 LIMIT 1");
$stmt->execute([$token]);
$user = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$user || strtotime($user['reset_token_expiry']) < time()) {
$error = "This password reset link is invalid or has expired.";
}
if ($_SERVER['REQUEST_METHOD'] === 'POST' && !$error) {
$password = $_POST['password'] ?? '';
$confirmPassword = $_POST['confirm_password'] ?? '';
if (empty($password)) {
$error = "Please enter a new password.";
} elseif ($password !== $confirmPassword) {
$error = "Passwords do not match.";
} elseif (strlen($password) < 6) {
$error = "Password must be at least 6 characters long.";
} else {
$hashedPassword = password_hash($password, PASSWORD_DEFAULT);
$stmt = $pdo->prepare("UPDATE users SET password = ?, reset_token = NULL, reset_token_expiry = NULL WHERE id = ?");
$stmt->execute([$hashedPassword, $user['id']]);
header("Location: login.php?reset=success");
exit;
}
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Reset Password - <?= htmlspecialchars($settings['company_name']) ?></title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css">
<link rel="stylesheet" href="<?= $baseUrl ?>assets/css/custom.css">
<style>
body {
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
height: 100vh;
display: flex;
align-items: center;
justify-content: center;
}
.login-card {
width: 100%;
max-width: 400px;
border-radius: 1.5rem;
box-shadow: 0 1rem 3rem rgba(0,0,0,0.1);
}
</style>
</head>
<body>
<div class="card login-card border-0">
<div class="card-body p-5">
<div class="text-center mb-4">
<h3 class="fw-bold">Set New Password</h3>
<p class="text-muted small">Choose a secure password for your account</p>
</div>
<?php if ($error): ?>
<div class="alert alert-danger small py-2"><?= $error ?></div>
<div class="text-center mt-3">
<a href="forgot_password.php" class="small text-decoration-none">Request a new reset link</a>
</div>
<?php else: ?>
<form method="POST">
<div class="mb-3">
<label class="form-label small fw-medium">New Password</label>
<div class="input-group">
<span class="input-group-text bg-light border-0"><i class="bi bi-lock"></i></span>
<input type="password" name="password" class="form-control bg-light border-0" required autofocus>
</div>
</div>
<div class="mb-4">
<label class="form-label small fw-medium">Confirm New Password</label>
<div class="input-group">
<span class="input-group-text bg-light border-0"><i class="bi bi-lock-fill"></i></span>
<input type="password" name="confirm_password" class="form-control bg-light border-0" required>
</div>
</div>
<button type="submit" class="btn btn-primary w-100 py-2 fw-bold">Reset Password</button>
</form>
<?php endif; ?>
</div>
</div>
</body>
</html>