diff --git a/composer.json b/composer.json index 0c743040..7b3190e4 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 eec1e47c..93e60d6b 100644 --- a/features/site.feature +++ b/features/site.feature @@ -763,3 +763,83 @@ 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: + | Field | Value | + | 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 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 + """ + 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 6aafffab..9e83bdd4 100644 --- a/src/Site_Command.php +++ b/src/Site_Command.php @@ -384,6 +384,139 @@ 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 + * + * # 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() ) { + 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.' ) ) { + // 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( $url_to_parse ); + + 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 .= '/'; + } + + // Use WordPress's cached function to get the blog ID + $blog_id = get_blog_id_from_url( $domain, $path ); + + 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 ); + } + + // 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 + $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 + 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. *