diff --git a/src/wp-admin/network/settings.php b/src/wp-admin/network/settings.php index 1e13084a858c1..a0d933a90ed77 100644 --- a/src/wp-admin/network/settings.php +++ b/src/wp-admin/network/settings.php @@ -116,6 +116,17 @@ } } + // Ensure the network title (site_name) is not left empty. + $network_title_error = ''; + if ( isset( $_POST['site_name'] ) ) { + $site_name = wp_unslash( $_POST['site_name'] ); + + if ( ! is_scalar( $site_name ) || '' === trim( (string) $site_name ) ) { + unset( $_POST['site_name'] ); + $network_title_error = __( 'The network title cannot be empty. Please enter a title for your network.' ); + } + } + foreach ( $options as $option_name ) { if ( ! isset( $_POST[ $option_name ] ) ) { continue; @@ -131,21 +142,42 @@ */ do_action( 'update_wpmu_options' ); - wp_redirect( add_query_arg( 'updated', 'true', network_admin_url( 'settings.php' ) ) ); + if ( $network_title_error ) { + set_transient( 'network_settings_errors', array( $network_title_error ), 30 ); + wp_redirect( add_query_arg( 'updated', 'error', network_admin_url( 'settings.php' ) ) ); + } else { + wp_redirect( add_query_arg( 'updated', 'true', network_admin_url( 'settings.php' ) ) ); + } exit; } require_once ABSPATH . 'wp-admin/admin-header.php'; if ( isset( $_GET['updated'] ) ) { - wp_admin_notice( - __( 'Settings saved.' ), - array( - 'type' => 'success', - 'dismissible' => true, - 'id' => 'message', - ) - ); + if ( 'true' === $_GET['updated'] ) { + wp_admin_notice( + __( 'Settings saved.' ), + array( + 'type' => 'success', + 'dismissible' => true, + 'id' => 'message', + ) + ); + } elseif ( 'error' === $_GET['updated'] ) { + $network_settings_errors = get_transient( 'network_settings_errors' ); + delete_transient( 'network_settings_errors' ); + + $error_messages = is_array( $network_settings_errors ) ? $network_settings_errors : array( __( 'Some settings could not be saved.' ) ); + foreach ( $error_messages as $error_message ) { + wp_admin_notice( + $error_message, + array( + 'type' => 'error', + 'dismissible' => true, + ) + ); + } + } } ?> @@ -155,10 +187,10 @@

- + diff --git a/src/wp-admin/network/site-settings.php b/src/wp-admin/network/site-settings.php index 8b1248f6b13b2..68bff1d222be0 100644 --- a/src/wp-admin/network/site-settings.php +++ b/src/wp-admin/network/site-settings.php @@ -60,10 +60,25 @@ do_action( 'wpmu_update_blog_options', $id ); restore_current_blog(); + + $update = 'updated'; + + /* + * sanitize_option() rejects invalid values (such as an empty, required + * Site Title) by registering a settings error and keeping the stored value. + * Carry those errors across the redirect so an error notice can be shown + * instead of a success message. + */ + $settings_errors = get_settings_errors(); + if ( ! empty( $settings_errors ) ) { + set_transient( 'settings_errors', $settings_errors, 30 ); + $update = 'not-updated'; + } + wp_redirect( add_query_arg( array( - 'update' => 'updated', + 'update' => $update, 'id' => $id, ), 'site-settings.php' @@ -72,10 +87,25 @@ exit; } +$messages = array(); +$error_messages = array(); + if ( isset( $_GET['update'] ) ) { - $messages = array(); if ( 'updated' === $_GET['update'] ) { $messages[] = __( 'Site options updated.' ); + } elseif ( 'not-updated' === $_GET['update'] ) { + $settings_errors = get_transient( 'settings_errors' ); + delete_transient( 'settings_errors' ); + + if ( is_array( $settings_errors ) ) { + foreach ( $settings_errors as $settings_error ) { + $error_messages[] = $settings_error['message']; + } + } + + if ( empty( $error_messages ) ) { + $error_messages[] = __( 'Some settings could not be saved.' ); + } } } @@ -103,16 +133,27 @@ ) ); +if ( ! empty( $error_messages ) ) { + foreach ( $error_messages as $msg ) { + wp_admin_notice( + $msg, + array( + 'type' => 'error', + 'dismissible' => true, + ) + ); + } +} + if ( ! empty( $messages ) ) { - $notice_args = array( - 'type' => 'success', - 'dismissible' => true, - 'id' => 'message', + wp_admin_notice( + $messages[0], + array( + 'type' => 'success', + 'dismissible' => true, + 'id' => 'message', + ) ); - - foreach ( $messages as $msg ) { - wp_admin_notice( $msg, $notice_args ); - } } ?> diff --git a/src/wp-admin/options-general.php b/src/wp-admin/options-general.php index 6a9a6d2f3812d..a7758c7320866 100644 --- a/src/wp-admin/options-general.php +++ b/src/wp-admin/options-general.php @@ -72,9 +72,9 @@ - + - +get_error_message(); } else { $value = esc_html( $value ); + + // The site title (blogname) cannot be empty. + if ( 'blogname' === $option && '' === trim( $value ) ) { + $error = __( 'The site title cannot be empty. Please enter a title for your site.' ); + } } break; diff --git a/src/wp-includes/option.php b/src/wp-includes/option.php index 8bd6a1821162e..35fb609998e00 100644 --- a/src/wp-includes/option.php +++ b/src/wp-includes/option.php @@ -2746,7 +2746,11 @@ function register_initial_settings() { 'blogname', array( 'show_in_rest' => array( - 'name' => 'title', + 'name' => 'title', + 'schema' => array( + 'minLength' => 1, + 'pattern' => '\S', + ), ), 'type' => 'string', 'label' => __( 'Title' ), diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-settings-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-settings-controller.php index 142836c7c8921..69e30b3352fc4 100644 --- a/src/wp-includes/rest-api/endpoints/class-wp-rest-settings-controller.php +++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-settings-controller.php @@ -198,6 +198,14 @@ public function update_item( $request ) { ); } + if ( 'blogname' === $args['option_name'] ) { + return new WP_Error( + 'rest_invalid_param', + __( 'The site title cannot be empty. Please enter a title for your site.' ), + array( 'status' => 400 ) + ); + } + delete_option( $args['option_name'] ); } else { update_option( $args['option_name'], $request[ $name ] ); diff --git a/tests/phpunit/tests/rest-api/rest-settings-controller.php b/tests/phpunit/tests/rest-api/rest-settings-controller.php index e8f90b53f20f1..6f8d04d5242b2 100644 --- a/tests/phpunit/tests/rest-api/rest-settings-controller.php +++ b/tests/phpunit/tests/rest-api/rest-settings-controller.php @@ -598,6 +598,36 @@ public function test_update_item_with_invalid_type() { $this->assertErrorResponse( 'rest_invalid_param', $response, 400 ); } + /** + * @ticket 65718 + * + * @dataProvider data_update_item_with_invalid_title + */ + public function test_update_item_with_invalid_title( $invalid_title ) { + wp_set_current_user( self::$administrator ); + $original_title = get_option( 'blogname' ); + + $request = new WP_REST_Request( 'PUT', '/wp/v2/settings' ); + $request->set_param( 'title', $invalid_title ); + $response = rest_get_server()->dispatch( $request ); + + $this->assertErrorResponse( 'rest_invalid_param', $response, 400 ); + $this->assertSame( $original_title, get_option( 'blogname' ) ); + } + + /** + * Data provider. + * + * @return array[] + */ + public function data_update_item_with_invalid_title() { + return array( + 'empty string' => array( '' ), + 'whitespace only' => array( ' ' ), + 'null' => array( null ), + ); + } + public function test_update_item_with_integer() { wp_set_current_user( self::$administrator ); $request = new WP_REST_Request( 'PUT', '/wp/v2/settings' ); diff --git a/tests/qunit/fixtures/wp-api-generated.js b/tests/qunit/fixtures/wp-api-generated.js index 1b0eaac6cccd1..4b896df076368 100644 --- a/tests/qunit/fixtures/wp-api-generated.js +++ b/tests/qunit/fixtures/wp-api-generated.js @@ -11134,6 +11134,8 @@ mockedApiResponse.Schema = { "title": "Title", "description": "Site title.", "type": "string", + "minLength": 1, + "pattern": "\\S", "required": false }, "description": {