Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions src/js/_enqueues/wp/updates.js
Original file line number Diff line number Diff line change
Expand Up @@ -387,6 +387,23 @@
$dashboardNavMenuUpdateCount.remove();
}

/*
* Keep the menu item's hidden count description in sync. The visible
* count bubble is aria-hidden, so this description is what assistive
* technologies announce via aria-describedby.
*/
if ( settings.totals.counts.total > 0 ) {
$( '#wp-menu-updates-count-description' ).text(
sprintf(
/* translators: %s: Number of updates available. */
_n( '%s update available', '%s updates available', settings.totals.counts.total ),
settings.totals.counts.total
)
);
} else {
$( '#wp-menu-updates-count-description' ).text( '' );
}

// Update the "Plugins" menu item.
$pluginsNavMenuUpdateCount.each( function( index, element ) {
element.className = element.className.replace( /count-\d+/, 'count-' + settings.totals.counts.plugins );
Expand All @@ -397,6 +414,19 @@
$pluginsNavMenuUpdateCount.remove();
}

// Keep the menu item's hidden count description in sync. See above.
if ( settings.totals.counts.plugins > 0 ) {
$( '#wp-menu-plugins-count-description' ).text(
sprintf(
/* translators: %s: Number of available plugin updates. */
_n( '%s plugin update available', '%s plugin updates available', settings.totals.counts.plugins ),
settings.totals.counts.plugins
)
);
} else {
$( '#wp-menu-plugins-count-description' ).text( '' );
}

// Update the "Appearance" menu item.
$appearanceNavMenuUpdateCount.each( function( index, element ) {
element.className = element.className.replace( /count-\d+/, 'count-' + settings.totals.counts.themes );
Expand All @@ -407,6 +437,19 @@
$appearanceNavMenuUpdateCount.remove();
}

// Keep the menu item's hidden count description in sync. See above.
if ( settings.totals.counts.themes > 0 ) {
$( '#wp-menu-themes-count-description' ).text(
sprintf(
/* translators: %s: Number of available theme updates. */
_n( '%s theme update available', '%s theme updates available', settings.totals.counts.themes ),
settings.totals.counts.themes
)
);
} else {
$( '#wp-menu-themes-count-description' ).text( '' );
}

// Update list table filter navigation.
if ( 'plugins' === pagenow || 'plugins-network' === pagenow ) {
itemCount = settings.totals.counts.plugins;
Expand Down
45 changes: 39 additions & 6 deletions src/wp-admin/menu-header.php
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ function _wp_menu_output( $menu, $submenu, $submenu_as_parent = true ) {

$first = true;
// 0 = menu_title, 1 = capability, 2 = menu_slug, 3 = page_title, 4 = classes, 5 = hookname, 6 = icon_url.
// Optional 'count_description' = array( 'id' => string, 'html' => string ) for a counter's hidden description.
foreach ( $menu as $key => $item ) {
$admin_is_parent = false;
$class = array();
Expand Down Expand Up @@ -145,6 +146,23 @@ function _wp_menu_output( $menu, $submenu, $submenu_as_parent = true ) {

$title = wptexturize( $item[0] );

/*
* When a menu item carries a hidden count description (e.g. pending
* updates or comments awaiting moderation), associate it with the link
* via aria-describedby and render it inside the link. This keeps the
* count out of the link's accessible name so voice control users can
* operate it by its visible label, while the count is still announced by
* assistive technologies. The id and markup are supplied explicitly by
* the menu item's 'count_description' entry rather than parsed from the
* title, so the association does not depend on the title's HTML shape.
*/
$describedby = '';
$count_description = '';
if ( ! empty( $item['count_description']['id'] ) ) {
$describedby = ' aria-describedby="' . esc_attr( $item['count_description']['id'] ) . '"';
$count_description = $item['count_description']['html'];
}

// Hide separators from screen readers.
if ( $is_separator ) {
$aria_hidden = ' aria-hidden="true"';
Expand All @@ -170,9 +188,9 @@ function _wp_menu_output( $menu, $submenu, $submenu_as_parent = true ) {
&& ! file_exists( ABSPATH . "/wp-admin/$menu_file" ) )
) {
$admin_is_parent = true;
echo "<a href='admin.php?page={$submenu_items[0][2]}'$class $aria_attributes><div class='wp-menu-image$img_class'$img_style aria-hidden='true'>$img</div><div class='wp-menu-name'>$title</div></a>";
echo "<a href='admin.php?page={$submenu_items[0][2]}'$class $aria_attributes$describedby><div class='wp-menu-image$img_class'$img_style aria-hidden='true'>$img</div><div class='wp-menu-name'>$title$count_description</div></a>";
} else {
echo "\n\t<a href='{$submenu_items[0][2]}'$class $aria_attributes><div class='wp-menu-image$img_class'$img_style aria-hidden='true'>$img</div><div class='wp-menu-name'>$title</div></a>";
echo "\n\t<a href='{$submenu_items[0][2]}'$class $aria_attributes$describedby><div class='wp-menu-image$img_class'$img_style aria-hidden='true'>$img</div><div class='wp-menu-name'>$title$count_description</div></a>";
}
} elseif ( ! empty( $item[2] ) && current_user_can( $item[1] ) ) {
$menu_hook = get_plugin_page_hook( $item[2], 'admin.php' );
Expand All @@ -189,19 +207,26 @@ function _wp_menu_output( $menu, $submenu, $submenu_as_parent = true ) {
&& ! file_exists( ABSPATH . "/wp-admin/$menu_file" ) )
) {
$admin_is_parent = true;
echo "\n\t<a href='admin.php?page={$item[2]}'$class $aria_attributes><div class='wp-menu-image$img_class'$img_style aria-hidden='true'>$img</div><div class='wp-menu-name'>{$item[0]}</div></a>";
echo "\n\t<a href='admin.php?page={$item[2]}'$class $aria_attributes$describedby><div class='wp-menu-image$img_class'$img_style aria-hidden='true'>$img</div><div class='wp-menu-name'>{$item[0]}$count_description</div></a>";
} else {
echo "\n\t<a href='{$item[2]}'$class $aria_attributes><div class='wp-menu-image$img_class'$img_style aria-hidden='true'>$img</div><div class='wp-menu-name'>{$item[0]}</div></a>";
echo "\n\t<a href='{$item[2]}'$class $aria_attributes$describedby><div class='wp-menu-image$img_class'$img_style aria-hidden='true'>$img</div><div class='wp-menu-name'>{$item[0]}$count_description</div></a>";
}
}

if ( ! empty( $submenu_items ) ) {
echo "\n\t<ul class='wp-submenu wp-submenu-wrap'>";

/*
* The submenu head repeats the top-level title. The count description
* is no longer part of the title itself (it is rendered from the item's
* 'count_description' entry), so its id is not duplicated here.
*/
echo "<li class='wp-submenu-head' aria-hidden='true'>{$item[0]}</li>";

$first = true;

// 0 = menu_title, 1 = capability, 2 = menu_slug, 3 = page_title, 4 = classes.
// Optional 'count_description' = array( 'id' => string, 'html' => string ) for a counter's hidden description.
foreach ( $submenu_items as $sub_key => $sub_item ) {
if ( ! current_user_can( $sub_item[1] ) ) {
continue;
Expand Down Expand Up @@ -258,6 +283,14 @@ function _wp_menu_output( $menu, $submenu, $submenu_as_parent = true ) {

$title = wptexturize( $sub_item[0] );

// Associate a hidden count description with the submenu link. See above.
$sub_describedby = '';
$sub_count_description = '';
if ( ! empty( $sub_item['count_description']['id'] ) ) {
$sub_describedby = ' aria-describedby="' . esc_attr( $sub_item['count_description']['id'] ) . '"';
$sub_count_description = $sub_item['count_description']['html'];
}

if ( ! empty( $menu_hook )
|| ( ( 'index.php' !== $sub_item[2] )
&& file_exists( WP_PLUGIN_DIR . "/$sub_file" )
Expand All @@ -271,9 +304,9 @@ function _wp_menu_output( $menu, $submenu, $submenu_as_parent = true ) {
}

$sub_item_url = esc_url( $sub_item_url );
echo "<li$class><a href='$sub_item_url'$class$aria_attributes>$title</a></li>";
echo "<li$class><a href='$sub_item_url'$class$aria_attributes$sub_describedby>$title$sub_count_description</a></li>";
} else {
echo "<li$class><a href='{$sub_item[2]}'$class$aria_attributes>$title</a></li>";
echo "<li$class><a href='{$sub_item[2]}'$class$aria_attributes$sub_describedby>$title$sub_count_description</a></li>";
}
}
echo '</ul>';
Expand Down
102 changes: 89 additions & 13 deletions src/wp-admin/menu.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,20 +49,36 @@
$capability = 'update_languages';
}

$updates_count = sprintf(
'<span class="update-plugins count-%s" aria-hidden="true"><span class="update-count">%s</span></span>',
$update_data['counts']['total'],

Check warning on line 54 in src/wp-admin/menu.php

View workflow job for this annotation

GitHub Actions / PHP static analysis / Run PHP static analysis

Ignored error pattern #^Variable \$update_data might not be defined\.$# (variable.undefined) in path /home/runner/work/wordpress-develop/wordpress-develop/src/wp-admin/menu.php is expected to occur 2 times, but occurred 4 times.
number_format_i18n( $update_data['counts']['total'] )
);

$updates_text = sprintf(
/* translators: Hidden accessibility text. %s: Number of updates available. */
_n( '%s update available', '%s updates available', $update_data['counts']['total'] ),

Check warning on line 60 in src/wp-admin/menu.php

View workflow job for this annotation

GitHub Actions / PHP static analysis / Run PHP static analysis

Variable $update_data might not be defined.
number_format_i18n( $update_data['counts']['total'] )

Check warning on line 61 in src/wp-admin/menu.php

View workflow job for this annotation

GitHub Actions / PHP static analysis / Run PHP static analysis

Variable $update_data might not be defined.
);

$updates_description = '<span id="wp-menu-updates-count-description" class="wp-menu-count-description screen-reader-text" aria-hidden="true">' . $updates_text . '</span>';

$submenu['index.php'][10] = array(
sprintf(
/* translators: %s: Number of pending updates. */
__( 'Updates %s' ),
sprintf(
'<span class="update-plugins count-%s"><span class="update-count">%s</span></span>',
$update_data['counts']['total'],
number_format_i18n( $update_data['counts']['total'] )
)
$updates_count
),
$capability,
'update-core.php',
);

// Associate the hidden count description with the link. See _wp_menu_output().
$submenu['index.php'][10]['count_description'] = array(
'id' => 'wp-menu-updates-count-description',
'html' => $updates_description,
);

unset( $capability );
}

Expand Down Expand Up @@ -103,8 +119,11 @@
$awaiting_moderation_text = sprintf( _n( '%s Comment in moderation', '%s Comments in moderation', $awaiting_moderation ), $awaiting_moderation_i18n );

$menu[25] = array(
/* translators: %s: Number of comments. */
sprintf( __( 'Comments %s' ), '<span class="awaiting-mod count-' . absint( $awaiting_moderation ) . '"><span class="pending-count" aria-hidden="true">' . $awaiting_moderation_i18n . '</span><span class="comments-in-moderation-text screen-reader-text">' . $awaiting_moderation_text . '</span></span>' ),
sprintf(
/* translators: %s: Number of comments. */
__( 'Comments %s' ),
'<span class="awaiting-mod count-' . absint( $awaiting_moderation ) . '" aria-hidden="true"><span class="pending-count">' . $awaiting_moderation_i18n . '</span></span>'
),
'edit_posts',
'edit-comments.php',
'',
Expand All @@ -113,6 +132,12 @@
'dashicons-admin-comments',
);

// Associate the hidden count description with the link. See _wp_menu_output().
$menu[25]['count_description'] = array(
'id' => 'wp-menu-comments-count-description',
'html' => '<span id="wp-menu-comments-count-description" class="wp-menu-count-description comments-in-moderation-text screen-reader-text" aria-hidden="true">' . $awaiting_moderation_text . '</span>',
);

unset( $awaiting_moderation );
}

Expand Down Expand Up @@ -208,22 +233,39 @@

$menu[60] = array( __( 'Appearance' ), $appearance_capability, 'themes.php', '', 'menu-top menu-icon-appearance', 'menu-appearance', 'dashicons-admin-appearance' );

$count = '';
$count = '';
$description = '';
if ( ! is_multisite() && current_user_can( 'update_themes' ) ) {
if ( ! isset( $update_data ) ) {
$update_data = wp_get_update_data();
}

$count = sprintf(
'<span class="update-plugins count-%s"><span class="theme-count">%s</span></span>',
'<span class="update-plugins count-%s" aria-hidden="true"><span class="theme-count">%s</span></span>',
$update_data['counts']['themes'],
number_format_i18n( $update_data['counts']['themes'] )
);

$themes_text = sprintf(
/* translators: Hidden accessibility text. %s: Number of available theme updates. */
_n( '%s theme update available', '%s theme updates available', $update_data['counts']['themes'] ),
number_format_i18n( $update_data['counts']['themes'] )
);

$description = '<span id="wp-menu-themes-count-description" class="wp-menu-count-description screen-reader-text" aria-hidden="true">' . $themes_text . '</span>';
}

/* translators: %s: Number of available theme updates. */
$submenu['themes.php'][5] = array( sprintf( __( 'Themes %s' ), $count ), $appearance_capability, 'themes.php' );

// Associate the hidden count description with the link. See _wp_menu_output().
if ( '' !== $description ) {
$submenu['themes.php'][5]['count_description'] = array(
'id' => 'wp-menu-themes-count-description',
'html' => $description,
);
}

if ( wp_is_block_theme() ) {
$submenu['themes.php'][6] = array( _x( 'Editor', 'site editor menu item' ), 'edit_theme_options', 'site-editor.php' );
} else {
Expand Down Expand Up @@ -307,21 +349,38 @@
);
}

$count = '';
$count = '';
$description = '';
if ( ! is_multisite() && current_user_can( 'update_plugins' ) ) {
if ( ! isset( $update_data ) ) {
$update_data = wp_get_update_data();
}
$count = sprintf(
'<span class="update-plugins count-%s"><span class="plugin-count">%s</span></span>',
'<span class="update-plugins count-%s" aria-hidden="true"><span class="plugin-count">%s</span></span>',
$update_data['counts']['plugins'],
number_format_i18n( $update_data['counts']['plugins'] )
);

$plugins_text = sprintf(
/* translators: Hidden accessibility text. %s: Number of available plugin updates. */
_n( '%s plugin update available', '%s plugin updates available', $update_data['counts']['plugins'] ),
number_format_i18n( $update_data['counts']['plugins'] )
);

$description = '<span id="wp-menu-plugins-count-description" class="wp-menu-count-description screen-reader-text" aria-hidden="true">' . $plugins_text . '</span>';
}

/* translators: %s: Number of available plugin updates. */
$menu[65] = array( sprintf( __( 'Plugins %s' ), $count ), 'activate_plugins', 'plugins.php', '', 'menu-top menu-icon-plugins', 'menu-plugins', 'dashicons-admin-plugins' );

// Associate the hidden count description with the link. See _wp_menu_output().
if ( '' !== $description ) {
$menu[65]['count_description'] = array(
'id' => 'wp-menu-plugins-count-description',
'html' => $description,
);
}

$submenu['plugins.php'][5] = array( __( 'Installed Plugins' ), 'activate_plugins', 'plugins.php' );

if ( ! is_multisite() ) {
Expand Down Expand Up @@ -362,7 +421,8 @@
}
}

$site_health_count = '';
$site_health_count = '';
$site_health_description = '';
if ( ! is_multisite() && current_user_can( 'view_site_health_checks' ) ) {
$get_issues = get_transient( 'health-check-site-status-result' );

Expand All @@ -381,10 +441,18 @@
}

$site_health_count = sprintf(
'<span class="menu-counter site-health-counter count-%s"><span class="count">%s</span></span>',
'<span class="menu-counter site-health-counter count-%s" aria-hidden="true"><span class="count">%s</span></span>',
$issue_counts['critical'],
number_format_i18n( $issue_counts['critical'] )
);

$site_health_text = sprintf(
/* translators: Hidden accessibility text. %s: Number of critical Site Health checks. */
_n( '%s critical issue', '%s critical issues', $issue_counts['critical'] ),
number_format_i18n( $issue_counts['critical'] )
);

$site_health_description = '<span id="wp-menu-site-health-count-description" class="wp-menu-count-description screen-reader-text" aria-hidden="true">' . $site_health_text . '</span>';
}

$menu[75] = array( __( 'Tools' ), 'edit_posts', 'tools.php', '', 'menu-top menu-icon-tools', 'menu-tools', 'dashicons-admin-tools' );
Expand All @@ -393,6 +461,14 @@
$submenu['tools.php'][15] = array( __( 'Export' ), 'export', 'export.php' );
/* translators: %s: Number of critical Site Health checks. */
$submenu['tools.php'][20] = array( sprintf( __( 'Site Health %s' ), $site_health_count ), 'view_site_health_checks', 'site-health.php' );

// Associate the hidden count description with the link. See _wp_menu_output().
if ( '' !== $site_health_description ) {
$submenu['tools.php'][20]['count_description'] = array(
'id' => 'wp-menu-site-health-count-description',
'html' => $site_health_description,
);
}
$submenu['tools.php'][25] = array( __( 'Export Personal Data' ), 'export_others_personal_data', 'export-personal-data.php' );
$submenu['tools.php'][30] = array( __( 'Erase Personal Data' ), 'erase_others_personal_data', 'erase-personal-data.php' );
if ( is_multisite() && ! is_main_site() && '1' !== get_site()->deleted ) {
Expand Down
12 changes: 10 additions & 2 deletions src/wp-includes/admin-bar.php
Original file line number Diff line number Diff line change
Expand Up @@ -1134,13 +1134,17 @@ function wp_admin_bar_comments_menu( $wp_admin_bar ) {

$icon = '<span class="ab-icon" aria-hidden="true"></span>';
$title = '<span class="ab-label awaiting-mod pending-count count-' . $awaiting_mod . '" aria-hidden="true">' . number_format_i18n( $awaiting_mod ) . '</span>';
$title .= '<span class="screen-reader-text comments-in-moderation-text">' . $awaiting_text . '</span>';
$title .= '<span class="screen-reader-text">' . __( 'Comments' ) . '</span>';
$title .= '<span id="wp-admin-bar-comments-count-description" class="screen-reader-text comments-in-moderation-text" aria-hidden="true">' . $awaiting_text . '</span>';

$wp_admin_bar->add_node(
array(
'id' => 'comments',
'title' => $icon . $title,
'href' => admin_url( 'edit-comments.php' ),
'meta' => array(
'aria-describedby' => 'wp-admin-bar-comments-count-description',
),
)
Comment on lines +1137 to 1148
);
}
Expand Down Expand Up @@ -1249,13 +1253,17 @@ function wp_admin_bar_updates_menu( $wp_admin_bar ) {

$icon = '<span class="ab-icon" aria-hidden="true"></span>';
$title = '<span class="ab-label" aria-hidden="true">' . number_format_i18n( $update_data['counts']['total'] ) . '</span>';
$title .= '<span class="screen-reader-text updates-available-text">' . $updates_text . '</span>';
$title .= '<span class="screen-reader-text">' . __( 'Updates' ) . '</span>';
$title .= '<span id="wp-admin-bar-updates-count-description" class="screen-reader-text updates-available-text" aria-hidden="true">' . $updates_text . '</span>';

$wp_admin_bar->add_node(
array(
'id' => 'updates',
'title' => $icon . $title,
'href' => network_admin_url( 'update-core.php' ),
'meta' => array(
'aria-describedby' => 'wp-admin-bar-updates-count-description',
),
)
Comment on lines +1256 to 1267
);
}
Expand Down
Loading
Loading