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