diff --git a/app/Controllers/DashboardController.php b/app/Controllers/DashboardController.php index af38d60..74c98fb 100644 --- a/app/Controllers/DashboardController.php +++ b/app/Controllers/DashboardController.php @@ -22,20 +22,35 @@ class DashboardController extends Controller public function index() { - $stats = $this->domainModel->getStatistics(); - $recentDomains = $this->domainModel->getRecent(5); // Get 5 most recent domains + // Get current user and isolation mode + $userId = \Core\Auth::id(); + $settingModel = new \App\Models\Setting(); + $isolationMode = $settingModel->getValue('user_isolation_mode', 'shared'); + + // Get user-specific or global statistics + if ($isolationMode === 'isolated') { + $stats = $this->domainModel->getStatistics($userId); + $recentDomains = $this->domainModel->getRecent(5, $userId); + $groups = $this->groupModel->getAllWithChannelCount($userId); + } else { + $stats = $this->domainModel->getStatistics(); + $recentDomains = $this->domainModel->getRecent(5); + $groups = $this->groupModel->getAllWithChannelCount(); + } // Get expiring threshold from settings - $settingModel = new \App\Models\Setting(); $notificationDays = $settingModel->getNotificationDays(); $expiringThreshold = !empty($notificationDays) ? max($notificationDays) : 30; // Get expiring domains limited to top 5 - $allExpiringDomains = $this->domainModel->getExpiringDomains($expiringThreshold); + if ($isolationMode === 'isolated') { + $allExpiringDomains = $this->domainModel->getExpiringDomains($expiringThreshold, $userId); + } else { + $allExpiringDomains = $this->domainModel->getExpiringDomains($expiringThreshold); + } $expiringThisMonth = array_slice($allExpiringDomains, 0, 5); $recentLogs = $this->logModel->getRecent(10); - $groups = $this->groupModel->all(); // Check system status $systemStatus = $this->checkSystemStatus(); diff --git a/app/Controllers/DomainController.php b/app/Controllers/DomainController.php index f32991a..a7cc60c 100644 --- a/app/Controllers/DomainController.php +++ b/app/Controllers/DomainController.php @@ -22,6 +22,11 @@ class DomainController extends Controller public function index() { + // Get current user and isolation mode + $userId = \Core\Auth::id(); + $settingModel = new \App\Models\Setting(); + $isolationMode = $settingModel->getValue('user_isolation_mode', 'shared'); + // Get filter parameters $search = \App\Helpers\InputValidator::sanitizeSearch($_GET['search'] ?? '', 100); $status = $_GET['status'] ?? ''; @@ -33,7 +38,6 @@ class DomainController extends Controller $perPage = max(10, min(100, (int)($_GET['per_page'] ?? 25))); // Between 10 and 100 // Get expiring threshold from settings - $settingModel = new \App\Models\Setting(); $notificationDays = $settingModel->getNotificationDays(); $expiringThreshold = !empty($notificationDays) ? max($notificationDays) : 30; @@ -46,12 +50,16 @@ class DomainController extends Controller ]; // Get filtered and paginated domains using model - $result = $this->domainModel->getFilteredPaginated($filters, $sortBy, $sortOrder, $page, $perPage, $expiringThreshold); + $result = $this->domainModel->getFilteredPaginated($filters, $sortBy, $sortOrder, $page, $perPage, $expiringThreshold, $isolationMode === 'isolated' ? $userId : null); - $groups = $this->groupModel->all(); - - // Get all unique tags for filter dropdown - $allTags = $this->domainModel->getAllTags(); + // Get groups and tags based on isolation mode + if ($isolationMode === 'isolated') { + $groups = $this->groupModel->getAllWithChannelCount($userId); + $allTags = $this->domainModel->getAllTags($userId); + } else { + $groups = $this->groupModel->getAllWithChannelCount(); + $allTags = $this->domainModel->getAllTags(); + } // Format domains for display $formattedDomains = \App\Helpers\DomainHelper::formatMultiple($result['domains']); @@ -756,5 +764,106 @@ class DomainController extends Controller $_SESSION['success'] = "Tags removed from $updated domain(s)"; $this->redirect('/domains'); } + + /** + * Transfer domain to another user (Admin only) + */ + public function transfer() + { + \Core\Auth::requireAdmin(); + + if ($_SERVER['REQUEST_METHOD'] !== 'POST') { + $this->redirect('/domains'); + return; + } + + $this->verifyCsrf('/domains'); + + $domainId = (int)($_POST['domain_id'] ?? 0); + $targetUserId = (int)($_POST['target_user_id'] ?? 0); + + if (!$domainId || !$targetUserId) { + $_SESSION['error'] = 'Invalid domain or user selected'; + $this->redirect('/domains'); + return; + } + + // Validate domain exists + $domain = $this->domainModel->find($domainId); + if (!$domain) { + $_SESSION['error'] = 'Domain not found'; + $this->redirect('/domains'); + return; + } + + // Validate target user exists + $userModel = new \App\Models\User(); + $targetUser = $userModel->find($targetUserId); + if (!$targetUser) { + $_SESSION['error'] = 'Target user not found'; + $this->redirect('/domains'); + return; + } + + try { + // Transfer domain + $this->domainModel->update($domainId, ['user_id' => $targetUserId]); + + $_SESSION['success'] = "Domain '{$domain['domain_name']}' transferred to {$targetUser['username']}"; + } catch (\Exception $e) { + $_SESSION['error'] = 'Failed to transfer domain. Please try again.'; + } + + $this->redirect('/domains'); + } + + /** + * Bulk transfer domains to another user (Admin only) + */ + public function bulkTransfer() + { + \Core\Auth::requireAdmin(); + + if ($_SERVER['REQUEST_METHOD'] !== 'POST') { + $this->redirect('/domains'); + return; + } + + $this->verifyCsrf('/domains'); + + $domainIds = $_POST['domain_ids'] ?? []; + $targetUserId = (int)($_POST['target_user_id'] ?? 0); + + if (empty($domainIds) || !$targetUserId) { + $_SESSION['error'] = 'No domains selected or invalid user'; + $this->redirect('/domains'); + return; + } + + // Validate target user exists + $userModel = new \App\Models\User(); + $targetUser = $userModel->find($targetUserId); + if (!$targetUser) { + $_SESSION['error'] = 'Target user not found'; + $this->redirect('/domains'); + return; + } + + $transferred = 0; + foreach ($domainIds as $domainId) { + $domainId = (int)$domainId; + if ($domainId > 0) { + try { + $this->domainModel->update($domainId, ['user_id' => $targetUserId]); + $transferred++; + } catch (\Exception $e) { + // Continue with other domains + } + } + } + + $_SESSION['success'] = "$transferred domain(s) transferred to {$targetUser['username']}"; + $this->redirect('/domains'); + } } diff --git a/app/Controllers/InstallerController.php b/app/Controllers/InstallerController.php index a3b8a3b..3a7b501 100644 --- a/app/Controllers/InstallerController.php +++ b/app/Controllers/InstallerController.php @@ -49,6 +49,7 @@ class InstallerController extends Controller '015_create_error_logs_table.sql', '016_add_tags_to_domains.sql', '017_add_two_factor_authentication.sql', + '018_add_user_isolation.sql', ]; try { @@ -268,6 +269,8 @@ class InstallerController extends Controller '014_add_captcha_settings.sql', '015_create_error_logs_table.sql', '016_add_tags_to_domains.sql', + '017_add_two_factor_authentication.sql', + '018_add_user_isolation.sql', ]; $stmt = $pdo->prepare("INSERT INTO migrations (migration) VALUES (?) ON DUPLICATE KEY UPDATE migration=migration"); diff --git a/app/Controllers/NotificationGroupController.php b/app/Controllers/NotificationGroupController.php index d4fd843..baba30f 100644 --- a/app/Controllers/NotificationGroupController.php +++ b/app/Controllers/NotificationGroupController.php @@ -19,10 +19,28 @@ class NotificationGroupController extends Controller public function index() { - $groups = $this->groupModel->getAllWithChannelCount(); + // Get current user and isolation mode + $userId = \Core\Auth::id(); + $settingModel = new \App\Models\Setting(); + $isolationMode = $settingModel->getValue('user_isolation_mode', 'shared'); + + // Get groups based on isolation mode + if ($isolationMode === 'isolated') { + $groups = $this->groupModel->getAllWithChannelCount($userId); + } else { + $groups = $this->groupModel->getAllWithChannelCount(); + } + + // Get users for transfer functionality (admin only) + $users = []; + if (\Core\Auth::user()['role'] === 'admin') { + $userModel = new \App\Models\User(); + $users = $userModel->all(); + } $this->view('groups/index', [ 'groups' => $groups, + 'users' => $users, 'title' => 'Notification Groups' ]); } @@ -69,10 +87,22 @@ class NotificationGroupController extends Controller } try { - $id = $this->groupModel->create([ + // Get current user and isolation mode + $userId = \Core\Auth::id(); + $settingModel = new \App\Models\Setting(); + $isolationMode = $settingModel->getValue('user_isolation_mode', 'shared'); + + $groupData = [ 'name' => $name, 'description' => $description - ]); + ]; + + // Assign to current user if in isolated mode + if ($isolationMode === 'isolated') { + $groupData['user_id'] = $userId; + } + + $id = $this->groupModel->create($groupData); $_SESSION['success'] = "Group '$name' created successfully"; $this->redirect("/groups/edit?id=$id"); @@ -492,5 +522,116 @@ class NotificationGroupController extends Controller $this->redirect('/groups'); } + + /** + * Transfer group to another user (Admin only) + */ + public function transfer() + { + \Core\Auth::requireAdmin(); + + if ($_SERVER['REQUEST_METHOD'] !== 'POST') { + $this->redirect('/groups'); + return; + } + + $this->verifyCsrf('/groups'); + + $groupId = (int)($_POST['group_id'] ?? 0); + $targetUserId = (int)($_POST['target_user_id'] ?? 0); + + if (!$groupId || !$targetUserId) { + $_SESSION['error'] = 'Invalid group or user selected'; + $this->redirect('/groups'); + return; + } + + // Validate group exists + $group = $this->groupModel->find($groupId); + if (!$group) { + $_SESSION['error'] = 'Group not found'; + $this->redirect('/groups'); + return; + } + + // Validate target user exists + $userModel = new \App\Models\User(); + $targetUser = $userModel->find($targetUserId); + if (!$targetUser) { + $_SESSION['error'] = 'Target user not found'; + $this->redirect('/groups'); + return; + } + + try { + // Transfer group + $this->groupModel->update($groupId, ['user_id' => $targetUserId]); + + // Also transfer all domains in this group + $domainModel = new \App\Models\Domain(); + $domainModel->updateWhere(['notification_group_id' => $groupId], ['user_id' => $targetUserId]); + + $_SESSION['success'] = "Group '{$group['name']}' and its domains transferred to {$targetUser['username']}"; + } catch (\Exception $e) { + $_SESSION['error'] = 'Failed to transfer group. Please try again.'; + } + + $this->redirect('/groups'); + } + + /** + * Bulk transfer groups to another user (Admin only) + */ + public function bulkTransfer() + { + \Core\Auth::requireAdmin(); + + if ($_SERVER['REQUEST_METHOD'] !== 'POST') { + $this->redirect('/groups'); + return; + } + + $this->verifyCsrf('/groups'); + + $groupIds = $_POST['group_ids'] ?? []; + $targetUserId = (int)($_POST['target_user_id'] ?? 0); + + if (empty($groupIds) || !$targetUserId) { + $_SESSION['error'] = 'No groups selected or invalid user'; + $this->redirect('/groups'); + return; + } + + // Validate target user exists + $userModel = new \App\Models\User(); + $targetUser = $userModel->find($targetUserId); + if (!$targetUser) { + $_SESSION['error'] = 'Target user not found'; + $this->redirect('/groups'); + return; + } + + $transferred = 0; + foreach ($groupIds as $groupId) { + $groupId = (int)$groupId; + if ($groupId > 0) { + try { + // Transfer group + $this->groupModel->update($groupId, ['user_id' => $targetUserId]); + + // Also transfer all domains in this group + $domainModel = new \App\Models\Domain(); + $domainModel->updateWhere(['notification_group_id' => $groupId], ['user_id' => $targetUserId]); + + $transferred++; + } catch (\Exception $e) { + // Continue with other groups + } + } + } + + $_SESSION['success'] = "$transferred group(s) and their domains transferred to {$targetUser['username']}"; + $this->redirect('/groups'); + } } diff --git a/app/Controllers/SearchController.php b/app/Controllers/SearchController.php index 694109d..2bb5165 100644 --- a/app/Controllers/SearchController.php +++ b/app/Controllers/SearchController.php @@ -27,12 +27,17 @@ class SearchController extends Controller return; } + // Get current user and isolation mode + $userId = \Core\Auth::id(); + $settingModel = new \App\Models\Setting(); + $isolationMode = $settingModel->getValue('user_isolation_mode', 'shared'); + // Pagination parameters $page = max(1, (int)($_GET['page'] ?? 1)); $perPage = max(10, min(100, (int)($_GET['per_page'] ?? 25))); // Search existing domains in database - $allResults = $this->searchDomains($query); + $allResults = $this->searchDomains($query, $isolationMode === 'isolated' ? $userId : null); $totalResults = count($allResults); // Calculate pagination @@ -144,25 +149,47 @@ class SearchController extends Controller /** * Search domains in database */ - private function searchDomains(string $query): array + 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 ? + WHERE (d.domain_name LIKE ? OR d.registrar LIKE ? - OR ng.name LIKE ? - ORDER BY d.domain_name ASC - LIMIT 50"; + 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"; - $searchTerm = '%' . $query . '%'; $stmt = $db->prepare($sql); - $stmt->execute([$searchTerm, $searchTerm, $searchTerm]); + $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 c62ef24..55b7fda 100644 --- a/app/Controllers/SettingsController.php +++ b/app/Controllers/SettingsController.php @@ -27,6 +27,7 @@ class SettingsController extends Controller $emailSettings = $this->settingModel->getEmailSettings(); $captchaSettings = $this->settingModel->getCaptchaSettings(); $twoFactorSettings = $this->settingModel->getTwoFactorSettings(); + $isolationSettings = $this->getIsolationSettings(); // Predefined notification day options $notificationPresets = [ @@ -71,6 +72,7 @@ class SettingsController extends Controller 'emailSettings' => $emailSettings, 'captchaSettings' => $captchaSettings, 'twoFactorSettings' => $twoFactorSettings, + 'isolationSettings' => $isolationSettings, 'notificationPresets' => $notificationPresets, 'checkIntervalPresets' => $checkIntervalPresets, 'title' => 'Settings' @@ -491,5 +493,107 @@ class SettingsController extends Controller $this->redirect('/settings#security'); } } + + /** + * Get isolation settings + */ + private function getIsolationSettings(): array + { + return [ + 'user_isolation_mode' => $this->settingModel->getValue('user_isolation_mode', 'shared') + ]; + } + + /** + * Toggle isolation mode + */ + public function toggleIsolationMode() + { + if ($_SERVER['REQUEST_METHOD'] !== 'POST') { + $this->redirect('/settings'); + return; + } + + $this->verifyCsrf('/settings#isolation'); + + $newMode = $_POST['user_isolation_mode'] ?? 'shared'; + + try { + if ($newMode === 'isolated') { + // Check if we have any admin users + $domainModel = new \App\Models\Domain(); + $adminUser = $domainModel->getFirstAdminUser(); + if (!$adminUser) { + $_SESSION['error'] = 'No admin users found. Please create an admin user first.'; + $this->redirect('/settings#isolation'); + return; + } + + // Run migration + $migrationResult = $this->migrateToIsolatedMode(); + if (!$migrationResult['success']) { + $_SESSION['error'] = 'Migration failed: ' . $migrationResult['error']; + $this->redirect('/settings#isolation'); + return; + } + + $_SESSION['success'] = "Isolation mode enabled. {$migrationResult['domains_assigned']} domains and {$migrationResult['groups_assigned']} groups assigned to admin."; + } else { + // Switching back to shared mode + $this->settingModel->setValue('user_isolation_mode', 'shared'); + $_SESSION['success'] = 'Switched to shared mode. All users can now see all domains and groups.'; + } + + $this->redirect('/settings#isolation'); + + } catch (\Exception $e) { + $_SESSION['error'] = 'Error updating isolation mode: ' . $e->getMessage(); + $this->redirect('/settings#isolation'); + } + } + + /** + * Migrate existing data to isolated mode + */ + private function migrateToIsolatedMode(): array + { + try { + // Get the first admin user + $domainModel = new \App\Models\Domain(); + $adminUser = $domainModel->getFirstAdminUser(); + + if (!$adminUser) { + throw new \Exception('No admin user found. Please create an admin user first.'); + } + + $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(); + + // 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(); + + // Set isolation mode + $this->settingModel->setValue('user_isolation_mode', 'isolated'); + + return [ + 'success' => true, + 'admin_id' => $adminId, + 'domains_assigned' => $domainCount, + 'groups_assigned' => $groupCount + ]; + + } catch (\Exception $e) { + return [ + 'success' => false, + 'error' => $e->getMessage() + ]; + } + } } diff --git a/app/Models/Domain.php b/app/Models/Domain.php index e99edf5..3c7528b 100644 --- a/app/Models/Domain.php +++ b/app/Models/Domain.php @@ -11,21 +11,28 @@ class Domain extends Model /** * Get all domains with their notification group */ - public function getAllWithGroups(): array + public function getAllWithGroups(?int $userId = null): array { $sql = "SELECT d.*, ng.name as group_name FROM domains d - LEFT JOIN notification_groups ng ON d.notification_group_id = ng.id - ORDER BY d.status DESC, d.expiration_date ASC"; - - $stmt = $this->db->query($sql); + LEFT JOIN notification_groups ng ON d.notification_group_id = ng.id"; + + if ($userId && !$this->isAdmin($userId)) { + $sql .= " WHERE d.user_id = ?"; + $stmt = $this->db->prepare($sql); + $stmt->execute([$userId]); + } else { + $sql .= " ORDER BY d.status DESC, d.expiration_date ASC"; + $stmt = $this->db->query($sql); + } + return $stmt->fetchAll(); } /** * Get domains expiring within days */ - public function getExpiringDomains(int $days): array + public function getExpiringDomains(int $days, ?int $userId = null): array { $sql = "SELECT d.*, ng.name as group_name FROM domains d @@ -33,27 +40,43 @@ class Domain extends Model WHERE d.is_active = 1 AND d.expiration_date IS NOT NULL AND d.expiration_date <= DATE_ADD(CURDATE(), INTERVAL ? DAY) - AND d.expiration_date >= CURDATE() - ORDER BY d.expiration_date ASC"; + AND d.expiration_date >= CURDATE()"; + + $params = [$days]; + + if ($userId && !$this->isAdmin($userId)) { + $sql .= " AND d.user_id = ?"; + $params[] = $userId; + } + + $sql .= " ORDER BY d.expiration_date ASC"; $stmt = $this->db->prepare($sql); - $stmt->execute([$days]); + $stmt->execute($params); return $stmt->fetchAll(); } /** * Get domains by status */ - public function getByStatus(string $status): array + public function getByStatus(string $status, ?int $userId = null): 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.status = ? - ORDER BY d.expiration_date ASC"; + WHERE d.status = ?"; + + $params = [$status]; + + if ($userId && !$this->isAdmin($userId)) { + $sql .= " AND d.user_id = ?"; + $params[] = $userId; + } + + $sql .= " ORDER BY d.expiration_date ASC"; $stmt = $this->db->prepare($sql); - $stmt->execute([$status]); + $stmt->execute($params); return $stmt->fetchAll(); } @@ -100,24 +123,32 @@ class Domain extends Model /** * Get recent domains */ - public function getRecent(int $limit = 5): array + public function getRecent(int $limit = 5, ?int $userId = null): 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.is_active = 1 - ORDER BY d.created_at DESC, d.id DESC - LIMIT ?"; + WHERE d.is_active = 1"; + + $params = []; + + if ($userId && !$this->isAdmin($userId)) { + $sql .= " AND d.user_id = ?"; + $params[] = $userId; + } + + $sql .= " ORDER BY d.created_at DESC, d.id DESC LIMIT ?"; + $params[] = $limit; $stmt = $this->db->prepare($sql); - $stmt->execute([$limit]); + $stmt->execute($params); return $stmt->fetchAll(); } /** * Get dashboard statistics */ - public function getStatistics(): array + public function getStatistics(?int $userId = null): array { $stats = [ 'total' => 0, @@ -127,9 +158,19 @@ class Domain extends Model 'inactive' => 0, ]; + // Build WHERE clause for user filtering + $whereClause = "WHERE is_active = 1"; + $params = []; + + if ($userId && !$this->isAdmin($userId)) { + $whereClause .= " AND user_id = ?"; + $params[] = $userId; + } + // Get status counts for active domains only - $sql = "SELECT status, COUNT(*) as count FROM domains WHERE is_active = 1 GROUP BY status"; - $stmt = $this->db->query($sql); + $sql = "SELECT status, COUNT(*) as count FROM domains $whereClause GROUP BY status"; + $stmt = $this->db->prepare($sql); + $stmt->execute($params); $results = $stmt->fetchAll(); $stats['total'] = array_sum(array_column($results, 'count')); @@ -139,7 +180,16 @@ class Domain extends Model } // Get count of inactive domains (is_active = 0) - $inactiveStmt = $this->db->query("SELECT COUNT(*) as count FROM domains WHERE is_active = 0"); + $inactiveWhereClause = "WHERE is_active = 0"; + $inactiveParams = []; + + if ($userId && !$this->isAdmin($userId)) { + $inactiveWhereClause .= " AND user_id = ?"; + $inactiveParams[] = $userId; + } + + $inactiveStmt = $this->db->prepare("SELECT COUNT(*) as count FROM domains $inactiveWhereClause"); + $inactiveStmt->execute($inactiveParams); $inactiveResult = $inactiveStmt->fetch(); $stats['inactive'] = $inactiveResult['count'] ?? 0; @@ -152,10 +202,10 @@ class Domain extends Model /** * Get filtered, sorted, and paginated domains */ - public function getFilteredPaginated(array $filters, string $sortBy, string $sortOrder, int $page, int $perPage, int $expiringThreshold = 30): array + public function getFilteredPaginated(array $filters, string $sortBy, string $sortOrder, int $page, int $perPage, int $expiringThreshold = 30, ?int $userId = null): array { // Get all domains with groups - $domains = $this->getAllWithGroups(); + $domains = $this->getAllWithGroups($userId); // Apply search filter if (!empty($filters['search'])) { @@ -242,9 +292,18 @@ class Domain extends Model /** * Get all unique tags from all domains */ - public function getAllTags(): array + public function getAllTags(?int $userId = null): array { - $stmt = $this->db->query("SELECT DISTINCT tags FROM domains WHERE tags IS NOT NULL AND tags != ''"); + $sql = "SELECT DISTINCT tags FROM domains WHERE tags IS NOT NULL AND tags != ''"; + $params = []; + + if ($userId && !$this->isAdmin($userId)) { + $sql .= " AND user_id = ?"; + $params[] = $userId; + } + + $stmt = $this->db->prepare($sql); + $stmt->execute($params); $results = $stmt->fetchAll(); $allTags = []; @@ -260,5 +319,29 @@ class Domain extends Model sort($allTags); return $allTags; } + + /** + * 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 + */ + public function getFirstAdminUser(): ?array + { + $stmt = $this->db->query("SELECT * FROM users WHERE role = 'admin' ORDER BY id ASC LIMIT 1"); + return $stmt->fetch() ?: null; + } } diff --git a/app/Models/NotificationGroup.php b/app/Models/NotificationGroup.php index 99cbe05..d6dc504 100644 --- a/app/Models/NotificationGroup.php +++ b/app/Models/NotificationGroup.php @@ -11,25 +11,31 @@ class NotificationGroup extends Model /** * Get all groups with channel count */ - public function getAllWithChannelCount(): array + public function getAllWithChannelCount(?int $userId = null): array { $sql = "SELECT ng.*, COUNT(DISTINCT nc.id) as channel_count, COUNT(DISTINCT d.id) as domain_count FROM notification_groups ng LEFT JOIN notification_channels nc ON ng.id = nc.notification_group_id - LEFT JOIN domains d ON ng.id = d.notification_group_id - GROUP BY ng.id - ORDER BY ng.name ASC"; - - $stmt = $this->db->query($sql); + LEFT JOIN domains d ON ng.id = d.notification_group_id"; + + if ($userId && !$this->isAdmin($userId)) { + $sql .= " WHERE ng.user_id = ?"; + $stmt = $this->db->prepare($sql); + $stmt->execute([$userId]); + } else { + $sql .= " GROUP BY ng.id ORDER BY ng.name ASC"; + $stmt = $this->db->query($sql); + } + return $stmt->fetchAll(); } /** * Get group with channels and domains */ - public function getWithDetails(int $id): ?array + public function getWithDetails(int $id, ?int $userId = null): ?array { $group = $this->find($id); @@ -37,13 +43,22 @@ class NotificationGroup extends Model return null; } + // Check if user has access to this group + if ($userId && !$this->isAdmin($userId) && $group['user_id'] != $userId) { + return null; + } + // Get channels $channelModel = new NotificationChannel(); $group['channels'] = $channelModel->getByGroupId($id); - // Get domains + // Get domains (filtered by user if needed) $domainModel = new Domain(); - $group['domains'] = $domainModel->where('notification_group_id', $id); + if ($userId && !$this->isAdmin($userId)) { + $group['domains'] = $domainModel->where('notification_group_id', $id, $userId); + } else { + $group['domains'] = $domainModel->where('notification_group_id', $id); + } return $group; } @@ -61,5 +76,29 @@ 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 + */ + public function getFirstAdminUser(): ?array + { + $stmt = $this->db->query("SELECT * FROM users WHERE role = 'admin' ORDER BY id ASC LIMIT 1"); + return $stmt->fetch() ?: null; + } } diff --git a/app/Views/groups/index.php b/app/Views/groups/index.php index 8ef620e..46b548b 100644 --- a/app/Views/groups/index.php +++ b/app/Views/groups/index.php @@ -37,6 +37,13 @@ ob_start();