166 lines
5.7 KiB
PHP
166 lines
5.7 KiB
PHP
<?php
|
|
|
|
namespace App\Vito\Plugins\LiittleCookie\VitoTechnitiumDns\Services;
|
|
|
|
/**
|
|
* Handles the mapping between Technitium's rData objects (type-specific fields)
|
|
* and the flat content/priority format that Vito expects.
|
|
*
|
|
* Also handles encoding/decoding composite record IDs since Technitium
|
|
* doesn't have a unique record ID — records are identified by domain + type + rData.
|
|
*/
|
|
class RecordMapper
|
|
{
|
|
/**
|
|
* Build a virtual record ID from the record's identifying fields.
|
|
* Technitium has no numeric record IDs, so we encode the full identity.
|
|
*/
|
|
public static function encodeRecordId(string $domain, string $type, array $rData): string
|
|
{
|
|
return base64_encode(json_encode([
|
|
'domain' => $domain,
|
|
'type' => $type,
|
|
'rData' => $rData,
|
|
]));
|
|
}
|
|
|
|
/**
|
|
* Decode a virtual record ID back into its components.
|
|
*
|
|
* @return array{domain: string, type: string, rData: array}
|
|
*/
|
|
public static function decodeRecordId(string $recordId): array
|
|
{
|
|
$decoded = json_decode(base64_decode($recordId), true);
|
|
|
|
if (! $decoded || ! isset($decoded['domain'], $decoded['type'], $decoded['rData'])) {
|
|
throw new \InvalidArgumentException('Invalid record ID format');
|
|
}
|
|
|
|
return $decoded;
|
|
}
|
|
|
|
/**
|
|
* Extract the human-readable "content" from Technitium's rData object.
|
|
*/
|
|
public static function rDataToContent(string $type, array $rData): string
|
|
{
|
|
return match (strtoupper($type)) {
|
|
'A', 'AAAA' => $rData['ipAddress'] ?? '',
|
|
'CNAME' => $rData['cname'] ?? '',
|
|
'MX' => $rData['exchange'] ?? '',
|
|
'NS' => $rData['nameServer'] ?? '',
|
|
'TXT' => $rData['text'] ?? '',
|
|
'SRV' => $rData['target'] ?? '',
|
|
'CAA' => sprintf('%d %s "%s"', $rData['flags'] ?? 0, $rData['tag'] ?? '', $rData['value'] ?? ''),
|
|
'PTR' => $rData['ptrName'] ?? '',
|
|
'SOA' => sprintf(
|
|
'%s %s',
|
|
$rData['primaryNameServer'] ?? '',
|
|
$rData['responsiblePerson'] ?? '',
|
|
),
|
|
default => json_encode($rData),
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Extract the priority from rData, if applicable for this record type.
|
|
*/
|
|
public static function rDataToPriority(string $type, array $rData): ?int
|
|
{
|
|
return match (strtoupper($type)) {
|
|
'MX' => isset($rData['preference']) ? (int) $rData['preference'] : null,
|
|
'SRV' => isset($rData['priority']) ? (int) $rData['priority'] : null,
|
|
default => null,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Convert Vito's flat content/type into Technitium API parameters for add/update.
|
|
*/
|
|
public static function contentToApiParams(string $type, string $content, ?int $priority = null): array
|
|
{
|
|
return match (strtoupper($type)) {
|
|
'A', 'AAAA' => ['ipAddress' => $content],
|
|
'CNAME' => ['cname' => $content],
|
|
'MX' => array_filter([
|
|
'exchange' => $content,
|
|
'preference' => $priority ?? 10,
|
|
]),
|
|
'NS' => ['nameServer' => $content],
|
|
'TXT' => ['text' => $content],
|
|
'SRV' => array_filter([
|
|
'target' => $content,
|
|
'priority' => $priority ?? 0,
|
|
'weight' => 0,
|
|
'port' => 0,
|
|
]),
|
|
'CAA' => self::parseCaaContent($content),
|
|
'PTR' => ['ptrName' => $content],
|
|
default => [],
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Build the "old" record parameters needed for a Technitium update call.
|
|
* The API requires specifying the old values to identify which record to update.
|
|
*/
|
|
public static function oldRecordParams(string $type, array $rData): array
|
|
{
|
|
$prefix = 'old';
|
|
|
|
return match (strtoupper($type)) {
|
|
'A', 'AAAA' => ["{$prefix}IpAddress" => $rData['ipAddress'] ?? ''],
|
|
'CNAME' => ["{$prefix}Cname" => $rData['cname'] ?? ''],
|
|
'MX' => [
|
|
"{$prefix}Exchange" => $rData['exchange'] ?? '',
|
|
"{$prefix}Preference" => $rData['preference'] ?? 10,
|
|
],
|
|
'NS' => ["{$prefix}NameServer" => $rData['nameServer'] ?? ''],
|
|
'TXT' => ["{$prefix}Text" => $rData['text'] ?? ''],
|
|
'SRV' => [
|
|
"{$prefix}Target" => $rData['target'] ?? '',
|
|
"{$prefix}Priority" => $rData['priority'] ?? 0,
|
|
"{$prefix}Weight" => $rData['weight'] ?? 0,
|
|
"{$prefix}Port" => $rData['port'] ?? 0,
|
|
],
|
|
'CAA' => [
|
|
"{$prefix}Flags" => $rData['flags'] ?? 0,
|
|
"{$prefix}Tag" => $rData['tag'] ?? '',
|
|
"{$prefix}Value" => $rData['value'] ?? '',
|
|
],
|
|
'PTR' => ["{$prefix}PtrName" => $rData['ptrName'] ?? ''],
|
|
default => [],
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Build the delete parameters from rData.
|
|
* Technitium needs the record data to identify which record to delete.
|
|
*/
|
|
public static function deleteParams(string $type, array $rData): array
|
|
{
|
|
return self::contentToApiParams(
|
|
$type,
|
|
self::rDataToContent($type, $rData),
|
|
self::rDataToPriority($type, $rData),
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Parse a CAA record content string like: 0 issue "letsencrypt.org"
|
|
*/
|
|
private static function parseCaaContent(string $content): array
|
|
{
|
|
if (preg_match('/^(\d+)\s+(\S+)\s+"?([^"]*)"?$/', $content, $matches)) {
|
|
return [
|
|
'flags' => (int) $matches[1],
|
|
'tag' => $matches[2],
|
|
'value' => $matches[3],
|
|
];
|
|
}
|
|
|
|
return ['flags' => 0, 'tag' => 'issue', 'value' => $content];
|
|
}
|
|
}
|