Add comprehensive DNS management and input validation, plus safer transfer and logging behavior. - Add CronHelper utilities for cron scripts and unify logging/formatting. - Improve InputValidator: sanitizeDomainInput and validateRootDomain (handles multi-level TLDs) and use throughout domain import/create flows to reject subdomains. - DomainController: refactor DNS refresh to support quick/deep discovery (background deep scans), add endpoints to discover, add/delete/bulk-delete DNS records, import BIND zone files, enrich IP metadata via enrichIpDetails, and strengthen bulk import/reporting messages. - DnsRecord model: add source column handling (discovered/manual/imported), avoid auto-deleting manual/imported records, and add helpers for deleting, bulk deleting, manual adding and importing zone records. - Tag, NotificationGroup and Domain transfer logic: unlink groups when ownership changes, remove tags that belong to other users, add audit logging via Logger and improved bulk transfer reporting. TagController/View: show transferable users for admins and skip global tags on transfer. - Notification channels (Discord, Mattermost, etc.) and EmailHelper: allow explicit subjects and improve payload fields based on notification type. - Add new migration 029_add_dns_record_source.sql and wire it into the installer; update migrations detection. - Add new views/partials for confirm/import/transfer modals, update various domain/group/tag templates, and update cron scripts and routes for discovery. These changes preserve manual/imported DNS records, improve root-domain validation, enable background deep discovery, and add better logging/audit trails for transfers and imports.
189 lines
6.4 KiB
PHP
189 lines
6.4 KiB
PHP
<?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 - use subject when provided (DNS, SSL, etc.)
|
|
if (!empty($data['subject'])) {
|
|
$payload['title'] = $data['subject'];
|
|
} elseif (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
|
|
};
|
|
}
|
|
}
|
|
|