From b9adbb33da4f4d42a67180f69baea88ecaa8268a Mon Sep 17 00:00:00 2001 From: Joe Dolson Date: Thu, 21 Sep 2023 18:22:10 +0000 Subject: [PATCH] Login and Registration: Improve HTML for errors and notices. Improve markup on Login and Registration errors. Use list markup for multiple issues, paragraph when only one to reduce semantic burden in the most common case. Normalize classes and markup for wrapper using `wp_admin_notice()` and `wp_get_admin_notice()` functions. Move definition of those functions from `wp-admin\includes\misc.php` to `wp-includes\functions.php`. Move tests to functions group. Props extendwings, sabernhardt, afercia, lukecavanagh, rianrietveld, oglekler, sergeybiryukov, costdev, joedolson. Fixes #30685. git-svn-id: https://develop.svn.wordpress.org/trunk@56654 602fd350-edb4-49c9-b593-d223f7449a82 --- src/wp-admin/css/login.css | 26 +++- src/wp-admin/includes/misc.php | 144 ------------------ src/wp-includes/functions.php | 144 ++++++++++++++++++ src/wp-login.php | 95 ++++++++++-- .../{admin => functions}/wpAdminNotice.php | 4 +- .../{admin => functions}/wpGetAdminNotice.php | 4 +- 6 files changed, 249 insertions(+), 168 deletions(-) rename tests/phpunit/tests/{admin => functions}/wpAdminNotice.php (96%) rename tests/phpunit/tests/{admin => functions}/wpGetAdminNotice.php (96%) diff --git a/src/wp-admin/css/login.css b/src/wp-admin/css/login.css index 8f43504c1c..fe790f0b42 100644 --- a/src/wp-admin/css/login.css +++ b/src/wp-admin/css/login.css @@ -42,8 +42,8 @@ p { } .login .message, -.login .success, -.login #login_error { +.login .notice, +.login .success { border-left: 4px solid #72aee6; padding: 12px; margin-left: 0; @@ -57,10 +57,19 @@ p { border-left-color: #00a32a; } -.login #login_error { +/* Match border color from common.css */ +.login .notice-error { border-left-color: #d63638; } +.login .login-error-list { + list-style: none; +} + +.login .login-error-list li + li { + margin-top: 4px; +} + #loginform p.submit, .login-action-lostpassword p.submit { border: none; @@ -237,6 +246,11 @@ p { margin-bottom: 0; } +#login form .indicator-hint, +#login #reg_passmail { + margin-bottom: 16px; +} + #login form p.submit { margin: 0; padding: 0; @@ -342,9 +356,7 @@ p { font-family: Consolas, Monaco, monospace; } -.js.login input.password-input, -.js.login-action-rp form .input, -.js.login-action-rp input[type="text"] { +.js.login input.password-input { padding-right: 2.5rem; } @@ -354,6 +366,8 @@ p { background: #fff; } +.js.login-action-resetpass input[type="text"], +.js.login-action-resetpass input[type="password"], .js.login-action-rp input[type="text"], .js.login-action-rp input[type="password"] { margin-bottom: 0; diff --git a/src/wp-admin/includes/misc.php b/src/wp-admin/includes/misc.php index 8c4caa468d..090282062f 100644 --- a/src/wp-admin/includes/misc.php +++ b/src/wp-admin/includes/misc.php @@ -1642,147 +1642,3 @@ function wp_check_php_version() { return $response; } - -/** - * Creates and returns the markup for an admin notice. - * - * @since 6.4.0 - * - * @param string $message The message. - * @param array $args { - * Optional. An array of arguments for the admin notice. Default empty array. - * - * @type string $type Optional. The type of admin notice. - * For example, 'error', 'success', 'warning', 'info'. - * Default empty string. - * @type bool $dismissible Optional. Whether the admin notice is dismissible. Default false. - * @type string $id Optional. The value of the admin notice's ID attribute. Default empty string. - * @type string[] $additional_classes Optional. A string array of class names. Default empty array. - * @type string[] $attributes Optional. Additional attributes for the notice div. Default empty array. - * @type bool $paragraph_wrap Optional. Whether to wrap the message in paragraph tags. Default true. - * } - * @return string The markup for an admin notice. - */ -function wp_get_admin_notice( $message, $args = array() ) { - $defaults = array( - 'type' => '', - 'dismissible' => false, - 'id' => '', - 'additional_classes' => array(), - 'attributes' => array(), - 'paragraph_wrap' => true, - ); - - $args = wp_parse_args( $args, $defaults ); - - /** - * Filters the arguments for an admin notice. - * - * @since 6.4.0 - * - * @param array $args The arguments for the admin notice. - * @param string $message The message for the admin notice. - */ - $args = apply_filters( 'wp_admin_notice_args', $args, $message ); - $id = ''; - $classes = 'notice'; - $attributes = ''; - - if ( is_string( $args['id'] ) ) { - $trimmed_id = trim( $args['id'] ); - - if ( '' !== $trimmed_id ) { - $id = 'id="' . $trimmed_id . '" '; - } - } - - if ( is_string( $args['type'] ) ) { - $type = trim( $args['type'] ); - - if ( str_contains( $type, ' ' ) ) { - _doing_it_wrong( - __FUNCTION__, - sprintf( - /* translators: %s: The "type" key. */ - __( 'The %s key must be a string without spaces.' ), - 'type' - ), - '6.4.0' - ); - } - - if ( '' !== $type ) { - $classes .= ' notice-' . $type; - } - } - - if ( true === $args['dismissible'] ) { - $classes .= ' is-dismissible'; - } - - if ( is_array( $args['additional_classes'] ) && ! empty( $args['additional_classes'] ) ) { - $classes .= ' ' . implode( ' ', $args['additional_classes'] ); - } - - if ( is_array( $args['attributes'] ) && ! empty( $args['attributes'] ) ) { - $attributes = ''; - foreach ( $args['attributes'] as $attr => $val ) { - if ( is_bool( $val ) ) { - $attributes .= $val ? ' ' . $attr : ''; - } elseif ( is_int( $attr ) ) { - $attributes .= ' ' . esc_attr( trim( $val ) ); - } elseif ( $val ) { - $attributes .= ' ' . $attr . '="' . esc_attr( trim( $val ) ) . '"'; - } - } - } - - if ( false !== $args['paragraph_wrap'] ) { - $message = "

$message

"; - } - - $markup = sprintf( '
%4$s
', $id, $classes, $attributes, $message ); - - /** - * Filters the markup for an admin notice. - * - * @since 6.4.0 - * - * @param string $markup The HTML markup for the admin notice. - * @param string $message The message for the admin notice. - * @param array $args The arguments for the admin notice. - */ - return apply_filters( 'wp_admin_notice_markup', $markup, $message, $args ); -} - -/** - * Outputs an admin notice. - * - * @since 6.4.0 - * - * @param string $message The message to output. - * @param array $args { - * Optional. An array of arguments for the admin notice. Default empty array. - * - * @type string $type Optional. The type of admin notice. - * For example, 'error', 'success', 'warning', 'info'. - * Default empty string. - * @type bool $dismissible Optional. Whether the admin notice is dismissible. Default false. - * @type string $id Optional. The value of the admin notice's ID attribute. Default empty string. - * @type string[] $additional_classes Optional. A string array of class names. Default empty array. - * @type bool $paragraph_wrap Optional. Whether to wrap the message in paragraph tags. Default true. - * } - */ -function wp_admin_notice( $message, $args = array() ) { - /** - * Fires before an admin notice is output. - * - * @since 6.4.0 - * - * @param string $message The message for the admin notice. - * @param array $args The arguments for the admin notice. - */ - do_action( 'wp_admin_notice', $message, $args ); - - echo wp_kses_post( wp_get_admin_notice( $message, $args ) ); -} diff --git a/src/wp-includes/functions.php b/src/wp-includes/functions.php index f3c4af1405..d2c02abedd 100644 --- a/src/wp-includes/functions.php +++ b/src/wp-includes/functions.php @@ -8762,3 +8762,147 @@ function is_php_version_compatible( $required ) { function wp_fuzzy_number_match( $expected, $actual, $precision = 1 ) { return abs( (float) $expected - (float) $actual ) <= $precision; } + +/** + * Creates and returns the markup for an admin notice. + * + * @since 6.4.0 + * + * @param string $message The message. + * @param array $args { + * Optional. An array of arguments for the admin notice. Default empty array. + * + * @type string $type Optional. The type of admin notice. + * For example, 'error', 'success', 'warning', 'info'. + * Default empty string. + * @type bool $dismissible Optional. Whether the admin notice is dismissible. Default false. + * @type string $id Optional. The value of the admin notice's ID attribute. Default empty string. + * @type string[] $additional_classes Optional. A string array of class names. Default empty array. + * @type string[] $attributes Optional. Additional attributes for the notice div. Default empty array. + * @type bool $paragraph_wrap Optional. Whether to wrap the message in paragraph tags. Default true. + * } + * @return string The markup for an admin notice. + */ +function wp_get_admin_notice( $message, $args = array() ) { + $defaults = array( + 'type' => '', + 'dismissible' => false, + 'id' => '', + 'additional_classes' => array(), + 'attributes' => array(), + 'paragraph_wrap' => true, + ); + + $args = wp_parse_args( $args, $defaults ); + + /** + * Filters the arguments for an admin notice. + * + * @since 6.4.0 + * + * @param array $args The arguments for the admin notice. + * @param string $message The message for the admin notice. + */ + $args = apply_filters( 'wp_admin_notice_args', $args, $message ); + $id = ''; + $classes = 'notice'; + $attributes = ''; + + if ( is_string( $args['id'] ) ) { + $trimmed_id = trim( $args['id'] ); + + if ( '' !== $trimmed_id ) { + $id = 'id="' . $trimmed_id . '" '; + } + } + + if ( is_string( $args['type'] ) ) { + $type = trim( $args['type'] ); + + if ( str_contains( $type, ' ' ) ) { + _doing_it_wrong( + __FUNCTION__, + sprintf( + /* translators: %s: The "type" key. */ + __( 'The %s key must be a string without spaces.' ), + 'type' + ), + '6.4.0' + ); + } + + if ( '' !== $type ) { + $classes .= ' notice-' . $type; + } + } + + if ( true === $args['dismissible'] ) { + $classes .= ' is-dismissible'; + } + + if ( is_array( $args['additional_classes'] ) && ! empty( $args['additional_classes'] ) ) { + $classes .= ' ' . implode( ' ', $args['additional_classes'] ); + } + + if ( is_array( $args['attributes'] ) && ! empty( $args['attributes'] ) ) { + $attributes = ''; + foreach ( $args['attributes'] as $attr => $val ) { + if ( is_bool( $val ) ) { + $attributes .= $val ? ' ' . $attr : ''; + } elseif ( is_int( $attr ) ) { + $attributes .= ' ' . esc_attr( trim( $val ) ); + } elseif ( $val ) { + $attributes .= ' ' . $attr . '="' . esc_attr( trim( $val ) ) . '"'; + } + } + } + + if ( false !== $args['paragraph_wrap'] ) { + $message = "

$message

"; + } + + $markup = sprintf( '
%4$s
', $id, $classes, $attributes, $message ); + + /** + * Filters the markup for an admin notice. + * + * @since 6.4.0 + * + * @param string $markup The HTML markup for the admin notice. + * @param string $message The message for the admin notice. + * @param array $args The arguments for the admin notice. + */ + return apply_filters( 'wp_admin_notice_markup', $markup, $message, $args ); +} + +/** + * Outputs an admin notice. + * + * @since 6.4.0 + * + * @param string $message The message to output. + * @param array $args { + * Optional. An array of arguments for the admin notice. Default empty array. + * + * @type string $type Optional. The type of admin notice. + * For example, 'error', 'success', 'warning', 'info'. + * Default empty string. + * @type bool $dismissible Optional. Whether the admin notice is dismissible. Default false. + * @type string $id Optional. The value of the admin notice's ID attribute. Default empty string. + * @type string[] $additional_classes Optional. A string array of class names. Default empty array. + * @type bool $paragraph_wrap Optional. Whether to wrap the message in paragraph tags. Default true. + * } + */ +function wp_admin_notice( $message, $args = array() ) { + /** + * Fires before an admin notice is output. + * + * @since 6.4.0 + * + * @param string $message The message for the admin notice. + * @param array $args The arguments for the admin notice. + */ + do_action( 'wp_admin_notice', $message, $args ); + + echo wp_kses_post( wp_get_admin_notice( $message, $args ) ); +} diff --git a/src/wp-login.php b/src/wp-login.php index de2ee486c7..60877bdb70 100644 --- a/src/wp-login.php +++ b/src/wp-login.php @@ -228,21 +228,35 @@ function login_header( $title = 'Log In', $message = '', $wp_error = null ) { } if ( $wp_error->has_errors() ) { - $errors = ''; - $messages = ''; + $error_list = array(); + $messages = ''; foreach ( $wp_error->get_error_codes() as $code ) { $severity = $wp_error->get_error_data( $code ); foreach ( $wp_error->get_error_messages( $code ) as $error_message ) { if ( 'message' === $severity ) { - $messages .= ' ' . $error_message . "
\n"; + $messages .= '

' . $error_message . '

'; } else { - $errors .= ' ' . $error_message . "
\n"; + $error_list[] = $error_message; } } } - if ( ! empty( $errors ) ) { + if ( ! empty( $error_list ) ) { + $errors = ''; + + if ( count( $error_list ) > 1 ) { + $errors .= ''; + } else { + $errors .= '

' . $error_message . '

'; + } + /** * Filters the error messages displayed above the login form. * @@ -250,7 +264,15 @@ function login_header( $title = 'Log In', $message = '', $wp_error = null ) { * * @param string $errors Login error message. */ - echo '
' . apply_filters( 'login_errors', $errors ) . "
\n"; + $errors = apply_filters( 'login_errors', $errors ); + wp_admin_notice( + $errors, + array( + 'type' => 'error', + 'id' => 'login_error', + 'paragraph_wrap' => false, + ) + ); } if ( ! empty( $messages ) ) { @@ -261,7 +283,16 @@ function login_header( $title = 'Log In', $message = '', $wp_error = null ) { * * @param string $messages Login messages. */ - echo '

' . apply_filters( 'login_messages', $messages ) . "

\n"; + $messages = apply_filters( 'login_messages', $messages ); + wp_admin_notice( + $messages, + array( + 'type' => 'info', + 'id' => 'login-message', + 'additional_classes' => array( 'message' ), + 'paragraph_wrap' => false, + ) + ); } } } // End of login_header(). @@ -399,7 +430,6 @@ function login_footer( $input_id = '' ) { do_action( 'login_footer' ); ?> -
' . __( 'Please enter your username or email address. You will receive an email message with instructions on how to reset your password.' ) . '

', $errors ); + login_header( + __( 'Lost Password' ), + wp_get_admin_notice( + __( 'Please enter your username or email address. You will receive an email message with instructions on how to reset your password.' ), + array( + 'type' => 'info', + 'additional_classes' => array( 'message' ), + ) + ), + $errors + ); $user_login = ''; @@ -946,7 +986,16 @@ switch ( $action ) { if ( ( ! $errors->has_errors() ) && isset( $_POST['pass1'] ) && ! empty( $_POST['pass1'] ) ) { reset_password( $user, $_POST['pass1'] ); setcookie( $rp_cookie, ' ', time() - YEAR_IN_SECONDS, $rp_path, COOKIE_DOMAIN, is_ssl(), true ); - login_header( __( 'Password Reset' ), '

' . __( 'Your password has been reset.' ) . ' ' . __( 'Log in' ) . '

' ); + login_header( + __( 'Password Reset' ), + wp_get_admin_notice( + __( 'Your password has been reset.' ) . ' ' . __( 'Log in' ) . '', + array( + 'type' => 'info', + 'additional_classes' => array( 'message', 'reset-pass' ), + ) + ) + ); login_footer(); exit; } @@ -954,7 +1003,17 @@ switch ( $action ) { wp_enqueue_script( 'utils' ); wp_enqueue_script( 'user-profile' ); - login_header( __( 'Reset Password' ), '

' . __( 'Enter your new password below or generate one.' ) . '

', $errors ); + login_header( + __( 'Reset Password' ), + wp_get_admin_notice( + __( 'Enter your new password below or generate one.' ), + array( + 'type' => 'info', + 'additional_classes' => array( 'message', 'reset-pass' ), + ) + ), + $errors + ); ?>
@@ -985,7 +1044,6 @@ switch ( $action ) {

-
' . __( 'Register For This Site' ) . '

', $errors ); + login_header( + __( 'Registration Form' ), + wp_get_admin_notice( + __( 'Register For This Site' ), + array( + 'type' => 'info', + 'additional_classes' => array( 'message', 'register' ), + ) + ), + $errors + ); ?> @@ -1104,7 +1172,6 @@ switch ( $action ) {

-

diff --git a/tests/phpunit/tests/admin/wpAdminNotice.php b/tests/phpunit/tests/functions/wpAdminNotice.php similarity index 96% rename from tests/phpunit/tests/admin/wpAdminNotice.php rename to tests/phpunit/tests/functions/wpAdminNotice.php index d882c93457..2756ab1de9 100644 --- a/tests/phpunit/tests/admin/wpAdminNotice.php +++ b/tests/phpunit/tests/functions/wpAdminNotice.php @@ -3,11 +3,11 @@ /** * Tests for `wp_admin_notice()`. * - * @group admin + * @group functions.php * * @covers ::wp_admin_notice */ -class Tests_Admin_WpAdminNotice extends WP_UnitTestCase { +class Tests_Functions_WpAdminNotice extends WP_UnitTestCase { /** * Tests that `wp_admin_notice()` outputs the expected admin notice markup. diff --git a/tests/phpunit/tests/admin/wpGetAdminNotice.php b/tests/phpunit/tests/functions/wpGetAdminNotice.php similarity index 96% rename from tests/phpunit/tests/admin/wpGetAdminNotice.php rename to tests/phpunit/tests/functions/wpGetAdminNotice.php index 718fdf3d94..aeeadff96d 100644 --- a/tests/phpunit/tests/admin/wpGetAdminNotice.php +++ b/tests/phpunit/tests/functions/wpGetAdminNotice.php @@ -3,11 +3,11 @@ /** * Tests for `wp_get_admin_notice()`. * - * @group admin + * @group functions.php * * @covers ::wp_get_admin_notice */ -class Tests_Admin_WpGetAdminNotice extends WP_UnitTestCase { +class Tests_Functions_WpGetAdminNotice extends WP_UnitTestCase { /** * Tests that `wp_get_admin_notice()` returns the expected admin notice markup.