diff --git a/app/Controllers/AuthController.php b/app/Controllers/AuthController.php index e869c83..ee77640 100644 --- a/app/Controllers/AuthController.php +++ b/app/Controllers/AuthController.php @@ -380,10 +380,7 @@ class AuthController extends Controller $this->logger->warning("No user found with verification token: " . substr($token, 0, 10) . "..."); // Debug: Check if any user has this token (regardless of verification status) - $pdo = \Core\Database::getConnection(); - $stmt = $pdo->prepare("SELECT id, email, email_verified, email_verification_token FROM users WHERE email_verification_token = ?"); - $stmt->execute([$token]); - $debugUser = $stmt->fetch(); + $debugUser = $this->userModel->findByVerificationTokenDebug($token); if ($debugUser) { $this->logger->info("Debug: Found user with token - ID: {$debugUser['id']}, Email: {$debugUser['email']}, Verified: {$debugUser['email_verified']}"); } else { diff --git a/app/Controllers/SearchController.php b/app/Controllers/SearchController.php index 2bb5165..a73b766 100644 --- a/app/Controllers/SearchController.php +++ b/app/Controllers/SearchController.php @@ -37,7 +37,7 @@ class SearchController extends Controller $perPage = max(10, min(100, (int)($_GET['per_page'] ?? 25))); // Search existing domains in database - $allResults = $this->searchDomains($query, $isolationMode === 'isolated' ? $userId : null); + $allResults = $this->domainModel->searchDomains($query, $isolationMode === 'isolated' ? $userId : null); $totalResults = count($allResults); // Calculate pagination @@ -99,19 +99,7 @@ class SearchController extends Controller } // Search existing domains (limit to 5 for quick results) - $db = \Core\Database::getConnection(); - $sql = "SELECT d.id, d.domain_name, d.registrar, d.expiration_date, d.status, ng.name as group_name - FROM domains d - LEFT JOIN notification_groups ng ON d.notification_group_id = ng.id - WHERE d.domain_name LIKE ? - OR d.registrar LIKE ? - ORDER BY d.domain_name ASC - LIMIT 5"; - - $searchTerm = '%' . $query . '%'; - $stmt = $db->prepare($sql); - $stmt->execute([$searchTerm, $searchTerm]); - $results = $stmt->fetchAll(); + $results = $this->domainModel->searchSuggestions($query, 5); // Calculate days left for each domain foreach ($results as &$domain) { @@ -146,49 +134,6 @@ class SearchController extends Controller exit; } - /** - * Search domains in database - */ - private function searchDomains(string $query, ?int $userId = null): array - { - $db = \Core\Database::getConnection(); - $sql = "SELECT d.*, ng.name as group_name - FROM domains d - LEFT JOIN notification_groups ng ON d.notification_group_id = ng.id - WHERE (d.domain_name LIKE ? - OR d.registrar LIKE ? - OR ng.name LIKE ?)"; - - $params = ['%' . $query . '%', '%' . $query . '%', '%' . $query . '%']; - - if ($userId && !$this->isAdmin($userId)) { - $sql .= " AND d.user_id = ?"; - $params[] = $userId; - } - - $sql .= " ORDER BY d.domain_name ASC LIMIT 50"; - - $stmt = $db->prepare($sql); - $stmt->execute($params); - - return $stmt->fetchAll(); - } - - /** - * Check if user is admin - */ - private function isAdmin(?int $userId): bool - { - if (!$userId) { - return false; - } - - $db = \Core\Database::getConnection(); - $stmt = $db->prepare("SELECT role FROM users WHERE id = ?"); - $stmt->execute([$userId]); - $user = $stmt->fetch(); - return $user && $user['role'] === 'admin'; - } /** * Check if string looks like a domain name diff --git a/app/Controllers/SettingsController.php b/app/Controllers/SettingsController.php index 55b7fda..83eb716 100644 --- a/app/Controllers/SettingsController.php +++ b/app/Controllers/SettingsController.php @@ -174,11 +174,7 @@ class SettingsController extends Controller 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(); + $deleted = $this->settingModel->clearOldNotificationLogs(30); $_SESSION['success'] = "Cleared $deleted old notification log(s)"; $this->redirect('/settings#maintenance'); @@ -521,8 +517,8 @@ class SettingsController extends Controller try { if ($newMode === 'isolated') { // Check if we have any admin users - $domainModel = new \App\Models\Domain(); - $adminUser = $domainModel->getFirstAdminUser(); + $userModel = new \App\Models\User(); + $adminUser = $userModel->getFirstAdminUser(); if (!$adminUser) { $_SESSION['error'] = 'No admin users found. Please create an admin user first.'; $this->redirect('/settings#isolation'); @@ -559,8 +555,8 @@ class SettingsController extends Controller { try { // Get the first admin user - $domainModel = new \App\Models\Domain(); - $adminUser = $domainModel->getFirstAdminUser(); + $userModel = new \App\Models\User(); + $adminUser = $userModel->getFirstAdminUser(); if (!$adminUser) { throw new \Exception('No admin user found. Please create an admin user first.'); @@ -569,14 +565,12 @@ class SettingsController extends Controller $adminId = $adminUser['id']; // Assign all domains to admin - $domainStmt = $this->settingModel->db->prepare("UPDATE domains SET user_id = ? WHERE user_id IS NULL"); - $domainStmt->execute([$adminId]); - $domainCount = $domainStmt->rowCount(); + $domainModel = new \App\Models\Domain(); + $domainCount = $domainModel->assignUnassignedDomainsToUser($adminId); // Assign all groups to admin - $groupStmt = $this->settingModel->db->prepare("UPDATE notification_groups SET user_id = ? WHERE user_id IS NULL"); - $groupStmt->execute([$adminId]); - $groupCount = $groupStmt->rowCount(); + $groupModel = new \App\Models\NotificationGroup(); + $groupCount = $groupModel->assignUnassignedGroupsToUser($adminId); // Set isolation mode $this->settingModel->setValue('user_isolation_mode', 'isolated'); diff --git a/app/Controllers/UserController.php b/app/Controllers/UserController.php index 53c446d..2bfb495 100644 --- a/app/Controllers/UserController.php +++ b/app/Controllers/UserController.php @@ -320,7 +320,7 @@ class UserController extends Controller // Prevent deleting the last admin if ($user['role'] === 'admin') { - $allAdmins = $this->userModel->where('role', 'admin'); + $allAdmins = $this->userModel->getAllAdmins(); if (count($allAdmins) <= 1) { $_SESSION['error'] = 'Cannot delete the last admin user'; $this->redirect('/users'); @@ -457,7 +457,7 @@ class UserController extends Controller // Prevent deleting if this is the last admin $user = $this->userModel->find((int)$userId); if ($user && $user['role'] === 'admin') { - $allAdmins = $this->userModel->where('role', 'admin'); + $allAdmins = $this->userModel->getAllAdmins(); if (count($allAdmins) <= 1) { continue; // Skip - can't delete last admin } diff --git a/app/Models/Domain.php b/app/Models/Domain.php index 3c7528b..71e4b2d 100644 --- a/app/Models/Domain.php +++ b/app/Models/Domain.php @@ -8,6 +8,14 @@ class Domain extends Model { protected static string $table = 'domains'; + /** + * Get User model instance + */ + private function getUserModel(): \App\Models\User + { + return new \App\Models\User(); + } + /** * Get all domains with their notification group */ @@ -17,7 +25,7 @@ class Domain extends Model FROM domains d LEFT JOIN notification_groups ng ON d.notification_group_id = ng.id"; - if ($userId && !$this->isAdmin($userId)) { + if ($userId && !$this->getUserModel()->isAdmin($userId)) { $sql .= " WHERE d.user_id = ?"; $stmt = $this->db->prepare($sql); $stmt->execute([$userId]); @@ -44,7 +52,7 @@ class Domain extends Model $params = [$days]; - if ($userId && !$this->isAdmin($userId)) { + if ($userId && !$this->getUserModel()->isAdmin($userId)) { $sql .= " AND d.user_id = ?"; $params[] = $userId; } @@ -68,7 +76,7 @@ class Domain extends Model $params = [$status]; - if ($userId && !$this->isAdmin($userId)) { + if ($userId && !$this->getUserModel()->isAdmin($userId)) { $sql .= " AND d.user_id = ?"; $params[] = $userId; } @@ -132,7 +140,7 @@ class Domain extends Model $params = []; - if ($userId && !$this->isAdmin($userId)) { + if ($userId && !$this->getUserModel()->isAdmin($userId)) { $sql .= " AND d.user_id = ?"; $params[] = $userId; } @@ -162,7 +170,7 @@ class Domain extends Model $whereClause = "WHERE is_active = 1"; $params = []; - if ($userId && !$this->isAdmin($userId)) { + if ($userId && !$this->getUserModel()->isAdmin($userId)) { $whereClause .= " AND user_id = ?"; $params[] = $userId; } @@ -183,7 +191,7 @@ class Domain extends Model $inactiveWhereClause = "WHERE is_active = 0"; $inactiveParams = []; - if ($userId && !$this->isAdmin($userId)) { + if ($userId && !$this->getUserModel()->isAdmin($userId)) { $inactiveWhereClause .= " AND user_id = ?"; $inactiveParams[] = $userId; } @@ -297,7 +305,7 @@ class Domain extends Model $sql = "SELECT DISTINCT tags FROM domains WHERE tags IS NOT NULL AND tags != ''"; $params = []; - if ($userId && !$this->isAdmin($userId)) { + if ($userId && !$this->getUserModel()->isAdmin($userId)) { $sql .= " AND user_id = ?"; $params[] = $userId; } @@ -320,28 +328,62 @@ class Domain extends Model return $allTags; } + /** - * Check if user is admin + * Assign all domains without user_id to a specific user */ - private function isAdmin(?int $userId): bool + public function assignUnassignedDomainsToUser(int $userId): int { - if (!$userId) { - return false; - } - - $stmt = $this->db->prepare("SELECT role FROM users WHERE id = ?"); + $stmt = $this->db->prepare("UPDATE domains SET user_id = ? WHERE user_id IS NULL"); $stmt->execute([$userId]); - $user = $stmt->fetch(); - return $user && $user['role'] === 'admin'; + return $stmt->rowCount(); } /** - * Get first admin user + * Search domains for suggestions (quick search) */ - public function getFirstAdminUser(): ?array + public function searchSuggestions(string $query, int $limit = 5): array { - $stmt = $this->db->query("SELECT * FROM users WHERE role = 'admin' ORDER BY id ASC LIMIT 1"); - return $stmt->fetch() ?: null; + $sql = "SELECT d.id, d.domain_name, d.registrar, d.expiration_date, d.status, ng.name as group_name + FROM domains d + LEFT JOIN notification_groups ng ON d.notification_group_id = ng.id + WHERE d.domain_name LIKE ? + OR d.registrar LIKE ? + ORDER BY d.domain_name ASC + LIMIT ?"; + + $searchTerm = '%' . $query . '%'; + $stmt = $this->db->prepare($sql); + $stmt->execute([$searchTerm, $searchTerm, $limit]); + return $stmt->fetchAll(); + } + + /** + * Search domains with user isolation support + */ + public function searchDomains(string $query, ?int $userId = null, int $limit = 50): array + { + $sql = "SELECT d.*, ng.name as group_name + FROM domains d + LEFT JOIN notification_groups ng ON d.notification_group_id = ng.id + WHERE (d.domain_name LIKE ? + OR d.registrar LIKE ? + OR ng.name LIKE ?)"; + + $params = ['%' . $query . '%', '%' . $query . '%', '%' . $query . '%']; + + if ($userId && !$this->getUserModel()->isAdmin($userId)) { + $sql .= " AND d.user_id = ?"; + $params[] = $userId; + } + + $sql .= " ORDER BY d.domain_name ASC LIMIT ?"; + $params[] = $limit; + + $stmt = $this->db->prepare($sql); + $stmt->execute($params); + + return $stmt->fetchAll(); } } diff --git a/app/Models/NotificationGroup.php b/app/Models/NotificationGroup.php index d6dc504..2e51c2f 100644 --- a/app/Models/NotificationGroup.php +++ b/app/Models/NotificationGroup.php @@ -8,6 +8,14 @@ class NotificationGroup extends Model { protected static string $table = 'notification_groups'; + /** + * Get User model instance + */ + private function getUserModel(): \App\Models\User + { + return new \App\Models\User(); + } + /** * Get all groups with channel count */ @@ -20,7 +28,7 @@ class NotificationGroup extends Model LEFT JOIN notification_channels nc ON ng.id = nc.notification_group_id LEFT JOIN domains d ON ng.id = d.notification_group_id"; - if ($userId && !$this->isAdmin($userId)) { + if ($userId && !$this->getUserModel()->isAdmin($userId)) { $sql .= " WHERE ng.user_id = ?"; $stmt = $this->db->prepare($sql); $stmt->execute([$userId]); @@ -44,7 +52,7 @@ class NotificationGroup extends Model } // Check if user has access to this group - if ($userId && !$this->isAdmin($userId) && $group['user_id'] != $userId) { + if ($userId && !$this->getUserModel()->isAdmin($userId) && $group['user_id'] != $userId) { return null; } @@ -54,7 +62,7 @@ class NotificationGroup extends Model // Get domains (filtered by user if needed) $domainModel = new Domain(); - if ($userId && !$this->isAdmin($userId)) { + if ($userId && !$this->getUserModel()->isAdmin($userId)) { $group['domains'] = $domainModel->where('notification_group_id', $id, $userId); } else { $group['domains'] = $domainModel->where('notification_group_id', $id); @@ -77,28 +85,15 @@ class NotificationGroup extends Model return $this->delete($id); } - /** - * Check if user is admin - */ - private function isAdmin(?int $userId): bool - { - if (!$userId) { - return false; - } - - $stmt = $this->db->prepare("SELECT role FROM users WHERE id = ?"); - $stmt->execute([$userId]); - $user = $stmt->fetch(); - return $user && $user['role'] === 'admin'; - } /** - * Get first admin user + * Assign all notification groups without user_id to a specific user */ - public function getFirstAdminUser(): ?array + public function assignUnassignedGroupsToUser(int $userId): int { - $stmt = $this->db->query("SELECT * FROM users WHERE role = 'admin' ORDER BY id ASC LIMIT 1"); - return $stmt->fetch() ?: null; + $stmt = $this->db->prepare("UPDATE notification_groups SET user_id = ? WHERE user_id IS NULL"); + $stmt->execute([$userId]); + return $stmt->rowCount(); } } diff --git a/app/Models/Setting.php b/app/Models/Setting.php index 9a36649..04d1c85 100644 --- a/app/Models/Setting.php +++ b/app/Models/Setting.php @@ -284,5 +284,17 @@ class Setting extends Model return $result; } + + /** + * Clear old notification logs + */ + public function clearOldNotificationLogs(int $daysOld = 30): int + { + $stmt = $this->db->prepare( + "DELETE FROM notification_logs WHERE sent_at < DATE_SUB(NOW(), INTERVAL ? DAY)" + ); + $stmt->execute([$daysOld]); + return $stmt->rowCount(); + } } diff --git a/app/Models/User.php b/app/Models/User.php index 9c0b9ea..37724ca 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -284,5 +284,58 @@ class User extends Model $user = $this->find($userId); return $user && $user['email_verified']; } + + /** + * Check if user is admin + */ + public function isAdmin(?int $userId): bool + { + if (!$userId) { + return false; + } + + $stmt = $this->db->prepare("SELECT role FROM users WHERE id = ?"); + $stmt->execute([$userId]); + $user = $stmt->fetch(); + return $user && $user['role'] === 'admin'; + } + + /** + * Get first admin user + */ + public function getFirstAdminUser(): ?array + { + $stmt = $this->db->query("SELECT * FROM users WHERE role = 'admin' ORDER BY id ASC LIMIT 1"); + return $stmt->fetch() ?: null; + } + + /** + * Get all admin users + */ + public function getAllAdmins(): array + { + return $this->where('role', 'admin'); + } + + /** + * Count admin users + */ + public function countAdmins(): int + { + $stmt = $this->db->query("SELECT COUNT(*) as count FROM users WHERE role = 'admin'"); + $result = $stmt->fetch(); + return (int)$result['count']; + } + + /** + * Find user by verification token (debug version - includes all users regardless of verification status) + */ + public function findByVerificationTokenDebug(string $token): ?array + { + $stmt = $this->db->prepare("SELECT id, email, email_verified, email_verification_token FROM users WHERE email_verification_token = ?"); + $stmt->execute([$token]); + $result = $stmt->fetch(); + return $result ?: null; + } } diff --git a/app/Services/NotificationService.php b/app/Services/NotificationService.php index cef98db..f518224 100644 --- a/app/Services/NotificationService.php +++ b/app/Services/NotificationService.php @@ -276,9 +276,8 @@ class NotificationService public function notifyAdminsUpgrade(string $fromVersion, string $toVersion, int $migrationsCount): void { try { - $pdo = \Core\Database::getConnection(); - $stmt = $pdo->query("SELECT id FROM users WHERE role = 'admin'"); - $admins = $stmt->fetchAll(\PDO::FETCH_ASSOC); + $userModel = new \App\Models\User(); + $admins = $userModel->getAllAdmins(); foreach ($admins as $admin) { $this->notifySystemUpgrade($admin['id'], $fromVersion, $toVersion, $migrationsCount); diff --git a/app/Views/settings/index.php b/app/Views/settings/index.php index 61c346e..10bf282 100644 --- a/app/Views/settings/index.php +++ b/app/Views/settings/index.php @@ -446,10 +446,10 @@ foreach ($notificationPresets as $key => $preset) {

@@ -487,8 +487,8 @@ foreach ($notificationPresets as $key => $preset) { -

-