Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
14 changes: 13 additions & 1 deletion inc/limitations/class-limit.php
Original file line number Diff line number Diff line change
Expand Up @@ -140,9 +140,21 @@ public function setup($data): void {

/*
* Sets the own enabled flag, if necessary.
*
* Treat empty string the same as "not set" — when limitations are
* serialized through the WooCommerce addon or other external flows,
* the enabled flag can arrive as '' instead of being absent entirely.
* Casting '' to bool yields false, which incorrectly disables the
* capability instead of inheriting from the parent (plan/membership).
*
* @since 2.7.1
*/
if (wu_get_isset($data, 'enabled', 'not-set') === 'not-set') {
$current_enabled = wu_get_isset($data, 'enabled', 'not-set');

if ('not-set' === $current_enabled || '' === $current_enabled) {
$this->has_own_enabled = false;

unset($data['enabled']);
}

$data = wp_parse_args(
Expand Down
92 changes: 92 additions & 0 deletions inc/managers/class-domain-manager.php
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,8 @@ public function init(): void {

add_action('wu_transition_domain_domain', [$this, 'send_domain_to_host'], 10, 3);

add_action('wu_domain_post_save', [$this, 'maybe_auto_promote_primary_domain'], 10, 3);

add_action('wu_settings_domain_mapping', [$this, 'add_domain_mapping_settings']);

add_action('wu_settings_sso', [$this, 'add_sso_settings']);
Expand Down Expand Up @@ -1085,6 +1087,96 @@ public function async_remove_old_primary_domains($domains): void {
}
}

/**
* Auto-promote a custom domain to primary when it reaches done/done-without-ssl stage.
*
* When a non-subdomain mapping becomes active (stage = done or done-without-ssl)
* for a blog that has no other primary domain (or only has the default subdomain
* as primary), auto-promote the custom domain. This is the behavior customers
* expect: "I added my domain, verified DNS/SSL, my custom domain is now the
* primary one and the subdomain redirects to it."
*
* Hooked into wu_domain_post_save so it works regardless of whether the stage
* was set by core's async_process_domain_stage or by an external plugin.
*
* @since 2.7.1
*
* @param array $data The saved data.
* @param \WP_Ultimo\Models\Domain $domain The domain instance.
* @param bool $new Whether this is a new domain.
* @return void
*/
public function maybe_auto_promote_primary_domain($data, $domain, $new): void {

$done_stages = [
Domain_Stage::DONE,
Domain_Stage::DONE_WITHOUT_SSL,
];

if ( ! in_array($domain->get_stage(), $done_stages, true)) {
return;
}

/*
* Already primary — nothing to do.
*/
if ($domain->is_primary_domain()) {
return;
}

$domain_url = $domain->get_domain();

/*
* Only auto-promote custom (non-subdomain) domains.
* Subdomains like foo.kursopro.com should not auto-promote.
*/
if ( ! self::is_main_domain($domain_url)) {
return;
}

$blog_id = $domain->get_blog_id();

/*
* Check if the blog already has a primary custom domain.
* If so, don't override — let the admin manage it manually.
*/
$existing_domains = wu_get_domains(
[
'blog_id' => $blog_id,
'primary_domain' => true,
'id__not_in' => [$domain->get_id()],
]
);

foreach ($existing_domains as $existing) {
if (self::is_main_domain($existing->get_domain())) {
/*
* Another custom domain is already primary for this blog.
* Do not auto-promote to avoid overriding explicit choice.
*/
return;
}
Comment on lines +1151 to +1158
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.

⚠️ Potential issue | 🟠 Major

Existing primary check can miss custom subdomains and override explicit primary choice.

The guard only blocks when an existing primary passes is_main_domain(). A primary like www.example.com is a custom domain but won’t pass that check, so a later domain can still be auto-promoted unexpectedly.

💡 Suggested fix
-		foreach ($existing_domains as $existing) {
-			if (self::is_main_domain($existing->get_domain())) {
+		$network_domain = defined('DOMAIN_CURRENT_SITE') ? strtolower((string) DOMAIN_CURRENT_SITE) : '';
+		foreach ($existing_domains as $existing) {
+			$existing_domain = strtolower((string) $existing->get_domain());
+			$is_custom_domain = '' === $network_domain
+				? self::is_main_domain($existing_domain)
+				: 0 === preg_match('/(^|\.)' . preg_quote($network_domain, '/') . '$/', $existing_domain);
+
+			if ($is_custom_domain) {
 				/*
 				 * Another custom domain is already primary for this blog.
 				 * Do not auto-promote to avoid overriding explicit choice.
 				 */
 				return;
 			}
 		}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@inc/managers/class-domain-manager.php` around lines 1151 - 1158, The current
guard only skips auto-promotion for domains where
self::is_main_domain($existing->get_domain()) is true, which misses custom
primary domains like "www.example.com"; replace that condition with a check that
detects any existing primary domain (not just main ones) — for example, check
the domain object's primary flag (e.g., $existing->is_primary() or
$existing->get_is_primary()) or compare $existing->get_domain() against the
blog's known primary domain via the manager API — and return early when any
existing domain is marked primary to avoid auto-promoting another domain.

}

/*
* Promote this domain to primary. The Domain::save() method
* already handles demoting old primaries via wu_async_remove_old_primary_domains.
*/
$domain->set_primary_domain(true);

$domain->save();

wu_log_add(
"domain-{$domain_url}",
sprintf(
// translators: %s is the domain name, %d is the blog ID.
__('Auto-promoted %1$s as primary domain for site %2$d.', 'ultimate-multisite'),
$domain_url,
$blog_id
)
);
Comment thread
superdav42 marked this conversation as resolved.
}

/**
* Tests the integration in the Wizard context.
*
Expand Down
28 changes: 27 additions & 1 deletion inc/models/class-site.php
Original file line number Diff line number Diff line change
Expand Up @@ -1977,10 +1977,36 @@ public function save() {
}

/**
* Handles membership
* Handles membership.
*
* When the site is created through external gateways (e.g. the
* WooCommerce addon), the membership_id may not have been set on
* the site object before save(). If get_membership() returns false
* but we have a customer_id, attempt to infer the membership from
* the customer's active memberships as a defensive fallback.
*
* @since 2.7.1
*/
$membership = $this->get_membership();

if ( ! $membership && $this->get_customer_id() && function_exists('wu_get_memberships')) {
$memberships = wu_get_memberships(
[
'customer_id' => $this->get_customer_id(),
'status__in' => ['active', 'trialing'],
'number' => 2,
]
);

if (1 === count($memberships)) {
$membership = $memberships[0];

$this->set_membership_id($membership->get_id());

update_site_meta($this->get_id(), self::META_MEMBERSHIP_ID, $membership->get_id());
}
Comment thread
superdav42 marked this conversation as resolved.
}

if ($membership) {
$customer_id = $membership->get_customer_id();

Expand Down
17 changes: 15 additions & 2 deletions inc/objects/class-limitations.php
Original file line number Diff line number Diff line change
Expand Up @@ -277,13 +277,26 @@ protected function merge_recursive(array &$array1, array &$array2, $should_sum =
$array2['enabled'] = true;
}

if ( ! wu_get_isset($array1, 'enabled', true)) {
/*
* Only collapse a module to {enabled:false} when the enabled flag is
* explicitly set to a boolean false — not when it is absent or an
* empty string. An empty string arrives from external flows (e.g. the
* WooCommerce addon) where capability flags were never written; it
* means "not configured" and must NOT suppress inherited values.
*
* @since 2.7.1
*/
$a1_enabled = wu_get_isset($array1, 'enabled', 'not-set');

if (false === $a1_enabled) {
$array1 = [
'enabled' => false,
];
}

if ( ! wu_get_isset($array2, 'enabled', true) && $should_sum) {
$a2_enabled = wu_get_isset($array2, 'enabled', 'not-set');

if (false === $a2_enabled && $should_sum) {
return;
}

Expand Down
Loading