Skip to content

Commit 04a5d00

Browse files
CopilotswissspidyCopilot
authored
Add --site-url parameter for custom domain configuration in site create (#576)
Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Pascal Birchler <pascalb@google.com>
1 parent b9153c7 commit 04a5d00

File tree

2 files changed

+256
-3
lines changed

2 files changed

+256
-3
lines changed

features/site-create.feature

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,3 +93,160 @@ Feature: Create a new site on a WP multisite
9393
| blog_id | url |
9494
| 1 | http://localhost/ |
9595
| 2 | http://localhost/newsite/ |
96+
97+
Scenario: Create site with custom URL in subdomain multisite
98+
Given a WP multisite subdomain install
99+
100+
When I run `wp site create --site-url=http://custom.example.com`
101+
Then STDOUT should contain:
102+
"""
103+
Success: Site 2 created: http://custom.example.com/
104+
"""
105+
106+
When I run `wp site list --fields=blog_id,url`
107+
Then STDOUT should be a table containing rows:
108+
| blog_id | url |
109+
| 1 | https://example.com/ |
110+
| 2 | http://custom.example.com/ |
111+
112+
When I run `wp --url=custom.example.com option get home`
113+
Then STDOUT should be:
114+
"""
115+
http://custom.example.com
116+
"""
117+
118+
Scenario: Create site with custom URL in subdirectory multisite
119+
Given a WP multisite subdirectory install
120+
121+
When I run `wp site create --site-url=http://example.com/custom/path/`
122+
Then STDOUT should contain:
123+
"""
124+
Success: Site 2 created:
125+
"""
126+
And STDOUT should contain:
127+
"""
128+
://example.com/custom/path/
129+
"""
130+
131+
When I run `wp site list --fields=blog_id,url`
132+
Then STDOUT should contain:
133+
"""
134+
://example.com/custom/path/
135+
"""
136+
137+
Scenario: Create site with custom URL and explicit slug
138+
Given a WP multisite subdomain install
139+
140+
When I run `wp site create --site-url=http://custom.example.com --slug=myslug`
141+
Then STDOUT should contain:
142+
"""
143+
Success: Site 2 created: http://custom.example.com/
144+
"""
145+
146+
Scenario: Error when neither slug nor site-url is provided
147+
Given a WP multisite install
148+
149+
When I try `wp site create --title="Test Site"`
150+
Then STDERR should be:
151+
"""
152+
Error: Either --slug or --site-url must be provided.
153+
"""
154+
And the return code should be 1
155+
156+
Scenario: Error when invalid URL format is provided
157+
Given a WP multisite install
158+
159+
When I try `wp site create --site-url=not-a-valid-url`
160+
Then STDERR should contain:
161+
"""
162+
Error: Invalid URL format
163+
"""
164+
And the return code should be 1
165+
166+
Scenario: Error when invalid scheme is provided
167+
Given a WP multisite install
168+
169+
When I try `wp site create --site-url=ftp://example.com/site`
170+
Then STDERR should be:
171+
"""
172+
Error: Invalid URL scheme. Only http and https schemes are supported.
173+
"""
174+
And the return code should be 1
175+
176+
Scenario: Error when root path provided without explicit slug
177+
Given a WP multisite subdirectory install
178+
179+
When I try `wp site create --site-url=http://example.com/`
180+
Then STDERR should be:
181+
"""
182+
Error: Could not derive a valid slug from the URL path. Please provide --slug explicitly.
183+
"""
184+
And the return code should be 1
185+
186+
Scenario: Create site with URL without trailing slash
187+
Given a WP multisite subdirectory install
188+
189+
When I run `wp site create --site-url=http://example.com/notrailing`
190+
Then STDOUT should contain:
191+
"""
192+
Success: Site 2 created:
193+
"""
194+
And STDOUT should contain:
195+
"""
196+
://example.com/notrailing/
197+
"""
198+
199+
Scenario: Error when numeric-only domain is provided without slug
200+
Given a WP multisite subdomain install
201+
202+
When I try `wp site create --site-url=http://123.example.com`
203+
Then STDERR should be:
204+
"""
205+
Error: Could not derive a valid slug from the domain (numeric-only or empty slugs are not allowed). Please provide --slug explicitly.
206+
"""
207+
And the return code should be 1
208+
209+
Scenario: Create site with different domain in subdirectory multisite shows warning
210+
Given a WP multisite subdirectory install
211+
212+
When I try `wp site create --site-url=http://custom.example.com/mypath/`
213+
Then STDERR should contain:
214+
"""
215+
Warning: Using a different domain for a subdirectory multisite install may require additional configuration
216+
"""
217+
And STDOUT should contain:
218+
"""
219+
Success: Site 2 created:
220+
"""
221+
And STDOUT should contain:
222+
"""
223+
://custom.example.com/mypath/
224+
"""
225+
226+
Scenario: Create site with both site-url and slug uses slug for internal operations
227+
Given a WP multisite subdomain install
228+
229+
When I run `wp site create --site-url=http://custom.example.com --slug=myslug --porcelain`
230+
Then STDOUT should be a number
231+
And save STDOUT as {SITE_ID}
232+
233+
When I run `wp site list --site__in={SITE_ID} --field=url`
234+
Then STDOUT should contain:
235+
"""
236+
custom.example.com
237+
"""
238+
239+
Scenario: Preserve existing slug behavior
240+
Given a WP multisite subdomain install
241+
242+
When I run `wp site create --slug=testsite`
243+
Then STDOUT should contain:
244+
"""
245+
Success: Site 2 created: http://testsite.example.com/
246+
"""
247+
248+
When I run `wp site list --fields=blog_id,url`
249+
Then STDOUT should be a table containing rows:
250+
| blog_id | url |
251+
| 1 | https://example.com/ |
252+
| 2 | http://testsite.example.com/ |

src/Site_Command.php

Lines changed: 99 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -522,8 +522,15 @@ public function get( $args, $assoc_args ) {
522522
*
523523
* ## OPTIONS
524524
*
525-
* --slug=<slug>
525+
* [--slug=<slug>]
526526
* : Path for the new site. Subdomain on subdomain installs, directory on subdirectory installs.
527+
* Required if --site-url is not provided.
528+
*
529+
* [--site-url=<url>]
530+
* : Full URL for the new site. Use this to specify a custom domain instead of the auto-generated one.
531+
* For subdomain installs, this allows you to use a different base domain (e.g., 'http://site.example.com' instead of 'http://site.main.example.com').
532+
* For subdirectory installs, this allows you to use a different path.
533+
* If provided, --slug is optional and will be derived from the URL. If both --slug and --site-url are provided, --slug will be used as the base for internal operations (like user creation), while the domain/path from --site-url will be used for the actual site URL.
527534
*
528535
* [--title=<title>]
529536
* : Title of the new site. Default: prettified slug.
@@ -542,8 +549,17 @@ public function get( $args, $assoc_args ) {
542549
*
543550
* ## EXAMPLES
544551
*
552+
* # Create a site with auto-generated domain
545553
* $ wp site create --slug=example
546554
* Success: Site 3 created: http://www.example.com/example/
555+
*
556+
* # Create a site with a custom domain (subdomain multisite)
557+
* $ wp site create --site-url=http://site.example.com
558+
* Success: Site 4 created: http://site.example.com/
559+
*
560+
* # Create a site with a custom subdirectory (subdirectory multisite)
561+
* $ wp site create --site-url=http://example.com/custom/path/
562+
* Success: Site 5 created: http://example.com/custom/path/
547563
*/
548564
public function create( $args, $assoc_args ) {
549565
if ( ! is_multisite() ) {
@@ -552,7 +568,72 @@ public function create( $args, $assoc_args ) {
552568

553569
global $wpdb, $current_site;
554570

555-
$base = $assoc_args['slug'];
571+
// Check if either slug or site-url is provided
572+
$has_slug = isset( $assoc_args['slug'] );
573+
$has_site_url = isset( $assoc_args['site-url'] );
574+
575+
if ( ! $has_slug && ! $has_site_url ) {
576+
WP_CLI::error( 'Either --slug or --site-url must be provided.' );
577+
}
578+
579+
// If site URL is provided, parse it to get domain and path
580+
$custom_domain = null;
581+
$custom_path = null;
582+
$base = null;
583+
584+
if ( $has_site_url ) {
585+
$parsed_url = wp_parse_url( $assoc_args['site-url'] );
586+
if ( ! isset( $parsed_url['host'] ) ) {
587+
WP_CLI::error( 'Invalid URL format. Please provide a valid URL (e.g., http://site.example.com).' );
588+
}
589+
590+
// Validate the scheme if present
591+
if ( isset( $parsed_url['scheme'] ) && ! in_array( $parsed_url['scheme'], [ 'http', 'https' ], true ) ) {
592+
WP_CLI::error( 'Invalid URL scheme. Only http and https schemes are supported.' );
593+
}
594+
595+
// Sanitize domain and path
596+
$custom_domain = sanitize_text_field( $parsed_url['host'] );
597+
$custom_path = isset( $parsed_url['path'] ) ? sanitize_text_field( '/' . ltrim( $parsed_url['path'], '/' ) ) : '/';
598+
599+
// Ensure path ends with /
600+
if ( '/' !== substr( $custom_path, -1 ) ) {
601+
$custom_path .= '/';
602+
}
603+
604+
// Derive base/slug from the URL if not explicitly provided
605+
if ( ! $has_slug ) {
606+
if ( is_subdomain_install() ) {
607+
// For subdomain installs, use the first part of the domain as the base
608+
$domain_parts = explode( '.', $custom_domain );
609+
$base = $domain_parts[0];
610+
611+
// Validate that the derived base is suitable for use as a slug
612+
if ( empty( $base ) || is_numeric( $base ) ) {
613+
WP_CLI::error( 'Could not derive a valid slug from the domain (numeric-only or empty slugs are not allowed). Please provide --slug explicitly.' );
614+
}
615+
616+
// Sanitize and lowercase the derived base
617+
$base = strtolower( $base );
618+
} else {
619+
// For subdirectory installs, derive slug from the last part of the path.
620+
$path_parts = array_filter( explode( '/', trim( $custom_path, '/' ) ) );
621+
$base = (string) array_pop( $path_parts );
622+
623+
// If base is empty (root path), require explicit slug.
624+
if ( empty( $base ) ) {
625+
WP_CLI::error( 'Could not derive a valid slug from the URL path. Please provide --slug explicitly.' );
626+
}
627+
628+
// Sanitize and lowercase the derived base.
629+
$base = strtolower( $base );
630+
}
631+
} else {
632+
$base = $assoc_args['slug'];
633+
}
634+
} else {
635+
$base = $assoc_args['slug'];
636+
}
556637

557638
/**
558639
* @var string $title
@@ -603,10 +684,25 @@ public function create( $args, $assoc_args ) {
603684
}
604685
}
605686

606-
if ( is_subdomain_install() ) {
687+
if ( null !== $custom_domain ) {
688+
// A custom site URL was provided.
689+
$newdomain = $custom_domain;
690+
$path = $custom_path;
691+
692+
// For subdirectory installs, warn if the domain is different from the network's domain.
693+
if ( ! is_subdomain_install() ) {
694+
$network_domain = preg_replace( '|^www\.|', '', $current_site->domain );
695+
$custom_domain_normalized = preg_replace( '|^www\.|', '', $custom_domain );
696+
if ( $custom_domain_normalized !== $network_domain ) {
697+
WP_CLI::warning( 'Using a different domain for a subdirectory multisite install may require additional configuration (such as domain mapping) to work properly.' );
698+
}
699+
}
700+
} elseif ( is_subdomain_install() ) {
701+
// No custom site URL, use the slug to generate the domain/path for subdomain install.
607702
$newdomain = $base . '.' . preg_replace( '|^www\.|', '', $current_site->domain );
608703
$path = $current_site->path;
609704
} else {
705+
// No custom site URL, use the slug to generate the domain/path for subdirectory install.
610706
$newdomain = $current_site->domain;
611707
$path = $current_site->path . $base . '/';
612708
}

0 commit comments

Comments
 (0)