Skip to content

Commit 9ca95a1

Browse files
committed
Merge remote-tracking branch 'fork-1/feature/40365-rest-sites-endpoint-check-exists' into feature/40365-rest-sites-endpoint-check-exists
# Conflicts: # src/wp-includes/rest-api/endpoints/class-wp-rest-sites-controller.php
2 parents e0801aa + 8fe0b65 commit 9ca95a1

2 files changed

Lines changed: 175 additions & 0 deletions

File tree

‎src/wp-includes/rest-api/endpoints/class-wp-rest-sites-controller.php‎

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -970,6 +970,11 @@ protected function prepare_item_for_database( $request ) {
970970
$prepared_site['domain'] = $request['domain'];
971971
}
972972

973+
$domain_check = $this->check_domain_is_available( $prepared_site, $request );
974+
if ( is_wp_error( $domain_check ) ) {
975+
return $domain_check;
976+
}
977+
973978
/**
974979
* Filters a site after it is prepared for the database.
975980
*
@@ -1139,6 +1144,53 @@ private function is_valid_path( $path ) {
11391144
);
11401145
}
11411146

1147+
/**
1148+
* Checks that the domain and path for a prepared site are not already in use.
1149+
*
1150+
* When creating a site, the domain and path must not belong to any existing
1151+
* site. When updating a site, they may only belong to the site being updated.
1152+
*
1153+
* @since 7.2.0
1154+
*
1155+
* @param array $prepared_site The prepared site data for `wp_insert_site()`.
1156+
* @param WP_REST_Request $request The current request.
1157+
* @return true|WP_Error True if the domain and path are available, WP_Error otherwise.
1158+
*/
1159+
protected function check_domain_is_available( $prepared_site, $request ) {
1160+
$id = (int) $request['id'];
1161+
$domain = isset( $prepared_site['domain'] ) ? $prepared_site['domain'] : '';
1162+
$path = isset( $prepared_site['path'] ) ? $prepared_site['path'] : '/';
1163+
$network_id = isset( $prepared_site['network_id'] ) ? $prepared_site['network_id'] : get_current_network_id();
1164+
1165+
if ( ! empty( $id ) ) {
1166+
// Updating a site: fall back to the current values for anything the request left out.
1167+
$current_site = $this->get_site( $id );
1168+
1169+
if ( is_wp_error( $current_site ) ) {
1170+
return $current_site;
1171+
}
1172+
1173+
if ( ! isset( $prepared_site['domain'] ) ) {
1174+
$domain = $current_site->domain;
1175+
}
1176+
if ( ! isset( $prepared_site['path'] ) ) {
1177+
$path = $current_site->path;
1178+
}
1179+
if ( ! isset( $prepared_site['network_id'] ) ) {
1180+
$network_id = (int) $current_site->network_id;
1181+
}
1182+
}
1183+
1184+
$existing_site_id = domain_exists( $domain, $path, $network_id );
1185+
1186+
// The domain and path may only belong to the site being updated.
1187+
if ( $existing_site_id && (int) $existing_site_id !== $id ) {
1188+
return new WP_Error( 'rest_site_taken', __( 'Sorry, that site already exists!' ), array( 'status' => 400 ) );
1189+
}
1190+
1191+
return true;
1192+
}
1193+
11421194
/**
11431195
* Retrieves the site's schema, conforming to JSON Schema.
11441196
*
@@ -1166,6 +1218,7 @@ public function get_item_schema() {
11661218
'description' => __( 'The site\'s network ID. Default is the current network ID.' ),
11671219
'type' => 'integer',
11681220
'context' => array( 'view', 'edit', 'embed' ),
1221+
'default' => get_current_network_id(),
11691222
),
11701223
'domain' => array(
11711224
'description' => __( 'Site domain.' ),

‎tests/phpunit/tests/rest-api/wpRestSitesController.php‎

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,128 @@ public function test_create_item() {
183183
$this->assertEquals( '/tempor/', $site->path );
184184
}
185185

186+
/**
187+
* @ticket 40365
188+
* @covers ::create_item
189+
* @group ms-required
190+
*/
191+
public function test_create_item_rejects_an_existing_domain_and_path() {
192+
wp_set_current_user( self::$superadmin_id );
193+
194+
self::factory()->blog->create(
195+
array(
196+
'domain' => WP_TESTS_DOMAIN,
197+
'path' => '/existing/',
198+
)
199+
);
200+
201+
$request = new WP_REST_Request( 'POST', '/wp/v2/sites' );
202+
$request->set_param( 'domain', WP_TESTS_DOMAIN );
203+
$request->set_param( 'path', '/existing/' );
204+
205+
$response = rest_get_server()->dispatch( $request );
206+
207+
$this->assertErrorResponse( 'rest_site_taken', $response, 400 );
208+
}
209+
210+
/**
211+
* @ticket 40365
212+
* @covers ::create_item
213+
* @group ms-required
214+
*/
215+
public function test_create_item_allows_an_existing_domain_and_path_on_a_different_network() {
216+
wp_set_current_user( self::$superadmin_id );
217+
218+
self::factory()->blog->create(
219+
array(
220+
'domain' => WP_TESTS_DOMAIN,
221+
'path' => '/shared/',
222+
)
223+
);
224+
225+
$network_id = self::factory()->network->create(
226+
array(
227+
'domain' => 'other-network.example.org',
228+
'path' => '/',
229+
)
230+
);
231+
232+
$request = new WP_REST_Request( 'POST', '/wp/v2/sites' );
233+
$request->set_param( 'domain', WP_TESTS_DOMAIN );
234+
$request->set_param( 'path', '/shared/' );
235+
$request->set_param( 'network', $network_id );
236+
237+
$response = rest_get_server()->dispatch( $request );
238+
239+
$this->assertEquals( 201, $response->get_status() );
240+
241+
$data = $response->get_data();
242+
243+
$this->assertEquals( WP_TESTS_DOMAIN, $data['domain'] );
244+
$this->assertEquals( '/shared/', $data['path'] );
245+
$this->assertEquals( $network_id, $data['network'] );
246+
}
247+
248+
/**
249+
* @ticket 40365
250+
* @covers ::update_item
251+
* @group ms-required
252+
*/
253+
public function test_update_item_rejects_another_sites_domain_and_path() {
254+
wp_set_current_user( self::$superadmin_id );
255+
256+
self::factory()->blog->create(
257+
array(
258+
'domain' => WP_TESTS_DOMAIN,
259+
'path' => '/taken/',
260+
)
261+
);
262+
$blog_id = self::factory()->blog->create(
263+
array(
264+
'domain' => WP_TESTS_DOMAIN,
265+
'path' => '/free/',
266+
)
267+
);
268+
269+
$request = new WP_REST_Request( 'PUT', '/wp/v2/sites/' . $blog_id );
270+
$request->set_param( 'domain', WP_TESTS_DOMAIN );
271+
$request->set_param( 'path', '/taken/' );
272+
273+
$response = rest_get_server()->dispatch( $request );
274+
275+
$this->assertErrorResponse( 'rest_site_taken', $response, 400 );
276+
}
277+
278+
/**
279+
* @ticket 40365
280+
* @covers ::update_item
281+
* @group ms-required
282+
*/
283+
public function test_update_item_allows_a_site_to_keep_its_own_domain_and_path() {
284+
wp_set_current_user( self::$superadmin_id );
285+
286+
$blog_id = self::factory()->blog->create(
287+
array(
288+
'domain' => WP_TESTS_DOMAIN,
289+
'path' => '/keep/',
290+
)
291+
);
292+
293+
$request = new WP_REST_Request( 'PUT', '/wp/v2/sites/' . $blog_id );
294+
$request->set_param( 'domain', WP_TESTS_DOMAIN );
295+
$request->set_param( 'path', '/keep/' );
296+
$request->set_param( 'mature', 1 );
297+
298+
$response = rest_get_server()->dispatch( $request );
299+
300+
$this->assertEquals( 200, $response->get_status() );
301+
302+
$data = $response->get_data();
303+
304+
$this->assertEquals( '/keep/', $data['path'] );
305+
$this->assertEquals( 1, $data['mature'] );
306+
}
307+
186308
/**
187309
* @ticket 40365
188310
* @covers ::update_item

0 commit comments

Comments
 (0)