From ee2dd9c5839b07321f13d000a0dee51ce138ef14 Mon Sep 17 00:00:00 2001 From: David Stone Date: Thu, 4 Sep 2025 20:00:33 -0600 Subject: [PATCH 1/6] Add new option to not always create www subdomain. --- assets/js/domain-logs.min.js | 2 +- .../class-cloudflare-host-provider.php | 7 +- .../class-cloudways-host-provider.php | 4 +- .../class-runcloud-host-provider.php | 2 +- .../class-serverpilot-host-provider.php | 10 ++- .../class-wpmudev-host-provider.php | 2 +- inc/managers/class-domain-manager.php | 64 +++++++++++++++++++ readme.txt | 4 ++ 8 files changed, 87 insertions(+), 8 deletions(-) diff --git a/assets/js/domain-logs.min.js b/assets/js/domain-logs.min.js index 162af579c..a2955ff15 100644 --- a/assets/js/domain-logs.min.js +++ b/assets/js/domain-logs.min.js @@ -1 +1 @@ -(o=>{o(document).ready(function(){function t(e){o.ajax({url:ajaxurl,method:"GET",data:{action:"wu_handle_view_logs",file:wu_domain_logs.log_file,return_ascii:"no"},success(n){o("#content").html(n.data.contents),void 0!==e&&e()}})}t(),setInterval(t,6e4),o(document).on("click","#refresh-logs",function(n){let e=wu_block_ui("#content");n.preventDefault(),t(function(){e.unblock()})})})})(jQuery); \ No newline at end of file +(o=>{o(document).ready(function(){function t(e){o.ajax({url:ajaxurl,method:"GET",data:{action:"wu_handle_view_logs",file:wu_domain_logs.log_file,return_ascii:"no"},success(n){o("#content").text(n.data.contents),void 0!==e&&e()}})}t(),setInterval(t,6e4),o(document).on("click","#refresh-logs",function(n){let e=wu_block_ui("#content");n.preventDefault(),t(function(){e.unblock()})})})})(jQuery); \ No newline at end of file diff --git a/inc/integrations/host-providers/class-cloudflare-host-provider.php b/inc/integrations/host-providers/class-cloudflare-host-provider.php index 2bdbf91b8..86808fd18 100644 --- a/inc/integrations/host-providers/class-cloudflare-host-provider.php +++ b/inc/integrations/host-providers/class-cloudflare-host-provider.php @@ -252,7 +252,12 @@ public function on_add_subdomain($subdomain, $site_id): void { return; } - $should_add_www = apply_filters('wu_cloudflare_should_add_www', true, $subdomain, $site_id); + $should_add_www = apply_filters( + 'wu_cloudflare_should_add_www', + \WP_Ultimo\Managers\Domain_Manager::get_instance()->should_create_www_subdomain($subdomain), + $subdomain, + $site_id + ); $domains_to_send = [$subdomain]; diff --git a/inc/integrations/host-providers/class-cloudways-host-provider.php b/inc/integrations/host-providers/class-cloudways-host-provider.php index e21a04e63..a6e0fc5cd 100644 --- a/inc/integrations/host-providers/class-cloudways-host-provider.php +++ b/inc/integrations/host-providers/class-cloudways-host-provider.php @@ -291,7 +291,7 @@ private function get_domains(): array { $domain_list = $this->get_domain_list(); foreach ($domain_list as $naked_domain) { - if (! str_starts_with((string) $naked_domain, 'www.') && ! str_starts_with((string) $naked_domain, '*.')) { + if (! str_starts_with((string) $naked_domain, 'www.') && ! str_starts_with((string) $naked_domain, '*.') && \WP_Ultimo\Managers\Domain_Manager::get_instance()->should_create_www_subdomain($naked_domain)) { $domain_list[] = 'www.' . $naked_domain; } } @@ -378,7 +378,7 @@ public function get_all_mapped_domains() { foreach ($mappings as $domain) { $final_domain_list[] = $domain; - if (! str_starts_with((string) $domain, 'www.')) { + if (! str_starts_with((string) $domain, 'www.') && \WP_Ultimo\Managers\Domain_Manager::get_instance()->should_create_www_subdomain($domain)) { $final_domain_list[] = "www.$domain"; } } diff --git a/inc/integrations/host-providers/class-runcloud-host-provider.php b/inc/integrations/host-providers/class-runcloud-host-provider.php index 525ac77c3..cadc7792e 100644 --- a/inc/integrations/host-providers/class-runcloud-host-provider.php +++ b/inc/integrations/host-providers/class-runcloud-host-provider.php @@ -128,7 +128,7 @@ public function on_add_domain($domain, $site_id): void { $this->get_runcloud_base_url('domains'), [ 'name' => $domain, - 'www' => true, + 'www' => \WP_Ultimo\Managers\Domain_Manager::get_instance()->should_create_www_subdomain($domain), 'redirection' => 'non-www', ], 'POST' diff --git a/inc/integrations/host-providers/class-serverpilot-host-provider.php b/inc/integrations/host-providers/class-serverpilot-host-provider.php index d2bf992f8..c36adf405 100644 --- a/inc/integrations/host-providers/class-serverpilot-host-provider.php +++ b/inc/integrations/host-providers/class-serverpilot-host-provider.php @@ -11,7 +11,7 @@ use Psr\Log\LogLevel; -defined( 'ABSPATH' ) || exit; +defined('ABSPATH') || exit; /** * This base class should be extended to implement new host integrations for SSL and domains. @@ -119,10 +119,16 @@ public function on_add_domain($domain, $site_id): void { $current_domain_list = $this->get_server_pilot_domains(); if ($current_domain_list && is_array($current_domain_list)) { + $domains_to_add = [$domain]; + + if (\WP_Ultimo\Managers\Domain_Manager::get_instance()->should_create_www_subdomain($domain)) { + $domains_to_add[] = 'www.' . $domain; + } + $this->send_server_pilot_api_request( '', [ - 'domains' => array_merge($current_domain_list, [$domain, 'www.' . $domain]), + 'domains' => array_merge($current_domain_list, $domains_to_add), ] ); diff --git a/inc/integrations/host-providers/class-wpmudev-host-provider.php b/inc/integrations/host-providers/class-wpmudev-host-provider.php index 6e5e41d62..22636a175 100644 --- a/inc/integrations/host-providers/class-wpmudev-host-provider.php +++ b/inc/integrations/host-providers/class-wpmudev-host-provider.php @@ -135,7 +135,7 @@ public function on_add_domain($domain, $site_id): void { $domains = [$domain]; - if (! str_starts_with($domain, 'www.')) { + if (! str_starts_with($domain, 'www.') && \WP_Ultimo\Managers\Domain_Manager::get_instance()->should_create_www_subdomain($domain)) { $domains[] = "www.$domain"; } diff --git a/inc/managers/class-domain-manager.php b/inc/managers/class-domain-manager.php index bedf40d6f..152afc2f7 100644 --- a/inc/managers/class-domain-manager.php +++ b/inc/managers/class-domain-manager.php @@ -395,6 +395,70 @@ public function add_domain_mapping_settings(): void { ], ] ); + + wu_register_settings_field( + 'domain-mapping', + 'auto_create_www_subdomain', + [ + 'title' => __('Create www Subdomain Automatically?', 'multisite-ultimate'), + 'desc' => __('Control when www subdomains should be automatically created for mapped domains.', 'multisite-ultimate'), + 'tooltip' => __('This setting applies to all hosting integrations and determines when a www version of the domain should be automatically created.', 'multisite-ultimate'), + 'type' => 'select', + 'default' => 'always', + 'options' => [ + 'always' => __('Always - Create www subdomain for all domains', 'multisite-ultimate'), + 'main_only' => __('Only for main domains (e.g., example.com but not subdomain.example.com)', 'multisite-ultimate'), + 'never' => __('Never - Do not automatically create www subdomains', 'multisite-ultimate'), + ], + 'require' => [ + 'enable_domain_mapping' => true, + ], + ] + ); + } + + /** + * Check if a www subdomain should be created for the given domain. + * + * @since 2.0.0 + * @param string $domain The domain to check. + * @return bool True if www subdomain should be created, false otherwise. + */ + public function should_create_www_subdomain($domain) { + + $setting = wu_get_setting('auto_create_www_subdomain', 'always'); + + switch ($setting) { + case 'never': + return false; + + case 'main_only': + // Check if this is a main domain (no subdomain parts) + // A main domain has only 2 parts when split by dots (e.g., example.com) + // or 3 parts if it's a known TLD structure (e.g., example.co.uk) + $parts = explode('.', $domain); + + // Simple heuristic: if domain has only 2 parts, it's definitely a main domain + if (count($parts) <= 2) { + return true; // e.g., example.com + } + + // For 3+ parts, check if it's a main domain with multi-part TLD + $known_multi_part_tlds = ['.co.uk', '.com.au', '.co.nz', '.com.br', '.co.in']; + $last_two_parts = '.' . $parts[ count($parts) - 2 ] . '.' . $parts[ count($parts) - 1 ]; + + // If it has exactly 3 parts and matches a known multi-part TLD, it's a main domain + if (count($parts) === 3 && in_array($last_two_parts, $known_multi_part_tlds, true)) { + return true; // e.g., example.co.uk + } + + // Otherwise, it's a subdomain + return false; + + case 'always': + default: + return true; + } } /** diff --git a/readme.txt b/readme.txt index 1887a2912..200b1b68f 100644 --- a/readme.txt +++ b/readme.txt @@ -233,6 +233,10 @@ Version [2.4.4] - Released on 2025-08-XX - Fixed: Saving email templates without stripping html - New: Option to allow site owners to edit users on their site - Fixed: Invoices not loading when logo is not set +- Fixed: Verify DNS settings when using a reverse proxy +- Improved: Lazy load limitations for better performance and compatibility +- New: Add Admin Notice if sunrise.php is not setup +- New: Option to not always create www. subdomains with hosting integrations Version [2.4.3] - Released on 2025-08-15 - Fixed: Bug in Slim SEO plugin From bf47b10a2a21a1cafae825a13f56940418f3f551 Mon Sep 17 00:00:00 2001 From: David Stone Date: Thu, 4 Sep 2025 20:01:05 -0600 Subject: [PATCH 2/6] fix domain loopback verification --- inc/class-domain-mapping.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/inc/class-domain-mapping.php b/inc/class-domain-mapping.php index f00dac6c5..41086bc0c 100644 --- a/inc/class-domain-mapping.php +++ b/inc/class-domain-mapping.php @@ -242,6 +242,15 @@ public function get_www_and_nowww_versions($domain) { return [$nowww, $www]; } + /** + * Check if this is a special loopback request. + * + * @param null|false|\WP_Site $current_site Current Site. + * @param string $domain Current domain. + * @param string $path Current Path. + * + * @return void + */ public function verify_dns_mapping($current_site, $domain, $path) { // Nonce functions are unavailable and the wp_hash is basically the same. @@ -274,6 +283,7 @@ public function verify_dns_mapping($current_site, $domain, $path) { */ public function check_domain_mapping($site, $domain) { + $this->verify_dns_mapping($site, $domain, '/'); // Have we already matched? (Allows other plugins to match first) if ( ! empty($site)) { return $site; From d5010cc8f20bac6952b6e70d90e37fc015bcdeeb Mon Sep 17 00:00:00 2001 From: David Stone Date: Thu, 4 Sep 2025 21:08:55 -0600 Subject: [PATCH 3/6] Update inc/integrations/host-providers/class-cloudflare-host-provider.php Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- .../class-cloudflare-host-provider.php | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/inc/integrations/host-providers/class-cloudflare-host-provider.php b/inc/integrations/host-providers/class-cloudflare-host-provider.php index 86808fd18..5b3b52b90 100644 --- a/inc/integrations/host-providers/class-cloudflare-host-provider.php +++ b/inc/integrations/host-providers/class-cloudflare-host-provider.php @@ -252,12 +252,14 @@ public function on_add_subdomain($subdomain, $site_id): void { return; } - $should_add_www = apply_filters( - 'wu_cloudflare_should_add_www', - \WP_Ultimo\Managers\Domain_Manager::get_instance()->should_create_www_subdomain($subdomain), - $subdomain, - $site_id - ); + // Build FQDN so Domain_Manager can classify main vs. subdomain correctly. + $full_domain = $subdomain . '.' . $current_site->domain; + $should_add_www = apply_filters( + 'wu_cloudflare_should_add_www', + \WP_Ultimo\Managers\Domain_Manager::get_instance()->should_create_www_subdomain($full_domain), + $subdomain, + $site_id + ); $domains_to_send = [$subdomain]; From 442c2a909eac197adfe295f153bc01f88be40bba Mon Sep 17 00:00:00 2001 From: David Stone Date: Thu, 4 Sep 2025 21:11:54 -0600 Subject: [PATCH 4/6] Update for runcloud correct www redirect --- .../host-providers/class-runcloud-host-provider.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/inc/integrations/host-providers/class-runcloud-host-provider.php b/inc/integrations/host-providers/class-runcloud-host-provider.php index cadc7792e..2b88249cd 100644 --- a/inc/integrations/host-providers/class-runcloud-host-provider.php +++ b/inc/integrations/host-providers/class-runcloud-host-provider.php @@ -124,12 +124,14 @@ public function on_add_domain($domain, $site_id): void { $success = false; + $create_www = \WP_Ultimo\Managers\Domain_Manager::get_instance()->should_create_www_subdomain($domain); + $response = $this->send_runcloud_request( $this->get_runcloud_base_url('domains'), [ 'name' => $domain, - 'www' => \WP_Ultimo\Managers\Domain_Manager::get_instance()->should_create_www_subdomain($domain), - 'redirection' => 'non-www', + 'www' => $create_www, + 'redirection' => $create_www ? 'non-www' : 'none', ], 'POST' ); From 8f1f0c07b0278d28b454ac09f1ba8947ee09e753 Mon Sep 17 00:00:00 2001 From: David Stone Date: Thu, 4 Sep 2025 21:17:06 -0600 Subject: [PATCH 5/6] never double www. --- inc/managers/class-domain-manager.php | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/inc/managers/class-domain-manager.php b/inc/managers/class-domain-manager.php index 152afc2f7..e894fa913 100644 --- a/inc/managers/class-domain-manager.php +++ b/inc/managers/class-domain-manager.php @@ -426,6 +426,14 @@ public function add_domain_mapping_settings(): void { */ public function should_create_www_subdomain($domain) { + // Normalize incoming domain + $domain = trim(strtolower($domain)); + + // Guard against double-prefixing - return false if already starts with www. + if (strpos($domain, 'www.') === 0) { + return false; + } + $setting = wu_get_setting('auto_create_www_subdomain', 'always'); switch ($setting) { @@ -444,7 +452,7 @@ public function should_create_www_subdomain($domain) { } // For 3+ parts, check if it's a main domain with multi-part TLD - $known_multi_part_tlds = ['.co.uk', '.com.au', '.co.nz', '.com.br', '.co.in']; + $known_multi_part_tlds = apply_filters('wu_multi_part_tlds', ['.co.uk', '.com.au', '.co.nz', '.com.br', '.co.in']); $last_two_parts = '.' . $parts[ count($parts) - 2 ] . '.' . $parts[ count($parts) - 1 ]; // If it has exactly 3 parts and matches a known multi-part TLD, it's a main domain From 1e4eeaafb7e34dabcbc5eb78cf947d045933792e Mon Sep 17 00:00:00 2001 From: David Stone Date: Fri, 5 Sep 2025 00:29:19 -0600 Subject: [PATCH 6/6] avoid warnings --- inc/managers/class-domain-manager.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inc/managers/class-domain-manager.php b/inc/managers/class-domain-manager.php index e894fa913..5fa99498d 100644 --- a/inc/managers/class-domain-manager.php +++ b/inc/managers/class-domain-manager.php @@ -1027,7 +1027,7 @@ public function verify_domain_with_loopback_request(Domain $domain): bool { [ 'timeout' => 10, 'redirection' => 0, - 'sslverify' => $protocol_config['sslverify'], + 'sslverify' => $protocol_config['sslverify'] ?? false, 'body' => ['async_check_dns_nonce' => wp_hash($domain_url)], ] );