Initial Commit

This commit is contained in:
Hosteroid
2025-10-08 14:23:07 +03:00
commit b3b3ac66ff
78 changed files with 14248 additions and 0 deletions

View File

@@ -0,0 +1,93 @@
<?php
namespace App\Controllers;
use Core\Controller;
use App\Models\User;
class AuthController extends Controller
{
private User $userModel;
public function __construct()
{
$this->userModel = new User();
}
/**
* Show login form
*/
public function showLogin()
{
// If already logged in, redirect to dashboard
if (isset($_SESSION['user_id'])) {
$this->redirect('/');
}
$this->view('auth/login', [
'title' => 'Login'
]);
}
/**
* Process login
*/
public function login()
{
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
$this->redirect('/login');
return;
}
$username = trim($_POST['username'] ?? '');
$password = $_POST['password'] ?? '';
// Validate input
if (empty($username) || empty($password)) {
$_SESSION['error'] = 'Username and password are required';
$this->redirect('/login');
return;
}
// Find user
$user = $this->userModel->findByUsername($username);
if (!$user) {
$_SESSION['error'] = 'Invalid username or password';
$this->redirect('/login');
return;
}
// Verify password
if (!$this->userModel->verifyPassword($password, $user['password'])) {
$_SESSION['error'] = 'Invalid username or password';
$this->redirect('/login');
return;
}
// Login successful - create session
$_SESSION['user_id'] = $user['id'];
$_SESSION['username'] = $user['username'];
$_SESSION['full_name'] = $user['full_name'];
// Update last login
$this->userModel->updateLastLogin($user['id']);
// Redirect to dashboard
$this->redirect('/');
}
/**
* Logout
*/
public function logout()
{
// Destroy session
session_destroy();
session_start();
$_SESSION['success'] = 'You have been logged out successfully';
$this->redirect('/login');
}
}

View File

@@ -0,0 +1,39 @@
<?php
namespace App\Controllers;
use Core\Controller;
use App\Models\Domain;
use App\Models\NotificationGroup;
use App\Models\NotificationLog;
class DashboardController extends Controller
{
private Domain $domainModel;
private NotificationGroup $groupModel;
private NotificationLog $logModel;
public function __construct()
{
$this->domainModel = new Domain();
$this->groupModel = new NotificationGroup();
$this->logModel = new NotificationLog();
}
public function index()
{
$stats = $this->domainModel->getStatistics();
$recentDomains = $this->domainModel->getRecent(5); // Get 5 most recent domains
$expiringThisMonth = $this->domainModel->getExpiringDomains(30); // Domains expiring within 30 days
$recentLogs = $this->logModel->getRecent(10);
$this->view('dashboard/index', [
'stats' => $stats,
'recentDomains' => $recentDomains,
'expiringThisMonth' => $expiringThisMonth,
'recentLogs' => $recentLogs,
'title' => 'Dashboard'
]);
}
}

View File

@@ -0,0 +1,304 @@
<?php
namespace App\Controllers;
use Core\Controller;
use App\Services\WhoisService;
class DebugController extends Controller
{
/**
* Show raw WHOIS data for a domain
*/
public function whois()
{
$domain = $_GET['domain'] ?? '';
if (empty($domain)) {
$this->view('debug/whois', [
'domain' => '',
'title' => 'WHOIS Debug Tool'
]);
return;
}
// Get TLD
$parts = explode('.', $domain);
$tld = $parts[count($parts) - 1];
// Use reflection to access the WhoisService's discovery methods
$whoisService = new WhoisService();
// Use reflection to call private discoverTldServers method
$reflection = new \ReflectionClass($whoisService);
$discoverMethod = $reflection->getMethod('discoverTldServers');
$discoverMethod->setAccessible(true);
// Handle double TLDs
$doubleTld = null;
if (count($parts) >= 3) {
$doubleTld = $parts[count($parts) - 2] . '.' . $tld;
}
// Try double TLD first, then single TLD
$discoveryDebug = [];
$discoveryDebug[] = "=== IANA DISCOVERY PROCESS ===";
$discoveryDebug[] = "";
$discoveryDebug[] = "Step 1: Querying IANA WHOIS (whois.iana.org) for TLD information";
$discoveryDebug[] = "Step 2: Querying IANA RDAP Bootstrap (https://data.iana.org/rdap/dns.json)";
$discoveryDebug[] = "Step 3: Fallback to IANA HTML page if needed";
$discoveryDebug[] = "";
if ($doubleTld) {
$discoveryDebug[] = "Trying double TLD: {$doubleTld}";
$servers = $discoverMethod->invoke($whoisService, $doubleTld);
$discoveryDebug[] = " -> RDAP: " . ($servers['rdap_url'] ?? 'Not found');
$discoveryDebug[] = " -> WHOIS: " . ($servers['whois_server'] ?? 'Not found');
if (!$servers['rdap_url'] && !$servers['whois_server']) {
$discoveryDebug[] = "";
$discoveryDebug[] = "Double TLD failed, trying single TLD: {$tld}";
$servers = $discoverMethod->invoke($whoisService, $tld);
$discoveryDebug[] = " -> RDAP: " . ($servers['rdap_url'] ?? 'Not found');
$discoveryDebug[] = " -> WHOIS: " . ($servers['whois_server'] ?? 'Not found');
}
} else {
$discoveryDebug[] = "Trying single TLD: {$tld}";
$servers = $discoverMethod->invoke($whoisService, $tld);
$discoveryDebug[] = " -> RDAP: " . ($servers['rdap_url'] ?? 'Not found');
$discoveryDebug[] = " -> WHOIS: " . ($servers['whois_server'] ?? 'Not found');
}
$rdapUrl = $servers['rdap_url'];
$whoisServer = $servers['whois_server'] ?? 'whois.iana.org';
$discoveryDebug[] = "";
$discoveryDebug[] = "=== FINAL RESULTS ===";
$discoveryDebug[] = "RDAP URL: " . ($rdapUrl ?? 'Not available - will use WHOIS fallback');
$discoveryDebug[] = "WHOIS Server: {$whoisServer}";
$discoveryDebug[] = "";
if (!$rdapUrl) {
$discoveryDebug[] = "NOTE: No RDAP server found in IANA sources. Will use traditional WHOIS.";
}
// Get raw response - try RDAP first, then WHOIS
$response = '';
$parsedData = [];
$server = $whoisServer;
$rdapSucceeded = false;
// Add discovery debug info
$response .= "=== TLD DISCOVERY DEBUG ===\n\n";
foreach ($discoveryDebug as $debug) {
$response .= $debug . "\n";
}
$response .= "\n";
// Try RDAP first if available
if ($rdapUrl) {
$server = parse_url($rdapUrl, PHP_URL_HOST) . ' (RDAP)';
// Construct full RDAP URL
// RDAP standard format: {base_url}domain/{domain_name}
if (!preg_match('/domain\/$/', $rdapUrl)) {
$fullRdapUrl = rtrim($rdapUrl, '/') . '/domain/' . strtolower($domain);
} else {
$fullRdapUrl = rtrim($rdapUrl, '/') . '/' . strtolower($domain);
}
// Query RDAP
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $fullRdapUrl);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Accept: application/rdap+json']);
$rdapResponse = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$curlError = curl_error($ch);
$curlInfo = curl_getinfo($ch);
curl_close($ch);
if ($httpCode === 200 && $rdapResponse) {
// Pretty print JSON
$rdapData = json_decode($rdapResponse, true);
// Check if RDAP returned an error in the JSON
if ($rdapData && isset($rdapData['errorCode'])) {
$rdapSucceeded = true; // HTTP succeeded, but domain not found
$response .= "\n=== RDAP QUERY SUCCESS (Domain Not Found) ===\n\n";
$response .= "RDAP URL: {$fullRdapUrl}\n";
$response .= "HTTP Status: {$httpCode}\n";
$response .= "RDAP Error Code: {$rdapData['errorCode']}\n";
$response .= "Title: " . ($rdapData['title'] ?? 'N/A') . "\n";
$response .= "Description: " . (isset($rdapData['description']) ? implode(', ', (array)$rdapData['description']) : 'N/A') . "\n\n";
if ($rdapData['errorCode'] == 404) {
$response .= "✓ Domain is AVAILABLE (not registered)\n\n";
$parsedData[] = ['key' => 'Status', 'value' => 'AVAILABLE'];
$parsedData[] = ['key' => 'Registrar', 'value' => 'Not Registered'];
}
$response .= "--- RDAP JSON RESPONSE ---\n\n";
$response .= json_encode($rdapData, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
} else {
$rdapSucceeded = true;
$response .= "\n=== RDAP QUERY SUCCESS ===\n\n";
$response .= "RDAP URL: {$fullRdapUrl}\n";
$response .= "HTTP Status: {$httpCode}\n\n";
$response .= "--- RDAP JSON RESPONSE ---\n\n";
if ($rdapData) {
$response .= json_encode($rdapData, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
// Parse some key fields for the table
if (isset($rdapData['entities'])) {
foreach ($rdapData['entities'] as $entity) {
if (isset($entity['vcardArray'][1])) {
foreach ($entity['vcardArray'][1] as $field) {
if (is_array($field) && count($field) >= 4) {
$parsedData[] = [
'key' => $field[0],
'value' => is_array($field[3]) ? implode(', ', $field[3]) : $field[3]
];
}
}
}
}
}
if (isset($rdapData['events'])) {
foreach ($rdapData['events'] as $event) {
$parsedData[] = [
'key' => ucfirst($event['eventAction'] ?? 'event'),
'value' => $event['eventDate'] ?? 'N/A'
];
}
}
} else {
$response .= $rdapResponse;
}
}
} elseif ($httpCode === 404 && $rdapResponse) {
// Handle 404 responses as domain not found
$rdapData = json_decode($rdapResponse, true);
if ($rdapData && isset($rdapData['errorCode']) && $rdapData['errorCode'] == 404) {
$rdapSucceeded = true; // Treat as successful domain not found
$response .= "\n=== RDAP QUERY SUCCESS (Domain Not Found) ===\n\n";
$response .= "RDAP URL: {$fullRdapUrl}\n";
$response .= "HTTP Status: {$httpCode}\n";
$response .= "RDAP Error Code: {$rdapData['errorCode']}\n";
$response .= "Title: " . ($rdapData['title'] ?? 'N/A') . "\n";
$response .= "Description: " . (isset($rdapData['description']) ? implode(', ', (array)$rdapData['description']) : 'N/A') . "\n\n";
$response .= "✓ Domain is AVAILABLE (not registered)\n\n";
$parsedData[] = ['key' => 'Status', 'value' => 'AVAILABLE'];
$parsedData[] = ['key' => 'Registrar', 'value' => 'Not Registered'];
$response .= "--- RDAP JSON RESPONSE ---\n\n";
$response .= json_encode($rdapData, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
} else {
$response .= "\n=== RDAP QUERY FAILED ===\n\n";
$response .= "RDAP URL: {$fullRdapUrl}\n";
$response .= "HTTP Status: {$httpCode}\n";
$response .= "\nError: Could not retrieve RDAP data\n\n";
}
} else {
$response .= "\n=== RDAP QUERY FAILED ===\n\n";
$response .= "RDAP URL: {$fullRdapUrl}\n";
$response .= "HTTP Status: {$httpCode}\n";
if ($curlError) {
$response .= "cURL Error: {$curlError}\n";
}
// Show detailed cURL info
$response .= "\ncURL Debug Info:\n";
$response .= " - Total Time: " . ($curlInfo['total_time'] ?? 'N/A') . "s\n";
$response .= " - Name Lookup Time: " . ($curlInfo['namelookup_time'] ?? 'N/A') . "s\n";
$response .= " - Connect Time: " . ($curlInfo['connect_time'] ?? 'N/A') . "s\n";
$response .= " - Primary IP: " . ($curlInfo['primary_ip'] ?? 'N/A') . "\n";
if ($httpCode === 0) {
$response .= "\nNote: HTTP Status 0 usually means:\n";
$response .= " - SSL certificate verification failed\n";
$response .= " - Connection timeout\n";
$response .= " - DNS resolution failed\n";
$response .= " - URL is malformed\n";
}
$response .= "\nError: Could not retrieve RDAP data\n\n";
}
}
// If RDAP failed or not available, query WHOIS
if (!$rdapSucceeded && $whoisServer) {
if ($rdapUrl) {
$response .= "\n\n=== WHOIS FALLBACK (RDAP Failed) ===\n\n";
} else {
$response = "=== WHOIS QUERY ===\n\n";
$server = $whoisServer;
}
$response .= "WHOIS Server: {$whoisServer}\n\n";
$response .= "--- WHOIS TEXT RESPONSE ---\n\n";
$fp = @fsockopen($whoisServer, 43, $errno, $errstr, 10);
if ($fp) {
fputs($fp, $domain . "\r\n");
$whoisResponse = '';
while (!feof($fp)) {
$whoisResponse .= fgets($fp, 128);
}
fclose($fp);
$response .= $whoisResponse;
// Check if domain is not found/available
$whoisResponseLower = strtolower($whoisResponse);
if (preg_match('/not found|no match|no entries found|no data found|domain not found|no such domain|not registered|available for registration/i', $whoisResponseLower)) {
$response .= "\n\n=== DOMAIN STATUS DETECTED ===\n";
$response .= "✓ Domain is AVAILABLE (not registered)\n";
$parsedData[] = ['key' => 'Status', 'value' => 'AVAILABLE'];
$parsedData[] = ['key' => 'Registrar', 'value' => 'Not Registered'];
} else {
// Parse key-value pairs from WHOIS
$lines = explode("\n", $whoisResponse);
foreach ($lines as $line) {
$line = trim($line);
if (empty($line) || $line[0] === '%' || $line[0] === '#') {
continue;
}
if (strpos($line, ':') !== false) {
list($key, $value) = explode(':', $line, 2);
$parsedData[] = [
'key' => trim($key),
'value' => trim($value)
];
}
}
}
} else {
$response .= "Error: Could not connect to WHOIS server: $errstr ($errno)";
}
}
// Get parsed info using WhoisService
$info = $whoisService->getDomainInfo($domain);
$this->view('debug/whois', [
'domain' => $domain,
'server' => $server,
'tld' => $tld,
'response' => $response,
'parsedData' => $parsedData,
'info' => $info,
'title' => 'WHOIS Debug - ' . $domain
]);
}
}

View File

@@ -0,0 +1,569 @@
<?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();
}
public function index()
{
// Get filter parameters
$search = $_GET['search'] ?? '';
$status = $_GET['status'] ?? '';
$groupId = $_GET['group'] ?? '';
$sortBy = $_GET['sort'] ?? 'domain_name';
$sortOrder = $_GET['order'] ?? 'asc';
$page = max(1, (int)($_GET['page'] ?? 1));
$perPage = max(10, min(100, (int)($_GET['per_page'] ?? 25))); // Between 10 and 100
// Get all domains with groups
$domains = $this->domainModel->getAllWithGroups();
// Apply filters
if (!empty($search)) {
$domains = array_filter($domains, function($domain) use ($search) {
return stripos($domain['domain_name'], $search) !== false ||
stripos($domain['registrar'] ?? '', $search) !== false;
});
}
if (!empty($status)) {
$domains = array_filter($domains, function($domain) use ($status) {
if ($status === 'expiring_soon') {
// Check if domain expires within 30 days
if (!empty($domain['expiration_date'])) {
$daysLeft = floor((strtotime($domain['expiration_date']) - time()) / 86400);
return $daysLeft <= 30 && $daysLeft >= 0;
}
return false;
}
return $domain['status'] === $status;
});
}
if (!empty($groupId)) {
$domains = array_filter($domains, function($domain) use ($groupId) {
return $domain['notification_group_id'] == $groupId;
});
}
// Get total count after filtering
$totalDomains = count($domains);
// Apply sorting
usort($domains, function($a, $b) use ($sortBy, $sortOrder) {
$aVal = $a[$sortBy] ?? '';
$bVal = $b[$sortBy] ?? '';
$comparison = strcasecmp($aVal, $bVal);
return $sortOrder === 'desc' ? -$comparison : $comparison;
});
// Calculate pagination
$totalPages = ceil($totalDomains / $perPage);
$page = min($page, max(1, $totalPages)); // Ensure page is within valid range
$offset = ($page - 1) * $perPage;
// Slice array for current page
$paginatedDomains = array_slice($domains, $offset, $perPage);
$groups = $this->groupModel->all();
$this->view('domains/index', [
'domains' => $paginatedDomains,
'groups' => $groups,
'filters' => [
'search' => $search,
'status' => $status,
'group' => $groupId,
'sort' => $sortBy,
'order' => $sortOrder
],
'pagination' => [
'current_page' => $page,
'per_page' => $perPage,
'total' => $totalDomains,
'total_pages' => $totalPages,
'showing_from' => $totalDomains > 0 ? $offset + 1 : 0,
'showing_to' => min($offset + $perPage, $totalDomains)
],
'title' => 'Domains'
]);
}
public function create()
{
$groups = $this->groupModel->all();
$this->view('domains/create', [
'groups' => $groups,
'title' => 'Add Domain'
]);
}
public function store()
{
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
$this->redirect('/domains/create');
return;
}
$domainName = trim($_POST['domain_name'] ?? '');
$groupId = !empty($_POST['notification_group_id']) ? (int)$_POST['notification_group_id'] : null;
// Validate
if (empty($domainName)) {
$_SESSION['error'] = 'Domain name is required';
$this->redirect('/domains/create');
return;
}
// 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
$status = $this->whoisService->getDomainStatus($whoisData['expiration_date'], $whoisData['status'] ?? []);
// 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),
'is_active' => 1
]);
if ($status !== 'available') {
$_SESSION['success'] = "Domain '$domainName' added successfully";
}
$this->redirect('/domains');
}
public function edit($params = [])
{
$id = $params['id'] ?? 0;
$domain = $this->domainModel->find($id);
if (!$domain) {
$_SESSION['error'] = 'Domain not found';
$this->redirect('/domains');
return;
}
$groups = $this->groupModel->all();
$this->view('domains/edit', [
'domain' => $domain,
'groups' => $groups,
'title' => 'Edit Domain'
]);
}
public function update($params = [])
{
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
$this->redirect('/domains');
return;
}
$id = (int)($params['id'] ?? 0);
$domain = $this->domainModel->find($id);
if (!$domain) {
$_SESSION['error'] = 'Domain not found';
$this->redirect('/domains');
return;
}
$groupId = !empty($_POST['notification_group_id']) ? (int)$_POST['notification_group_id'] : null;
$isActive = isset($_POST['is_active']) ? 1 : 0;
// Check if monitoring status changed
$statusChanged = ($domain['is_active'] != $isActive);
$oldGroupId = $domain['notification_group_id'];
$this->domainModel->update($id, [
'notification_group_id' => $groupId,
'is_active' => $isActive
]);
// 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);
}
// 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);
}
}
$_SESSION['success'] = 'Domain updated successfully';
$this->redirect('/domains/' . $id);
}
public function refresh($params = [])
{
$id = $params['id'] ?? 0;
$domain = $this->domainModel->find($id);
if (!$domain) {
$_SESSION['error'] = 'Domain not found';
$this->redirect('/domains');
return;
}
// Get fresh WHOIS information
$whoisData = $this->whoisService->getDomainInfo($domain['domain_name']);
if (!$whoisData) {
$_SESSION['error'] = 'Could not retrieve WHOIS information';
// Check if we came from view page
$referer = $_SERVER['HTTP_REFERER'] ?? '';
if (strpos($referer, '/domains/' . $id) !== false) {
$this->redirect('/domains/' . $id);
} else {
$this->redirect('/domains');
}
return;
}
$status = $this->whoisService->getDomainStatus($whoisData['expiration_date'], $whoisData['status'] ?? []);
$this->domainModel->update($id, [
'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)
]);
$_SESSION['success'] = 'Domain information refreshed';
// Check if we came from view page or list page
$referer = $_SERVER['HTTP_REFERER'] ?? '';
if (strpos($referer, '/domains/' . $id) !== false) {
// Came from view page, go back to view page
$this->redirect('/domains/' . $id);
} else {
// Came from list page, stay on list page
$this->redirect('/domains');
}
}
public function delete($params = [])
{
$id = $params['id'] ?? 0;
$domain = $this->domainModel->find($id);
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;
$domain = $this->domainModel->getWithChannels($id);
if (!$domain) {
$_SESSION['error'] = 'Domain not found';
$this->redirect('/domains');
return;
}
$logModel = new \App\Models\NotificationLog();
$logs = $logModel->getByDomain($id, 20);
$this->view('domains/view', [
'domain' => $domain,
'logs' => $logs,
'title' => $domain['domain_name']
]);
}
public function bulkAdd()
{
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
$groups = $this->groupModel->all();
$this->view('domains/bulk-add', [
'groups' => $groups,
'title' => 'Bulk Add Domains'
]);
return;
}
// POST - Process bulk add
$domainsText = trim($_POST['domains'] ?? '');
$groupId = !empty($_POST['notification_group_id']) ? (int)$_POST['notification_group_id'] : null;
if (empty($domainsText)) {
$_SESSION['error'] = 'Please enter at least one domain';
$this->redirect('/domains/bulk-add');
return;
}
// Split by new lines and clean
$domainNames = array_filter(array_map('trim', explode("\n", $domainsText)));
$added = 0;
$skipped = 0;
$availableCount = 0;
$errors = [];
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;
}
$status = $this->whoisService->getDomainStatus($whoisData['expiration_date'], $whoisData['status'] ?? []);
// Track available domains
if ($status === 'available') {
$availableCount++;
}
$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
]);
$added++;
}
$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;
}
$domainIds = $_POST['domain_ids'] ?? [];
if (empty($domainIds)) {
$_SESSION['error'] = 'No domains selected';
$this->redirect('/domains');
return;
}
$refreshed = 0;
$failed = 0;
foreach ($domainIds as $id) {
$domain = $this->domainModel->find($id);
if (!$domain) continue;
$whoisData = $this->whoisService->getDomainInfo($domain['domain_name']);
if (!$whoisData) {
$failed++;
continue;
}
$status = $this->whoisService->getDomainStatus($whoisData['expiration_date'], $whoisData['status'] ?? []);
$this->domainModel->update($id, [
'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)
]);
$refreshed++;
}
$_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;
}
$domainIds = $_POST['domain_ids'] ?? [];
if (empty($domainIds)) {
$_SESSION['error'] = 'No domains selected';
$this->redirect('/domains');
return;
}
$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;
}
$domainIds = $_POST['domain_ids'] ?? [];
$groupId = !empty($_POST['group_id']) ? (int)$_POST['group_id'] : null;
if (empty($domainIds)) {
$_SESSION['error'] = 'No domains selected';
$this->redirect('/domains');
return;
}
$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;
}
$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;
}
$updated = 0;
foreach ($domainIds as $id) {
if ($this->domainModel->update($id, ['is_active' => $isActive])) {
$updated++;
}
}
$status = $isActive ? 'enabled' : 'disabled';
$_SESSION['success'] = "Monitoring $status for $updated domain(s)";
$this->redirect('/domains');
}
}

View File

@@ -0,0 +1,194 @@
<?php
namespace App\Controllers;
use Core\Controller;
use App\Models\NotificationGroup;
use App\Models\NotificationChannel;
class NotificationGroupController extends Controller
{
private NotificationGroup $groupModel;
private NotificationChannel $channelModel;
public function __construct()
{
$this->groupModel = new NotificationGroup();
$this->channelModel = new NotificationChannel();
}
public function index()
{
$groups = $this->groupModel->getAllWithChannelCount();
$this->view('groups/index', [
'groups' => $groups,
'title' => 'Notification Groups'
]);
}
public function create()
{
$this->view('groups/create', [
'title' => 'Create Notification Group'
]);
}
public function store()
{
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
$this->redirect('/groups/create');
return;
}
$name = trim($_POST['name'] ?? '');
$description = trim($_POST['description'] ?? '');
if (empty($name)) {
$_SESSION['error'] = 'Group name is required';
$this->redirect('/groups/create');
return;
}
$id = $this->groupModel->create([
'name' => $name,
'description' => $description
]);
$_SESSION['success'] = "Group '$name' created successfully";
$this->redirect("/groups/edit?id=$id");
}
public function edit()
{
$id = $_GET['id'] ?? 0;
$group = $this->groupModel->getWithDetails($id);
if (!$group) {
$_SESSION['error'] = 'Group not found';
$this->redirect('/groups');
return;
}
$this->view('groups/edit', [
'group' => $group,
'title' => 'Edit Group: ' . $group['name']
]);
}
public function update()
{
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
$this->redirect('/groups');
return;
}
$id = (int)$_POST['id'];
$name = trim($_POST['name'] ?? '');
$description = trim($_POST['description'] ?? '');
if (empty($name)) {
$_SESSION['error'] = 'Group name is required';
$this->redirect("/groups/edit?id=$id");
return;
}
$this->groupModel->update($id, [
'name' => $name,
'description' => $description
]);
$_SESSION['success'] = 'Group updated successfully';
$this->redirect("/groups/edit?id=$id");
}
public function delete()
{
$id = $_GET['id'] ?? 0;
$group = $this->groupModel->find($id);
if (!$group) {
$_SESSION['error'] = 'Group not found';
$this->redirect('/groups');
return;
}
$this->groupModel->deleteWithRelations($id);
$_SESSION['success'] = 'Group deleted successfully';
$this->redirect('/groups');
}
public function addChannel()
{
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
$this->redirect('/groups');
return;
}
$groupId = (int)$_POST['group_id'];
$channelType = $_POST['channel_type'] ?? '';
$config = $this->buildChannelConfig($channelType, $_POST);
if (!$config) {
$_SESSION['error'] = 'Invalid channel configuration';
$this->redirect("/groups/edit?id=$groupId");
return;
}
$this->channelModel->createChannel($groupId, $channelType, $config);
$_SESSION['success'] = 'Channel added successfully';
$this->redirect("/groups/edit?id=$groupId");
}
public function deleteChannel()
{
$id = $_GET['id'] ?? 0;
$groupId = $_GET['group_id'] ?? 0;
$this->channelModel->delete($id);
$_SESSION['success'] = 'Channel deleted successfully';
$this->redirect("/groups/edit?id=$groupId");
}
public function toggleChannel()
{
$id = $_GET['id'] ?? 0;
$groupId = $_GET['group_id'] ?? 0;
$this->channelModel->toggleActive($id);
$_SESSION['success'] = 'Channel status updated';
$this->redirect("/groups/edit?id=$groupId");
}
private function buildChannelConfig(string $type, array $data): ?array
{
switch ($type) {
case 'email':
if (empty($data['email'])) return null;
return ['email' => $data['email']];
case 'telegram':
if (empty($data['bot_token']) || empty($data['chat_id'])) return null;
return [
'bot_token' => $data['bot_token'],
'chat_id' => $data['chat_id']
];
case 'discord':
if (empty($data['webhook_url'])) return null;
return ['webhook_url' => $data['webhook_url']];
case 'slack':
if (empty($data['webhook_url'])) return null;
return ['webhook_url' => $data['webhook_url']];
default:
return null;
}
}
}

View File

@@ -0,0 +1,172 @@
<?php
namespace App\Controllers;
use Core\Controller;
use App\Models\Domain;
use App\Services\WhoisService;
class SearchController extends Controller
{
private Domain $domainModel;
private WhoisService $whoisService;
public function __construct()
{
$this->domainModel = new Domain();
$this->whoisService = new WhoisService();
}
public function index()
{
$query = trim($_GET['q'] ?? '');
if (empty($query)) {
$_SESSION['error'] = 'Please enter a search term';
$this->redirect('/domains');
return;
}
// Pagination parameters
$page = max(1, (int)($_GET['page'] ?? 1));
$perPage = max(10, min(100, (int)($_GET['per_page'] ?? 25)));
// Search existing domains in database
$allResults = $this->searchDomains($query);
$totalResults = count($allResults);
// Calculate pagination
$totalPages = ceil($totalResults / $perPage);
$page = min($page, max(1, $totalPages)); // Ensure page is within valid range
$offset = ($page - 1) * $perPage;
// Slice results for current page
$existingDomains = array_slice($allResults, $offset, $perPage);
// Check if query looks like a domain name
$isDomainLike = $this->isDomainFormat($query);
// If it looks like a domain and not found in database, offer WHOIS lookup
$whoisData = null;
$whoisError = null;
if ($isDomainLike && empty($allResults)) {
// Do WHOIS lookup
$whoisData = $this->whoisService->getDomainInfo($query);
if (!$whoisData) {
$whoisError = "Could not retrieve WHOIS information for '$query'";
}
}
$this->view('search/results', [
'query' => $query,
'existingDomains' => $existingDomains,
'whoisData' => $whoisData,
'whoisError' => $whoisError,
'isDomainLike' => $isDomainLike,
'pagination' => [
'current_page' => $page,
'per_page' => $perPage,
'total' => $totalResults,
'total_pages' => $totalPages,
'showing_from' => $totalResults > 0 ? $offset + 1 : 0,
'showing_to' => min($offset + $perPage, $totalResults)
],
'title' => 'Search Results'
]);
}
/**
* AJAX endpoint for live search suggestions
*/
public function suggest()
{
header('Content-Type: application/json');
$query = trim($_GET['q'] ?? '');
if (empty($query)) {
echo json_encode(['domains' => [], 'isDomainLike' => false]);
exit;
}
// Search existing domains (limit to 5 for quick results)
$db = \Core\Database::getConnection();
$sql = "SELECT d.id, d.domain_name, d.registrar, d.expiration_date, d.status, ng.name as group_name
FROM domains d
LEFT JOIN notification_groups ng ON d.notification_group_id = ng.id
WHERE d.domain_name LIKE ?
OR d.registrar LIKE ?
ORDER BY d.domain_name ASC
LIMIT 5";
$searchTerm = '%' . $query . '%';
$stmt = $db->prepare($sql);
$stmt->execute([$searchTerm, $searchTerm]);
$results = $stmt->fetchAll();
// Calculate days left for each domain
foreach ($results as &$domain) {
if (!empty($domain['expiration_date'])) {
$daysLeft = floor((strtotime($domain['expiration_date']) - time()) / 86400);
$domain['days_left'] = $daysLeft;
// Color coding
if ($daysLeft < 0) {
$domain['status_color'] = 'red';
} elseif ($daysLeft <= 30) {
$domain['status_color'] = 'orange';
} elseif ($daysLeft <= 90) {
$domain['status_color'] = 'yellow';
} else {
$domain['status_color'] = 'green';
}
} else {
$domain['days_left'] = null;
$domain['status_color'] = 'gray';
}
}
// Check if query looks like a domain
$isDomainLike = $this->isDomainFormat($query);
echo json_encode([
'domains' => $results,
'isDomainLike' => $isDomainLike,
'query' => $query
]);
exit;
}
/**
* Search domains in database
*/
private function searchDomains(string $query): array
{
$db = \Core\Database::getConnection();
$sql = "SELECT d.*, ng.name as group_name
FROM domains d
LEFT JOIN notification_groups ng ON d.notification_group_id = ng.id
WHERE d.domain_name LIKE ?
OR d.registrar LIKE ?
OR ng.name LIKE ?
ORDER BY d.domain_name ASC
LIMIT 50";
$searchTerm = '%' . $query . '%';
$stmt = $db->prepare($sql);
$stmt->execute([$searchTerm, $searchTerm, $searchTerm]);
return $stmt->fetchAll();
}
/**
* Check if string looks like a domain name
*/
private function isDomainFormat(string $query): bool
{
// Basic domain validation
return preg_match('/^[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,}$/i', $query);
}
}

View File

@@ -0,0 +1,515 @@
<?php
namespace App\Controllers;
use Core\Controller;
use App\Models\TldRegistry;
use App\Models\TldImportLog;
use App\Services\TldRegistryService;
class TldRegistryController extends Controller
{
private TldRegistry $tldModel;
private TldImportLog $importLogModel;
private TldRegistryService $tldService;
public function __construct()
{
$this->tldModel = new TldRegistry();
$this->importLogModel = new TldImportLog();
$this->tldService = new TldRegistryService();
}
/**
* Display TLD registry dashboard
*/
public function index()
{
$search = $_GET['search'] ?? '';
$status = $_GET['status'] ?? '';
$dataType = $_GET['data_type'] ?? '';
$page = max(1, (int)($_GET['page'] ?? 1));
$perPage = max(10, min(100, (int)($_GET['per_page'] ?? 50)));
$sort = $_GET['sort'] ?? 'tld';
$order = $_GET['order'] ?? 'asc';
$result = $this->tldModel->getPaginated($page, $perPage, $search, $sort, $order, $status, $dataType);
$stats = $this->tldModel->getStatistics();
$this->view('tld-registry/index', [
'tlds' => $result['tlds'],
'pagination' => $result['pagination'],
'stats' => $stats,
'filters' => [
'search' => $search,
'status' => $status,
'data_type' => $dataType,
'sort' => $sort,
'order' => $order
],
'title' => 'TLD Registry'
]);
}
/**
* Show TLD details
*/
public function show($params = [])
{
$id = $params['id'] ?? 0;
$tld = $this->tldModel->find($id);
if (!$tld) {
$_SESSION['error'] = 'TLD not found';
$this->redirect('/tld-registry');
return;
}
$this->view('tld-registry/view', [
'tld' => $tld,
'title' => 'TLD: ' . $tld['tld']
]);
}
/**
* Import TLD list from IANA
*/
public function importTldList()
{
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
$this->redirect('/tld-registry');
return;
}
try {
$stats = $this->tldService->importTldList();
$message = "TLD list import completed: ";
$message .= "{$stats['total_tlds']} total, ";
$message .= "{$stats['new_tlds']} new, ";
$message .= "{$stats['updated_tlds']} updated";
if ($stats['failed_tlds'] > 0) {
$message .= ", {$stats['failed_tlds']} failed";
}
$message .= ". Next: Import RDAP servers for these TLDs.";
$_SESSION['success'] = $message;
} catch (\Exception $e) {
$_SESSION['error'] = 'TLD list import failed: ' . $e->getMessage();
}
$this->redirect('/tld-registry');
}
/**
* Import RDAP data from IANA
*/
public function importRdap()
{
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
$this->redirect('/tld-registry');
return;
}
try {
$stats = $this->tldService->importRdapData();
$message = "RDAP import completed: ";
$message .= "{$stats['total_tlds']} total, ";
$message .= "{$stats['new_tlds']} new, ";
$message .= "{$stats['updated_tlds']} updated";
if ($stats['failed_tlds'] > 0) {
$message .= ", {$stats['failed_tlds']} failed";
}
$message .= ". Next: Import WHOIS servers for TLDs missing RDAP.";
$_SESSION['success'] = $message;
} catch (\Exception $e) {
$_SESSION['error'] = 'RDAP import failed: ' . $e->getMessage();
}
$this->redirect('/tld-registry');
}
/**
* Import WHOIS data for missing TLDs
*/
public function importWhois()
{
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
$this->redirect('/tld-registry');
return;
}
try {
$stats = $this->tldService->importWhoisDataForMissingTlds();
$remainingCount = $this->tldService->getTldsNeedingWhoisCount();
$message = "WHOIS import completed: ";
$message .= "{$stats['total_tlds']} total, ";
$message .= "{$stats['updated_tlds']} updated";
if ($stats['failed_tlds'] > 0) {
$message .= ", {$stats['failed_tlds']} failed";
}
if ($remainingCount > 0) {
$message .= ". {$remainingCount} TLDs still need WHOIS data. Run import again to continue.";
} else {
$message .= ". TLD registry setup complete! Use 'Check Updates' to monitor for changes.";
}
$_SESSION['success'] = $message;
} catch (\Exception $e) {
$_SESSION['error'] = 'WHOIS import failed: ' . $e->getMessage();
}
$this->redirect('/tld-registry');
}
/**
* Check for IANA updates
*/
public function checkUpdates()
{
try {
$updateInfo = $this->tldService->checkForUpdates();
if ($updateInfo['overall_needs_update']) {
$messages = [];
if ($updateInfo['tld_list']['needs_update']) {
$messages[] = "TLD list updated: Version " .
($updateInfo['tld_list']['current_version'] ?? 'Unknown') .
" (was " . ($updateInfo['tld_list']['last_version'] ?? 'None') . ")";
}
if ($updateInfo['rdap']['needs_update']) {
$messages[] = "RDAP data updated: " .
($updateInfo['rdap']['current_publication'] ?? 'Unknown') .
" (was " . ($updateInfo['rdap']['last_publication'] ?? 'None') . ")";
}
$_SESSION['info'] = "IANA data has been updated. " . implode(' | ', $messages);
} else {
$_SESSION['success'] = "TLD registry is up to date";
}
// Show any errors
if (!empty($updateInfo['errors'])) {
$_SESSION['warning'] = "Some checks failed: " . implode(', ', $updateInfo['errors']);
}
} catch (\Exception $e) {
$_SESSION['error'] = 'Failed to check for updates: ' . $e->getMessage();
}
$this->redirect('/tld-registry');
}
/**
* Start progressive import (universal)
*/
public function startProgressiveImport()
{
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
$this->redirect('/tld-registry');
return;
}
$importType = $_POST['import_type'] ?? '';
if (!in_array($importType, ['tld_list', 'rdap', 'whois', 'check_updates', 'complete_workflow'])) {
$_SESSION['error'] = 'Invalid import type';
$this->redirect('/tld-registry');
return;
}
try {
$result = $this->tldService->startProgressiveImport($importType);
if ($result['status'] === 'complete') {
$_SESSION['success'] = $result['message'];
$this->redirect('/tld-registry');
} else {
// Redirect to progress page
$this->redirect('/tld-registry/import-progress/' . $result['log_id']);
}
} catch (\Exception $e) {
$_SESSION['error'] = 'Failed to start import: ' . $e->getMessage();
$this->redirect('/tld-registry');
}
}
/**
* Show import progress page (universal)
*/
public function importProgress($params = [])
{
$logId = $params['log_id'] ?? 0;
if (!$logId) {
$_SESSION['error'] = 'Invalid import session';
$this->redirect('/tld-registry');
return;
}
// Get import type from log
$log = $this->importLogModel->find($logId);
if (!$log) {
$_SESSION['error'] = 'Import log not found';
$this->redirect('/tld-registry');
return;
}
$importType = $log['import_type'];
$titles = [
'tld_list' => 'TLD List Import Progress',
'rdap' => 'RDAP Import Progress',
'whois' => 'WHOIS Import Progress',
'check_updates' => 'Update Check Progress'
];
$this->view('tld-registry/import-progress', [
'log_id' => $logId,
'import_type' => $importType,
'title' => $titles[$importType] ?? 'Import Progress'
]);
}
/**
* API endpoint to get import progress
*/
public function apiGetImportProgress()
{
$logId = $_GET['log_id'] ?? 0;
if (!$logId) {
http_response_code(400);
echo json_encode(['error' => 'Log ID required']);
return;
}
try {
$result = $this->tldService->processNextBatch($logId);
echo json_encode($result);
} catch (\Exception $e) {
http_response_code(500);
echo json_encode(['error' => $e->getMessage()]);
}
}
/**
* Bulk delete TLDs
*/
public function bulkDelete()
{
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
$this->redirect('/tld-registry');
return;
}
$tldIds = $_POST['tld_ids'] ?? [];
if (empty($tldIds)) {
$_SESSION['error'] = 'No TLDs selected for deletion';
$this->redirect('/tld-registry');
return;
}
try {
$deletedCount = 0;
foreach ($tldIds as $id) {
if ($this->tldModel->delete($id)) {
$deletedCount++;
}
}
$_SESSION['success'] = "Successfully deleted {$deletedCount} TLD(s)";
} catch (\Exception $e) {
$_SESSION['error'] = 'Failed to delete TLDs: ' . $e->getMessage();
}
$this->redirect('/tld-registry');
}
/**
* Toggle TLD active status
*/
public function toggleActive($params = [])
{
$id = $params['id'] ?? 0;
$tld = $this->tldModel->find($id);
if (!$tld) {
$_SESSION['error'] = 'TLD not found';
$this->redirect('/tld-registry');
return;
}
$this->tldModel->toggleActive($id);
$status = $tld['is_active'] ? 'disabled' : 'enabled';
$_SESSION['success'] = "TLD {$tld['tld']} has been {$status}";
$this->redirect('/tld-registry');
}
/**
* Refresh TLD data from IANA
*/
public function refresh($params = [])
{
$id = $params['id'] ?? 0;
$tld = $this->tldModel->find($id);
if (!$tld) {
$_SESSION['error'] = 'TLD not found';
$this->redirect('/tld-registry');
return;
}
try {
// Remove dot from TLD for URL
$tldForUrl = ltrim($tld['tld'], '.');
$url = "https://www.iana.org/domains/root/db/{$tldForUrl}.html";
$client = new \GuzzleHttp\Client();
$response = $client->get($url);
$html = $response->getBody()->getContents();
// Extract data from HTML
$whoisServer = $this->extractWhoisServer($html);
$lastUpdated = $this->extractLastUpdated($html);
$registryUrl = $this->extractRegistryUrl($html);
$registrationDate = $this->extractRegistrationDate($html);
$updateData = [
'updated_at' => date('Y-m-d H:i:s')
];
if ($whoisServer) $updateData['whois_server'] = $whoisServer;
if ($lastUpdated) $updateData['record_last_updated'] = $lastUpdated;
if ($registryUrl) $updateData['registry_url'] = $registryUrl;
if ($registrationDate) $updateData['registration_date'] = $registrationDate;
$this->tldModel->update($id, $updateData);
$_SESSION['success'] = "TLD {$tld['tld']} data refreshed successfully";
} catch (\Exception $e) {
$_SESSION['error'] = 'Failed to refresh TLD data: ' . $e->getMessage();
}
$this->redirect('/tld-registry');
}
/**
* Show import logs
*/
public function importLogs()
{
$page = max(1, (int)($_GET['page'] ?? 1));
$perPage = max(10, min(100, (int)($_GET['per_page'] ?? 20)));
$result = $this->importLogModel->getPaginated($page, $perPage);
$importStats = $this->importLogModel->getImportStatistics();
$this->view('tld-registry/import-logs', [
'imports' => $result['logs'],
'pagination' => $result['pagination'],
'stats' => $importStats,
'title' => 'TLD Import Logs'
]);
}
/**
* API endpoint to get TLD info for a domain
*/
public function apiGetTldInfo()
{
$domain = $_GET['domain'] ?? '';
if (empty($domain)) {
http_response_code(400);
echo json_encode(['error' => 'Domain parameter is required']);
return;
}
try {
$tldInfo = $this->tldService->getTldInfo($domain);
if ($tldInfo) {
echo json_encode([
'success' => true,
'data' => $tldInfo
]);
} else {
echo json_encode([
'success' => false,
'message' => 'TLD information not found'
]);
}
} catch (\Exception $e) {
http_response_code(500);
echo json_encode([
'success' => false,
'error' => $e->getMessage()
]);
}
}
/**
* Extract WHOIS server from HTML
*/
private function extractWhoisServer(string $html): ?string
{
if (preg_match('/WHOIS Server:\s*([^\s<]+)/i', $html, $matches)) {
return trim($matches[1]);
}
return null;
}
/**
* Extract last updated date from HTML
*/
private function extractLastUpdated(string $html): ?string
{
if (preg_match('/Record last updated\s+(\d{4}-\d{2}-\d{2})/i', $html, $matches)) {
return $matches[1] . ' 00:00:00';
}
return null;
}
/**
* Extract registry URL from HTML
*/
private function extractRegistryUrl(string $html): ?string
{
if (preg_match('/URL for registration services:\s*([^\s<]+)/i', $html, $matches)) {
return trim($matches[1]);
}
return null;
}
/**
* Extract registration date from HTML
*/
private function extractRegistrationDate(string $html): ?string
{
if (preg_match('/Registration date\s+(\d{4}-\d{2}-\d{2})/i', $html, $matches)) {
return $matches[1];
}
return null;
}
}