fix: #3588169 Validator: do not count the port wildcard against the host wildcard limit

Summary

Two related fixes in the alias pattern validator, plus the cleanup work that surfaced from the audit:

  1. Validator counted host and port wildcards together. DomainAliasValidator and DomainAliasPatternConstraintValidator summed substr_count($pattern, '*') across host and port portions when enforcing the "one wildcard" rule. The port wildcard :* means "any port for this host" — conceptually distinct from a host wildcard — and the rest of the alias pipeline already treats it that way (buildPortPatterns(), DomainAliasPatternResolver, the priority model docs added in #3588155). Co-counting blocked patterns the README documents as supported (e.g. example.*:*, *.com:*).

    Fix: split the port suffix off the pattern before counting wildcards. Apply the existing single-wildcard limit to host stars only.

  2. Inert ? wildcard handling dropped. Audit found ? was half-treated as a wildcard:

    • The validators counted substr_count($pattern, '?') toward the wildcard limit.
    • DomainAliasForm translated \? to [^.:] in its "pattern shadows a canonical hostname" warning regex.

    But the matching pipeline never honors ?: DomainAliasPatternResolver::patternToRegex() only replaces *, buildPatterns() only inserts *, loadByPattern() does an exact-string DB lookup. The README only documents * as the wildcard. A pattern like foo?.example.com was capped (or warned about) as if it had a wildcard, but at runtime would only ever match the literal hostname foo?.example.com.

    Fix: drop the ? counting in both validators and the ?-to-regex translation in the form.

    Historical context: ? was a real single-character wildcard in Domain 6.x/7.x (see _domain_alias_link() on the 7.x-3.x branch). The 8.x-1.x port silently dropped the matcher logic but kept the validator. Sites migrated from D7 with ? patterns have had silently inert aliases ever since.

  3. Deprecation pathway for 4.0.0. ? stays in the character allowlist for backwards compatibility (existing config with ? still loads), but a new hook_ENTITY_TYPE_presave('domain_alias') fires @trigger_error(... E_USER_DEPRECATED) when an alias pattern contains ?. Catches every save path (UI form, Drush, config import). Five @todo Remove in domain:4.0.0 markers across the validators and the hook make a future cleanup-grep find every related code path with no orphans.

Issue: #3588169.

What ships in this MR

File Change
DomainAliasValidator.php Host/port split, ? no longer counted, @todos for 4.0.0.
DomainAliasPatternConstraintValidator.php Same on the entity-constraint side.
DomainAliasForm.php ? removed from the canonical-shadow warning regex. Pattern field #description now spells out the host-wildcard / port-suffix combination rules.
DomainAliasHooks.php New #[Hook('domain_alias_presave')] firing the ? deprecation. @todo Remove this entire method in domain:4.0.0.
domain_alias/README.md "Alias Patterns" rewritten with the patterns the validator actually accepts. New "Constraints" subsection. ? flagged as deprecated and removed in 4.0.0.
DomainAliasValidatorTest.php Positive cases: example.com:*, *.example.com:*, example.*:*, example.*:8080.
DomainAliasConstraintTest.php testValidPatterns extended with the same combinations.

Behavior changes

  • Patterns previously rejected for combining host wildcard with port suffix (e.g. example.*:*, *.example.com:*, example.*:8080) now save successfully. Strict superset; no patterns previously valid are now rejected.
  • Patterns containing ? save with a deprecation notice. No matching change — ? was never honored as a wildcard at request time.

Test plan

  • DomainAliasValidatorTest::testDomainAliasValidator — 25 assertions, OK locally (Drupal 11.x + MySQL).
  • DomainAliasConstraintTest full file — 16 tests, 28 assertions, OK locally.
  • PHPCS / PHPStan / cspell / eslint / stylelint clean.
  • CI phpunit (D11) green; phpunit (previous major / D10) running.
  • Spot-check on a multi-alias setup that registering example.*:* succeeds and resolves correctly on incoming requests.

Out of scope (separate issues)

  • Raising the host-wildcard limit to match the README's "max 3" claim.
  • DomainAliasPatternResolver::replaceWildcards() substitution hardening (prerequisite for raising the cap).
  • 4.0.0 cleanup itself: drop ? from the allowlist, delete the deprecation hook, simplify the validator comments. The @todo Remove in domain:4.0.0 markers in this MR will surface every related code path.

Coordination

No hard dependencies. Independent of !371 (merged) (#3588168 buildPatterns completeness) — different functions, different test methods. !370 (merged) (#3588155 sort + priority) already merged.

Release notes

The domain_alias pattern validator no longer counts the port wildcard :* against the host-wildcard limit. Patterns combining a single host wildcard with a port suffix (e.g. example.*:*, *.example.com:8080) now save successfully — these patterns were always documented as supported and handled by the matching pipeline; only the validator blocked them.

Patterns containing ? are now flagged with a deprecation notice on save. ? was a single-character wildcard in Domain 6.x/7.x, but has not been honored as a wildcard since the 8.x-1.x port. The character will be removed from the alias pattern allowlist entirely in domain:4.0.0. Sites with ? patterns should clean them up before upgrading to 4.0.0.

Edited by Frank Mably

Merge request reports

Loading