Replace comma-separated tags with relational tag system.
- Add tags and domain_tags tables - Support tag management - Support user isolation (global/private tags) - Add filtering all domain views to operations - Update all domain views automatically
This commit is contained in:
@@ -77,6 +77,14 @@ class DomainController extends Controller
|
||||
$allTags = $this->domainModel->getAllTags();
|
||||
}
|
||||
|
||||
// Get available tags for bulk operations
|
||||
$tagModel = new \App\Models\Tag();
|
||||
if ($isolationMode === 'isolated') {
|
||||
$availableTags = $tagModel->getAllWithUsage($userId);
|
||||
} else {
|
||||
$availableTags = $tagModel->getAllWithUsage();
|
||||
}
|
||||
|
||||
// Format domains for display
|
||||
$formattedDomains = \App\Helpers\DomainHelper::formatMultiple($result['domains']);
|
||||
|
||||
@@ -91,6 +99,7 @@ class DomainController extends Controller
|
||||
'domains' => $formattedDomains,
|
||||
'groups' => $groups,
|
||||
'allTags' => $allTags,
|
||||
'availableTags' => $availableTags,
|
||||
'users' => $users,
|
||||
'filters' => [
|
||||
'search' => $search,
|
||||
@@ -117,9 +126,18 @@ class DomainController extends Controller
|
||||
} else {
|
||||
$groups = $this->groupModel->getAllWithChannelCount();
|
||||
}
|
||||
|
||||
// Get available tags for the new tag system
|
||||
$tagModel = new \App\Models\Tag();
|
||||
if ($isolationMode === 'isolated') {
|
||||
$availableTags = $tagModel->getAllWithUsage($userId);
|
||||
} else {
|
||||
$availableTags = $tagModel->getAllWithUsage();
|
||||
}
|
||||
|
||||
$this->view('domains/create', [
|
||||
'groups' => $groups,
|
||||
'availableTags' => $availableTags,
|
||||
'title' => 'Add Domain'
|
||||
]);
|
||||
}
|
||||
@@ -237,7 +255,18 @@ class DomainController extends Controller
|
||||
public function edit($params = [])
|
||||
{
|
||||
$id = $params['id'] ?? 0;
|
||||
$domain = $this->checkDomainAccess($id);
|
||||
|
||||
// Get current user and isolation mode
|
||||
$userId = \Core\Auth::id();
|
||||
$settingModel = new \App\Models\Setting();
|
||||
$isolationMode = $settingModel->getValue('user_isolation_mode', 'shared');
|
||||
|
||||
// Get domain with tags and groups
|
||||
if ($isolationMode === 'isolated') {
|
||||
$domain = $this->domainModel->getWithTagsAndGroups($id, $userId);
|
||||
} else {
|
||||
$domain = $this->domainModel->getWithTagsAndGroups($id);
|
||||
}
|
||||
|
||||
if (!$domain) {
|
||||
$_SESSION['error'] = 'Domain not found';
|
||||
@@ -246,19 +275,28 @@ class DomainController extends Controller
|
||||
}
|
||||
|
||||
// Get groups based on isolation mode
|
||||
$userId = \Core\Auth::id();
|
||||
$settingModel = new \App\Models\Setting();
|
||||
$isolationMode = $settingModel->getValue('user_isolation_mode', 'shared');
|
||||
|
||||
if ($isolationMode === 'isolated') {
|
||||
$groups = $this->groupModel->getAllWithChannelCount($userId);
|
||||
} else {
|
||||
$groups = $this->groupModel->getAllWithChannelCount();
|
||||
}
|
||||
|
||||
// Get available tags for the new tag system
|
||||
$tagModel = new \App\Models\Tag();
|
||||
if ($isolationMode === 'isolated') {
|
||||
$availableTags = $tagModel->getAllWithUsage($userId);
|
||||
} else {
|
||||
$availableTags = $tagModel->getAllWithUsage();
|
||||
}
|
||||
|
||||
// Get referrer for cancel button
|
||||
$referrer = $_GET['from'] ?? '/domains/' . $domain['id'];
|
||||
|
||||
$this->view('domains/edit', [
|
||||
'domain' => $domain,
|
||||
'groups' => $groups,
|
||||
'availableTags' => $availableTags,
|
||||
'referrer' => $referrer,
|
||||
'title' => 'Edit Domain'
|
||||
]);
|
||||
}
|
||||
@@ -319,7 +357,6 @@ class DomainController extends Controller
|
||||
|
||||
$this->domainModel->update($id, [
|
||||
'notification_group_id' => $groupId,
|
||||
'tags' => $tags,
|
||||
'is_active' => $isActive,
|
||||
'expiration_date' => $manualExpirationDate
|
||||
]);
|
||||
@@ -362,6 +399,16 @@ class DomainController extends Controller
|
||||
}
|
||||
}
|
||||
|
||||
// Handle tags using the new tag system
|
||||
if (!empty($tags)) {
|
||||
$tagModel = new \App\Models\Tag();
|
||||
$tagModel->updateDomainTags($id, $tags, $userId);
|
||||
} else {
|
||||
// Remove all tags from domain
|
||||
$tagModel = new \App\Models\Tag();
|
||||
$tagModel->removeAllFromDomain($id);
|
||||
}
|
||||
|
||||
$_SESSION['success'] = 'Domain updated successfully';
|
||||
$this->redirect('/domains/' . $id);
|
||||
}
|
||||
@@ -471,11 +518,11 @@ class DomainController extends Controller
|
||||
$settingModel = new \App\Models\Setting();
|
||||
$isolationMode = $settingModel->getValue('user_isolation_mode', 'shared');
|
||||
|
||||
// Check domain access based on isolation mode
|
||||
// Get domain with tags and groups
|
||||
if ($isolationMode === 'isolated') {
|
||||
$domain = $this->domainModel->getWithChannels($id, $userId);
|
||||
$domain = $this->domainModel->getWithTagsAndGroups($id, $userId);
|
||||
} else {
|
||||
$domain = $this->domainModel->getWithChannels($id);
|
||||
$domain = $this->domainModel->getWithTagsAndGroups($id);
|
||||
}
|
||||
|
||||
if (!$domain) {
|
||||
@@ -502,10 +549,19 @@ class DomainController extends Controller
|
||||
if (!empty($domain['channels'])) {
|
||||
$formattedDomain['activeChannelCount'] = \App\Helpers\DomainHelper::getActiveChannelCount($domain['channels']);
|
||||
}
|
||||
|
||||
// Get available tags for the new tag system
|
||||
$tagModel = new \App\Models\Tag();
|
||||
if ($isolationMode === 'isolated') {
|
||||
$availableTags = $tagModel->getAllWithUsage($userId);
|
||||
} else {
|
||||
$availableTags = $tagModel->getAllWithUsage();
|
||||
}
|
||||
|
||||
$this->view('domains/view', [
|
||||
'domain' => $formattedDomain,
|
||||
'logs' => $logs,
|
||||
'availableTags' => $availableTags,
|
||||
'title' => $domain['domain_name']
|
||||
]);
|
||||
}
|
||||
@@ -524,8 +580,17 @@ class DomainController extends Controller
|
||||
$groups = $this->groupModel->getAllWithChannelCount();
|
||||
}
|
||||
|
||||
// Get available tags for the new tag system
|
||||
$tagModel = new \App\Models\Tag();
|
||||
if ($isolationMode === 'isolated') {
|
||||
$availableTags = $tagModel->getAllWithUsage($userId);
|
||||
} else {
|
||||
$availableTags = $tagModel->getAllWithUsage();
|
||||
}
|
||||
|
||||
$this->view('domains/bulk-add', [
|
||||
'groups' => $groups,
|
||||
'availableTags' => $availableTags,
|
||||
'title' => 'Bulk Add Domains'
|
||||
]);
|
||||
return;
|
||||
@@ -1007,6 +1072,7 @@ class DomainController extends Controller
|
||||
$settingModel = new \App\Models\Setting();
|
||||
$isolationMode = $settingModel->getValue('user_isolation_mode', 'shared');
|
||||
|
||||
$tagModel = new \App\Models\Tag();
|
||||
$updated = 0;
|
||||
foreach ($domainIds as $id) {
|
||||
// Check domain access based on isolation mode
|
||||
@@ -1016,7 +1082,7 @@ class DomainController extends Controller
|
||||
$domain = $this->domainModel->find($id);
|
||||
}
|
||||
|
||||
if ($domain && $this->domainModel->update($id, ['tags' => ''])) {
|
||||
if ($domain && $tagModel->removeAllFromDomain($id)) {
|
||||
$updated++;
|
||||
}
|
||||
}
|
||||
@@ -1025,6 +1091,112 @@ class DomainController extends Controller
|
||||
$this->redirect('/domains');
|
||||
}
|
||||
|
||||
/**
|
||||
* Bulk remove specific tag from domains
|
||||
*/
|
||||
public function bulkRemoveSpecificTag()
|
||||
{
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
$this->redirect('/domains');
|
||||
return;
|
||||
}
|
||||
|
||||
// CSRF Protection
|
||||
$this->verifyCsrf('/domains');
|
||||
|
||||
$domainIds = $_POST['domain_ids'] ?? [];
|
||||
$tagId = (int)($_POST['tag_id'] ?? 0);
|
||||
|
||||
if (empty($domainIds) || !$tagId) {
|
||||
$_SESSION['error'] = 'Invalid request';
|
||||
$this->redirect('/domains');
|
||||
return;
|
||||
}
|
||||
|
||||
$tagModel = new \App\Models\Tag();
|
||||
$tag = $tagModel->find($tagId);
|
||||
if (!$tag) {
|
||||
$_SESSION['error'] = 'Tag not found';
|
||||
$this->redirect('/domains');
|
||||
return;
|
||||
}
|
||||
|
||||
// Get current user and isolation mode
|
||||
$userId = \Core\Auth::id();
|
||||
$settingModel = new \App\Models\Setting();
|
||||
$isolationMode = $settingModel->getValue('user_isolation_mode', 'shared');
|
||||
|
||||
$removed = 0;
|
||||
foreach ($domainIds as $domainId) {
|
||||
// Check domain access based on isolation mode
|
||||
if ($isolationMode === 'isolated') {
|
||||
$domain = $this->domainModel->findWithIsolation($domainId, $userId);
|
||||
} else {
|
||||
$domain = $this->domainModel->find($domainId);
|
||||
}
|
||||
|
||||
if ($domain && $tagModel->removeFromDomain($domainId, $tagId)) {
|
||||
$removed++;
|
||||
}
|
||||
}
|
||||
|
||||
$_SESSION['success'] = "Tag '{$tag['name']}' removed from $removed domain(s)";
|
||||
$this->redirect('/domains');
|
||||
}
|
||||
|
||||
/**
|
||||
* Bulk assign existing tag to domains
|
||||
*/
|
||||
public function bulkAssignExistingTag()
|
||||
{
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
$this->redirect('/domains');
|
||||
return;
|
||||
}
|
||||
|
||||
// CSRF Protection
|
||||
$this->verifyCsrf('/domains');
|
||||
|
||||
$domainIds = $_POST['domain_ids'] ?? [];
|
||||
$tagId = (int)($_POST['tag_id'] ?? 0);
|
||||
|
||||
if (empty($domainIds) || !$tagId) {
|
||||
$_SESSION['error'] = 'Invalid request';
|
||||
$this->redirect('/domains');
|
||||
return;
|
||||
}
|
||||
|
||||
$tagModel = new \App\Models\Tag();
|
||||
$tag = $tagModel->find($tagId);
|
||||
if (!$tag) {
|
||||
$_SESSION['error'] = 'Tag not found';
|
||||
$this->redirect('/domains');
|
||||
return;
|
||||
}
|
||||
|
||||
// Get current user and isolation mode
|
||||
$userId = \Core\Auth::id();
|
||||
$settingModel = new \App\Models\Setting();
|
||||
$isolationMode = $settingModel->getValue('user_isolation_mode', 'shared');
|
||||
|
||||
$added = 0;
|
||||
foreach ($domainIds as $domainId) {
|
||||
// Check domain access based on isolation mode
|
||||
if ($isolationMode === 'isolated') {
|
||||
$domain = $this->domainModel->findWithIsolation($domainId, $userId);
|
||||
} else {
|
||||
$domain = $this->domainModel->find($domainId);
|
||||
}
|
||||
|
||||
if ($domain && $tagModel->addToDomain($domainId, $tagId)) {
|
||||
$added++;
|
||||
}
|
||||
}
|
||||
|
||||
$_SESSION['success'] = "Tag '{$tag['name']}' added to $added domain(s)";
|
||||
$this->redirect('/domains');
|
||||
}
|
||||
|
||||
/**
|
||||
* Transfer domain to another user (Admin only)
|
||||
*/
|
||||
@@ -1125,5 +1297,34 @@ class DomainController extends Controller
|
||||
$_SESSION['success'] = "$transferred domain(s) transferred to {$targetUser['username']}";
|
||||
$this->redirect('/domains');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get tags for specific domains (API endpoint)
|
||||
*/
|
||||
public function getTagsForDomains()
|
||||
{
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
$this->json(['error' => 'Method not allowed'], 405);
|
||||
return;
|
||||
}
|
||||
|
||||
// Get JSON input
|
||||
$input = json_decode(file_get_contents('php://input'), true);
|
||||
|
||||
if (!isset($input['domain_ids']) || !is_array($input['domain_ids'])) {
|
||||
$this->json(['error' => 'Invalid domain IDs'], 400);
|
||||
return;
|
||||
}
|
||||
|
||||
$domainIds = array_map('intval', $input['domain_ids']);
|
||||
$userId = \Core\Auth::id();
|
||||
$settingModel = new \App\Models\Setting();
|
||||
$isolationMode = $settingModel->getValue('user_isolation_mode', 'shared');
|
||||
|
||||
// Get tags that are assigned to the specified domains
|
||||
$tags = $this->domainModel->getTagsForDomains($domainIds, $isolationMode === 'isolated' ? $userId : null);
|
||||
|
||||
$this->json(['tags' => $tags]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -50,6 +50,7 @@ class InstallerController extends Controller
|
||||
'017_add_two_factor_authentication.sql',
|
||||
'018_add_user_isolation.sql',
|
||||
'019_add_webhook_channel_type.sql',
|
||||
'020_create_tags_system.sql',
|
||||
];
|
||||
|
||||
try {
|
||||
@@ -185,7 +186,8 @@ class InstallerController extends Controller
|
||||
'016_add_tags_to_domains.sql',
|
||||
'017_add_two_factor_authentication.sql',
|
||||
'018_add_user_isolation.sql',
|
||||
'019_add_webhook_channel_type.sql'
|
||||
'019_add_webhook_channel_type.sql',
|
||||
'020_create_tags_system.sql'
|
||||
];
|
||||
}
|
||||
|
||||
@@ -367,6 +369,7 @@ class InstallerController extends Controller
|
||||
'017_add_two_factor_authentication.sql',
|
||||
'018_add_user_isolation.sql',
|
||||
'019_add_webhook_channel_type.sql',
|
||||
'020_create_tags_system.sql',
|
||||
];
|
||||
|
||||
$stmt = $pdo->prepare("INSERT INTO migrations (migration) VALUES (?) ON DUPLICATE KEY UPDATE migration=migration");
|
||||
|
||||
@@ -533,7 +533,7 @@ class SettingsController extends Controller
|
||||
return;
|
||||
}
|
||||
|
||||
$_SESSION['success'] = "Isolation mode enabled. {$migrationResult['domains_assigned']} domains and {$migrationResult['groups_assigned']} groups assigned to admin.";
|
||||
$_SESSION['success'] = "Isolation mode enabled. {$migrationResult['domains_assigned']} domains, {$migrationResult['groups_assigned']} groups, and {$migrationResult['tags_assigned']} tags assigned to admin.";
|
||||
} else {
|
||||
// Switching back to shared mode
|
||||
$this->settingModel->setValue('user_isolation_mode', 'shared');
|
||||
@@ -572,6 +572,10 @@ class SettingsController extends Controller
|
||||
$groupModel = new \App\Models\NotificationGroup();
|
||||
$groupCount = $groupModel->assignUnassignedGroupsToUser($adminId);
|
||||
|
||||
// Assign all tags to admin
|
||||
$tagModel = new \App\Models\Tag();
|
||||
$tagCount = $tagModel->assignUnassignedTagsToUser($adminId);
|
||||
|
||||
// Set isolation mode
|
||||
$this->settingModel->setValue('user_isolation_mode', 'isolated');
|
||||
|
||||
@@ -579,7 +583,8 @@ class SettingsController extends Controller
|
||||
'success' => true,
|
||||
'admin_id' => $adminId,
|
||||
'domains_assigned' => $domainCount,
|
||||
'groups_assigned' => $groupCount
|
||||
'groups_assigned' => $groupCount,
|
||||
'tags_assigned' => $tagCount
|
||||
];
|
||||
|
||||
} catch (\Exception $e) {
|
||||
|
||||
492
app/Controllers/TagController.php
Normal file
492
app/Controllers/TagController.php
Normal file
@@ -0,0 +1,492 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controllers;
|
||||
|
||||
use App\Models\Tag;
|
||||
use App\Models\Domain;
|
||||
use Core\Controller;
|
||||
|
||||
class TagController extends Controller
|
||||
{
|
||||
private $tagModel;
|
||||
private $domainModel;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->tagModel = new Tag();
|
||||
$this->domainModel = new Domain();
|
||||
}
|
||||
|
||||
/**
|
||||
* Show tag management page
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$userId = \Core\Auth::id();
|
||||
$settingModel = new \App\Models\Setting();
|
||||
$isolationMode = $settingModel->getValue('user_isolation_mode', 'shared');
|
||||
|
||||
// Get filter parameters
|
||||
$search = $_GET['search'] ?? '';
|
||||
$color = $_GET['color'] ?? '';
|
||||
$type = $_GET['type'] ?? '';
|
||||
$sortBy = $_GET['sort'] ?? 'name';
|
||||
$sortOrder = $_GET['order'] ?? 'asc';
|
||||
$page = max(1, (int)($_GET['page'] ?? 1));
|
||||
$perPage = max(10, min(100, (int)($_GET['per_page'] ?? 25))); // Between 10 and 100
|
||||
|
||||
// Prepare filters array
|
||||
$filters = [
|
||||
'search' => $search,
|
||||
'color' => $color,
|
||||
'type' => $type,
|
||||
'sort' => $sortBy,
|
||||
'order' => $sortOrder
|
||||
];
|
||||
|
||||
// Get filtered and paginated tags
|
||||
$result = $this->tagModel->getFilteredPaginated($filters, $sortBy, $sortOrder, $page, $perPage, $isolationMode === 'isolated' ? $userId : null);
|
||||
|
||||
$availableColors = $this->tagModel->getAvailableColors();
|
||||
|
||||
$this->view('tags/index', [
|
||||
'tags' => $result['tags'],
|
||||
'pagination' => $result['pagination'],
|
||||
'filters' => $filters,
|
||||
'availableColors' => $availableColors,
|
||||
'isolationMode' => $isolationMode
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new tag
|
||||
*/
|
||||
public function create()
|
||||
{
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
$this->redirect('/tags');
|
||||
return;
|
||||
}
|
||||
|
||||
$this->verifyCsrf('/tags');
|
||||
|
||||
$name = trim($_POST['name'] ?? '');
|
||||
$color = $_POST['color'] ?? 'bg-gray-100 text-gray-700 border-gray-300';
|
||||
$description = trim($_POST['description'] ?? '');
|
||||
$userId = \Core\Auth::id();
|
||||
|
||||
if (empty($name)) {
|
||||
$_SESSION['error'] = 'Tag name is required';
|
||||
$this->redirect('/tags');
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate tag name format
|
||||
if (!preg_match('/^[a-z0-9-]+$/', $name)) {
|
||||
$_SESSION['error'] = 'Invalid tag name format (use only letters, numbers, and hyphens)';
|
||||
$this->redirect('/tags');
|
||||
return;
|
||||
}
|
||||
|
||||
// Check isolation mode
|
||||
$settingModel = new \App\Models\Setting();
|
||||
$isolationMode = $settingModel->getValue('user_isolation_mode', 'shared');
|
||||
|
||||
$data = [
|
||||
'name' => $name,
|
||||
'color' => $color,
|
||||
'description' => $description,
|
||||
'user_id' => $isolationMode === 'isolated' ? $userId : null
|
||||
];
|
||||
|
||||
if ($this->tagModel->create($data)) {
|
||||
$_SESSION['success'] = "Tag '$name' created successfully";
|
||||
} else {
|
||||
$_SESSION['error'] = 'Failed to create tag (name may already exist)';
|
||||
}
|
||||
|
||||
$this->redirect('/tags');
|
||||
}
|
||||
|
||||
/**
|
||||
* Update tag
|
||||
*/
|
||||
public function update()
|
||||
{
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
$this->redirect('/tags');
|
||||
return;
|
||||
}
|
||||
|
||||
$this->verifyCsrf('/tags');
|
||||
|
||||
$id = (int)($_POST['id'] ?? 0);
|
||||
$name = trim($_POST['name'] ?? '');
|
||||
$color = $_POST['color'] ?? 'bg-gray-100 text-gray-700 border-gray-300';
|
||||
$description = trim($_POST['description'] ?? '');
|
||||
$userId = \Core\Auth::id();
|
||||
|
||||
if (!$id || empty($name)) {
|
||||
$_SESSION['error'] = 'Invalid request';
|
||||
$this->redirect('/tags');
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if user can access this tag in isolation mode
|
||||
$settingModel = new \App\Models\Setting();
|
||||
$isolationMode = $settingModel->getValue('user_isolation_mode', 'shared');
|
||||
|
||||
if ($isolationMode === 'isolated' && !$this->tagModel->canUserAccessTag($id, $userId, true)) {
|
||||
$_SESSION['error'] = 'You do not have permission to edit this tag';
|
||||
$this->redirect('/tags');
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if this is a global tag (user_id = NULL) - only admins can edit global tags
|
||||
$tag = $this->tagModel->find($id);
|
||||
if ($tag && $tag['user_id'] === null && !\Core\Auth::isAdmin()) {
|
||||
$_SESSION['error'] = 'Only administrators can edit global tags';
|
||||
$this->redirect('/tags');
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate tag name format
|
||||
if (!preg_match('/^[a-z0-9-]+$/', $name)) {
|
||||
$_SESSION['error'] = 'Invalid tag name format (use only letters, numbers, and hyphens)';
|
||||
$this->redirect('/tags');
|
||||
return;
|
||||
}
|
||||
|
||||
$data = [
|
||||
'name' => $name,
|
||||
'color' => $color,
|
||||
'description' => $description
|
||||
];
|
||||
|
||||
if ($this->tagModel->update($id, $data)) {
|
||||
$_SESSION['success'] = "Tag updated successfully";
|
||||
} else {
|
||||
$_SESSION['error'] = 'Failed to update tag';
|
||||
}
|
||||
|
||||
$this->redirect('/tags');
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete tag
|
||||
*/
|
||||
public function delete()
|
||||
{
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
$this->redirect('/tags');
|
||||
return;
|
||||
}
|
||||
|
||||
$this->verifyCsrf('/tags');
|
||||
|
||||
$id = (int)($_POST['id'] ?? 0);
|
||||
$userId = \Core\Auth::id();
|
||||
|
||||
if (!$id) {
|
||||
$_SESSION['error'] = 'Invalid request';
|
||||
$this->redirect('/tags');
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if user can access this tag in isolation mode
|
||||
$settingModel = new \App\Models\Setting();
|
||||
$isolationMode = $settingModel->getValue('user_isolation_mode', 'shared');
|
||||
|
||||
if ($isolationMode === 'isolated' && !$this->tagModel->canUserAccessTag($id, $userId, true)) {
|
||||
$_SESSION['error'] = 'You do not have permission to delete this tag';
|
||||
$this->redirect('/tags');
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if this is a global tag (user_id = NULL) - only admins can delete global tags
|
||||
$tag = $this->tagModel->find($id);
|
||||
if ($tag && $tag['user_id'] === null && !\Core\Auth::isAdmin()) {
|
||||
$_SESSION['error'] = 'Only administrators can delete global tags';
|
||||
$this->redirect('/tags');
|
||||
return;
|
||||
}
|
||||
|
||||
$tag = $this->tagModel->find($id);
|
||||
if (!$tag) {
|
||||
$_SESSION['error'] = 'Tag not found';
|
||||
$this->redirect('/tags');
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->tagModel->deleteWithRelationships($id)) {
|
||||
$_SESSION['success'] = "Tag '{$tag['name']}' deleted successfully";
|
||||
} else {
|
||||
$_SESSION['error'] = 'Failed to delete tag';
|
||||
}
|
||||
|
||||
$this->redirect('/tags');
|
||||
}
|
||||
|
||||
/**
|
||||
* Show domains for a specific tag
|
||||
*/
|
||||
public function show($params = [])
|
||||
{
|
||||
$id = (int)($params['id'] ?? 0);
|
||||
|
||||
if (!$id) {
|
||||
$_SESSION['error'] = 'Invalid tag ID';
|
||||
$this->redirect('/tags');
|
||||
return;
|
||||
}
|
||||
|
||||
$tag = $this->tagModel->find($id);
|
||||
if (!$tag) {
|
||||
$_SESSION['error'] = 'Tag not found';
|
||||
$this->redirect('/tags');
|
||||
return;
|
||||
}
|
||||
|
||||
$userId = \Core\Auth::id();
|
||||
$settingModel = new \App\Models\Setting();
|
||||
$isolationMode = $settingModel->getValue('user_isolation_mode', 'shared');
|
||||
|
||||
// Get domains for this tag with proper formatting
|
||||
$domainModel = new \App\Models\Domain();
|
||||
$rawDomains = $this->tagModel->getDomainsForTag($id, $isolationMode === 'isolated' ? $userId : null);
|
||||
|
||||
// Format domains using DomainHelper (same as other pages)
|
||||
$domains = [];
|
||||
foreach ($rawDomains as $domain) {
|
||||
$domains[] = \App\Helpers\DomainHelper::formatForDisplay($domain);
|
||||
}
|
||||
|
||||
// Get current filters from request
|
||||
$filters = [
|
||||
'search' => $_GET['search'] ?? '',
|
||||
'status' => $_GET['status'] ?? '',
|
||||
'registrar' => $_GET['registrar'] ?? '',
|
||||
'sort' => $_GET['sort'] ?? 'domain_name',
|
||||
'order' => $_GET['order'] ?? 'asc'
|
||||
];
|
||||
|
||||
// Apply filters
|
||||
if (!empty($filters['search'])) {
|
||||
$domains = array_filter($domains, function($domain) use ($filters) {
|
||||
return stripos($domain['domain_name'], $filters['search']) !== false;
|
||||
});
|
||||
}
|
||||
|
||||
if (!empty($filters['status'])) {
|
||||
$domains = array_filter($domains, function($domain) use ($filters) {
|
||||
return $domain['status'] === $filters['status'];
|
||||
});
|
||||
}
|
||||
|
||||
if (!empty($filters['registrar'])) {
|
||||
$domains = array_filter($domains, function($domain) use ($filters) {
|
||||
return stripos($domain['registrar'] ?? '', $filters['registrar']) !== false;
|
||||
});
|
||||
}
|
||||
|
||||
// Apply sorting
|
||||
usort($domains, function($a, $b) use ($filters) {
|
||||
$aVal = $a[$filters['sort']] ?? '';
|
||||
$bVal = $b[$filters['sort']] ?? '';
|
||||
|
||||
$comparison = strcasecmp($aVal, $bVal);
|
||||
return $filters['order'] === 'desc' ? -$comparison : $comparison;
|
||||
});
|
||||
|
||||
// Pagination
|
||||
$page = max(1, (int)($_GET['page'] ?? 1));
|
||||
$perPage = max(10, min(100, (int)($_GET['per_page'] ?? 25)));
|
||||
$total = count($domains);
|
||||
$totalPages = ceil($total / $perPage);
|
||||
$offset = ($page - 1) * $perPage;
|
||||
$paginatedDomains = array_slice($domains, $offset, $perPage);
|
||||
|
||||
$pagination = [
|
||||
'current_page' => $page,
|
||||
'per_page' => $perPage,
|
||||
'total' => $total,
|
||||
'total_pages' => $totalPages,
|
||||
'showing_from' => $total > 0 ? $offset + 1 : 0,
|
||||
'showing_to' => min($offset + $perPage, $total)
|
||||
];
|
||||
|
||||
$this->view('tags/view', [
|
||||
'tag' => $tag,
|
||||
'domains' => $paginatedDomains,
|
||||
'filters' => $filters,
|
||||
'pagination' => $pagination
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Bulk add tag to domains
|
||||
*/
|
||||
public function bulkAddToDomains()
|
||||
{
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
$this->redirect('/domains');
|
||||
return;
|
||||
}
|
||||
|
||||
$this->verifyCsrf('/domains');
|
||||
|
||||
$domainIds = $_POST['domain_ids'] ?? [];
|
||||
$tagId = (int)($_POST['tag_id'] ?? 0);
|
||||
|
||||
if (empty($domainIds) || !$tagId) {
|
||||
$_SESSION['error'] = 'Invalid request';
|
||||
$this->redirect('/domains');
|
||||
return;
|
||||
}
|
||||
|
||||
$tag = $this->tagModel->find($tagId);
|
||||
if (!$tag) {
|
||||
$_SESSION['error'] = 'Tag not found';
|
||||
$this->redirect('/domains');
|
||||
return;
|
||||
}
|
||||
|
||||
// Get current user and isolation mode
|
||||
$userId = \Core\Auth::id();
|
||||
$settingModel = new \App\Models\Setting();
|
||||
$isolationMode = $settingModel->getValue('user_isolation_mode', 'shared');
|
||||
|
||||
$added = 0;
|
||||
foreach ($domainIds as $domainId) {
|
||||
// Check domain access based on isolation mode
|
||||
if ($isolationMode === 'isolated') {
|
||||
$domain = $this->domainModel->findWithIsolation($domainId, $userId);
|
||||
} else {
|
||||
$domain = $this->domainModel->find($domainId);
|
||||
}
|
||||
|
||||
if ($domain && $this->tagModel->addToDomain($domainId, $tagId)) {
|
||||
$added++;
|
||||
}
|
||||
}
|
||||
|
||||
$_SESSION['success'] = "Tag '{$tag['name']}' added to $added domain(s)";
|
||||
$this->redirect('/domains');
|
||||
}
|
||||
|
||||
/**
|
||||
* Bulk remove tag from domains
|
||||
*/
|
||||
public function bulkRemoveFromDomains()
|
||||
{
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
$this->redirect('/domains');
|
||||
return;
|
||||
}
|
||||
|
||||
$this->verifyCsrf('/domains');
|
||||
|
||||
$domainIds = $_POST['domain_ids'] ?? [];
|
||||
$tagId = (int)($_POST['tag_id'] ?? 0);
|
||||
|
||||
if (empty($domainIds) || !$tagId) {
|
||||
$_SESSION['error'] = 'Invalid request';
|
||||
$this->redirect('/domains');
|
||||
return;
|
||||
}
|
||||
|
||||
$tag = $this->tagModel->find($tagId);
|
||||
if (!$tag) {
|
||||
$_SESSION['error'] = 'Tag not found';
|
||||
$this->redirect('/domains');
|
||||
return;
|
||||
}
|
||||
|
||||
// Get current user and isolation mode
|
||||
$userId = \Core\Auth::id();
|
||||
$settingModel = new \App\Models\Setting();
|
||||
$isolationMode = $settingModel->getValue('user_isolation_mode', 'shared');
|
||||
|
||||
$removed = 0;
|
||||
foreach ($domainIds as $domainId) {
|
||||
// Check domain access based on isolation mode
|
||||
if ($isolationMode === 'isolated') {
|
||||
$domain = $this->domainModel->findWithIsolation($domainId, $userId);
|
||||
} else {
|
||||
$domain = $this->domainModel->find($domainId);
|
||||
}
|
||||
|
||||
if ($domain && $this->tagModel->removeFromDomain($domainId, $tagId)) {
|
||||
$removed++;
|
||||
}
|
||||
}
|
||||
|
||||
$_SESSION['success'] = "Tag '{$tag['name']}' removed from $removed domain(s)";
|
||||
$this->redirect('/domains');
|
||||
}
|
||||
|
||||
/**
|
||||
* Bulk delete tags
|
||||
*/
|
||||
public function bulkDelete()
|
||||
{
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
$this->redirect('/tags');
|
||||
return;
|
||||
}
|
||||
|
||||
// Verify CSRF token
|
||||
if (!\Core\Csrf::verify($_POST['csrf_token'] ?? '')) {
|
||||
$_SESSION['error'] = 'Invalid request';
|
||||
$this->redirect('/tags');
|
||||
return;
|
||||
}
|
||||
|
||||
$tagIds = $_POST['tag_ids'] ?? [];
|
||||
if (empty($tagIds)) {
|
||||
$_SESSION['error'] = 'No tags selected';
|
||||
$this->redirect('/tags');
|
||||
return;
|
||||
}
|
||||
|
||||
$userId = \Core\Auth::id();
|
||||
$settingModel = new \App\Models\Setting();
|
||||
$isolationMode = $settingModel->getValue('user_isolation_mode', 'shared');
|
||||
|
||||
$deleted = 0;
|
||||
$errors = [];
|
||||
|
||||
foreach ($tagIds as $tagId) {
|
||||
$tagId = (int)$tagId;
|
||||
|
||||
// Check if user can access this tag
|
||||
if (!$this->tagModel->canUserAccessTag($tagId, $userId, $isolationMode === 'isolated')) {
|
||||
$errors[] = "You don't have permission to delete tag ID $tagId";
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if it's a global tag and user is not admin
|
||||
$tag = $this->tagModel->find($tagId);
|
||||
if ($tag && $tag['user_id'] === null && !\Core\Auth::isAdmin()) {
|
||||
$errors[] = "Only administrators can delete global tags";
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($this->tagModel->delete($tagId)) {
|
||||
$deleted++;
|
||||
} else {
|
||||
$errors[] = "Failed to delete tag ID $tagId";
|
||||
}
|
||||
}
|
||||
|
||||
if ($deleted > 0) {
|
||||
$_SESSION['success'] = "$deleted tag(s) deleted successfully";
|
||||
}
|
||||
|
||||
if (!empty($errors)) {
|
||||
$_SESSION['error'] = implode(', ', $errors);
|
||||
}
|
||||
|
||||
$this->redirect('/tags');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user