Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 10 additions & 10 deletions inc/integrations/host-providers/class-base-host-provider.php
Original file line number Diff line number Diff line change
Expand Up @@ -100,17 +100,17 @@ public function init(): void {
// avoid text domain loaded at the wrong time.
add_action('init', [$this, 'alert_provider_not_setup']);
}
}

/*
* Load the dependencies.
*/
$this->load_dependencies();
} else {
/*
* Load the dependencies.
*/
$this->load_dependencies();

/*
* Initialize the hooks.
*/
$this->register_hooks();
/*
* Initialize the hooks.
*/
$this->register_hooks();
}
}

add_filter('wu_domain_manager_get_integrations', [$this, 'self_register']);
Expand Down
18 changes: 8 additions & 10 deletions inc/integrations/host-providers/class-closte-host-provider.php
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ public function on_add_domain($domain, $site_id): void {
if (wu_get_isset($domain_response, 'success', false)) {
wu_log_add('integration-closte', sprintf('Domain %s added successfully, requesting SSL certificate', $domain));
$this->request_ssl_certificate($domain);
} elseif (isset($domain_response['error']) && 'Invalid or empty domain: ' . $domain === $domain_response['error'] ) {
} elseif (isset($domain_response['error']) && 'Invalid or empty domain: ' . $domain === $domain_response['error']) {
wu_log_add('integration-closte', sprintf('Domain %s rejected by Closte API as invalid. This may be expected for Closte subdomains or internal domains.', $domain));
} else {
wu_log_add('integration-closte', sprintf('Failed to add domain %s. Response: %s', $domain, wp_json_encode($domain_response)));
Expand Down Expand Up @@ -218,8 +218,6 @@ private function request_ssl_certificate($domain) {
'/certificate/install',
];

$ssl_response = null;

foreach ($ssl_endpoints as $endpoint) {
wu_log_add('integration-closte', sprintf('Trying SSL endpoint: %s', $endpoint));

Expand Down Expand Up @@ -255,7 +253,7 @@ private function request_ssl_certificate($domain) {
*
* @since 2.0.0
* @param string $domain The domain to check SSL status for.
* @return array|object
* @return array
*/
public function check_ssl_status($domain) {

Expand Down Expand Up @@ -302,21 +300,21 @@ public function test_connection(): void {
* @since 1.7.3
* @param string $endpoint Endpoint to send the call to.
* @param array $data Array containing the params to the call.
* @return object
* @return array
*/
public function send_closte_api_request($endpoint, $data) {

if (defined('CLOSTE_CLIENT_API_KEY') === false) {
wu_log_add('integration-closte', 'CLOSTE_CLIENT_API_KEY constant not defined');
return (object) [
return [
'success' => false,
'error' => 'Closte API Key not found.',
];
}

if (empty(CLOSTE_CLIENT_API_KEY)) {
wu_log_add('integration-closte', 'CLOSTE_CLIENT_API_KEY is empty');
return (object) [
return [
'success' => false,
'error' => 'Closte API Key is empty.',
];
Expand Down Expand Up @@ -362,7 +360,7 @@ public function send_closte_api_request($endpoint, $data) {
// Check for HTTP errors
if ($response_code >= 400) {
wu_log_add('integration-closte', sprintf('HTTP error %d for endpoint %s', $response_code, $endpoint));
return (object) [
return [
'success' => false,
'error' => sprintf('HTTP %d error', $response_code),
'response_body' => $response_body,
Expand All @@ -384,15 +382,15 @@ public function send_closte_api_request($endpoint, $data) {
}

wu_log_add('integration-closte', sprintf('JSON decode error: %s', json_last_error_msg()));
return (object) [
return [
'success' => false,
'error' => 'Invalid JSON response',
'json_error' => json_last_error_msg(),
];
}

wu_log_add('integration-closte', 'Empty response body');
return (object) [
return [
'success' => false,
'error' => 'Empty response',
];
Expand Down
112 changes: 78 additions & 34 deletions inc/integrations/host-providers/class-enhance-host-provider.php
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,8 @@ class Enhance_Host_Provider extends Base_Host_Provider {
protected $constants = [
'WU_ENHANCE_API_TOKEN',
'WU_ENHANCE_API_URL',
'WU_ENHANCE_SERVER_ID',
'WU_ENHANCE_ORG_ID',
'WU_ENHANCE_WEBSITE_ID',
];

/**
Expand Down Expand Up @@ -98,18 +99,37 @@ public function detect(): bool {
public function get_fields() {

return [
'WU_ENHANCE_API_TOKEN' => [
'WU_ENHANCE_API_TOKEN' => [
'type' => 'password',
'html_attr' => ['autocomplete' => 'new-password'],
'title' => __('Enhance API Token', 'ultimate-multisite'),
'desc' => sprintf(
/* translators: %s is the link to the API token documentation */
__('Generate an API token in your Enhance Control Panel under Settings &rarr; API Tokens. <a href="%s" target="_blank">Learn more</a>', 'ultimate-multisite'),
'https://apidocs.enhance.com/#section/Authentication'
),
'placeholder' => __('Your bearer token', 'ultimate-multisite'),
],
'WU_ENHANCE_API_URL' => [
'WU_ENHANCE_API_URL' => [
'title' => __('Enhance API URL', 'ultimate-multisite'),
'placeholder' => __('e.g. https://your-enhance-server.com', 'ultimate-multisite'),
'desc' => __('The API URL of your Enhance Control Panel (e.g., https://your-enhance-server.com/api).', 'ultimate-multisite'),
'placeholder' => __('e.g. https://your-enhance-server.com/api', 'ultimate-multisite'),
'html_attr' => [
'id' => 'wu_enhance_api_url',
],
],
'WU_ENHANCE_SERVER_ID' => [
'title' => __('Server ID', 'ultimate-multisite'),
'placeholder' => __('UUID of your server', 'ultimate-multisite'),
'WU_ENHANCE_ORG_ID' => [
'title' => __('Organization ID', 'ultimate-multisite'),
'desc' => __('The UUID of your organization. You can find this in your Enhance Control Panel URL when viewing the organization (e.g., /org/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx).', 'ultimate-multisite'),
'placeholder' => __('e.g. xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx', 'ultimate-multisite'),
'html_attr' => [
'id' => 'wu_enhance_org_id',
],
],
'WU_ENHANCE_WEBSITE_ID' => [
'title' => __('Website ID', 'ultimate-multisite'),
'placeholder' => __('e.g. xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx', 'ultimate-multisite'),
'desc' => __('The UUID of the website where domains should be added. You can find this in your Enhance Control Panel URL when viewing a website (e.g., /websites/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx).', 'ultimate-multisite'),
],
];
}
Expand All @@ -126,24 +146,33 @@ public function on_add_domain($domain, $site_id): void {

wu_log_add('integration-enhance', sprintf('Adding domain: %s for site ID: %d', $domain, $site_id));

$server_id = defined('WU_ENHANCE_SERVER_ID') ? WU_ENHANCE_SERVER_ID : '';
$org_id = defined('WU_ENHANCE_ORG_ID') ? WU_ENHANCE_ORG_ID : '';
$website_id = defined('WU_ENHANCE_WEBSITE_ID') ? WU_ENHANCE_WEBSITE_ID : '';

if (empty($org_id)) {
wu_log_add('integration-enhance', 'Organization ID not configured');
return;
}

if (empty($server_id)) {
wu_log_add('integration-enhance', 'Server ID not configured');
if (empty($website_id)) {
wu_log_add('integration-enhance', 'Website ID not configured');
return;
}

// Add the domain to the server
// Add the domain to the website
// POST /orgs/{org_id}/websites/{website_id}/domains
$domain_data = [
'domain' => $domain,
];

$domain_response = $this->send_enhance_api_request(
'/servers/' . $server_id . '/domains',
'/orgs/' . $org_id . '/websites/' . $website_id . '/domains',
'POST',
[
'domain' => $domain,
]
$domain_data
);

// Check if domain was added successfully
if (wu_get_isset($domain_response, 'id')) {
if (wu_get_isset($domain_response, 'id') || (isset($domain_response['success']) && $domain_response['success'])) {
wu_log_add('integration-enhance', sprintf('Domain %s added successfully. SSL will be automatically provisioned via LetsEncrypt when DNS resolves.', $domain));
} elseif (isset($domain_response['error'])) {
wu_log_add('integration-enhance', sprintf('Failed to add domain %s. Error: %s', $domain, wp_json_encode($domain_response)));
Expand All @@ -164,17 +193,23 @@ public function on_remove_domain($domain, $site_id): void {

wu_log_add('integration-enhance', sprintf('Removing domain: %s for site ID: %d', $domain, $site_id));

$server_id = defined('WU_ENHANCE_SERVER_ID') ? WU_ENHANCE_SERVER_ID : '';
$org_id = defined('WU_ENHANCE_ORG_ID') ? WU_ENHANCE_ORG_ID : '';
$website_id = defined('WU_ENHANCE_WEBSITE_ID') ? WU_ENHANCE_WEBSITE_ID : '';

if (empty($server_id)) {
wu_log_add('integration-enhance', 'Server ID not configured');
if (empty($org_id)) {
wu_log_add('integration-enhance', 'Organization ID not configured');
return;
}

if (empty($website_id)) {
wu_log_add('integration-enhance', 'Website ID not configured');
return;
}

// First, get the domain ID by listing domains and finding a match
// GET /orgs/{org_id}/websites/{website_id}/domains
$domains_list = $this->send_enhance_api_request(
'/servers/' . $server_id . '/domains',
'GET'
'/orgs/' . $org_id . '/websites/' . $website_id . '/domains'
);

$domain_id = null;
Expand All @@ -194,8 +229,9 @@ public function on_remove_domain($domain, $site_id): void {
}

// Delete the domain
$delete_response = $this->send_enhance_api_request(
'/servers/' . $server_id . '/domains/' . $domain_id,
// DELETE /orgs/{org_id}/websites/{website_id}/domains/{domain_id}
$this->send_enhance_api_request(
'/orgs/' . $org_id . '/websites/' . $website_id . '/domains/' . $domain_id,
'DELETE'
);

Expand Down Expand Up @@ -240,28 +276,36 @@ public function on_remove_subdomain($subdomain, $site_id): void {
*/
public function test_connection(): void {

$server_id = defined('WU_ENHANCE_SERVER_ID') ? WU_ENHANCE_SERVER_ID : '';
$org_id = defined('WU_ENHANCE_ORG_ID') ? WU_ENHANCE_ORG_ID : '';
$website_id = defined('WU_ENHANCE_WEBSITE_ID') ? WU_ENHANCE_WEBSITE_ID : '';

if (empty($org_id)) {
$error = new \WP_Error('no-org-id', __('Organization ID is not configured', 'ultimate-multisite'));
wp_send_json_error($error);
return;
}

if (empty($server_id)) {
$error = new \WP_Error('no-server-id', __('Server ID is not configured', 'ultimate-multisite'));
if (empty($website_id)) {
$error = new \WP_Error('no-website-id', __('Website ID is not configured', 'ultimate-multisite'));
wp_send_json_error($error);
return;
}

// Test by attempting to list domains
// Test by attempting to get website info.
// GET /orgs/{org_id}/websites/{website_id}
$response = $this->send_enhance_api_request(
'/servers/' . $server_id . '/domains',
'GET'
'/orgs/' . $org_id . '/websites/' . $website_id
);

if (isset($response['items']) || isset($response['id'])) {
if (isset($response['id'])) {
wp_send_json_success(
[
'message' => __('Connection successful', 'ultimate-multisite'),
]
);
} else {
$error = new \WP_Error('connection-failed', __('Failed to connect to Enhance API', 'ultimate-multisite'));
// Translators: %s the full error message.
$error = new \WP_Error('connection-failed', sprintf(__('Failed to connect to Enhance API: %s', 'ultimate-multisite'), $response['error'] ?? 'Unknown error'));
wp_send_json_error($error);
}
}
Expand All @@ -270,9 +314,9 @@ public function test_connection(): void {
* Sends a request to the Enhance API with the configured bearer token.
*
* @since 2.0.0
* @param string $endpoint API endpoint (relative to base URL).
* @param string $method HTTP method (GET, POST, DELETE, etc.).
* @param array $data Request body data (for POST/PUT/PATCH).
* @param string $endpoint API endpoint (relative to base URL).
* @param string $method HTTP method (GET, POST, DELETE, etc.).
* @param array|string $data Request body data (for POST/PUT/PATCH).
* @return array|object
*/
public function send_enhance_api_request($endpoint, $method = 'GET', $data = []) {
Comment on lines +317 to 322
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for calls to send_enhance_api_request with potential string arguments

# First, let's see all calls to this method
rg -n 'send_enhance_api_request\s*\(' --type=php

echo "---"
# Search specifically for calls with string literals
rg -n "send_enhance_api_request\s*\([^)]*['\"]" --type=php

Repository: Multisite-Ultimate/ultimate-multisite

Length of output: 847


🏁 Script executed:

# Also check the implementation around the encoding line
cat -n inc/integrations/host-providers/class-enhance-host-provider.php | sed -n '335,350p'

Repository: Multisite-Ultimate/ultimate-multisite

Length of output: 692


🏁 Script executed:

# Let's check what arguments are actually passed in each call
rg -n -A 5 'send_enhance_api_request\(' inc/integrations/host-providers/class-enhance-host-provider.php | head -50

Repository: Multisite-Ultimate/ultimate-multisite

Length of output: 1184


🏁 Script executed:

# Also check the docblock definition to confirm the @param line
cat -n inc/integrations/host-providers/class-enhance-host-provider.php | sed -n '300,310p'

Repository: Multisite-Ultimate/ultimate-multisite

Length of output: 660


Resolve docblock/implementation mismatch for $data parameter.

The docblock indicates @param array|string $data (line 305), but the implementation always JSON-encodes the data (line 341), which would double-encode if a JSON string is passed. The function signature defaults to an empty array ($data = []).

Current usage in this file and throughout the codebase only passes arrays as the $data argument. Either:

  1. Revert the docblock to @param array $data to match the actual implementation, or
  2. Add conditional encoding to handle pre-encoded string inputs properly

Reverting the docblock is the appropriate fix unless string support is intentionally planned.

🤖 Prompt for AI Agents
In @inc/integrations/host-providers/class-enhance-host-provider.php around lines
303 - 308, The docblock for send_enhance_api_request incorrectly lists $data as
array|string while the implementation always JSON-encodes the payload
(json_encode used when preparing the request), which would double-encode if a
JSON string were passed; fix by updating the docblock to @param array $data to
reflect actual usage (or alternatively implement conditional encoding if string
support is desired), and ensure the function signature and examples/comments
consistently treat $data as an array.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ class Gridpane_Host_Provider extends Base_Host_Provider {
*/
protected $constants = [
'WU_GRIDPANE',
'WU_GRIDPANE_API_KEY',
'WU_GRIDPANE_APP_ID',
'WU_GRIDPANE_SERVER_ID',
];

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,9 @@ public function load_dependencies(): void {
* @return void
*/
public function on_add_domain($domain, $site_id): void {

if (! class_exists('WPE_API')) {
return;
}
$api = new \WPE_API();

$api->set_arg('method', 'domain');
Expand All @@ -124,6 +126,9 @@ public function on_add_domain($domain, $site_id): void {
* @return void
*/
public function on_remove_domain($domain, $site_id): void {
if (! class_exists('WPE_API')) {
return;
}

$api = new \WPE_API();

Expand Down Expand Up @@ -197,6 +202,9 @@ public function get_logo() {
* @return void
*/
public function test_connection(): void {
if (! class_exists('WPE_API')) {
wp_send_json_error(__('Class WPE_API is not installed.', 'ultimate-multisite'));
}

$api = new \WPE_API();

Expand Down
Loading