From 7a19e78d51501ec13ef69e2331d1265ea241943d Mon Sep 17 00:00:00 2001 From: Yuri Karamian Date: Wed, 15 Jul 2026 00:03:43 +0200 Subject: [PATCH] update v1.2.5 --- README.md | 27 ++- .../ArkHostHetznerVPS/ArkHostHetznerVPS.php | 157 +++++++++++++----- modules/servers/ArkHostHetznerVPS/hooks.php | 3 +- .../template/clientarea_direct.tpl | 2 +- 4 files changed, 138 insertions(+), 51 deletions(-) diff --git a/README.md b/README.md index 0e819e4..9c06923 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,7 @@ WHMCS server module for Hetzner Cloud VPS management. - Server Type: `cx23`, `cx33`, `cx43`, `cx53` (Cost-Optimized), `cpx11`, `cpx21`, `cax11`, `ccx13`, etc. - Operating System: `ubuntu-22.04`, `debian-11`, `rocky-9`, etc. -- Datacenter: `fsn1`, `nbg1`, `hel1`, `ash`, `hil`, `sin` +- Location: `fsn1`, `nbg1`, `hel1`, `ash`, `hil`, `sin` - Backups: On/Off - Create Floating IP: On/Off - Cloud-Init YAML: Optional custom cloud-init configuration @@ -84,17 +84,19 @@ Create in Setup → Products/Services → Configurable Options: - `No|None` - `Yes|1 Floating IP` -**2\. Datacenter Selection** +**2\. Location Selection** -- Option Name: `datacenter` +- Option Name: `Location` - Option Type: `Dropdown` - Options: - - `fsn1-dc14|Falkenstein, Germany` - - `nbg1-dc3|Nuremberg, Germany` - - `hel1-dc2|Helsinki, Finland` - - `ash-dc1|Ashburn, USA` - - `hil-dc1|Hillsboro, USA` - - `sin-dc1|Singapore, Singapore` + - `fsn1|Falkenstein, Germany` + - `nbg1|Nuremberg, Germany` + - `hel1|Helsinki, Finland` + - `ash|Ashburn, USA` + - `hil|Hillsboro, USA` + - `sin|Singapore, Singapore` + +Existing configurable options named `datacenter` remain supported. Legacy values such as `fsn1-dc14` are automatically converted to their corresponding location (`fsn1`) when provisioning. **3\. Server Type** @@ -329,6 +331,13 @@ Check out our other WHMCS modules at [arkhost.com/whmcs-modules.php](https://ar ## Changelog +### v1.2.5 +**Fixed:** +- Restored server provisioning after Hetzner removed the deprecated `datacenter` request property on July 1, 2026 +- Replaced datacenter discovery with the Hetzner Locations API +- Updated client-area location display for the current server response format +- Added automatic conversion of legacy datacenter values such as `fsn1-dc14` to `fsn1` + ### v1.2.4 **Fixed:** - IP address now stored on the service at provisioning (and backfilled for existing services) so it displays in WHMCS instead of showing blank diff --git a/modules/servers/ArkHostHetznerVPS/ArkHostHetznerVPS.php b/modules/servers/ArkHostHetznerVPS/ArkHostHetznerVPS.php index cbb977f..f8304e7 100644 --- a/modules/servers/ArkHostHetznerVPS/ArkHostHetznerVPS.php +++ b/modules/servers/ArkHostHetznerVPS/ArkHostHetznerVPS.php @@ -3,7 +3,7 @@ * WHMCS Server Module - Hetzner VPS * * @package WHMCS - * @version 1.2.4 + * @version 1.2.5 * @copyright Copyright (c) ArkHost 2025 * @author ArkHost * @link https://arkhost.com @@ -104,6 +104,57 @@ function ArkHostHetznerVPS_GetConfigurableOption(array $params, $optionName) { return null; } +/** + * Convert a legacy Hetzner datacenter name (for example fsn1-dc14) to the + * corresponding location name (fsn1). Location names are normalized to + * lowercase, while numeric IDs remain unchanged. + */ +function ArkHostHetznerVPS_NormalizeLocation($location) { + if (!is_scalar($location)) { + return ''; + } + + $location = trim((string)$location); + if ($location === '') { + return ''; + } + + if (preg_match('/^([a-z0-9]+)-dc[0-9]+$/i', $location, $matches)) { + return strtolower($matches[1]); + } + + return strtolower($location); +} + +/** + * Resolve the server location while remaining compatible with configurable + * options named either "Location" or the former "Datacenter". + */ +function ArkHostHetznerVPS_GetLocationOption(array $params, $default = NULL) { + foreach (array('configoptions', 'customfields') as $collection) { + if (!isset($params[$collection]) || !is_array($params[$collection])) { + continue; + } + + // Prefer the current option name if both current and legacy options + // happen to exist on the same product. + foreach (array('location', 'datacenter') as $optionName) { + foreach ($params[$collection] as $name => $value) { + $normalizedName = strtolower(trim((string)$name)); + if ($normalizedName === $optionName && $value !== '') { + return ArkHostHetznerVPS_NormalizeLocation($value); + } + } + } + } + + // Keep using the original module setting key so existing WHMCS products + // retain their configoption3 value after upgrading. + return ArkHostHetznerVPS_NormalizeLocation( + ArkHostHetznerVPS_GetOption($params, 'datacenter', $default) + ); +} + function ArkHostHetznerVPS_API(array $params) { $url = 'https://api.hetzner.cloud/v1/'; $data = []; @@ -125,8 +176,8 @@ function ArkHostHetznerVPS_API(array $params) { $method = 'GET'; break; - case 'Datacenters': - $url .= 'datacenters'; + case 'Locations': + $url .= 'locations?per_page=50'; $method = 'GET'; break; @@ -160,10 +211,11 @@ function ArkHostHetznerVPS_API(array $params) { ) ); - // Add datacenter if specified - $datacenter = ArkHostHetznerVPS_GetOption($params, 'datacenter'); - if ($datacenter) { - $data['datacenter'] = $datacenter; + // Hetzner removed the datacenter request property on 1 July 2026. + // Normalize legacy values such as fsn1-dc14 before sending location. + $location = ArkHostHetznerVPS_GetLocationOption($params); + if ($location) { + $data['location'] = $location; } // Handle backups @@ -975,9 +1027,11 @@ function ArkHostHetznerVPS_ConfigOptions() { 'Type' => 'dropdown', 'Options' => array(), ), + // Keep the original array key and position for existing WHMCS product + // settings. Only the Hetzner-facing value and admin label have changed. 'datacenter' => array( - 'FriendlyName' => 'Datacenter', - 'Description' => 'The datacenter location for the server.', + 'FriendlyName' => 'Location', + 'Description' => 'The Hetzner location for the server (Configurable option: Location or legacy datacenter).', 'Type' => 'dropdown', 'Options' => array(), ), @@ -993,7 +1047,7 @@ function ArkHostHetznerVPS_ConfigOptions() { ), 'floating_ip_location' => array( 'FriendlyName' => 'Floating IP Location', - 'Description' => 'Location for floating IPs (same as datacenter)', + 'Description' => 'Location for floating IPs (normally the same as the server location).', 'Type' => 'dropdown', 'Options' => array(), ), @@ -1082,19 +1136,31 @@ function ArkHostHetznerVPS_ConfigOptions() { } } - // Fetch datacenters - $params['action'] = 'Datacenters'; - $datacenters = ArkHostHetznerVPS_API($params); - - if (isset($datacenters['datacenters'])) { - foreach ($datacenters['datacenters'] as $datacenter) { - $array['datacenter']['Options'] += array( - $datacenter['name'] => $datacenter['description'] . ' (' . $datacenter['location']['city'] . ')' - ); - // Also populate floating IP locations - $array['floating_ip_location']['Options'] += array( - $datacenter['location']['name'] => $datacenter['location']['city'] . ', ' . $datacenter['location']['country'] - ); + // Fetch locations. Hetzner's datacenter endpoints are deprecated + // and are scheduled for removal after 1 October 2026. + $params['action'] = 'Locations'; + $locations = ArkHostHetznerVPS_API($params); + + if (isset($locations['locations'])) { + foreach ($locations['locations'] as $location) { + if (empty($location['name'])) { + continue; + } + + $place = array_filter(array( + $location['city'] ?? '', + $location['country'] ?? '' + )); + $description = !empty($location['description']) + ? $location['description'] + : $location['name']; + $label = $description; + if (!empty($place)) { + $label .= ' (' . implode(', ', $place) . ')'; + } + + $array['datacenter']['Options'][$location['name']] = $label; + $array['floating_ip_location']['Options'][$location['name']] = $label; } } } @@ -1218,7 +1284,9 @@ function ArkHostHetznerVPS_CreateAccount(array $params) { $floatingIpParams = $params; $floatingIpParams['action'] = 'Create Floating IP'; $floatingIpParams['ip_type'] = $ipType; - $floatingIpParams['location'] = ArkHostHetznerVPS_GetOption($params, 'floating_ip_location'); + $floatingIpParams['location'] = ArkHostHetznerVPS_NormalizeLocation( + ArkHostHetznerVPS_GetOption($params, 'floating_ip_location') + ); $floatingIpParams['description'] = 'Server ID: ' . $create['server']['id']; $floatingIpParams['assign_to_server'] = true; @@ -1691,8 +1759,9 @@ function ArkHostHetznerVPS_ClientAreaAPI(array $params) { $createParams = $params; $createParams['action'] = 'Create Floating IP'; $createParams['ip_type'] = 'ipv4'; - $createParams['location'] = ArkHostHetznerVPS_GetOption($params, 'floating_ip_location') ?: - ArkHostHetznerVPS_GetOption($params, 'datacenter'); + $createParams['location'] = ArkHostHetznerVPS_NormalizeLocation( + ArkHostHetznerVPS_GetOption($params, 'floating_ip_location') + ) ?: ArkHostHetznerVPS_GetLocationOption($params); $createParams['description'] = 'WHMCS Service ID: ' . $params['serviceid']; $createParams['assign_to_server'] = true; @@ -2485,21 +2554,31 @@ function ArkHostHetznerVPS_ClientArea(array $params) { $serverInfo['bandwidth'] = 20480; // 20TB in GB (20 * 1024) $serverInfo['bandwidth_used'] = 0; // Not available via API - // Add datacenter info - format as "City, Country" - $city = isset($serverInfo['datacenter']['location']['city']) ? $serverInfo['datacenter']['location']['city'] : ''; - $country = isset($serverInfo['datacenter']['location']['country']) ? $serverInfo['datacenter']['location']['country'] : ''; - - if ($city && $country) { - $serverInfo['datacenter'] = $city . ', ' . $country; - } elseif ($city) { - $serverInfo['datacenter'] = $city; - } elseif ($country) { - $serverInfo['datacenter'] = $country; - } else { - $serverInfo['datacenter'] = 'N/A'; + // Hetzner now returns location directly on the server. Retain a fallback + // for cached/older responses that still contain datacenter.location. + $locationInfo = array(); + if (isset($serverInfo['location']) && is_array($serverInfo['location'])) { + $locationInfo = $serverInfo['location']; + } elseif (isset($serverInfo['datacenter']['location']) && is_array($serverInfo['datacenter']['location'])) { + $locationInfo = $serverInfo['datacenter']['location']; } - $serverInfo['location'] = $city ?: 'N/A'; + $city = $locationInfo['city'] ?? ''; + $country = $locationInfo['country'] ?? ''; + + if ($city && $country) { + $locationDisplay = $city . ', ' . $country; + } elseif ($city) { + $locationDisplay = $city; + } elseif ($country) { + $locationDisplay = $country; + } else { + $locationDisplay = 'N/A'; + } + + $serverInfo['location_display'] = $locationDisplay; + // Preserve the former display key for third-party/custom templates. + $serverInfo['datacenter'] = $locationDisplay; // Get root password with expiration check (72 hours) $passwordSetTime = $params['model']->serviceProperties->get('ArkHostHetznerVPS|Password Set Time'); diff --git a/modules/servers/ArkHostHetznerVPS/hooks.php b/modules/servers/ArkHostHetznerVPS/hooks.php index 11f9a33..7e7557f 100644 --- a/modules/servers/ArkHostHetznerVPS/hooks.php +++ b/modules/servers/ArkHostHetznerVPS/hooks.php @@ -3,7 +3,7 @@ * WHMCS Server Module - Hetzner VPS Hooks * * @package WHMCS - * @version 1.2.4 + * @version 1.2.5 * @copyright Copyright (c) ArkHost 2025 * @author ArkHost * @link https://arkhost.com @@ -70,4 +70,3 @@ add_hook('ClientAreaHeadOutput', 1, function($vars) { HTML; }); - diff --git a/modules/servers/ArkHostHetznerVPS/template/clientarea_direct.tpl b/modules/servers/ArkHostHetznerVPS/template/clientarea_direct.tpl index dc3c17a..b20c41b 100644 --- a/modules/servers/ArkHostHetznerVPS/template/clientarea_direct.tpl +++ b/modules/servers/ArkHostHetznerVPS/template/clientarea_direct.tpl @@ -207,7 +207,7 @@ {$ADDONLANG.Overview.Location}: - {$serverInfo['datacenter']} + {$serverInfo['location_display']} {$ADDONLANG.Overview.ServerType}: