Initial Commit
This commit is contained in:
93
app/Controllers/AuthController.php
Normal file
93
app/Controllers/AuthController.php
Normal 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');
|
||||
}
|
||||
}
|
||||
|
||||
39
app/Controllers/DashboardController.php
Normal file
39
app/Controllers/DashboardController.php
Normal 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'
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
304
app/Controllers/DebugController.php
Normal file
304
app/Controllers/DebugController.php
Normal 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
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
569
app/Controllers/DomainController.php
Normal file
569
app/Controllers/DomainController.php
Normal 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');
|
||||
}
|
||||
}
|
||||
|
||||
194
app/Controllers/NotificationGroupController.php
Normal file
194
app/Controllers/NotificationGroupController.php
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
172
app/Controllers/SearchController.php
Normal file
172
app/Controllers/SearchController.php
Normal 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);
|
||||
}
|
||||
}
|
||||
|
||||
515
app/Controllers/TldRegistryController.php
Normal file
515
app/Controllers/TldRegistryController.php
Normal 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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user