diff --git a/src/wp-admin/about.php b/src/wp-admin/about.php index eb2b2656dd..769a0c81e4 100644 --- a/src/wp-admin/about.php +++ b/src/wp-admin/about.php @@ -139,7 +139,7 @@ include( ABSPATH . 'wp-admin/admin-header.php' ); 'height' => 550 ); - $plugin_link = 'WordPress REST API'; + $plugin_link = 'WordPress REST API'; } else { $plugin_link = 'WordPress REST API'; } diff --git a/src/wp-admin/css/common.css b/src/wp-admin/css/common.css index 9d734d4aa7..e6e3a18f76 100644 --- a/src/wp-admin/css/common.css +++ b/src/wp-admin/css/common.css @@ -2814,67 +2814,67 @@ body.index-php #TB_ajaxWindowTitle { display: none; } -body.about-php .tb-close-icon, -body.plugin-install-php .tb-close-icon, -body.import-php .tb-close-icon, -body.plugins-php .tb-close-icon, -body.update-core-php .tb-close-icon, -body.index-php .tb-close-icon { +/* only on these screens */ +.about-php #TB_closeWindowButton, +.plugin-install-php #TB_closeWindowButton, +.import-php #TB_closeWindowButton, +.plugins-php #TB_closeWindowButton, +.update-core-php #TB_closeWindowButton, +.index-php #TB_closeWindowButton { left: auto; right: -30px; color: #eee; - -webkit-transition: color .1s ease-in-out, background .1s ease-in-out; - transition: color .1s ease-in-out, background .1s ease-in-out; } + +body.about-php #TB_closeWindowButton:hover, body.about-php #TB_closeWindowButton:focus, -body.about-php #TB_closeWindowButton:focus .tb-close-icon, -body.about-php .tb-close-icon:focus, -body.about-php .tb-close-icon:hover, +body.plugin-install-php #TB_closeWindowButton:hover, body.plugin-install-php #TB_closeWindowButton:focus, -body.plugin-install-php #TB_closeWindowButton:focus .tb-close-icon, -body.plugin-install-php .tb-close-icon:focus, -body.plugin-install-php .tb-close-icon:hover, +body.import-php #TB_closeWindowButton:hover, body.import-php #TB_closeWindowButton:focus, -body.import-php #TB_closeWindowButton:focus .tb-close-icon, -body.import-php .tb-close-icon:focus, -body.import-php .tb-close-icon:hover, +body.plugins-php #TB_closeWindowButton:hover, body.plugins-php #TB_closeWindowButton:focus, -body.plugins-php #TB_closeWindowButton:focus .tb-close-icon, -body.plugins-php .tb-close-icon:focus, -body.plugins-php .tb-close-icon:hover, +body.update-core-php #TB_closeWindowButton:hover, body.update-core-php #TB_closeWindowButton:focus, -body.update-core-php #TB_closeWindowButton:focus .tb-close-icon, -body.update-core-php .tb-close-icon:focus, -body.update-core-php .tb-close-icon:hover, -body.index-php #TB_closeWindowButton:focus, -body.index-php #TB_closeWindowButton:focus .tb-close-icon, -body.index-php .tb-close-icon:focus, -body.index-php .tb-close-icon:hover { +body.index-php #TB_closeWindowButton:hover, +body.index-php #TB_closeWindowButton:focus { color: #00a0d2; outline: none; -webkit-box-shadow: none; box-shadow: none; } -body.about-php .tb-close-icon:before, -body.plugin-install-php .tb-close-icon:before, -body.import-php .tb-close-icon:before, -body.plugins-php .tb-close-icon:before, -body.update-core-php .tb-close-icon:before, -body.index-php .tb-close-icon:before { +body.about-php .tb-close-icon, +body.plugin-install-php .tb-close-icon, +body.import-php .tb-close-icon, +body.plugins-php .tb-close-icon, +body.update-core-php .tb-close-icon, +body.index-php .tb-close-icon { + display: none; +} + +body.about-php #TB_closeWindowButton:after, +body.plugin-install-php #TB_closeWindowButton:after, +body.import-php #TB_closeWindowButton:after, +body.plugins-php #TB_closeWindowButton:after, +body.update-core-php #TB_closeWindowButton:after, +body.index-php #TB_closeWindowButton:after { content: "\f335"; - font-size: 32px; + font: normal 32px/29px 'dashicons'; + speak: none; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; } /* move plugin install close icon to top on narrow screens */ @media screen and ( max-width: 830px ) { - body.about-php .tb-close-icon, - body.plugin-install-php .tb-close-icon, - body.import-php .tb-close-icon, - body.plugins-php .tb-close-icon, - body.update-core-php .tb-close-icon, - body.index-php .tb-close-icon { + body.about-php #TB_closeWindowButton, + body.plugin-install-php #TB_closeWindowButton, + body.import-php #TB_closeWindowButton, + body.plugins-php #TB_closeWindowButton, + body.update-core-php #TB_closeWindowButton, + body.index-php #TB_closeWindowButton { right: 0; top: -30px; } diff --git a/src/wp-admin/import.php b/src/wp-admin/import.php index ae82ccf3ee..a1ce80e2b3 100644 --- a/src/wp-admin/import.php +++ b/src/wp-admin/import.php @@ -101,7 +101,7 @@ if ( empty( $importers ) ) { if ( empty($action) ) { if ( is_main_site() ) { $action = '' . $data[0] . ''; } else { $action = $data[0]; diff --git a/src/wp-admin/includes/class-wp-plugin-install-list-table.php b/src/wp-admin/includes/class-wp-plugin-install-list-table.php index 6b78850479..cdd0c6e021 100644 --- a/src/wp-admin/includes/class-wp-plugin-install-list-table.php +++ b/src/wp-admin/includes/class-wp-plugin-install-list-table.php @@ -476,7 +476,7 @@ class WP_Plugin_Install_List_Table extends WP_List_Table { '&TB_iframe=true&width=600&height=550' ); /* translators: 1: Plugin name and version. */ - $action_links[] = '' . __( 'More Details' ) . ''; + $action_links[] = '' . __( 'More Details' ) . ''; if ( !empty( $plugin['icons']['svg'] ) ) { $plugin_icon_url = $plugin['icons']['svg']; @@ -504,7 +504,7 @@ class WP_Plugin_Install_List_Table extends WP_List_Table {

- + diff --git a/src/wp-admin/includes/class-wp-plugins-list-table.php b/src/wp-admin/includes/class-wp-plugins-list-table.php index 5b65354baa..f49d1c8697 100644 --- a/src/wp-admin/includes/class-wp-plugins-list-table.php +++ b/src/wp-admin/includes/class-wp-plugins-list-table.php @@ -742,7 +742,7 @@ class WP_Plugins_List_Table extends WP_List_Table { // Details link using API info, if available if ( isset( $plugin_data['slug'] ) && current_user_can( 'install_plugins' ) ) { - $plugin_meta[] = sprintf( '%s', + $plugin_meta[] = sprintf( '%s', esc_url( network_admin_url( 'plugin-install.php?tab=plugin-information&plugin=' . $plugin_data['slug'] . '&TB_iframe=true&width=600&height=550' ) ), esc_attr( sprintf( __( 'More information about %s' ), $plugin_name ) ), diff --git a/src/wp-admin/includes/dashboard.php b/src/wp-admin/includes/dashboard.php index 410ee1dd0c..5a234857d8 100644 --- a/src/wp-admin/includes/dashboard.php +++ b/src/wp-admin/includes/dashboard.php @@ -1245,7 +1245,7 @@ function wp_dashboard_plugins_output( $rss, $args = array() ) { $ilink = wp_nonce_url('plugin-install.php?tab=plugin-information&plugin=' . $slug, 'install-plugin_' . $slug) . '&TB_iframe=true&width=600&height=800'; echo '
  • ' . __( 'Popular Plugin' ) . ': ' . esc_html( $raw_title ) . - ' (' . __( 'Install' ) . ')
  • '; diff --git a/src/wp-admin/includes/update.php b/src/wp-admin/includes/update.php index 8939c89901..e412073ebe 100644 --- a/src/wp-admin/includes/update.php +++ b/src/wp-admin/includes/update.php @@ -339,7 +339,7 @@ function wp_plugin_update_row( $file, $plugin_data ) { if ( ! current_user_can( 'update_plugins' ) ) { /* translators: 1: plugin name, 2: details URL, 3: accessibility text, 4: version number */ - printf( __( 'There is a new version of %1$s available. View version %4$s details.' ), + printf( __( 'There is a new version of %1$s available. View version %4$s details.' ), $plugin_name, esc_url( $details_url ), /* translators: 1: plugin name, 2: version number */ @@ -348,7 +348,7 @@ function wp_plugin_update_row( $file, $plugin_data ) { ); } elseif ( empty( $r->package ) ) { /* translators: 1: plugin name, 2: details URL, 3: accessibility text, 4: version number */ - printf( __( 'There is a new version of %1$s available. View version %4$s details. Automatic update is unavailable for this plugin.' ), + printf( __( 'There is a new version of %1$s available. View version %4$s details. Automatic update is unavailable for this plugin.' ), $plugin_name, esc_url( $details_url ), /* translators: 1: plugin name, 2: version number */ @@ -357,7 +357,7 @@ function wp_plugin_update_row( $file, $plugin_data ) { ); } else { /* translators: 1: plugin name, 2: details URL, 3: accessibility text, 4: version number, 5: update URL, 6: accessibility text */ - printf( __( 'There is a new version of %1$s available. View version %4$s details or update now.' ), + printf( __( 'There is a new version of %1$s available. View version %4$s details or update now.' ), $plugin_name, esc_url( $details_url ), /* translators: 1: plugin name, 2: version number */ @@ -469,7 +469,7 @@ function wp_theme_update_row( $theme_key, $theme ) { echo '
    '; if ( ! current_user_can('update_themes') ) { /* translators: 1: theme name, 2: details URL, 3: accessibility text, 4: version number */ - printf( __( 'There is a new version of %1$s available. View version %4$s details.'), + printf( __( 'There is a new version of %1$s available. View version %4$s details.'), $theme_name, esc_url( $details_url ), /* translators: 1: theme name, 2: version number */ @@ -478,7 +478,7 @@ function wp_theme_update_row( $theme_key, $theme ) { ); } elseif ( empty( $r['package'] ) ) { /* translators: 1: theme name, 2: details URL, 3: accessibility text, 4: version number */ - printf( __( 'There is a new version of %1$s available. View version %4$s details. Automatic update is unavailable for this theme.' ), + printf( __( 'There is a new version of %1$s available. View version %4$s details. Automatic update is unavailable for this theme.' ), $theme_name, esc_url( $details_url ), /* translators: 1: theme name, 2: version number */ @@ -487,7 +487,7 @@ function wp_theme_update_row( $theme_key, $theme ) { ); } else { /* translators: 1: theme name, 2: details URL, 3: accessibility text, 4: version number, 5: update URL, 6: accessibility text */ - printf( __( 'There is a new version of %1$s available. View version %4$s details or update now.' ), + printf( __( 'There is a new version of %1$s available. View version %4$s details or update now.' ), $theme_name, esc_url( $details_url ), /* translators: 1: theme name, 2: version number */ diff --git a/src/wp-admin/js/plugin-install.js b/src/wp-admin/js/plugin-install.js index 6693598e17..dc0f5a58d8 100644 --- a/src/wp-admin/js/plugin-install.js +++ b/src/wp-admin/js/plugin-install.js @@ -1,14 +1,23 @@ -/* global plugininstallL10n, tb_click */ +/* global plugininstallL10n, tb_click, tb_remove */ /* Plugin Browser Thickbox related JS*/ var tb_position; jQuery( document ).ready( function( $ ) { + + var tbWindow, + $focusedBefore, + $iframeBody, + $tabbables, + $firstTabbable, + $lastTabbable; + tb_position = function() { - var tbWindow = $( '#TB_window' ), - width = $( window ).width(), + var width = $( window ).width(), H = $( window ).height() - ( ( 792 < width ) ? 60 : 20 ), W = ( 792 < width ) ? 772 : width - 20; + tbWindow = $( '#TB_window' ); + if ( tbWindow.length ) { tbWindow.width( W ).height( H ); $( '#TB_iframeContent' ).width( W ).height( H ); @@ -38,16 +47,111 @@ jQuery( document ).ready( function( $ ) { tb_position(); }); - $( '.plugin-card, .plugins .plugin-version-author-uri' ).on( 'click', 'a.thickbox', function( e ) { + /* + * Custom events: when a Thickbox iframe has loaded and when the Thickbox + * modal gets removed from the DOM. + */ + $( 'body' ) + .on( 'thickbox:iframe:loaded', tbWindow, function() { + iframeLoaded(); + }) + .on( 'thickbox:removed', function() { + // Set focus back to the element that opened the modal dialog. + // Note: IE 8 would need this wrapped in a fake setTimeout `0`. + $focusedBefore.focus(); + }); + + function iframeLoaded() { + var $iframe = tbWindow.find( '#TB_iframeContent' ); + + // Get the iframe body. + $iframeBody = $iframe.contents().find( 'body' ); + + // Get the tabbable elements and handle the keydown event on first load. + handleTabbables(); + + // Set initial focus on the "Close" button. + $firstTabbable.focus(); + + /* + * When the "Install" button is disabled (e.g. the Plugin is already installed) + * then we can't predict where the last focusable element is. We need to get + * the tabbable elements and handle the keydown event again and again, + * each time the active tab panel changes. + */ + $( '#plugin-information-tabs a', $iframeBody ).on( 'click', function() { + handleTabbables(); + }); + + // Close the modal when pressing Escape. + $iframeBody.on( 'keydown', function( event ) { + if ( 27 !== event.which ) { + return; + } + tb_remove(); + }); + } + + /* + * Get the tabbable elements and detach/attach the keydown event. + * Called after the iframe has fully loaded so we have all the elements we need. + * Called again each time a Tab gets clicked. + * @todo Consider to implement a WordPress general utility for this and don't use jQuery UI. + */ + function handleTabbables() { + var $firstAndLast; + // Get all the tabbable elements. + $tabbables = $( ':tabbable', $iframeBody ); + // Our first tabbable element is always the "Close" button. + $firstTabbable = tbWindow.find( '#TB_closeWindowButton' ); + // Get the last tabbable element. + $lastTabbable = $tabbables.last(); + // Make a jQuery collection. + $firstAndLast = $firstTabbable.add( $lastTabbable ); + // Detach any previously attached keydown event. + $firstAndLast.off( 'keydown.wp-plugin-details' ); + // Attach again the keydown event on the first and last focusable elements. + $firstAndLast.on( 'keydown.wp-plugin-details', function( event ) { + constrainTabbing( event ); + }); + } + + // Constrain tabbing within the plugin modal dialog. + function constrainTabbing( event ) { + if ( 9 !== event.which ) { + return; + } + + if ( $lastTabbable[0] === event.target && ! event.shiftKey ) { + event.preventDefault(); + $firstTabbable.focus(); + } else if ( $firstTabbable[0] === event.target && event.shiftKey ) { + event.preventDefault(); + $lastTabbable.focus(); + } + } + + // Open the Plugin details modal. + $( '.thickbox.open-plugin-details-modal' ).on( 'click', function( e ) { + // The `data-title` attribute is used only in the Plugin screens. + var title = $( this ).data( 'title' ) ? plugininstallL10n.plugin_information + ' ' + $( this ).data( 'title' ) : plugininstallL10n.plugin_modal_label; + e.preventDefault(); e.stopPropagation(); + // Store the element that has focus before opening the modal dialog, i.e. the control which opens it. + $focusedBefore = $( this ); + tb_click.call(this); - $('#TB_title').css({'background-color':'#23282d','color':'#cfcfcf'}); - $('#TB_ajaxWindowTitle').html( '' + plugininstallL10n.plugin_information + ' ' + $(this).data( 'title' ) ); - $('#TB_iframeContent').attr( 'title', plugininstallL10n.plugin_information + ' ' + $(this).data( 'title' ) ); - $('#TB_closeWindowButton').focus(); + // Set ARIA role and ARIA label. + tbWindow.attr({ + 'role': 'dialog', + 'aria-label': plugininstallL10n.plugin_modal_label + }); + + // Set title attribute on the iframe. + tbWindow.find( '#TB_iframeContent' ).attr( 'title', title ); }); /* Plugin install related JS */ diff --git a/src/wp-admin/update-core.php b/src/wp-admin/update-core.php index a38b5bbba1..bbed380326 100644 --- a/src/wp-admin/update-core.php +++ b/src/wp-admin/update-core.php @@ -284,7 +284,7 @@ function list_plugin_updates() { $details_name = sprintf( '%1$s', esc_attr( $plugin_data->Name ) ); /* translators: 1: Plugin name 2: Plugin version */ $details_text = sprintf( __( 'View %1$s version %2$s details.' ), $details_name, $plugin_data->update->new_version ); - $details = sprintf( '%2$s', esc_url( $details_url ), $details_text ); + $details = sprintf( '%2$s', esc_url( $details_url ), $details_text ); $checkbox_id = "checkbox_" . md5( $plugin_data->Name ); ?> diff --git a/src/wp-includes/js/thickbox/thickbox.css b/src/wp-includes/js/thickbox/thickbox.css index 28c99adf4e..6bfac61ee4 100644 --- a/src/wp-includes/js/thickbox/thickbox.css +++ b/src/wp-includes/js/thickbox/thickbox.css @@ -43,12 +43,19 @@ float: right; } -#TB_closeAjaxWindow { - float: right; -} - -#TB_closeAjaxWindow a { - text-decoration: none; +#TB_closeWindowButton { + position: absolute; + left: auto; + right: 0; + width: 29px; + height: 29px; + border: 0; + padding: 0; + background: none; + cursor: pointer; + outline: none; + -webkit-transition: color .1s ease-in-out, background .1s ease-in-out; + transition: color .1s ease-in-out, background .1s ease-in-out; } #TB_ajaxWindowTitle { @@ -115,6 +122,7 @@ } .tb-close-icon { + display: block; color: #666; text-align: center; line-height: 29px; @@ -133,6 +141,7 @@ -moz-osx-font-smoothing: grayscale; } -.tb-close-icon:hover { +#TB_closeWindowButton:hover .tb-close-icon, +#TB_closeWindowButton:focus .tb-close-icon { color: #00a0d2; } diff --git a/src/wp-includes/js/thickbox/thickbox.js b/src/wp-includes/js/thickbox/thickbox.js index 581838496b..164f72e616 100644 --- a/src/wp-includes/js/thickbox/thickbox.js +++ b/src/wp-includes/js/thickbox/thickbox.js @@ -41,6 +41,8 @@ function tb_click(){ function tb_show(caption, url, imageGroup) {//function called when the user clicks on a thickbox link + var $closeBtn; + try { if (typeof document.body.style.maxHeight === "undefined") {//if IE 6 jQuery("body","html").css({height: "100%", width: "100%"}); @@ -137,7 +139,7 @@ function tb_show(caption, url, imageGroup) {//function called when the user clic TB_WIDTH = imageWidth + 30; TB_HEIGHT = imageHeight + 60; - jQuery("#TB_window").append(""+thickboxL10n.close+""+caption+"" + "
    "+caption+"
    " + TB_imageCount + TB_PrevHTML + TB_NextHTML + "
    "); + jQuery("#TB_window").append(""+thickboxL10n.close+""+caption+"" + "
    "+caption+"
    " + TB_imageCount + TB_PrevHTML + TB_NextHTML + "
    "); jQuery("#TB_closeWindowButton").click(tb_remove); @@ -202,10 +204,10 @@ function tb_show(caption, url, imageGroup) {//function called when the user clic urlNoQuery = url.split('TB_'); jQuery("#TB_iframeContent").remove(); if(params['modal'] != "true"){//iframe no modal - jQuery("#TB_window").append(""); + jQuery("#TB_window").append("
    "+caption+"
    "); }else{//iframe modal jQuery("#TB_overlay").unbind(); - jQuery("#TB_window").append(""); + jQuery("#TB_window").append(""); } }else{// not an iframe, ajax if(jQuery("#TB_window").css("visibility") != "visible"){ @@ -259,6 +261,16 @@ function tb_show(caption, url, imageGroup) {//function called when the user clic }); } + $closeBtn = jQuery( '#TB_closeWindowButton' ); + /* + * If the native Close button icon is visible, move focus on the button + * (e.g. in the Network Admin Themes screen). + * In other admin screens is hidden and replaced by a different icon. + */ + if ( $closeBtn.find( '.tb-close-icon' ).is( ':visible' ) ) { + $closeBtn.focus(); + } + } catch(e) { //nothing here } @@ -273,7 +285,10 @@ function tb_showIframe(){ function tb_remove() { jQuery("#TB_imageOff").unbind("click"); jQuery("#TB_closeWindowButton").unbind("click"); - jQuery("#TB_window").fadeOut("fast",function(){jQuery('#TB_window,#TB_overlay,#TB_HideSelect').trigger("tb_unload").unbind().remove();}); + jQuery( '#TB_window' ).fadeOut( 'fast', function() { + jQuery( '#TB_window, #TB_overlay, #TB_HideSelect' ).trigger( 'tb_unload' ).unbind().remove(); + jQuery( 'body' ).trigger( 'thickbox:removed' ); + }); jQuery( 'body' ).removeClass( 'modal-open' ); jQuery("#TB_load").remove(); if (typeof document.body.style.maxHeight == "undefined") {//if IE 6 diff --git a/src/wp-includes/script-loader.php b/src/wp-includes/script-loader.php index 8f867ec89a..b40bab76a5 100644 --- a/src/wp-includes/script-loader.php +++ b/src/wp-includes/script-loader.php @@ -580,9 +580,10 @@ function wp_default_scripts( &$scripts ) { 'saved' => __( 'Changes saved.' ), ) ); - $scripts->add( 'plugin-install', "/wp-admin/js/plugin-install$suffix.js", array( 'jquery', 'thickbox' ), false, 1 ); + $scripts->add( 'plugin-install', "/wp-admin/js/plugin-install$suffix.js", array( 'jquery', 'jquery-ui-core', 'thickbox' ), false, 1 ); did_action( 'init' ) && $scripts->localize( 'plugin-install', 'plugininstallL10n', array( - 'plugin_information' => __('Plugin Information:'), + 'plugin_information' => __( 'Plugin:' ), + 'plugin_modal_label' => __( 'Plugin details' ), 'ays' => __('Are you sure you want to install this plugin?') ) );