2025-10-08 14:23:07 +03:00
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
namespace App\Controllers;
|
|
|
|
|
|
|
|
|
|
use Core\Controller;
|
|
|
|
|
use App\Models\Domain;
|
|
|
|
|
use App\Models\NotificationGroup;
|
|
|
|
|
use App\Services\WhoisService;
|
|
|
|
|
|
|
|
|
|
class DomainController extends Controller
|
|
|
|
|
{
|
|
|
|
|
private Domain $domainModel;
|
|
|
|
|
private NotificationGroup $groupModel;
|
|
|
|
|
private WhoisService $whoisService;
|
|
|
|
|
|
|
|
|
|
public function __construct()
|
|
|
|
|
{
|
|
|
|
|
$this->domainModel = new Domain();
|
|
|
|
|
$this->groupModel = new NotificationGroup();
|
|
|
|
|
$this->whoisService = new WhoisService();
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-20 21:08:09 +03:00
|
|
|
/**
|
|
|
|
|
* Check domain access based on isolation mode
|
|
|
|
|
*/
|
|
|
|
|
private function checkDomainAccess(int $id): ?array
|
|
|
|
|
{
|
|
|
|
|
$userId = \Core\Auth::id();
|
|
|
|
|
$settingModel = new \App\Models\Setting();
|
|
|
|
|
$isolationMode = $settingModel->getValue('user_isolation_mode', 'shared');
|
|
|
|
|
|
|
|
|
|
if ($isolationMode === 'isolated') {
|
|
|
|
|
return $this->domainModel->findWithIsolation($id, $userId);
|
|
|
|
|
} else {
|
|
|
|
|
return $this->domainModel->find($id);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-08 14:23:07 +03:00
|
|
|
public function index()
|
|
|
|
|
{
|
2025-10-20 17:04:13 +03:00
|
|
|
// Get current user and isolation mode
|
|
|
|
|
$userId = \Core\Auth::id();
|
|
|
|
|
$settingModel = new \App\Models\Setting();
|
|
|
|
|
$isolationMode = $settingModel->getValue('user_isolation_mode', 'shared');
|
|
|
|
|
|
2025-10-08 14:23:07 +03:00
|
|
|
// Get filter parameters
|
Add CSRF, CAPTCHA, and input validation improvements
Introduces CSRF protection to all sensitive controller actions, integrates configurable CAPTCHA (reCAPTCHA v2/v3, Turnstile) for authentication and registration flows, and centralizes input validation via a new InputValidator helper. Adds new helpers and services for CSRF and CAPTCHA, updates settings and migration for CAPTCHA configuration, and enhances logging and error handling in TLD registry import processes. Also improves validation for user, domain, group, and profile inputs throughout the application.
2025-10-10 00:04:12 +03:00
|
|
|
$search = \App\Helpers\InputValidator::sanitizeSearch($_GET['search'] ?? '', 100);
|
2025-10-08 14:23:07 +03:00
|
|
|
$status = $_GET['status'] ?? '';
|
|
|
|
|
$groupId = $_GET['group'] ?? '';
|
2025-10-12 12:46:16 +03:00
|
|
|
$tag = $_GET['tag'] ?? '';
|
2025-10-08 14:23:07 +03:00
|
|
|
$sortBy = $_GET['sort'] ?? 'domain_name';
|
|
|
|
|
$sortOrder = $_GET['order'] ?? 'asc';
|
|
|
|
|
$page = max(1, (int)($_GET['page'] ?? 1));
|
Add import/export and update system
Implement CSV/JSON import and export for domains, notification groups and tags (with masking for sensitive channel data), including size/format validation, in-memory CSV building, and logging. Add tag transfer and bulk transfer actions (admin-only). Introduce a new update system: Add UpdateController and UpdateService, migration 025_add_update_system_v1.1.3.sql, and installer changes to include the new migration and version handling; provide endpoints to check, apply, rollback and configure updates. Update helpers and UI bits: add getUpdateBadgeInfo in LayoutHelper, update notification icons/redirects, and add getMaxUploadSize in ViewHelper. Misc: add NotificationGroup::findByName, tweak .gitignore backups path, and update related views and routes.
2026-02-11 17:43:23 +02:00
|
|
|
// Remember per_page preference via cookie
|
|
|
|
|
if (isset($_GET['per_page'])) {
|
|
|
|
|
$perPage = max(10, min(100, (int)$_GET['per_page']));
|
|
|
|
|
setcookie('domains_per_page', (string)$perPage, time() + 365 * 24 * 60 * 60, '/');
|
|
|
|
|
} else {
|
|
|
|
|
$perPage = max(10, min(100, (int)($_COOKIE['domains_per_page'] ?? 25)));
|
|
|
|
|
}
|
2025-10-08 14:23:07 +03:00
|
|
|
|
2025-10-08 18:54:34 +03:00
|
|
|
// Get expiring threshold from settings
|
|
|
|
|
$notificationDays = $settingModel->getNotificationDays();
|
|
|
|
|
$expiringThreshold = !empty($notificationDays) ? max($notificationDays) : 30;
|
|
|
|
|
|
2025-10-10 14:01:19 +03:00
|
|
|
// Prepare filters array
|
|
|
|
|
$filters = [
|
|
|
|
|
'search' => $search,
|
|
|
|
|
'status' => $status,
|
2025-10-12 12:46:16 +03:00
|
|
|
'group' => $groupId,
|
|
|
|
|
'tag' => $tag
|
2025-10-10 14:01:19 +03:00
|
|
|
];
|
2025-10-08 14:23:07 +03:00
|
|
|
|
2025-10-10 14:01:19 +03:00
|
|
|
// Get filtered and paginated domains using model
|
2025-10-20 18:13:57 +03:00
|
|
|
$result = $this->domainModel->getFilteredPaginated($filters, $sortBy, $sortOrder, $page, $perPage, $expiringThreshold, $isolationMode === 'isolated' ? $userId : null);
|
2025-10-08 14:23:07 +03:00
|
|
|
|
2025-10-20 18:13:57 +03:00
|
|
|
// 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();
|
|
|
|
|
}
|
2025-10-12 12:46:16 +03:00
|
|
|
|
2025-10-25 02:04:00 +03:00
|
|
|
// Get available tags for bulk operations
|
|
|
|
|
$tagModel = new \App\Models\Tag();
|
|
|
|
|
if ($isolationMode === 'isolated') {
|
|
|
|
|
$availableTags = $tagModel->getAllWithUsage($userId);
|
|
|
|
|
} else {
|
|
|
|
|
$availableTags = $tagModel->getAllWithUsage();
|
|
|
|
|
}
|
|
|
|
|
|
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
|
|
|
// Format domains for display
|
2025-10-10 14:01:19 +03:00
|
|
|
$formattedDomains = \App\Helpers\DomainHelper::formatMultiple($result['domains']);
|
2025-10-08 14:23:07 +03:00
|
|
|
|
2025-10-20 17:40:43 +03:00
|
|
|
// Get users for transfer functionality (admin only)
|
|
|
|
|
$users = [];
|
|
|
|
|
if (\Core\Auth::isAdmin()) {
|
|
|
|
|
$userModel = new \App\Models\User();
|
|
|
|
|
$users = $userModel->all();
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-08 14:23:07 +03:00
|
|
|
$this->view('domains/index', [
|
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
|
|
|
'domains' => $formattedDomains,
|
2025-10-08 14:23:07 +03:00
|
|
|
'groups' => $groups,
|
2025-10-12 12:46:16 +03:00
|
|
|
'allTags' => $allTags,
|
2025-10-25 02:04:00 +03:00
|
|
|
'availableTags' => $availableTags,
|
2025-10-20 17:40:43 +03:00
|
|
|
'users' => $users,
|
2025-10-08 14:23:07 +03:00
|
|
|
'filters' => [
|
|
|
|
|
'search' => $search,
|
|
|
|
|
'status' => $status,
|
|
|
|
|
'group' => $groupId,
|
2025-10-12 12:46:16 +03:00
|
|
|
'tag' => $tag,
|
2025-10-08 14:23:07 +03:00
|
|
|
'sort' => $sortBy,
|
|
|
|
|
'order' => $sortOrder
|
|
|
|
|
],
|
2025-10-10 14:01:19 +03:00
|
|
|
'pagination' => $result['pagination'],
|
2025-10-08 14:23:07 +03:00
|
|
|
'title' => 'Domains'
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
|
Add import/export and update system
Implement CSV/JSON import and export for domains, notification groups and tags (with masking for sensitive channel data), including size/format validation, in-memory CSV building, and logging. Add tag transfer and bulk transfer actions (admin-only). Introduce a new update system: Add UpdateController and UpdateService, migration 025_add_update_system_v1.1.3.sql, and installer changes to include the new migration and version handling; provide endpoints to check, apply, rollback and configure updates. Update helpers and UI bits: add getUpdateBadgeInfo in LayoutHelper, update notification icons/redirects, and add getMaxUploadSize in ViewHelper. Misc: add NotificationGroup::findByName, tweak .gitignore backups path, and update related views and routes.
2026-02-11 17:43:23 +02:00
|
|
|
/**
|
|
|
|
|
* Export domains as CSV or JSON
|
|
|
|
|
*/
|
|
|
|
|
public function export()
|
|
|
|
|
{
|
|
|
|
|
$logger = new \App\Services\Logger('export');
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
$userId = \Core\Auth::id();
|
|
|
|
|
$settingModel = new \App\Models\Setting();
|
|
|
|
|
$isolationMode = $settingModel->getValue('user_isolation_mode', 'shared');
|
|
|
|
|
$format = $_GET['format'] ?? 'csv';
|
|
|
|
|
$logger->info("Domains export started", ['format' => $format, 'user_id' => $userId]);
|
|
|
|
|
|
|
|
|
|
if (!in_array($format, ['csv', 'json'])) {
|
|
|
|
|
$_SESSION['error'] = 'Invalid export format';
|
|
|
|
|
$this->redirect('/domains');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Get all domains with groups and tags
|
|
|
|
|
$domains = $this->domainModel->getAllWithGroups($isolationMode === 'isolated' ? $userId : null);
|
|
|
|
|
|
|
|
|
|
$exportData = [];
|
|
|
|
|
foreach ($domains as $domain) {
|
|
|
|
|
$exportData[] = [
|
|
|
|
|
'domain_name' => $domain['domain_name'],
|
|
|
|
|
'status' => $domain['status'] ?? '',
|
|
|
|
|
'registrar' => $domain['registrar'] ?? '',
|
|
|
|
|
'expiration_date' => $domain['expiration_date'] ?? '',
|
|
|
|
|
'tags' => $domain['tags'] ?? '',
|
|
|
|
|
'notification_group' => $domain['group_name'] ?? '',
|
|
|
|
|
'notes' => $domain['notes'] ?? ''
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$date = date('Y-m-d');
|
|
|
|
|
$filename = "domains_export_{$date}";
|
|
|
|
|
|
|
|
|
|
// Clean any prior output buffers to prevent header conflicts
|
|
|
|
|
while (ob_get_level()) {
|
|
|
|
|
ob_end_clean();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($format === 'json') {
|
|
|
|
|
header('Content-Type: application/json');
|
|
|
|
|
header("Content-Disposition: attachment; filename=\"{$filename}.json\"");
|
|
|
|
|
header('Cache-Control: no-cache, must-revalidate');
|
|
|
|
|
header('Pragma: no-cache');
|
|
|
|
|
echo json_encode($exportData, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
|
|
|
|
|
} else {
|
|
|
|
|
// Build CSV in memory to avoid fopen('php://output') issues
|
|
|
|
|
$csvContent = $this->buildCsv($exportData, ['domain_name', 'status', 'registrar', 'expiration_date', 'tags', 'notification_group', 'notes']);
|
|
|
|
|
$logger->info("CSV content built", ['bytes' => strlen($csvContent)]);
|
|
|
|
|
|
|
|
|
|
header('Content-Type: text/csv; charset=utf-8');
|
|
|
|
|
header("Content-Disposition: attachment; filename=\"{$filename}.csv\"");
|
|
|
|
|
header('Content-Length: ' . strlen($csvContent));
|
|
|
|
|
header('Cache-Control: no-cache, must-revalidate');
|
|
|
|
|
header('Pragma: no-cache');
|
|
|
|
|
echo $csvContent;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$logger->info("Domains export completed successfully");
|
|
|
|
|
exit;
|
|
|
|
|
} catch (\Throwable $e) {
|
|
|
|
|
$logger->error("Domains export failed", [
|
|
|
|
|
'error' => $e->getMessage(),
|
|
|
|
|
'file' => $e->getFile(),
|
|
|
|
|
'line' => $e->getLine()
|
|
|
|
|
]);
|
|
|
|
|
$_SESSION['error'] = 'Export failed: ' . $e->getMessage();
|
|
|
|
|
$this->redirect('/domains');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Build CSV string in memory from array data
|
|
|
|
|
*/
|
|
|
|
|
private function buildCsv(array $rows, array $headers): string
|
|
|
|
|
{
|
|
|
|
|
$handle = fopen('php://temp', 'r+');
|
|
|
|
|
fputcsv($handle, $headers, ',', '"', '\\');
|
|
|
|
|
foreach ($rows as $row) {
|
|
|
|
|
fputcsv($handle, array_values($row), ',', '"', '\\');
|
|
|
|
|
}
|
|
|
|
|
rewind($handle);
|
|
|
|
|
$csv = stream_get_contents($handle);
|
|
|
|
|
fclose($handle);
|
|
|
|
|
return $csv;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Import domains from CSV or JSON file
|
|
|
|
|
*/
|
|
|
|
|
public function import()
|
|
|
|
|
{
|
|
|
|
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
|
|
|
|
$this->redirect('/domains/bulk-add');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$this->verifyCsrf('/domains/bulk-add');
|
|
|
|
|
|
2026-03-02 11:17:58 +02:00
|
|
|
$logger = new \App\Services\Logger('import');
|
|
|
|
|
$userId = \Core\Auth::id();
|
|
|
|
|
$logger->info('Domains import started', ['user_id' => $userId]);
|
|
|
|
|
|
Add import/export and update system
Implement CSV/JSON import and export for domains, notification groups and tags (with masking for sensitive channel data), including size/format validation, in-memory CSV building, and logging. Add tag transfer and bulk transfer actions (admin-only). Introduce a new update system: Add UpdateController and UpdateService, migration 025_add_update_system_v1.1.3.sql, and installer changes to include the new migration and version handling; provide endpoints to check, apply, rollback and configure updates. Update helpers and UI bits: add getUpdateBadgeInfo in LayoutHelper, update notification icons/redirects, and add getMaxUploadSize in ViewHelper. Misc: add NotificationGroup::findByName, tweak .gitignore backups path, and update related views and routes.
2026-02-11 17:43:23 +02:00
|
|
|
if (!isset($_FILES['import_file']) || $_FILES['import_file']['error'] !== UPLOAD_ERR_OK) {
|
2026-03-02 11:17:58 +02:00
|
|
|
$logger->warning('No valid file uploaded for domains import');
|
Add import/export and update system
Implement CSV/JSON import and export for domains, notification groups and tags (with masking for sensitive channel data), including size/format validation, in-memory CSV building, and logging. Add tag transfer and bulk transfer actions (admin-only). Introduce a new update system: Add UpdateController and UpdateService, migration 025_add_update_system_v1.1.3.sql, and installer changes to include the new migration and version handling; provide endpoints to check, apply, rollback and configure updates. Update helpers and UI bits: add getUpdateBadgeInfo in LayoutHelper, update notification icons/redirects, and add getMaxUploadSize in ViewHelper. Misc: add NotificationGroup::findByName, tweak .gitignore backups path, and update related views and routes.
2026-02-11 17:43:23 +02:00
|
|
|
$_SESSION['error'] = 'Please select a valid file to import';
|
|
|
|
|
$this->redirect('/domains/bulk-add');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$file = $_FILES['import_file'];
|
2026-03-02 11:17:58 +02:00
|
|
|
$logger->info('Import file received', [
|
|
|
|
|
'filename' => $file['name'],
|
|
|
|
|
'size' => $file['size']
|
|
|
|
|
]);
|
Add import/export and update system
Implement CSV/JSON import and export for domains, notification groups and tags (with masking for sensitive channel data), including size/format validation, in-memory CSV building, and logging. Add tag transfer and bulk transfer actions (admin-only). Introduce a new update system: Add UpdateController and UpdateService, migration 025_add_update_system_v1.1.3.sql, and installer changes to include the new migration and version handling; provide endpoints to check, apply, rollback and configure updates. Update helpers and UI bits: add getUpdateBadgeInfo in LayoutHelper, update notification icons/redirects, and add getMaxUploadSize in ViewHelper. Misc: add NotificationGroup::findByName, tweak .gitignore backups path, and update related views and routes.
2026-02-11 17:43:23 +02:00
|
|
|
|
|
|
|
|
// Validate file size (5MB max for domains)
|
|
|
|
|
if ($file['size'] > 5242880) {
|
2026-03-02 11:17:58 +02:00
|
|
|
$logger->warning('Import file too large', ['size' => $file['size']]);
|
Add import/export and update system
Implement CSV/JSON import and export for domains, notification groups and tags (with masking for sensitive channel data), including size/format validation, in-memory CSV building, and logging. Add tag transfer and bulk transfer actions (admin-only). Introduce a new update system: Add UpdateController and UpdateService, migration 025_add_update_system_v1.1.3.sql, and installer changes to include the new migration and version handling; provide endpoints to check, apply, rollback and configure updates. Update helpers and UI bits: add getUpdateBadgeInfo in LayoutHelper, update notification icons/redirects, and add getMaxUploadSize in ViewHelper. Misc: add NotificationGroup::findByName, tweak .gitignore backups path, and update related views and routes.
2026-02-11 17:43:23 +02:00
|
|
|
$_SESSION['error'] = 'File is too large. Maximum size is 5MB';
|
|
|
|
|
$this->redirect('/domains/bulk-add');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
|
|
|
|
|
if (!in_array($ext, ['csv', 'json'])) {
|
2026-03-02 11:17:58 +02:00
|
|
|
$logger->warning('Invalid file type for domains import', ['extension' => $ext]);
|
Add import/export and update system
Implement CSV/JSON import and export for domains, notification groups and tags (with masking for sensitive channel data), including size/format validation, in-memory CSV building, and logging. Add tag transfer and bulk transfer actions (admin-only). Introduce a new update system: Add UpdateController and UpdateService, migration 025_add_update_system_v1.1.3.sql, and installer changes to include the new migration and version handling; provide endpoints to check, apply, rollback and configure updates. Update helpers and UI bits: add getUpdateBadgeInfo in LayoutHelper, update notification icons/redirects, and add getMaxUploadSize in ViewHelper. Misc: add NotificationGroup::findByName, tweak .gitignore backups path, and update related views and routes.
2026-02-11 17:43:23 +02:00
|
|
|
$_SESSION['error'] = 'Invalid file type. Please upload a CSV or JSON file';
|
|
|
|
|
$this->redirect('/domains/bulk-add');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$content = file_get_contents($file['tmp_name']);
|
|
|
|
|
$domainsData = [];
|
|
|
|
|
|
|
|
|
|
if ($ext === 'json') {
|
|
|
|
|
$parsed = json_decode($content, true);
|
|
|
|
|
if (!is_array($parsed)) {
|
2026-03-02 11:17:58 +02:00
|
|
|
$logger->error('Invalid JSON file for domains import');
|
Add import/export and update system
Implement CSV/JSON import and export for domains, notification groups and tags (with masking for sensitive channel data), including size/format validation, in-memory CSV building, and logging. Add tag transfer and bulk transfer actions (admin-only). Introduce a new update system: Add UpdateController and UpdateService, migration 025_add_update_system_v1.1.3.sql, and installer changes to include the new migration and version handling; provide endpoints to check, apply, rollback and configure updates. Update helpers and UI bits: add getUpdateBadgeInfo in LayoutHelper, update notification icons/redirects, and add getMaxUploadSize in ViewHelper. Misc: add NotificationGroup::findByName, tweak .gitignore backups path, and update related views and routes.
2026-02-11 17:43:23 +02:00
|
|
|
$_SESSION['error'] = 'Invalid JSON file';
|
|
|
|
|
$this->redirect('/domains/bulk-add');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
$domainsData = $parsed;
|
|
|
|
|
} else {
|
|
|
|
|
$lines = array_filter(explode("\n", $content));
|
|
|
|
|
$header = null;
|
|
|
|
|
foreach ($lines as $line) {
|
|
|
|
|
$row = str_getcsv(trim($line), ',', '"', '\\');
|
|
|
|
|
if (!$header) {
|
|
|
|
|
$header = array_map('strtolower', array_map('trim', $row));
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
$item = [];
|
|
|
|
|
foreach ($header as $i => $col) {
|
|
|
|
|
$item[$col] = $row[$i] ?? '';
|
|
|
|
|
}
|
|
|
|
|
$domainsData[] = $item;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (empty($domainsData)) {
|
2026-03-02 11:17:58 +02:00
|
|
|
$logger->warning('No domains found in import file');
|
Add import/export and update system
Implement CSV/JSON import and export for domains, notification groups and tags (with masking for sensitive channel data), including size/format validation, in-memory CSV building, and logging. Add tag transfer and bulk transfer actions (admin-only). Introduce a new update system: Add UpdateController and UpdateService, migration 025_add_update_system_v1.1.3.sql, and installer changes to include the new migration and version handling; provide endpoints to check, apply, rollback and configure updates. Update helpers and UI bits: add getUpdateBadgeInfo in LayoutHelper, update notification icons/redirects, and add getMaxUploadSize in ViewHelper. Misc: add NotificationGroup::findByName, tweak .gitignore backups path, and update related views and routes.
2026-02-11 17:43:23 +02:00
|
|
|
$_SESSION['error'] = 'No domains found in file';
|
|
|
|
|
$this->redirect('/domains/bulk-add');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-02 11:17:58 +02:00
|
|
|
$logger->info('Domains data parsed from file', ['entries' => count($domainsData)]);
|
|
|
|
|
|
Add import/export and update system
Implement CSV/JSON import and export for domains, notification groups and tags (with masking for sensitive channel data), including size/format validation, in-memory CSV building, and logging. Add tag transfer and bulk transfer actions (admin-only). Introduce a new update system: Add UpdateController and UpdateService, migration 025_add_update_system_v1.1.3.sql, and installer changes to include the new migration and version handling; provide endpoints to check, apply, rollback and configure updates. Update helpers and UI bits: add getUpdateBadgeInfo in LayoutHelper, update notification icons/redirects, and add getMaxUploadSize in ViewHelper. Misc: add NotificationGroup::findByName, tweak .gitignore backups path, and update related views and routes.
2026-02-11 17:43:23 +02:00
|
|
|
$settingModel = new \App\Models\Setting();
|
|
|
|
|
$isolationMode = $settingModel->getValue('user_isolation_mode', 'shared');
|
|
|
|
|
$tagModel = new \App\Models\Tag();
|
|
|
|
|
|
|
|
|
|
// Form-level notification group
|
|
|
|
|
$formGroupId = (int)($_POST['notification_group_id'] ?? 0);
|
|
|
|
|
|
|
|
|
|
$added = 0;
|
|
|
|
|
$skipped = 0;
|
|
|
|
|
$errors = [];
|
|
|
|
|
|
|
|
|
|
foreach ($domainsData as $row) {
|
|
|
|
|
$domainName = strtolower(trim($row['domain_name'] ?? ''));
|
|
|
|
|
if (empty($domainName)) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Remove protocol/www
|
|
|
|
|
$domainName = preg_replace('#^https?://#', '', $domainName);
|
|
|
|
|
$domainName = preg_replace('#^www\.#', '', $domainName);
|
|
|
|
|
$domainName = rtrim($domainName, '/');
|
|
|
|
|
|
|
|
|
|
if ($this->domainModel->existsByDomain($domainName)) {
|
|
|
|
|
$skipped++;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
// Fetch WHOIS data
|
|
|
|
|
$whoisData = $this->whoisService->getDomainInfo($domainName);
|
|
|
|
|
|
|
|
|
|
if (!$whoisData) {
|
|
|
|
|
$errors[] = $domainName;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$status = $this->whoisService->getDomainStatus(
|
|
|
|
|
$whoisData['expiration_date'] ?? null,
|
|
|
|
|
$whoisData['status'] ?? [],
|
|
|
|
|
$whoisData
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Determine notification group: from file column or form fallback
|
|
|
|
|
$groupId = null;
|
|
|
|
|
$groupName = trim($row['notification_group'] ?? '');
|
|
|
|
|
if (!empty($groupName)) {
|
|
|
|
|
$groupStmt = $this->groupModel->findByName($groupName, $isolationMode === 'isolated' ? $userId : null);
|
|
|
|
|
if ($groupStmt) {
|
|
|
|
|
$groupId = $groupStmt['id'];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (!$groupId && $formGroupId > 0) {
|
|
|
|
|
$groupId = $formGroupId;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$domainId = $this->domainModel->create([
|
|
|
|
|
'domain_name' => $domainName,
|
|
|
|
|
'registrar' => $whoisData['registrar'] ?? null,
|
|
|
|
|
'registrar_url' => $whoisData['registrar_url'] ?? null,
|
|
|
|
|
'expiration_date' => $whoisData['expiration_date'] ?? null,
|
|
|
|
|
'updated_date' => $whoisData['updated_date'] ?? null,
|
|
|
|
|
'abuse_email' => $whoisData['abuse_email'] ?? null,
|
|
|
|
|
'status' => $status,
|
|
|
|
|
'whois_data' => json_encode($whoisData),
|
|
|
|
|
'notes' => trim($row['notes'] ?? ''),
|
|
|
|
|
'last_checked' => date('Y-m-d H:i:s'),
|
|
|
|
|
'notification_group_id' => $groupId,
|
|
|
|
|
'user_id' => $isolationMode === 'isolated' ? $userId : null
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
// Handle tags from file
|
|
|
|
|
$fileTags = trim($row['tags'] ?? '');
|
|
|
|
|
if (!empty($fileTags) && $domainId) {
|
|
|
|
|
$tagModel->updateDomainTags($domainId, $fileTags, $userId);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($domainId) {
|
|
|
|
|
$added++;
|
|
|
|
|
}
|
|
|
|
|
} catch (\Exception $e) {
|
|
|
|
|
$errors[] = $domainName;
|
|
|
|
|
$logger->error('Domain import failed', ['domain' => $domainName, 'error' => $e->getMessage()]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-02 11:17:58 +02:00
|
|
|
$logger->info('Domains import completed', [
|
|
|
|
|
'added' => $added,
|
|
|
|
|
'skipped' => $skipped,
|
|
|
|
|
'failed' => count($errors)
|
|
|
|
|
]);
|
|
|
|
|
|
Add import/export and update system
Implement CSV/JSON import and export for domains, notification groups and tags (with masking for sensitive channel data), including size/format validation, in-memory CSV building, and logging. Add tag transfer and bulk transfer actions (admin-only). Introduce a new update system: Add UpdateController and UpdateService, migration 025_add_update_system_v1.1.3.sql, and installer changes to include the new migration and version handling; provide endpoints to check, apply, rollback and configure updates. Update helpers and UI bits: add getUpdateBadgeInfo in LayoutHelper, update notification icons/redirects, and add getMaxUploadSize in ViewHelper. Misc: add NotificationGroup::findByName, tweak .gitignore backups path, and update related views and routes.
2026-02-11 17:43:23 +02:00
|
|
|
$msg = "{$added} domain(s) imported successfully";
|
|
|
|
|
if ($skipped > 0) $msg .= ", {$skipped} skipped (already exist)";
|
|
|
|
|
if (!empty($errors)) $msg .= ", " . count($errors) . " failed";
|
|
|
|
|
$_SESSION['success'] = $msg;
|
|
|
|
|
$this->redirect('/domains');
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-08 14:23:07 +03:00
|
|
|
public function create()
|
|
|
|
|
{
|
2025-10-20 18:50:32 +03:00
|
|
|
// 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();
|
|
|
|
|
}
|
2025-10-25 02:04:00 +03:00
|
|
|
|
|
|
|
|
// Get available tags for the new tag system
|
|
|
|
|
$tagModel = new \App\Models\Tag();
|
|
|
|
|
if ($isolationMode === 'isolated') {
|
|
|
|
|
$availableTags = $tagModel->getAllWithUsage($userId);
|
|
|
|
|
} else {
|
|
|
|
|
$availableTags = $tagModel->getAllWithUsage();
|
|
|
|
|
}
|
2025-10-08 14:23:07 +03:00
|
|
|
|
|
|
|
|
$this->view('domains/create', [
|
|
|
|
|
'groups' => $groups,
|
2025-10-25 02:04:00 +03:00
|
|
|
'availableTags' => $availableTags,
|
2025-10-08 14:23:07 +03:00
|
|
|
'title' => 'Add Domain'
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function store()
|
|
|
|
|
{
|
|
|
|
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
|
|
|
|
$this->redirect('/domains/create');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
Add CSRF, CAPTCHA, and input validation improvements
Introduces CSRF protection to all sensitive controller actions, integrates configurable CAPTCHA (reCAPTCHA v2/v3, Turnstile) for authentication and registration flows, and centralizes input validation via a new InputValidator helper. Adds new helpers and services for CSRF and CAPTCHA, updates settings and migration for CAPTCHA configuration, and enhances logging and error handling in TLD registry import processes. Also improves validation for user, domain, group, and profile inputs throughout the application.
2025-10-10 00:04:12 +03:00
|
|
|
// CSRF Protection
|
|
|
|
|
$this->verifyCsrf('/domains/create');
|
|
|
|
|
|
2025-10-08 14:23:07 +03:00
|
|
|
$domainName = trim($_POST['domain_name'] ?? '');
|
|
|
|
|
$groupId = !empty($_POST['notification_group_id']) ? (int)$_POST['notification_group_id'] : null;
|
2025-10-12 12:46:16 +03:00
|
|
|
$tagsInput = trim($_POST['tags'] ?? '');
|
2025-10-20 19:05:33 +03:00
|
|
|
$userId = \Core\Auth::id();
|
2025-10-08 14:23:07 +03:00
|
|
|
|
|
|
|
|
// Validate
|
|
|
|
|
if (empty($domainName)) {
|
|
|
|
|
$_SESSION['error'] = 'Domain name is required';
|
|
|
|
|
$this->redirect('/domains/create');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
Add CSRF, CAPTCHA, and input validation improvements
Introduces CSRF protection to all sensitive controller actions, integrates configurable CAPTCHA (reCAPTCHA v2/v3, Turnstile) for authentication and registration flows, and centralizes input validation via a new InputValidator helper. Adds new helpers and services for CSRF and CAPTCHA, updates settings and migration for CAPTCHA configuration, and enhances logging and error handling in TLD registry import processes. Also improves validation for user, domain, group, and profile inputs throughout the application.
2025-10-10 00:04:12 +03:00
|
|
|
// Validate domain format
|
|
|
|
|
if (!\App\Helpers\InputValidator::validateDomain($domainName)) {
|
|
|
|
|
$_SESSION['error'] = 'Invalid domain name format (e.g., example.com)';
|
|
|
|
|
$this->redirect('/domains/create');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-12 12:46:16 +03:00
|
|
|
// Validate tags
|
|
|
|
|
$tagValidation = \App\Helpers\InputValidator::validateTags($tagsInput);
|
|
|
|
|
if (!$tagValidation['valid']) {
|
|
|
|
|
$_SESSION['error'] = $tagValidation['error'];
|
|
|
|
|
$this->redirect('/domains/create');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
$tags = $tagValidation['tags'];
|
|
|
|
|
|
2025-10-20 19:02:56 +03:00
|
|
|
// Validate notification group in isolation mode
|
|
|
|
|
if ($groupId) {
|
|
|
|
|
$settingModel = new \App\Models\Setting();
|
|
|
|
|
$isolationMode = $settingModel->getValue('user_isolation_mode', 'shared');
|
|
|
|
|
|
|
|
|
|
if ($isolationMode === 'isolated') {
|
|
|
|
|
$group = $this->groupModel->find($groupId);
|
|
|
|
|
if (!$group || $group['user_id'] != $userId) {
|
|
|
|
|
$_SESSION['error'] = 'You can only assign domains to your own notification groups';
|
|
|
|
|
$this->redirect('/domains/create');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-08 14:23:07 +03:00
|
|
|
// Check if domain already exists
|
|
|
|
|
if ($this->domainModel->existsByDomain($domainName)) {
|
|
|
|
|
$_SESSION['error'] = 'Domain already exists';
|
|
|
|
|
$this->redirect('/domains/create');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Get WHOIS information
|
|
|
|
|
$whoisData = $this->whoisService->getDomainInfo($domainName);
|
|
|
|
|
|
|
|
|
|
if (!$whoisData) {
|
|
|
|
|
$_SESSION['error'] = 'Could not retrieve WHOIS information for this domain';
|
|
|
|
|
$this->redirect('/domains/create');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Create domain
|
2025-11-18 13:22:49 +02:00
|
|
|
$status = $this->whoisService->getDomainStatus($whoisData['expiration_date'], $whoisData['status'] ?? [], $whoisData);
|
2025-10-08 14:23:07 +03:00
|
|
|
|
|
|
|
|
// Warn if domain is available (not registered)
|
|
|
|
|
if ($status === 'available') {
|
|
|
|
|
$_SESSION['warning'] = "Note: '$domainName' appears to be AVAILABLE (not registered). You're monitoring an unregistered domain.";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$id = $this->domainModel->create([
|
|
|
|
|
'domain_name' => $domainName,
|
|
|
|
|
'notification_group_id' => $groupId,
|
|
|
|
|
'registrar' => $whoisData['registrar'],
|
|
|
|
|
'registrar_url' => $whoisData['registrar_url'] ?? null,
|
|
|
|
|
'expiration_date' => $whoisData['expiration_date'],
|
|
|
|
|
'updated_date' => $whoisData['updated_date'] ?? null,
|
|
|
|
|
'abuse_email' => $whoisData['abuse_email'] ?? null,
|
|
|
|
|
'last_checked' => date('Y-m-d H:i:s'),
|
|
|
|
|
'status' => $status,
|
|
|
|
|
'whois_data' => json_encode($whoisData),
|
2025-10-20 19:02:56 +03:00
|
|
|
'is_active' => 1,
|
|
|
|
|
'user_id' => $userId
|
|
|
|
|
]);
|
|
|
|
|
|
2025-10-25 12:40:47 +03:00
|
|
|
// Handle tags using the new tag system
|
|
|
|
|
if (!empty($tags)) {
|
|
|
|
|
$tagModel = new \App\Models\Tag();
|
|
|
|
|
$tagModel->updateDomainTags($id, $tags, $userId);
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-20 19:02:56 +03:00
|
|
|
// Log domain creation
|
|
|
|
|
$logger = new \App\Services\Logger();
|
|
|
|
|
$logger->info('Domain created', [
|
|
|
|
|
'domain_id' => $id,
|
|
|
|
|
'domain_name' => $domainName,
|
|
|
|
|
'user_id' => $userId,
|
|
|
|
|
'status' => $status,
|
|
|
|
|
'expiration_date' => $whoisData['expiration_date'],
|
|
|
|
|
'notification_group_id' => $groupId
|
2025-10-08 14:23:07 +03:00
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
if ($status !== 'available') {
|
|
|
|
|
$_SESSION['success'] = "Domain '$domainName' added successfully";
|
|
|
|
|
}
|
|
|
|
|
$this->redirect('/domains');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function edit($params = [])
|
|
|
|
|
{
|
|
|
|
|
$id = $params['id'] ?? 0;
|
2025-10-25 02:04:00 +03:00
|
|
|
|
|
|
|
|
// 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);
|
|
|
|
|
}
|
2025-10-08 14:23:07 +03:00
|
|
|
|
|
|
|
|
if (!$domain) {
|
|
|
|
|
$_SESSION['error'] = 'Domain not found';
|
|
|
|
|
$this->redirect('/domains');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-20 18:50:32 +03:00
|
|
|
// Get groups based on isolation mode
|
|
|
|
|
if ($isolationMode === 'isolated') {
|
|
|
|
|
$groups = $this->groupModel->getAllWithChannelCount($userId);
|
|
|
|
|
} else {
|
|
|
|
|
$groups = $this->groupModel->getAllWithChannelCount();
|
|
|
|
|
}
|
2025-10-25 02:04:00 +03:00
|
|
|
|
|
|
|
|
// Get available tags for the new tag system
|
|
|
|
|
$tagModel = new \App\Models\Tag();
|
|
|
|
|
if ($isolationMode === 'isolated') {
|
|
|
|
|
$availableTags = $tagModel->getAllWithUsage($userId);
|
|
|
|
|
} else {
|
|
|
|
|
$availableTags = $tagModel->getAllWithUsage();
|
|
|
|
|
}
|
2025-10-08 14:23:07 +03:00
|
|
|
|
2025-10-25 02:04:00 +03:00
|
|
|
// Get referrer for cancel button
|
|
|
|
|
$referrer = $_GET['from'] ?? '/domains/' . $domain['id'];
|
|
|
|
|
|
2025-10-08 14:23:07 +03:00
|
|
|
$this->view('domains/edit', [
|
|
|
|
|
'domain' => $domain,
|
|
|
|
|
'groups' => $groups,
|
2025-10-25 02:04:00 +03:00
|
|
|
'availableTags' => $availableTags,
|
|
|
|
|
'referrer' => $referrer,
|
2025-10-08 14:23:07 +03:00
|
|
|
'title' => 'Edit Domain'
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function update($params = [])
|
|
|
|
|
{
|
|
|
|
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
|
|
|
|
$this->redirect('/domains');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
Add CSRF, CAPTCHA, and input validation improvements
Introduces CSRF protection to all sensitive controller actions, integrates configurable CAPTCHA (reCAPTCHA v2/v3, Turnstile) for authentication and registration flows, and centralizes input validation via a new InputValidator helper. Adds new helpers and services for CSRF and CAPTCHA, updates settings and migration for CAPTCHA configuration, and enhances logging and error handling in TLD registry import processes. Also improves validation for user, domain, group, and profile inputs throughout the application.
2025-10-10 00:04:12 +03:00
|
|
|
// CSRF Protection
|
|
|
|
|
$this->verifyCsrf('/domains');
|
|
|
|
|
|
2025-10-08 14:23:07 +03:00
|
|
|
$id = (int)($params['id'] ?? 0);
|
2025-10-20 21:08:09 +03:00
|
|
|
$domain = $this->checkDomainAccess($id);
|
2025-10-08 14:23:07 +03:00
|
|
|
|
|
|
|
|
if (!$domain) {
|
|
|
|
|
$_SESSION['error'] = 'Domain not found';
|
|
|
|
|
$this->redirect('/domains');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-22 18:59:05 +03:00
|
|
|
$userId = \Core\Auth::id();
|
|
|
|
|
|
2025-10-08 14:23:07 +03:00
|
|
|
$groupId = !empty($_POST['notification_group_id']) ? (int)$_POST['notification_group_id'] : null;
|
|
|
|
|
$isActive = isset($_POST['is_active']) ? 1 : 0;
|
Add DNS monitoring and refresh functionality
Introduce DNS monitoring: add DnsService (comprehensive DNS lookup, crt.sh discovery, Cloudflare detection, IP enrichment) and a new DnsRecord model to persist snapshots, manage diffs, and provide queries/stats. Update DomainController to support a dns_monitoring_enabled flag, refactor WHOIS/DNS refresh logic into performWhoisRefresh/performDnsRefresh, and add endpoints for refreshWhois, refreshDns and refreshAll; send notifications when DNS monitoring is toggled. Add UI templates/tabs for DNS, billing, notifications, overview, SSL and WHOIS and wire DNS data into the domain view; expose cached IP details. Add cron/check_dns.php and migration 027_add_dns_monitoring.sql (and include it in installer migration lists). Other tweaks: safer EmailHelper subject handling, TldRegistry search improvements, domain sorting using an effective status (expiring_soon), Discord channel null-safe fields, settings UI additions (domain_view_template and cron staleness warnings), and route/migration updates. This enables scheduled and manual DNS scans with persistent records and notifications.
2026-03-08 14:32:05 +02:00
|
|
|
$dnsMonitoringEnabled = isset($_POST['dns_monitoring_enabled']) ? 1 : 0;
|
2025-10-12 12:46:16 +03:00
|
|
|
$tagsInput = trim($_POST['tags'] ?? '');
|
2025-10-21 16:13:58 +03:00
|
|
|
$manualExpirationDate = !empty($_POST['manual_expiration_date']) ? $_POST['manual_expiration_date'] : null;
|
2025-10-12 12:46:16 +03:00
|
|
|
|
|
|
|
|
// Validate tags
|
|
|
|
|
$tagValidation = \App\Helpers\InputValidator::validateTags($tagsInput);
|
|
|
|
|
if (!$tagValidation['valid']) {
|
|
|
|
|
$_SESSION['error'] = $tagValidation['error'];
|
|
|
|
|
$this->redirect('/domains/' . $id . '/edit');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
$tags = $tagValidation['tags'];
|
2025-10-20 19:02:56 +03:00
|
|
|
|
|
|
|
|
// Validate notification group in isolation mode
|
|
|
|
|
if ($groupId) {
|
|
|
|
|
$settingModel = new \App\Models\Setting();
|
|
|
|
|
$isolationMode = $settingModel->getValue('user_isolation_mode', 'shared');
|
|
|
|
|
|
|
|
|
|
if ($isolationMode === 'isolated') {
|
|
|
|
|
$group = $this->groupModel->find($groupId);
|
|
|
|
|
if (!$group || $group['user_id'] != $userId) {
|
|
|
|
|
$_SESSION['error'] = 'You can only assign domains to your own notification groups';
|
|
|
|
|
$this->redirect('/domains/' . $id . '/edit');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-10-08 14:23:07 +03:00
|
|
|
|
|
|
|
|
// Check if monitoring status changed
|
|
|
|
|
$statusChanged = ($domain['is_active'] != $isActive);
|
|
|
|
|
$oldGroupId = $domain['notification_group_id'];
|
|
|
|
|
|
|
|
|
|
$this->domainModel->update($id, [
|
|
|
|
|
'notification_group_id' => $groupId,
|
2025-10-21 16:13:58 +03:00
|
|
|
'is_active' => $isActive,
|
Add DNS monitoring and refresh functionality
Introduce DNS monitoring: add DnsService (comprehensive DNS lookup, crt.sh discovery, Cloudflare detection, IP enrichment) and a new DnsRecord model to persist snapshots, manage diffs, and provide queries/stats. Update DomainController to support a dns_monitoring_enabled flag, refactor WHOIS/DNS refresh logic into performWhoisRefresh/performDnsRefresh, and add endpoints for refreshWhois, refreshDns and refreshAll; send notifications when DNS monitoring is toggled. Add UI templates/tabs for DNS, billing, notifications, overview, SSL and WHOIS and wire DNS data into the domain view; expose cached IP details. Add cron/check_dns.php and migration 027_add_dns_monitoring.sql (and include it in installer migration lists). Other tweaks: safer EmailHelper subject handling, TldRegistry search improvements, domain sorting using an effective status (expiring_soon), Discord channel null-safe fields, settings UI additions (domain_view_template and cron staleness warnings), and route/migration updates. This enables scheduled and manual DNS scans with persistent records and notifications.
2026-03-08 14:32:05 +02:00
|
|
|
'dns_monitoring_enabled' => $dnsMonitoringEnabled,
|
2025-10-21 16:13:58 +03:00
|
|
|
'expiration_date' => $manualExpirationDate
|
2025-10-08 14:23:07 +03:00
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
// Send notification if monitoring status changed and has notification group
|
|
|
|
|
if ($statusChanged && $groupId) {
|
|
|
|
|
$notificationService = new \App\Services\NotificationService();
|
|
|
|
|
|
|
|
|
|
if ($isActive) {
|
|
|
|
|
// Monitoring activated
|
|
|
|
|
$message = "🟢 Domain monitoring has been ACTIVATED for {$domain['domain_name']}\n\n" .
|
|
|
|
|
"The domain will now be monitored regularly and you'll receive expiration alerts.";
|
|
|
|
|
$subject = "✅ Monitoring Activated: {$domain['domain_name']}";
|
|
|
|
|
} else {
|
|
|
|
|
// Monitoring deactivated
|
|
|
|
|
$message = "🔴 Domain monitoring has been DEACTIVATED for {$domain['domain_name']}\n\n" .
|
|
|
|
|
"You will no longer receive alerts for this domain until monitoring is re-enabled.";
|
|
|
|
|
$subject = "⏸️ Monitoring Paused: {$domain['domain_name']}";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$notificationService->sendToGroup($groupId, $subject, $message);
|
|
|
|
|
}
|
Add DNS monitoring and refresh functionality
Introduce DNS monitoring: add DnsService (comprehensive DNS lookup, crt.sh discovery, Cloudflare detection, IP enrichment) and a new DnsRecord model to persist snapshots, manage diffs, and provide queries/stats. Update DomainController to support a dns_monitoring_enabled flag, refactor WHOIS/DNS refresh logic into performWhoisRefresh/performDnsRefresh, and add endpoints for refreshWhois, refreshDns and refreshAll; send notifications when DNS monitoring is toggled. Add UI templates/tabs for DNS, billing, notifications, overview, SSL and WHOIS and wire DNS data into the domain view; expose cached IP details. Add cron/check_dns.php and migration 027_add_dns_monitoring.sql (and include it in installer migration lists). Other tweaks: safer EmailHelper subject handling, TldRegistry search improvements, domain sorting using an effective status (expiring_soon), Discord channel null-safe fields, settings UI additions (domain_view_template and cron staleness warnings), and route/migration updates. This enables scheduled and manual DNS scans with persistent records and notifications.
2026-03-08 14:32:05 +02:00
|
|
|
|
|
|
|
|
// Send notification if DNS monitoring changed and has notification group
|
|
|
|
|
$dnsMonitoringChanged = (($domain['dns_monitoring_enabled'] ?? 1) != $dnsMonitoringEnabled);
|
|
|
|
|
if ($dnsMonitoringChanged && $groupId) {
|
|
|
|
|
$notificationService = new \App\Services\NotificationService();
|
|
|
|
|
|
|
|
|
|
if ($dnsMonitoringEnabled) {
|
|
|
|
|
$message = "🟢 DNS monitoring has been ENABLED for {$domain['domain_name']}\n\n" .
|
|
|
|
|
"DNS records will be checked for changes and you'll receive alerts when they change.";
|
|
|
|
|
$subject = "✅ DNS Monitoring Enabled: {$domain['domain_name']}";
|
|
|
|
|
} else {
|
|
|
|
|
$message = "🔴 DNS monitoring has been DISABLED for {$domain['domain_name']}\n\n" .
|
|
|
|
|
"DNS records will no longer be checked. You will not receive DNS change alerts.";
|
|
|
|
|
$subject = "⏸️ DNS Monitoring Disabled: {$domain['domain_name']}";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$notificationService->sendToGroup($groupId, $subject, $message);
|
|
|
|
|
}
|
2025-10-08 14:23:07 +03:00
|
|
|
|
|
|
|
|
// Also send notification if group changed and monitoring is active
|
|
|
|
|
if (!$statusChanged && $isActive && $oldGroupId != $groupId) {
|
|
|
|
|
$notificationService = new \App\Services\NotificationService();
|
|
|
|
|
|
|
|
|
|
if ($groupId) {
|
|
|
|
|
// Assigned to new group
|
|
|
|
|
$groupModel = new NotificationGroup();
|
|
|
|
|
$group = $groupModel->find($groupId);
|
|
|
|
|
$groupName = $group ? $group['name'] : 'Unknown Group';
|
|
|
|
|
|
|
|
|
|
$message = "🔔 Notification group updated for {$domain['domain_name']}\n\n" .
|
|
|
|
|
"This domain is now assigned to: {$groupName}\n" .
|
|
|
|
|
"You will receive expiration alerts through this notification group.";
|
|
|
|
|
$subject = "📬 Group Changed: {$domain['domain_name']}";
|
|
|
|
|
|
|
|
|
|
$notificationService->sendToGroup($groupId, $subject, $message);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-25 02:04:00 +03:00
|
|
|
// 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);
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-08 14:23:07 +03:00
|
|
|
$_SESSION['success'] = 'Domain updated successfully';
|
|
|
|
|
$this->redirect('/domains/' . $id);
|
|
|
|
|
}
|
|
|
|
|
|
Add DNS monitoring and refresh functionality
Introduce DNS monitoring: add DnsService (comprehensive DNS lookup, crt.sh discovery, Cloudflare detection, IP enrichment) and a new DnsRecord model to persist snapshots, manage diffs, and provide queries/stats. Update DomainController to support a dns_monitoring_enabled flag, refactor WHOIS/DNS refresh logic into performWhoisRefresh/performDnsRefresh, and add endpoints for refreshWhois, refreshDns and refreshAll; send notifications when DNS monitoring is toggled. Add UI templates/tabs for DNS, billing, notifications, overview, SSL and WHOIS and wire DNS data into the domain view; expose cached IP details. Add cron/check_dns.php and migration 027_add_dns_monitoring.sql (and include it in installer migration lists). Other tweaks: safer EmailHelper subject handling, TldRegistry search improvements, domain sorting using an effective status (expiring_soon), Discord channel null-safe fields, settings UI additions (domain_view_template and cron staleness warnings), and route/migration updates. This enables scheduled and manual DNS scans with persistent records and notifications.
2026-03-08 14:32:05 +02:00
|
|
|
/**
|
|
|
|
|
* Perform WHOIS lookup and persist results.
|
|
|
|
|
* @return string|null Status message on success, null on failure.
|
|
|
|
|
*/
|
|
|
|
|
private function performWhoisRefresh(int $id, array $domain): ?string
|
2025-10-08 14:23:07 +03:00
|
|
|
{
|
2025-10-21 15:16:56 +03:00
|
|
|
$logger = new \App\Services\Logger();
|
|
|
|
|
|
2025-10-08 14:23:07 +03:00
|
|
|
$whoisData = $this->whoisService->getDomainInfo($domain['domain_name']);
|
|
|
|
|
|
|
|
|
|
if (!$whoisData) {
|
Add DNS monitoring and refresh functionality
Introduce DNS monitoring: add DnsService (comprehensive DNS lookup, crt.sh discovery, Cloudflare detection, IP enrichment) and a new DnsRecord model to persist snapshots, manage diffs, and provide queries/stats. Update DomainController to support a dns_monitoring_enabled flag, refactor WHOIS/DNS refresh logic into performWhoisRefresh/performDnsRefresh, and add endpoints for refreshWhois, refreshDns and refreshAll; send notifications when DNS monitoring is toggled. Add UI templates/tabs for DNS, billing, notifications, overview, SSL and WHOIS and wire DNS data into the domain view; expose cached IP details. Add cron/check_dns.php and migration 027_add_dns_monitoring.sql (and include it in installer migration lists). Other tweaks: safer EmailHelper subject handling, TldRegistry search improvements, domain sorting using an effective status (expiring_soon), Discord channel null-safe fields, settings UI additions (domain_view_template and cron staleness warnings), and route/migration updates. This enables scheduled and manual DNS scans with persistent records and notifications.
2026-03-08 14:32:05 +02:00
|
|
|
$logger->error('WHOIS refresh failed', [
|
2025-10-21 15:16:56 +03:00
|
|
|
'domain_id' => $id,
|
|
|
|
|
'domain_name' => $domain['domain_name'],
|
|
|
|
|
'user_id' => \Core\Auth::id()
|
|
|
|
|
]);
|
Add DNS monitoring and refresh functionality
Introduce DNS monitoring: add DnsService (comprehensive DNS lookup, crt.sh discovery, Cloudflare detection, IP enrichment) and a new DnsRecord model to persist snapshots, manage diffs, and provide queries/stats. Update DomainController to support a dns_monitoring_enabled flag, refactor WHOIS/DNS refresh logic into performWhoisRefresh/performDnsRefresh, and add endpoints for refreshWhois, refreshDns and refreshAll; send notifications when DNS monitoring is toggled. Add UI templates/tabs for DNS, billing, notifications, overview, SSL and WHOIS and wire DNS data into the domain view; expose cached IP details. Add cron/check_dns.php and migration 027_add_dns_monitoring.sql (and include it in installer migration lists). Other tweaks: safer EmailHelper subject handling, TldRegistry search improvements, domain sorting using an effective status (expiring_soon), Discord channel null-safe fields, settings UI additions (domain_view_template and cron staleness warnings), and route/migration updates. This enables scheduled and manual DNS scans with persistent records and notifications.
2026-03-08 14:32:05 +02:00
|
|
|
return null;
|
2025-10-08 14:23:07 +03:00
|
|
|
}
|
|
|
|
|
|
2025-10-21 16:13:58 +03:00
|
|
|
$expirationDate = $whoisData['expiration_date'] ?? $domain['expiration_date'];
|
2025-11-18 13:22:49 +02:00
|
|
|
$status = $this->whoisService->getDomainStatus($expirationDate, $whoisData['status'] ?? [], $whoisData);
|
2025-10-08 14:23:07 +03:00
|
|
|
|
|
|
|
|
$this->domainModel->update($id, [
|
|
|
|
|
'registrar' => $whoisData['registrar'],
|
|
|
|
|
'registrar_url' => $whoisData['registrar_url'] ?? null,
|
2025-10-21 16:13:58 +03:00
|
|
|
'expiration_date' => $expirationDate,
|
2025-10-08 14:23:07 +03:00
|
|
|
'updated_date' => $whoisData['updated_date'] ?? null,
|
|
|
|
|
'abuse_email' => $whoisData['abuse_email'] ?? null,
|
|
|
|
|
'last_checked' => date('Y-m-d H:i:s'),
|
|
|
|
|
'status' => $status,
|
|
|
|
|
'whois_data' => json_encode($whoisData)
|
|
|
|
|
]);
|
|
|
|
|
|
Add DNS monitoring and refresh functionality
Introduce DNS monitoring: add DnsService (comprehensive DNS lookup, crt.sh discovery, Cloudflare detection, IP enrichment) and a new DnsRecord model to persist snapshots, manage diffs, and provide queries/stats. Update DomainController to support a dns_monitoring_enabled flag, refactor WHOIS/DNS refresh logic into performWhoisRefresh/performDnsRefresh, and add endpoints for refreshWhois, refreshDns and refreshAll; send notifications when DNS monitoring is toggled. Add UI templates/tabs for DNS, billing, notifications, overview, SSL and WHOIS and wire DNS data into the domain view; expose cached IP details. Add cron/check_dns.php and migration 027_add_dns_monitoring.sql (and include it in installer migration lists). Other tweaks: safer EmailHelper subject handling, TldRegistry search improvements, domain sorting using an effective status (expiring_soon), Discord channel null-safe fields, settings UI additions (domain_view_template and cron staleness warnings), and route/migration updates. This enables scheduled and manual DNS scans with persistent records and notifications.
2026-03-08 14:32:05 +02:00
|
|
|
$logger->info('WHOIS refresh completed', [
|
2025-10-21 15:16:56 +03:00
|
|
|
'domain_id' => $id,
|
|
|
|
|
'domain_name' => $domain['domain_name'],
|
|
|
|
|
'new_status' => $status,
|
|
|
|
|
'registrar' => $whoisData['registrar'],
|
|
|
|
|
'expiration_date' => $whoisData['expiration_date'],
|
|
|
|
|
'user_id' => \Core\Auth::id()
|
|
|
|
|
]);
|
|
|
|
|
|
Add DNS monitoring and refresh functionality
Introduce DNS monitoring: add DnsService (comprehensive DNS lookup, crt.sh discovery, Cloudflare detection, IP enrichment) and a new DnsRecord model to persist snapshots, manage diffs, and provide queries/stats. Update DomainController to support a dns_monitoring_enabled flag, refactor WHOIS/DNS refresh logic into performWhoisRefresh/performDnsRefresh, and add endpoints for refreshWhois, refreshDns and refreshAll; send notifications when DNS monitoring is toggled. Add UI templates/tabs for DNS, billing, notifications, overview, SSL and WHOIS and wire DNS data into the domain view; expose cached IP details. Add cron/check_dns.php and migration 027_add_dns_monitoring.sql (and include it in installer migration lists). Other tweaks: safer EmailHelper subject handling, TldRegistry search improvements, domain sorting using an effective status (expiring_soon), Discord channel null-safe fields, settings UI additions (domain_view_template and cron staleness warnings), and route/migration updates. This enables scheduled and manual DNS scans with persistent records and notifications.
2026-03-08 14:32:05 +02:00
|
|
|
return 'WHOIS updated';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Perform DNS lookup and persist results.
|
|
|
|
|
* @return string Status message (always returns, even on zero records).
|
|
|
|
|
*/
|
|
|
|
|
private function performDnsRefresh(int $id, array $domain): string
|
|
|
|
|
{
|
|
|
|
|
$logger = new \App\Services\Logger('dns');
|
|
|
|
|
|
|
|
|
|
$dnsService = new \App\Services\DnsService();
|
|
|
|
|
$dnsModel = new \App\Models\DnsRecord();
|
|
|
|
|
|
|
|
|
|
// Feed previously known hosts so manual refresh doesn't lose crt.sh-discovered subdomains
|
|
|
|
|
$existingHosts = $dnsModel->getDistinctHosts($id);
|
|
|
|
|
$records = $dnsService->lookup($domain['domain_name'], $existingHosts);
|
|
|
|
|
$totalRecords = array_sum(array_map('count', $records));
|
|
|
|
|
|
|
|
|
|
if ($totalRecords === 0) {
|
|
|
|
|
$logger->warning('DNS refresh returned no records', [
|
|
|
|
|
'domain_name' => $domain['domain_name'],
|
|
|
|
|
]);
|
|
|
|
|
return 'DNS: no records found';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Enrich A/AAAA records with IP details (PTR, ASN, geo) and store in raw_data
|
|
|
|
|
$ips = [];
|
|
|
|
|
foreach (['A', 'AAAA'] as $type) {
|
|
|
|
|
if (!empty($records[$type])) {
|
|
|
|
|
foreach ($records[$type] as $r) {
|
|
|
|
|
if (!empty($r['value'])) {
|
|
|
|
|
$ips[] = $r['value'];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (!empty($ips)) {
|
|
|
|
|
$ipDetails = $dnsService->lookupIpDetails($ips);
|
|
|
|
|
foreach (['A', 'AAAA'] as $type) {
|
|
|
|
|
if (!empty($records[$type])) {
|
|
|
|
|
foreach ($records[$type] as &$rec) {
|
|
|
|
|
if (!empty($rec['value']) && isset($ipDetails[$rec['value']])) {
|
|
|
|
|
$rec['raw']['_ip_info'] = $ipDetails[$rec['value']];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
unset($rec);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$stats = $dnsModel->saveSnapshot($id, $records);
|
|
|
|
|
$this->domainModel->update($id, ['dns_last_checked' => date('Y-m-d H:i:s')]);
|
|
|
|
|
|
|
|
|
|
$logger->info('DNS refresh completed', [
|
|
|
|
|
'domain_name' => $domain['domain_name'],
|
|
|
|
|
'total' => $totalRecords,
|
|
|
|
|
'added' => $stats['added'],
|
|
|
|
|
'updated' => $stats['updated'],
|
|
|
|
|
'removed' => $stats['removed'],
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
return "DNS updated ({$totalRecords} records)";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Redirect back to the originating page (domain view or list).
|
|
|
|
|
*/
|
|
|
|
|
private function redirectBackToDomain(int $id, string $hash = ''): void
|
|
|
|
|
{
|
2025-10-08 14:23:07 +03:00
|
|
|
$referer = $_SERVER['HTTP_REFERER'] ?? '';
|
|
|
|
|
if (strpos($referer, '/domains/' . $id) !== false) {
|
Add DNS monitoring and refresh functionality
Introduce DNS monitoring: add DnsService (comprehensive DNS lookup, crt.sh discovery, Cloudflare detection, IP enrichment) and a new DnsRecord model to persist snapshots, manage diffs, and provide queries/stats. Update DomainController to support a dns_monitoring_enabled flag, refactor WHOIS/DNS refresh logic into performWhoisRefresh/performDnsRefresh, and add endpoints for refreshWhois, refreshDns and refreshAll; send notifications when DNS monitoring is toggled. Add UI templates/tabs for DNS, billing, notifications, overview, SSL and WHOIS and wire DNS data into the domain view; expose cached IP details. Add cron/check_dns.php and migration 027_add_dns_monitoring.sql (and include it in installer migration lists). Other tweaks: safer EmailHelper subject handling, TldRegistry search improvements, domain sorting using an effective status (expiring_soon), Discord channel null-safe fields, settings UI additions (domain_view_template and cron staleness warnings), and route/migration updates. This enables scheduled and manual DNS scans with persistent records and notifications.
2026-03-08 14:32:05 +02:00
|
|
|
$this->redirect('/domains/' . $id . $hash);
|
|
|
|
|
} else {
|
|
|
|
|
$this->redirect('/domains');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function refreshWhois($params = [])
|
|
|
|
|
{
|
|
|
|
|
$id = (int)($params['id'] ?? 0);
|
|
|
|
|
$domain = $this->checkDomainAccess($id);
|
|
|
|
|
|
|
|
|
|
if (!$domain) {
|
|
|
|
|
$_SESSION['error'] = 'Domain not found';
|
|
|
|
|
$this->redirect('/domains');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$result = $this->performWhoisRefresh($id, $domain);
|
|
|
|
|
|
|
|
|
|
if ($result === null) {
|
|
|
|
|
$_SESSION['error'] = 'Could not retrieve WHOIS information';
|
2025-10-08 14:23:07 +03:00
|
|
|
} else {
|
Add DNS monitoring and refresh functionality
Introduce DNS monitoring: add DnsService (comprehensive DNS lookup, crt.sh discovery, Cloudflare detection, IP enrichment) and a new DnsRecord model to persist snapshots, manage diffs, and provide queries/stats. Update DomainController to support a dns_monitoring_enabled flag, refactor WHOIS/DNS refresh logic into performWhoisRefresh/performDnsRefresh, and add endpoints for refreshWhois, refreshDns and refreshAll; send notifications when DNS monitoring is toggled. Add UI templates/tabs for DNS, billing, notifications, overview, SSL and WHOIS and wire DNS data into the domain view; expose cached IP details. Add cron/check_dns.php and migration 027_add_dns_monitoring.sql (and include it in installer migration lists). Other tweaks: safer EmailHelper subject handling, TldRegistry search improvements, domain sorting using an effective status (expiring_soon), Discord channel null-safe fields, settings UI additions (domain_view_template and cron staleness warnings), and route/migration updates. This enables scheduled and manual DNS scans with persistent records and notifications.
2026-03-08 14:32:05 +02:00
|
|
|
$_SESSION['success'] = 'WHOIS information refreshed';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$this->redirectBackToDomain($id);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function refreshAll($params = [])
|
|
|
|
|
{
|
|
|
|
|
$id = (int)($params['id'] ?? 0);
|
|
|
|
|
$domain = $this->checkDomainAccess($id);
|
|
|
|
|
|
|
|
|
|
if (!$domain) {
|
|
|
|
|
$_SESSION['error'] = 'Domain not found';
|
2025-10-08 14:23:07 +03:00
|
|
|
$this->redirect('/domains');
|
Add DNS monitoring and refresh functionality
Introduce DNS monitoring: add DnsService (comprehensive DNS lookup, crt.sh discovery, Cloudflare detection, IP enrichment) and a new DnsRecord model to persist snapshots, manage diffs, and provide queries/stats. Update DomainController to support a dns_monitoring_enabled flag, refactor WHOIS/DNS refresh logic into performWhoisRefresh/performDnsRefresh, and add endpoints for refreshWhois, refreshDns and refreshAll; send notifications when DNS monitoring is toggled. Add UI templates/tabs for DNS, billing, notifications, overview, SSL and WHOIS and wire DNS data into the domain view; expose cached IP details. Add cron/check_dns.php and migration 027_add_dns_monitoring.sql (and include it in installer migration lists). Other tweaks: safer EmailHelper subject handling, TldRegistry search improvements, domain sorting using an effective status (expiring_soon), Discord channel null-safe fields, settings UI additions (domain_view_template and cron staleness warnings), and route/migration updates. This enables scheduled and manual DNS scans with persistent records and notifications.
2026-03-08 14:32:05 +02:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$messages = [];
|
|
|
|
|
$messages[] = $this->performWhoisRefresh($id, $domain) ?? 'WHOIS failed';
|
|
|
|
|
if (!empty($domain['dns_monitoring_enabled'])) {
|
|
|
|
|
$messages[] = $this->performDnsRefresh($id, $domain);
|
|
|
|
|
} else {
|
|
|
|
|
$messages[] = 'DNS skipped (monitoring disabled)';
|
2025-10-08 14:23:07 +03:00
|
|
|
}
|
Add DNS monitoring and refresh functionality
Introduce DNS monitoring: add DnsService (comprehensive DNS lookup, crt.sh discovery, Cloudflare detection, IP enrichment) and a new DnsRecord model to persist snapshots, manage diffs, and provide queries/stats. Update DomainController to support a dns_monitoring_enabled flag, refactor WHOIS/DNS refresh logic into performWhoisRefresh/performDnsRefresh, and add endpoints for refreshWhois, refreshDns and refreshAll; send notifications when DNS monitoring is toggled. Add UI templates/tabs for DNS, billing, notifications, overview, SSL and WHOIS and wire DNS data into the domain view; expose cached IP details. Add cron/check_dns.php and migration 027_add_dns_monitoring.sql (and include it in installer migration lists). Other tweaks: safer EmailHelper subject handling, TldRegistry search improvements, domain sorting using an effective status (expiring_soon), Discord channel null-safe fields, settings UI additions (domain_view_template and cron staleness warnings), and route/migration updates. This enables scheduled and manual DNS scans with persistent records and notifications.
2026-03-08 14:32:05 +02:00
|
|
|
|
|
|
|
|
$_SESSION['success'] = 'Domain refreshed: ' . implode(', ', $messages);
|
|
|
|
|
$this->redirectBackToDomain($id);
|
2025-10-08 14:23:07 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function delete($params = [])
|
|
|
|
|
{
|
|
|
|
|
$id = $params['id'] ?? 0;
|
2025-10-20 21:08:09 +03:00
|
|
|
$domain = $this->checkDomainAccess($id);
|
2025-10-08 14:23:07 +03:00
|
|
|
|
|
|
|
|
if (!$domain) {
|
|
|
|
|
$_SESSION['error'] = 'Domain not found';
|
|
|
|
|
$this->redirect('/domains');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$this->domainModel->delete($id);
|
|
|
|
|
$_SESSION['success'] = 'Domain deleted successfully';
|
|
|
|
|
$this->redirect('/domains');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function show($params = [])
|
|
|
|
|
{
|
|
|
|
|
$id = $params['id'] ?? 0;
|
2025-10-20 21:08:09 +03:00
|
|
|
|
|
|
|
|
// Get current user and isolation mode
|
|
|
|
|
$userId = \Core\Auth::id();
|
|
|
|
|
$settingModel = new \App\Models\Setting();
|
|
|
|
|
$isolationMode = $settingModel->getValue('user_isolation_mode', 'shared');
|
|
|
|
|
|
2025-10-25 02:04:00 +03:00
|
|
|
// Get domain with tags and groups
|
2025-10-20 21:08:09 +03:00
|
|
|
if ($isolationMode === 'isolated') {
|
2025-10-25 02:04:00 +03:00
|
|
|
$domain = $this->domainModel->getWithTagsAndGroups($id, $userId);
|
2025-10-20 21:08:09 +03:00
|
|
|
} else {
|
2025-10-25 02:04:00 +03:00
|
|
|
$domain = $this->domainModel->getWithTagsAndGroups($id);
|
2025-10-20 21:08:09 +03:00
|
|
|
}
|
2025-10-08 14:23:07 +03:00
|
|
|
|
|
|
|
|
if (!$domain) {
|
2026-02-09 00:20:17 +02:00
|
|
|
$_SESSION['error'] = 'You do not have permission to view this domain.';
|
2025-10-08 14:23:07 +03:00
|
|
|
$this->redirect('/domains');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$logModel = new \App\Models\NotificationLog();
|
|
|
|
|
$logs = $logModel->getByDomain($id, 20);
|
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
|
|
|
|
|
|
|
|
// Format domain for display
|
|
|
|
|
$formattedDomain = \App\Helpers\DomainHelper::formatForDisplay($domain);
|
|
|
|
|
|
|
|
|
|
// Parse WHOIS data for display
|
|
|
|
|
$whoisData = json_decode($domain['whois_data'] ?? '{}', true);
|
|
|
|
|
if (!empty($whoisData['status']) && is_array($whoisData['status'])) {
|
|
|
|
|
$formattedDomain['parsedStatuses'] = \App\Helpers\DomainHelper::parseWhoisStatuses($whoisData['status']);
|
|
|
|
|
} else {
|
|
|
|
|
$formattedDomain['parsedStatuses'] = [];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Calculate active channel count
|
|
|
|
|
if (!empty($domain['channels'])) {
|
|
|
|
|
$formattedDomain['activeChannelCount'] = \App\Helpers\DomainHelper::getActiveChannelCount($domain['channels']);
|
|
|
|
|
}
|
2025-10-25 02:04:00 +03:00
|
|
|
|
|
|
|
|
// Get available tags for the new tag system
|
|
|
|
|
$tagModel = new \App\Models\Tag();
|
|
|
|
|
if ($isolationMode === 'isolated') {
|
|
|
|
|
$availableTags = $tagModel->getAllWithUsage($userId);
|
|
|
|
|
} else {
|
|
|
|
|
$availableTags = $tagModel->getAllWithUsage();
|
|
|
|
|
}
|
2025-10-08 14:23:07 +03:00
|
|
|
|
Add DNS monitoring and refresh functionality
Introduce DNS monitoring: add DnsService (comprehensive DNS lookup, crt.sh discovery, Cloudflare detection, IP enrichment) and a new DnsRecord model to persist snapshots, manage diffs, and provide queries/stats. Update DomainController to support a dns_monitoring_enabled flag, refactor WHOIS/DNS refresh logic into performWhoisRefresh/performDnsRefresh, and add endpoints for refreshWhois, refreshDns and refreshAll; send notifications when DNS monitoring is toggled. Add UI templates/tabs for DNS, billing, notifications, overview, SSL and WHOIS and wire DNS data into the domain view; expose cached IP details. Add cron/check_dns.php and migration 027_add_dns_monitoring.sql (and include it in installer migration lists). Other tweaks: safer EmailHelper subject handling, TldRegistry search improvements, domain sorting using an effective status (expiring_soon), Discord channel null-safe fields, settings UI additions (domain_view_template and cron staleness warnings), and route/migration updates. This enables scheduled and manual DNS scans with persistent records and notifications.
2026-03-08 14:32:05 +02:00
|
|
|
// Get DNS records for the DNS tab
|
|
|
|
|
$dnsModel = new \App\Models\DnsRecord();
|
|
|
|
|
$dnsRecords = $dnsModel->getByDomainGrouped($id);
|
|
|
|
|
$dnsRecordCount = $dnsModel->countByDomain($id);
|
|
|
|
|
$dnsHasCloudflare = $dnsModel->hasCloudflare($id);
|
|
|
|
|
|
|
|
|
|
// Extract cached IP details (PTR, ASN, geo) from stored raw_data
|
|
|
|
|
$dnsIpDetails = [];
|
|
|
|
|
foreach (['A', 'AAAA'] as $type) {
|
|
|
|
|
if (!empty($dnsRecords[$type])) {
|
|
|
|
|
foreach ($dnsRecords[$type] as $r) {
|
|
|
|
|
if (!empty($r['raw_data']) && !empty($r['value'])) {
|
|
|
|
|
$raw = json_decode($r['raw_data'], true);
|
|
|
|
|
if (!empty($raw['_ip_info'])) {
|
|
|
|
|
$dnsIpDetails[$r['value']] = $raw['_ip_info'];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$viewTemplate = $settingModel->getValue('domain_view_template', 'detailed');
|
|
|
|
|
$templateName = $viewTemplate === 'detailed' ? 'domains/view-detailed' : 'domains/view';
|
|
|
|
|
|
|
|
|
|
$this->view($templateName, [
|
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
|
|
|
'domain' => $formattedDomain,
|
2026-03-04 11:16:56 +02:00
|
|
|
'whoisData' => $whoisData,
|
2025-10-08 14:23:07 +03:00
|
|
|
'logs' => $logs,
|
2025-10-25 02:04:00 +03:00
|
|
|
'availableTags' => $availableTags,
|
Add DNS monitoring and refresh functionality
Introduce DNS monitoring: add DnsService (comprehensive DNS lookup, crt.sh discovery, Cloudflare detection, IP enrichment) and a new DnsRecord model to persist snapshots, manage diffs, and provide queries/stats. Update DomainController to support a dns_monitoring_enabled flag, refactor WHOIS/DNS refresh logic into performWhoisRefresh/performDnsRefresh, and add endpoints for refreshWhois, refreshDns and refreshAll; send notifications when DNS monitoring is toggled. Add UI templates/tabs for DNS, billing, notifications, overview, SSL and WHOIS and wire DNS data into the domain view; expose cached IP details. Add cron/check_dns.php and migration 027_add_dns_monitoring.sql (and include it in installer migration lists). Other tweaks: safer EmailHelper subject handling, TldRegistry search improvements, domain sorting using an effective status (expiring_soon), Discord channel null-safe fields, settings UI additions (domain_view_template and cron staleness warnings), and route/migration updates. This enables scheduled and manual DNS scans with persistent records and notifications.
2026-03-08 14:32:05 +02:00
|
|
|
'dnsRecords' => $dnsRecords,
|
|
|
|
|
'dnsRecordCount' => $dnsRecordCount,
|
|
|
|
|
'dnsHasCloudflare' => $dnsHasCloudflare,
|
|
|
|
|
'dnsIpDetails' => $dnsIpDetails,
|
2025-10-08 14:23:07 +03:00
|
|
|
'title' => $domain['domain_name']
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function bulkAdd()
|
|
|
|
|
{
|
|
|
|
|
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
|
2025-10-20 18:50:32 +03:00
|
|
|
// 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();
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-25 02:04:00 +03:00
|
|
|
// Get available tags for the new tag system
|
|
|
|
|
$tagModel = new \App\Models\Tag();
|
|
|
|
|
if ($isolationMode === 'isolated') {
|
|
|
|
|
$availableTags = $tagModel->getAllWithUsage($userId);
|
|
|
|
|
} else {
|
|
|
|
|
$availableTags = $tagModel->getAllWithUsage();
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-08 14:23:07 +03:00
|
|
|
$this->view('domains/bulk-add', [
|
|
|
|
|
'groups' => $groups,
|
2025-10-25 02:04:00 +03:00
|
|
|
'availableTags' => $availableTags,
|
2025-10-08 14:23:07 +03:00
|
|
|
'title' => 'Bulk Add Domains'
|
|
|
|
|
]);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
Add CSRF, CAPTCHA, and input validation improvements
Introduces CSRF protection to all sensitive controller actions, integrates configurable CAPTCHA (reCAPTCHA v2/v3, Turnstile) for authentication and registration flows, and centralizes input validation via a new InputValidator helper. Adds new helpers and services for CSRF and CAPTCHA, updates settings and migration for CAPTCHA configuration, and enhances logging and error handling in TLD registry import processes. Also improves validation for user, domain, group, and profile inputs throughout the application.
2025-10-10 00:04:12 +03:00
|
|
|
// CSRF Protection
|
|
|
|
|
$this->verifyCsrf('/domains/bulk-add');
|
|
|
|
|
|
2025-10-08 14:23:07 +03:00
|
|
|
// POST - Process bulk add
|
|
|
|
|
$domainsText = trim($_POST['domains'] ?? '');
|
|
|
|
|
$groupId = !empty($_POST['notification_group_id']) ? (int)$_POST['notification_group_id'] : null;
|
2025-10-12 12:46:16 +03:00
|
|
|
$tagsInput = trim($_POST['tags'] ?? '');
|
2025-10-20 19:05:33 +03:00
|
|
|
$userId = \Core\Auth::id();
|
2025-10-08 14:23:07 +03:00
|
|
|
|
|
|
|
|
if (empty($domainsText)) {
|
|
|
|
|
$_SESSION['error'] = 'Please enter at least one domain';
|
|
|
|
|
$this->redirect('/domains/bulk-add');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-12 12:46:16 +03:00
|
|
|
// Validate tags
|
|
|
|
|
$tagValidation = \App\Helpers\InputValidator::validateTags($tagsInput);
|
|
|
|
|
if (!$tagValidation['valid']) {
|
|
|
|
|
$_SESSION['error'] = $tagValidation['error'];
|
|
|
|
|
$this->redirect('/domains/bulk-add');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
$tags = $tagValidation['tags'];
|
|
|
|
|
|
2025-10-20 19:02:56 +03:00
|
|
|
// Validate notification group in isolation mode
|
|
|
|
|
if ($groupId) {
|
|
|
|
|
$settingModel = new \App\Models\Setting();
|
|
|
|
|
$isolationMode = $settingModel->getValue('user_isolation_mode', 'shared');
|
|
|
|
|
|
|
|
|
|
if ($isolationMode === 'isolated') {
|
|
|
|
|
$group = $this->groupModel->find($groupId);
|
|
|
|
|
if (!$group || $group['user_id'] != $userId) {
|
|
|
|
|
$_SESSION['error'] = 'You can only assign domains to your own notification groups';
|
|
|
|
|
$this->redirect('/domains/bulk-add');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-08 14:23:07 +03:00
|
|
|
// Split by new lines and clean
|
|
|
|
|
$domainNames = array_filter(array_map('trim', explode("\n", $domainsText)));
|
|
|
|
|
|
|
|
|
|
$added = 0;
|
|
|
|
|
$skipped = 0;
|
|
|
|
|
$availableCount = 0;
|
|
|
|
|
$errors = [];
|
2025-10-20 19:02:56 +03:00
|
|
|
$userId = \Core\Auth::id();
|
|
|
|
|
|
|
|
|
|
// Log bulk add start
|
|
|
|
|
$logger = new \App\Services\Logger();
|
|
|
|
|
$logger->info('Bulk domain add started', [
|
|
|
|
|
'user_id' => $userId,
|
|
|
|
|
'domain_count' => count($domainNames),
|
|
|
|
|
'notification_group_id' => $groupId,
|
|
|
|
|
'tags' => $tags
|
|
|
|
|
]);
|
2025-10-08 14:23:07 +03:00
|
|
|
|
|
|
|
|
foreach ($domainNames as $domainName) {
|
|
|
|
|
// Skip if already exists
|
|
|
|
|
if ($this->domainModel->existsByDomain($domainName)) {
|
|
|
|
|
$skipped++;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Get WHOIS information
|
|
|
|
|
$whoisData = $this->whoisService->getDomainInfo($domainName);
|
|
|
|
|
|
|
|
|
|
if (!$whoisData) {
|
|
|
|
|
$errors[] = $domainName;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-18 13:22:49 +02:00
|
|
|
$status = $this->whoisService->getDomainStatus($whoisData['expiration_date'], $whoisData['status'] ?? [], $whoisData);
|
2025-10-08 14:23:07 +03:00
|
|
|
|
|
|
|
|
// Track available domains
|
|
|
|
|
if ($status === 'available') {
|
|
|
|
|
$availableCount++;
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-08 22:58:59 +02:00
|
|
|
try {
|
|
|
|
|
$domainId = $this->domainModel->create([
|
|
|
|
|
'domain_name' => $domainName,
|
|
|
|
|
'notification_group_id' => $groupId,
|
|
|
|
|
'registrar' => $whoisData['registrar'],
|
|
|
|
|
'registrar_url' => $whoisData['registrar_url'] ?? null,
|
|
|
|
|
'expiration_date' => $whoisData['expiration_date'],
|
|
|
|
|
'updated_date' => $whoisData['updated_date'] ?? null,
|
|
|
|
|
'abuse_email' => $whoisData['abuse_email'] ?? null,
|
|
|
|
|
'last_checked' => date('Y-m-d H:i:s'),
|
|
|
|
|
'status' => $status,
|
|
|
|
|
'whois_data' => json_encode($whoisData),
|
|
|
|
|
'is_active' => 1,
|
|
|
|
|
'user_id' => \Core\Auth::id()
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
// Handle tags using the new tag system
|
|
|
|
|
if (!empty($tags) && $domainId) {
|
|
|
|
|
$tagModel = new \App\Models\Tag();
|
|
|
|
|
$tagModel->updateDomainTags($domainId, $tags, $userId);
|
|
|
|
|
}
|
2025-10-08 14:23:07 +03:00
|
|
|
|
2026-02-08 22:58:59 +02:00
|
|
|
$added++;
|
|
|
|
|
} catch (\PDOException $e) {
|
|
|
|
|
// Handle duplicate key (race condition between existsByDomain check and insert)
|
|
|
|
|
if (str_contains($e->getMessage(), 'Duplicate entry')) {
|
|
|
|
|
$skipped++;
|
|
|
|
|
} else {
|
|
|
|
|
$logger->error('Failed to add domain in bulk', [
|
|
|
|
|
'domain' => $domainName,
|
|
|
|
|
'error' => $e->getMessage()
|
|
|
|
|
]);
|
|
|
|
|
$errors[] = $domainName;
|
|
|
|
|
}
|
2025-10-25 12:40:47 +03:00
|
|
|
}
|
2025-10-08 14:23:07 +03:00
|
|
|
}
|
|
|
|
|
|
2025-10-20 19:02:56 +03:00
|
|
|
// Log bulk add completion
|
|
|
|
|
$logger->info('Bulk domain add completed', [
|
|
|
|
|
'user_id' => $userId,
|
|
|
|
|
'added' => $added,
|
|
|
|
|
'skipped' => $skipped,
|
|
|
|
|
'errors' => count($errors),
|
|
|
|
|
'available_count' => $availableCount
|
|
|
|
|
]);
|
|
|
|
|
|
2025-10-08 14:23:07 +03:00
|
|
|
$message = "Added $added domain(s)";
|
|
|
|
|
if ($skipped > 0) $message .= ", skipped $skipped duplicate(s)";
|
|
|
|
|
if (count($errors) > 0) $message .= ", failed to add " . count($errors) . " domain(s)";
|
|
|
|
|
|
|
|
|
|
if ($availableCount > 0) {
|
|
|
|
|
$_SESSION['warning'] = "Note: $availableCount domain(s) appear to be AVAILABLE (not registered).";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$_SESSION['success'] = $message;
|
|
|
|
|
$this->redirect('/domains');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function bulkRefresh()
|
|
|
|
|
{
|
|
|
|
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
|
|
|
|
$this->redirect('/domains');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
Add CSRF, CAPTCHA, and input validation improvements
Introduces CSRF protection to all sensitive controller actions, integrates configurable CAPTCHA (reCAPTCHA v2/v3, Turnstile) for authentication and registration flows, and centralizes input validation via a new InputValidator helper. Adds new helpers and services for CSRF and CAPTCHA, updates settings and migration for CAPTCHA configuration, and enhances logging and error handling in TLD registry import processes. Also improves validation for user, domain, group, and profile inputs throughout the application.
2025-10-10 00:04:12 +03:00
|
|
|
// CSRF Protection
|
|
|
|
|
$this->verifyCsrf('/domains');
|
|
|
|
|
|
2025-10-08 14:23:07 +03:00
|
|
|
$domainIds = $_POST['domain_ids'] ?? [];
|
|
|
|
|
|
|
|
|
|
if (empty($domainIds)) {
|
|
|
|
|
$_SESSION['error'] = 'No domains selected';
|
|
|
|
|
$this->redirect('/domains');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
Add CSRF, CAPTCHA, and input validation improvements
Introduces CSRF protection to all sensitive controller actions, integrates configurable CAPTCHA (reCAPTCHA v2/v3, Turnstile) for authentication and registration flows, and centralizes input validation via a new InputValidator helper. Adds new helpers and services for CSRF and CAPTCHA, updates settings and migration for CAPTCHA configuration, and enhances logging and error handling in TLD registry import processes. Also improves validation for user, domain, group, and profile inputs throughout the application.
2025-10-10 00:04:12 +03:00
|
|
|
// Validate bulk operation size
|
|
|
|
|
$sizeError = \App\Helpers\InputValidator::validateArraySize($domainIds, 1000, 'Domain selection');
|
|
|
|
|
if ($sizeError) {
|
|
|
|
|
$_SESSION['error'] = $sizeError;
|
|
|
|
|
$this->redirect('/domains');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-20 21:08:09 +03:00
|
|
|
// Get current user and isolation mode
|
|
|
|
|
$userId = \Core\Auth::id();
|
|
|
|
|
$settingModel = new \App\Models\Setting();
|
|
|
|
|
$isolationMode = $settingModel->getValue('user_isolation_mode', 'shared');
|
|
|
|
|
|
2025-10-21 15:16:56 +03:00
|
|
|
// Log bulk refresh start
|
|
|
|
|
$logger = new \App\Services\Logger();
|
|
|
|
|
$logger->info('Bulk domain refresh started', [
|
|
|
|
|
'user_id' => $userId,
|
|
|
|
|
'domain_count' => count($domainIds),
|
|
|
|
|
'isolation_mode' => $isolationMode,
|
|
|
|
|
'ip_address' => $_SERVER['REMOTE_ADDR'] ?? 'unknown'
|
|
|
|
|
]);
|
|
|
|
|
|
2025-10-08 14:23:07 +03:00
|
|
|
$refreshed = 0;
|
|
|
|
|
$failed = 0;
|
|
|
|
|
|
|
|
|
|
foreach ($domainIds as $id) {
|
2025-10-20 21:08:09 +03:00
|
|
|
// Check domain access based on isolation mode
|
|
|
|
|
if ($isolationMode === 'isolated') {
|
|
|
|
|
$domain = $this->domainModel->findWithIsolation($id, $userId);
|
|
|
|
|
} else {
|
|
|
|
|
$domain = $this->domainModel->find($id);
|
|
|
|
|
}
|
2025-10-08 14:23:07 +03:00
|
|
|
if (!$domain) continue;
|
|
|
|
|
|
|
|
|
|
$whoisData = $this->whoisService->getDomainInfo($domain['domain_name']);
|
|
|
|
|
|
|
|
|
|
if (!$whoisData) {
|
2025-10-21 15:16:56 +03:00
|
|
|
$logger->warning('Bulk refresh failed for domain - WHOIS data not retrieved', [
|
|
|
|
|
'domain_id' => $id,
|
|
|
|
|
'domain_name' => $domain['domain_name'] ?? 'unknown',
|
|
|
|
|
'user_id' => $userId
|
|
|
|
|
]);
|
2025-10-08 14:23:07 +03:00
|
|
|
$failed++;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-21 16:13:58 +03:00
|
|
|
// Use WHOIS expiration date if available, otherwise preserve manual expiration date
|
|
|
|
|
$expirationDate = $whoisData['expiration_date'] ?? $domain['expiration_date'];
|
|
|
|
|
|
2025-11-18 13:22:49 +02:00
|
|
|
$status = $this->whoisService->getDomainStatus($expirationDate, $whoisData['status'] ?? [], $whoisData);
|
2025-10-08 14:23:07 +03:00
|
|
|
|
|
|
|
|
$this->domainModel->update($id, [
|
|
|
|
|
'registrar' => $whoisData['registrar'],
|
|
|
|
|
'registrar_url' => $whoisData['registrar_url'] ?? null,
|
2025-10-21 16:13:58 +03:00
|
|
|
'expiration_date' => $expirationDate,
|
2025-10-08 14:23:07 +03:00
|
|
|
'updated_date' => $whoisData['updated_date'] ?? null,
|
|
|
|
|
'abuse_email' => $whoisData['abuse_email'] ?? null,
|
|
|
|
|
'last_checked' => date('Y-m-d H:i:s'),
|
|
|
|
|
'status' => $status,
|
|
|
|
|
'whois_data' => json_encode($whoisData)
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$refreshed++;
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-21 15:16:56 +03:00
|
|
|
// Log bulk refresh completion
|
|
|
|
|
$logger->info('Bulk domain refresh completed', [
|
|
|
|
|
'user_id' => $userId,
|
|
|
|
|
'total_domains' => count($domainIds),
|
|
|
|
|
'refreshed' => $refreshed,
|
|
|
|
|
'failed' => $failed,
|
|
|
|
|
'success_rate' => count($domainIds) > 0 ? round(($refreshed / count($domainIds)) * 100, 2) . '%' : '0%'
|
|
|
|
|
]);
|
|
|
|
|
|
2025-10-08 14:23:07 +03:00
|
|
|
$_SESSION['success'] = "Refreshed $refreshed domain(s)" . ($failed > 0 ? ", $failed failed" : '');
|
|
|
|
|
$this->redirect('/domains');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function bulkDelete()
|
|
|
|
|
{
|
|
|
|
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
|
|
|
|
$this->redirect('/domains');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
Add CSRF, CAPTCHA, and input validation improvements
Introduces CSRF protection to all sensitive controller actions, integrates configurable CAPTCHA (reCAPTCHA v2/v3, Turnstile) for authentication and registration flows, and centralizes input validation via a new InputValidator helper. Adds new helpers and services for CSRF and CAPTCHA, updates settings and migration for CAPTCHA configuration, and enhances logging and error handling in TLD registry import processes. Also improves validation for user, domain, group, and profile inputs throughout the application.
2025-10-10 00:04:12 +03:00
|
|
|
// CSRF Protection
|
|
|
|
|
$this->verifyCsrf('/domains');
|
|
|
|
|
|
2025-10-08 14:23:07 +03:00
|
|
|
$domainIds = $_POST['domain_ids'] ?? [];
|
|
|
|
|
|
|
|
|
|
if (empty($domainIds)) {
|
|
|
|
|
$_SESSION['error'] = 'No domains selected';
|
|
|
|
|
$this->redirect('/domains');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
Add CSRF, CAPTCHA, and input validation improvements
Introduces CSRF protection to all sensitive controller actions, integrates configurable CAPTCHA (reCAPTCHA v2/v3, Turnstile) for authentication and registration flows, and centralizes input validation via a new InputValidator helper. Adds new helpers and services for CSRF and CAPTCHA, updates settings and migration for CAPTCHA configuration, and enhances logging and error handling in TLD registry import processes. Also improves validation for user, domain, group, and profile inputs throughout the application.
2025-10-10 00:04:12 +03:00
|
|
|
// Validate bulk operation size
|
|
|
|
|
$sizeError = \App\Helpers\InputValidator::validateArraySize($domainIds, 1000, 'Domain selection');
|
|
|
|
|
if ($sizeError) {
|
|
|
|
|
$_SESSION['error'] = $sizeError;
|
|
|
|
|
$this->redirect('/domains');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-08 14:23:07 +03:00
|
|
|
$deleted = 0;
|
|
|
|
|
foreach ($domainIds as $id) {
|
|
|
|
|
if ($this->domainModel->delete($id)) {
|
|
|
|
|
$deleted++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$_SESSION['success'] = "Deleted $deleted domain(s)";
|
|
|
|
|
$this->redirect('/domains');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function bulkAssignGroup()
|
|
|
|
|
{
|
|
|
|
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
|
|
|
|
$this->redirect('/domains');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
Add CSRF, CAPTCHA, and input validation improvements
Introduces CSRF protection to all sensitive controller actions, integrates configurable CAPTCHA (reCAPTCHA v2/v3, Turnstile) for authentication and registration flows, and centralizes input validation via a new InputValidator helper. Adds new helpers and services for CSRF and CAPTCHA, updates settings and migration for CAPTCHA configuration, and enhances logging and error handling in TLD registry import processes. Also improves validation for user, domain, group, and profile inputs throughout the application.
2025-10-10 00:04:12 +03:00
|
|
|
// CSRF Protection
|
|
|
|
|
$this->verifyCsrf('/domains');
|
|
|
|
|
|
2025-10-08 14:23:07 +03:00
|
|
|
$domainIds = $_POST['domain_ids'] ?? [];
|
|
|
|
|
$groupId = !empty($_POST['group_id']) ? (int)$_POST['group_id'] : null;
|
2025-10-20 19:05:33 +03:00
|
|
|
$userId = \Core\Auth::id();
|
2025-10-08 14:23:07 +03:00
|
|
|
|
|
|
|
|
if (empty($domainIds)) {
|
|
|
|
|
$_SESSION['error'] = 'No domains selected';
|
|
|
|
|
$this->redirect('/domains');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
Add CSRF, CAPTCHA, and input validation improvements
Introduces CSRF protection to all sensitive controller actions, integrates configurable CAPTCHA (reCAPTCHA v2/v3, Turnstile) for authentication and registration flows, and centralizes input validation via a new InputValidator helper. Adds new helpers and services for CSRF and CAPTCHA, updates settings and migration for CAPTCHA configuration, and enhances logging and error handling in TLD registry import processes. Also improves validation for user, domain, group, and profile inputs throughout the application.
2025-10-10 00:04:12 +03:00
|
|
|
// Validate bulk operation size
|
|
|
|
|
$sizeError = \App\Helpers\InputValidator::validateArraySize($domainIds, 1000, 'Domain selection');
|
|
|
|
|
if ($sizeError) {
|
|
|
|
|
$_SESSION['error'] = $sizeError;
|
|
|
|
|
$this->redirect('/domains');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-20 19:02:56 +03:00
|
|
|
// Validate notification group in isolation mode
|
|
|
|
|
if ($groupId) {
|
|
|
|
|
$settingModel = new \App\Models\Setting();
|
|
|
|
|
$isolationMode = $settingModel->getValue('user_isolation_mode', 'shared');
|
|
|
|
|
|
|
|
|
|
if ($isolationMode === 'isolated') {
|
|
|
|
|
$group = $this->groupModel->find($groupId);
|
|
|
|
|
if (!$group || $group['user_id'] != $userId) {
|
|
|
|
|
$_SESSION['error'] = 'You can only assign domains to your own notification groups';
|
|
|
|
|
$this->redirect('/domains');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-08 14:23:07 +03:00
|
|
|
$updated = 0;
|
|
|
|
|
foreach ($domainIds as $id) {
|
|
|
|
|
if ($this->domainModel->update($id, ['notification_group_id' => $groupId])) {
|
|
|
|
|
$updated++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$_SESSION['success'] = "Updated $updated domain(s)";
|
|
|
|
|
$this->redirect('/domains');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function bulkToggleStatus()
|
|
|
|
|
{
|
|
|
|
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
|
|
|
|
$this->redirect('/domains');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
Add CSRF, CAPTCHA, and input validation improvements
Introduces CSRF protection to all sensitive controller actions, integrates configurable CAPTCHA (reCAPTCHA v2/v3, Turnstile) for authentication and registration flows, and centralizes input validation via a new InputValidator helper. Adds new helpers and services for CSRF and CAPTCHA, updates settings and migration for CAPTCHA configuration, and enhances logging and error handling in TLD registry import processes. Also improves validation for user, domain, group, and profile inputs throughout the application.
2025-10-10 00:04:12 +03:00
|
|
|
// CSRF Protection
|
|
|
|
|
$this->verifyCsrf('/domains');
|
|
|
|
|
|
2025-10-08 14:23:07 +03:00
|
|
|
$domainIds = $_POST['domain_ids'] ?? [];
|
|
|
|
|
$isActive = isset($_POST['is_active']) ? (int)$_POST['is_active'] : 1;
|
|
|
|
|
|
|
|
|
|
if (empty($domainIds)) {
|
|
|
|
|
$_SESSION['error'] = 'No domains selected';
|
|
|
|
|
$this->redirect('/domains');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
Add CSRF, CAPTCHA, and input validation improvements
Introduces CSRF protection to all sensitive controller actions, integrates configurable CAPTCHA (reCAPTCHA v2/v3, Turnstile) for authentication and registration flows, and centralizes input validation via a new InputValidator helper. Adds new helpers and services for CSRF and CAPTCHA, updates settings and migration for CAPTCHA configuration, and enhances logging and error handling in TLD registry import processes. Also improves validation for user, domain, group, and profile inputs throughout the application.
2025-10-10 00:04:12 +03:00
|
|
|
// Validate bulk operation size
|
|
|
|
|
$sizeError = \App\Helpers\InputValidator::validateArraySize($domainIds, 1000, 'Domain selection');
|
|
|
|
|
if ($sizeError) {
|
|
|
|
|
$_SESSION['error'] = $sizeError;
|
|
|
|
|
$this->redirect('/domains');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-20 21:08:09 +03:00
|
|
|
// Get current user and isolation mode
|
|
|
|
|
$userId = \Core\Auth::id();
|
|
|
|
|
$settingModel = new \App\Models\Setting();
|
|
|
|
|
$isolationMode = $settingModel->getValue('user_isolation_mode', 'shared');
|
|
|
|
|
|
2025-10-08 14:23:07 +03:00
|
|
|
$updated = 0;
|
|
|
|
|
foreach ($domainIds as $id) {
|
2025-10-20 21:08:09 +03:00
|
|
|
// Check domain access based on isolation mode
|
|
|
|
|
if ($isolationMode === 'isolated') {
|
|
|
|
|
$domain = $this->domainModel->findWithIsolation($id, $userId);
|
|
|
|
|
} else {
|
|
|
|
|
$domain = $this->domainModel->find($id);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($domain && $this->domainModel->update($id, ['is_active' => $isActive])) {
|
2025-10-08 14:23:07 +03:00
|
|
|
$updated++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$status = $isActive ? 'enabled' : 'disabled';
|
|
|
|
|
$_SESSION['success'] = "Monitoring $status for $updated domain(s)";
|
|
|
|
|
$this->redirect('/domains');
|
|
|
|
|
}
|
2025-10-08 20:56:25 +03:00
|
|
|
|
|
|
|
|
public function updateNotes($params = [])
|
|
|
|
|
{
|
|
|
|
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
|
|
|
|
$this->redirect('/domains');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
Add CSRF, CAPTCHA, and input validation improvements
Introduces CSRF protection to all sensitive controller actions, integrates configurable CAPTCHA (reCAPTCHA v2/v3, Turnstile) for authentication and registration flows, and centralizes input validation via a new InputValidator helper. Adds new helpers and services for CSRF and CAPTCHA, updates settings and migration for CAPTCHA configuration, and enhances logging and error handling in TLD registry import processes. Also improves validation for user, domain, group, and profile inputs throughout the application.
2025-10-10 00:04:12 +03:00
|
|
|
// CSRF Protection
|
|
|
|
|
$this->verifyCsrf('/domains');
|
|
|
|
|
|
2025-10-08 20:56:25 +03:00
|
|
|
$id = (int)($params['id'] ?? 0);
|
2025-10-20 21:08:09 +03:00
|
|
|
$domain = $this->checkDomainAccess($id);
|
2025-10-08 20:56:25 +03:00
|
|
|
|
|
|
|
|
if (!$domain) {
|
|
|
|
|
$_SESSION['error'] = 'Domain not found';
|
|
|
|
|
$this->redirect('/domains');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$notes = $_POST['notes'] ?? '';
|
|
|
|
|
|
Add CSRF, CAPTCHA, and input validation improvements
Introduces CSRF protection to all sensitive controller actions, integrates configurable CAPTCHA (reCAPTCHA v2/v3, Turnstile) for authentication and registration flows, and centralizes input validation via a new InputValidator helper. Adds new helpers and services for CSRF and CAPTCHA, updates settings and migration for CAPTCHA configuration, and enhances logging and error handling in TLD registry import processes. Also improves validation for user, domain, group, and profile inputs throughout the application.
2025-10-10 00:04:12 +03:00
|
|
|
// Validate notes length
|
|
|
|
|
$lengthError = \App\Helpers\InputValidator::validateLength($notes, 5000, 'Notes');
|
Add DNS monitoring and refresh functionality
Introduce DNS monitoring: add DnsService (comprehensive DNS lookup, crt.sh discovery, Cloudflare detection, IP enrichment) and a new DnsRecord model to persist snapshots, manage diffs, and provide queries/stats. Update DomainController to support a dns_monitoring_enabled flag, refactor WHOIS/DNS refresh logic into performWhoisRefresh/performDnsRefresh, and add endpoints for refreshWhois, refreshDns and refreshAll; send notifications when DNS monitoring is toggled. Add UI templates/tabs for DNS, billing, notifications, overview, SSL and WHOIS and wire DNS data into the domain view; expose cached IP details. Add cron/check_dns.php and migration 027_add_dns_monitoring.sql (and include it in installer migration lists). Other tweaks: safer EmailHelper subject handling, TldRegistry search improvements, domain sorting using an effective status (expiring_soon), Discord channel null-safe fields, settings UI additions (domain_view_template and cron staleness warnings), and route/migration updates. This enables scheduled and manual DNS scans with persistent records and notifications.
2026-03-08 14:32:05 +02:00
|
|
|
|
|
|
|
|
$settingModel = new \App\Models\Setting();
|
|
|
|
|
$viewTemplate = $settingModel->getValue('domain_view_template', 'detailed');
|
|
|
|
|
$redirect = '/domains/' . $id . ($viewTemplate === 'detailed' ? '#overview' : '');
|
|
|
|
|
|
Add CSRF, CAPTCHA, and input validation improvements
Introduces CSRF protection to all sensitive controller actions, integrates configurable CAPTCHA (reCAPTCHA v2/v3, Turnstile) for authentication and registration flows, and centralizes input validation via a new InputValidator helper. Adds new helpers and services for CSRF and CAPTCHA, updates settings and migration for CAPTCHA configuration, and enhances logging and error handling in TLD registry import processes. Also improves validation for user, domain, group, and profile inputs throughout the application.
2025-10-10 00:04:12 +03:00
|
|
|
if ($lengthError) {
|
|
|
|
|
$_SESSION['error'] = $lengthError;
|
Add DNS monitoring and refresh functionality
Introduce DNS monitoring: add DnsService (comprehensive DNS lookup, crt.sh discovery, Cloudflare detection, IP enrichment) and a new DnsRecord model to persist snapshots, manage diffs, and provide queries/stats. Update DomainController to support a dns_monitoring_enabled flag, refactor WHOIS/DNS refresh logic into performWhoisRefresh/performDnsRefresh, and add endpoints for refreshWhois, refreshDns and refreshAll; send notifications when DNS monitoring is toggled. Add UI templates/tabs for DNS, billing, notifications, overview, SSL and WHOIS and wire DNS data into the domain view; expose cached IP details. Add cron/check_dns.php and migration 027_add_dns_monitoring.sql (and include it in installer migration lists). Other tweaks: safer EmailHelper subject handling, TldRegistry search improvements, domain sorting using an effective status (expiring_soon), Discord channel null-safe fields, settings UI additions (domain_view_template and cron staleness warnings), and route/migration updates. This enables scheduled and manual DNS scans with persistent records and notifications.
2026-03-08 14:32:05 +02:00
|
|
|
$this->redirect($redirect);
|
Add CSRF, CAPTCHA, and input validation improvements
Introduces CSRF protection to all sensitive controller actions, integrates configurable CAPTCHA (reCAPTCHA v2/v3, Turnstile) for authentication and registration flows, and centralizes input validation via a new InputValidator helper. Adds new helpers and services for CSRF and CAPTCHA, updates settings and migration for CAPTCHA configuration, and enhances logging and error handling in TLD registry import processes. Also improves validation for user, domain, group, and profile inputs throughout the application.
2025-10-10 00:04:12 +03:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-08 20:56:25 +03:00
|
|
|
$this->domainModel->update($id, [
|
|
|
|
|
'notes' => $notes
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$_SESSION['success'] = 'Notes updated successfully';
|
Add DNS monitoring and refresh functionality
Introduce DNS monitoring: add DnsService (comprehensive DNS lookup, crt.sh discovery, Cloudflare detection, IP enrichment) and a new DnsRecord model to persist snapshots, manage diffs, and provide queries/stats. Update DomainController to support a dns_monitoring_enabled flag, refactor WHOIS/DNS refresh logic into performWhoisRefresh/performDnsRefresh, and add endpoints for refreshWhois, refreshDns and refreshAll; send notifications when DNS monitoring is toggled. Add UI templates/tabs for DNS, billing, notifications, overview, SSL and WHOIS and wire DNS data into the domain view; expose cached IP details. Add cron/check_dns.php and migration 027_add_dns_monitoring.sql (and include it in installer migration lists). Other tweaks: safer EmailHelper subject handling, TldRegistry search improvements, domain sorting using an effective status (expiring_soon), Discord channel null-safe fields, settings UI additions (domain_view_template and cron staleness warnings), and route/migration updates. This enables scheduled and manual DNS scans with persistent records and notifications.
2026-03-08 14:32:05 +02:00
|
|
|
$this->redirect($redirect);
|
2025-10-08 20:56:25 +03:00
|
|
|
}
|
2025-10-12 12:46:16 +03:00
|
|
|
|
|
|
|
|
public function bulkAddTags()
|
|
|
|
|
{
|
|
|
|
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
|
|
|
|
$this->redirect('/domains');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// CSRF Protection
|
|
|
|
|
$this->verifyCsrf('/domains');
|
|
|
|
|
|
|
|
|
|
$domainIds = $_POST['domain_ids'] ?? [];
|
|
|
|
|
$tagToAdd = trim($_POST['tag'] ?? '');
|
|
|
|
|
|
|
|
|
|
if (empty($domainIds) || empty($tagToAdd)) {
|
|
|
|
|
$_SESSION['error'] = 'Invalid request';
|
|
|
|
|
$this->redirect('/domains');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Validate tag format
|
|
|
|
|
if (!preg_match('/^[a-z0-9-]+$/', $tagToAdd)) {
|
|
|
|
|
$_SESSION['error'] = 'Invalid tag format (use only letters, numbers, and hyphens)';
|
|
|
|
|
$this->redirect('/domains');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-20 21:08:09 +03:00
|
|
|
// Get current user and isolation mode
|
|
|
|
|
$userId = \Core\Auth::id();
|
|
|
|
|
$settingModel = new \App\Models\Setting();
|
|
|
|
|
$isolationMode = $settingModel->getValue('user_isolation_mode', 'shared');
|
|
|
|
|
|
2025-10-25 12:40:47 +03:00
|
|
|
// Initialize Tag model
|
|
|
|
|
$tagModel = new \App\Models\Tag();
|
|
|
|
|
|
|
|
|
|
// Find or create the tag
|
|
|
|
|
$tag = $tagModel->findByName($tagToAdd, $userId);
|
|
|
|
|
if (!$tag) {
|
|
|
|
|
// Create new tag
|
|
|
|
|
$tagId = $tagModel->create([
|
|
|
|
|
'name' => $tagToAdd,
|
|
|
|
|
'color' => 'bg-gray-100 text-gray-700 border-gray-300',
|
|
|
|
|
'description' => '',
|
|
|
|
|
'user_id' => $userId
|
|
|
|
|
]);
|
|
|
|
|
if (!$tagId) {
|
|
|
|
|
$_SESSION['error'] = 'Failed to create tag';
|
|
|
|
|
$this->redirect('/domains');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
$tagId = $tag['id'];
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-12 12:46:16 +03:00
|
|
|
$updated = 0;
|
|
|
|
|
foreach ($domainIds as $id) {
|
2025-10-20 21:08:09 +03:00
|
|
|
// Check domain access based on isolation mode
|
|
|
|
|
if ($isolationMode === 'isolated') {
|
|
|
|
|
$domain = $this->domainModel->findWithIsolation($id, $userId);
|
|
|
|
|
} else {
|
|
|
|
|
$domain = $this->domainModel->find($id);
|
|
|
|
|
}
|
2025-10-12 12:46:16 +03:00
|
|
|
if (!$domain) continue;
|
|
|
|
|
|
2025-10-25 12:40:47 +03:00
|
|
|
// Add tag to domain using Tag model
|
|
|
|
|
if ($tagModel->addToDomain($id, $tagId)) {
|
|
|
|
|
$updated++;
|
2025-10-12 12:46:16 +03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$_SESSION['success'] = "Tag '$tagToAdd' added to $updated domain(s)";
|
|
|
|
|
$this->redirect('/domains');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function bulkRemoveTags()
|
|
|
|
|
{
|
|
|
|
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
|
|
|
|
$this->redirect('/domains');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// CSRF Protection
|
|
|
|
|
$this->verifyCsrf('/domains');
|
|
|
|
|
|
|
|
|
|
$domainIds = $_POST['domain_ids'] ?? [];
|
|
|
|
|
|
|
|
|
|
if (empty($domainIds)) {
|
|
|
|
|
$_SESSION['error'] = 'No domains selected';
|
|
|
|
|
$this->redirect('/domains');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-20 21:08:09 +03:00
|
|
|
// Get current user and isolation mode
|
|
|
|
|
$userId = \Core\Auth::id();
|
|
|
|
|
$settingModel = new \App\Models\Setting();
|
|
|
|
|
$isolationMode = $settingModel->getValue('user_isolation_mode', 'shared');
|
|
|
|
|
|
2025-10-25 02:04:00 +03:00
|
|
|
$tagModel = new \App\Models\Tag();
|
2025-10-12 12:46:16 +03:00
|
|
|
$updated = 0;
|
|
|
|
|
foreach ($domainIds as $id) {
|
2025-10-20 21:08:09 +03:00
|
|
|
// Check domain access based on isolation mode
|
|
|
|
|
if ($isolationMode === 'isolated') {
|
|
|
|
|
$domain = $this->domainModel->findWithIsolation($id, $userId);
|
|
|
|
|
} else {
|
|
|
|
|
$domain = $this->domainModel->find($id);
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-25 02:04:00 +03:00
|
|
|
if ($domain && $tagModel->removeAllFromDomain($id)) {
|
2025-10-12 12:46:16 +03:00
|
|
|
$updated++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$_SESSION['success'] = "Tags removed from $updated domain(s)";
|
|
|
|
|
$this->redirect('/domains');
|
|
|
|
|
}
|
2025-10-20 17:04:13 +03:00
|
|
|
|
2025-10-25 02:04:00 +03:00
|
|
|
/**
|
|
|
|
|
* 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');
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-20 17:04:13 +03:00
|
|
|
/**
|
|
|
|
|
* 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');
|
Add DNS monitoring and refresh functionality
Introduce DNS monitoring: add DnsService (comprehensive DNS lookup, crt.sh discovery, Cloudflare detection, IP enrichment) and a new DnsRecord model to persist snapshots, manage diffs, and provide queries/stats. Update DomainController to support a dns_monitoring_enabled flag, refactor WHOIS/DNS refresh logic into performWhoisRefresh/performDnsRefresh, and add endpoints for refreshWhois, refreshDns and refreshAll; send notifications when DNS monitoring is toggled. Add UI templates/tabs for DNS, billing, notifications, overview, SSL and WHOIS and wire DNS data into the domain view; expose cached IP details. Add cron/check_dns.php and migration 027_add_dns_monitoring.sql (and include it in installer migration lists). Other tweaks: safer EmailHelper subject handling, TldRegistry search improvements, domain sorting using an effective status (expiring_soon), Discord channel null-safe fields, settings UI additions (domain_view_template and cron staleness warnings), and route/migration updates. This enables scheduled and manual DNS scans with persistent records and notifications.
2026-03-08 14:32:05 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ========================================
|
|
|
|
|
// DNS MONITORING
|
|
|
|
|
// ========================================
|
|
|
|
|
|
|
|
|
|
public function refreshDns($params = [])
|
|
|
|
|
{
|
|
|
|
|
$id = (int)($params['id'] ?? 0);
|
|
|
|
|
$domain = $this->checkDomainAccess($id);
|
|
|
|
|
|
|
|
|
|
if (!$domain) {
|
|
|
|
|
$_SESSION['error'] = 'Domain not found';
|
|
|
|
|
$this->redirect('/domains');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$result = $this->performDnsRefresh($id, $domain);
|
|
|
|
|
|
|
|
|
|
if (strpos($result, 'no records') !== false) {
|
|
|
|
|
$_SESSION['warning'] = 'No DNS records found for this domain';
|
|
|
|
|
} else {
|
|
|
|
|
$_SESSION['success'] = $result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$this->redirectBackToDomain($id, '#dns');
|
2025-10-20 17:04:13 +03:00
|
|
|
}
|
2025-10-25 02:04:00 +03:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 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]);
|
|
|
|
|
}
|
2025-10-08 14:23:07 +03:00
|
|
|
}
|
|
|
|
|
|