Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add site generator command #498

Merged
merged 10 commits into from
May 4, 2024
Merged
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@
"site activate",
"site archive",
"site create",
"site generate",
"site deactivate",
"site delete",
"site empty",
Expand Down
107 changes: 107 additions & 0 deletions features/site-generate.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
Feature: Generate new WordPress sites

Scenario: Generate on single site
Given a WP install
When I try `wp site generate`
Then STDERR should contain:
"""
This is not a multisite installation.
"""
And STDOUT should be empty
And the return code should be 1

Scenario: Generate a specific number of sites
Given a WP multisite install
When I run `wp site generate --count=10`
And I run `wp site list --format=count`
Then STDOUT should be:
"""
11
"""

Scenario: Generate sites assigned to a specific network
Given a WP multisite install
When I try `wp site generate --count=4 --network_id=2`
Then STDERR should contain:
"""
Network with id 2 does not exist.
"""
And STDOUT should be empty
And the return code should be 1

Scenario: Generate sites and output ids
Given a WP multisite install
When I run `wp site generate --count=3 --format=ids`
When I run `wp site list --format=ids`
Then STDOUT should be:
"""
1 2 3 4
"""
And STDERR should be empty
And the return code should be 0

Scenario: Generate subdomain sites
Given a WP multisite subdomain install

When I run `wp site generate --count=1`
Then STDOUT should be empty

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://site1.example.com/ |
When I run `wp site list --format=ids`
Then STDOUT should be:
"""
1 2
"""

Scenario: Generate subdirectory sites
Given a WP multisite subdirectory install
When I run `wp site generate --count=1`
Then STDOUT should be empty
And I run `wp site list --site__in=2 --field=url | sed -e's,^\(.*\)://.*,\1,g'`
And save STDOUT as {SCHEME}

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 | {SCHEME}://example.com/site1/ |
When I run `wp site list --format=ids`
Then STDOUT should be:
"""
1 2
"""

Scenario: Generate sites with a slug
Given a WP multisite subdirectory install
When I run `wp site generate --count=2 --slug=subsite`
Then STDOUT should be empty
And I run `wp site list --site__in=2 --field=url | sed -e's,^\(.*\)://.*,\1,g'`
And save STDOUT as {SCHEME1}
And I run `wp site list --site__in=3 --field=url | sed -e's,^\(.*\)://.*,\1,g'`
And save STDOUT as {SCHEME2}

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 | {SCHEME1}://example.com/subsite1/ |
| 3 | {SCHEME2}://example.com/subsite2/ |
When I run `wp site list --format=ids`
Then STDOUT should be:
"""
1 2 3
"""

Scenario: Generate sites with reserved slug
Given a WP multisite subdirectory install
When I try `wp site generate --count=2 --slug=page`
Then STDERR should contain:
"""
The following words are reserved and cannot be used as blog names: page, comments, blog, files, feed
"""
And STDOUT should be empty
And the return code should be 1
191 changes: 189 additions & 2 deletions src/Site_Command.php
Original file line number Diff line number Diff line change
Expand Up @@ -424,8 +424,7 @@ public function create( $args, $assoc_args ) {

// If not a subdomain install, make sure the domain isn't a reserved word
if ( ! is_subdomain_install() ) {
// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Calling WordPress native hook.
$subdirectory_reserved_names = apply_filters( 'subdirectory_reserved_names', [ 'page', 'comments', 'blog', 'files', 'feed' ] );
$subdirectory_reserved_names = $this->get_subdirectory_reserved_names();
if ( in_array( $base, $subdirectory_reserved_names, true ) ) {
WP_CLI::error( 'The following words are reserved and cannot be used as blog names: ' . implode( ', ', $subdirectory_reserved_names ) );
}
Expand Down Expand Up @@ -487,6 +486,194 @@ public function create( $args, $assoc_args ) {
}
}

/**
* Generate some sites.
*
* Creates a specified number of new sites.
*
* ## OPTIONS
*
* [--count=<number>]
* : How many sites to generates?
* ---
* default: 100
* ---
*
* [--slug=<slug>]
* : Path for the new site. Subdomain on subdomain installs, directory on subdirectory installs.
*
* [--email=<email>]
* : Email for admin user. User will be created if none exists. Assignment to super admin if not included.
*
* [--network_id=<network-id>]
* : Network to associate new site with. Defaults to current network (typically 1).
*
* [--private]
* : If set, the new site will be non-public (not indexed)
*
* [--format=<format>]
* : Render output in a particular format.
* ---
* default: progress
* options:
* - progress
* - ids
* ---
*
* ## EXAMPLES
*
* # Generate 10 sites.
* $ wp site generate --count=10
* Generating sites 100% [================================================] 0:01 / 0:04
*/
public function generate( $args, $assoc_args ) {
if ( ! is_multisite() ) {
WP_CLI::error( 'This is not a multisite installation.' );
}

global $wpdb, $current_site;

$defaults = [
'count' => 100,
'email' => '',
'network_id' => 1,
'slug' => 'site',
];

$assoc_args = array_merge( $defaults, $assoc_args );

// Base.
$base = $assoc_args['slug'];
if ( preg_match( '|^([a-zA-Z0-9-])+$|', $base ) ) {
$base = strtolower( $base );
}

$is_subdomain_install = is_subdomain_install();
// If not a subdomain install, make sure the domain isn't a reserved word
if ( ! $is_subdomain_install ) {
$subdirectory_reserved_names = $this->get_subdirectory_reserved_names();
if ( in_array( $base, $subdirectory_reserved_names, true ) ) {
WP_CLI::error( 'The following words are reserved and cannot be used as blog names: ' . implode( ', ', $subdirectory_reserved_names ) );
}
}

// Network.
if ( ! empty( $assoc_args['network_id'] ) ) {
$network = $this->get_network( $assoc_args['network_id'] );
if ( false === $network ) {
WP_CLI::error( "Network with id {$assoc_args['network_id']} does not exist." );
}
} else {
$network = $current_site;
}

// Public.
$public = ! Utils\get_flag_value( $assoc_args, 'private' );

// Limit.
$limit = $assoc_args['count'];

// Email.
$email = sanitize_email( $assoc_args['email'] );
if ( empty( $email ) || ! is_email( $email ) ) {
$super_admins = get_super_admins();
$email = '';
if ( ! empty( $super_admins ) && is_array( $super_admins ) ) {
$super_login = reset( $super_admins );
$super_user = get_user_by( 'login', $super_login );
if ( $super_user ) {
$email = $super_user->user_email;
}
}
}

$user_id = email_exists( $email );
if ( ! $user_id ) {
$password = wp_generate_password( 24, false );
$user_id = wpmu_create_user( $base . '-admin', $password, $email );

if ( false === $user_id ) {
WP_CLI::error( "Can't create user." );
} else {
User_Command::wp_new_user_notification( $user_id, $password );
}
}

$format = Utils\get_flag_value( $assoc_args, 'format', 'progress' );

$notify = false;
if ( 'progress' === $format ) {
$notify = Utils\make_progress_bar( 'Generating sites', $limit );
}

for ( $index = 1; $index <= $limit; $index++ ) {
$current_base = $base . $index;
$title = ucfirst( $base ) . ' ' . $index;

if ( $is_subdomain_install ) {
$new_domain = $current_base . '.' . preg_replace( '|^www\.|', '', $network->domain );
$path = $network->path;
} else {
$new_domain = $network->domain;
$path = $network->path . $current_base . '/';
}

$wpdb->hide_errors();
$title = wp_slash( $title );
$id = wpmu_create_blog( $new_domain, $path, $title, $user_id, [ 'public' => $public ], $network->id );
$wpdb->show_errors();
if ( ! is_wp_error( $id ) ) {
if ( ! is_super_admin( $user_id ) && ! get_user_option( 'primary_blog', $user_id ) ) {
update_user_option( $user_id, 'primary_blog', $id, true );
}
} else {
WP_CLI::error( $id->get_error_message() );
}

if ( 'progress' === $format ) {
$notify->tick();
} else {
echo $id;
if ( $index < $limit - 1 ) {
echo ' ';
}
}
}

if ( 'progress' === $format ) {
$notify->finish();
}
}

/**
* Retrieves a list of reserved site on a sub-directory Multisite installation.
*
* Works on older WordPress versions where get_subdirectory_reserved_names() does not exist.
*
* @return string[] Array of reserved names.
*/
private function get_subdirectory_reserved_names() {
if ( function_exists( 'get_subdirectory_reserved_names' ) ) {
return get_subdirectory_reserved_names();
}

$names = array(
'page',
'comments',
'blog',
'files',
'feed',
'wp-admin',
'wp-content',
'wp-includes',
'wp-json',
'embed',
);

// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Calling WordPress native hook.
return apply_filters( 'subdirectory_reserved_names', $names );
}

/**
* Gets network data for a given id.
*
Expand Down
Loading