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
2 changes: 1 addition & 1 deletion assets/js/domain-logs.min.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions inc/class-domain-mapping.php
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,14 @@ public function on_add_subdomain($subdomain, $site_id): void {
return;
}

$should_add_www = apply_filters('wu_cloudflare_should_add_www', true, $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];

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
Expand Down Expand Up @@ -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";
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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' => true,
'redirection' => 'non-www',
'www' => $create_www,
'redirection' => $create_www ? 'non-www' : 'none',
],
'POST'
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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),
]
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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";
}

Expand Down
74 changes: 73 additions & 1 deletion inc/managers/class-domain-manager.php
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,78 @@ 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) {

// 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) {
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 = 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
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;
}
}
Comment on lines +420 to 470
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Harden domain normalization and avoid fatal on unexpected input; fix @SInCE

Cast to string before strtolower/trim and strip scheme/port/trailing dot to cover common edge cases (URLs passed in, ports, trailing dot). Also align the docblock @SInCE to the version introducing this method.

Apply:

-	 * @since 2.0.0
+	 * @since 2.4.4
@@
-	public function should_create_www_subdomain($domain) {
+	public function should_create_www_subdomain($domain) {
@@
-		// Normalize incoming domain
-		$domain = trim(strtolower($domain));
+		// Normalize incoming domain
+		$domain = strtolower(trim((string) $domain));
+		// If a URL sneaks in, extract host
+		if (strpos($domain, '://') !== false) {
+			$parsed = wp_parse_url($domain);
+			if (!empty($parsed['host'])) {
+				$domain = $parsed['host'];
+			}
+		}
+		// Drop trailing dot and any port suffix
+	$domain = rtrim($domain, '.');
+	$domain = preg_replace('/:\d+$/', '', $domain);

Optional: add a return type for consistency with this class’ other methods.

-	public function should_create_www_subdomain($domain) {
+	public function should_create_www_subdomain($domain): bool {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/**
* 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) {
// 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) {
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 = 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
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;
}
}
/**
* Check if a www subdomain should be created for the given domain.
*
* @since 2.4.4
* @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) {
// Normalize incoming domain
$domain = strtolower(trim((string) $domain));
// If a URL sneaks in, extract host
if (strpos($domain, '://') !== false) {
$parsed = wp_parse_url($domain);
if (! empty( $parsed['host'] )) {
$domain = $parsed['host'];
}
}
// Drop trailing dot and any port suffix
$domain = rtrim($domain, '.');
$domain = preg_replace('/:\d+$/', '', $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) {
case 'never':
return false;
case 'main_only':
// ...existing logic...
🤖 Prompt for AI Agents
In inc/managers/class-domain-manager.php around lines 420 to 470, the domain
normalization can raise a fatal on non-string input and the docblock @since is
inaccurate; cast the incoming $domain to string before using strtolower/trim,
then strip URL scheme (http:// or https://), remove any trailing port (":1234")
and trailing dot to normalize inputs like full URLs or hostnames with ports,
then continue the existing www-prefix check; update the @since tag to the actual
version that introduced this method (replace 2.0.0 with the correct version),
and optionally add a bool return type to the method signature for consistency
(e.g., public function should_create_www_subdomain($domain): bool).


/**
Expand Down Expand Up @@ -955,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)],
]
);
Expand Down
4 changes: 4 additions & 0 deletions readme.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading