Files
domnitor/app/Controllers/SettingsController.php

492 lines
19 KiB
PHP
Raw Normal View History

<?php
namespace App\Controllers;
use Core\Controller;
use Core\Auth;
use App\Models\Setting;
class SettingsController extends Controller
{
private Setting $settingModel;
public function __construct()
{
Auth::requireAdmin();
$this->settingModel = new Setting();
}
public function index()
{
$settings = $this->settingModel->getAllAsKeyValue();
$appSettings = $this->settingModel->getAppSettings();
$emailSettings = $this->settingModel->getEmailSettings();
$captchaSettings = $this->settingModel->getCaptchaSettings();
// Predefined notification day options
$notificationPresets = [
'minimal' => [
'label' => 'Minimal (30, 7, 1 days)',
'value' => '30,7,1'
],
'standard' => [
'label' => 'Standard (60, 30, 21, 14, 7, 5, 3, 2, 1 days)',
'value' => '60,30,21,14,7,5,3,2,1'
],
'frequent' => [
'label' => 'Frequent (90, 60, 45, 30, 21, 14, 10, 7, 5, 3, 2, 1 days)',
'value' => '90,60,45,30,21,14,10,7,5,3,2,1'
],
'business' => [
'label' => 'Business Focused (60, 30, 14, 7, 3, 1 days)',
'value' => '60,30,14,7,3,1'
],
'conservative' => [
'label' => 'Conservative (30, 15, 7, 3, 1 days)',
'value' => '30,15,7,3,1'
],
'custom' => [
'label' => 'Custom',
'value' => 'custom'
]
];
// Check interval presets
$checkIntervalPresets = [
['label' => 'Every 6 hours', 'value' => 6],
['label' => 'Every 12 hours', 'value' => 12],
['label' => 'Daily (24 hours)', 'value' => 24],
['label' => 'Every 2 days (48 hours)', 'value' => 48],
['label' => 'Weekly (168 hours)', 'value' => 168]
];
$this->view('settings/index', [
'settings' => $settings,
'appSettings' => $appSettings,
'emailSettings' => $emailSettings,
'captchaSettings' => $captchaSettings,
'notificationPresets' => $notificationPresets,
'checkIntervalPresets' => $checkIntervalPresets,
'title' => 'Settings'
]);
}
public function update()
{
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
$this->redirect('/settings');
return;
}
// CSRF Protection
$this->verifyCsrf('/settings#monitoring');
try {
// Update notification days
$notificationPreset = $_POST['notification_preset'] ?? 'standard';
if ($notificationPreset === 'custom') {
// Custom days entered by user
$customDays = trim($_POST['custom_notification_days'] ?? '');
if (empty($customDays)) {
$_SESSION['error'] = 'Please enter notification days for custom preset';
$this->redirect('/settings#monitoring');
return;
}
// Validate custom days (comma-separated integers)
$daysArray = array_map('trim', explode(',', $customDays));
$daysArray = array_filter($daysArray, function($day) {
return is_numeric($day) && $day > 0;
});
if (empty($daysArray)) {
$_SESSION['error'] = 'Invalid notification days format. Use comma-separated numbers (e.g., 30,15,7,1)';
$this->redirect('/settings#monitoring');
return;
}
// Sort in descending order
rsort($daysArray, SORT_NUMERIC);
$notificationDays = implode(',', $daysArray);
} else {
// Use preset value
$notificationDays = $_POST['notification_days_before'] ?? '30,15,7,3,1';
}
// Update check interval
$checkInterval = (int)($_POST['check_interval_hours'] ?? 24);
if ($checkInterval < 1 || $checkInterval > 720) { // Max 30 days
$_SESSION['error'] = 'Check interval must be between 1 and 720 hours';
$this->redirect('/settings#monitoring');
return;
}
// Save settings
$this->settingModel->setValue('notification_days_before', $notificationDays);
$this->settingModel->setValue('check_interval_hours', $checkInterval);
$_SESSION['success'] = 'Settings updated successfully';
$this->redirect('/settings#monitoring');
} catch (\Exception $e) {
$_SESSION['error'] = 'Failed to update settings: ' . $e->getMessage();
$this->redirect('/settings#monitoring');
}
}
public function testCron()
{
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
$this->redirect('/settings');
return;
}
// CSRF Protection
$this->verifyCsrf('/settings');
// Update last check run time to show the test worked
$this->settingModel->updateLastCheckRun();
$_SESSION['info'] = 'Test notification sent (feature coming soon). Last check time updated.';
$this->redirect('/settings');
}
public function clearLogs()
{
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
$this->redirect('/settings');
return;
}
// CSRF Protection
$this->verifyCsrf('/settings#maintenance');
try {
// Clear notification logs older than 30 days
$stmt = $this->settingModel->db->prepare(
"DELETE FROM notification_logs WHERE sent_at < DATE_SUB(NOW(), INTERVAL 30 DAY)"
);
$stmt->execute();
$deleted = $stmt->rowCount();
$_SESSION['success'] = "Cleared $deleted old notification log(s)";
$this->redirect('/settings#maintenance');
} catch (\Exception $e) {
$_SESSION['error'] = 'Failed to clear logs: ' . $e->getMessage();
$this->redirect('/settings#maintenance');
}
}
public function updateApp()
{
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
$this->redirect('/settings');
return;
}
// CSRF Protection
$this->verifyCsrf('/settings#app');
try {
$appSettings = [
'app_name' => trim($_POST['app_name'] ?? 'Domain Monitor'),
'app_url' => trim($_POST['app_url'] ?? 'http://localhost:8000'),
'app_timezone' => trim($_POST['app_timezone'] ?? 'UTC')
];
// Validate app_name
if (empty($appSettings['app_name'])) {
$_SESSION['error'] = 'Application name is required';
$this->redirect('/settings#app');
return;
}
// Validate app_url
if (empty($appSettings['app_url']) || !filter_var($appSettings['app_url'], FILTER_VALIDATE_URL)) {
$_SESSION['error'] = 'Please enter a valid application URL';
$this->redirect('/settings#app');
return;
}
// Validate timezone
$validTimezones = timezone_identifiers_list();
if (!in_array($appSettings['app_timezone'], $validTimezones)) {
$_SESSION['error'] = 'Invalid timezone selected';
$this->redirect('/settings#app');
return;
}
Upgraded to 1.1.0 1.1.0 (2025-10-09) - **User Notifications System** - In-app notification center with 7 notification types, filtering, pagination - **Advanced Session Management** - Database-backed sessions with geolocation (country, city, ISP) - **Remote Session Control** - Terminate any device instantly with immediate logout validation - **Enhanced Profile Page** - Sidebar navigation with 4 tabs, hash-based routing (#profile, #security, #sessions) - **MVC Architecture Refactoring** - 3 new Helpers (Layout, Domain, Session), ~265 lines cleaned from views - **Geolocation Tracking** - IP-based location detection using ip-api.com, country flags with flag-icons - **Device Detection** - Browser & device type parsing (Chrome/Firefox/Safari, Desktop/Mobile/Tablet) - **Auto-Detected Cron Paths** - Settings show actual installation paths (thanks @jadeops) - **Welcome Notifications** - Sent to new users on registration or fresh install - **Upgrade Notifications** - Admins notified on system updates with version & migration count - **Web-Based Installer** - Replaces CLI, auto-generates encryption key, one-time password display - **Web-Based Updater** - `/install/update` for running new migrations with smart detection - **User Registration** - Full signup flow with email verification, password reset, resend verification - **User Management** - CRUD for users with filtering, sorting, pagination (admin-only) - **Remember Me** - 30-day secure tokens linked to sessions, cascade deletion on logout - **Session Validator** - Middleware validates sessions on every request for instant remote logout - **Consistent UI/UX** - Unified filtering, sorting, pagination across Domains, Users, Notifications, TLD Registry - **Smart Migrations** - Consolidated schema for fresh installs, incremental for upgrades - **XSS Protection** - htmlspecialchars() applied across all user-facing data (thanks @jadeops)
2025-10-09 18:02:46 +03:00
// Update app settings
$this->settingModel->updateAppSettings($appSettings);
Upgraded to 1.1.0 1.1.0 (2025-10-09) - **User Notifications System** - In-app notification center with 7 notification types, filtering, pagination - **Advanced Session Management** - Database-backed sessions with geolocation (country, city, ISP) - **Remote Session Control** - Terminate any device instantly with immediate logout validation - **Enhanced Profile Page** - Sidebar navigation with 4 tabs, hash-based routing (#profile, #security, #sessions) - **MVC Architecture Refactoring** - 3 new Helpers (Layout, Domain, Session), ~265 lines cleaned from views - **Geolocation Tracking** - IP-based location detection using ip-api.com, country flags with flag-icons - **Device Detection** - Browser & device type parsing (Chrome/Firefox/Safari, Desktop/Mobile/Tablet) - **Auto-Detected Cron Paths** - Settings show actual installation paths (thanks @jadeops) - **Welcome Notifications** - Sent to new users on registration or fresh install - **Upgrade Notifications** - Admins notified on system updates with version & migration count - **Web-Based Installer** - Replaces CLI, auto-generates encryption key, one-time password display - **Web-Based Updater** - `/install/update` for running new migrations with smart detection - **User Registration** - Full signup flow with email verification, password reset, resend verification - **User Management** - CRUD for users with filtering, sorting, pagination (admin-only) - **Remember Me** - 30-day secure tokens linked to sessions, cascade deletion on logout - **Session Validator** - Middleware validates sessions on every request for instant remote logout - **Consistent UI/UX** - Unified filtering, sorting, pagination across Domains, Users, Notifications, TLD Registry - **Smart Migrations** - Consolidated schema for fresh installs, incremental for upgrades - **XSS Protection** - htmlspecialchars() applied across all user-facing data (thanks @jadeops)
2025-10-09 18:02:46 +03:00
// Update registration settings
$registrationEnabled = isset($_POST['registration_enabled']) ? '1' : '0';
$requireEmailVerification = isset($_POST['require_email_verification']) ? '1' : '0';
$this->settingModel->setValue('registration_enabled', $registrationEnabled);
$this->settingModel->setValue('require_email_verification', $requireEmailVerification);
$_SESSION['success'] = 'Application settings updated successfully';
$this->redirect('/settings#app');
} catch (\Exception $e) {
$_SESSION['error'] = 'Failed to update application settings: ' . $e->getMessage();
$this->redirect('/settings#app');
}
}
public function updateEmail()
{
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
$this->redirect('/settings');
return;
}
// CSRF Protection
$this->verifyCsrf('/settings#email');
try {
$emailSettings = [
'mail_host' => trim($_POST['mail_host'] ?? ''),
'mail_port' => trim($_POST['mail_port'] ?? '2525'),
'mail_username' => trim($_POST['mail_username'] ?? ''),
'mail_password' => trim($_POST['mail_password'] ?? ''),
'mail_encryption' => trim($_POST['mail_encryption'] ?? 'tls'),
'mail_from_address' => trim($_POST['mail_from_address'] ?? ''),
'mail_from_name' => trim($_POST['mail_from_name'] ?? 'Domain Monitor')
];
// Validate required fields
if (empty($emailSettings['mail_host'])) {
$_SESSION['error'] = 'Mail host is required';
$this->redirect('/settings#email');
return;
}
if (empty($emailSettings['mail_from_address']) || !filter_var($emailSettings['mail_from_address'], FILTER_VALIDATE_EMAIL)) {
$_SESSION['error'] = 'Please enter a valid from email address';
$this->redirect('/settings#email');
return;
}
// Validate port
if (!is_numeric($emailSettings['mail_port']) || $emailSettings['mail_port'] < 1 || $emailSettings['mail_port'] > 65535) {
$_SESSION['error'] = 'Please enter a valid port number (1-65535)';
$this->redirect('/settings#email');
return;
}
$this->settingModel->updateEmailSettings($emailSettings);
$_SESSION['success'] = 'Email settings updated successfully';
$this->redirect('/settings#email');
} catch (\Exception $e) {
$_SESSION['error'] = 'Failed to update email settings: ' . $e->getMessage();
$this->redirect('/settings#email');
}
}
public function updateCaptcha()
{
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
$this->redirect('/settings');
return;
}
// CSRF Protection
$this->verifyCsrf('/settings#security');
try {
$captchaProvider = trim($_POST['captcha_provider'] ?? 'disabled');
$captchaSiteKey = trim($_POST['captcha_site_key'] ?? '');
$captchaSecretKey = trim($_POST['captcha_secret_key'] ?? '');
$recaptchaV3Threshold = trim($_POST['recaptcha_v3_score_threshold'] ?? '0.5');
// Validate provider
$validProviders = ['disabled', 'recaptcha_v2', 'recaptcha_v3', 'turnstile'];
if (!in_array($captchaProvider, $validProviders)) {
$_SESSION['error'] = 'Invalid CAPTCHA provider selected';
$this->redirect('/settings#security');
return;
}
// If CAPTCHA is enabled, validate keys
if ($captchaProvider !== 'disabled') {
if (empty($captchaSiteKey)) {
$_SESSION['error'] = 'Site key is required when CAPTCHA is enabled';
$this->redirect('/settings#security');
return;
}
if (empty($captchaSecretKey)) {
$_SESSION['error'] = 'Secret key is required when CAPTCHA is enabled';
$this->redirect('/settings#security');
return;
}
}
// Validate v3 score threshold
if ($captchaProvider === 'recaptcha_v3') {
$threshold = floatval($recaptchaV3Threshold);
if ($threshold < 0.0 || $threshold > 1.0) {
$_SESSION['error'] = 'reCAPTCHA v3 score threshold must be between 0.0 and 1.0';
$this->redirect('/settings#security');
return;
}
}
// Prepare settings array
$captchaSettings = [
'captcha_provider' => $captchaProvider,
'captcha_site_key' => $captchaSiteKey,
'recaptcha_v3_score_threshold' => $recaptchaV3Threshold
];
// Only update secret key if provided (to allow updating other settings without re-entering secret)
if (!empty($captchaSecretKey)) {
$captchaSettings['captcha_secret_key'] = $captchaSecretKey;
}
// Update CAPTCHA settings
$this->settingModel->updateCaptchaSettings($captchaSettings);
$_SESSION['success'] = 'CAPTCHA settings updated successfully';
$this->redirect('/settings#security');
} catch (\Exception $e) {
$_SESSION['error'] = 'Failed to update CAPTCHA settings: ' . $e->getMessage();
$this->redirect('/settings#security');
}
}
public function testEmail()
{
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
$this->redirect('/settings');
return;
}
// CSRF Protection
$this->verifyCsrf('/settings#email');
$testEmail = trim($_POST['test_email'] ?? '');
if (empty($testEmail) || !filter_var($testEmail, FILTER_VALIDATE_EMAIL)) {
$_SESSION['error'] = 'Please enter a valid email address';
$this->redirect('/settings#email');
return;
}
try {
// Get current email settings
$emailSettings = $this->settingModel->getEmailSettings();
$appSettings = $this->settingModel->getAppSettings();
// Create PHPMailer instance
$mail = new \PHPMailer\PHPMailer\PHPMailer(true);
// Server settings
$mail->isSMTP();
$mail->Host = $emailSettings['mail_host'];
$mail->SMTPAuth = !empty($emailSettings['mail_username']);
$mail->Username = $emailSettings['mail_username'];
$mail->Password = $emailSettings['mail_password'];
$mail->SMTPSecure = $emailSettings['mail_encryption'];
$mail->Port = $emailSettings['mail_port'];
// Recipients
$mail->setFrom($emailSettings['mail_from_address'], $emailSettings['mail_from_name']);
$mail->addAddress($testEmail);
// Content
$mail->isHTML(true);
$mail->Subject = 'Test Email from ' . $appSettings['app_name'];
$appName = htmlspecialchars($appSettings['app_name']);
$appUrl = htmlspecialchars($appSettings['app_url']);
$currentTime = date('F j, Y g:i A');
$mail->Body = "
<html>
<head>
<style>
body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; }
.container { max-width: 600px; margin: 0 auto; padding: 20px; }
.header { background: #4A90E2; color: white; padding: 20px; border-radius: 5px 5px 0 0; }
.content { background: #f9f9f9; padding: 20px; border: 1px solid #ddd; }
.success { background: #d4edda; border: 1px solid #c3e6cb; color: #155724; padding: 12px; border-radius: 4px; margin: 15px 0; }
.info-table { width: 100%; margin-top: 15px; }
.info-table td { padding: 8px; border-bottom: 1px solid #ddd; }
.info-table td:first-child { font-weight: bold; width: 150px; }
.footer { background: #333; color: white; padding: 10px; text-align: center; font-size: 12px; border-radius: 0 0 5px 5px; }
</style>
</head>
<body>
<div class='container'>
<div class='header'>
<h2> Email Test Successful!</h2>
</div>
<div class='content'>
<div class='success'>
<strong>Success!</strong> Your email configuration is working correctly.
</div>
<p>This is a test email from <strong>{$appName}</strong>.</p>
<p>If you're seeing this message, it means your SMTP settings are configured properly and emails are being delivered successfully.</p>
<table class='info-table'>
<tr>
<td>SMTP Host:</td>
<td>" . htmlspecialchars($emailSettings['mail_host']) . "</td>
</tr>
<tr>
<td>SMTP Port:</td>
<td>" . htmlspecialchars($emailSettings['mail_port']) . "</td>
</tr>
<tr>
<td>Encryption:</td>
<td>" . htmlspecialchars($emailSettings['mail_encryption'] ?: 'None') . "</td>
</tr>
<tr>
<td>From Address:</td>
<td>" . htmlspecialchars($emailSettings['mail_from_address']) . "</td>
</tr>
<tr>
<td>Test Time:</td>
<td>{$currentTime}</td>
</tr>
</table>
</div>
<div class='footer'>
<p>This is an automated test message from {$appName}</p>
<p style='margin-top: 5px;'><a href='{$appUrl}' style='color: #4A90E2;'>Visit Dashboard</a></p>
</div>
</div>
</body>
</html>
";
$mail->AltBody = "Email Test Successful!\n\n" .
"This is a test email from {$appName}.\n" .
"Your SMTP configuration is working correctly.\n\n" .
"SMTP Host: {$emailSettings['mail_host']}\n" .
"SMTP Port: {$emailSettings['mail_port']}\n" .
"From: {$emailSettings['mail_from_address']}\n" .
"Test Time: {$currentTime}";
$mail->send();
$_SESSION['success'] = "Test email sent successfully to {$testEmail}. Please check your inbox.";
$this->redirect('/settings#email');
} catch (\Exception $e) {
$_SESSION['error'] = "Failed to send test email: " . $e->getMessage();
$this->redirect('/settings#email');
}
}
}