@@ -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