From bb111bc6caf1b1dd2ab6af0263dd3c70216453c1 Mon Sep 17 00:00:00 2001 From: Simon Date: Fri, 8 May 2026 10:59:52 +0200 Subject: [PATCH 1/2] Fix phpstan Offset 'number' might not exist on array --- .../includes/classes/class-wp-ms-rest-networks-controller.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wp-multi-network/includes/classes/class-wp-ms-rest-networks-controller.php b/wp-multi-network/includes/classes/class-wp-ms-rest-networks-controller.php index bd1ebb5..f179690 100644 --- a/wp-multi-network/includes/classes/class-wp-ms-rest-networks-controller.php +++ b/wp-multi-network/includes/classes/class-wp-ms-rest-networks-controller.php @@ -168,7 +168,7 @@ public function get_items( $request ) { $prepared_args['no_found_rows'] = false; - if ( isset( $registered['page'] ) && empty( $request['offset'] ) ) { + if ( isset( $registered['page'] ) && isset( $prepared_args['number'] ) && empty( $request['offset'] ) ) { $prepared_args['offset'] = $prepared_args['number'] * ( absint( $request['page'] ) - 1 ); } From 8f1cca4a92a3670f39319a34ac7e747d4b5c6dd2 Mon Sep 17 00:00:00 2001 From: Simon Date: Fri, 8 May 2026 10:50:44 +0200 Subject: [PATCH 2/2] Existing site as root site for new network --- .../tests/test-add-network-existing-site.php | 177 ++++++++++++++++++ .../assets/css/wp-multi-network-rtl.css | 20 +- .../assets/css/wp-multi-network-rtl.min.css | 2 +- .../assets/css/wp-multi-network.css | 20 +- .../assets/css/wp-multi-network.min.css | 2 +- .../assets/js/wp-multi-network.js | 13 ++ .../assets/js/wp-multi-network.min.js | 2 +- .../classes/class-wp-ms-networks-admin.php | 54 +++++- wp-multi-network/includes/functions.php | 55 ++++-- .../includes/metaboxes/edit-network.php | 38 +++- 10 files changed, 361 insertions(+), 22 deletions(-) create mode 100644 tests/integration/tests/test-add-network-existing-site.php diff --git a/tests/integration/tests/test-add-network-existing-site.php b/tests/integration/tests/test-add-network-existing-site.php new file mode 100644 index 0000000..ed9c13b --- /dev/null +++ b/tests/integration/tests/test-add-network-existing-site.php @@ -0,0 +1,177 @@ +factory->user->create( + array( + 'role' => 'administrator', + ) + ); + grant_super_admin( $user_id ); + + // Create a site to use as the existing root site. + $site_id = $this->factory->blog->create( + array( + 'domain' => 'existing-site.example.com', + 'path' => '/', + ) + ); + + // Create a network using the existing site. + $network_id = add_network( + array( + 'domain' => 'existing-site.example.com', + 'path' => '/', + 'site_name' => 'Existing Site', + 'network_name' => 'Test Network', + 'user_id' => $user_id, + 'network_admin_id' => $user_id, + 'existing_blog_id' => $site_id, + ) + ); + + // Verify network was created successfully. + $this->assertNotWPError( $network_id, 'Network should be created successfully' ); + $this->assertIsInt( $network_id, 'Network ID should be an integer' ); + + // Verify the site was moved to the new network. + $site = get_site( $site_id ); + $this->assertEquals( $network_id, (int) $site->network_id, 'Site should be in the new network' ); + + // Verify the site is the main site of the new network. + $main_site_id = get_network_option( $network_id, 'main_site' ); + $this->assertEquals( $site_id, (int) $main_site_id, 'Site should be the main site of the new network' ); + } + + /** + * Test adding a network with a nonexistent site returns an error. + * + * @since NEXT + */ + public function test_add_network_with_nonexistent_site() { + + // Create a test user. + $user_id = $this->factory->user->create( + array( + 'role' => 'administrator', + ) + ); + grant_super_admin( $user_id ); + + // Attempt to create a network with a nonexistent site ID. + $result = add_network( + array( + 'domain' => 'nonexistent.example.com', + 'path' => '/', + 'site_name' => 'Test Site', + 'network_name' => 'Test Network', + 'user_id' => $user_id, + 'network_admin_id' => $user_id, + 'existing_blog_id' => 999999, + ) + ); + + $this->assertWPError( $result, 'Should return WP_Error for nonexistent site' ); + $this->assertEquals( 'blog_not_exist', $result->get_error_code(), 'Error code should be blog_not_exist' ); + } + + /** + * Test adding a network with a main site of another network returns an error. + * + * @since NEXT + */ + public function test_add_network_with_main_site() { + + // Create a test user. + $user_id = $this->factory->user->create( + array( + 'role' => 'administrator', + ) + ); + grant_super_admin( $user_id ); + + // Create a network with add_network() so it has a real main site. + $first_network_id = add_network( + array( + 'domain' => 'first-network.example.com', + 'path' => '/', + 'site_name' => 'First Network Site', + 'network_name' => 'First Network', + 'user_id' => $user_id, + 'network_admin_id' => $user_id, + ) + ); + + // Get the main site of the first network. + $main_site_id = get_main_site_id( $first_network_id ); + + // Attempt to create a second network using the main site. + $result = add_network( + array( + 'domain' => 'second-network.example.com', + 'path' => '/', + 'site_name' => 'Test Site', + 'network_name' => 'Second Network', + 'user_id' => $user_id, + 'network_admin_id' => $user_id, + 'existing_blog_id' => $main_site_id, + ) + ); + + $this->assertWPError( $result, 'Should return WP_Error when using a main site' ); + $this->assertEquals( 'blog_is_main_site', $result->get_error_code(), 'Error code should be blog_is_main_site' ); + } + + /** + * Test adding a network without existing_blog_id creates a new site. + * + * @since NEXT + */ + public function test_add_network_without_existing_site() { + + // Create a test user. + $user_id = $this->factory->user->create( + array( + 'role' => 'administrator', + ) + ); + grant_super_admin( $user_id ); + + // Create a network without existing_blog_id. + $network_id = add_network( + array( + 'domain' => 'site-test.example.com', + 'path' => '/', + 'site_name' => 'Test Site', + 'network_name' => 'Test Network', + 'user_id' => $user_id, + 'network_admin_id' => $user_id, + ) + ); + + // Verify network was created successfully. + $this->assertNotWPError( $network_id, 'Network should be created successfully' ); + $this->assertIsInt( $network_id, 'Network ID should be an integer' ); + + // Verify a new site was created as the main site. + $main_site_id = get_network_option( $network_id, 'main_site' ); + $this->assertNotEmpty( $main_site_id, 'A main site should have been created' ); + + $main_site = get_site( $main_site_id ); + $this->assertNotNull( $main_site, 'Main site should exist' ); + $this->assertEquals( 'site-test.example.com', $main_site->domain, 'Main site should have the network domain' ); + } +} diff --git a/wp-multi-network/assets/css/wp-multi-network-rtl.css b/wp-multi-network/assets/css/wp-multi-network-rtl.css index 110ecab..99c7a3a 100644 --- a/wp-multi-network/assets/css/wp-multi-network-rtl.css +++ b/wp-multi-network/assets/css/wp-multi-network-rtl.css @@ -1,12 +1,15 @@ th.column-title { width: 35%; } + th.column-path { width: 15%; } + th.column-blogs { width: 10%; } + td.column-domain { white-space: nowrap; overflow: hidden; @@ -123,6 +126,7 @@ table.assign-sites td.column-available { table.assign-sites td.column-assigned { padding-right: 0; } + table.assign-sites td.column-available, table.assign-sites td.column-assigned { width: 42%; @@ -135,8 +139,8 @@ table.assign-sites td.column-assigned { div.network-delete { background: #fff; border-right: 4px solid #dc3232; - -webkit-box-shadow: 0 1px 1px 0 rgba( 0, 0, 0, 0.1 ); - box-shadow: 0 1px 1px 0 rgba( 0, 0, 0, 0.1 ); + -webkit-box-shadow: 0 1px 1px 0 rgba(0, 0, 0, 0.1); + box-shadow: 0 1px 1px 0 rgba(0, 0, 0, 0.1); margin: 5px 0 2px; padding: 1px 12px; } @@ -154,3 +158,15 @@ ul.delete-sites { ul.delete-sites li { margin: 0; } + +#root-site-toggle { + margin-top: 12px; +} + +#root-site-toggle label { + margin-left: 16px; +} + +.root-site-existing { + display: none; +} diff --git a/wp-multi-network/assets/css/wp-multi-network-rtl.min.css b/wp-multi-network/assets/css/wp-multi-network-rtl.min.css index b0b73d2..fb846b1 100644 --- a/wp-multi-network/assets/css/wp-multi-network-rtl.min.css +++ b/wp-multi-network/assets/css/wp-multi-network-rtl.min.css @@ -1 +1 @@ -th.column-title{width:35%}th.column-path{width:15%}th.column-blogs{width:10%}td.column-domain{overflow:hidden;white-space:nowrap}.edit-network label span.scheme{display:inline-block}.edit-network .form-field label input[type=text]{display:inline-block;width:50%}#poststuff #wpmn-edit-network-assign-sites .inside{margin:0;padding:0}#wpmn-edit-network-publish .submitbox,#wpmn-move-site-publish .submitbox{margin:10px -12px -12px}#wpmn-edit-network-publish #major-publishing-actions,#wpmn-move-site-publish #major-publishing-actions{margin-top:10px}#misc-publishing-actions #network:before,#misc-publishing-actions #sites:before{font:normal 20px/1 dashicons;speak:none;display:inline-block;right:-1px;padding:0 0 0 2px;position:relative;top:0;vertical-align:top;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;text-decoration:none!important}#misc-publishing-actions #network:before{content:"\f319"}#misc-publishing-actions #sites:before{content:"\f325"}table.widefat.assign-sites,table.widefat.move-site{border:none;box-shadow:none}table.assign-sites thead th,table.move-site thead th{background:#f4f4f4;font-size:13px;font-weight:600;text-align:center}.networks tbody td,.networks tbody th{box-shadow:inset 0 -1px 0 rgba(0,0,0,.1);padding:10px 9px}.networks .current th.check-column{border-right:4px solid #00a0d2}.networks .current td,.networks .current th{background-color:#f7fcfe;padding:10px 9px}table.assign-sites select[multiple]{height:80px!important;margin:0;width:100%}table.assign-sites .button{height:80px;margin:0 5px;width:35%}table.assign-sites td.column-actions{padding:8px 0;width:16%}table.assign-sites td.column-actions .assign{content:"\f341";float:right}table.assign-sites td.column-actions .unassign{float:left}table.assign-sites td.column-available{padding-left:0}table.assign-sites td.column-assigned{padding-right:0}table.assign-sites td.column-assigned,table.assign-sites td.column-available{width:42%}table.assign-sites td.column-assigned{text-align:left}div.network-delete{background:#fff;border-right:4px solid #dc3232;box-shadow:0 1px 1px 0 rgba(0,0,0,.1);margin:5px 0 2px;padding:1px 12px}div.network-delete p{margin:.5em 0;padding:2px}ul.delete-sites{list-style:square;margin:10px 20px}ul.delete-sites li{margin:0} +th.column-title{width:35%}th.column-path{width:15%}th.column-blogs{width:10%}td.column-domain{overflow:hidden;white-space:nowrap}.edit-network label span.scheme{display:inline-block}.edit-network .form-field label input[type=text]{display:inline-block;width:50%}#poststuff #wpmn-edit-network-assign-sites .inside{margin:0;padding:0}#wpmn-edit-network-publish .submitbox,#wpmn-move-site-publish .submitbox{margin:10px -12px -12px}#wpmn-edit-network-publish #major-publishing-actions,#wpmn-move-site-publish #major-publishing-actions{margin-top:10px}#misc-publishing-actions #network:before,#misc-publishing-actions #sites:before{font:normal 20px/1 dashicons;speak:none;display:inline-block;right:-1px;padding:0 0 0 2px;position:relative;top:0;vertical-align:top;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;text-decoration:none!important}#misc-publishing-actions #network:before{content:"\f319"}#misc-publishing-actions #sites:before{content:"\f325"}table.widefat.assign-sites,table.widefat.move-site{border:none;box-shadow:none}table.assign-sites thead th,table.move-site thead th{background:#f4f4f4;font-size:13px;font-weight:600;text-align:center}.networks tbody td,.networks tbody th{box-shadow:inset 0 -1px 0 rgba(0,0,0,.1);padding:10px 9px}.networks .current th.check-column{border-right:4px solid #00a0d2}.networks .current td,.networks .current th{background-color:#f7fcfe;padding:10px 9px}table.assign-sites select[multiple]{height:80px!important;margin:0;width:100%}table.assign-sites .button{height:80px;margin:0 5px;width:35%}table.assign-sites td.column-actions{padding:8px 0;width:16%}table.assign-sites td.column-actions .assign{content:"\f341";float:right}table.assign-sites td.column-actions .unassign{float:left}table.assign-sites td.column-available{padding-left:0}table.assign-sites td.column-assigned{padding-right:0}table.assign-sites td.column-assigned,table.assign-sites td.column-available{width:42%}table.assign-sites td.column-assigned{text-align:left}div.network-delete{background:#fff;border-right:4px solid #dc3232;box-shadow:0 1px 1px 0 rgba(0,0,0,.1);margin:5px 0 2px;padding:1px 12px}div.network-delete p{margin:.5em 0;padding:2px}ul.delete-sites{list-style:square;margin:10px 20px}ul.delete-sites li{margin:0}#root-site-toggle{margin-top:12px}#root-site-toggle label{margin-left:16px}.root-site-existing{display:none} diff --git a/wp-multi-network/assets/css/wp-multi-network.css b/wp-multi-network/assets/css/wp-multi-network.css index 4738b4f..1db0aba 100644 --- a/wp-multi-network/assets/css/wp-multi-network.css +++ b/wp-multi-network/assets/css/wp-multi-network.css @@ -1,12 +1,15 @@ th.column-title { width: 35%; } + th.column-path { width: 15%; } + th.column-blogs { width: 10%; } + td.column-domain { white-space: nowrap; overflow: hidden; @@ -123,6 +126,7 @@ table.assign-sites td.column-available { table.assign-sites td.column-assigned { padding-left: 0; } + table.assign-sites td.column-available, table.assign-sites td.column-assigned { width: 42%; @@ -135,8 +139,8 @@ table.assign-sites td.column-assigned { div.network-delete { background: #fff; border-left: 4px solid #dc3232; - -webkit-box-shadow: 0 1px 1px 0 rgba( 0, 0, 0, 0.1 ); - box-shadow: 0 1px 1px 0 rgba( 0, 0, 0, 0.1 ); + -webkit-box-shadow: 0 1px 1px 0 rgba(0, 0, 0, 0.1); + box-shadow: 0 1px 1px 0 rgba(0, 0, 0, 0.1); margin: 5px 0 2px; padding: 1px 12px; } @@ -154,3 +158,15 @@ ul.delete-sites { ul.delete-sites li { margin: 0; } + +#root-site-toggle { + margin-top: 12px; +} + +#root-site-toggle label { + margin-right: 16px; +} + +.root-site-existing { + display: none; +} diff --git a/wp-multi-network/assets/css/wp-multi-network.min.css b/wp-multi-network/assets/css/wp-multi-network.min.css index 175a66d..23ae7de 100644 --- a/wp-multi-network/assets/css/wp-multi-network.min.css +++ b/wp-multi-network/assets/css/wp-multi-network.min.css @@ -1 +1 @@ -th.column-title{width:35%}th.column-path{width:15%}th.column-blogs{width:10%}td.column-domain{overflow:hidden;white-space:nowrap}.edit-network label span.scheme{display:inline-block}.edit-network .form-field label input[type=text]{display:inline-block;width:50%}#poststuff #wpmn-edit-network-assign-sites .inside{margin:0;padding:0}#wpmn-edit-network-publish .submitbox,#wpmn-move-site-publish .submitbox{margin:10px -12px -12px}#wpmn-edit-network-publish #major-publishing-actions,#wpmn-move-site-publish #major-publishing-actions{margin-top:10px}#misc-publishing-actions #network:before,#misc-publishing-actions #sites:before{font:normal 20px/1 dashicons;speak:none;display:inline-block;left:-1px;padding:0 2px 0 0;position:relative;top:0;vertical-align:top;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;text-decoration:none!important}#misc-publishing-actions #network:before{content:"\f319"}#misc-publishing-actions #sites:before{content:"\f325"}table.widefat.assign-sites,table.widefat.move-site{border:none;box-shadow:none}table.assign-sites thead th,table.move-site thead th{background:#f4f4f4;font-size:13px;font-weight:600;text-align:center}.networks tbody td,.networks tbody th{box-shadow:inset 0 -1px 0 rgba(0,0,0,.1);padding:10px 9px}.networks .current th.check-column{border-left:4px solid #00a0d2}.networks .current td,.networks .current th{background-color:#f7fcfe;padding:10px 9px}table.assign-sites select[multiple]{height:80px!important;margin:0;width:100%}table.assign-sites .button{height:80px;margin:0 5px;width:35%}table.assign-sites td.column-actions{padding:8px 0;width:16%}table.assign-sites td.column-actions .assign{content:"\f341";float:left}table.assign-sites td.column-actions .unassign{float:right}table.assign-sites td.column-available{padding-right:0}table.assign-sites td.column-assigned{padding-left:0}table.assign-sites td.column-assigned,table.assign-sites td.column-available{width:42%}table.assign-sites td.column-assigned{text-align:right}div.network-delete{background:#fff;border-left:4px solid #dc3232;box-shadow:0 1px 1px 0 rgba(0,0,0,.1);margin:5px 0 2px;padding:1px 12px}div.network-delete p{margin:.5em 0;padding:2px}ul.delete-sites{list-style:square;margin:10px 20px}ul.delete-sites li{margin:0} +th.column-title{width:35%}th.column-path{width:15%}th.column-blogs{width:10%}td.column-domain{overflow:hidden;white-space:nowrap}.edit-network label span.scheme{display:inline-block}.edit-network .form-field label input[type=text]{display:inline-block;width:50%}#poststuff #wpmn-edit-network-assign-sites .inside{margin:0;padding:0}#wpmn-edit-network-publish .submitbox,#wpmn-move-site-publish .submitbox{margin:10px -12px -12px}#wpmn-edit-network-publish #major-publishing-actions,#wpmn-move-site-publish #major-publishing-actions{margin-top:10px}#misc-publishing-actions #network:before,#misc-publishing-actions #sites:before{font:normal 20px/1 dashicons;speak:none;display:inline-block;left:-1px;padding:0 2px 0 0;position:relative;top:0;vertical-align:top;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;text-decoration:none!important}#misc-publishing-actions #network:before{content:"\f319"}#misc-publishing-actions #sites:before{content:"\f325"}table.widefat.assign-sites,table.widefat.move-site{border:none;box-shadow:none}table.assign-sites thead th,table.move-site thead th{background:#f4f4f4;font-size:13px;font-weight:600;text-align:center}.networks tbody td,.networks tbody th{box-shadow:inset 0 -1px 0 rgba(0,0,0,.1);padding:10px 9px}.networks .current th.check-column{border-left:4px solid #00a0d2}.networks .current td,.networks .current th{background-color:#f7fcfe;padding:10px 9px}table.assign-sites select[multiple]{height:80px!important;margin:0;width:100%}table.assign-sites .button{height:80px;margin:0 5px;width:35%}table.assign-sites td.column-actions{padding:8px 0;width:16%}table.assign-sites td.column-actions .assign{content:"\f341";float:left}table.assign-sites td.column-actions .unassign{float:right}table.assign-sites td.column-available{padding-right:0}table.assign-sites td.column-assigned{padding-left:0}table.assign-sites td.column-assigned,table.assign-sites td.column-available{width:42%}table.assign-sites td.column-assigned{text-align:right}div.network-delete{background:#fff;border-left:4px solid #dc3232;box-shadow:0 1px 1px 0 rgba(0,0,0,.1);margin:5px 0 2px;padding:1px 12px}div.network-delete p{margin:.5em 0;padding:2px}ul.delete-sites{list-style:square;margin:10px 20px}ul.delete-sites li{margin:0}#root-site-toggle{margin-top:12px}#root-site-toggle label{margin-right:16px}.root-site-existing{display:none} diff --git a/wp-multi-network/assets/js/wp-multi-network.js b/wp-multi-network/assets/js/wp-multi-network.js index a4794fa..e17b3cc 100644 --- a/wp-multi-network/assets/js/wp-multi-network.js +++ b/wp-multi-network/assets/js/wp-multi-network.js @@ -25,6 +25,19 @@ jQuery( document ).ready( function ( $ ) { move( 'to', 'from' ); } ); + /* Toggle root site option between new and existing */ + $( 'input[name=root_site_option]' ).on( 'change', function () { + if ( $( this ).val() === 'existing' ) { + $( '.root-site-new' ).hide(); + $( '.root-site-existing' ).show(); + $( '#wpmn-edit-network-details' ).hide(); + } else { + $( '.root-site-new' ).show(); + $( '.root-site-existing' ).hide(); + $( '#wpmn-edit-network-details' ).show(); + } + } ); + /* Select all sites in "selected" box when submitting */ $( '#edit-network-form' ).submit( function () { $( '#to' ).children( 'option:enabled' ).attr( 'selected', true ); diff --git a/wp-multi-network/assets/js/wp-multi-network.min.js b/wp-multi-network/assets/js/wp-multi-network.min.js index c004779..5885d25 100644 --- a/wp-multi-network/assets/js/wp-multi-network.min.js +++ b/wp-multi-network/assets/js/wp-multi-network.min.js @@ -1 +1 @@ -(()=>{var e={822:()=>{jQuery(document).ready(function(e){function t(e,t){jQuery("#"+e).children("option:selected").each(function(){jQuery("#"+t).append(jQuery(this).clone()),jQuery(this).remove()})}e(".if-js-closed").removeClass("if-js-closed").addClass("closed"),e(".postbox").children("h3").click(function(){e(this.parentNode).hasClass("closed")?e(this.parentNode).removeClass("closed"):e(this.parentNode).addClass("closed")}),e("input[name=assign]").click(function(){t("from","to")}),e("input[name=unassign]").click(function(){t("to","from")}),e("#edit-network-form").submit(function(){e("#to").children("option:enabled").attr("selected",!0),e("#from").children("option:enabled").attr("selected",!0)})})}},t={};function o(n){var r=t[n];if(void 0!==r)return r.exports;var s=t[n]={exports:{}};return e[n](s,s.exports,o),s.exports}o.n=e=>{var t=e&&e.__esModule?()=>e.default:()=>e;return o.d(t,{a:t}),t},o.d=(e,t)=>{for(var n in t)o.o(t,n)&&!o.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},o.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),(()=>{"use strict";o(822)})()})(); \ No newline at end of file +jQuery(document).ready(function(e){function t(e,t){jQuery("#"+e).children("option:selected").each(function(){jQuery("#"+t).append(jQuery(this).clone()),jQuery(this).remove()})}e(".if-js-closed").removeClass("if-js-closed").addClass("closed"),e(".postbox").children("h3").click(function(){e(this.parentNode).hasClass("closed")?e(this.parentNode).removeClass("closed"):e(this.parentNode).addClass("closed")}),e("input[name=assign]").click(function(){t("from","to")}),e("input[name=unassign]").click(function(){t("to","from")}),e("input[name=root_site_option]").on("change",function(){"existing"===e(this).val()?(e(".root-site-new").hide(),e(".root-site-existing").show(),e("#wpmn-edit-network-details").hide()):(e(".root-site-new").show(),e(".root-site-existing").hide(),e("#wpmn-edit-network-details").show())}),e("#edit-network-form").submit(function(){e("#to").children("option:enabled").attr("selected",!0),e("#from").children("option:enabled").attr("selected",!0)})}); \ No newline at end of file diff --git a/wp-multi-network/includes/classes/class-wp-ms-networks-admin.php b/wp-multi-network/includes/classes/class-wp-ms-networks-admin.php index 883a1aa..48b4d8e 100644 --- a/wp-multi-network/includes/classes/class-wp-ms-networks-admin.php +++ b/wp-multi-network/includes/classes/class-wp-ms-networks-admin.php @@ -472,6 +472,21 @@ public function page_edit_network() { + + +
+
+ + +
+
+
@@ -1009,6 +1024,38 @@ private function handle_add_network() { ? sanitize_text_field( wp_unslash( $_POST['new_site'] ) ) : $network_title; // Fallback to network title if not explicitly set. + // Root site option: 'new' (default) or 'existing'. + $root_site_option = ! empty( $_POST['root_site_option'] ) + ? sanitize_key( wp_unslash( $_POST['root_site_option'] ) ) + : 'new'; + + $existing_site_id = 0; + + if ( 'existing' === $root_site_option && ! empty( $_POST['existing_site_id'] ) ) { + $existing_site_id = absint( wp_unslash( $_POST['existing_site_id'] ) ); + + $existing_site = get_site( $existing_site_id ); + + // Validate the existing site. + if ( empty( $existing_site ) || is_main_site_for_network( $existing_site_id ) ) { + $this->handle_redirect( + array( + 'page' => 'add-new-network', + 'network_created' => '0', + ) + ); + } + + // Use the existing site's domain and path for the network. + $network_domain = $existing_site->domain; + $network_path = $existing_site->path; + $site_name = $existing_site->blogname; + + if ( empty( $network_title ) ) { + $network_title = $existing_site->blogname; + } + } + // Bail if missing fields. if ( empty( $network_domain ) || empty( $network_path ) ) { $this->handle_redirect( @@ -1029,6 +1076,11 @@ private function handle_add_network() { 'options_to_clone' => $options_to_clone, ); + // Use existing site as root site if selected. + if ( $existing_site_id > 0 ) { + $args['existing_blog_id'] = $existing_site_id; + } + // Add network. $result = add_network( $args ); @@ -1357,7 +1409,7 @@ private function handle_delete_networks() { * @since 2.0.0 * * @param array $args Optional. URL query arguments. Default empty array. - * @return void + * @return never */ private function handle_redirect( $args = array() ) { wp_safe_redirect( $this->admin_url( $args ) ); diff --git a/wp-multi-network/includes/functions.php b/wp-multi-network/includes/functions.php index 10d3a32..0f4e6c0 100644 --- a/wp-multi-network/includes/functions.php +++ b/wp-multi-network/includes/functions.php @@ -438,6 +438,9 @@ function insert_network( $domain = '', $path = '/' ) { * @type string $network_name Name of the new network. * @type integer $user_id ID of the user to add as the site owner. * Defaults to current user ID. + * @type integer $existing_blog_id Optional. ID of an existing site to use as the + * root site instead of creating a new one. + * Default 0. @since NEXT. * @type integer $network_admin_id ID of the user to add as the network administrator. * Defaults to current user ID. * @type array $meta Array of metadata to save to this network. @@ -515,6 +518,9 @@ function add_network( $args = array() ) { 'user_id' => $current_user_id, 'meta' => $default_site_meta, + // Existing site argument. + 'existing_blog_id' => 0, + // Network arguments. 'network_name' => esc_attr__( 'New Network', 'wp-multi-network' ), 'network_admin_id' => $current_user_id, @@ -549,6 +555,20 @@ function add_network( $args = array() ) { $r['domain'] = str_replace( ' ', '', strtolower( $r['domain'] ) ); $r['path'] = str_replace( ' ', '', strtolower( $r['path'] ) ); + // Validate existing site before creating the network. + if ( ! empty( $r['existing_blog_id'] ) ) { + + $existing_site = get_site( $r['existing_blog_id'] ); + + if ( empty( $existing_site ) ) { + return new WP_Error( 'blog_not_exist', esc_html__( 'The specified site does not exist.', 'wp-multi-network' ) ); + } + + if ( is_main_site_for_network( $r['existing_blog_id'] ) ) { + return new WP_Error( 'blog_is_main_site', esc_html__( 'The specified site is already a main site for another network.', 'wp-multi-network' ) ); + } + } + // Insert the new network. $new_network_id = insert_network( $r['domain'], $r['path'] ); @@ -570,17 +590,28 @@ function add_network( $args = array() ) { // Make sure upload constants are defined. ms_upload_constants(); - // Attempt to create the site. - $new_blog_id = wpmu_create_blog( - $r['domain'], - $r['path'], - $r['site_name'], - $r['user_id'], - $r['meta'], - $new_network_id - ); + // Use existing site or create a new one. + if ( ! empty( $r['existing_blog_id'] ) ) { + + // Move the existing site to the new network. + move_site( $r['existing_blog_id'], $new_network_id ); + + $new_blog_id = (int) $r['existing_blog_id']; + + } else { + + // Attempt to create a new site. + $new_blog_id = wpmu_create_blog( + $r['domain'], + $r['path'], + $r['site_name'], + $r['user_id'], + $r['meta'], + $new_network_id + ); + } - // Grant super admin priviledges. + // Grant super admin privileges. grant_super_admin( $r['network_admin_id'] ); // Restore current network. @@ -615,13 +646,13 @@ function add_network( $args = array() ) { update_network_option( $new_network_id, $key, $value ); } - // Fix upload path and URLs in WP < 3.7. + // Fix upload path and URLs in WP < 3.7 (only for newly created sites). $use_files_rewriting = defined( 'SITE_ID_CURRENT_SITE' ) && get_network( SITE_ID_CURRENT_SITE ) ? get_network_option( SITE_ID_CURRENT_SITE, 'ms_files_rewriting' ) : get_site_option( 'ms_files_rewriting' ); // Not using rewriting, and using a newer version of WordPress than 3.7. - if ( empty( $use_files_rewriting ) && version_compare( $wp_version, '3.7', '>' ) ) { + if ( empty( $r['existing_blog_id'] ) && empty( $use_files_rewriting ) && version_compare( $wp_version, '3.7', '>' ) ) { // WP_CONTENT_URL is locked to the current site and can't be overridden, // so we have to replace the hostname the hard way. diff --git a/wp-multi-network/includes/metaboxes/edit-network.php b/wp-multi-network/includes/metaboxes/edit-network.php index b214387..5e502fa 100644 --- a/wp-multi-network/includes/metaboxes/edit-network.php +++ b/wp-multi-network/includes/metaboxes/edit-network.php @@ -65,15 +65,28 @@ function wpmn_edit_network_details_metabox( $network = null ) { * Renders the metabox for defining the main site for a new network. * * @since 1.7.0 + * @since NEXT Added support for selecting an existing site as root site. + * * @return void */ function wpmn_edit_network_new_site_metabox() { + + // Get all sites that are not main sites of any network. + $all_sites = get_sites( array( 'number' => 0 ) ); + $eligible_sites = array(); + + foreach ( $all_sites as $site ) { + if ( ! is_main_site_for_network( $site->id ) ) { + $eligible_sites[] = $site; + } + } + ?> - + @@ -83,6 +96,27 @@ function wpmn_edit_network_new_site_metabox() { + + + + +
+ + + + +

+ +

+ +
@@ -232,7 +266,7 @@ function wpmn_edit_network_publish_metabox( $network = null ) { ?>
- +