Introduce DNS monitoring: add DnsService (comprehensive DNS lookup, crt.sh discovery, Cloudflare detection, IP enrichment) and a new DnsRecord model to persist snapshots, manage diffs, and provide queries/stats. Update DomainController to support a dns_monitoring_enabled flag, refactor WHOIS/DNS refresh logic into performWhoisRefresh/performDnsRefresh, and add endpoints for refreshWhois, refreshDns and refreshAll; send notifications when DNS monitoring is toggled. Add UI templates/tabs for DNS, billing, notifications, overview, SSL and WHOIS and wire DNS data into the domain view; expose cached IP details. Add cron/check_dns.php and migration 027_add_dns_monitoring.sql (and include it in installer migration lists). Other tweaks: safer EmailHelper subject handling, TldRegistry search improvements, domain sorting using an effective status (expiring_soon), Discord channel null-safe fields, settings UI additions (domain_view_template and cron staleness warnings), and route/migration updates. This enables scheduled and manual DNS scans with persistent records and notifications.
279 lines
8.9 KiB
PHP
279 lines
8.9 KiB
PHP
<?php
|
|
|
|
namespace App\Models;
|
|
|
|
use Core\Model;
|
|
|
|
class TldRegistry extends Model
|
|
{
|
|
protected static string $table = 'tld_registry';
|
|
|
|
/**
|
|
* Get TLD by domain extension
|
|
*/
|
|
public function getByTld(string $tld): ?array
|
|
{
|
|
// Ensure TLD starts with dot
|
|
if (!str_starts_with($tld, '.')) {
|
|
$tld = '.' . $tld;
|
|
}
|
|
|
|
$stmt = $this->db->prepare("SELECT * FROM tld_registry WHERE tld = ? AND is_active = 1");
|
|
$stmt->execute([$tld]);
|
|
return $stmt->fetch() ?: null;
|
|
}
|
|
|
|
/**
|
|
* Get all active TLDs
|
|
*/
|
|
public function getAllActive(): array
|
|
{
|
|
$stmt = $this->db->query("SELECT * FROM tld_registry WHERE is_active = 1 ORDER BY tld ASC");
|
|
return $stmt->fetchAll();
|
|
}
|
|
|
|
/**
|
|
* Get TLDs that need updating (older than specified days)
|
|
*/
|
|
public function getTldsNeedingUpdate(int $daysOld = 30): array
|
|
{
|
|
$sql = "SELECT * FROM tld_registry
|
|
WHERE is_active = 1
|
|
AND (updated_at < DATE_SUB(NOW(), INTERVAL ? DAY)
|
|
OR updated_at IS NULL)
|
|
ORDER BY updated_at ASC";
|
|
|
|
$stmt = $this->db->prepare($sql);
|
|
$stmt->execute([$daysOld]);
|
|
return $stmt->fetchAll();
|
|
}
|
|
|
|
/**
|
|
* Create or update TLD registry entry
|
|
*/
|
|
public function createOrUpdate(array $data): int
|
|
{
|
|
$tld = $data['tld'];
|
|
|
|
// Check if TLD already exists
|
|
$existing = $this->getByTld($tld);
|
|
|
|
if ($existing) {
|
|
// Update existing record
|
|
$this->update($existing['id'], $data);
|
|
return $existing['id'];
|
|
} else {
|
|
// Create new record
|
|
return $this->create($data);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get TLD statistics
|
|
*/
|
|
public function getStatistics(): array
|
|
{
|
|
$stats = [
|
|
'total' => 0,
|
|
'active' => 0,
|
|
'with_rdap' => 0,
|
|
'with_whois' => 0,
|
|
'recently_updated' => 0,
|
|
'needs_update' => 0
|
|
];
|
|
|
|
// Total TLDs
|
|
$stmt = $this->db->query("SELECT COUNT(*) as count FROM tld_registry");
|
|
$stats['total'] = $stmt->fetch()['count'];
|
|
|
|
// Active TLDs
|
|
$stmt = $this->db->query("SELECT COUNT(*) as count FROM tld_registry WHERE is_active = 1");
|
|
$stats['active'] = $stmt->fetch()['count'];
|
|
|
|
// TLDs with RDAP servers
|
|
$stmt = $this->db->query("SELECT COUNT(*) as count FROM tld_registry WHERE rdap_servers IS NOT NULL AND rdap_servers != '[]' AND is_active = 1");
|
|
$stats['with_rdap'] = $stmt->fetch()['count'];
|
|
|
|
// TLDs with WHOIS servers
|
|
$stmt = $this->db->query("SELECT COUNT(*) as count FROM tld_registry WHERE whois_server IS NOT NULL AND whois_server != '' AND is_active = 1");
|
|
$stats['with_whois'] = $stmt->fetch()['count'];
|
|
|
|
// Recently updated (last 7 days)
|
|
$stmt = $this->db->query("SELECT COUNT(*) as count FROM tld_registry WHERE updated_at > DATE_SUB(NOW(), INTERVAL 7 DAY) AND is_active = 1");
|
|
$stats['recently_updated'] = $stmt->fetch()['count'];
|
|
|
|
// Needs update (older than 30 days)
|
|
$stmt = $this->db->query("SELECT COUNT(*) as count FROM tld_registry WHERE updated_at < DATE_SUB(NOW(), INTERVAL 30 DAY) AND is_active = 1");
|
|
$stats['needs_update'] = $stmt->fetch()['count'];
|
|
|
|
return $stats;
|
|
}
|
|
|
|
/**
|
|
* Get TLDs by search term
|
|
*/
|
|
public function search(string $search): array
|
|
{
|
|
$tldNorm = strtolower(trim(trim($search), '.'));
|
|
$suffix = '%.' . $tldNorm;
|
|
$sql = "SELECT * FROM tld_registry
|
|
WHERE (LOWER(TRIM(BOTH '.' FROM tld)) = ? OR LOWER(tld) LIKE ?)
|
|
ORDER BY tld ASC";
|
|
|
|
$stmt = $this->db->prepare($sql);
|
|
$stmt->execute([$tldNorm, $suffix]);
|
|
return $stmt->fetchAll();
|
|
}
|
|
|
|
/**
|
|
* Get TLDs with pagination and sorting
|
|
*/
|
|
public function getPaginated(int $page = 1, int $perPage = 50, string $search = '', string $sort = 'tld', string $order = 'asc', string $status = '', string $dataType = ''): array
|
|
{
|
|
$offset = ($page - 1) * $perPage;
|
|
|
|
// Validate sort column
|
|
$allowedSorts = ['tld', 'rdap_servers', 'whois_server', 'updated_at', 'is_active'];
|
|
if (!in_array($sort, $allowedSorts)) {
|
|
$sort = 'tld';
|
|
}
|
|
|
|
// Validate order
|
|
$order = strtolower($order) === 'desc' ? 'DESC' : 'ASC';
|
|
|
|
// Build WHERE clause
|
|
$whereConditions = [];
|
|
$params = [];
|
|
|
|
// Search filter: exact match OR TLDs ending with .{search} (e.g. "za" -> .za, .co.za, .net.za)
|
|
if (!empty($search)) {
|
|
$tldNorm = strtolower(trim(trim($search), '.'));
|
|
$suffix = '%.' . $tldNorm;
|
|
$whereConditions[] = "(LOWER(TRIM(BOTH '.' FROM tld)) = ? OR LOWER(tld) LIKE ?)";
|
|
$params = array_merge($params, [$tldNorm, $suffix]);
|
|
}
|
|
|
|
// Status filter
|
|
if ($status === 'active') {
|
|
$whereConditions[] = "is_active = 1";
|
|
} elseif ($status === 'inactive') {
|
|
$whereConditions[] = "is_active = 0";
|
|
}
|
|
|
|
// Data type filter
|
|
if ($dataType === 'with_rdap') {
|
|
$whereConditions[] = "(rdap_servers IS NOT NULL AND rdap_servers != '' AND rdap_servers != '[]')";
|
|
} elseif ($dataType === 'with_whois') {
|
|
$whereConditions[] = "(whois_server IS NOT NULL AND whois_server != '')";
|
|
} elseif ($dataType === 'with_registry') {
|
|
$whereConditions[] = "(registry_url IS NOT NULL AND registry_url != '')";
|
|
} elseif ($dataType === 'missing_data') {
|
|
$whereConditions[] = "((rdap_servers IS NULL OR rdap_servers = '' OR rdap_servers = '[]') AND (whois_server IS NULL OR whois_server = '') AND (registry_url IS NULL OR registry_url = ''))";
|
|
}
|
|
|
|
$whereClause = !empty($whereConditions) ? 'WHERE ' . implode(' AND ', $whereConditions) : '';
|
|
|
|
// Build ORDER BY clause
|
|
$orderBy = "ORDER BY $sort $order";
|
|
if ($sort === 'tld') {
|
|
$orderBy .= ", tld ASC"; // Secondary sort for consistent results
|
|
}
|
|
|
|
// Build main query
|
|
$sql = "SELECT * FROM tld_registry $whereClause $orderBy LIMIT ? OFFSET ?";
|
|
$stmt = $this->db->prepare($sql);
|
|
$stmt->execute(array_merge($params, [$perPage, $offset]));
|
|
$tlds = $stmt->fetchAll();
|
|
|
|
// Get total count
|
|
$countSql = "SELECT COUNT(*) as count FROM tld_registry $whereClause";
|
|
$countStmt = $this->db->prepare($countSql);
|
|
$countStmt->execute($params);
|
|
$total = $countStmt->fetch()['count'];
|
|
|
|
return [
|
|
'tlds' => $tlds,
|
|
'pagination' => [
|
|
'current_page' => $page,
|
|
'per_page' => $perPage,
|
|
'total' => $total,
|
|
'total_pages' => ceil($total / $perPage),
|
|
'showing_from' => $total > 0 ? $offset + 1 : 0,
|
|
'showing_to' => min($offset + $perPage, $total)
|
|
]
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Toggle TLD active status
|
|
*/
|
|
public function toggleActive(int $id): bool
|
|
{
|
|
$sql = "UPDATE tld_registry SET is_active = NOT is_active, updated_at = NOW() WHERE id = ?";
|
|
$stmt = $this->db->prepare($sql);
|
|
return $stmt->execute([$id]);
|
|
}
|
|
|
|
/**
|
|
* Get TLDs that have RDAP servers
|
|
*/
|
|
public function getTldsWithRdap(): array
|
|
{
|
|
$sql = "SELECT * FROM tld_registry
|
|
WHERE rdap_servers IS NOT NULL
|
|
AND rdap_servers != '[]'
|
|
AND is_active = 1
|
|
ORDER BY tld ASC";
|
|
|
|
$stmt = $this->db->query($sql);
|
|
return $stmt->fetchAll();
|
|
}
|
|
|
|
/**
|
|
* Get TLDs that have WHOIS servers
|
|
*/
|
|
public function getTldsWithWhois(): array
|
|
{
|
|
$sql = "SELECT * FROM tld_registry
|
|
WHERE whois_server IS NOT NULL
|
|
AND whois_server != ''
|
|
AND is_active = 1
|
|
ORDER BY tld ASC";
|
|
|
|
$stmt = $this->db->query($sql);
|
|
return $stmt->fetchAll();
|
|
}
|
|
|
|
/**
|
|
* Find TLD by extension (regardless of active status)
|
|
*/
|
|
public function findByTld(string $tld): ?array
|
|
{
|
|
if (!str_starts_with($tld, '.')) {
|
|
$tld = '.' . $tld;
|
|
}
|
|
|
|
$stmt = $this->db->prepare("SELECT * FROM tld_registry WHERE tld = ?");
|
|
$stmt->execute([$tld]);
|
|
return $stmt->fetch() ?: null;
|
|
}
|
|
|
|
/**
|
|
* Get all TLDs (regardless of active status) for export
|
|
*/
|
|
public function getAll(): array
|
|
{
|
|
$stmt = $this->db->query("SELECT * FROM tld_registry ORDER BY tld ASC");
|
|
return $stmt->fetchAll();
|
|
}
|
|
|
|
/**
|
|
* Execute a custom SQL query
|
|
*/
|
|
public function query(string $sql): array
|
|
{
|
|
$stmt = $this->db->query($sql);
|
|
return $stmt->fetchAll();
|
|
}
|
|
}
|