Add Pushover notification channel and improve status detection
Introduces Pushover as a new notification channel with priority-based alerts, device targeting, and custom sounds. Enhances domain status detection for .nl and .eu domains, ensuring accurate handling when expiration dates or explicit status flags are missing. Fixes PHP 8.x compatibility issues with null parameters in date functions and improves error handling and logging by replacing error_log() with a centralized Logger service. Updates documentation and migrations for version 1.1.1.
This commit is contained in:
@@ -53,7 +53,8 @@ class CaptchaService
|
||||
|
||||
default:
|
||||
// Unknown provider - allow through but log
|
||||
error_log("Unknown CAPTCHA provider: $provider");
|
||||
$logger = new \App\Services\Logger();
|
||||
$logger->warning('Unknown CAPTCHA provider', ['provider' => $provider]);
|
||||
return ['success' => true, 'error' => null, 'score' => null];
|
||||
}
|
||||
}
|
||||
@@ -66,7 +67,8 @@ class CaptchaService
|
||||
$secretKey = $this->captchaSettings['secret_key'] ?? '';
|
||||
|
||||
if (empty($secretKey)) {
|
||||
error_log('reCAPTCHA v2 secret key is not configured');
|
||||
$logger = new \App\Services\Logger();
|
||||
$logger->error('reCAPTCHA v2 secret key is not configured');
|
||||
return ['success' => false, 'error' => 'CAPTCHA is misconfigured. Please contact administrator.', 'score' => null];
|
||||
}
|
||||
|
||||
@@ -87,7 +89,8 @@ class CaptchaService
|
||||
|
||||
if (!isset($result['success']) || !$result['success']) {
|
||||
$errorCodes = $result['error-codes'] ?? [];
|
||||
error_log('reCAPTCHA v2 verification failed: ' . json_encode($errorCodes));
|
||||
$logger = new \App\Services\Logger();
|
||||
$logger->warning('reCAPTCHA v2 verification failed', ['error_codes' => $errorCodes]);
|
||||
return ['success' => false, 'error' => 'CAPTCHA verification failed. Please try again.', 'score' => null];
|
||||
}
|
||||
|
||||
@@ -103,7 +106,8 @@ class CaptchaService
|
||||
$threshold = floatval($this->captchaSettings['score_threshold'] ?? 0.5);
|
||||
|
||||
if (empty($secretKey)) {
|
||||
error_log('reCAPTCHA v3 secret key is not configured');
|
||||
$logger = new \App\Services\Logger();
|
||||
$logger->error('reCAPTCHA v3 secret key is not configured');
|
||||
return ['success' => false, 'error' => 'CAPTCHA is misconfigured. Please contact administrator.', 'score' => null];
|
||||
}
|
||||
|
||||
@@ -124,7 +128,8 @@ class CaptchaService
|
||||
|
||||
if (!isset($result['success']) || !$result['success']) {
|
||||
$errorCodes = $result['error-codes'] ?? [];
|
||||
error_log('reCAPTCHA v3 verification failed: ' . json_encode($errorCodes));
|
||||
$logger = new \App\Services\Logger();
|
||||
$logger->warning('reCAPTCHA v3 verification failed', ['error_codes' => $errorCodes]);
|
||||
return ['success' => false, 'error' => 'CAPTCHA verification failed. Please try again.', 'score' => null];
|
||||
}
|
||||
|
||||
@@ -132,7 +137,12 @@ class CaptchaService
|
||||
$score = floatval($result['score'] ?? 0);
|
||||
|
||||
if ($score < $threshold) {
|
||||
error_log("reCAPTCHA v3 score too low: $score (threshold: $threshold)");
|
||||
$logger = new \App\Services\Logger();
|
||||
$logger->warning('reCAPTCHA v3 score too low', [
|
||||
'score' => $score,
|
||||
'threshold' => $threshold,
|
||||
'ip' => $_SERVER['REMOTE_ADDR'] ?? 'unknown'
|
||||
]);
|
||||
return ['success' => false, 'error' => 'Security verification failed. Please try again or contact support.', 'score' => $score];
|
||||
}
|
||||
|
||||
@@ -147,7 +157,8 @@ class CaptchaService
|
||||
$secretKey = $this->captchaSettings['secret_key'] ?? '';
|
||||
|
||||
if (empty($secretKey)) {
|
||||
error_log('Turnstile secret key is not configured');
|
||||
$logger = new \App\Services\Logger();
|
||||
$logger->error('Turnstile secret key is not configured');
|
||||
return ['success' => false, 'error' => 'CAPTCHA is misconfigured. Please contact administrator.', 'score' => null];
|
||||
}
|
||||
|
||||
@@ -168,7 +179,8 @@ class CaptchaService
|
||||
|
||||
if (!isset($result['success']) || !$result['success']) {
|
||||
$errorCodes = $result['error-codes'] ?? [];
|
||||
error_log('Turnstile verification failed: ' . json_encode($errorCodes));
|
||||
$logger = new \App\Services\Logger();
|
||||
$logger->warning('Turnstile verification failed', ['error_codes' => $errorCodes]);
|
||||
return ['success' => false, 'error' => 'CAPTCHA verification failed. Please try again.', 'score' => null];
|
||||
}
|
||||
|
||||
@@ -193,14 +205,18 @@ class CaptchaService
|
||||
$response = @file_get_contents($url, false, $context);
|
||||
|
||||
if ($response === false) {
|
||||
error_log("Failed to connect to CAPTCHA verification service: $url");
|
||||
$logger = new \App\Services\Logger();
|
||||
$logger->error('Failed to connect to CAPTCHA verification service', ['url' => $url]);
|
||||
return null;
|
||||
}
|
||||
|
||||
$result = json_decode($response, true);
|
||||
|
||||
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||
error_log("Failed to parse CAPTCHA verification response: " . json_last_error_msg());
|
||||
$logger = new \App\Services\Logger();
|
||||
$logger->error('Failed to parse CAPTCHA verification response', [
|
||||
'error' => json_last_error_msg()
|
||||
]);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
186
app/Services/Channels/PushoverChannel.php
Normal file
186
app/Services/Channels/PushoverChannel.php
Normal file
@@ -0,0 +1,186 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Channels;
|
||||
|
||||
use GuzzleHttp\Client;
|
||||
use App\Services\Logger;
|
||||
|
||||
class PushoverChannel implements NotificationChannelInterface
|
||||
{
|
||||
private Client $client;
|
||||
private Logger $logger;
|
||||
private const API_URL = 'https://api.pushover.net/1/messages.json';
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->client = new Client(['timeout' => 10]);
|
||||
$this->logger = new Logger('pushover_channel');
|
||||
}
|
||||
|
||||
public function send(array $config, string $message, array $data = []): bool
|
||||
{
|
||||
// Required configuration
|
||||
if (!isset($config['api_token']) || !isset($config['user_key'])) {
|
||||
$this->logger->error('Pushover configuration incomplete', [
|
||||
'has_api_token' => isset($config['api_token']),
|
||||
'has_user_key' => isset($config['user_key'])
|
||||
]);
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
// Determine priority based on days left
|
||||
$priority = $this->getPriorityByDaysLeft($data['days_left'] ?? null);
|
||||
|
||||
// Build request payload
|
||||
$payload = [
|
||||
'token' => $config['api_token'], // Your application's API token
|
||||
'user' => $config['user_key'], // User/group key
|
||||
'message' => $message,
|
||||
'priority' => $priority,
|
||||
];
|
||||
|
||||
// Optional: Add title
|
||||
if (isset($data['domain'])) {
|
||||
$payload['title'] = '🔔 Domain Expiration Alert: ' . $data['domain'];
|
||||
} else {
|
||||
$payload['title'] = '🔔 Domain Monitor Notification';
|
||||
}
|
||||
|
||||
// Optional: Add device (if configured)
|
||||
if (!empty($config['device'])) {
|
||||
$payload['device'] = $config['device'];
|
||||
}
|
||||
|
||||
// Optional: Add sound (if configured)
|
||||
if (!empty($config['sound'])) {
|
||||
$payload['sound'] = $config['sound'];
|
||||
} else {
|
||||
// Default sounds based on priority
|
||||
$payload['sound'] = $this->getSoundByPriority($priority);
|
||||
}
|
||||
|
||||
// Optional: Add URL for domain link
|
||||
if (isset($data['domain_id'])) {
|
||||
$baseUrl = $_ENV['APP_URL'] ?? 'http://localhost';
|
||||
$payload['url'] = rtrim($baseUrl, '/') . '/domains/' . $data['domain_id'];
|
||||
$payload['url_title'] = 'View Domain Details';
|
||||
}
|
||||
|
||||
// For emergency priority (2), add retry and expire parameters
|
||||
if ($priority === 2) {
|
||||
$payload['retry'] = 300; // Retry every 5 minutes
|
||||
$payload['expire'] = 3600; // Give up after 1 hour
|
||||
}
|
||||
|
||||
// Add timestamp
|
||||
$payload['timestamp'] = time();
|
||||
|
||||
// Optional: Add HTML formatting if message contains line breaks
|
||||
if (strpos($message, "\n") !== false) {
|
||||
$payload['html'] = 1;
|
||||
$payload['message'] = nl2br(htmlspecialchars($message, ENT_QUOTES, 'UTF-8'));
|
||||
}
|
||||
|
||||
$response = $this->client->post(self::API_URL, [
|
||||
'form_params' => $payload
|
||||
]);
|
||||
|
||||
$statusCode = $response->getStatusCode();
|
||||
$body = json_decode($response->getBody()->getContents(), true);
|
||||
|
||||
if ($statusCode === 200 && isset($body['status']) && $body['status'] === 1) {
|
||||
$this->logger->info('Pushover message sent successfully', [
|
||||
'status' => $statusCode,
|
||||
'request_id' => $body['request'] ?? null,
|
||||
'priority' => $priority
|
||||
]);
|
||||
return true;
|
||||
} else {
|
||||
$this->logger->error('Pushover API returned non-success status', [
|
||||
'status' => $statusCode,
|
||||
'response' => $body
|
||||
]);
|
||||
return false;
|
||||
}
|
||||
|
||||
} catch (\GuzzleHttp\Exception\ClientException $e) {
|
||||
// Handle 4xx errors (authentication, invalid parameters, etc.)
|
||||
$response = $e->getResponse();
|
||||
$body = json_decode($response->getBody()->getContents(), true);
|
||||
|
||||
$this->logger->error('Pushover client error', [
|
||||
'status' => $response->getStatusCode(),
|
||||
'errors' => $body['errors'] ?? [],
|
||||
'message' => $e->getMessage()
|
||||
]);
|
||||
return false;
|
||||
|
||||
} catch (\Exception $e) {
|
||||
$this->logger->error('Pushover send failed', [
|
||||
'exception' => $e->getMessage(),
|
||||
'trace' => $e->getTraceAsString()
|
||||
]);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get priority based on days left until expiration
|
||||
*
|
||||
* Pushover priorities:
|
||||
* -2 = Lowest (no notification/alert)
|
||||
* -1 = Low (no sound or vibration)
|
||||
* 0 = Normal (default)
|
||||
* 1 = High (bypasses quiet hours)
|
||||
* 2 = Emergency (requires acknowledgement)
|
||||
*/
|
||||
private function getPriorityByDaysLeft(?int $daysLeft): int
|
||||
{
|
||||
if ($daysLeft === null) {
|
||||
return 0; // Normal priority for unknown
|
||||
}
|
||||
|
||||
if ($daysLeft <= 0) {
|
||||
return 2; // Emergency - Domain expired!
|
||||
}
|
||||
|
||||
if ($daysLeft <= 1) {
|
||||
return 2; // Emergency - Expires tomorrow or today
|
||||
}
|
||||
|
||||
if ($daysLeft <= 3) {
|
||||
return 1; // High - Expires very soon
|
||||
}
|
||||
|
||||
if ($daysLeft <= 7) {
|
||||
return 1; // High - Expires this week
|
||||
}
|
||||
|
||||
if ($daysLeft <= 14) {
|
||||
return 0; // Normal - Expires within 2 weeks
|
||||
}
|
||||
|
||||
return -1; // Low priority for longer timeframes
|
||||
}
|
||||
|
||||
/**
|
||||
* Get appropriate sound based on priority
|
||||
*
|
||||
* Available sounds: pushover, bike, bugle, cashregister, classical, cosmic,
|
||||
* falling, gamelan, incoming, intermission, magic, mechanical, pianobar,
|
||||
* siren, spacealarm, tugboat, alien, climb, persistent, echo, updown, vibrate, none
|
||||
*/
|
||||
private function getSoundByPriority(int $priority): string
|
||||
{
|
||||
return match($priority) {
|
||||
2 => 'siren', // Emergency
|
||||
1 => 'persistent', // High
|
||||
0 => 'pushover', // Normal (default)
|
||||
-1 => 'gamelan', // Low
|
||||
-2 => 'none', // Lowest
|
||||
default => 'pushover' // Fallback
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ use App\Services\Channels\DiscordChannel;
|
||||
use App\Services\Channels\SlackChannel;
|
||||
use App\Services\Channels\MattermostChannel;
|
||||
use App\Services\Channels\WebhookChannel;
|
||||
use App\Services\Channels\PushoverChannel;
|
||||
|
||||
class NotificationService
|
||||
{
|
||||
@@ -22,6 +23,7 @@ class NotificationService
|
||||
'slack' => new SlackChannel(),
|
||||
'mattermost' => new MattermostChannel(),
|
||||
'webhook' => new WebhookChannel(),
|
||||
'pushover' => new PushoverChannel(),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -37,7 +39,11 @@ class NotificationService
|
||||
try {
|
||||
return $this->channels[$channelType]->send($config, $message, $data);
|
||||
} catch (\Exception $e) {
|
||||
error_log("Notification send failed [$channelType]: " . $e->getMessage());
|
||||
$logger = new \App\Services\Logger();
|
||||
$logger->error("Notification send failed", [
|
||||
'channel_type' => $channelType,
|
||||
'error' => $e->getMessage()
|
||||
]);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -119,7 +125,7 @@ class NotificationService
|
||||
private function formatExpirationMessage(array $domain, int $daysLeft): string
|
||||
{
|
||||
$domainName = $domain['domain_name'];
|
||||
$expirationDate = date('F j, Y', strtotime($domain['expiration_date']));
|
||||
$expirationDate = $domain['expiration_date'] ? date('F j, Y', strtotime($domain['expiration_date'])) : 'Unknown';
|
||||
$registrar = $domain['registrar'] ?? 'Unknown';
|
||||
|
||||
if ($daysLeft <= 0) {
|
||||
@@ -285,7 +291,10 @@ class NotificationService
|
||||
$this->notifySystemUpgrade($admin['id'], $fromVersion, $toVersion, $migrationsCount);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
error_log("Failed to notify admins about upgrade: " . $e->getMessage());
|
||||
$logger = new \App\Services\Logger();
|
||||
$logger->error("Failed to notify admins about upgrade", [
|
||||
'error' => $e->getMessage()
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -303,7 +312,10 @@ class NotificationService
|
||||
);
|
||||
$stmt->execute([$daysOld]);
|
||||
} catch (\Exception $e) {
|
||||
error_log("Failed to clean old notifications: " . $e->getMessage());
|
||||
$logger = new \App\Services\Logger();
|
||||
$logger->error("Failed to clean old notifications", [
|
||||
'error' => $e->getMessage()
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,7 +65,12 @@ class WhoisService
|
||||
if ($rdapUrl) {
|
||||
$rdapData = $this->queryRDAPGeneric($domain, $rdapUrl);
|
||||
if ($rdapData) {
|
||||
error_log("RDAP Success for $domain - Status: " . json_encode($rdapData['status'] ?? []) . " | Registrar: " . ($rdapData['registrar'] ?? 'null'));
|
||||
$logger = new \App\Services\Logger();
|
||||
$logger->debug("RDAP Success", [
|
||||
'domain' => $domain,
|
||||
'status' => $rdapData['status'] ?? [],
|
||||
'registrar' => $rdapData['registrar'] ?? 'null'
|
||||
]);
|
||||
// If RDAP succeeded but is missing expiration date, try WHOIS as fallback
|
||||
// But only if the domain is not already marked as available
|
||||
$isAvailable = false;
|
||||
@@ -88,13 +93,20 @@ class WhoisService
|
||||
}
|
||||
|
||||
if ($whoisData) {
|
||||
// Parse WHOIS data to get expiration date
|
||||
// Parse WHOIS data to get expiration date and cleaner registrar
|
||||
$whoisInfo = $this->parseWhoisData($domain, $whoisData, $referralServer ?? $whoisServer);
|
||||
|
||||
// Merge expiration date from WHOIS into RDAP data
|
||||
if (!empty($whoisInfo['expiration_date'])) {
|
||||
$rdapData['expiration_date'] = $whoisInfo['expiration_date'];
|
||||
}
|
||||
|
||||
// Also merge registrar if WHOIS has a cleaner version (without "Name:" prefix)
|
||||
if (!empty($whoisInfo['registrar']) &&
|
||||
$whoisInfo['registrar'] !== 'Unknown' &&
|
||||
(!empty($rdapData['registrar']) && strpos($rdapData['registrar'], 'Name:') !== false)) {
|
||||
$rdapData['registrar'] = $whoisInfo['registrar'];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -416,11 +428,21 @@ class WhoisService
|
||||
curl_close($ch);
|
||||
|
||||
// Debug logging for RDAP requests
|
||||
error_log("RDAP Request: $rdapUrl | HTTP: $httpCode | Response Length: " . strlen($response));
|
||||
$logger = new \App\Services\Logger();
|
||||
$logger->debug("RDAP Request", [
|
||||
'url' => $rdapUrl,
|
||||
'http_code' => $httpCode,
|
||||
'response_length' => strlen($response)
|
||||
]);
|
||||
|
||||
if ($httpCode === 200 && $response) {
|
||||
$data = json_decode($response, true);
|
||||
if ($data) {
|
||||
error_log("RDAP Success - Domain: $domain | Status: " . json_encode($data['status'] ?? []) . " | Entities: " . count($data['entities'] ?? []));
|
||||
$logger->debug("RDAP Success", [
|
||||
'domain' => $domain,
|
||||
'status' => $data['status'] ?? [],
|
||||
'entities_count' => count($data['entities'] ?? [])
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -560,7 +582,12 @@ class WhoisService
|
||||
foreach ($entity['vcardArray'][1] as $vcardField) {
|
||||
if (is_array($vcardField) && count($vcardField) >= 4) {
|
||||
if ($vcardField[0] === 'fn') {
|
||||
$info['registrar'] = $vcardField[3];
|
||||
$registrarName = $vcardField[3];
|
||||
// .eu RDAP returns "Name: Company Name" - strip "Name:" prefix
|
||||
if (preg_match('/^Name:\s*(.+)/i', $registrarName, $matches)) {
|
||||
$registrarName = trim($matches[1]);
|
||||
}
|
||||
$info['registrar'] = $registrarName;
|
||||
} elseif ($vcardField[0] === 'url') {
|
||||
$info['registrar_url'] = $vcardField[3];
|
||||
}
|
||||
@@ -624,7 +651,13 @@ class WhoisService
|
||||
$fp = @fsockopen($server, $port, $errno, $errstr, $timeout);
|
||||
|
||||
if (!$fp) {
|
||||
error_log("WHOIS connection failed to $server: $errstr ($errno)");
|
||||
$logger = new \App\Services\Logger();
|
||||
$logger->warning("WHOIS connection failed", [
|
||||
'server' => $server,
|
||||
'port' => $port,
|
||||
'error' => $errstr,
|
||||
'errno' => $errno
|
||||
]);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -666,9 +699,17 @@ class WhoisService
|
||||
// Check if domain is not found/available
|
||||
$whoisDataLower = strtolower($whoisData);
|
||||
// More specific patterns to avoid false positives
|
||||
if (preg_match('/^(not found|no match|no entries found|no data found|domain not found|no such domain|available for registration|does not exist|queried object does not exist|is free|not registered)$/m', $whoisDataLower) ||
|
||||
preg_match('/^status:\s*(not found|no match|no entries found|no data found|domain not found|no such domain|available for registration|does not exist|queried object does not exist|is free|not registered)$/m', $whoisDataLower) ||
|
||||
preg_match('/^domain status:\s*(not found|no match|no entries found|no data found|domain not found|no such domain|available for registration|does not exist|queried object does not exist|is free|not registered)$/m', $whoisDataLower)) {
|
||||
if (preg_match('/^(not found|no match|no entries found|no data found|domain not found|no such domain|available for registration|does not exist|queried object does not exist|is free|not registered|available)$/m', $whoisDataLower) ||
|
||||
preg_match('/^status:\s*(not found|no match|no entries found|no data found|domain not found|no such domain|available for registration|does not exist|queried object does not exist|is free|not registered|available)$/m', $whoisDataLower) ||
|
||||
preg_match('/^domain status:\s*(not found|no match|no entries found|no data found|domain not found|no such domain|available for registration|does not exist|queried object does not exist|is free|not registered|available)$/m', $whoisDataLower)) {
|
||||
$data['status'][] = 'AVAILABLE';
|
||||
$data['registrar'] = 'Not Registered';
|
||||
return $data;
|
||||
}
|
||||
|
||||
// Special handling for .eu domains that are available
|
||||
// EURid returns "Status: AVAILABLE" in a specific format
|
||||
if (preg_match('/status:\s*available/i', $whoisDataLower)) {
|
||||
$data['status'][] = 'AVAILABLE';
|
||||
$data['registrar'] = 'Not Registered';
|
||||
return $data;
|
||||
@@ -696,6 +737,10 @@ class WhoisService
|
||||
// Extract registrar name (remove [Tag = XXX] part)
|
||||
$registrarName = preg_replace('/\s*\[Tag\s*=\s*[^\]]+\]/i', '', $nextLine);
|
||||
$registrarName = trim($registrarName);
|
||||
// .eu format: Strip "Name:" prefix if present
|
||||
if (preg_match('/^Name:\s*(.+)/i', $registrarName, $matches)) {
|
||||
$registrarName = trim($matches[1]);
|
||||
}
|
||||
if (!empty($registrarName)) {
|
||||
$data['registrar'] = $registrarName;
|
||||
$registrarFound = true;
|
||||
@@ -749,10 +794,23 @@ class WhoisService
|
||||
!preg_match('/@/', $value) &&
|
||||
!preg_match('/^\d+$/', $value) &&
|
||||
strlen($value) > 3) {
|
||||
$data['registrar'] = $value;
|
||||
$registrarFound = true;
|
||||
|
||||
// .eu format: If value starts with "Name:", extract just the name part
|
||||
if (preg_match('/^Name:\s*(.+)/i', $value, $matches)) {
|
||||
$data['registrar'] = trim($matches[1]);
|
||||
$registrarFound = true;
|
||||
} else {
|
||||
$data['registrar'] = $value;
|
||||
$registrarFound = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// .eu specific registrar format: "Name: Registrar Name" (as separate line)
|
||||
if (!$registrarFound && $key === 'name' && $currentSection === 'registrar' && !empty($value)) {
|
||||
$data['registrar'] = $value;
|
||||
$registrarFound = true;
|
||||
}
|
||||
|
||||
// Nameservers (standard format)
|
||||
if (preg_match('/(name server|nserver|nameserver)/i', $key) && !empty($value)) {
|
||||
@@ -861,8 +919,12 @@ class WhoisService
|
||||
|
||||
/**
|
||||
* Get domain status based on expiration and WHOIS status
|
||||
*
|
||||
* @param string|null $expirationDate The domain expiration date
|
||||
* @param array $statusArray WHOIS/RDAP status flags
|
||||
* @param array $whoisData Full WHOIS data (optional, for additional checks)
|
||||
*/
|
||||
public function getDomainStatus(?string $expirationDate, array $statusArray = []): string
|
||||
public function getDomainStatus(?string $expirationDate, array $statusArray = [], array $whoisData = []): string
|
||||
{
|
||||
// Check if domain is available (not registered)
|
||||
foreach ($statusArray as $status) {
|
||||
@@ -874,34 +936,70 @@ class WhoisService
|
||||
}
|
||||
}
|
||||
|
||||
// Also check if expiration date is null and no status indicates it's registered
|
||||
if ($expirationDate === null && empty($statusArray)) {
|
||||
return 'available';
|
||||
}
|
||||
|
||||
// If domain has "active" status but no expiration date, consider it active
|
||||
// This handles TLDs like .nl that don't provide expiration dates
|
||||
// This handles TLDs like .nl that don't provide expiration dates via RDAP
|
||||
foreach ($statusArray as $status) {
|
||||
if (stripos($status, 'active') !== false) {
|
||||
return 'active';
|
||||
}
|
||||
}
|
||||
|
||||
$days = $this->daysUntilExpiration($expirationDate);
|
||||
|
||||
if ($days === null) {
|
||||
return 'error';
|
||||
// Check for other positive status indicators (domain is registered)
|
||||
$registeredIndicators = ['ok', 'registered', 'client', 'server'];
|
||||
foreach ($statusArray as $status) {
|
||||
foreach ($registeredIndicators as $indicator) {
|
||||
if (stripos($status, $indicator) !== false) {
|
||||
// Domain has a registered status, check expiration
|
||||
if ($expirationDate === null) {
|
||||
// Has registered status but no expiration date (like .nl domains)
|
||||
return 'active';
|
||||
}
|
||||
break 2; // Exit both loops
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($days < 0) {
|
||||
return 'expired';
|
||||
// Check if domain has nameservers (strong indicator it's registered)
|
||||
// This handles TLDs like .eu that don't provide status or expiration dates
|
||||
if (!empty($whoisData['nameservers']) && count($whoisData['nameservers']) > 0) {
|
||||
// Domain has nameservers, so it's registered and active
|
||||
return 'active';
|
||||
}
|
||||
|
||||
if ($days <= 30) {
|
||||
return 'expiring_soon';
|
||||
// Check if domain has a registrar that's not "Unknown" or "Not Registered"
|
||||
// Another indicator the domain is registered
|
||||
if (!empty($whoisData['registrar']) &&
|
||||
$whoisData['registrar'] !== 'Unknown' &&
|
||||
$whoisData['registrar'] !== 'Not Registered') {
|
||||
// Has a valid registrar, likely registered
|
||||
if ($expirationDate === null) {
|
||||
return 'active';
|
||||
}
|
||||
}
|
||||
|
||||
return 'active';
|
||||
// If we have an expiration date, use it to determine status
|
||||
if ($expirationDate !== null) {
|
||||
$days = $this->daysUntilExpiration($expirationDate);
|
||||
|
||||
if ($days === null) {
|
||||
return 'error';
|
||||
}
|
||||
|
||||
if ($days < 0) {
|
||||
return 'expired';
|
||||
}
|
||||
|
||||
if ($days <= 30) {
|
||||
return 'expiring_soon';
|
||||
}
|
||||
|
||||
return 'active';
|
||||
}
|
||||
|
||||
// No expiration date and no clear status indicators
|
||||
// This should only happen for newly added domains or error cases
|
||||
// Return error to avoid incorrectly marking registered domains as available
|
||||
return 'error';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -920,7 +1018,7 @@ class WhoisService
|
||||
];
|
||||
}
|
||||
|
||||
$status = $this->getDomainStatus($info['expiration_date'], $info['status']);
|
||||
$status = $this->getDomainStatus($info['expiration_date'], $info['status'], $info);
|
||||
|
||||
return [
|
||||
'domain' => $domain,
|
||||
|
||||
Reference in New Issue
Block a user