Skip to content

Commit 93476c2

Browse files
authored
Merge pull request #498 from i-am-chitti/GH-231/site-generate
2 parents 3648d0e + 9bd8441 commit 93476c2

File tree

3 files changed

+297
-2
lines changed

3 files changed

+297
-2
lines changed

composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@
125125
"site activate",
126126
"site archive",
127127
"site create",
128+
"site generate",
128129
"site deactivate",
129130
"site delete",
130131
"site empty",

features/site-generate.feature

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
Feature: Generate new WordPress sites
2+
3+
Scenario: Generate on single site
4+
Given a WP install
5+
When I try `wp site generate`
6+
Then STDERR should contain:
7+
"""
8+
This is not a multisite installation.
9+
"""
10+
And STDOUT should be empty
11+
And the return code should be 1
12+
13+
Scenario: Generate a specific number of sites
14+
Given a WP multisite install
15+
When I run `wp site generate --count=10`
16+
And I run `wp site list --format=count`
17+
Then STDOUT should be:
18+
"""
19+
11
20+
"""
21+
22+
Scenario: Generate sites assigned to a specific network
23+
Given a WP multisite install
24+
When I try `wp site generate --count=4 --network_id=2`
25+
Then STDERR should contain:
26+
"""
27+
Network with id 2 does not exist.
28+
"""
29+
And STDOUT should be empty
30+
And the return code should be 1
31+
32+
Scenario: Generate sites and output ids
33+
Given a WP multisite install
34+
When I run `wp site generate --count=3 --format=ids`
35+
When I run `wp site list --format=ids`
36+
Then STDOUT should be:
37+
"""
38+
1 2 3 4
39+
"""
40+
And STDERR should be empty
41+
And the return code should be 0
42+
43+
Scenario: Generate subdomain sites
44+
Given a WP multisite subdomain install
45+
46+
When I run `wp site generate --count=1`
47+
Then STDOUT should be empty
48+
49+
When I run `wp site list --fields=blog_id,url`
50+
Then STDOUT should be a table containing rows:
51+
| blog_id | url |
52+
| 1 | https://example.com/ |
53+
| 2 | http://site1.example.com/ |
54+
When I run `wp site list --format=ids`
55+
Then STDOUT should be:
56+
"""
57+
1 2
58+
"""
59+
60+
Scenario: Generate subdirectory sites
61+
Given a WP multisite subdirectory install
62+
When I run `wp site generate --count=1`
63+
Then STDOUT should be empty
64+
And I run `wp site list --site__in=2 --field=url | sed -e's,^\(.*\)://.*,\1,g'`
65+
And save STDOUT as {SCHEME}
66+
67+
When I run `wp site list --fields=blog_id,url`
68+
Then STDOUT should be a table containing rows:
69+
| blog_id | url |
70+
| 1 | https://example.com/ |
71+
| 2 | {SCHEME}://example.com/site1/ |
72+
When I run `wp site list --format=ids`
73+
Then STDOUT should be:
74+
"""
75+
1 2
76+
"""
77+
78+
Scenario: Generate sites with a slug
79+
Given a WP multisite subdirectory install
80+
When I run `wp site generate --count=2 --slug=subsite`
81+
Then STDOUT should be empty
82+
And I run `wp site list --site__in=2 --field=url | sed -e's,^\(.*\)://.*,\1,g'`
83+
And save STDOUT as {SCHEME1}
84+
And I run `wp site list --site__in=3 --field=url | sed -e's,^\(.*\)://.*,\1,g'`
85+
And save STDOUT as {SCHEME2}
86+
87+
When I run `wp site list --fields=blog_id,url`
88+
Then STDOUT should be a table containing rows:
89+
| blog_id | url |
90+
| 1 | https://example.com/ |
91+
| 2 | {SCHEME1}://example.com/subsite1/ |
92+
| 3 | {SCHEME2}://example.com/subsite2/ |
93+
When I run `wp site list --format=ids`
94+
Then STDOUT should be:
95+
"""
96+
1 2 3
97+
"""
98+
99+
Scenario: Generate sites with reserved slug
100+
Given a WP multisite subdirectory install
101+
When I try `wp site generate --count=2 --slug=page`
102+
Then STDERR should contain:
103+
"""
104+
The following words are reserved and cannot be used as blog names: page, comments, blog, files, feed
105+
"""
106+
And STDOUT should be empty
107+
And the return code should be 1

src/Site_Command.php

Lines changed: 189 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -424,8 +424,7 @@ public function create( $args, $assoc_args ) {
424424

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

489+
/**
490+
* Generate some sites.
491+
*
492+
* Creates a specified number of new sites.
493+
*
494+
* ## OPTIONS
495+
*
496+
* [--count=<number>]
497+
* : How many sites to generates?
498+
* ---
499+
* default: 100
500+
* ---
501+
*
502+
* [--slug=<slug>]
503+
* : Path for the new site. Subdomain on subdomain installs, directory on subdirectory installs.
504+
*
505+
* [--email=<email>]
506+
* : Email for admin user. User will be created if none exists. Assignment to super admin if not included.
507+
*
508+
* [--network_id=<network-id>]
509+
* : Network to associate new site with. Defaults to current network (typically 1).
510+
*
511+
* [--private]
512+
* : If set, the new site will be non-public (not indexed)
513+
*
514+
* [--format=<format>]
515+
* : Render output in a particular format.
516+
* ---
517+
* default: progress
518+
* options:
519+
* - progress
520+
* - ids
521+
* ---
522+
*
523+
* ## EXAMPLES
524+
*
525+
* # Generate 10 sites.
526+
* $ wp site generate --count=10
527+
* Generating sites 100% [================================================] 0:01 / 0:04
528+
*/
529+
public function generate( $args, $assoc_args ) {
530+
if ( ! is_multisite() ) {
531+
WP_CLI::error( 'This is not a multisite installation.' );
532+
}
533+
534+
global $wpdb, $current_site;
535+
536+
$defaults = [
537+
'count' => 100,
538+
'email' => '',
539+
'network_id' => 1,
540+
'slug' => 'site',
541+
];
542+
543+
$assoc_args = array_merge( $defaults, $assoc_args );
544+
545+
// Base.
546+
$base = $assoc_args['slug'];
547+
if ( preg_match( '|^([a-zA-Z0-9-])+$|', $base ) ) {
548+
$base = strtolower( $base );
549+
}
550+
551+
$is_subdomain_install = is_subdomain_install();
552+
// If not a subdomain install, make sure the domain isn't a reserved word
553+
if ( ! $is_subdomain_install ) {
554+
$subdirectory_reserved_names = $this->get_subdirectory_reserved_names();
555+
if ( in_array( $base, $subdirectory_reserved_names, true ) ) {
556+
WP_CLI::error( 'The following words are reserved and cannot be used as blog names: ' . implode( ', ', $subdirectory_reserved_names ) );
557+
}
558+
}
559+
560+
// Network.
561+
if ( ! empty( $assoc_args['network_id'] ) ) {
562+
$network = $this->get_network( $assoc_args['network_id'] );
563+
if ( false === $network ) {
564+
WP_CLI::error( "Network with id {$assoc_args['network_id']} does not exist." );
565+
}
566+
} else {
567+
$network = $current_site;
568+
}
569+
570+
// Public.
571+
$public = ! Utils\get_flag_value( $assoc_args, 'private' );
572+
573+
// Limit.
574+
$limit = $assoc_args['count'];
575+
576+
// Email.
577+
$email = sanitize_email( $assoc_args['email'] );
578+
if ( empty( $email ) || ! is_email( $email ) ) {
579+
$super_admins = get_super_admins();
580+
$email = '';
581+
if ( ! empty( $super_admins ) && is_array( $super_admins ) ) {
582+
$super_login = reset( $super_admins );
583+
$super_user = get_user_by( 'login', $super_login );
584+
if ( $super_user ) {
585+
$email = $super_user->user_email;
586+
}
587+
}
588+
}
589+
590+
$user_id = email_exists( $email );
591+
if ( ! $user_id ) {
592+
$password = wp_generate_password( 24, false );
593+
$user_id = wpmu_create_user( $base . '-admin', $password, $email );
594+
595+
if ( false === $user_id ) {
596+
WP_CLI::error( "Can't create user." );
597+
} else {
598+
User_Command::wp_new_user_notification( $user_id, $password );
599+
}
600+
}
601+
602+
$format = Utils\get_flag_value( $assoc_args, 'format', 'progress' );
603+
604+
$notify = false;
605+
if ( 'progress' === $format ) {
606+
$notify = Utils\make_progress_bar( 'Generating sites', $limit );
607+
}
608+
609+
for ( $index = 1; $index <= $limit; $index++ ) {
610+
$current_base = $base . $index;
611+
$title = ucfirst( $base ) . ' ' . $index;
612+
613+
if ( $is_subdomain_install ) {
614+
$new_domain = $current_base . '.' . preg_replace( '|^www\.|', '', $network->domain );
615+
$path = $network->path;
616+
} else {
617+
$new_domain = $network->domain;
618+
$path = $network->path . $current_base . '/';
619+
}
620+
621+
$wpdb->hide_errors();
622+
$title = wp_slash( $title );
623+
$id = wpmu_create_blog( $new_domain, $path, $title, $user_id, [ 'public' => $public ], $network->id );
624+
$wpdb->show_errors();
625+
if ( ! is_wp_error( $id ) ) {
626+
if ( ! is_super_admin( $user_id ) && ! get_user_option( 'primary_blog', $user_id ) ) {
627+
update_user_option( $user_id, 'primary_blog', $id, true );
628+
}
629+
} else {
630+
WP_CLI::error( $id->get_error_message() );
631+
}
632+
633+
if ( 'progress' === $format ) {
634+
$notify->tick();
635+
} else {
636+
echo $id;
637+
if ( $index < $limit - 1 ) {
638+
echo ' ';
639+
}
640+
}
641+
}
642+
643+
if ( 'progress' === $format ) {
644+
$notify->finish();
645+
}
646+
}
647+
648+
/**
649+
* Retrieves a list of reserved site on a sub-directory Multisite installation.
650+
*
651+
* Works on older WordPress versions where get_subdirectory_reserved_names() does not exist.
652+
*
653+
* @return string[] Array of reserved names.
654+
*/
655+
private function get_subdirectory_reserved_names() {
656+
if ( function_exists( 'get_subdirectory_reserved_names' ) ) {
657+
return get_subdirectory_reserved_names();
658+
}
659+
660+
$names = array(
661+
'page',
662+
'comments',
663+
'blog',
664+
'files',
665+
'feed',
666+
'wp-admin',
667+
'wp-content',
668+
'wp-includes',
669+
'wp-json',
670+
'embed',
671+
);
672+
673+
// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Calling WordPress native hook.
674+
return apply_filters( 'subdirectory_reserved_names', $names );
675+
}
676+
490677
/**
491678
* Gets network data for a given id.
492679
*

0 commit comments

Comments
 (0)