From c6ab3f6548d37d8780389adec45fede7c9107e5d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 14 Feb 2026 22:31:24 +0000 Subject: [PATCH 01/11] Initial plan From 3232b098471be41066ed192e9102482024754dd7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 14 Feb 2026 22:34:00 +0000 Subject: [PATCH 02/11] Add --url parameter support to site create command Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com> --- src/Site_Command.php | 91 +++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 85 insertions(+), 6 deletions(-) diff --git a/src/Site_Command.php b/src/Site_Command.php index e1ded0171..fe5945bf7 100644 --- a/src/Site_Command.php +++ b/src/Site_Command.php @@ -389,8 +389,15 @@ public function delete( $args, $assoc_args ) { * * ## OPTIONS * - * --slug= + * [--slug=] * : Path for the new site. Subdomain on subdomain installs, directory on subdirectory installs. + * Required if --url is not provided. + * + * [--url=] + * : Full URL for the new site. Use this to specify a custom domain instead of the auto-generated one. + * 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'). + * For subdirectory installs, this allows you to use a different path. + * If provided, --slug is optional and will be derived from the URL. * * [--title=] * : Title of the new site. Default: prettified slug. @@ -409,8 +416,17 @@ public function delete( $args, $assoc_args ) { * * ## EXAMPLES * + * # Create a site with auto-generated domain * $ wp site create --slug=example * Success: Site 3 created: http://www.example.com/example/ + * + * # Create a site with a custom domain (subdomain multisite) + * $ wp site create --url=http://site.example.com + * Success: Site 4 created: http://site.example.com/ + * + * # Create a site with a custom subdirectory (subdirectory multisite) + * $ wp site create --url=http://example.com/custom/path/ + * Success: Site 5 created: http://example.com/custom/path/ */ public function create( $args, $assoc_args ) { if ( ! is_multisite() ) { @@ -419,7 +435,56 @@ public function create( $args, $assoc_args ) { global $wpdb, $current_site; - $base = $assoc_args['slug']; + // Check if either slug or url is provided + $has_slug = isset( $assoc_args['slug'] ); + $has_url = isset( $assoc_args['url'] ); + + if ( ! $has_slug && ! $has_url ) { + WP_CLI::error( 'Either --slug or --url must be provided.' ); + } + + // If URL is provided, parse it to get domain and path + $custom_domain = null; + $custom_path = null; + $base = null; + + if ( $has_url ) { + $parsed_url = parse_url( $assoc_args['url'] ); + if ( ! isset( $parsed_url['host'] ) ) { + WP_CLI::error( 'Invalid URL format. Please provide a valid URL (e.g., http://site.example.com).' ); + } + + $custom_domain = $parsed_url['host']; + $custom_path = isset( $parsed_url['path'] ) ? $parsed_url['path'] : '/'; + + // Ensure path ends with / + if ( '/' !== substr( $custom_path, -1 ) ) { + $custom_path .= '/'; + } + + // Derive base/slug from the URL if not explicitly provided + if ( ! $has_slug ) { + if ( is_subdomain_install() ) { + // For subdomain installs, use the first part of the domain as the base + $domain_parts = explode( '.', $custom_domain ); + $base = $domain_parts[0]; + } else { + // For subdirectory installs, use the path as the base + $base = trim( $custom_path, '/' ); + // Use the last part of the path if there are multiple segments + $path_parts = explode( '/', $base ); + $base = end( $path_parts ); + // If base is empty (root path), generate a random one + if ( empty( $base ) ) { + $base = 'site-' . wp_generate_password( 8, false ); + } + } + } else { + $base = $assoc_args['slug']; + } + } else { + $base = $assoc_args['slug']; + } /** * @var string $title @@ -471,11 +536,25 @@ public function create( $args, $assoc_args ) { } if ( is_subdomain_install() ) { - $newdomain = $base . '.' . preg_replace( '|^www\.|', '', $current_site->domain ); - $path = $current_site->path; + if ( null !== $custom_domain ) { + // Use custom domain if provided via --url + $newdomain = $custom_domain; + $path = $custom_path; + } else { + // Use default behavior + $newdomain = $base . '.' . preg_replace( '|^www\.|', '', $current_site->domain ); + $path = $current_site->path; + } } else { - $newdomain = $current_site->domain; - $path = $current_site->path . $base . '/'; + if ( null !== $custom_domain ) { + // Use custom domain and path if provided via --url + $newdomain = $custom_domain; + $path = $custom_path; + } else { + // Use default behavior + $newdomain = $current_site->domain; + $path = $current_site->path . $base . '/'; + } } $user_id = email_exists( $email ); From ea3049e7e0d0875a870310df8e74147113c161aa Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 14 Feb 2026 22:34:24 +0000 Subject: [PATCH 03/11] Add comprehensive Behat tests for --url parameter Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com> --- features/site-create.feature | 86 ++++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) diff --git a/features/site-create.feature b/features/site-create.feature index f068115c3..d80c51ec6 100644 --- a/features/site-create.feature +++ b/features/site-create.feature @@ -93,3 +93,89 @@ Feature: Create a new site on a WP multisite | blog_id | url | | 1 | http://localhost/ | | 2 | http://localhost/newsite/ | + + Scenario: Create site with custom URL in subdomain multisite + Given a WP multisite subdomain install + + When I run `wp site create --url=http://custom.example.com` + Then STDOUT should contain: + """ + Success: Site 2 created: http://custom.example.com/ + """ + + When I run `wp site list --fields=blog_id,url` + Then STDOUT should be a table containing rows: + | blog_id | url | + | 1 | https://example.com/ | + | 2 | http://custom.example.com/ | + + When I run `wp --url=custom.example.com option get home` + Then STDOUT should be: + """ + http://custom.example.com + """ + + Scenario: Create site with custom URL in subdirectory multisite + Given a WP multisite subdirectory install + + When I run `wp site create --url=http://example.com/custom/path/` + Then STDOUT should contain: + """ + Success: Site 2 created: http://example.com/custom/path/ + """ + + When I run `wp site list --fields=blog_id,url` + Then STDOUT should be a table containing rows: + | blog_id | url | + | 1 | https://example.com/ | + | 2 | http://example.com/custom/path/ | + + When I run `wp --url=example.com/custom/path option get home` + Then STDOUT should be: + """ + http://example.com/custom/path + """ + + Scenario: Create site with custom URL and explicit slug + Given a WP multisite subdomain install + + When I run `wp site create --url=http://custom.example.com --slug=myslug` + Then STDOUT should contain: + """ + Success: Site 2 created: http://custom.example.com/ + """ + + Scenario: Error when neither slug nor url is provided + Given a WP multisite install + + When I try `wp site create --title="Test Site"` + Then STDERR should be: + """ + Error: Either --slug or --url must be provided. + """ + And the return code should be 1 + + Scenario: Error when invalid URL format is provided + Given a WP multisite install + + When I try `wp site create --url=not-a-valid-url` + Then STDERR should contain: + """ + Error: Invalid URL format + """ + And the return code should be 1 + + Scenario: Preserve existing slug behavior + Given a WP multisite subdomain install + + When I run `wp site create --slug=testsite` + Then STDOUT should contain: + """ + Success: Site 2 created: http://testsite.example.com/ + """ + + When I run `wp site list --fields=blog_id,url` + Then STDOUT should be a table containing rows: + | blog_id | url | + | 1 | https://example.com/ | + | 2 | http://testsite.example.com/ | From f6e02b78e1b6fc253c6d0ef9066767cb470b5a1d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 14 Feb 2026 22:36:24 +0000 Subject: [PATCH 04/11] Rename --url to --site-url to avoid conflict with global WP-CLI parameter Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com> --- features/site-create.feature | 12 ++++++------ src/Site_Command.php | 28 ++++++++++++++-------------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/features/site-create.feature b/features/site-create.feature index d80c51ec6..fd9183bda 100644 --- a/features/site-create.feature +++ b/features/site-create.feature @@ -97,7 +97,7 @@ Feature: Create a new site on a WP multisite Scenario: Create site with custom URL in subdomain multisite Given a WP multisite subdomain install - When I run `wp site create --url=http://custom.example.com` + When I run `wp site create --site-url=http://custom.example.com` Then STDOUT should contain: """ Success: Site 2 created: http://custom.example.com/ @@ -118,7 +118,7 @@ Feature: Create a new site on a WP multisite Scenario: Create site with custom URL in subdirectory multisite Given a WP multisite subdirectory install - When I run `wp site create --url=http://example.com/custom/path/` + When I run `wp site create --site-url=http://example.com/custom/path/` Then STDOUT should contain: """ Success: Site 2 created: http://example.com/custom/path/ @@ -139,26 +139,26 @@ Feature: Create a new site on a WP multisite Scenario: Create site with custom URL and explicit slug Given a WP multisite subdomain install - When I run `wp site create --url=http://custom.example.com --slug=myslug` + When I run `wp site create --site-url=http://custom.example.com --slug=myslug` Then STDOUT should contain: """ Success: Site 2 created: http://custom.example.com/ """ - Scenario: Error when neither slug nor url is provided + Scenario: Error when neither slug nor site-url is provided Given a WP multisite install When I try `wp site create --title="Test Site"` Then STDERR should be: """ - Error: Either --slug or --url must be provided. + Error: Either --slug or --site-url must be provided. """ And the return code should be 1 Scenario: Error when invalid URL format is provided Given a WP multisite install - When I try `wp site create --url=not-a-valid-url` + When I try `wp site create --site-url=not-a-valid-url` Then STDERR should contain: """ Error: Invalid URL format diff --git a/src/Site_Command.php b/src/Site_Command.php index fe5945bf7..eac0f4346 100644 --- a/src/Site_Command.php +++ b/src/Site_Command.php @@ -391,9 +391,9 @@ public function delete( $args, $assoc_args ) { * * [--slug=<slug>] * : Path for the new site. Subdomain on subdomain installs, directory on subdirectory installs. - * Required if --url is not provided. + * Required if --site-url is not provided. * - * [--url=<url>] + * [--site-url=<url>] * : Full URL for the new site. Use this to specify a custom domain instead of the auto-generated one. * 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'). * For subdirectory installs, this allows you to use a different path. @@ -421,11 +421,11 @@ public function delete( $args, $assoc_args ) { * Success: Site 3 created: http://www.example.com/example/ * * # Create a site with a custom domain (subdomain multisite) - * $ wp site create --url=http://site.example.com + * $ wp site create --site-url=http://site.example.com * Success: Site 4 created: http://site.example.com/ * * # Create a site with a custom subdirectory (subdirectory multisite) - * $ wp site create --url=http://example.com/custom/path/ + * $ wp site create --site-url=http://example.com/custom/path/ * Success: Site 5 created: http://example.com/custom/path/ */ public function create( $args, $assoc_args ) { @@ -435,21 +435,21 @@ public function create( $args, $assoc_args ) { global $wpdb, $current_site; - // Check if either slug or url is provided - $has_slug = isset( $assoc_args['slug'] ); - $has_url = isset( $assoc_args['url'] ); + // Check if either slug or site-url is provided + $has_slug = isset( $assoc_args['slug'] ); + $has_site_url = isset( $assoc_args['site-url'] ); - if ( ! $has_slug && ! $has_url ) { - WP_CLI::error( 'Either --slug or --url must be provided.' ); + if ( ! $has_slug && ! $has_site_url ) { + WP_CLI::error( 'Either --slug or --site-url must be provided.' ); } - // If URL is provided, parse it to get domain and path + // If site URL is provided, parse it to get domain and path $custom_domain = null; $custom_path = null; $base = null; - if ( $has_url ) { - $parsed_url = parse_url( $assoc_args['url'] ); + if ( $has_site_url ) { + $parsed_url = parse_url( $assoc_args['site-url'] ); if ( ! isset( $parsed_url['host'] ) ) { WP_CLI::error( 'Invalid URL format. Please provide a valid URL (e.g., http://site.example.com).' ); } @@ -537,7 +537,7 @@ public function create( $args, $assoc_args ) { if ( is_subdomain_install() ) { if ( null !== $custom_domain ) { - // Use custom domain if provided via --url + // Use custom domain if provided via --site-url $newdomain = $custom_domain; $path = $custom_path; } else { @@ -547,7 +547,7 @@ public function create( $args, $assoc_args ) { } } else { if ( null !== $custom_domain ) { - // Use custom domain and path if provided via --url + // Use custom domain and path if provided via --site-url $newdomain = $custom_domain; $path = $custom_path; } else { From 4b38f5889fe818e019dd8afabeffa92ada3b1b10 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 14 Feb 2026 22:38:11 +0000 Subject: [PATCH 05/11] Fix test assertion for subdirectory multisite scenario Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com> --- features/site-create.feature | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/features/site-create.feature b/features/site-create.feature index fd9183bda..a180154c3 100644 --- a/features/site-create.feature +++ b/features/site-create.feature @@ -121,19 +121,17 @@ Feature: Create a new site on a WP multisite When I run `wp site create --site-url=http://example.com/custom/path/` Then STDOUT should contain: """ - Success: Site 2 created: http://example.com/custom/path/ + Success: Site 2 created: + """ + And STDOUT should contain: + """ + ://example.com/custom/path/ """ When I run `wp site list --fields=blog_id,url` - Then STDOUT should be a table containing rows: - | blog_id | url | - | 1 | https://example.com/ | - | 2 | http://example.com/custom/path/ | - - When I run `wp --url=example.com/custom/path option get home` - Then STDOUT should be: + Then STDOUT should contain: """ - http://example.com/custom/path + ://example.com/custom/path/ """ Scenario: Create site with custom URL and explicit slug From 4a3f2f4429188c60ee04e3fb188b987058b78a6c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 14 Feb 2026 22:40:47 +0000 Subject: [PATCH 06/11] Fix code style issues - use wp_parse_url and elseif structure Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com> --- src/Site_Command.php | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/src/Site_Command.php b/src/Site_Command.php index eac0f4346..3cfd2ba1c 100644 --- a/src/Site_Command.php +++ b/src/Site_Command.php @@ -449,7 +449,7 @@ public function create( $args, $assoc_args ) { $base = null; if ( $has_site_url ) { - $parsed_url = parse_url( $assoc_args['site-url'] ); + $parsed_url = wp_parse_url( $assoc_args['site-url'] ); if ( ! isset( $parsed_url['host'] ) ) { WP_CLI::error( 'Invalid URL format. Please provide a valid URL (e.g., http://site.example.com).' ); } @@ -545,16 +545,14 @@ public function create( $args, $assoc_args ) { $newdomain = $base . '.' . preg_replace( '|^www\.|', '', $current_site->domain ); $path = $current_site->path; } + } elseif ( null !== $custom_domain ) { + // Use custom domain and path if provided via --site-url + $newdomain = $custom_domain; + $path = $custom_path; } else { - if ( null !== $custom_domain ) { - // Use custom domain and path if provided via --site-url - $newdomain = $custom_domain; - $path = $custom_path; - } else { - // Use default behavior - $newdomain = $current_site->domain; - $path = $current_site->path . $base . '/'; - } + // Use default behavior + $newdomain = $current_site->domain; + $path = $current_site->path . $base . '/'; } $user_id = email_exists( $email ); From 8a75d2dee65bddc475438d36c77fa72297c360cb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 14 Feb 2026 22:44:09 +0000 Subject: [PATCH 07/11] Address code review feedback - improve validation and slug derivation Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com> --- features/site-create.feature | 23 +++++++++++++++++++++++ src/Site_Command.php | 15 +++++++++++---- 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/features/site-create.feature b/features/site-create.feature index a180154c3..8fb25d64e 100644 --- a/features/site-create.feature +++ b/features/site-create.feature @@ -163,6 +163,29 @@ Feature: Create a new site on a WP multisite """ And the return code should be 1 + Scenario: Error when numeric-only domain is provided without slug + Given a WP multisite subdomain install + + When I try `wp site create --site-url=http://123.example.com` + Then STDERR should be: + """ + Error: Could not derive a valid slug from the domain. Please provide --slug explicitly. + """ + And the return code should be 1 + + Scenario: Create site with different domain in subdirectory multisite + Given a WP multisite subdirectory install + + When I run `wp site create --site-url=http://custom.example.com/mypath/` + Then STDOUT should contain: + """ + Success: Site 2 created: + """ + And STDOUT should contain: + """ + ://custom.example.com/mypath/ + """ + Scenario: Preserve existing slug behavior Given a WP multisite subdomain install diff --git a/src/Site_Command.php b/src/Site_Command.php index 3cfd2ba1c..aa7802eb8 100644 --- a/src/Site_Command.php +++ b/src/Site_Command.php @@ -468,15 +468,22 @@ public function create( $args, $assoc_args ) { // For subdomain installs, use the first part of the domain as the base $domain_parts = explode( '.', $custom_domain ); $base = $domain_parts[0]; + + // Validate that the derived base is suitable for use as a slug + if ( empty( $base ) || is_numeric( $base ) ) { + WP_CLI::error( 'Could not derive a valid slug from the domain. Please provide --slug explicitly.' ); + } } else { // For subdirectory installs, use the path as the base $base = trim( $custom_path, '/' ); // Use the last part of the path if there are multiple segments - $path_parts = explode( '/', $base ); - $base = end( $path_parts ); - // If base is empty (root path), generate a random one + if ( ! empty( $base ) ) { + $path_parts = explode( '/', $base ); + $base = $path_parts[ count( $path_parts ) - 1 ]; + } + // If base is empty (root path), generate an auto slug if ( empty( $base ) ) { - $base = 'site-' . wp_generate_password( 8, false ); + $base = 'auto-' . time(); } } } else { From c0a97bc62e1e8d975e1c742a452331f66afdd55b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 14 Feb 2026 22:48:07 +0000 Subject: [PATCH 08/11] Improve error messages and auto-generated slug naming Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com> --- features/site-create.feature | 2 +- src/Site_Command.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/features/site-create.feature b/features/site-create.feature index 8fb25d64e..caef0e2bb 100644 --- a/features/site-create.feature +++ b/features/site-create.feature @@ -169,7 +169,7 @@ Feature: Create a new site on a WP multisite When I try `wp site create --site-url=http://123.example.com` Then STDERR should be: """ - Error: Could not derive a valid slug from the domain. Please provide --slug explicitly. + Error: Could not derive a valid slug from the domain (numeric-only or empty slugs are not allowed). Please provide --slug explicitly. """ And the return code should be 1 diff --git a/src/Site_Command.php b/src/Site_Command.php index aa7802eb8..0e2458bc6 100644 --- a/src/Site_Command.php +++ b/src/Site_Command.php @@ -471,7 +471,7 @@ public function create( $args, $assoc_args ) { // Validate that the derived base is suitable for use as a slug if ( empty( $base ) || is_numeric( $base ) ) { - WP_CLI::error( 'Could not derive a valid slug from the domain. Please provide --slug explicitly.' ); + WP_CLI::error( 'Could not derive a valid slug from the domain (numeric-only or empty slugs are not allowed). Please provide --slug explicitly.' ); } } else { // For subdirectory installs, use the path as the base @@ -483,7 +483,7 @@ public function create( $args, $assoc_args ) { } // If base is empty (root path), generate an auto slug if ( empty( $base ) ) { - $base = 'auto-' . time(); + $base = 'site-' . time(); } } } else { From a63eeab05bac3f2aea4c6fce65edbc1776b9d468 Mon Sep 17 00:00:00 2001 From: Pascal Birchler <pascalb@google.com> Date: Sun, 15 Feb 2026 17:04:52 +0100 Subject: [PATCH 09/11] Update src/Site_Command.php Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/Site_Command.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Site_Command.php b/src/Site_Command.php index 0e2458bc6..36102bb6e 100644 --- a/src/Site_Command.php +++ b/src/Site_Command.php @@ -479,7 +479,10 @@ public function create( $args, $assoc_args ) { // Use the last part of the path if there are multiple segments if ( ! empty( $base ) ) { $path_parts = explode( '/', $base ); - $base = $path_parts[ count( $path_parts ) - 1 ]; + $last_part = array_pop( $path_parts ); + if ( null !== $last_part && '' !== $last_part ) { + $base = $last_part; + } } // If base is empty (root path), generate an auto slug if ( empty( $base ) ) { From 72febc4112eaea64f9c0ca6d371a2b2922371051 Mon Sep 17 00:00:00 2001 From: Pascal Birchler <pascalb@google.com> Date: Sun, 15 Feb 2026 17:05:09 +0100 Subject: [PATCH 10/11] Update src/Site_Command.php Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/Site_Command.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Site_Command.php b/src/Site_Command.php index 36102bb6e..f52e798eb 100644 --- a/src/Site_Command.php +++ b/src/Site_Command.php @@ -455,7 +455,7 @@ public function create( $args, $assoc_args ) { } $custom_domain = $parsed_url['host']; - $custom_path = isset( $parsed_url['path'] ) ? $parsed_url['path'] : '/'; + $custom_path = isset( $parsed_url['path'] ) ? '/' . ltrim( $parsed_url['path'], '/' ) : '/'; // Ensure path ends with / if ( '/' !== substr( $custom_path, -1 ) ) { From f7ab7431d1280c051c25168426021d13676a63d6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 15 Feb 2026 16:10:44 +0000 Subject: [PATCH 11/11] Address PR review feedback: add validation, sanitization, and comprehensive tests Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com> --- features/site-create.feature | 54 ++++++++++++++++++++++++++++++++++-- src/Site_Command.php | 29 +++++++++++++++---- 2 files changed, 76 insertions(+), 7 deletions(-) diff --git a/features/site-create.feature b/features/site-create.feature index caef0e2bb..0b59f9a8f 100644 --- a/features/site-create.feature +++ b/features/site-create.feature @@ -163,6 +163,39 @@ Feature: Create a new site on a WP multisite """ And the return code should be 1 + Scenario: Error when invalid scheme is provided + Given a WP multisite install + + When I try `wp site create --site-url=ftp://example.com/site` + Then STDERR should be: + """ + Error: Invalid URL scheme. Only http and https schemes are supported. + """ + And the return code should be 1 + + Scenario: Error when root path provided without explicit slug + Given a WP multisite subdirectory install + + When I try `wp site create --site-url=http://example.com/` + Then STDERR should be: + """ + Error: Could not derive a valid slug from the URL path. Please provide --slug explicitly. + """ + And the return code should be 1 + + Scenario: Create site with URL without trailing slash + Given a WP multisite subdirectory install + + When I run `wp site create --site-url=http://example.com/notrailing` + Then STDOUT should contain: + """ + Success: Site 2 created: + """ + And STDOUT should contain: + """ + ://example.com/notrailing/ + """ + Scenario: Error when numeric-only domain is provided without slug Given a WP multisite subdomain install @@ -173,11 +206,15 @@ Feature: Create a new site on a WP multisite """ And the return code should be 1 - Scenario: Create site with different domain in subdirectory multisite + Scenario: Create site with different domain in subdirectory multisite shows warning Given a WP multisite subdirectory install When I run `wp site create --site-url=http://custom.example.com/mypath/` - Then STDOUT should contain: + Then STDERR should contain: + """ + Warning: Using a different domain for a subdirectory multisite install may require additional configuration + """ + And STDOUT should contain: """ Success: Site 2 created: """ @@ -186,6 +223,19 @@ Feature: Create a new site on a WP multisite ://custom.example.com/mypath/ """ + Scenario: Create site with both site-url and slug uses slug for internal operations + Given a WP multisite subdomain install + + When I run `wp site create --site-url=http://custom.example.com --slug=myslug --porcelain` + Then STDOUT should be a number + And save STDOUT as {SITE_ID} + + When I run `wp site list --site__in={SITE_ID} --field=url` + Then STDOUT should contain: + """ + custom.example.com + """ + Scenario: Preserve existing slug behavior Given a WP multisite subdomain install diff --git a/src/Site_Command.php b/src/Site_Command.php index f52e798eb..f5c41cffe 100644 --- a/src/Site_Command.php +++ b/src/Site_Command.php @@ -397,7 +397,7 @@ public function delete( $args, $assoc_args ) { * : Full URL for the new site. Use this to specify a custom domain instead of the auto-generated one. * 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'). * For subdirectory installs, this allows you to use a different path. - * If provided, --slug is optional and will be derived from the URL. + * 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. * * [--title=<title>] * : Title of the new site. Default: prettified slug. @@ -454,8 +454,14 @@ public function create( $args, $assoc_args ) { WP_CLI::error( 'Invalid URL format. Please provide a valid URL (e.g., http://site.example.com).' ); } - $custom_domain = $parsed_url['host']; - $custom_path = isset( $parsed_url['path'] ) ? '/' . ltrim( $parsed_url['path'], '/' ) : '/'; + // Validate the scheme if present + if ( isset( $parsed_url['scheme'] ) && ! in_array( $parsed_url['scheme'], [ 'http', 'https' ], true ) ) { + WP_CLI::error( 'Invalid URL scheme. Only http and https schemes are supported.' ); + } + + // Sanitize domain and path + $custom_domain = sanitize_text_field( $parsed_url['host'] ); + $custom_path = isset( $parsed_url['path'] ) ? sanitize_text_field( '/' . ltrim( $parsed_url['path'], '/' ) ) : '/'; // Ensure path ends with / if ( '/' !== substr( $custom_path, -1 ) ) { @@ -473,6 +479,9 @@ public function create( $args, $assoc_args ) { if ( empty( $base ) || is_numeric( $base ) ) { WP_CLI::error( 'Could not derive a valid slug from the domain (numeric-only or empty slugs are not allowed). Please provide --slug explicitly.' ); } + + // Sanitize and lowercase the derived base + $base = strtolower( $base ); } else { // For subdirectory installs, use the path as the base $base = trim( $custom_path, '/' ); @@ -484,10 +493,13 @@ public function create( $args, $assoc_args ) { $base = $last_part; } } - // If base is empty (root path), generate an auto slug + // If base is empty (root path), require explicit slug if ( empty( $base ) ) { - $base = 'site-' . time(); + WP_CLI::error( 'Could not derive a valid slug from the URL path. Please provide --slug explicitly.' ); } + + // Sanitize and lowercase the derived base + $base = strtolower( $base ); } } else { $base = $assoc_args['slug']; @@ -559,6 +571,13 @@ public function create( $args, $assoc_args ) { // Use custom domain and path if provided via --site-url $newdomain = $custom_domain; $path = $custom_path; + + // Warn if using a different domain in subdirectory install + $network_domain = preg_replace( '|^www\.|', '', $current_site->domain ); + $custom_domain_normalized = preg_replace( '|^www\.|', '', $custom_domain ); + if ( $custom_domain_normalized !== $network_domain ) { + WP_CLI::warning( 'Using a different domain for a subdirectory multisite install may require additional configuration (such as domain mapping) to work properly.' ); + } } else { // Use default behavior $newdomain = $current_site->domain;