WHM Server Admin Helper
Build private PHP tools for WHM work: hosting accounts, packages, DNS zones, services, AutoSSL, backups, transfers, reseller tasks, and server checks.
Object signature
new PhpogWhmApiClient($config)
Classification
TypeServer Admin Helper ObjectUsage levelProductionCategories
- Admin Tools
- APIs and Webhooks
- System Health
Compatibility
Works with WHM API 1 over HTTPS when the token or reseller role has permission for the task. Use the simple methods for common admin jobs, and use rawWhmRequest() for advanced WHM features.
Constructor parameters
HostWHM server hostname or URL, usually https://server.example.com:2087.UsernameWHM username, usually root or a reseller username.API tokenWHM API token. Store this outside public web roots and never echo it.Access hashLegacy WHM access hash. API tokens are preferred and should be used for new work.Authentication methodAuthentication mode. Supported values are token and access_hash.PortSecure WHM API port when the host does not already include a port.Verify TLS certificateWhether cURL verifies TLS certificates. Keep true in production.Retry limitSmall retry count for transient 408, 429, or 5xx or cURL transport failures.When to use it
Build root or reseller toolsUse this for private dashboards, CLI jobs, provisioning scripts, migration helpers, and monitoring tasks that need WHM access.Manage hosting accountsGood fits include listing accounts, creating accounts, suspending accounts, changing packages, and checking account details.Handle common server tasksUse it for DNS zones, service checks, AutoSSL status, restore queues, transfers, feature lists, and token administration.Call cPanel for one usercallUapiForUser() helps a WHM tool run an allowed cPanel task without storing a separate cPanel token.When not to use it
One-account cPanel workUse the cPanel helper when the job only needs one cPanel account and should not have WHM power.Public unauthenticated formsDo not expose WHM actions to anonymous requests. Require login, permission checks, CSRF protection, confirmation, and audit logs.Wrong control-panel boundaryWHM has its own endpoint, port, auth header, and permission model. Do not send WHM calls through cPanel or Webmail routes.Assuming root accessServer profiles, reseller ACLs, feature lists, disabled roles, and token scopes can block individual tasks.How it works
Add WHM connection settingsPass the WHM host, username, token, timeout, and retry settings from private config.Call a helper methodUse readable methods for accounts, packages, DNS, services, AutoSSL, restores, transfers, tokens, and feature checks.The object talks to WHMIt sends the request over HTTPS, reads WHM metadata, and handles HTTP or cURL errors.You get a clean resultThe return value uses a consistent success, message, data, and debug shape for easier controller code.Advanced calls stay possiblerawWhmRequest() lets experienced users call allowed WHM API 1 functions that do not need a named helper yet.Integration notes
Treat this as privileged codeWHM calls can affect accounts and server services. Keep this object behind private admin, CLI, or queue code only.Use the smallest useful token scopeCreate tokens with only the privileges the tool needs, especially for reseller dashboards and delegated staff tools.Confirm destructive workAccount removal, suspension, DNS edits, service restarts, package changes, and reboot calls should require confirmation and audit logging.Explain permission failuresA blocked task often means token scope, reseller ACL, server profile, or package policy blocked it.Safety notes
- Never log or display WHM API tokens, access hashes, passwords, private keys, certificate bodies, authorization headers, or raw debug payloads containing secrets.
- Use CSRF checks, admin authentication, account ownership checks, and explicit confirmation before every POST/controller action that calls this object.
- Use WHM API tokens with the smallest practical privilege scope and rotate them when staff, server, or deployment access changes.
- Do not run destructive WHM methods from public GET links. Put destructive actions behind POST-only controllers and confirmation screens.
- Expect server-profile, role, reseller-ACL, token-scope, feature-list, and package-limit errors and present them as operational status, not fatal PHP errors.
- Treat DNS zone edits as high-risk because a wrong line number, record value, or mass edit can break mail or site resolution.
Security notes
- The object redacts secret-like keys in debug snapshots, but callers must still avoid writing raw provider responses containing sensitive account/server data to public logs.
- TLS verification is enabled by default and should remain enabled against production WHM servers.
- The raw passthrough method intentionally requires an explicit function name so the calling controller remains responsible for authorization and audit policy.
- Access-hash authentication is included for legacy compatibility only; API-token authentication is the preferred mode for new WHM automation.
Code
<?php
/*
* Copyright (c) 2026 Jeffery L. Paris <jparis@phpog.com>.
* Free for personal and internal use. Paid project use requires visible credit
* to Jeffery L. Paris. Corporate use requires a paid license fee unless a
* separate written license states otherwise.
*/
/**
* WHM Server Admin Helper for private root and reseller tools.
*
* This object helps PHP projects automate controlled WHM work such as account management, packages, DNS zones, services, AutoSSL, backups, transfers, reseller tasks, and server checks.
*
* Authentication:
* - Recommended: WHM API token with Authorization: whm username:token.
* - Legacy fallback: WHM access hash with Authorization: WHM username:hash.
* - Send secure remote calls to WHM port 2087 unless a service subdomain uses
* port 443.
*
* Safety notes:
* - WHM is server/reseller level. Treat every write call as privileged.
* - Use root/reseller tokens with the smallest privilege set that will work.
* - Keep TLS verification enabled in production.
* - Validate account ownership and confirmation state before destructive calls.
* - Server profiles, disabled roles, reseller ACLs, feature lists, and package
* limits can disable individual API functions.
*
* @package PHPOG\Objects
*/
class PhpogWhmApiClient {
/**
* Normalized WHM host, including scheme and optional port.
*
* @var string
*/
protected $host = '';
/**
* WHM username, usually root or a reseller username.
*
* @var string
*/
protected $username = 'root';
/**
* WHM API token or access hash. Never expose this value.
*
* @var string
*/
protected $secret = '';
/**
* Authentication mode: token or access_hash.
*
* @var string
*/
protected $auth_method = 'token';
/**
* Default secure WHM port.
*
* @var int
*/
protected $port = 2087;
/**
* Request timeout in seconds.
*
* @var int
*/
protected $timeout_seconds = 30;
/**
* Connection timeout in seconds.
*
* @var int
*/
protected $connect_timeout_seconds = 10;
/**
* Whether cURL should verify TLS certificates.
*
* @var bool
*/
protected $verify_peer = true;
/**
* HTTP user agent sent to WHM.
*
* @var string
*/
protected $user_agent = 'PHPOG WHM API Client/1.0';
/**
* Whether diagnostic methods include redacted request/response snapshots.
*
* @var bool
*/
protected $debug = false;
/**
* Automatic retry count for retryable transport/HTTP failures.
*
* @var int
*/
protected $max_retries = 1;
/**
* Last redacted request snapshot.
*
* @var array
*/
protected $last_request = array();
/**
* Last normalized response snapshot.
*
* @var array
*/
protected $last_response = array();
/**
* Build a WHM API client.
*
* Recognized config keys: host, username, api_token, access_hash, secret,
* auth_method, port, timeout_seconds, connect_timeout_seconds, verify_peer,
* user_agent, debug, and max_retries.
*
* @param array $config Client configuration values.
*/
public function __construct($config = array()) {
if (!is_array($config)) {
$config = array();
}
if (!empty($config['host'])) {
$this->host = $this->normalizeHost($config['host']);
}
if (!empty($config['username'])) {
$this->username = trim((string)$config['username']);
}
if (!empty($config['api_token'])) {
$this->secret = trim((string)$config['api_token']);
$this->auth_method = 'token';
} elseif (!empty($config['access_hash'])) {
$this->secret = $this->normalizeAccessHash($config['access_hash']);
$this->auth_method = 'access_hash';
} elseif (!empty($config['secret'])) {
$this->secret = trim((string)$config['secret']);
}
if (!empty($config['auth_method'])) {
$this->auth_method = $this->normalizeAuthMethod($config['auth_method']);
}
if (!empty($config['port'])) {
$this->port = (int)$config['port'];
}
if (!empty($config['timeout_seconds'])) {
$this->timeout_seconds = (int)$config['timeout_seconds'];
}
if (!empty($config['connect_timeout_seconds'])) {
$this->connect_timeout_seconds = (int)$config['connect_timeout_seconds'];
}
if (isset($config['verify_peer'])) {
$this->verify_peer = (bool)$config['verify_peer'];
}
if (!empty($config['user_agent'])) {
$this->user_agent = trim((string)$config['user_agent']);
}
if (isset($config['debug'])) {
$this->debug = (bool)$config['debug'];
}
if (isset($config['max_retries'])) {
$this->max_retries = (int)$config['max_retries'];
if ($this->max_retries < 0) {
$this->max_retries = 0;
}
}
if ($this->port < 1) {
$this->port = 2087;
}
if ($this->timeout_seconds < 1) {
$this->timeout_seconds = 30;
}
if ($this->connect_timeout_seconds < 1) {
$this->connect_timeout_seconds = 10;
}
}
/**
* Set token credentials for WHM API 1.
*
* @param string $host WHM server hostname or URL.
* @param string $username WHM username, usually root or a reseller.
* @param string $api_token WHM API token.
* @return $this
*/
public function setTokenCredentials($host, $username, $api_token) {
$this->host = $this->normalizeHost($host);
$this->username = trim((string)$username);
$this->secret = trim((string)$api_token);
$this->auth_method = 'token';
return $this;
}
/**
* Set legacy access-hash credentials for WHM API 1.
*
* API tokens are preferred. Access hashes remain useful for older internal
* tooling where the operator has deliberately accepted that legacy mode.
*
* @param string $host WHM server hostname or URL.
* @param string $username WHM username, usually root.
* @param string $access_hash WHM access hash content.
* @return $this
*/
public function setAccessHashCredentials($host, $username, $access_hash) {
$this->host = $this->normalizeHost($host);
$this->username = trim((string)$username);
$this->secret = $this->normalizeAccessHash($access_hash);
$this->auth_method = 'access_hash';
return $this;
}
/**
* Set a runtime option without rebuilding the object.
*
* @param string $name Option name.
* @param mixed $value Option value.
* @return $this
*/
public function setOption($name, $value) {
$name = trim((string)$name);
if ($name === 'port') {
$this->port = (int)$value;
} elseif ($name === 'timeout_seconds') {
$this->timeout_seconds = (int)$value;
} elseif ($name === 'connect_timeout_seconds') {
$this->connect_timeout_seconds = (int)$value;
} elseif ($name === 'verify_peer') {
$this->verify_peer = (bool)$value;
} elseif ($name === 'user_agent') {
$this->user_agent = trim((string)$value);
} elseif ($name === 'debug') {
$this->debug = (bool)$value;
} elseif ($name === 'max_retries') {
$this->max_retries = (int)$value;
if ($this->max_retries < 0) {
$this->max_retries = 0;
}
} elseif ($name === 'auth_method') {
$this->auth_method = $this->normalizeAuthMethod($value);
}
if ($this->port < 1) {
$this->port = 2087;
}
if ($this->timeout_seconds < 1) {
$this->timeout_seconds = 30;
}
if ($this->connect_timeout_seconds < 1) {
$this->connect_timeout_seconds = 10;
}
return $this;
}
/**
* Return the last redacted request snapshot.
*
* @return array
*/
public function getLastRequest() {
return $this->last_request;
}
/**
* Return the last normalized response snapshot.
*
* @return array
*/
public function getLastResponse() {
return $this->last_response;
}
/**
* Full-coverage WHM API 1 passthrough.
*
* Use this method for any documented WHM API 1 endpoint that does not have a
* named wrapper here, or for endpoints added after this object was published.
* The function name is intentionally explicit so calling controllers can
* authorize each action by function group.
*
* @param string $function WHM API 1 function name, for example listaccts.
* @param array $parameters API parameters.
* @param string $method GET or POST.
* @return array Normalized PHPOG response shape.
*/
public function rawWhmRequest($function, $parameters = array(), $method = 'GET') {
return $this->request($function, $parameters, $method);
}
/**
* Base WHM API 1 request method.
*
* @param string $function WHM API 1 function name.
* @param array $parameters Request parameters.
* @param string $method HTTP method.
* @return array Normalized PHPOG response shape.
*/
public function request($function, $parameters = array(), $method = 'GET') {
$function = $this->sanitizeFunctionName($function);
$method = strtoupper(trim((string)$method));
if ($method !== 'POST') {
$method = 'GET';
}
if (empty($function)) {
return $this->buildLocalFailure('Missing WHM API function name.', array());
}
if (empty($this->host) || empty($this->username) || empty($this->secret)) {
return $this->buildLocalFailure('Missing WHM host, username, or credential secret.', array(
'function' => $function
));
}
if (!is_array($parameters)) {
$parameters = array();
}
$parameters = $this->normalizeParameters($parameters);
$parameters['api.version'] = '1';
$url = $this->buildUrl($function, $parameters, $method);
$body = '';
if ($method === 'POST') {
$body = $this->buildQueryString($parameters);
}
$this->last_request = array(
'function' => $function,
'method' => $method,
'url' => $this->redactUrl($url),
'parameters' => $this->redactArray($parameters),
'auth_method' => $this->auth_method,
'port' => $this->port
);
$attempt = 0;
$response = array();
while ($attempt <= $this->max_retries) {
$attempt++;
$response = $this->sendCurl($url, $method, $body, $function, $attempt);
if (empty($response['retry'])) {
break;
}
}
$response['data']['attempts'] = $attempt;
$this->last_response = $response;
return $response;
}
/**
* Call cPanel UAPI through WHM API 1's uapi_cpanel function.
*
* This is useful for root/reseller tools that must perform account-level
* UAPI calls without storing a separate cPanel account token. cPanel UAPI
* module and function names are case-sensitive.
*
* @param string $cpanel_user Lowercase cPanel username.
* @param string $module UAPI module name, for example Email.
* @param string $function UAPI function name, for example list_pops.
* @param array $parameters UAPI function parameters.
* @return array Normalized PHPOG response shape.
*/
public function callUapiForUser($cpanel_user, $module, $function, $parameters = array()) {
$payload = array(
'cpanel_jsonapi_user' => strtolower(trim((string)$cpanel_user)),
'cpanel_jsonapi_module' => trim((string)$module),
'cpanel_jsonapi_func' => trim((string)$function),
'cpanel_jsonapi_apiversion' => '3'
);
if (is_array($parameters)) {
foreach ($parameters as $name => $value) {
$payload[$name] = $value;
}
}
return $this->request('uapi_cpanel', $payload, 'GET');
}
/**
* Execute a WHM API function on a remote server through WHM's remote API
* execution endpoint when the target server supports it.
*
* @param string $command Remote WHM API function name.
* @param array $parameters Remote function parameters.
* @return array Normalized PHPOG response shape.
*/
public function executeRemoteWhmCommand($command, $parameters = array()) {
$payload = array(
'command' => $this->sanitizeFunctionName($command)
);
if (is_array($parameters)) {
foreach ($parameters as $name => $value) {
$payload[$name] = $value;
}
}
return $this->request('execute_remote_whmapi1', $payload, 'GET');
}
/**
* Return all cPanel accounts visible to the WHM user.
*
* @param string $search Optional search term.
* @param string $searchtype Optional WHM search type.
* @return array Normalized PHPOG response shape.
*/
public function listAccounts($search = '', $searchtype = '') {
$parameters = array();
if ((string)$search !== '') {
$parameters['search'] = $search;
}
if ((string)$searchtype !== '') {
$parameters['searchtype'] = $searchtype;
}
return $this->request('listaccts', $parameters, 'GET');
}
/**
* Return one cPanel account summary.
*
* @param string $user cPanel username.
* @return array Normalized PHPOG response shape.
*/
public function accountSummary($user) {
return $this->request('accountsummary', array(
'user' => trim((string)$user)
), 'GET');
}
/**
* Create a cPanel account.
*
* Required WHM parameters are commonly username, domain, password, and package
* or plan details. Additional account options pass through unchanged.
*
* @param string $username New cPanel username.
* @param string $domain Primary domain.
* @param string $password Initial password.
* @param array $options Additional createacct parameters.
* @return array Normalized PHPOG response shape.
*/
public function createAccount($username, $domain, $password, $options = array()) {
$parameters = $this->mergeParameters(array(
'username' => trim((string)$username),
'domain' => trim((string)$domain),
'password' => (string)$password
), $options);
return $this->request('createacct', $parameters, 'GET');
}
/**
* Modify cPanel account properties.
*
* @param string $user cPanel username.
* @param array $options modifyacct parameters.
* @return array Normalized PHPOG response shape.
*/
public function modifyAccount($user, $options = array()) {
$parameters = $this->mergeParameters(array(
'user' => trim((string)$user)
), $options);
return $this->request('modifyacct', $parameters, 'GET');
}
/**
* Update multiple cPanel accounts in one WHM call.
*
* @param array $parameters massmodifyacct parameters.
* @return array Normalized PHPOG response shape.
*/
public function massModifyAccounts($parameters) {
return $this->request('massmodifyacct', $parameters, 'GET');
}
/**
* Suspend a cPanel account.
*
* @param string $user cPanel username.
* @param string $reason Optional suspension reason.
* @return array Normalized PHPOG response shape.
*/
public function suspendAccount($user, $reason = '') {
$parameters = array(
'user' => trim((string)$user)
);
if ((string)$reason !== '') {
$parameters['reason'] = $reason;
}
return $this->request('suspendacct', $parameters, 'GET');
}
/**
* Unsuspend a cPanel account.
*
* @param string $user cPanel username.
* @return array Normalized PHPOG response shape.
*/
public function unsuspendAccount($user) {
return $this->request('unsuspendacct', array(
'user' => trim((string)$user)
), 'GET');
}
/**
* Remove a cPanel account.
*
* @param string $user cPanel username.
* @param bool $keep_dns Whether to keep DNS records.
* @return array Normalized PHPOG response shape.
*/
public function removeAccount($user, $keep_dns = false) {
$parameters = array(
'user' => trim((string)$user)
);
if (!empty($keep_dns)) {
$parameters['keepdns'] = 1;
}
return $this->request('removeacct', $parameters, 'GET');
}
/**
* Change a cPanel account package.
*
* @param string $user cPanel username.
* @param string $package New package name.
* @return array Normalized PHPOG response shape.
*/
public function changePackage($user, $package) {
return $this->request('changepackage', array(
'user' => trim((string)$user),
'pkg' => trim((string)$package)
), 'GET');
}
/**
* Change a cPanel account password.
*
* @param string $user cPanel username.
* @param string $password New password.
* @return array Normalized PHPOG response shape.
*/
public function changeAccountPassword($user, $password) {
return $this->request('passwd', array(
'user' => trim((string)$user),
'password' => (string)$password
), 'GET');
}
/**
* Return suspended cPanel accounts.
*
* @return array Normalized PHPOG response shape.
*/
public function listSuspendedAccounts() {
return $this->request('listsuspended', array(), 'GET');
}
/**
* Return domain userdata for an account/domain.
*
* @param string $domain Domain name.
* @return array Normalized PHPOG response shape.
*/
public function domainUserData($domain) {
return $this->request('domainuserdata', array(
'domain' => trim((string)$domain)
), 'GET');
}
/**
* Return all known domain information.
*
* @return array Normalized PHPOG response shape.
*/
public function getDomainInfo() {
return $this->request('get_domain_info', array(), 'GET');
}
/**
* Convert an addon domain into its own cPanel account where supported.
*
* @param string $domain Addon domain.
* @param array $options Conversion options.
* @return array Normalized PHPOG response shape.
*/
public function convertAddonDomainToAccount($domain, $options = array()) {
$parameters = $this->mergeParameters(array(
'domain' => trim((string)$domain)
), $options);
return $this->request('convert_addon_domain_to_account', $parameters, 'GET');
}
/**
* Return package information.
*
* @param string $package Optional package name.
* @return array Normalized PHPOG response shape.
*/
public function listPackages($package = '') {
$parameters = array();
if ((string)$package !== '') {
$parameters['pkg'] = $package;
}
return $this->request('listpkgs', $parameters, 'GET');
}
/**
* Add a WHM package.
*
* @param string $package Package name.
* @param array $limits Package limit and feature parameters.
* @return array Normalized PHPOG response shape.
*/
public function addPackage($package, $limits = array()) {
$parameters = $this->mergeParameters(array(
'name' => trim((string)$package)
), $limits);
return $this->request('addpkg', $parameters, 'GET');
}
/**
* Edit a WHM package.
*
* @param string $package Package name.
* @param array $limits Package limit and feature parameters.
* @return array Normalized PHPOG response shape.
*/
public function editPackage($package, $limits = array()) {
$parameters = $this->mergeParameters(array(
'name' => trim((string)$package)
), $limits);
return $this->request('editpkg', $parameters, 'GET');
}
/**
* Delete a WHM package.
*
* @param string $package Package name.
* @return array Normalized PHPOG response shape.
*/
public function deletePackage($package) {
return $this->request('killpkg', array(
'pkg' => trim((string)$package)
), 'GET');
}
/**
* Return all reseller accounts visible to the WHM user.
*
* @return array Normalized PHPOG response shape.
*/
public function listResellers() {
return $this->request('listresellers', array(), 'GET');
}
/**
* Make a cPanel account a reseller.
*
* @param string $user cPanel username.
* @param array $options setupreseller parameters.
* @return array Normalized PHPOG response shape.
*/
public function setupReseller($user, $options = array()) {
$parameters = $this->mergeParameters(array(
'user' => trim((string)$user)
), $options);
return $this->request('setupreseller', $parameters, 'GET');
}
/**
* Remove reseller privileges from a cPanel account.
*
* @param string $user cPanel username.
* @return array Normalized PHPOG response shape.
*/
public function unsetupReseller($user) {
return $this->request('unsetupreseller', array(
'user' => trim((string)$user)
), 'GET');
}
/**
* Set reseller privileges using WHM's setacls function.
*
* @param string $reseller Reseller username.
* @param array $acl_values ACL parameters.
* @return array Normalized PHPOG response shape.
*/
public function setResellerAcls($reseller, $acl_values = array()) {
$parameters = $this->mergeParameters(array(
'reseller' => trim((string)$reseller)
), $acl_values);
return $this->request('setacls', $parameters, 'GET');
}
/**
* Return server hostname.
*
* @return array Normalized PHPOG response shape.
*/
public function getHostname() {
return $this->request('gethostname', array(), 'GET');
}
/**
* Set server hostname.
*
* @param string $hostname New hostname.
* @return array Normalized PHPOG response shape.
*/
public function setHostname($hostname) {
return $this->request('sethostname', array(
'hostname' => trim((string)$hostname)
), 'GET');
}
/**
* Return disk usage information.
*
* @return array Normalized PHPOG response shape.
*/
public function getDiskUsage() {
return $this->request('getdiskusage', array(), 'GET');
}
/**
* Return server load averages.
*
* @return array Normalized PHPOG response shape.
*/
public function systemLoadAverage() {
return $this->request('systemloadavg', array(), 'GET');
}
/**
* Return whether the system needs a reboot.
*
* @return array Normalized PHPOG response shape.
*/
public function systemNeedsReboot() {
return $this->request('system_needs_reboot', array(), 'GET');
}
/**
* Request a server reboot. Use only behind explicit operator confirmation.
*
* @return array Normalized PHPOG response shape.
*/
public function rebootServer() {
return $this->request('reboot', array(), 'GET');
}
/**
* Return WHM service status.
*
* @param string $service Optional service name filter where supported.
* @return array Normalized PHPOG response shape.
*/
public function serviceStatus($service = '') {
$parameters = array();
if ((string)$service !== '') {
$parameters['service'] = $service;
}
return $this->request('servicestatus', $parameters, 'GET');
}
/**
* Restart a WHM-managed service.
*
* @param string $service Service name.
* @param bool $restart Force restart when supported.
* @return array Normalized PHPOG response shape.
*/
public function restartService($service, $restart = true) {
return $this->request('restartservice', array(
'service' => trim((string)$service),
'restart' => !empty($restart) ? 1 : 0
), 'GET');
}
/**
* Return service configuration settings where supported.
*
* @param string $service Service name.
* @return array Normalized PHPOG response shape.
*/
public function getServiceConfig($service) {
return $this->request('get_service_config', array(
'service' => trim((string)$service)
), 'GET');
}
/**
* Return service proxy backends for a cPanel account where supported.
*
* @param string $user cPanel username.
* @return array Normalized PHPOG response shape.
*/
public function getServiceProxyBackends($user) {
return $this->request('get_service_proxy_backends', array(
'user' => trim((string)$user)
), 'GET');
}
/**
* Return server DNS zones.
*
* @return array Normalized PHPOG response shape.
*/
public function listZones() {
return $this->request('listzones', array(), 'GET');
}
/**
* Return a parsed DNS zone.
*
* @param string $domain Zone domain.
* @return array Normalized PHPOG response shape.
*/
public function parseDnsZone($domain) {
return $this->request('parse_dns_zone', array(
'domain' => trim((string)$domain)
), 'GET');
}
/**
* Return a DNS zone record by line number.
*
* @param string $domain Zone domain.
* @param int $line Zone record line number.
* @return array Normalized PHPOG response shape.
*/
public function getZoneRecord($domain, $line) {
return $this->request('getzonerecord', array(
'domain' => trim((string)$domain),
'Line' => (int)$line
), 'GET');
}
/**
* Add a DNS zone record.
*
* WHM accepts record details as parameters. Keep caller-side validation tight
* because incorrect records can break resolution.
*
* @param string $domain Zone domain.
* @param array $record Record parameters.
* @return array Normalized PHPOG response shape.
*/
public function addZoneRecord($domain, $record = array()) {
$parameters = $this->mergeParameters(array(
'domain' => trim((string)$domain)
), $record);
return $this->request('addzonerecord', $parameters, 'GET');
}
/**
* Edit a DNS zone record.
*
* @param string $domain Zone domain.
* @param int $line Zone record line number.
* @param array $record Replacement record parameters.
* @return array Normalized PHPOG response shape.
*/
public function editZoneRecord($domain, $line, $record = array()) {
$parameters = $this->mergeParameters(array(
'domain' => trim((string)$domain),
'Line' => (int)$line
), $record);
return $this->request('editzonerecord', $parameters, 'POST');
}
/**
* Remove a DNS zone record by line number.
*
* @param string $domain Zone domain.
* @param int $line Zone record line number.
* @return array Normalized PHPOG response shape.
*/
public function removeZoneRecord($domain, $line) {
return $this->request('removezonerecord', array(
'domain' => trim((string)$domain),
'Line' => (int)$line
), 'GET');
}
/**
* Mass-edit a DNS zone.
*
* @param string $domain Zone domain.
* @param array $zone_edit_params mass_edit_dns_zone parameters.
* @return array Normalized PHPOG response shape.
*/
public function massEditDnsZone($domain, $zone_edit_params = array()) {
$parameters = $this->mergeParameters(array(
'domain' => trim((string)$domain)
), $zone_edit_params);
return $this->request('mass_edit_dns_zone', $parameters, 'GET');
}
/**
* Export zone files in zone-file format where supported.
*
* @param array $parameters export_zone_files parameters.
* @return array Normalized PHPOG response shape.
*/
public function exportZoneFiles($parameters = array()) {
return $this->request('export_zone_files', $parameters, 'GET');
}
/**
* Delete a DNS zone.
*
* @param string $domain Zone domain.
* @return array Normalized PHPOG response shape.
*/
public function killDnsZone($domain) {
return $this->request('killdns', array(
'domain' => trim((string)$domain)
), 'GET');
}
/**
* Update WHM's reverse DNS cache.
*
* @return array Normalized PHPOG response shape.
*/
public function updateReverseDnsCache() {
return $this->request('update_reverse_dns_cache', array(), 'GET');
}
/**
* Return all AutoSSL providers or provider metadata where supported.
*
* @return array Normalized PHPOG response shape.
*/
public function getAutoSslProviders() {
return $this->request('get_autossl_providers', array(), 'GET');
}
/**
* Return AutoSSL metadata.
*
* @param array $parameters set/get-specific parameters when required by WHM.
* @return array Normalized PHPOG response shape.
*/
public function getAutoSslMetadata($parameters = array()) {
return $this->request('get_autossl_metadata', $parameters, 'GET');
}
/**
* Update AutoSSL metadata.
*
* @param array $parameters set_autossl_metadata parameters.
* @return array Normalized PHPOG response shape.
*/
public function setAutoSslMetadata($parameters = array()) {
return $this->request('set_autossl_metadata', $parameters, 'GET');
}
/**
* Start AutoSSL for all users where supported.
*
* @return array Normalized PHPOG response shape.
*/
public function startAutoSslForAllUsers() {
return $this->request('start_autossl_check_for_all_users', array(), 'GET');
}
/**
* Start AutoSSL for one user where supported.
*
* @param string $user cPanel username.
* @return array Normalized PHPOG response shape.
*/
public function startAutoSslForUser($user) {
return $this->request('start_autossl_check_for_one_user', array(
'username' => trim((string)$user)
), 'GET');
}
/**
* Return WHM API tokens.
*
* @return array Normalized PHPOG response shape.
*/
public function listApiTokens() {
return $this->request('api_token_list', array(), 'GET');
}
/**
* Create a WHM API token.
*
* @param string $token_name Token name.
* @param array $options Token privilege/expiration options.
* @return array Normalized PHPOG response shape.
*/
public function createApiToken($token_name, $options = array()) {
$parameters = $this->mergeParameters(array(
'token_name' => trim((string)$token_name)
), $options);
return $this->request('api_token_create', $parameters, 'GET');
}
/**
* Update WHM API token settings.
*
* @param string $token_name Token name.
* @param array $options Token settings.
* @return array Normalized PHPOG response shape.
*/
public function updateApiToken($token_name, $options = array()) {
$parameters = $this->mergeParameters(array(
'token_name' => trim((string)$token_name)
), $options);
return $this->request('api_token_update', $parameters, 'GET');
}
/**
* Revoke a WHM API token.
*
* @param string $token_name Token name.
* @return array Normalized PHPOG response shape.
*/
public function revokeApiToken($token_name) {
return $this->request('api_token_revoke', array(
'token_name' => trim((string)$token_name)
), 'GET');
}
/**
* Return all available feature names.
*
* @return array Normalized PHPOG response shape.
*/
public function getFeatureNames() {
return $this->request('get_feature_names', array(), 'GET');
}
/**
* Return all feature lists.
*
* @return array Normalized PHPOG response shape.
*/
public function listFeatureLists() {
return $this->request('get_feature_lists', array(), 'GET');
}
/**
* Return feature list data.
*
* @param string $feature_list Feature list name.
* @return array Normalized PHPOG response shape.
*/
public function getFeatureList($feature_list) {
return $this->request('get_feature_list', array(
'featurelist' => trim((string)$feature_list)
), 'GET');
}
/**
* Return cPHulk status where available.
*
* @return array Normalized PHPOG response shape.
*/
public function getCpHulkStatus() {
return $this->request('cphulk_status', array(), 'GET');
}
/**
* Add one or more cPHulk whitelist entries.
*
* @param array $parameters cphulk whitelist parameters.
* @return array Normalized PHPOG response shape.
*/
public function createCpHulkWhitelist($parameters = array()) {
return $this->request('cphulkdwhitelist_create', $parameters, 'GET');
}
/**
* Add one or more cPHulk blacklist entries.
*
* @param array $parameters cphulk blacklist parameters.
* @return array Normalized PHPOG response shape.
*/
public function createCpHulkBlacklist($parameters = array()) {
return $this->request('cphulkdblacklist_create', $parameters, 'GET');
}
/**
* Return restore queue pending items.
*
* @return array Normalized PHPOG response shape.
*/
public function restoreQueueListPending() {
return $this->request('restore_queue_list_pending', array(), 'GET');
}
/**
* Return restore queue completed items.
*
* @return array Normalized PHPOG response shape.
*/
public function restoreQueueListCompleted() {
return $this->request('restore_queue_list_completed', array(), 'GET');
}
/**
* Queue an account restore operation where supported.
*
* @param array $parameters restore account parameters.
* @return array Normalized PHPOG response shape.
*/
public function restoreAccount($parameters = array()) {
return $this->request('restorepkg', $parameters, 'GET');
}
/**
* Return transfer sessions.
*
* @param array $parameters transfer session parameters.
* @return array Normalized PHPOG response shape.
*/
public function listTransferSessions($parameters = array()) {
return $this->request('list_transfer_sessions', $parameters, 'GET');
}
/**
* Start a transfer session where supported.
*
* @param array $parameters transfer start parameters.
* @return array Normalized PHPOG response shape.
*/
public function startTransferSession($parameters = array()) {
return $this->request('start_transfer_session', $parameters, 'GET');
}
/**
* Restrict root WHM password login by CIDR list where supported.
*
* Use of the underlying API replaces prior restrictions, so the caller must
* include the complete intended list, not just a delta.
*
* @param array $cidr_list CIDR entries.
* @return array Normalized PHPOG response shape.
*/
public function restrictRootLoginByCidr($cidr_list) {
$parameters = array();
if (is_array($cidr_list)) {
$parameters['repeat:cidr'] = $cidr_list;
} else {
$parameters['cidr'] = trim((string)$cidr_list);
}
return $this->request('restrict_whm_root_access', $parameters, 'GET');
}
/**
* Return ModSecurity vendor/rule status through raw parameters.
*
* @param array $parameters ModSecurity query parameters.
* @return array Normalized PHPOG response shape.
*/
public function modSecurityCall($parameters = array()) {
return $this->request('modsec_get_rules', $parameters, 'GET');
}
/**
* Return Greylisting trusted hosts.
*
* @param array $parameters Optional greylist list parameters.
* @return array Normalized PHPOG response shape.
*/
public function listGreylistTrustedHosts($parameters = array()) {
return $this->request('cpgreylist_load_trusted_hosts', $parameters, 'GET');
}
/**
* Add Greylisting trusted host entries.
*
* @param array $parameters cpgreylist_create_trusted_host parameters.
* @return array Normalized PHPOG response shape.
*/
public function addGreylistTrustedHosts($parameters = array()) {
return $this->request('cpgreylist_create_trusted_host', $parameters, 'GET');
}
/**
* Normalize host input and retain an explicit port when supplied.
*
* @param string $host Hostname or URL.
* @return string Normalized URL base.
*/
protected function normalizeHost($host) {
$host = trim((string)$host);
if ($host === '') {
return '';
}
if (strpos($host, '://') === false) {
$host = 'https://'.$host;
}
$host = rtrim($host, '/');
return $host;
}
/**
* Normalize legacy access hash formatting into a single-line value.
*
* @param string $access_hash Raw access hash.
* @return string Normalized access hash.
*/
protected function normalizeAccessHash($access_hash) {
$access_hash = (string)$access_hash;
$access_hash = str_replace(array("\r", "\n", "\t", ' '), '', $access_hash);
return $access_hash;
}
/**
* Normalize auth method names.
*
* @param string $auth_method Requested auth method.
* @return string Safe auth method.
*/
protected function normalizeAuthMethod($auth_method) {
$auth_method = strtolower(trim((string)$auth_method));
if ($auth_method === 'access_hash' || $auth_method === 'hash') {
return 'access_hash';
}
return 'token';
}
/**
* Sanitize a WHM API function name.
*
* @param string $function WHM function name.
* @return string Safe function name.
*/
protected function sanitizeFunctionName($function) {
$function = trim((string)$function);
$function = preg_replace('/[^A-Za-z0-9_]/', '', $function);
return $function;
}
/**
* Merge base parameters and caller-supplied overrides.
*
* @param array $base Required/base parameters.
* @param array $extra Optional parameters.
* @return array Merged parameters.
*/
protected function mergeParameters($base, $extra) {
if (!is_array($base)) {
$base = array();
}
if (!is_array($extra)) {
$extra = array();
}
foreach ($extra as $name => $value) {
$base[$name] = $value;
}
return $base;
}
/**
* Normalize parameters to WHM-friendly scalar values.
*
* WHM booleans are represented as 1/0. Array values are flattened with
* numeric suffixes so callers can pass repeated parameter groups safely.
*
* @param array $parameters Input parameters.
* @return array Normalized parameters.
*/
protected function normalizeParameters($parameters) {
$normalized = array();
foreach ($parameters as $name => $value) {
$name = trim((string)$name);
if ($name === '') {
continue;
}
if (strpos($name, 'repeat:') === 0) {
$name = substr($name, 7);
$name = trim((string)$name);
if ($name !== '') {
$normalized[$name] = array();
if (is_array($value)) {
foreach ($value as $repeat_value) {
if (is_bool($repeat_value)) {
$repeat_value = !empty($repeat_value) ? '1' : '0';
}
$normalized[$name][] = (string)$repeat_value;
}
} else {
$normalized[$name][] = (string)$value;
}
}
} elseif (is_bool($value)) {
$normalized[$name] = !empty($value) ? '1' : '0';
} elseif (is_array($value)) {
$counter = 0;
foreach ($value as $sub_value) {
$counter++;
if (is_bool($sub_value)) {
$sub_value = !empty($sub_value) ? '1' : '0';
}
$normalized[$name.'-'.$counter] = (string)$sub_value;
}
} elseif ($value === null) {
$normalized[$name] = '';
} else {
$normalized[$name] = (string)$value;
}
}
return $normalized;
}
/**
* Build a WHM JSON API URL.
*
* @param string $function WHM API function.
* @param array $parameters Request parameters.
* @param string $method HTTP method.
* @return string Request URL.
*/
protected function buildUrl($function, $parameters, $method) {
$host = $this->host;
$parts = parse_url($host);
if (is_array($parts) && empty($parts['port']) && $this->port > 0) {
$host .= ':'.$this->port;
}
$url = rtrim($host, '/').'/json-api/'.$function;
if ($method !== 'POST' && !empty($parameters)) {
$url .= '?'.$this->buildQueryString($parameters);
}
return $url;
}
/**
* Build an RFC3986 query string while preserving intentionally repeated
* parameter names.
*
* @param array $parameters Request parameters.
* @return string Query string.
*/
protected function buildQueryString($parameters) {
$pairs = array();
foreach ($parameters as $name => $value) {
if (is_array($value)) {
foreach ($value as $repeat_value) {
$pairs[] = rawurlencode((string)$name).'='.rawurlencode((string)$repeat_value);
}
} else {
$pairs[] = rawurlencode((string)$name).'='.rawurlencode((string)$value);
}
}
return implode('&', $pairs);
}
/**
* Send the cURL request and normalize the result.
*
* @param string $url Request URL.
* @param string $method HTTP method.
* @param string $body Encoded POST body.
* @param string $function WHM function name.
* @param int $attempt Current attempt number.
* @return array Normalized PHPOG response shape.
*/
protected function sendCurl($url, $method, $body, $function, $attempt) {
$curl = curl_init();
if ($curl === false) {
return $this->buildLocalFailure('Unable to initialize cURL.', array(
'function' => $function,
'attempt' => $attempt
));
}
$headers = array(
'Accept: application/json',
'Authorization: '.$this->buildAuthorizationHeader()
);
curl_setopt($curl, CURLOPT_URL, $url);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_HEADER, false);
curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, $this->connect_timeout_seconds);
curl_setopt($curl, CURLOPT_TIMEOUT, $this->timeout_seconds);
curl_setopt($curl, CURLOPT_USERAGENT, $this->user_agent);
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, $this->verify_peer);
curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, !empty($this->verify_peer) ? 2 : 0);
if ($method === 'POST') {
curl_setopt($curl, CURLOPT_POST, true);
curl_setopt($curl, CURLOPT_POSTFIELDS, $body);
}
$raw_body = curl_exec($curl);
$curl_errno = curl_errno($curl);
$curl_error = curl_error($curl);
$http_code = (int)curl_getinfo($curl, CURLINFO_HTTP_CODE);
$content_type = (string)curl_getinfo($curl, CURLINFO_CONTENT_TYPE);
curl_close($curl);
if ($raw_body === false) {
return array(
'success' => false,
'message' => 'WHM API transport error.',
'data' => array(
'http_code' => $http_code,
'content_type' => $content_type,
'curl_errno' => $curl_errno,
'curl_error' => $curl_error,
'function' => $function,
'attempt' => $attempt
),
'retry' => $this->isRetryableHttpCode($http_code)
);
}
$decoded = json_decode($raw_body, true);
$json_error = json_last_error();
if ($json_error !== JSON_ERROR_NONE || !is_array($decoded)) {
return array(
'success' => false,
'message' => 'WHM API returned a non-JSON or invalid JSON response.',
'data' => array(
'http_code' => $http_code,
'content_type' => $content_type,
'json_error' => function_exists('json_last_error_msg') ? json_last_error_msg() : (string)$json_error,
'raw_preview' => substr($raw_body, 0, 500),
'function' => $function,
'attempt' => $attempt
),
'retry' => $this->isRetryableHttpCode($http_code)
);
}
$metadata = array();
if (!empty($decoded['metadata']) && is_array($decoded['metadata'])) {
$metadata = $decoded['metadata'];
}
$result_value = null;
$reason = '';
if (isset($metadata['result'])) {
$result_value = $metadata['result'];
}
if (!empty($metadata['reason'])) {
$reason = (string)$metadata['reason'];
} elseif (!empty($decoded['statusmsg'])) {
$reason = (string)$decoded['statusmsg'];
} else {
$reason = 'WHM API response received.';
}
$success = false;
if ($http_code >= 200 && $http_code < 300) {
if ($result_value === null) {
$success = true;
} elseif ((string)$result_value === '1') {
$success = true;
}
}
return array(
'success' => $success,
'message' => $reason,
'data' => array(
'http_code' => $http_code,
'content_type' => $content_type,
'response' => $decoded,
'metadata' => $metadata,
'function' => $function,
'attempt' => $attempt
),
'retry' => (!$success && $this->isRetryableHttpCode($http_code))
);
}
/**
* Build the Authorization header value.
*
* @return string Authorization header value.
*/
protected function buildAuthorizationHeader() {
if ($this->auth_method === 'access_hash') {
return 'WHM '.$this->username.':'.$this->secret;
}
return 'whm '.$this->username.':'.$this->secret;
}
/**
* Determine whether an HTTP status is worth retrying.
*
* @param int $http_code HTTP status code.
* @return bool True when retryable.
*/
protected function isRetryableHttpCode($http_code) {
$http_code = (int)$http_code;
if ($http_code === 0 || $http_code === 408 || $http_code === 429) {
return true;
}
if ($http_code >= 500 && $http_code <= 599) {
return true;
}
return false;
}
/**
* Build a local failure response without contacting WHM.
*
* @param string $message Public-safe message.
* @param array $data Extra diagnostic data.
* @return array Normalized PHPOG response shape.
*/
protected function buildLocalFailure($message, $data) {
if (!is_array($data)) {
$data = array();
}
$response = array(
'success' => false,
'message' => $message,
'data' => $data,
'retry' => false
);
$this->last_response = $response;
return $response;
}
/**
* Redact a URL before storing it in diagnostics.
*
* @param string $url Request URL.
* @return string Redacted URL.
*/
protected function redactUrl($url) {
$url = (string)$url;
$url = preg_replace('/(password|pass|token|access_hash|secret|key|cert|crt|csr|authorization)=([^&]+)/i', '$1=[redacted]', $url);
return $url;
}
/**
* Redact secret-like keys in diagnostic arrays.
*
* @param array $values Input values.
* @return array Redacted values.
*/
protected function redactArray($values) {
$redacted = array();
if (!is_array($values)) {
return $redacted;
}
foreach ($values as $name => $value) {
$key = strtolower((string)$name);
if (strpos($key, 'token') !== false || strpos($key, 'password') !== false || strpos($key, 'secret') !== false || strpos($key, 'access') !== false || strpos($key, 'key') !== false || strpos($key, 'cert') !== false || strpos($key, 'csr') !== false || strpos($key, 'authorization') !== false) {
$redacted[$name] = '[redacted]';
} elseif (is_array($value)) {
$redacted[$name] = $this->redactArray($value);
} else {
$redacted[$name] = $value;
}
}
return $redacted;
}
}