VitoTechnitiumDns/Services/RecordMapper.php
Corentin BARNICHON 0d7b62539d first commit
2026-04-16 18:13:36 +02:00

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];
}
}