From 67c9c73e2f513f9e0a6129d8efcc40d961965b12 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 4 Feb 2026 16:26:27 +0000 Subject: [PATCH 1/7] Initial plan From f528456f2319b4c5d0d8f20a29ee52456b1b8624 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 4 Feb 2026 16:34:08 +0000 Subject: [PATCH 2/7] Implement wp site get command with ID and URL support Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com> --- composer.json | 1 + features/site.feature | 73 ++++++++++++++++++++++++ src/Site_Command.php | 128 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 202 insertions(+) diff --git a/composer.json b/composer.json index 0c743040a..7b3190e4d 100644 --- a/composer.json +++ b/composer.json @@ -149,6 +149,7 @@ "site deactivate", "site delete", "site empty", + "site get", "site list", "site mature", "site meta", diff --git a/features/site.feature b/features/site.feature index eec1e47c9..dce54c4c7 100644 --- a/features/site.feature +++ b/features/site.feature @@ -763,3 +763,76 @@ Feature: Manage sites in a multisite installation Then STDOUT should be a table containing rows: | blog_id | public | | 2 | 1 | + + Scenario: Get site by ID + Given a WP multisite install + + When I run `wp site create --slug=testsite --porcelain` + Then STDOUT should be a number + And save STDOUT as {SITE_ID} + + When I run `wp site get {SITE_ID} --field=blog_id` + Then STDOUT should be: + """ + {SITE_ID} + """ + + When I run `wp site get {SITE_ID}` + Then STDOUT should be a table containing rows: + | blog_id | + | {SITE_ID} | + + Scenario: Get site by URL + Given a WP multisite install + + When I run `wp site create --slug=testsite --porcelain` + Then STDOUT should be a number + And save STDOUT as {SITE_ID} + And I run `wp site list --blog_id={SITE_ID} --field=url` + And save STDOUT as {SITE_URL} + + When I run `wp site get {SITE_URL} --field=blog_id` + Then STDOUT should be: + """ + {SITE_ID} + """ + + Scenario: Get site by URL with subdirectory + Given a WP multisite subdirectory install + + When I run `wp site create --slug=mysubdir --porcelain` + Then STDOUT should be a number + And save STDOUT as {SITE_ID} + + When I run `wp site get http://example.com/mysubdir/ --field=blog_id` + Then STDOUT should be: + """ + {SITE_ID} + """ + + Scenario: Use site get with site delete + Given a WP multisite install + + When I run `wp site create --slug=deleteme --porcelain` + Then STDOUT should be a number + And save STDOUT as {SITE_ID} + + When I run `wp site delete $(wp site get http://example.com/deleteme/ --field=blog_id) --yes` + Then STDOUT should contain: + """ + Success: The site at + """ + And STDOUT should contain: + """ + was deleted. + """ + + Scenario: Get site with invalid URL should fail + Given a WP multisite install + + When I try `wp site get http://example.com/nonexistent/ --field=blog_id` + Then STDERR should contain: + """ + Error: Could not find site with URL: http://example.com/nonexistent/ + """ + And the return code should be 1 diff --git a/src/Site_Command.php b/src/Site_Command.php index 6aafffabd..499659bed 100644 --- a/src/Site_Command.php +++ b/src/Site_Command.php @@ -384,6 +384,134 @@ public function delete( $args, $assoc_args ) { WP_CLI::success( "The site at '{$site_url}' was deleted." ); } + /** + * Gets details about a site in a multisite installation. + * + * ## OPTIONS + * + * + * : Site ID or URL of the site to get. For subdirectory sites, use the full URL (e.g., http://example.com/subdir/). + * + * [--field=] + * : Instead of returning the whole site, returns the value of a single field. + * + * [--fields=] + * : Limit the output to specific fields. Defaults to all fields. + * + * [--format=] + * : Render output in a particular format. + * --- + * default: table + * options: + * - table + * - csv + * - json + * - yaml + * --- + * + * ## AVAILABLE FIELDS + * + * These fields will be displayed by default for the site: + * + * * blog_id + * * url + * * last_updated + * * registered + * + * These fields are optionally available: + * + * * site_id + * * domain + * * path + * * public + * * archived + * * mature + * * spam + * * deleted + * * lang_id + * + * ## EXAMPLES + * + * # Get site by ID + * $ wp site get 1 + * +---------+-------------------------+---------------------+---------------------+ + * | blog_id | url | last_updated | registered | + * +---------+-------------------------+---------------------+---------------------+ + * | 1 | http://example.com/ | 2025-01-01 12:00:00 | 2025-01-01 12:00:00 | + * +---------+-------------------------+---------------------+---------------------+ + * + * # Get site URL by site ID + * $ wp site get 1 --field=url + * http://example.com/ + * + * # Get site ID by URL + * $ wp site get http://example.com/subdir/ --field=blog_id + * 2 + */ + public function get( $args, $assoc_args ) { + if ( ! is_multisite() ) { + WP_CLI::error( 'This is not a multisite installation.' ); + } + + $site_arg = $args[0]; + $site = null; + + // Check if the argument is a URL (contains :// or starts with www.) + if ( false !== strpos( $site_arg, '://' ) || 0 === strpos( $site_arg, 'www.' ) ) { + // Parse the URL to get domain and path + $url_parts = wp_parse_url( $site_arg ); + + if ( ! isset( $url_parts['host'] ) ) { + WP_CLI::error( "Invalid URL: {$site_arg}" ); + } + + $domain = $url_parts['host']; + $path = isset( $url_parts['path'] ) ? $url_parts['path'] : '/'; + + // Ensure path ends with / + if ( '/' !== substr( $path, -1 ) ) { + $path .= '/'; + } + + // Query the database for the site + global $wpdb; + $site = $wpdb->get_row( + $wpdb->prepare( + "SELECT * FROM {$wpdb->blogs} WHERE domain = %s AND path = %s", + $domain, + $path + ) + ); + + if ( ! $site ) { + WP_CLI::error( "Could not find site with URL: {$site_arg}" ); + } + } else { + // Treat as site ID + $site = $this->fetcher->get_check( $site_arg ); + } + + // Get the site details and add URL + $site_data = get_object_vars( $site ); + $site_data['url'] = trailingslashit( get_home_url( $site->blog_id ) ); + + // Cast numeric fields to int for consistent output + if ( isset( $site_data['blog_id'] ) && is_scalar( $site_data['blog_id'] ) ) { + $site_data['blog_id'] = (int) $site_data['blog_id']; + } + if ( isset( $site_data['site_id'] ) && is_scalar( $site_data['site_id'] ) ) { + $site_data['site_id'] = (int) $site_data['site_id']; + } + + // Set default fields if not specified + if ( empty( $assoc_args['fields'] ) ) { + $assoc_args['fields'] = [ 'blog_id', 'url', 'last_updated', 'registered' ]; + } + + $formatter = $this->get_formatter( $assoc_args ); + $formatter->display_item( $site_data ); + } + /** * Creates a site in a multisite installation. * From 2da2c5f3fabf2c46bee2dc3eca81d304e3ec8eb7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 4 Feb 2026 16:37:32 +0000 Subject: [PATCH 3/7] Fix test expectations for site get command output format Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com> --- features/site.feature | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/features/site.feature b/features/site.feature index dce54c4c7..16e80cdbb 100644 --- a/features/site.feature +++ b/features/site.feature @@ -779,8 +779,8 @@ Feature: Manage sites in a multisite installation When I run `wp site get {SITE_ID}` Then STDOUT should be a table containing rows: - | blog_id | - | {SITE_ID} | + | Field | Value | + | blog_id | {SITE_ID} | Scenario: Get site by URL Given a WP multisite install From ad36514bb29059fec7f5c166dbd85ec9a11ddcfb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 4 Feb 2026 16:51:36 +0000 Subject: [PATCH 4/7] Add example for deleting site by URL to documentation Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com> --- src/Site_Command.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Site_Command.php b/src/Site_Command.php index 499659bed..70b3a382b 100644 --- a/src/Site_Command.php +++ b/src/Site_Command.php @@ -447,6 +447,10 @@ public function delete( $args, $assoc_args ) { * # Get site ID by URL * $ wp site get http://example.com/subdir/ --field=blog_id * 2 + * + * # Delete a site by URL + * $ wp site delete $(wp site get http://example.com/subdir/ --field=blog_id) --yes + * Success: The site at 'http://example.com/subdir/' was deleted. */ public function get( $args, $assoc_args ) { if ( ! is_multisite() ) { From 27d502e120fc468a420e19c59b35ceb81ff45be9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 4 Feb 2026 16:54:28 +0000 Subject: [PATCH 5/7] Address code review feedback: refactor field casting and improve test reliability Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com> --- features/site.feature | 9 ++++++++- src/Site_Command.php | 10 +++++----- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/features/site.feature b/features/site.feature index 16e80cdbb..93e60d6b4 100644 --- a/features/site.feature +++ b/features/site.feature @@ -817,7 +817,14 @@ Feature: Manage sites in a multisite installation Then STDOUT should be a number And save STDOUT as {SITE_ID} - When I run `wp site delete $(wp site get http://example.com/deleteme/ --field=blog_id) --yes` + When I run `wp site get http://example.com/deleteme/ --field=blog_id` + Then STDOUT should be: + """ + {SITE_ID} + """ + And save STDOUT as {BLOG_ID} + + When I run `wp site delete {BLOG_ID} --yes` Then STDOUT should contain: """ Success: The site at diff --git a/src/Site_Command.php b/src/Site_Command.php index 70b3a382b..cc37a5c2b 100644 --- a/src/Site_Command.php +++ b/src/Site_Command.php @@ -500,11 +500,11 @@ public function get( $args, $assoc_args ) { $site_data['url'] = trailingslashit( get_home_url( $site->blog_id ) ); // Cast numeric fields to int for consistent output - if ( isset( $site_data['blog_id'] ) && is_scalar( $site_data['blog_id'] ) ) { - $site_data['blog_id'] = (int) $site_data['blog_id']; - } - if ( isset( $site_data['site_id'] ) && is_scalar( $site_data['site_id'] ) ) { - $site_data['site_id'] = (int) $site_data['site_id']; + $numeric_fields = [ 'blog_id', 'site_id', 'public', 'archived', 'mature', 'spam', 'deleted', 'lang_id' ]; + foreach ( $numeric_fields as $field ) { + if ( isset( $site_data[ $field ] ) && is_scalar( $site_data[ $field ] ) ) { + $site_data[ $field ] = (int) $site_data[ $field ]; + } } // Set default fields if not specified From fe4046bd2a1cac284e3d0f399ef951cd67fb8a76 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Wed, 4 Feb 2026 14:25:03 -0500 Subject: [PATCH 6/7] Update src/Site_Command.php Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/Site_Command.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Site_Command.php b/src/Site_Command.php index cc37a5c2b..914198b91 100644 --- a/src/Site_Command.php +++ b/src/Site_Command.php @@ -462,8 +462,14 @@ public function get( $args, $assoc_args ) { // Check if the argument is a URL (contains :// or starts with www.) if ( false !== strpos( $site_arg, '://' ) || 0 === strpos( $site_arg, 'www.' ) ) { + // Normalize scheme-less URLs starting with www. for proper parsing. + $url_to_parse = $site_arg; + if ( 0 === strpos( $site_arg, 'www.' ) && false === strpos( $site_arg, '://' ) ) { + $url_to_parse = 'http://' . $site_arg; + } + // Parse the URL to get domain and path - $url_parts = wp_parse_url( $site_arg ); + $url_parts = wp_parse_url( $url_to_parse ); if ( ! isset( $url_parts['host'] ) ) { WP_CLI::error( "Invalid URL: {$site_arg}" ); From e91a6e5c2fbbeded53c4a7959c91f08c51f9b9a6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 5 Feb 2026 16:20:03 +0000 Subject: [PATCH 7/7] Use get_blog_id_from_url() for cached URL lookups Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com> --- src/Site_Command.php | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/src/Site_Command.php b/src/Site_Command.php index 914198b91..9e83bdd43 100644 --- a/src/Site_Command.php +++ b/src/Site_Command.php @@ -483,19 +483,14 @@ public function get( $args, $assoc_args ) { $path .= '/'; } - // Query the database for the site - global $wpdb; - $site = $wpdb->get_row( - $wpdb->prepare( - "SELECT * FROM {$wpdb->blogs} WHERE domain = %s AND path = %s", - $domain, - $path - ) - ); + // Use WordPress's cached function to get the blog ID + $blog_id = get_blog_id_from_url( $domain, $path ); - if ( ! $site ) { + if ( ! $blog_id ) { WP_CLI::error( "Could not find site with URL: {$site_arg}" ); } + + $site = $this->fetcher->get_check( $blog_id ); } else { // Treat as site ID $site = $this->fetcher->get_check( $site_arg );