Changeset 37714
- Timestamp:
- 06/15/2016 04:36:07 PM (8 years ago)
- Location:
- trunk
- Files:
-
- 2 added
- 28 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/wp-admin/admin-ajax.php
r36709 r37714 63 63 'save-user-color-scheme', 'update-widget', 'query-themes', 'parse-embed', 'set-attachment-thumbnail', 64 64 'parse-media-shortcode', 'destroy-sessions', 'install-plugin', 'update-plugin', 'press-this-save-post', 65 'press-this-add-category', 'crop-image', 'generate-password', 'save-wporg-username', 65 'press-this-add-category', 'crop-image', 'generate-password', 'save-wporg-username', 'delete-plugin', 66 'search-plugins', 'search-install-plugins', 'activate-plugin', 'update-theme', 'delete-theme', 67 'install-theme', 66 68 ); 67 69 -
trunk/src/wp-admin/css/common.css
r37439 r37714 1398 1398 } 1399 1399 1400 .update-message p:before, 1401 .updating-message p:before, 1402 .updated-message p:before, 1403 .import-php .updating-message:before, 1404 .button.updating-message:before, 1405 .button.updated-message:before, 1406 .button.installed:before, 1407 .button.installing:before { 1408 display: inline-block; 1409 font: normal 20px/1 'dashicons'; 1410 -webkit-font-smoothing: antialiased; 1411 -moz-osx-font-smoothing: grayscale; 1412 vertical-align: top; 1413 } 1414 1400 1415 .wrap .notice, 1401 1416 .wrap div.updated, … … 1404 1419 .media-upload-form div.error { 1405 1420 margin: 5px 0 15px; 1421 } 1422 1423 /* Update icon. */ 1424 .update-message p:before, 1425 .updating-message p:before, 1426 .import-php .updating-message:before, 1427 .button.updating-message:before, 1428 .button.installing:before { 1429 color: #f56e28; 1430 content: "\f463"; 1431 } 1432 1433 /* Spins the update icon. */ 1434 .updating-message p:before, 1435 .import-php .updating-message:before, 1436 .button.updating-message:before, 1437 .button.installing:before { 1438 -webkit-animation: rotation 2s infinite linear; 1439 animation: rotation 2s infinite linear; 1440 } 1441 1442 /* Updated icon (check mark). */ 1443 .updated-message p:before, 1444 .installed p:before, 1445 .button.updated-message:before { 1446 color: #79ba49; 1447 content: '\f147'; 1448 } 1449 1450 /* Error icon. */ 1451 .update-message.notice-error p:before { 1452 color: #dc3232; 1453 content: "\f534"; 1454 } 1455 1456 .wrap .notice p:before, 1457 .import-php .updating-message:before { 1458 margin-right: 6px; 1459 vertical-align: bottom; 1406 1460 } 1407 1461 … … 1420 1474 } 1421 1475 1422 .update-message {1423 color: #000;1424 }1425 1426 1476 ul#dismissed-updates { 1427 1477 display: none; … … 1454 1504 margin-left: 2em; 1455 1505 } 1506 1507 .button.updating-message:before, 1508 .button.updated-message:before, 1509 .button.installed:before, 1510 .button.installing:before { 1511 margin: 3px 5px 0 -2px; 1512 } 1513 1514 .button-primary.updating-message:before { 1515 color: #fff; 1516 } 1517 1518 .button-primary.updated-message:before { 1519 color: #66c6e4; 1520 } 1521 1522 .button.updated-message, 1523 .notice .button-link { 1524 -webkit-transition-property: border, background, color; 1525 transition-property: border, background, color; 1526 -webkit-transition-duration: .05s; 1527 transition-duration: .05s; 1528 -webkit-transition-timing-function: ease-in-out; 1529 transition-timing-function: ease-in-out; 1530 } 1531 1532 .notice .button-link { 1533 color: #0073aa; 1534 } 1535 1536 .notice .button-link:hover, 1537 .notice .button-link:active { 1538 color: #00a0d2; 1539 } 1540 1541 @media aural { 1542 .wrap .notice p:before, 1543 .button.installing:before, 1544 .button.installed:before, 1545 .update-message p:before { 1546 speak: none; 1547 } 1548 } 1549 1456 1550 1457 1551 /* @todo: this does not need its own section anymore */ -
trunk/src/wp-admin/css/forms.css
r37693 r37714 1045 1045 } 1046 1046 1047 .request-filesystem-credentials-dialog .ftp-username, 1048 .request-filesystem-credentials-dialog .ftp-password { 1049 float: none; 1050 width: auto; 1051 } 1052 1053 .request-filesystem-credentials-dialog .ftp-username { 1054 margin-bottom: 1em; 1055 } 1056 1057 .request-filesystem-credentials-dialog .ftp-password { 1058 margin: 0; 1059 } 1060 1061 .request-filesystem-credentials-dialog .ftp-password em { 1062 color: #888; 1063 } 1064 1065 .request-filesystem-credentials-dialog label { 1066 display: block; 1067 line-height: 1.5; 1068 margin-bottom: 1em; 1069 } 1070 1071 .request-filesystem-credentials-form legend { 1072 padding-bottom: 0; 1073 } 1074 1075 .request-filesystem-credentials-form #ssh-keys legend { 1076 font-size: 1.3em; 1077 } 1078 1079 .request-filesystem-credentials-form .notice { 1080 margin: 0 0 20px 0; 1081 clear: both; 1082 } 1083 1047 1084 1048 1085 /* =Media Queries -
trunk/src/wp-admin/css/list-tables.css
r36959 r37714 1272 1272 } 1273 1273 1274 .plugin-update-tr td {1275 border-top: 0;1276 }1277 1278 1274 .plugins .inactive td, 1279 1275 .plugins .inactive th, … … 1310 1306 } 1311 1307 1312 .plugins .active.update td,1313 .plugins .active.update th,1314 tr.active.update + tr.plugin-update-tr .plugin-update {1315 background-color: #fef7f1;1316 }1317 1318 1308 .plugins .active th.check-column, 1319 1309 .plugin-update-tr.active td { 1320 1310 border-left: 4px solid #00a0d2; 1321 }1322 1323 .plugins .active.update th.check-column,1324 .plugins .active.update + .plugin-update-tr .plugin-update {1325 border-left: 4px solid #d54e21;1326 1311 } 1327 1312 … … 1359 1344 } 1360 1345 1361 .plugin-update-tr .update-message { 1362 font-size: 13px; 1363 font-weight: normal; 1364 margin: 0 10px 8px 31px; 1365 padding: 6px 12px 8px 40px; 1366 background-color: #f7f7f7; 1367 background-color: rgba(0,0,0,0.03); 1368 } 1369 1370 .plugin-update-tr .update-message:before, 1371 .plugin-card .update-now:before, 1372 .plugin-card .install-now:before { 1373 color: #d54e21; 1346 .plugins .plugin-update-tr .plugin-update { 1347 -webkit-box-shadow: inset 0 -1px 0 rgba(0,0,0,0.1); 1348 box-shadow: inset 0 -1px 0 rgba(0,0,0,0.1); 1349 overflow: hidden; /* clearfix */ 1350 padding: 0; 1351 } 1352 1353 .plugins .plugin-update-tr .notice { 1354 margin: 5px 20px 15px 40px; 1355 } 1356 1357 .plugins .notice p { 1358 margin: 0.5em 0; 1359 } 1360 1361 .plugin-card .update-now:before { 1362 color: #f56e28; 1363 content: "\f463"; 1374 1364 display: inline-block; 1375 1365 font: normal 20px/1 dashicons; 1366 margin: 3px 5px 0 -2px; 1376 1367 speak: none; 1377 1368 -webkit-font-smoothing: antialiased; … … 1380 1371 } 1381 1372 1382 .plugin-update-tr .update-message:before,1383 .plugin-card .update-now:before {1384 content: "\f463";1385 }1386 1387 .plugin-update-tr .update-message:before {1388 margin: 0 10px 0 -30px;1389 }1390 1391 .plugin-card .update-now:before,1392 .plugin-card .install-now:before {1393 margin: 3px 5px 0 -2px;1394 }1395 1396 .plugin-update-tr .updating-message:before,1397 1373 .plugin-card .updating-message:before { 1398 1374 content: "\f463"; … … 1423 1399 } 1424 1400 1425 .plugin-update-tr .updated-message:before,1426 1401 .plugin-card .updated-message:before { 1427 1402 color: #79ba49; 1428 1403 content: "\f147"; 1429 }1430 1431 .wp-list-table.plugins tbody tr.plugin-update-tr td.plugin-update {1432 overflow: hidden; /* clearfix */1433 padding: 0;1434 -webkit-box-shadow: inset 0 -1px 0 rgba(0,0,0,0.1);1435 box-shadow: inset 0 -1px 0 rgba(0,0,0,0.1);1436 }1437 1438 /* update notices for active plugins */1439 tr.active + tr.plugin-update-tr .plugin-update {1440 background-color: #f7fcfe;1441 }1442 1443 tr.active + tr.plugin-update-tr:not(.updated) .plugin-update .update-message {1444 background-color: #fcf3ef;1445 1404 } 1446 1405 … … 2141 2100 } 2142 2101 2102 .plugins .active.update + .plugin-update-tr:before { 2103 background-color: #f7fcfe; 2104 border-left: 4px solid #00a0d2; 2105 } 2106 2107 .plugins .plugin-update-tr .update-message { 2108 margin-left: 0; 2109 } 2110 2143 2111 .wp-list-table.plugins .plugin-title strong, 2144 2112 .wp-list-table.plugins .theme-title strong { -
trunk/src/wp-admin/css/themes.css
r37341 r37714 12 12 } 13 13 14 .themes-php .wrap h1 { 15 float: left; 14 .themes-php:not(.network-admin) .wrap h1 { 16 15 margin-bottom: 15px; 17 }18 19 .network-admin.themes-php .wrap h1 {20 margin-bottom: 0;21 16 } 22 17 … … 38 33 39 34 /* Position admin messages */ 40 .themes-php div.updated, 41 .themes-php div.error, 42 .themes-php div.notice { 43 margin: 0 0 20px 0; 44 clear: both; 35 .theme .notice, 36 .theme .notice.is-dismissible { 37 left: 0; 38 margin: 0; 39 position: absolute; 40 right: 0; 41 top: 0; 45 42 } 46 43 … … 208 205 209 206 /** 210 * Displays a theme update notice211 * when an update is available.212 */213 .theme-browser .theme .theme-update,214 .theme-browser .theme .theme-installed {215 background: #d54e21;216 background: rgba(213, 78, 33, 0.95);217 color: #fff;218 display: block;219 font-size: 13px;220 font-weight: 400;221 height: 48px;222 line-height: 48px;223 padding: 0 10px;224 position: absolute;225 top: 0;226 right: 0;227 left: 0;228 border-bottom: 1px solid rgba(0,0,0,0.25);229 overflow: hidden;230 }231 232 .theme-browser .theme .theme-update:before,233 .theme-browser .theme .theme-installed:before {234 content: "\f463";235 display: inline-block;236 font: normal 20px/1 dashicons;237 margin: 0 6px 0 0;238 opacity: 0.8;239 position: relative;240 top: 5px;241 speak: none;242 -webkit-font-smoothing: antialiased;243 }244 245 246 /**247 207 * The currently active theme 248 208 */ … … 952 912 953 913 @media only screen and (max-width: 650px) { 954 .theme-overlay .theme-update,955 914 .theme-overlay .theme-description { 956 915 margin-left: 0; … … 1042 1001 background: #0073aa; 1043 1002 } 1044 .theme-browser .theme .theme-installed:before { 1003 .theme-browser .theme .notice-success p:before { 1004 color: #79ba49; 1045 1005 content: "\f147"; 1046 } 1047 .theme-browser .theme.is-installed .theme-actions .button-primary { 1048 display: none !important; 1006 display: inline-block; 1007 font: normal 20px/1 'dashicons'; 1008 -webkit-font-smoothing: antialiased; 1009 -moz-osx-font-smoothing: grayscale; 1010 vertical-align: top; 1011 } 1012 1013 .theme-install.updated-message:before { 1014 content: ''; 1049 1015 } 1050 1016 … … 1393 1359 cursor: default; 1394 1360 pointer-events: none; 1361 } 1362 1363 .theme-install-overlay .close-full-overlay, 1364 .theme-install-overlay .previous-theme, 1365 .theme-install-overlay .next-theme { 1366 border-left: 0; 1367 border-top: 0; 1368 border-bottom: 0; 1369 } 1370 1371 .theme-install-overlay .close-full-overlay:before, 1372 .theme-install-overlay .previous-theme:before, 1373 .theme-install-overlay .next-theme:before { 1374 top: 2px; 1375 left: 0; 1395 1376 } 1396 1377 … … 1709 1690 } 1710 1691 1711 .theme-install-overlay .wp-full-overlay-header . theme-install{1692 .theme-install-overlay .wp-full-overlay-header .button { 1712 1693 float: right; 1713 1694 margin: 8px 10px 0 0; … … 1804 1785 } 1805 1786 } 1787 1788 @media aural { 1789 .theme .notice:before, 1790 .theme-info .updating-message:before, 1791 .theme-info .updated-message:before, 1792 .theme-install.updating-message:before { 1793 speak: none; 1794 } 1795 } -
trunk/src/wp-admin/import.php
r36964 r37714 47 47 add_thickbox(); 48 48 wp_enqueue_script( 'plugin-install' ); 49 wp_enqueue_script( 'updates' ); 49 50 50 51 require_once( ABSPATH . 'wp-admin/admin-header.php' ); … … 132 133 133 134 <?php 135 wp_print_request_filesystem_credentials_modal(); 136 wp_print_admin_notice_templates(); 134 137 135 138 include( ABSPATH . 'wp-admin/admin-footer.php' ); -
trunk/src/wp-admin/includes/ajax-actions.php
r37674 r37714 2862 2862 ), $update_php ); 2863 2863 2864 if ( current_user_can( 'switch_themes' ) ) { 2865 if ( is_multisite() ) { 2866 $theme->activate_url = add_query_arg( array( 2867 'action' => 'enable', 2868 '_wpnonce' => wp_create_nonce( 'enable-theme_' . $theme->slug ), 2869 'theme' => $theme->slug, 2870 ), network_admin_url( 'themes.php' ) ); 2871 } else { 2872 $theme->activate_url = add_query_arg( array( 2873 'action' => 'activate', 2874 '_wpnonce' => wp_create_nonce( 'switch-theme_' . $theme->slug ), 2875 'stylesheet' => $theme->slug, 2876 ), admin_url( 'themes.php' ) ); 2877 } 2878 } 2879 2880 if ( ! is_multisite() && current_user_can( 'edit_theme_options' ) && current_user_can( 'customize' ) ) { 2881 $theme->customize_url = add_query_arg( array( 2882 'return' => urlencode( network_admin_url( 'theme-install.php', 'relative' ) ), 2883 ), wp_customize_url( $theme->slug ) ); 2884 } 2885 2864 2886 $theme->name = wp_kses( $theme->name, $themes_allowedtags ); 2865 2887 $theme->author = wp_kses( $theme->author, $themes_allowedtags ); … … 3068 3090 3069 3091 wp_send_json_success( array( 'message' => $message ) ); 3070 }3071 3072 3073 /**3074 * AJAX handler for updating a plugin.3075 *3076 * @since 4.2.03077 *3078 * @see Plugin_Upgrader3079 */3080 function wp_ajax_update_plugin() {3081 global $wp_filesystem;3082 3083 $plugin = urldecode( $_POST['plugin'] );3084 3085 $status = array(3086 'update' => 'plugin',3087 'plugin' => $plugin,3088 'slug' => sanitize_key( $_POST['slug'] ),3089 'oldVersion' => '',3090 'newVersion' => '',3091 );3092 3093 $plugin_data = get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin );3094 if ( $plugin_data['Version'] ) {3095 $status['oldVersion'] = sprintf( __( 'Version %s' ), $plugin_data['Version'] );3096 }3097 3098 if ( ! current_user_can( 'update_plugins' ) ) {3099 $status['error'] = __( 'You do not have sufficient permissions to update plugins for this site.' );3100 wp_send_json_error( $status );3101 }3102 3103 check_ajax_referer( 'updates' );3104 3105 include_once( ABSPATH . 'wp-admin/includes/class-wp-upgrader.php' );3106 3107 wp_update_plugins();3108 3109 $skin = new Automatic_Upgrader_Skin();3110 $upgrader = new Plugin_Upgrader( $skin );3111 $result = $upgrader->bulk_upgrade( array( $plugin ) );3112 3113 if ( is_array( $result ) && empty( $result[$plugin] ) && is_wp_error( $skin->result ) ) {3114 $result = $skin->result;3115 }3116 3117 if ( is_array( $result ) && !empty( $result[ $plugin ] ) ) {3118 $plugin_update_data = current( $result );3119 3120 /*3121 * If the `update_plugins` site transient is empty (e.g. when you update3122 * two plugins in quick succession before the transient repopulates),3123 * this may be the return.3124 *3125 * Preferably something can be done to ensure `update_plugins` isn't empty.3126 * For now, surface some sort of error here.3127 */3128 if ( $plugin_update_data === true ) {3129 $status['error'] = __( 'Plugin update failed.' );3130 wp_send_json_error( $status );3131 }3132 3133 $plugin_data = get_plugins( '/' . $result[ $plugin ]['destination_name'] );3134 $plugin_data = reset( $plugin_data );3135 3136 if ( $plugin_data['Version'] ) {3137 $status['newVersion'] = sprintf( __( 'Version %s' ), $plugin_data['Version'] );3138 }3139 3140 wp_send_json_success( $status );3141 } else if ( is_wp_error( $result ) ) {3142 $status['error'] = $result->get_error_message();3143 wp_send_json_error( $status );3144 3145 } else if ( is_bool( $result ) && ! $result ) {3146 $status['errorCode'] = 'unable_to_connect_to_filesystem';3147 $status['error'] = __( 'Unable to connect to the filesystem. Please confirm your credentials.' );3148 3149 // Pass through the error from WP_Filesystem if one was raised3150 if ( is_wp_error( $wp_filesystem->errors ) && $wp_filesystem->errors->get_error_code() ) {3151 $status['error'] = $wp_filesystem->errors->get_error_message();3152 }3153 3154 wp_send_json_error( $status );3155 3156 } else {3157 // An unhandled error occured3158 $status['error'] = __( 'Plugin update failed.' );3159 wp_send_json_error( $status );3160 }3161 3092 } 3162 3093 … … 3334 3265 wp_send_json_success( update_user_meta( get_current_user_id(), 'wporg_favorites', $username ) ); 3335 3266 } 3267 3268 /** 3269 * AJAX handler for installing a theme. 3270 * 3271 * @since 4.6.0 3272 */ 3273 function wp_ajax_install_theme() { 3274 check_ajax_referer( 'updates' ); 3275 3276 if ( empty( $_POST['slug'] ) ) { 3277 wp_send_json_error( array( 3278 'slug' => '', 3279 'errorCode' => 'no_theme_specified', 3280 'errorMessage' => __( 'No theme specified.' ), 3281 ) ); 3282 } 3283 3284 $slug = sanitize_key( wp_unslash( $_POST['slug'] ) ); 3285 3286 $status = array( 3287 'install' => 'theme', 3288 'slug' => $slug, 3289 ); 3290 3291 if ( ! current_user_can( 'install_themes' ) ) { 3292 $status['errorMessage'] = __( 'You do not have sufficient permissions to install themes on this site.' ); 3293 wp_send_json_error( $status ); 3294 } 3295 3296 include_once( ABSPATH . 'wp-admin/includes/class-wp-upgrader.php' ); 3297 include_once( ABSPATH . 'wp-admin/includes/theme.php' ); 3298 3299 $api = themes_api( 'theme_information', array( 3300 'slug' => $slug, 3301 'fields' => array( 'sections' => false ), 3302 ) ); 3303 3304 if ( is_wp_error( $api ) ) { 3305 $status['errorMessage'] = $api->get_error_message(); 3306 wp_send_json_error( $status ); 3307 } 3308 3309 $upgrader = new Theme_Upgrader( new Automatic_Upgrader_Skin() ); 3310 $result = $upgrader->install( $api->download_link ); 3311 3312 if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) { 3313 $status['debug'] = $upgrader->skin->get_upgrade_messages(); 3314 } 3315 3316 if ( is_wp_error( $result ) ) { 3317 $status['errorMessage'] = $result->get_error_message(); 3318 wp_send_json_error( $status ); 3319 } elseif ( is_null( $result ) ) { 3320 global $wp_filesystem; 3321 3322 $status['errorCode'] = 'unable_to_connect_to_filesystem'; 3323 $status['errorMessage'] = __( 'Unable to connect to the filesystem. Please confirm your credentials.' ); 3324 3325 // Pass through the error from WP_Filesystem if one was raised. 3326 if ( $wp_filesystem instanceof WP_Filesystem_Base && is_wp_error( $wp_filesystem->errors ) && $wp_filesystem->errors->get_error_code() ) { 3327 $status['errorMessage'] = esc_html( $wp_filesystem->errors->get_error_message() ); 3328 } 3329 3330 wp_send_json_error( $status ); 3331 } 3332 3333 if ( current_user_can( 'switch_themes' ) ) { 3334 if ( is_multisite() ) { 3335 $status['activateUrl'] = add_query_arg( array( 3336 'action' => 'enable', 3337 '_wpnonce' => wp_create_nonce( 'enable-theme_' . $slug ), 3338 'theme' => $slug, 3339 ), network_admin_url( 'themes.php' ) ); 3340 } else { 3341 $status['activateUrl'] = add_query_arg( array( 3342 'action' => 'activate', 3343 '_wpnonce' => wp_create_nonce( 'switch-theme_' . $slug ), 3344 'stylesheet' => $slug, 3345 ), admin_url( 'themes.php' ) ); 3346 } 3347 } 3348 3349 if ( ! is_multisite() && current_user_can( 'edit_theme_options' ) && current_user_can( 'customize' ) ) { 3350 $status['customizeUrl'] = add_query_arg( array( 3351 'return' => urlencode( network_admin_url( 'theme-install.php', 'relative' ) ), 3352 ), wp_customize_url( $slug ) ); 3353 } 3354 3355 /* 3356 * See WP_Theme_Install_List_Table::_get_theme_status() if we wanted to check 3357 * on post-install status. 3358 */ 3359 wp_send_json_success( $status ); 3360 } 3361 3362 /** 3363 * AJAX handler for updating a theme. 3364 * 3365 * @since 4.6.0 3366 * 3367 * @see Theme_Upgrader 3368 */ 3369 function wp_ajax_update_theme() { 3370 check_ajax_referer( 'updates' ); 3371 3372 if ( empty( $_POST['slug'] ) ) { 3373 wp_send_json_error( array( 3374 'slug' => '', 3375 'errorCode' => 'no_theme_specified', 3376 'errorMessage' => __( 'No theme specified.' ), 3377 ) ); 3378 } 3379 3380 $stylesheet = sanitize_key( wp_unslash( $_POST['slug'] ) ); 3381 $status = array( 3382 'update' => 'theme', 3383 'slug' => $stylesheet, 3384 'newVersion' => '', 3385 ); 3386 3387 if ( ! current_user_can( 'update_themes' ) ) { 3388 $status['errorMessage'] = __( 'You do not have sufficient permissions to update themes on this site.' ); 3389 wp_send_json_error( $status ); 3390 } 3391 3392 include_once( ABSPATH . 'wp-admin/includes/class-wp-upgrader.php' ); 3393 3394 $current = get_site_transient( 'update_themes' ); 3395 if ( empty( $current ) ) { 3396 wp_update_themes(); 3397 } 3398 3399 $upgrader = new Theme_Upgrader( new Automatic_Upgrader_Skin() ); 3400 $result = $upgrader->bulk_upgrade( array( $stylesheet ) ); 3401 3402 if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) { 3403 $status['debug'] = $upgrader->skin->get_upgrade_messages(); 3404 } 3405 3406 if ( is_array( $result ) && ! empty( $result[ $stylesheet ] ) ) { 3407 3408 // Theme is already at the latest version. 3409 if ( true === $result[ $stylesheet ] ) { 3410 $status['errorMessage'] = $upgrader->strings['up_to_date']; 3411 wp_send_json_error( $status ); 3412 } 3413 3414 $theme = wp_get_theme( $stylesheet ); 3415 if ( $theme->get( 'Version' ) ) { 3416 $status['newVersion'] = $theme->get( 'Version' ); 3417 } 3418 3419 wp_send_json_success( $status ); 3420 } elseif ( is_wp_error( $upgrader->skin->result ) ) { 3421 $status['errorCode'] = $upgrader->skin->result->get_error_code(); 3422 $status['errorMessage'] = $upgrader->skin->result->get_error_message(); 3423 wp_send_json_error( $status ); 3424 } elseif ( false === $result ) { 3425 global $wp_filesystem; 3426 3427 $status['errorCode'] = 'unable_to_connect_to_filesystem'; 3428 $status['errorMessage'] = __( 'Unable to connect to the filesystem. Please confirm your credentials.' ); 3429 3430 // Pass through the error from WP_Filesystem if one was raised. 3431 if ( $wp_filesystem instanceof WP_Filesystem_Base && is_wp_error( $wp_filesystem->errors ) && $wp_filesystem->errors->get_error_code() ) { 3432 $status['errorMessage'] = esc_html( $wp_filesystem->errors->get_error_message() ); 3433 } 3434 3435 wp_send_json_error( $status ); 3436 } 3437 3438 // An unhandled error occurred. 3439 $status['errorMessage'] = __( 'Update failed.' ); 3440 wp_send_json_error( $status ); 3441 } 3442 3443 /** 3444 * AJAX handler for deleting a theme. 3445 * 3446 * @since 4.6.0 3447 */ 3448 function wp_ajax_delete_theme() { 3449 check_ajax_referer( 'updates' ); 3450 3451 if ( empty( $_POST['slug'] ) ) { 3452 wp_send_json_error( array( 3453 'slug' => '', 3454 'errorCode' => 'no_theme_specified', 3455 'errorMessage' => __( 'No theme specified.' ), 3456 ) ); 3457 } 3458 3459 $stylesheet = sanitize_key( wp_unslash( $_POST['slug'] ) ); 3460 $status = array( 3461 'delete' => 'theme', 3462 'slug' => $stylesheet, 3463 ); 3464 3465 if ( ! current_user_can( 'delete_themes' ) ) { 3466 $status['errorMessage'] = __( 'You do not have sufficient permissions to delete themes on this site.' ); 3467 wp_send_json_error( $status ); 3468 } 3469 3470 if ( ! wp_get_theme( $stylesheet )->exists() ) { 3471 $status['errorMessage'] = __( 'The requested theme does not exist.' ); 3472 wp_send_json_error( $status ); 3473 } 3474 3475 // Check filesystem credentials. `delete_plugins()` will bail otherwise. 3476 ob_start(); 3477 $url = wp_nonce_url( 'themes.php?action=delete&stylesheet=' . urlencode( $stylesheet ), 'delete-theme_' . $stylesheet ); 3478 if ( false === ( $credentials = request_filesystem_credentials( $url ) ) || ! WP_Filesystem( $credentials ) ) { 3479 global $wp_filesystem; 3480 ob_end_clean(); 3481 3482 $status['errorCode'] = 'unable_to_connect_to_filesystem'; 3483 $status['errorMessage'] = __( 'Unable to connect to the filesystem. Please confirm your credentials.' ); 3484 3485 // Pass through the error from WP_Filesystem if one was raised. 3486 if ( $wp_filesystem instanceof WP_Filesystem_Base && is_wp_error( $wp_filesystem->errors ) && $wp_filesystem->errors->get_error_code() ) { 3487 $status['errorMessage'] = esc_html( $wp_filesystem->errors->get_error_message() ); 3488 } 3489 3490 wp_send_json_error( $status ); 3491 } 3492 3493 include_once( ABSPATH . 'wp-admin/includes/theme.php' ); 3494 3495 $result = delete_theme( $stylesheet ); 3496 3497 if ( is_wp_error( $result ) ) { 3498 $status['errorMessage'] = $result->get_error_message(); 3499 wp_send_json_error( $status ); 3500 } elseif ( false === $result ) { 3501 $status['errorMessage'] = __( 'Theme could not be deleted.' ); 3502 wp_send_json_error( $status ); 3503 } 3504 3505 wp_send_json_success( $status ); 3506 } 3507 3508 /** 3509 * AJAX handler for installing a plugin. 3510 * 3511 * @since 4.6.0 3512 */ 3513 function wp_ajax_install_plugin() { 3514 check_ajax_referer( 'updates' ); 3515 3516 if ( empty( $_POST['slug'] ) ) { 3517 wp_send_json_error( array( 3518 'slug' => '', 3519 'errorCode' => 'no_plugin_specified', 3520 'errorMessage' => __( 'No plugin specified.' ), 3521 ) ); 3522 } 3523 3524 $status = array( 3525 'install' => 'plugin', 3526 'slug' => sanitize_key( wp_unslash( $_POST['slug'] ) ), 3527 ); 3528 3529 if ( ! current_user_can( 'install_plugins' ) ) { 3530 $status['errorMessage'] = __( 'You do not have sufficient permissions to install plugins on this site.' ); 3531 wp_send_json_error( $status ); 3532 } 3533 3534 include_once( ABSPATH . 'wp-admin/includes/class-wp-upgrader.php' ); 3535 include_once( ABSPATH . 'wp-admin/includes/plugin-install.php' ); 3536 3537 $api = plugins_api( 'plugin_information', array( 3538 'slug' => sanitize_key( wp_unslash( $_POST['slug'] ) ), 3539 'fields' => array( 3540 'sections' => false, 3541 ), 3542 ) ); 3543 3544 if ( is_wp_error( $api ) ) { 3545 $status['errorMessage'] = $api->get_error_message(); 3546 wp_send_json_error( $status ); 3547 } 3548 3549 $status['pluginName'] = $api->name; 3550 3551 $upgrader = new Plugin_Upgrader( new Automatic_Upgrader_Skin() ); 3552 $result = $upgrader->install( $api->download_link ); 3553 3554 if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) { 3555 $status['debug'] = $upgrader->skin->get_upgrade_messages(); 3556 } 3557 3558 if ( is_wp_error( $result ) ) { 3559 $status['errorMessage'] = $result->get_error_message(); 3560 wp_send_json_error( $status ); 3561 } elseif ( is_null( $result ) ) { 3562 global $wp_filesystem; 3563 3564 $status['errorCode'] = 'unable_to_connect_to_filesystem'; 3565 $status['errorMessage'] = __( 'Unable to connect to the filesystem. Please confirm your credentials.' ); 3566 3567 // Pass through the error from WP_Filesystem if one was raised. 3568 if ( $wp_filesystem instanceof WP_Filesystem_Base && is_wp_error( $wp_filesystem->errors ) && $wp_filesystem->errors->get_error_code() ) { 3569 $status['errorMessage'] = esc_html( $wp_filesystem->errors->get_error_message() ); 3570 } 3571 3572 wp_send_json_error( $status ); 3573 } 3574 3575 $install_status = install_plugin_install_status( $api ); 3576 3577 if ( current_user_can( 'activate_plugins' ) && is_plugin_inactive( $install_status['file'] ) ) { 3578 $status['activateUrl'] = add_query_arg( array( 3579 '_wpnonce' => wp_create_nonce( 'activate-plugin_' . $install_status['file'] ), 3580 'action' => 'activate', 3581 'plugin' => $install_status['file'], 3582 ), network_admin_url( 'plugins.php' ) ); 3583 } 3584 3585 if ( is_multisite() && current_user_can( 'manage_network_plugins' ) ) { 3586 $status['activateUrl'] = add_query_arg( array( 'networkwide' => 1 ), $status['activateUrl'] ); 3587 } 3588 3589 wp_send_json_success( $status ); 3590 } 3591 3592 /** 3593 * AJAX handler for updating a plugin. 3594 * 3595 * @since 4.2.0 3596 * 3597 * @see Plugin_Upgrader 3598 */ 3599 function wp_ajax_update_plugin() { 3600 check_ajax_referer( 'updates' ); 3601 3602 if ( empty( $_POST['plugin'] ) || empty( $_POST['slug'] ) ) { 3603 wp_send_json_error( array( 3604 'slug' => '', 3605 'errorCode' => 'no_plugin_specified', 3606 'errorMessage' => __( 'No plugin specified.' ), 3607 ) ); 3608 } 3609 3610 $plugin = plugin_basename( sanitize_text_field( wp_unslash( $_POST['plugin'] ) ) ); 3611 $plugin_data = get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin ); 3612 3613 $status = array( 3614 'update' => 'plugin', 3615 'plugin' => $plugin, 3616 'slug' => sanitize_key( wp_unslash( $_POST['slug'] ) ), 3617 'pluginName' => $plugin_data['Name'], 3618 'oldVersion' => '', 3619 'newVersion' => '', 3620 ); 3621 3622 if ( $plugin_data['Version'] ) { 3623 /* translators: %s: Plugin version */ 3624 $status['oldVersion'] = sprintf( __( 'Version %s' ), $plugin_data['Version'] ); 3625 } 3626 3627 if ( ! current_user_can( 'update_plugins' ) ) { 3628 $status['errorMessage'] = __( 'You do not have sufficient permissions to update plugins for this site.' ); 3629 wp_send_json_error( $status ); 3630 } 3631 3632 include_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php'; 3633 3634 wp_update_plugins(); 3635 3636 $skin = new Automatic_Upgrader_Skin(); 3637 $upgrader = new Plugin_Upgrader( $skin ); 3638 $result = $upgrader->bulk_upgrade( array( $plugin ) ); 3639 3640 if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) { 3641 $status['debug'] = $upgrader->skin->get_upgrade_messages(); 3642 } 3643 3644 if ( is_array( $result ) && empty( $result[ $plugin ] ) && is_wp_error( $skin->result ) ) { 3645 $result = $skin->result; 3646 } 3647 3648 if ( is_array( $result ) && ! empty( $result[ $plugin ] ) ) { 3649 $plugin_update_data = current( $result ); 3650 3651 /* 3652 * If the `update_plugins` site transient is empty (e.g. when you update 3653 * two plugins in quick succession before the transient repopulates), 3654 * this may be the return. 3655 * 3656 * Preferably something can be done to ensure `update_plugins` isn't empty. 3657 * For now, surface some sort of error here. 3658 */ 3659 if ( true === $plugin_update_data ) { 3660 $status['errorMessage'] = __( 'Plugin update failed.' ); 3661 wp_send_json_error( $status ); 3662 } 3663 3664 $plugin_data = get_plugins( '/' . $result[ $plugin ]['destination_name'] ); 3665 $plugin_data = reset( $plugin_data ); 3666 3667 if ( $plugin_data['Version'] ) { 3668 /* translators: %s: Plugin version */ 3669 $status['newVersion'] = sprintf( __( 'Version %s' ), $plugin_data['Version'] ); 3670 } 3671 wp_send_json_success( $status ); 3672 } elseif ( is_wp_error( $result ) ) { 3673 $status['errorMessage'] = $result->get_error_message(); 3674 wp_send_json_error( $status ); 3675 } elseif ( false === $result ) { 3676 global $wp_filesystem; 3677 3678 $status['errorCode'] = 'unable_to_connect_to_filesystem'; 3679 $status['errorMessage'] = __( 'Unable to connect to the filesystem. Please confirm your credentials.' ); 3680 3681 // Pass through the error from WP_Filesystem if one was raised. 3682 if ( $wp_filesystem instanceof WP_Filesystem_Base && is_wp_error( $wp_filesystem->errors ) && $wp_filesystem->errors->get_error_code() ) { 3683 $status['errorMessage'] = esc_html( $wp_filesystem->errors->get_error_message() ); 3684 } 3685 3686 wp_send_json_error( $status ); 3687 } 3688 3689 // An unhandled error occurred. 3690 $status['errorMessage'] = __( 'Plugin update failed.' ); 3691 wp_send_json_error( $status ); 3692 } 3693 3694 /** 3695 * AJAX handler for deleting a plugin. 3696 * 3697 * @since 4.6.0 3698 */ 3699 function wp_ajax_delete_plugin() { 3700 check_ajax_referer( 'updates' ); 3701 3702 if ( empty( $_POST['slug'] ) || empty( $_POST['plugin'] ) ) { 3703 wp_send_json_error( array( 'errorCode' => 'no_plugin_specified' ) ); 3704 } 3705 3706 $plugin = plugin_basename( sanitize_text_field( wp_unslash( $_POST['plugin'] ) ) ); 3707 $plugin_data = get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin ); 3708 3709 $status = array( 3710 'delete' => 'plugin', 3711 'slug' => sanitize_key( wp_unslash( $_POST['slug'] ) ), 3712 'plugin' => $plugin, 3713 'pluginName' => $plugin_data['Name'], 3714 ); 3715 3716 if ( ! current_user_can( 'delete_plugins' ) ) { 3717 $status['errorMessage'] = __( 'You do not have sufficient permissions to delete plugins for this site.' ); 3718 wp_send_json_error( $status ); 3719 } 3720 3721 if ( is_plugin_active( $plugin ) ) { 3722 $status['errorMessage'] = __( 'You cannot delete a plugin while it is active on the main site.' ); 3723 wp_send_json_error( $status ); 3724 } 3725 3726 // Check filesystem credentials. `delete_plugins()` will bail otherwise. 3727 ob_start(); 3728 $url = wp_nonce_url( 'plugins.php?action=delete-selected&verify-delete=1&checked[]=' . $plugin, 'bulk-plugins' ); 3729 if ( false === ( $credentials = request_filesystem_credentials( $url ) ) || ! WP_Filesystem( $credentials ) ) { 3730 global $wp_filesystem; 3731 ob_end_clean(); 3732 3733 $status['errorCode'] = 'unable_to_connect_to_filesystem'; 3734 $status['errorMessage'] = __( 'Unable to connect to the filesystem. Please confirm your credentials.' ); 3735 3736 // Pass through the error from WP_Filesystem if one was raised. 3737 if ( $wp_filesystem instanceof WP_Filesystem_Base && is_wp_error( $wp_filesystem->errors ) && $wp_filesystem->errors->get_error_code() ) { 3738 $status['errorMessage'] = esc_html( $wp_filesystem->errors->get_error_message() ); 3739 } 3740 3741 wp_send_json_error( $status ); 3742 } 3743 3744 $result = delete_plugins( array( $plugin ) ); 3745 3746 if ( is_wp_error( $result ) ) { 3747 $status['errorMessage'] = $result->get_error_message(); 3748 wp_send_json_error( $status ); 3749 } elseif ( false === $result ) { 3750 $status['errorMessage'] = __( 'Plugin could not be deleted.' ); 3751 wp_send_json_error( $status ); 3752 } 3753 3754 wp_send_json_success( $status ); 3755 } 3756 3757 /** 3758 * AJAX handler for searching plugins. 3759 * 3760 * @since 4.6.0 3761 * 3762 * @global WP_List_Table $wp_list_table Current list table instance. 3763 * @global string $hook_suffix Current admin page. 3764 * @global string $s Search term. 3765 */ 3766 function wp_ajax_search_plugins() { 3767 check_ajax_referer( 'updates' ); 3768 3769 global $wp_list_table, $hook_suffix, $s; 3770 $hook_suffix = 'plugins.php'; 3771 3772 /** @var WP_Plugins_List_Table $wp_list_table */ 3773 $wp_list_table = _get_list_table( 'WP_Plugins_List_Table' ); 3774 $status = array(); 3775 3776 if ( ! $wp_list_table->ajax_user_can() ) { 3777 $status['errorMessage'] = __( 'You do not have sufficient permissions to manage plugins on this site.' ); 3778 wp_send_json_error( $status ); 3779 } 3780 3781 // Set the correct requester, so pagination works. 3782 $_SERVER['REQUEST_URI'] = add_query_arg( array_diff_key( $_POST, array( 3783 '_ajax_nonce' => null, 3784 'action' => null, 3785 ) ), network_admin_url( 'plugins.php', 'relative' ) ); 3786 3787 $s = sanitize_text_field( $_POST['s'] ); 3788 3789 $wp_list_table->prepare_items(); 3790 3791 ob_start(); 3792 $wp_list_table->display(); 3793 $status['items'] = ob_get_clean(); 3794 3795 wp_send_json_success( $status ); 3796 } 3797 3798 /** 3799 * AJAX handler for searching plugins to install. 3800 * 3801 * @since 4.6.0 3802 * 3803 * @global WP_List_Table $wp_list_table Current list table instance. 3804 * @global string $hook_suffix Current admin page. 3805 */ 3806 function wp_ajax_search_install_plugins() { 3807 check_ajax_referer( 'updates' ); 3808 3809 global $wp_list_table, $hook_suffix; 3810 $hook_suffix = 'plugin-install.php'; 3811 3812 /** @var WP_Plugin_Install_List_Table $wp_list_table */ 3813 $wp_list_table = _get_list_table( 'WP_Plugin_Install_List_Table' ); 3814 $status = array(); 3815 3816 if ( ! $wp_list_table->ajax_user_can() ) { 3817 $status['errorMessage'] = __( 'You do not have sufficient permissions to manage plugins on this site.' ); 3818 wp_send_json_error( $status ); 3819 } 3820 3821 // Set the correct requester, so pagination works. 3822 $_SERVER['REQUEST_URI'] = add_query_arg( array_diff_key( $_POST, array( 3823 '_ajax_nonce' => null, 3824 'action' => null, 3825 ) ), network_admin_url( 'plugin-install.php', 'relative' ) ); 3826 3827 $wp_list_table->prepare_items(); 3828 3829 ob_start(); 3830 $wp_list_table->display(); 3831 $status['items'] = ob_get_clean(); 3832 3833 wp_send_json_success( $status ); 3834 } -
trunk/src/wp-admin/includes/class-wp-filesystem-base.php
r37674 r37714 42 42 /** 43 43 * @access public 44 * @var WP_Error 44 45 */ 45 46 public $errors = null; -
trunk/src/wp-admin/includes/class-wp-ms-themes-list-table.php
r37488 r37714 150 150 $total_this_page = $totals[ $status ]; 151 151 152 wp_localize_script( 'updates', '_wpUpdatesItemCounts', array( 153 'totals' => $totals, 154 ) ); 155 152 156 if ( $orderby ) { 153 157 $orderby = ucfirst( $orderby ); -
trunk/src/wp-admin/includes/class-wp-plugin-install-list-table.php
r37488 r37714 462 462 $action_links[] = '<a class="install-now button" data-slug="' . esc_attr( $plugin['slug'] ) . '" href="' . esc_url( $status['url'] ) . '" aria-label="' . esc_attr( sprintf( __( 'Install %s now' ), $name ) ) . '" data-name="' . esc_attr( $name ) . '">' . __( 'Install Now' ) . '</a>'; 463 463 } 464 465 464 break; 465 466 466 case 'update_available': 467 467 if ( $status['url'] ) { … … 469 469 $action_links[] = '<a class="update-now button aria-button-if-js" data-plugin="' . esc_attr( $status['file'] ) . '" data-slug="' . esc_attr( $plugin['slug'] ) . '" href="' . esc_url( $status['url'] ) . '" aria-label="' . esc_attr( sprintf( __( 'Update %s now' ), $name ) ) . '" data-name="' . esc_attr( $name ) . '">' . __( 'Update Now' ) . '</a>'; 470 470 } 471 472 471 break; 472 473 473 case 'latest_installed': 474 474 case 'newer_installed': 475 $action_links[] = '<span class="button button-disabled">' . _x( 'Installed', 'plugin' ) . '</span>'; 475 if ( is_plugin_active( $status['file'] ) ) { 476 $action_links[] = '<button type="button" class="button button-disabled" disabled="disabled">' . _x( 'Active', 'plugin' ) . '</button>'; 477 } elseif ( current_user_can( 'activate_plugins' ) ) { 478 $button_text = __( 'Activate' ); 479 $activate_url = add_query_arg( array( 480 '_wpnonce' => wp_create_nonce( 'activate-plugin_' . $status['file'] ), 481 'action' => 'activate', 482 'plugin' => $status['file'], 483 ), network_admin_url( 'plugins.php' ) ); 484 485 if ( is_network_admin() ) { 486 $button_text = __( 'Network Activate' ); 487 $activate_url = add_query_arg( array( 'networkwide' => 1 ), $activate_url ); 488 } 489 490 $action_links[] = sprintf( 491 '<a href="%1$s" class="button activate-now button-secondary" aria-label="%2$s">%3$s</a>', 492 esc_url( $activate_url ), 493 /* translators: %s: Plugin name */ 494 esc_attr( sprintf( __( 'Activate %s' ), $plugin['name'] ) ), 495 $button_text 496 ); 497 } else { 498 $action_links[] = '<button type="button" class="button button-disabled" disabled="disabled">' . _x( 'Installed', 'plugin' ) . '</button>'; 499 } 476 500 break; 477 501 } -
trunk/src/wp-admin/includes/class-wp-plugins-list-table.php
r37488 r37714 246 246 247 247 $total_this_page = $totals[ $status ]; 248 249 $js_plugins = array(); 250 foreach ( $plugins as $key => $list ) { 251 $js_plugins[ $key ] = array_keys( (array) $list ); 252 } 253 254 wp_localize_script( 'updates', '_wpUpdatesItemCounts', array( 255 'plugins' => $js_plugins, 256 ) ); 248 257 249 258 if ( ! $orderby ) { -
trunk/src/wp-admin/includes/class-wp-upgrader-skin.php
r37432 r37714 19 19 public $done_header = false; 20 20 public $done_footer = false; 21 22 /** 23 * 24 * @var string|false|WP_Error 25 */ 21 26 public $result = false; 22 27 public $options = array(); -
trunk/src/wp-admin/includes/class-wp-upgrader.php
r37687 r37714 62 62 * @since 2.8.0 63 63 * @access public 64 * @var WP_Upgrader_Skin $skin64 * @var Automatic_Upgrader_Skin|WP_Upgrader_Skin $skin 65 65 */ 66 66 public $skin = null; -
trunk/src/wp-admin/includes/plugin-install.php
r37488 r37714 541 541 542 542 ?> 543 543 <div id="<?php echo $_tab; ?>-content" class='<?php echo $_with_banner; ?>'> 544 544 <div class="fyi"> 545 545 <ul> 546 <?php if ( ! empty( $api->version ) ) { ?> 547 <li><strong><?php _e( 'Version:' ); ?></strong> <?php echo $api->version; ?></li> 548 <?php } if ( ! empty( $api->author ) ) { ?> 549 <li><strong><?php _e( 'Author:' ); ?></strong> <?php echo links_add_target( $api->author, '_blank' ); ?></li> 550 <?php } if ( ! empty( $api->last_updated ) ) { ?> 551 <li><strong><?php _e( 'Last Updated:' ); ?></strong> 552 <?php printf( __( '%s ago' ), human_time_diff( strtotime( $api->last_updated ) ) ); ?> 553 </li> 554 <?php } if ( ! empty( $api->requires ) ) { ?> 555 <li><strong><?php _e( 'Requires WordPress Version:' ); ?></strong> <?php printf( __( '%s or higher' ), $api->requires ); ?></li> 556 <?php } if ( ! empty( $api->tested ) ) { ?> 557 <li><strong><?php _e( 'Compatible up to:' ); ?></strong> <?php echo $api->tested; ?></li> 558 <?php } if ( ! empty( $api->active_installs ) ) { ?> 559 <li><strong><?php _e( 'Active Installs:' ); ?></strong> <?php 560 if ( $api->active_installs >= 1000000 ) { 561 _ex( '1+ Million', 'Active plugin installs' ); 562 } else { 563 echo number_format_i18n( $api->active_installs ) . '+'; 564 } 565 ?></li> 566 <?php } if ( ! empty( $api->slug ) && empty( $api->external ) ) { ?> 567 <li><a target="_blank" href="https://wordpress.org/plugins/<?php echo $api->slug; ?>/"><?php _e( 'WordPress.org Plugin Page »' ); ?></a></li> 568 <?php } if ( ! empty( $api->homepage ) ) { ?> 569 <li><a target="_blank" href="<?php echo esc_url( $api->homepage ); ?>"><?php _e( 'Plugin Homepage »' ); ?></a></li> 570 <?php } if ( ! empty( $api->donate_link ) && empty( $api->contributors ) ) { ?> 571 <li><a target="_blank" href="<?php echo esc_url( $api->donate_link ); ?>"><?php _e( 'Donate to this plugin »' ); ?></a></li> 572 <?php } ?> 546 <?php if ( ! empty( $api->version ) ) { ?> 547 <li><strong><?php _e( 'Version:' ); ?></strong> <?php echo $api->version; ?></li> 548 <?php } if ( ! empty( $api->author ) ) { ?> 549 <li><strong><?php _e( 'Author:' ); ?></strong> <?php echo links_add_target( $api->author, '_blank' ); ?></li> 550 <?php } if ( ! empty( $api->last_updated ) ) { ?> 551 <li><strong><?php _e( 'Last Updated:' ); ?></strong> 552 <?php 553 /* translators: %s: Time since the last update */ 554 printf( __( '%s ago' ), human_time_diff( strtotime( $api->last_updated ) ) ); 555 ?> 556 </li> 557 <?php } if ( ! empty( $api->requires ) ) { ?> 558 <li> 559 <strong><?php _e( 'Requires WordPress Version:' ); ?></strong> 560 <?php 561 /* translators: %s: WordPress version */ 562 printf( __( '%s or higher' ), $api->requires ); 563 ?> 564 </li> 565 <?php } if ( ! empty( $api->tested ) ) { ?> 566 <li><strong><?php _e( 'Compatible up to:' ); ?></strong> <?php echo $api->tested; ?></li> 567 <?php } if ( ! empty( $api->active_installs ) ) { ?> 568 <li><strong><?php _e( 'Active Installs:' ); ?></strong> <?php 569 if ( $api->active_installs >= 1000000 ) { 570 _ex( '1+ Million', 'Active plugin installs' ); 571 } else { 572 echo number_format_i18n( $api->active_installs ) . '+'; 573 } 574 ?></li> 575 <?php } if ( ! empty( $api->slug ) && empty( $api->external ) ) { ?> 576 <li><a target="_blank" href="https://wordpress.org/plugins/<?php echo $api->slug; ?>/"><?php _e( 'WordPress.org Plugin Page »' ); ?></a></li> 577 <?php } if ( ! empty( $api->homepage ) ) { ?> 578 <li><a target="_blank" href="<?php echo esc_url( $api->homepage ); ?>"><?php _e( 'Plugin Homepage »' ); ?></a></li> 579 <?php } if ( ! empty( $api->donate_link ) && empty( $api->contributors ) ) { ?> 580 <li><a target="_blank" href="<?php echo esc_url( $api->donate_link ); ?>"><?php _e( 'Donate to this plugin »' ); ?></a></li> 581 <?php } ?> 573 582 </ul> 574 583 <?php if ( ! empty( $api->rating ) ) { ?> 575 <h3><?php _e( 'Average Rating' ); ?></h3>576 <?php wp_star_rating( array( 'rating' => $api->rating, 'type' => 'percent', 'number' => $api->num_ratings ) ); ?>577 <p aria-hidden="true" class="fyi-description"><?php printf( _n( '(based on %s rating)', '(based on %s ratings)', $api->num_ratings ), number_format_i18n( $api->num_ratings ) ); ?></p>584 <h3><?php _e( 'Average Rating' ); ?></h3> 585 <?php wp_star_rating( array( 'rating' => $api->rating, 'type' => 'percent', 'number' => $api->num_ratings ) ); ?> 586 <p aria-hidden="true" class="fyi-description"><?php printf( _n( '(based on %s rating)', '(based on %s ratings)', $api->num_ratings ), number_format_i18n( $api->num_ratings ) ); ?></p> 578 587 <?php } 579 588 … … 592 601 ?> 593 602 <div class="counter-container"> 594 <span class="counter-label"><a href="https://wordpress.org/support/view/plugin-reviews/<?php echo $api->slug; ?>?filter=<?php echo $key; ?>"595 target="_blank" aria-label="<?php echo $aria_label; ?>"><?php printf( _n( '%d star', '%d stars', $key ), $key ); ?></a></span>596 <span class="counter-back">597 <span class="counter-bar" style="width: <?php echo 92 * $_rating; ?>px;"></span>598 </span>603 <span class="counter-label"><a href="https://wordpress.org/support/view/plugin-reviews/<?php echo $api->slug; ?>?filter=<?php echo $key; ?>" 604 target="_blank" aria-label="<?php echo $aria_label; ?>"><?php printf( _n( '%d star', '%d stars', $key ), $key ); ?></a></span> 605 <span class="counter-back"> 606 <span class="counter-bar" style="width: <?php echo 92 * $_rating; ?>px;"></span> 607 </span> 599 608 <span class="counter-count" aria-hidden="true"><?php echo number_format_i18n( $ratecount ); ?></span> 600 609 </div> … … 629 638 <div id="section-holder" class="wrap"> 630 639 <?php 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 640 if ( ! empty( $api->tested ) && version_compare( substr( $GLOBALS['wp_version'], 0, strlen( $api->tested ) ), $api->tested, '>' ) ) { 641 echo '<div class="notice notice-warning notice-alt"><p>' . __( '<strong>Warning:</strong> This plugin has <strong>not been tested</strong> with your current version of WordPress.' ) . '</p></div>'; 642 } elseif ( ! empty( $api->requires ) && version_compare( substr( $GLOBALS['wp_version'], 0, strlen( $api->requires ) ), $api->requires, '<' ) ) { 643 echo '<div class="notice notice-warning notice-alt"><p>' . __( '<strong>Warning:</strong> This plugin has <strong>not been marked as compatible</strong> with your version of WordPress.' ) . '</p></div>'; 644 } 645 646 foreach ( (array) $api->sections as $section_name => $content ) { 647 $content = links_add_base_url( $content, 'https://wordpress.org/plugins/' . $api->slug . '/' ); 648 $content = links_add_target( $content, '_blank' ); 649 650 $san_section = esc_attr( $section_name ); 651 652 $display = ( $section_name === $section ) ? 'block' : 'none'; 653 654 echo "\t<div id='section-{$san_section}' class='section' style='display: {$display};'>\n"; 655 echo $content; 656 echo "\t</div>\n"; 657 } 649 658 echo "</div>\n"; 650 659 echo "</div>\n"; … … 656 665 case 'install': 657 666 if ( $status['url'] ) { 658 echo '<a class="button button-primary right" href="' . $status['url'] . '" target="_parent">' . __( 'Install Now' ) . '</a>';667 echo '<a data-slug="' . esc_attr( $api->slug ) . '" id="plugin_install_from_iframe" class="button button-primary right" href="' . $status['url'] . '" target="_parent">' . __( 'Install Now' ) . '</a>'; 659 668 } 660 669 break; … … 665 674 break; 666 675 case 'newer_installed': 676 /* translators: %s: Plugin version */ 667 677 echo '<a class="button button-primary right disabled">' . sprintf( __( 'Newer Version (%s) Installed'), $status['version'] ) . '</a>'; 668 678 break; -
trunk/src/wp-admin/includes/theme.php
r37488 r37714 173 173 if ( ! current_user_can('update_themes') ) { 174 174 /* translators: 1: theme name, 2: theme details URL, 3: accessibility text, 4: version number */ 175 $html = sprintf( '<p><strong>' . __( 'There is a new version of %1$s available. <a href="%2$s" class="thickbox " aria-label="%3$s">View version %4$s details</a>.' ) . '</strong></p>',175 $html = sprintf( '<p><strong>' . __( 'There is a new version of %1$s available. <a href="%2$s" class="thickbox open-plugin-details-modal" aria-label="%3$s">View version %4$s details</a>.' ) . '</strong></p>', 176 176 $theme_name, 177 177 esc_url( $details_url ), … … 182 182 } elseif ( empty( $update['package'] ) ) { 183 183 /* translators: 1: theme name, 2: theme details URL, 3: accessibility text, 4: version number */ 184 $html = sprintf( '<p><strong>' . __( 'There is a new version of %1$s available. <a href="%2$s" class="thickbox " aria-label="%3$s">View version %4$s details</a>. <em>Automatic update is unavailable for this theme.</em>' ) . '</strong></p>',184 $html = sprintf( '<p><strong>' . __( 'There is a new version of %1$s available. <a href="%2$s" class="thickbox open-plugin-details-modal" aria-label="%3$s">View version %4$s details</a>. <em>Automatic update is unavailable for this theme.</em>' ) . '</strong></p>', 185 185 $theme_name, 186 186 esc_url( $details_url ), … … 191 191 } else { 192 192 /* translators: 1: theme name, 2: theme details URL, 3: accessibility text, 4: version number, 5: update URL, 6: accessibility text */ 193 $html = sprintf( '<p><strong>' . __( 'There is a new version of %1$s available. <a href="%2$s" class="thickbox " aria-label="%3$s">View version %4$s details</a> or <a href="%5$s" aria-label="%6$s">update now</a>.' ) . '</strong></p>',193 $html = sprintf( '<p><strong>' . __( 'There is a new version of %1$s available. <a href="%2$s" class="thickbox open-plugin-details-modal" aria-label="%3$s">View version %4$s details</a> or <a href="%5$s" aria-label="%6$s" id="update-theme" data-slug="%7$s">update now</a>.' ) . '</strong></p>', 194 194 $theme_name, 195 195 esc_url( $details_url ), … … 199 199 $update_url, 200 200 /* translators: %s: theme name */ 201 esc_attr( sprintf( __( 'Update %s now' ), $theme_name ) ) 201 esc_attr( sprintf( __( 'Update %s now' ), $theme_name ) ), 202 $stylesheet 202 203 ); 203 204 } -
trunk/src/wp-admin/includes/update.php
r37675 r37714 330 330 331 331 /** 332 * 333 * @param string $file 334 * @param array $plugin_data 332 * Displays update information for a plugin. 333 * 334 * @param string $file Plugin basename. 335 * @param array $plugin_data Plugin information. 335 336 * @return false|void 336 337 */ 337 338 function wp_plugin_update_row( $file, $plugin_data ) { 338 339 $current = get_site_transient( 'update_plugins' ); 339 if ( !isset( $current->response[ $file ] ) ) 340 return false; 341 342 $r = $current->response[ $file ]; 343 344 $plugins_allowedtags = array('a' => array('href' => array(),'title' => array()),'abbr' => array('title' => array()),'acronym' => array('title' => array()),'code' => array(),'em' => array(),'strong' => array()); 345 $plugin_name = wp_kses( $plugin_data['Name'], $plugins_allowedtags ); 346 347 $details_url = self_admin_url('plugin-install.php?tab=plugin-information&plugin=' . $r->slug . '§ion=changelog&TB_iframe=true&width=600&height=800'); 348 349 $wp_list_table = _get_list_table('WP_Plugins_List_Table'); 350 351 if ( is_network_admin() || !is_multisite() ) { 340 if ( ! isset( $current->response[ $file ] ) ) { 341 return false; 342 } 343 344 $response = $current->response[ $file ]; 345 346 $plugins_allowedtags = array( 347 'a' => array( 'href' => array(), 'title' => array() ), 348 'abbr' => array( 'title' => array() ), 349 'acronym' => array( 'title' => array() ), 350 'code' => array(), 351 'em' => array(), 352 'strong' => array(), 353 ); 354 355 $plugin_name = wp_kses( $plugin_data['Name'], $plugins_allowedtags ); 356 $details_url = self_admin_url( 'plugin-install.php?tab=plugin-information&plugin=' . $response->slug . '§ion=changelog&TB_iframe=true&width=600&height=800' ); 357 358 /** @var WP_Plugins_List_Table $wp_list_table */ 359 $wp_list_table = _get_list_table( 'WP_Plugins_List_Table' ); 360 361 if ( is_network_admin() || ! is_multisite() ) { 352 362 if ( is_network_admin() ) { 353 $active_class = is_plugin_active_for_network( $file ) ? ' active' : '';363 $active_class = is_plugin_active_for_network( $file ) ? ' active' : ''; 354 364 } else { 355 365 $active_class = is_plugin_active( $file ) ? ' active' : ''; 356 366 } 357 367 358 echo '<tr class="plugin-update-tr' . $active_class . '" id="' . esc_attr( $r ->slug . '-update' ) . '" data-slug="' . esc_attr( $r->slug ) . '" data-plugin="' . esc_attr( $file ) . '"><td colspan="' . esc_attr( $wp_list_table->get_column_count() ) . '" class="plugin-update colspanchange"><div class="update-message">';368 echo '<tr class="plugin-update-tr' . $active_class . '" id="' . esc_attr( $response->slug . '-update' ) . '" data-slug="' . esc_attr( $response->slug ) . '" data-plugin="' . esc_attr( $file ) . '"><td colspan="' . esc_attr( $wp_list_table->get_column_count() ) . '" class="plugin-update colspanchange"><div class="update-message notice inline notice-warning notice-alt"><p>'; 359 369 360 370 if ( ! current_user_can( 'update_plugins' ) ) { … … 364 374 esc_url( $details_url ), 365 375 /* translators: 1: plugin name, 2: version number */ 366 esc_attr( sprintf( __( 'View %1$s version %2$s details' ), $plugin_name, $r ->new_version ) ),367 $r ->new_version376 esc_attr( sprintf( __( 'View %1$s version %2$s details' ), $plugin_name, $response->new_version ) ), 377 $response->new_version 368 378 ); 369 } elseif ( empty( $r ->package ) ) {379 } elseif ( empty( $response->package ) ) { 370 380 /* translators: 1: plugin name, 2: details URL, 3: accessibility text, 4: version number */ 371 381 printf( __( 'There is a new version of %1$s available. <a href="%2$s" class="thickbox open-plugin-details-modal" aria-label="%3$s">View version %4$s details</a>. <em>Automatic update is unavailable for this plugin.</em>' ), … … 373 383 esc_url( $details_url ), 374 384 /* translators: 1: plugin name, 2: version number */ 375 esc_attr( sprintf( __( 'View %1$s version %2$s details' ), $plugin_name, $r ->new_version ) ),376 $r ->new_version385 esc_attr( sprintf( __( 'View %1$s version %2$s details' ), $plugin_name, $response->new_version ) ), 386 $response->new_version 377 387 ); 378 388 } else { … … 382 392 esc_url( $details_url ), 383 393 /* translators: 1: plugin name, 2: version number */ 384 esc_attr( sprintf( __( 'View %1$s version %2$s details' ), $plugin_name, $r ->new_version ) ),385 $r ->new_version,394 esc_attr( sprintf( __( 'View %1$s version %2$s details' ), $plugin_name, $response->new_version ) ), 395 $response->new_version, 386 396 wp_nonce_url( self_admin_url( 'update.php?action=upgrade-plugin&plugin=' ) . $file, 'upgrade-plugin_' . $file ), 387 397 /* translators: %s: plugin name */ … … 389 399 ); 390 400 } 401 391 402 /** 392 403 * Fires at the end of the update message container in each … … 401 412 * An array of plugin metadata. 402 413 * 403 * @type string $name 404 * @type string $plugin_uri 405 * @type string $version 406 * @type string $description 407 * @type string $author 408 * @type string $author_uri 409 * @type string $text_domain 410 * @type string $domain_path 411 * @type bool $network 412 * @type string $title 413 * @type string $author_name 414 * @type bool $update 415 416 * @param array $r{417 418 419 * @type int $idPlugin ID.420 * @type string $slugPlugin slug.421 * @type string $new_versionNew plugin version.422 * @type string $urlPlugin URL.423 * @type string $packagePlugin update package URL.424 414 * @type string $name The human-readable name of the plugin. 415 * @type string $plugin_uri Plugin URI. 416 * @type string $version Plugin version. 417 * @type string $description Plugin description. 418 * @type string $author Plugin author. 419 * @type string $author_uri Plugin author URI. 420 * @type string $text_domain Plugin text domain. 421 * @type string $domain_path Relative path to the plugin's .mo file(s). 422 * @type bool $network Whether the plugin can only be activated network wide. 423 * @type string $title The human-readable title of the plugin. 424 * @type string $author_name Plugin author's name. 425 * @type bool $update Whether there's an available update. Default null. 426 * } 427 * @param array $response { 428 * An array of metadata about the available plugin update. 429 * 430 * @type int $id Plugin ID. 431 * @type string $slug Plugin slug. 432 * @type string $new_version New plugin version. 433 * @type string $url Plugin URL. 434 * @type string $package Plugin update package URL. 435 * } 425 436 */ 426 do_action( "in_plugin_update_message-{$file}", $plugin_data, $r );427 428 echo '</ div></td></tr>';437 do_action( "in_plugin_update_message-{$file}", $plugin_data, $response ); 438 439 echo '</p></div></td></tr>'; 429 440 } 430 441 } … … 467 478 468 479 /** 469 * 470 * @param string $theme_key 471 * @param WP_Theme $theme 480 * Displays update information for a theme. 481 * 482 * @param string $theme_key Theme stylesheet. 483 * @param WP_Theme $theme Theme object. 472 484 * @return false|void 473 485 */ 474 486 function wp_theme_update_row( $theme_key, $theme ) { 475 487 $current = get_site_transient( 'update_themes' ); 476 if ( !isset( $current->response[ $theme_key ] ) ) 477 return false; 478 479 $r = $current->response[ $theme_key ]; 480 481 $theme_name = $theme['Name']; 482 483 $details_url = add_query_arg( array( 'TB_iframe' => 'true', 'width' => 1024, 'height' => 800 ), $current->response[ $theme_key ]['url'] ); 484 485 $wp_list_table = _get_list_table('WP_MS_Themes_List_Table'); 486 487 $active = $theme->is_allowed( 'network' ) ? ' active': ''; 488 489 echo '<tr class="plugin-update-tr' . $active . '" id="' . esc_attr( $theme->get_stylesheet() . '-update' ) . '" data-slug="' . esc_attr( $theme->get_stylesheet() ) . '"><td colspan="' . $wp_list_table->get_column_count() . '" class="plugin-update colspanchange"><div class="update-message">'; 490 if ( ! current_user_can('update_themes') ) { 488 489 if ( ! isset( $current->response[ $theme_key ] ) ) { 490 return false; 491 } 492 493 $response = $current->response[ $theme_key ]; 494 495 $details_url = add_query_arg( array( 496 'TB_iframe' => 'true', 497 'width' => 1024, 498 'height' => 800, 499 ), $current->response[ $theme_key ]['url'] ); 500 501 /** @var WP_MS_Themes_List_Table $wp_list_table */ 502 $wp_list_table = _get_list_table( 'WP_MS_Themes_List_Table' ); 503 504 $active = $theme->is_allowed( 'network' ) ? ' active' : ''; 505 506 echo '<tr class="plugin-update-tr' . $active . '" id="' . esc_attr( $theme->get_stylesheet() . '-update' ) . '" data-slug="' . esc_attr( $theme->get_stylesheet() ) . '"><td colspan="' . $wp_list_table->get_column_count() . '" class="plugin-update colspanchange"><div class="update-message notice inline notice-warning notice-alt"><p>'; 507 if ( ! current_user_can( 'update_themes' ) ) { 491 508 /* translators: 1: theme name, 2: details URL, 3: accessibility text, 4: version number */ 492 509 printf( __( 'There is a new version of %1$s available. <a href="%2$s" class="thickbox open-plugin-details-modal" aria-label="%3$s">View version %4$s details</a>.'), 493 $theme _name,510 $theme['Name'], 494 511 esc_url( $details_url ), 495 512 /* translators: 1: theme name, 2: version number */ 496 esc_attr( sprintf( __( 'View %1$s version %2$s details' ), $theme _name, $r['new_version'] ) ),497 $r ['new_version']513 esc_attr( sprintf( __( 'View %1$s version %2$s details' ), $theme['Name'], $response['new_version'] ) ), 514 $response['new_version'] 498 515 ); 499 } elseif ( empty( $r ['package'] ) ) {516 } elseif ( empty( $response['package'] ) ) { 500 517 /* translators: 1: theme name, 2: details URL, 3: accessibility text, 4: version number */ 501 518 printf( __( 'There is a new version of %1$s available. <a href="%2$s" class="thickbox open-plugin-details-modal" aria-label="%3$s">View version %4$s details</a>. <em>Automatic update is unavailable for this theme.</em>' ), 502 $theme _name,519 $theme['Name'], 503 520 esc_url( $details_url ), 504 521 /* translators: 1: theme name, 2: version number */ 505 esc_attr( sprintf( __( 'View %1$s version %2$s details' ), $theme _name, $r['new_version'] ) ),506 $r ['new_version']522 esc_attr( sprintf( __( 'View %1$s version %2$s details' ), $theme['Name'], $response['new_version'] ) ), 523 $response['new_version'] 507 524 ); 508 525 } else { 509 526 /* translators: 1: theme name, 2: details URL, 3: accessibility text, 4: version number, 5: update URL, 6: accessibility text */ 510 527 printf( __( 'There is a new version of %1$s available. <a href="%2$s" class="thickbox open-plugin-details-modal" aria-label="%3$s">View version %4$s details</a> or <a href="%5$s" class="update-link" aria-label="%6$s">update now</a>.' ), 511 $theme _name,528 $theme['Name'], 512 529 esc_url( $details_url ), 513 530 /* translators: 1: theme name, 2: version number */ 514 esc_attr( sprintf( __( 'View %1$s version %2$s details' ), $theme _name, $r['new_version'] ) ),515 $r ['new_version'],531 esc_attr( sprintf( __( 'View %1$s version %2$s details' ), $theme['Name'], $response['new_version'] ) ), 532 $response['new_version'], 516 533 wp_nonce_url( self_admin_url( 'update.php?action=upgrade-theme&theme=' ) . $theme_key, 'upgrade-theme_' . $theme_key ), 517 534 /* translators: %s: theme name */ 518 esc_attr( sprintf( __( 'Update %s now' ), $theme _name) )535 esc_attr( sprintf( __( 'Update %s now' ), $theme['Name'] ) ) 519 536 ); 520 537 } 538 521 539 /** 522 540 * Fires at the end of the update message container in each … … 528 546 * @since 3.1.0 529 547 * 530 * @param WP_Theme $theme The WP_Theme object.531 * @param array $r {548 * @param WP_Theme $theme The WP_Theme object. 549 * @param array $response { 532 550 * An array of metadata about the available theme update. 533 551 * … … 537 555 * } 538 556 */ 539 do_action( "in_theme_update_message-{$theme_key}", $theme, $r );540 541 echo '</ div></td></tr>';557 do_action( "in_theme_update_message-{$theme_key}", $theme, $response ); 558 559 echo '</p></div></td></tr>'; 542 560 } 543 561 … … 578 596 echo "<div class='update-nag'>$msg</div>"; 579 597 } 598 599 /** 600 * Prints the JavaScript templates for update admin notices. 601 * 602 * Template takes one argument with four values: 603 * 604 * param {object} data { 605 * Arguments for admin notice. 606 * 607 * @type string id ID of the notice. 608 * @type string className Class names for the notice. 609 * @type string message The notice's message. 610 * @type string type The type of update the notice is for. Either 'plugin' or 'theme'. 611 * } 612 * 613 * @since 4.6.0 614 */ 615 function wp_print_admin_notice_templates() { 616 ?> 617 <script id="tmpl-wp-updates-admin-notice" type="text/html"> 618 <div <# if ( data.id ) { #>id="{{ data.id }}"<# } #> class="notice {{ data.className }}"><p>{{{ data.message }}}</p></div> 619 </script> 620 <script id="tmpl-wp-bulk-updates-admin-notice" type="text/html"> 621 <div id="{{ data.id }}" class="notice <# if ( data.errors ) { #>notice-error<# } else { #>notice-success<# } #>"> 622 <p> 623 <# if ( data.successes ) { #> 624 <# if ( 1 === data.successes ) { #> 625 <# if ( 'plugin' === data.type ) { #> 626 <?php 627 /* translators: %s: Number of plugins */ 628 printf( __( '%s plugin successfully updated.' ), '{{ data.successes }}' ); 629 ?> 630 <# } else { #> 631 <?php 632 /* translators: %s: Number of themes */ 633 printf( __( '%s theme successfully updated.' ), '{{ data.successes }}' ); 634 ?> 635 <# } #> 636 <# } else { #> 637 <# if ( 'plugin' === data.type ) { #> 638 <?php 639 /* translators: %s: Number of plugins */ 640 printf( __( '%s plugins successfully updated.' ), '{{ data.successes }}' ); 641 ?> 642 <# } else { #> 643 <?php 644 /* translators: %s: Number of themes */ 645 printf( __( '%s themes successfully updated.' ), '{{ data.successes }}' ); 646 ?> 647 <# } #> 648 <# } #> 649 <# } #> 650 <# if ( data.errors ) { #> 651 <# if ( 1 === data.errors ) { #> 652 <button class="button-link"> 653 <?php 654 /* translators: %s: Number of failures */ 655 printf( __( '%s failure.' ), '{{ data.errors }}' ); 656 ?> 657 </button> 658 <# } else { #> 659 <button class="button-link"> 660 <?php 661 /* translators: %s: Number of failures */ 662 printf( __( '%s failures.' ), '{{ data.errors }}' ); 663 ?> 664 </button> 665 <# } #> 666 <# } #> 667 </p> 668 <# if ( data.errors ) { #> 669 <ul class="hidden"> 670 <# _.each( data.errorMessages, function( errorMessage ) { #> 671 <li>{{ errorMessage }}</li> 672 <# } ); #> 673 </ul> 674 <# } #> 675 </div> 676 </script> 677 <?php 678 } 679 680 /** 681 * Prints the JavaScript templates for update and deletion rows in list tables. 682 * 683 * The update template takes one argument with four values: 684 * 685 * param {object} data { 686 * Arguments for the update row 687 * 688 * @type string slug Plugin slug. 689 * @type string plugin Plugin base name. 690 * @type string colspan The number of table columns this row spans. 691 * @type string content The row content. 692 * } 693 * 694 * The delete template takes one argument with four values: 695 * 696 * param {object} data { 697 * Arguments for the update row 698 * 699 * @type string slug Plugin slug. 700 * @type string plugin Plugin base name. 701 * @type string name Plugin name. 702 * @type string colspan The number of table columns this row spans. 703 * } 704 * 705 * @since 4.6.0 706 */ 707 function wp_print_update_row_templates() { 708 ?> 709 <script id="tmpl-item-update-row" type="text/template"> 710 <tr class="plugin-update-tr update" id="{{ data.slug }}-update" data-slug="{{ data.slug }}" <# if ( data.plugin ) { #>data-plugin="{{ data.plugin }}"<# } #>> 711 <td colspan="{{ data.colspan }}" class="plugin-update colspanchange"> 712 {{{ data.content }}} 713 </td> 714 </tr> 715 </script> 716 <script id="tmpl-item-deleted-row" type="text/template"> 717 <tr class="plugin-deleted-tr inactive deleted" id="{{ data.slug }}-deleted" data-slug="{{ data.slug }}" <# if ( data.plugin ) { #>data-plugin="{{ data.plugin }}"<# } #>> 718 <td colspan="{{ data.colspan }}" class="plugin-update colspanchange"> 719 <?php 720 printf( 721 /* translators: %s: Plugin or Theme name */ 722 __( '%s was successfully deleted.' ), 723 '<strong>{{{ data.name }}}</strong>' 724 ); 725 ?> 726 </td> 727 </tr> 728 </script> 729 <?php 730 } -
trunk/src/wp-admin/js/common.js
r37431 r37714 422 422 } 423 423 424 $document.on( 'wp-plugin-update-error', function() { 425 makeNoticesDismissible(); 426 }); 424 $document.on( 'wp-updates-notice-added wp-plugin-install-error wp-plugin-update-error wp-plugin-delete-error wp-theme-install-error wp-theme-delete-error', makeNoticesDismissible ); 427 425 428 426 // Init screen meta -
trunk/src/wp-admin/js/theme.js
r37221 r37714 376 376 'touchend': themes.isInstall ? 'preview': 'expand', 377 377 'keyup': 'addFocus', 378 'touchmove': 'preventExpand' 378 'touchmove': 'preventExpand', 379 'click .theme-install': 'installTheme', 380 'click .update-message': 'updateTheme' 379 381 }, 380 382 381 383 touchDrag: false, 384 385 initialize: function() { 386 this.model.on( 'change', this.render, this ); 387 }, 382 388 383 389 render: function() { 384 390 var data = this.model.toJSON(); 391 385 392 // Render themes using the html template 386 393 this.$el.html( this.html( data ) ).attr({ 387 394 tabindex: 0, 388 'aria-describedby' : data.id + '-action ' + data.id + '-name' 395 'aria-describedby' : data.id + '-action ' + data.id + '-name', 396 'data-slug': data.id 389 397 }); 390 398 … … 394 402 if ( this.model.get( 'displayAuthor' ) ) { 395 403 this.$el.addClass( 'display-author' ); 396 }397 398 if ( this.model.get( 'installed' ) ) {399 this.$el.addClass( 'is-installed' );400 404 } 401 405 }, … … 440 444 } 441 445 446 // Prevent the modal from showing when the user clicks one of the direct action buttons. 447 if ( $( event.target ).is( '.theme-actions a, .update-message, .button-link, .notice-dismiss' ) ) { 448 return; 449 } 450 442 451 // Set focused theme to current element 443 452 themes.focusedTheme = this.$el; … … 462 471 463 472 // Allow direct link path to installing a theme. 464 if ( $( event.target ). hasClass( 'button-primary' )) {473 if ( $( event.target ).not( '.install-theme-preview' ).parents( '.theme-actions' ).length ) { 465 474 return; 466 475 } … … 580 589 $themeInstaller.find( '.next-theme' ).addClass( 'disabled' ); 581 590 } 591 }, 592 593 installTheme: function( event ) { 594 var _this = this; 595 596 event.preventDefault(); 597 598 wp.updates.maybeRequestFilesystemCredentials( event ); 599 600 $( document ).on( 'wp-install-theme-success', function( event, response ) { 601 if ( _this.model.get( 'id' ) === response.slug ) { 602 _this.model.set( { 'installed': true } ); 603 } 604 } ); 605 606 wp.updates.installTheme( { 607 slug: $( event.target ).data( 'slug' ) 608 } ); 609 }, 610 611 updateTheme: function( event ) { 612 var _this = this; 613 event.preventDefault(); 614 this.$el.off( 'click', '.update-message' ); 615 616 wp.updates.maybeRequestFilesystemCredentials( event ); 617 618 $( document ).on( 'wp-theme-update-success', function( event, response ) { 619 _this.model.off( 'change', _this.render, _this ); 620 if ( _this.model.get( 'id' ) === response.slug ) { 621 _this.model.set( { 622 hasUpdate: false, 623 version: response.newVersion 624 } ); 625 } 626 _this.model.on( 'change', _this.render, _this ); 627 } ); 628 629 wp.updates.updateTheme( { 630 slug: $( event.target ).parents( 'div.theme' ).first().data( 'slug' ) 631 } ); 582 632 } 583 633 }); … … 594 644 'click .delete-theme': 'deleteTheme', 595 645 'click .left': 'previousTheme', 596 'click .right': 'nextTheme' 646 'click .right': 'nextTheme', 647 'click #update-theme': 'updateTheme' 597 648 }, 598 649 … … 714 765 }, 715 766 716 // Confirmation dialog for deleting a theme 717 deleteTheme: function() { 718 return confirm( themes.data.settings.confirmDelete ); 767 updateTheme: function( event ) { 768 var _this = this; 769 event.preventDefault(); 770 771 wp.updates.maybeRequestFilesystemCredentials( event ); 772 773 $( document ).on( 'wp-theme-update-success', function( event, response ) { 774 if ( _this.model.get( 'id' ) === response.slug ) { 775 _this.model.set( { 776 hasUpdate: false, 777 version: response.newVersion 778 } ); 779 } 780 _this.render(); 781 } ); 782 783 wp.updates.updateTheme( { 784 slug: $( event.target ).data( 'slug' ) 785 } ); 786 }, 787 788 deleteTheme: function( event ) { 789 var _this = this, 790 _collection = _this.model.collection, 791 _themes = themes; 792 event.preventDefault(); 793 794 // Confirmation dialog for deleting a theme. 795 if ( ! window.confirm( wp.themes.data.settings.confirmDelete ) ) { 796 return; 797 } 798 799 wp.updates.maybeRequestFilesystemCredentials( event ); 800 801 $( document ).one( 'wp-delete-theme-success', function( event, response ) { 802 _this.$el.find( '.close' ).trigger( 'click' ); 803 $( '[data-slug="' + response.slug + '"' ).css( { backgroundColor:'#faafaa' } ).fadeOut( 350, function() { 804 $( this ).remove(); 805 _themes.data.themes = _.without( _themes.data.themes, _.findWhere( _themes.data.themes, { id: response.slug } ) ); 806 807 $( '.wp-filter-search' ).val( '' ); 808 _collection.doSearch( '' ); 809 _collection.remove( _this.model ); 810 _collection.trigger( 'themes:update' ); 811 } ); 812 } ); 813 814 wp.updates.deleteTheme( { 815 slug: this.model.get( 'id' ) 816 } ); 719 817 }, 720 818 … … 760 858 'click .previous-theme': 'previousTheme', 761 859 'click .next-theme': 'nextTheme', 762 'keyup': 'keyEvent' 860 'keyup': 'keyEvent', 861 'click .theme-install': 'installTheme' 763 862 }, 764 863 … … 860 959 this.previousTheme(); 861 960 } 961 }, 962 963 installTheme: function( event ) { 964 var _this = this, 965 $target = $( event.target ); 966 event.preventDefault(); 967 968 if ( $target.hasClass( 'disabled' ) ) { 969 return; 970 } 971 972 wp.updates.maybeRequestFilesystemCredentials( event ); 973 974 $( document ).on( 'wp-install-theme-success', function() { 975 _this.model.set( { 'installed': true } ); 976 } ); 977 978 wp.updates.installTheme( { 979 slug: $target.data( 'slug' ) 980 } ); 862 981 } 863 982 }); … … 930 1049 } 931 1050 1051 // Bail if the filesystem credentials dialog is shown. 1052 if ( $( '#request-filesystem-credentials-dialog' ).is( ':visible' ) ) { 1053 return; 1054 } 1055 932 1056 // Pressing the right arrow key fires a theme:next event 933 1057 if ( event.keyCode === 39 ) { … … 1049 1173 // Uses the current model data 1050 1174 expand: function( id ) { 1051 var self = this ;1175 var self = this, $card, $modal; 1052 1176 1053 1177 // Set the current theme model … … 1067 1191 1068 1192 this.overlay.render(); 1193 1194 if ( this.model.get( 'hasUpdate' ) ) { 1195 $card = $( '[data-slug="' + this.model.id + '"]' ); 1196 $modal = $( this.overlay.el ); 1197 1198 if ( $card.find( '.updating-message' ).length ) { 1199 $modal.find( '.notice-warning h3' ).remove(); 1200 $modal.find( '.notice-warning' ) 1201 .removeClass( 'notice-large' ) 1202 .addClass( 'updating-message' ) 1203 .find( 'p' ).text( wp.updates.l10n.updating ); 1204 } else if ( $card.find( '.notice-error' ).length ) { 1205 $modal.find( '.notice-warning' ).remove(); 1206 } 1207 } 1208 1069 1209 this.$overlay.html( this.overlay.el ); 1070 1210 -
trunk/src/wp-admin/js/updates.js
r37467 r37714 1 /* global tb_remove */ 2 window.wp = window.wp || {}; 3 4 (function( $, wp, pagenow ) { 1 /** 2 * Functions for ajaxified updates, deletions and installs inside the WordPress admin. 3 * 4 * @version 4.2.0 5 * 6 * @package WordPress 7 * @subpackage Administration 8 */ 9 10 /* global pagenow */ 11 12 /** 13 * @param {jQuery} $ jQuery object. 14 * @param {object} wp WP object. 15 * @param {object} settings WP Updates settings. 16 * @param {string} settings.ajax_nonce AJAX nonce. 17 * @param {object} settings.l10n Translation strings. 18 * @param {object=} settings.plugins Base names of plugins in their different states. 19 * @param {Array} settings.plugins.all Base names of all plugins. 20 * @param {Array} settings.plugins.active Base names of active plugins. 21 * @param {Array} settings.plugins.inactive Base names of inactive plugins. 22 * @param {Array} settings.plugins.upgrade Base names of plugins with updates available. 23 * @param {Array} settings.plugins.recently_activated Base names of recently activated plugins. 24 * @param {object=} settings.totals Plugin/theme status information or null. 25 * @param {number} settings.totals.all Amount of all plugins or themes. 26 * @param {number} settings.totals.upgrade Amount of plugins or themes with updates available. 27 * @param {number} settings.totals.disabled Amount of disabled themes. 28 */ 29 (function( $, wp, settings ) { 30 var $document = $( document ); 31 32 wp = wp || {}; 33 34 /** 35 * The WP Updates object. 36 * 37 * @since 4.2.0 38 * 39 * @type {object} 40 */ 5 41 wp.updates = {}; 6 42 … … 10 46 * @since 4.2.0 11 47 * 12 * @ var string13 */ 14 wp.updates.ajaxNonce = window._wpUpdatesSettings.ajax_nonce;48 * @type {string} 49 */ 50 wp.updates.ajaxNonce = settings.ajax_nonce; 15 51 16 52 /** … … 19 55 * @since 4.2.0 20 56 * 21 * @var object 22 */ 23 wp.updates.l10n = window._wpUpdatesSettings.l10n; 57 * @type {object} 58 */ 59 wp.updates.l10n = settings.l10n; 60 61 /** 62 * Current search term. 63 * 64 * @since 4.6.0 65 * 66 * @type {string} 67 */ 68 wp.updates.searchTerm = ''; 24 69 25 70 /** … … 28 73 * @since 4.2.0 29 74 * 30 * @ var bool31 */ 32 wp.updates.shouldRequestFilesystemCredentials = null;75 * @type {bool} 76 */ 77 wp.updates.shouldRequestFilesystemCredentials = false; 33 78 34 79 /** … … 36 81 * 37 82 * @since 4.2.0 38 * 39 * @var object 83 * @since 4.6.0 Added `available` property to indicate whether credentials have been provided. 84 * 85 * @type {object} filesystemCredentials Holds filesystem credentials. 86 * @type {object} filesystemCredentials.ftp Holds FTP credentials. 87 * @type {string} filesystemCredentials.ftp.host FTP host. Default empty string. 88 * @type {string} filesystemCredentials.ftp.username FTP user name. Default empty string. 89 * @type {string} filesystemCredentials.ftp.password FTP password. Default empty string. 90 * @type {string} filesystemCredentials.ftp.connectionType Type of FTP connection. 'ssh', 'ftp', or 'ftps'. 91 * Default empty string. 92 * @type {object} filesystemCredentials.ssh Holds SSH credentials. 93 * @type {string} filesystemCredentials.ssh.publicKey The public key. Default empty string. 94 * @type {string} filesystemCredentials.ssh.privateKey The private key. Default empty string. 95 * @type {bool} filesystemCredentials.available Whether filesystem credentials have been provided. 96 * Default 'false'. 40 97 */ 41 98 wp.updates.filesystemCredentials = { 42 ftp: {43 host: null,44 username: null,45 password: null,46 connectionType: null99 ftp: { 100 host: '', 101 username: '', 102 password: '', 103 connectionType: '' 47 104 }, 48 ssh: { 49 publicKey: null, 50 privateKey: null 51 } 52 }; 53 54 /** 55 * Flag if we're waiting for an update to complete. 105 ssh: { 106 publicKey: '', 107 privateKey: '' 108 }, 109 available: false 110 }; 111 112 /** 113 * Whether we're waiting for an Ajax request to complete. 56 114 * 57 115 * @since 4.2.0 58 * 59 * @var bool 60 */ 61 wp.updates.updateLock = false; 62 63 /** 64 * * Flag if we've done an update successfully. 65 * 66 * @since 4.2.0 67 * 68 * @var bool 69 */ 70 wp.updates.updateDoneSuccessfully = false; 71 72 /** 116 * @since 4.6.0 More accurately named `ajaxLocked`. 117 * 118 * @type {bool} 119 */ 120 wp.updates.ajaxLocked = false; 121 122 /** 123 * Admin notice template. 124 * 125 * @since 4.6.0 126 * 127 * @type {function} A function that lazily-compiles the template requested. 128 */ 129 wp.updates.adminNotice = wp.template( 'wp-updates-admin-notice' ); 130 131 /** 132 * Update queue. 133 * 73 134 * If the user tries to update a plugin while an update is 74 135 * already happening, it can be placed in this queue to perform later. 75 136 * 76 137 * @since 4.2.0 77 * 78 * @var array 79 */ 80 wp.updates.updateQueue = []; 81 82 /** 83 * Store a jQuery reference to return focus to when exiting the request credentials modal. 138 * @since 4.6.0 More accurately named `queue`. 139 * 140 * @type {Array.object} 141 */ 142 wp.updates.queue = []; 143 144 /** 145 * Holds a jQuery reference to return focus to when exiting the request credentials modal. 84 146 * 85 147 * @since 4.2.0 86 148 * 87 * @var jQuery object 88 */ 89 wp.updates.$elToReturnFocusToFromCredentialsModal = null; 90 91 /** 92 * Decrement update counts throughout the various menus. 93 * 94 * @since 3.9.0 95 * 96 * @param {string} upgradeType 97 */ 98 wp.updates.decrementCount = function( upgradeType ) { 99 var count, 100 pluginCount, 101 $adminBarUpdateCount = $( '#wp-admin-bar-updates .ab-label' ), 102 $dashboardNavMenuUpdateCount = $( 'a[href="update-core.php"] .update-plugins' ), 103 $pluginsMenuItem = $( '#menu-plugins' ); 104 105 106 count = $adminBarUpdateCount.text(); 107 count = parseInt( count, 10 ) - 1; 108 if ( count < 0 || isNaN( count ) ) { 109 return; 110 } 111 $( '#wp-admin-bar-updates .ab-item' ).removeAttr( 'title' ); 112 $adminBarUpdateCount.text( count ); 113 114 115 $dashboardNavMenuUpdateCount.each( function( index, elem ) { 116 elem.className = elem.className.replace( /count-\d+/, 'count-' + count ); 117 } ); 118 $dashboardNavMenuUpdateCount.removeAttr( 'title' ); 119 $dashboardNavMenuUpdateCount.find( '.update-count' ).text( count ); 120 121 if ( 'plugin' === upgradeType ) { 122 pluginCount = $pluginsMenuItem.find( '.plugin-count' ).eq(0).text(); 123 pluginCount = parseInt( pluginCount, 10 ) - 1; 124 if ( pluginCount < 0 || isNaN( pluginCount ) ) { 125 return; 126 } 127 $pluginsMenuItem.find( '.plugin-count' ).text( pluginCount ); 128 $pluginsMenuItem.find( '.update-plugins' ).each( function( index, elem ) { 129 elem.className = elem.className.replace( /count-\d+/, 'count-' + pluginCount ); 149 * @type {jQuery} 150 */ 151 wp.updates.$elToReturnFocusToFromCredentialsModal = undefined; 152 153 /** 154 * Adds or updates an admin notice. 155 * 156 * @since 4.6.0 157 * 158 * @param {object} data 159 * @param {*=} data.selector Optional. Selector of an element to be replaced with the admin notice. 160 * @param {string=} data.id Optional. Unique id that will be used as the notice's id attribute. 161 * @param {string=} data.className Optional. Class names that will be used in the admin notice. 162 * @param {string=} data.message Optional. The message displayed in the notice. 163 * @param {number=} data.successes Optional. The amount of successful operations. 164 * @param {number=} data.errors Optional. The amount of failed operations. 165 * @param {Array=} data.errorMessages Optional. Error messages of failed operations. 166 * 167 */ 168 wp.updates.addAdminNotice = function( data ) { 169 var $notice = $( data.selector ), $adminNotice; 170 171 delete data.selector; 172 $adminNotice = wp.updates.adminNotice( data ); 173 174 // Check if this admin notice already exists. 175 if ( ! $notice.length ) { 176 $notice = $( '#' + data.id ); 177 } 178 179 if ( $notice.length ) { 180 $notice.replaceWith( $adminNotice ); 181 } else { 182 $( '.wrap' ).find( '> h1' ).after( $adminNotice ); 183 } 184 185 $document.trigger( 'wp-updates-notice-added' ); 186 }; 187 188 /** 189 * Handles Ajax requests to WordPress. 190 * 191 * @since 4.6.0 192 * 193 * @param {string} action The type of Ajax request ('update-plugin', 'install-theme', etc). 194 * @param {object} data Data that needs to be passed to the ajax callback. 195 * @return {$.promise} A jQuery promise that represents the request, 196 * decorated with an abort() method. 197 */ 198 wp.updates.ajax = function( action, data ) { 199 var options = {}; 200 201 if ( wp.updates.ajaxLocked ) { 202 wp.updates.queue.push( { 203 action: action, 204 data: data 130 205 } ); 131 206 132 if (pluginCount > 0 ) { 133 $( '.subsubsub .upgrade .count' ).text( '(' + pluginCount + ')' ); 134 } else { 135 $( '.subsubsub .upgrade' ).remove(); 136 } 137 } 138 }; 139 140 /** 141 * Send an Ajax request to the server to update a plugin. 142 * 143 * @since 4.2.0 144 * 145 * @param {string} plugin 146 * @param {string} slug 147 */ 148 wp.updates.updatePlugin = function( plugin, slug ) { 149 var $message, name, 150 $card = $( '.plugin-card-' + slug ); 151 152 if ( 'plugins' === pagenow || 'plugins-network' === pagenow ) { 153 $message = $( '[data-plugin="' + plugin + '"]' ).next().find( '.update-message' ); 154 } else if ( 'plugin-install' === pagenow ) { 155 $message = $card.find( '.update-now' ); 156 name = $message.data( 'name' ); 157 $message.attr( 'aria-label', wp.updates.l10n.updatingLabel.replace( '%s', name ) ); 158 // Remove previous error messages, if any. 159 $card.removeClass( 'plugin-card-update-failed' ).find( '.notice.notice-error' ).remove(); 160 } 161 162 $message.addClass( 'updating-message' ); 163 if ( $message.html() !== wp.updates.l10n.updating ){ 164 $message.data( 'originaltext', $message.html() ); 165 } 166 167 $message.text( wp.updates.l10n.updating ); 168 wp.a11y.speak( wp.updates.l10n.updatingMsg ); 169 170 if ( wp.updates.updateLock ) { 171 wp.updates.updateQueue.push( { 172 type: 'update-plugin', 173 data: { 174 plugin: plugin, 175 slug: slug 176 } 177 } ); 178 return; 179 } 180 181 wp.updates.updateLock = true; 182 183 var data = { 207 // Return a Deferred object so callbacks can always be registered. 208 return $.Deferred(); 209 } 210 211 wp.updates.ajaxLocked = true; 212 213 if ( data.success ) { 214 options.success = data.success; 215 delete data.success; 216 } 217 218 if ( data.error ) { 219 options.error = data.error; 220 delete data.error; 221 } 222 223 options.data = _.extend( data, { 224 action: action, 184 225 _ajax_nonce: wp.updates.ajaxNonce, 185 plugin: plugin,186 slug: slug,187 226 username: wp.updates.filesystemCredentials.ftp.username, 188 227 password: wp.updates.filesystemCredentials.ftp.password, … … 191 230 public_key: wp.updates.filesystemCredentials.ssh.publicKey, 192 231 private_key: wp.updates.filesystemCredentials.ssh.privateKey 193 }; 194 195 wp.ajax.post( 'update-plugin', data ) 196 .done( wp.updates.updateSuccess ) 197 .fail( wp.updates.updateError ); 198 }; 199 200 /** 201 * On a successful plugin update, update the UI with the result. 232 } ); 233 234 return wp.ajax.send( options ).always( wp.updates.ajaxAlways ); 235 }; 236 237 /** 238 * Actions performed after every Ajax request. 239 * 240 * @since 4.6.0 241 * 242 * @param {object} response 243 * @param {array=} response.debug Optional. Debug information. 244 * @param {string=} response.errorCode Optional. Error code for an error that occurred. 245 */ 246 wp.updates.ajaxAlways = function( response ) { 247 if ( ! response.errorCode && 'unable_to_connect_to_filesystem' !== response.errorCode ) { 248 wp.updates.ajaxLocked = false; 249 wp.updates.queueChecker(); 250 } 251 252 if ( 'undefined' !== typeof response.debug ) { 253 _.map( response.debug, function( message ) { 254 window.console.log( $( '<p />' ).html( message ).text() ); 255 } ); 256 } 257 }; 258 259 /** 260 * Decrements the update counts throughout the various menus. 261 * 262 * This includes the toolbar, the "Updates" menu item and the menu items 263 * for plugins and themes. 264 * 265 * @since 3.9.0 266 * 267 * @param {string} type The type of item that was updated or deleted. 268 * Can be 'plugin', 'theme'. 269 */ 270 wp.updates.decrementCount = function( type ) { 271 var $adminBarUpdates = $( '#wp-admin-bar-updates' ), 272 $dashboardNavMenuUpdateCount = $( 'a[href="update-core.php"] .update-plugins' ), 273 count = $adminBarUpdates.find( '.ab-label' ).text(), 274 $menuItem, $itemCount, itemCount; 275 276 count = parseInt( count, 10 ) - 1; 277 278 if ( count < 0 || isNaN( count ) ) { 279 return; 280 } 281 282 $adminBarUpdates.find( '.ab-item' ).removeAttr( 'title' ); 283 $adminBarUpdates.find( '.ab-label' ).text( count ); 284 285 // Remove the update count from the toolbar if it's zero. 286 if ( ! count ) { 287 $adminBarUpdates.find( '.ab-label' ).parents( 'li' ).remove(); 288 } 289 290 // Update the "Updates" menu item. 291 $dashboardNavMenuUpdateCount.each( function( index, element ) { 292 element.className = element.className.replace( /count-\d+/, 'count-' + count ); 293 } ); 294 295 $dashboardNavMenuUpdateCount.removeAttr( 'title' ); 296 $dashboardNavMenuUpdateCount.find( '.update-count' ).text( count ); 297 298 switch ( type ) { 299 case 'plugin': 300 $menuItem = $( '#menu-plugins' ); 301 $itemCount = $menuItem.find( '.plugin-count' ); 302 break; 303 304 case 'theme': 305 $menuItem = $( '#menu-appearance' ); 306 $itemCount = $menuItem.find( '.theme-count' ); 307 break; 308 309 default: 310 window.console.error( '"%s" is not white-listed to have its count decremented.', type ); 311 return; 312 } 313 314 // Decrement the counter of the other menu items. 315 if ( $itemCount ) { 316 itemCount = $itemCount.eq( 0 ).text(); 317 itemCount = parseInt( itemCount, 10 ) - 1; 318 } 319 320 if ( itemCount < 0 || isNaN( itemCount ) ) { 321 return; 322 } 323 324 if ( itemCount > 0 ) { 325 $( '.subsubsub .upgrade .count' ).text( '(' + itemCount + ')' ); 326 327 $itemCount.text( itemCount ); 328 $menuItem.find( '.update-plugins' ).each( function( index, element ) { 329 element.className = element.className.replace( /count-\d+/, 'count-' + itemCount ); 330 } ); 331 } else { 332 $( '.subsubsub .upgrade' ).remove(); 333 $menuItem.find( '.update-plugins' ).remove(); 334 } 335 }; 336 337 /** 338 * Sends an Ajax request to the server to update a plugin. 202 339 * 203 340 * @since 4.2.0 204 * 205 * @param {object} response 206 */ 207 wp.updates.updateSuccess = function( response ) { 208 var $updateMessage, name, $pluginRow, newText; 341 * @since 4.6.0 More accurately named `updatePlugin`. 342 * 343 * @param {object} args Arguments. 344 * @param {string} args.plugin Plugin basename. 345 * @param {string} args.slug Plugin slug. 346 * @param {updatePluginSuccess=} args.success Optional. Success callback. Default: wp.updates.updatePluginSuccess 347 * @param {updatePluginError=} args.error Optional. Error callback. Default: wp.updates.updatePluginError 348 * @return {$.promise} A jQuery promise that represents the request, 349 * decorated with an abort() method. 350 */ 351 wp.updates.updatePlugin = function( args ) { 352 var $updateRow, $card, $message, message; 353 354 args = _.extend( { 355 success: wp.updates.updatePluginSuccess, 356 error: wp.updates.updatePluginError 357 }, args ); 358 209 359 if ( 'plugins' === pagenow || 'plugins-network' === pagenow ) { 210 $pluginRow = $( '[data-plugin="' + response.plugin + '"]' ).first(); 211 $updateMessage = $pluginRow.next().find( '.update-message' ); 212 $pluginRow.addClass( 'updated' ).removeClass( 'update' ); 360 $updateRow = $( 'tr[data-plugin="' + args.plugin + '"]' ); 361 $message = $updateRow.find( '.update-message' ).addClass( 'updating-message' ).find( 'p' ); 362 message = wp.updates.l10n.updatingLabel.replace( '%s', $updateRow.find( '.plugin-title strong' ).text() ); 363 } else if ( 'plugin-install' === pagenow || 'plugin-install-network' === pagenow ) { 364 $card = $( '.plugin-card-' + args.slug ); 365 $message = $card.find( '.update-now' ).addClass( 'updating-message' ); 366 message = wp.updates.l10n.updatingLabel.replace( '%s', $message.data( 'name' ) ); 367 368 // Remove previous error messages, if any. 369 $card.removeClass( 'plugin-card-update-failed' ).find( '.notice.notice-error' ).remove(); 370 } 371 372 if ( $message.html() !== wp.updates.l10n.updating ) { 373 $message.data( 'originaltext', $message.html() ); 374 } 375 376 $message 377 .attr( 'aria-label', message ) 378 .text( wp.updates.l10n.updating ); 379 380 $document.trigger( 'wp-plugin-updating' ); 381 382 return wp.updates.ajax( 'update-plugin', args ); 383 }; 384 385 /** 386 * Updates the UI appropriately after a successful plugin update. 387 * 388 * @since 4.2.0 389 * @since 4.6.0 More accurately named `updatePluginSuccess`. 390 * 391 * @typedef {object} updatePluginSuccess 392 * @param {object} response Response from the server. 393 * @param {string} response.slug Slug of the plugin to be updated. 394 * @param {string} response.plugin Basename of the plugin to be updated. 395 * @param {string} response.pluginName Name of the plugin to be updated. 396 * @param {string} response.oldVersion Old version of the plugin. 397 * @param {string} response.newVersion New version of the plugin. 398 */ 399 wp.updates.updatePluginSuccess = function( response ) { 400 var $pluginRow, $updateMessage, newText; 401 402 if ( 'plugins' === pagenow || 'plugins-network' === pagenow ) { 403 $pluginRow = $( 'tr[data-plugin="' + response.plugin + '"]' ) 404 .removeClass( 'update' ) 405 .addClass( 'updated' ); 406 $updateMessage = $pluginRow.find( '.update-message' ) 407 .removeClass( 'updating-message notice-warning' ) 408 .addClass( 'updated-message notice-success' ).find( 'p' ); 213 409 214 410 // Update the version number in the row. 215 newText = $pluginRow.find('.plugin-version-author-uri').html().replace( response.oldVersion, response.newVersion ); 216 $pluginRow.find('.plugin-version-author-uri').html( newText ); 217 218 // Add updated class to update message parent tr 219 $pluginRow.next().addClass( 'updated' ); 220 } else if ( 'plugin-install' === pagenow ) { 221 $updateMessage = $( '.plugin-card-' + response.slug ).find( '.update-now' ); 222 $updateMessage.addClass( 'button-disabled' ); 223 name = $updateMessage.data( 'name' ); 224 $updateMessage.attr( 'aria-label', wp.updates.l10n.updatedLabel.replace( '%s', name ) ); 225 } 226 227 $updateMessage.removeClass( 'updating-message' ).addClass( 'updated-message' ); 228 $updateMessage.text( wp.updates.l10n.updated ); 229 wp.a11y.speak( wp.updates.l10n.updatedMsg ); 411 newText = $pluginRow.find( '.plugin-version-author-uri' ).html().replace( response.oldVersion, response.newVersion ); 412 $pluginRow.find( '.plugin-version-author-uri' ).html( newText ); 413 } else if ( 'plugin-install' === pagenow || 'plugin-install-network' === pagenow ) { 414 $updateMessage = $( '.plugin-card-' + response.slug ).find( '.update-now' ) 415 .removeClass( 'updating-message' ) 416 .addClass( 'button-disabled updated-message' ); 417 } 418 419 $updateMessage 420 .attr( 'aria-label', wp.updates.l10n.updatedLabel.replace( '%s', response.pluginName ) ) 421 .text( wp.updates.l10n.updated ); 422 423 wp.a11y.speak( wp.updates.l10n.updatedMsg, 'polite' ); 230 424 231 425 wp.updates.decrementCount( 'plugin' ); 232 426 233 wp.updates.updateDoneSuccessfully = true; 234 235 /* 236 * The lock can be released since the update was successful, 237 * and any other updates can commence. 238 */ 239 wp.updates.updateLock = false; 240 241 $(document).trigger( 'wp-plugin-update-success', response ); 242 243 wp.updates.queueChecker(); 244 }; 245 246 247 /** 248 * On a plugin update error, update the UI appropriately. 427 $document.trigger( 'wp-plugin-update-success', response ); 428 }; 429 430 /** 431 * Updates the UI appropriately after a failed plugin update. 249 432 * 250 433 * @since 4.2.0 251 * 252 * @param {object} response253 * /254 wp.updates.updateError = function( response ) {255 var $card = $( '.plugin-card-' + response.slug ),256 $message,257 $button,258 name,259 error_message;260 261 wp.updates.updateDoneSuccessfully = false;262 263 if ( response.errorCode && response.errorCode == 'unable_to_connect_to_filesystem' && wp.updates.shouldRequestFilesystemCredentials ) { 264 wp.updates.credentialError( response, 'update-plugin' );434 * @since 4.6.0 More accurately named `updatePluginError`. 435 * 436 * @typedef {object} updatePluginError 437 * @param {object} response Response from the server. 438 * @param {string} response.slug Slug of the plugin to be updated. 439 * @param {string} response.plugin Basename of the plugin to be updated. 440 * @param {string=} response.pluginName Optional. Name of the plugin to be updated. 441 * @param {string} response.errorCode Error code for the error that occurred. 442 * @param {string} response.errorMessage The error that occurred. 443 */ 444 wp.updates.updatePluginError = function( response ) { 445 var $card, $message, errorMessage; 446 447 if ( ! wp.updates.isValidResponse( response, 'update' ) ) { 265 448 return; 266 449 } 267 450 268 error_message = wp.updates.l10n.updateFailed.replace( '%s', response.error ); 451 if ( wp.updates.maybeHandleCredentialError( response, 'update-plugin' ) ) { 452 return; 453 } 454 455 errorMessage = wp.updates.l10n.updateFailed.replace( '%s', response.errorMessage ); 269 456 270 457 if ( 'plugins' === pagenow || 'plugins-network' === pagenow ) { 271 $message = $( '[data-plugin="' + response.plugin + '"]' ).next().find( '.update-message' ); 272 $message.html( error_message ).removeClass( 'updating-message' ); 273 } else if ( 'plugin-install' === pagenow ) { 274 $button = $card.find( '.update-now' ); 275 name = $button.data( 'name' ); 276 277 $card 458 $message = $( 'tr[data-plugin="' + response.plugin + '"]' ).find( '.update-message' ); 459 $message.removeClass( 'updating-message notice-warning' ).addClass( 'notice-error' ).find( 'p' ).html( errorMessage ); 460 } else if ( 'plugin-install' === pagenow || 'plugin-install-network' === pagenow ) { 461 $card = $( '.plugin-card-' + response.slug ) 278 462 .addClass( 'plugin-card-update-failed' ) 279 .append( '<div class="notice notice-error is-dismissible"><p>' + error_message + '</p></div>' ); 280 281 $button 282 .attr( 'aria-label', wp.updates.l10n.updateFailedLabel.replace( '%s', name ) ) 283 .html( wp.updates.l10n.updateFailedShort ).removeClass( 'updating-message' ); 463 .append( wp.updates.adminNotice( { 464 className: 'update-message notice-error notice-alt is-dismissible', 465 message: errorMessage 466 } ) ); 467 468 $card.find( '.update-now' ) 469 .attr( 'aria-label', wp.updates.l10n.updateFailedLabel.replace( '%s', response.pluginName ) ) 470 .text( wp.updates.l10n.updateFailedShort ).removeClass( 'updating-message' ); 284 471 285 472 $card.on( 'click', '.notice.is-dismissible .notice-dismiss', function() { 473 286 474 // Use same delay as the total duration of the notice fadeTo + slideUp animation. 287 475 setTimeout( function() { … … 289 477 .removeClass( 'plugin-card-update-failed' ) 290 478 .find( '.column-name a' ).focus(); 479 480 $card.find( '.update-now' ) 481 .attr( 'aria-label', false ) 482 .text( wp.updates.l10n.updateNow ); 291 483 }, 200 ); 292 }); 293 } 294 295 wp.a11y.speak( error_message, 'assertive' ); 296 297 /* 298 * The lock can be released since this failure was 299 * after the credentials form. 300 */ 301 wp.updates.updateLock = false; 302 303 $(document).trigger( 'wp-plugin-update-error', response ); 304 305 wp.updates.queueChecker(); 306 }; 307 308 /** 309 * Show an error message in the request for credentials form. 310 * 311 * @param {string} message 484 } ); 485 } 486 487 wp.a11y.speak( errorMessage, 'assertive' ); 488 489 $document.trigger( 'wp-plugin-update-error', response ); 490 }; 491 492 /** 493 * Sends an Ajax request to the server to install a plugin. 494 * 495 * @since 4.6.0 496 * 497 * @param {object} args Arguments. 498 * @param {string} args.slug Plugin identifier in the WordPress.org Plugin repository. 499 * @param {installPluginSuccess=} args.success Optional. Success callback. Default: wp.updates.installPluginSuccess 500 * @param {installPluginError=} args.error Optional. Error callback. Default: wp.updates.installPluginError 501 * @return {$.promise} A jQuery promise that represents the request, 502 * decorated with an abort() method. 503 */ 504 wp.updates.installPlugin = function( args ) { 505 var $card = $( '.plugin-card-' + args.slug ), 506 $message = $card.find( '.install-now' ); 507 508 args = _.extend( { 509 success: wp.updates.installPluginSuccess, 510 error: wp.updates.installPluginError 511 }, args ); 512 513 if ( 'import' === pagenow ) { 514 $message = $( 'a[href*="' + args.slug + '"]' ); 515 } else { 516 $message.text( wp.updates.l10n.installing ); 517 } 518 519 $message.addClass( 'updating-message' ); 520 521 wp.a11y.speak( wp.updates.l10n.installingMsg, 'polite' ); 522 523 // Remove previous error messages, if any. 524 $card.removeClass( 'plugin-card-install-failed' ).find( '.notice.notice-error' ).remove(); 525 526 return wp.updates.ajax( 'install-plugin', args ); 527 }; 528 529 /** 530 * Updates the UI appropriately after a successful plugin install. 531 * 532 * @since 4.6.0 533 * 534 * @typedef {object} installPluginSuccess 535 * @param {object} response Response from the server. 536 * @param {string} response.slug Slug of the installed plugin. 537 * @param {string} response.pluginName Name of the installed plugin. 538 * @param {string} response.activateUrl URL to activate the just installed plugin. 539 */ 540 wp.updates.installPluginSuccess = function( response ) { 541 var $message = $( '.plugin-card-' + response.slug ).find( '.install-now' ); 542 543 $message 544 .removeClass( 'updating-message' ) 545 .addClass( 'updated-message installed button-disabled' ) 546 .text( wp.updates.l10n.installed ); 547 548 wp.a11y.speak( wp.updates.l10n.installedMsg, 'polite' ); 549 550 $document.trigger( 'wp-plugin-install-success', response ); 551 552 if ( response.activateUrl ) { 553 setTimeout( function() { 554 555 // Transform the 'Install' button into an 'Activate' button. 556 $message.removeClass( 'install-now installed button-disabled updated-message' ).addClass( 'activate-now button-primary' ) 557 .attr( 'href', response.activateUrl ) 558 .text( wp.updates.l10n.activatePlugin ); 559 }, 1000 ); 560 } 561 }; 562 563 /** 564 * Updates the UI appropriately after a failed plugin install. 565 * 566 * @since 4.6.0 567 * 568 * @typedef {object} installPluginError 569 * @param {object} response Response from the server. 570 * @param {string} response.slug Slug of the plugin to be installed. 571 * @param {string=} response.pluginName Optional. Name of the plugin to be installed. 572 * @param {string} response.errorCode Error code for the error that occurred. 573 * @param {string} response.errorMessage The error that occurred. 574 */ 575 wp.updates.installPluginError = function( response ) { 576 var $card = $( '.plugin-card-' + response.slug ), 577 $button = $card.find( '.install-now' ), 578 errorMessage; 579 580 if ( ! wp.updates.isValidResponse( response, 'install' ) ) { 581 return; 582 } 583 584 if ( wp.updates.maybeHandleCredentialError( response, 'install-plugin' ) ) { 585 return; 586 } 587 588 errorMessage = wp.updates.l10n.installFailed.replace( '%s', response.errorMessage ); 589 590 $card 591 .addClass( 'plugin-card-update-failed' ) 592 .append( '<div class="notice notice-error notice-alt is-dismissible"><p>' + errorMessage + '</p></div>' ); 593 594 $card.on( 'click', '.notice.is-dismissible .notice-dismiss', function() { 595 596 // Use same delay as the total duration of the notice fadeTo + slideUp animation. 597 setTimeout( function() { 598 $card 599 .removeClass( 'plugin-card-update-failed' ) 600 .find( '.column-name a' ).focus(); 601 }, 200 ); 602 } ); 603 604 $button 605 .removeClass( 'updating-message' ).addClass( 'button-disabled' ) 606 .attr( 'aria-label', wp.updates.l10n.installFailedLabel.replace( '%s', response.pluginName ) ) 607 .text( wp.updates.l10n.installFailedShort ); 608 609 wp.a11y.speak( errorMessage, 'assertive' ); 610 611 $document.trigger( 'wp-plugin-install-error', response ); 612 }; 613 614 /** 615 * Updates the UI appropriately after a successful importer install. 616 * 617 * @since 4.6.0 618 * 619 * @typedef {object} installImporterSuccess 620 * @param {object} response Response from the server. 621 * @param {string} response.slug Slug of the installed plugin. 622 * @param {string} response.pluginName Name of the installed plugin. 623 * @param {string} response.activateUrl URL to activate the just installed plugin. 624 */ 625 wp.updates.installImporterSuccess = function( response ) { 626 wp.updates.addAdminNotice( { 627 id: 'install-success', 628 className: 'notice-success is-dismissible', 629 message: wp.updates.l10n.importerInstalledMsg.replace( '%s', response.activateUrl + '&from=import' ) 630 } ); 631 632 $( 'a[href*="' + response.slug + '"]' ) 633 .removeClass( 'thickbox open-plugin-details-modal updating-message' ) 634 .off( 'click' ) 635 .attr( 'href', response.activateUrl + '&from=import' ) 636 .attr( 'title', wp.updates.l10n.activateImporter ); 637 638 wp.a11y.speak( wp.updates.l10n.installedMsg, 'polite' ); 639 640 $document.trigger( 'wp-installer-install-success', response ); 641 }; 642 643 /** 644 * Updates the UI appropriately after a failed importer install. 645 * 646 * @since 4.6.0 647 * 648 * @typedef {object} installImporterError 649 * @param {object} response Response from the server. 650 * @param {string} response.slug Slug of the plugin to be installed. 651 * @param {string=} response.pluginName Optional. Name of the plugin to be installed. 652 * @param {string} response.errorCode Error code for the error that occurred. 653 * @param {string} response.errorMessage The error that occurred. 654 */ 655 wp.updates.installImporterError = function( response ) { 656 var errorMessage = wp.updates.l10n.installFailed.replace( '%s', response.errorMessage ); 657 658 if ( ! wp.updates.isValidResponse( response, 'install' ) ) { 659 return; 660 } 661 662 if ( wp.updates.maybeHandleCredentialError( response, 'install-plugin' ) ) { 663 return; 664 } 665 666 wp.updates.addAdminNotice( { 667 id: response.errorCode, 668 className: 'notice-error is-dismissible', 669 message: errorMessage 670 } ); 671 672 $( 'a[href*="' + response.slug + '"]' ).removeClass( 'updating-message' ); 673 674 wp.a11y.speak( errorMessage, 'assertive' ); 675 676 $document.trigger( 'wp-importer-install-error', response ); 677 }; 678 679 /** 680 * Sends an Ajax request to the server to delete a plugin. 681 * 682 * @since 4.6.0 683 * 684 * @param {object} args Arguments. 685 * @param {string} args.plugin Basename of the plugin to be deleted. 686 * @param {string} args.slug Slug of the plugin to be deleted. 687 * @param {deletePluginSuccess=} args.success Optional. Success callback. Default: wp.updates.deletePluginSuccess 688 * @param {deletePluginError=} args.error Optional. Error callback. Default: wp.updates.deletePluginError 689 * @return {$.promise} A jQuery promise that represents the request, 690 * decorated with an abort() method. 691 */ 692 wp.updates.deletePlugin = function( args ) { 693 var $message = $( '[data-plugin="' + args.plugin + '"]' ).find( '.update-message p' ); 694 695 args = _.extend( { 696 success: wp.updates.deletePluginSuccess, 697 error: wp.updates.deletePluginError 698 }, args ); 699 700 if ( $message.html() !== wp.updates.l10n.updating ) { 701 $message.data( 'originaltext', $message.html() ); 702 } 703 704 wp.a11y.speak( wp.updates.l10n.deleting, 'polite' ); 705 706 return wp.updates.ajax( 'delete-plugin', args ); 707 }; 708 709 /** 710 * Updates the UI appropriately after a successful plugin deletion. 711 * 712 * @since 4.6.0 713 * 714 * @typedef {object} deletePluginSuccess 715 * @param {object} response Response from the server. 716 * @param {string} response.slug Slug of the plugin that was deleted. 717 * @param {string} response.plugin Base name of the plugin that was deleted. 718 * @param {string} response.pluginName Name of the plugin that was deleted. 719 */ 720 wp.updates.deletePluginSuccess = function( response ) { 721 722 // Removes the plugin and updates rows. 723 $( '[data-plugin="' + response.plugin + '"]' ).css( { backgroundColor: '#faafaa' } ).fadeOut( 350, function() { 724 var $form = $( '#bulk-action-form' ), 725 $views = $( '.subsubsub' ), 726 $pluginRow = $( this ), 727 columnCount = $form.find( 'thead th:not(.hidden), thead td' ).length, 728 pluginDeletedRow = wp.template( 'item-deleted-row' ), 729 /** @type {object} plugins Base names of plugins in their different states. */ 730 plugins = settings.plugins; 731 732 // Add a success message after deleting a plugin. 733 if ( ! $pluginRow.hasClass( 'plugin-update-tr' ) ) { 734 $pluginRow.after( 735 pluginDeletedRow( { 736 slug: response.slug, 737 plugin: response.plugin, 738 colspan: columnCount, 739 name: response.pluginName 740 } ) 741 ); 742 } 743 744 $pluginRow.remove(); 745 746 // Remove plugin from update count. 747 if ( -1 !== _.indexOf( plugins.upgrade, response.plugin ) ) { 748 plugins.upgrade = _.without( plugins.upgrade, response.plugin ); 749 wp.updates.decrementCount( 'plugin' ); 750 } 751 752 // Remove from views. 753 if ( -1 !== _.indexOf( plugins.inactive, response.plugin ) ) { 754 plugins.inactive = _.without( plugins.inactive, response.plugin ); 755 if ( plugins.inactive.length ) { 756 $views.find( '.inactive .count' ).text( '(' + plugins.inactive.length + ')' ); 757 } else { 758 $views.find( '.inactive' ).remove(); 759 } 760 } 761 762 if ( -1 !== _.indexOf( plugins.active, response.plugin ) ) { 763 plugins.active = _.without( plugins.active, response.plugin ); 764 if ( plugins.active.length ) { 765 $views.find( '.active .count' ).text( '(' + plugins.active.length + ')' ); 766 } else { 767 $views.find( '.active' ).remove(); 768 } 769 } 770 771 if ( -1 !== _.indexOf( plugins.recently_activated, response.plugin ) ) { 772 plugins.recently_activated = _.without( plugins.recently_activated, response.plugin ); 773 if ( plugins.recently_activated.length ) { 774 $views.find( '.recently_activated .count' ).text( '(' + plugins.recently_activated.length + ')' ); 775 } else { 776 $views.find( '.recently_activated' ).remove(); 777 } 778 } 779 780 plugins.all = _.without( plugins.all, response.plugin ); 781 782 if ( plugins.all.length ) { 783 $views.find( '.all .count' ).text( '(' + plugins.all.length + ')' ); 784 } else { 785 $form.find( '.tablenav' ).css( { visibility: 'hidden' } ); 786 $views.find( '.all' ).remove(); 787 788 if ( ! $form.find( 'tr.no-items' ).length ) { 789 $form.find( '#the-list' ).append( '<tr class="no-items"><td class="colspanchange" colspan="' + columnCount + '">' + wp.updates.l10n.noPlugins + '</td></tr>' ); 790 } 791 } 792 } ); 793 794 wp.a11y.speak( wp.updates.l10n.deleted, 'polite' ); 795 796 $document.trigger( 'wp-plugin-delete-success', response ); 797 }; 798 799 /** 800 * Updates the UI appropriately after a failed plugin deletion. 801 * 802 * @since 4.6.0 803 * 804 * @typedef {object} deletePluginError 805 * @param {object} response Response from the server. 806 * @param {string} response.slug Slug of the plugin to be deleted. 807 * @param {string} response.plugin Base name of the plugin to be deleted 808 * @param {string=} response.pluginName Optional. Name of the plugin to be deleted. 809 * @param {string} response.errorCode Error code for the error that occurred. 810 * @param {string} response.errorMessage The error that occurred. 811 */ 812 wp.updates.deletePluginError = function( response ) { 813 var $plugin = $( 'tr.inactive[data-plugin="' + response.plugin + '"]' ), 814 pluginUpdateRow = wp.template( 'item-update-row' ), 815 $pluginUpdateRow = $plugin.siblings( '[data-plugin="' + response.plugin + '"]' ), 816 noticeContent = wp.updates.adminNotice( { 817 className: 'update-message notice-error notice-alt', 818 message: response.errorMessage 819 } ); 820 821 if ( ! wp.updates.isValidResponse( response, 'delete' ) ) { 822 return; 823 } 824 825 if ( wp.updates.maybeHandleCredentialError( response, 'delete-plugin' ) ) { 826 return; 827 } 828 829 // Add a plugin update row if it doesn't exist yet. 830 if ( ! $pluginUpdateRow.length ) { 831 $plugin.addClass( 'update' ).after( 832 pluginUpdateRow( { 833 slug: response.slug, 834 plugin: response.plugin, 835 colspan: $( '#bulk-action-form' ).find( 'thead th:not(.hidden), thead td' ).length, 836 content: noticeContent 837 } ) 838 ); 839 } else { 840 841 // Remove previous error messages, if any. 842 $pluginUpdateRow.find( '.notice-error' ).remove(); 843 844 $pluginUpdateRow.find( '.plugin-update' ).append( noticeContent ); 845 } 846 847 $document.trigger( 'wp-plugin-delete-error', response ); 848 }; 849 850 /** 851 * Sends an Ajax request to the server to update a theme. 852 * 853 * @since 4.6.0 854 * 855 * @param {object} args Arguments. 856 * @param {string} args.slug Theme stylesheet. 857 * @param {updateThemeSuccess=} args.success Optional. Success callback. Default: wp.updates.updateThemeSuccess 858 * @param {updateThemeError=} args.error Optional. Error callback. Default: wp.updates.updateThemeError 859 * @return {$.promise} A jQuery promise that represents the request, 860 * decorated with an abort() method. 861 */ 862 wp.updates.updateTheme = function( args ) { 863 var $notice; 864 865 args = _.extend( { 866 success: wp.updates.updateThemeSuccess, 867 error: wp.updates.updateThemeError 868 }, args ); 869 870 if ( 'themes-network' === pagenow ) { 871 $notice = $( '[data-slug="' + args.slug + '"]' ).find( '.update-message' ).addClass( 'updating-message' ).find( 'p' ); 872 873 } else { 874 $notice = $( '#update-theme' ).closest( '.notice' ).removeClass( 'notice-large' ); 875 876 $notice.find( 'h3' ).remove(); 877 878 $notice = $notice.add( $( '[data-slug="' + args.slug + '"]' ).find( '.update-message' ) ); 879 $notice = $notice.addClass( 'updating-message' ).find( 'p' ); 880 } 881 882 if ( $notice.html() !== wp.updates.l10n.updating ) { 883 $notice.data( 'originaltext', $notice.html() ); 884 } 885 886 wp.a11y.speak( wp.updates.l10n.updatingMsg, 'polite' ); 887 $notice.text( wp.updates.l10n.updating ); 888 889 return wp.updates.ajax( 'update-theme', args ); 890 }; 891 892 /** 893 * Updates the UI appropriately after a successful theme update. 894 * 895 * @since 4.6.0 896 * 897 * @typedef {object} updateThemeSuccess 898 * @param {object} response 899 * @param {string} response.slug Slug of the theme to be updated. 900 * @param {object} response.theme Updated theme. 901 * @param {string} response.oldVersion Old version of the theme. 902 * @param {string} response.newVersion New version of the theme. 903 */ 904 wp.updates.updateThemeSuccess = function( response ) { 905 var isModalOpen = $( 'body.modal-open' ).length, 906 $theme = $( '[data-slug="' + response.slug + '"]' ), 907 updatedMessage = { 908 className: 'updated-message notice-success notice-alt', 909 message: wp.updates.l10n.updated 910 }, 911 $notice, newText; 912 913 if ( 'themes-network' === pagenow ) { 914 $notice = $theme.find( '.update-message' ); 915 916 // Update the version number in the row. 917 newText = $theme.find( '.theme-version-author-uri' ).html().replace( response.oldVersion, response.newVersion ); 918 $theme.find( '.theme-version-author-uri' ).html( newText ); 919 } else { 920 $notice = $( '.theme-info .notice' ).add( $theme.find( '.update-message' ) ); 921 922 // Focus on Customize button after updating. 923 if ( isModalOpen ) { 924 $( '.load-customize:visible' ).focus(); 925 } else { 926 $theme.find( '.load-customize' ).focus(); 927 } 928 } 929 930 wp.updates.addAdminNotice( _.extend( { selector: $notice }, updatedMessage ) ); 931 wp.a11y.speak( wp.updates.l10n.updatedMsg, 'polite' ); 932 933 wp.updates.decrementCount( 'theme' ); 934 935 $document.trigger( 'wp-theme-update-success', response ); 936 937 // Show updated message after modal re-rendered. 938 if ( isModalOpen ) { 939 $( '.theme-info .theme-author' ).after( wp.updates.adminNotice( updatedMessage ) ); 940 } 941 }; 942 943 /** 944 * Updates the UI appropriately after a failed theme update. 945 * 946 * @since 4.6.0 947 * 948 * @typedef {object} updateThemeError 949 * @param {object} response Response from the server. 950 * @param {string} response.slug Slug of the theme to be updated. 951 * @param {string} response.errorCode Error code for the error that occurred. 952 * @param {string} response.errorMessage The error that occurred. 953 */ 954 wp.updates.updateThemeError = function( response ) { 955 var $theme = $( '[data-slug="' + response.slug + '"]' ), 956 errorMessage = wp.updates.l10n.updateFailed.replace( '%s', response.errorMessage ), 957 $notice; 958 959 if ( ! wp.updates.isValidResponse( response, 'update' ) ) { 960 return; 961 } 962 963 if ( wp.updates.maybeHandleCredentialError( response, 'update-theme' ) ) { 964 return; 965 } 966 967 if ( 'themes-network' === pagenow ) { 968 $notice = $theme.find( '.update-message ' ); 969 } else { 970 $notice = $( '.theme-info .notice' ).add( $theme.find( '.notice' ) ); 971 972 $( 'body.modal-open' ).length ? $( '.load-customize:visible' ).focus() : $theme.find( '.load-customize' ).focus(); 973 } 974 975 wp.updates.addAdminNotice( { 976 selector: $notice, 977 className: 'update-message notice-error notice-alt is-dismissible', 978 message: errorMessage 979 } ); 980 981 wp.a11y.speak( errorMessage, 'polite' ); 982 983 $document.trigger( 'wp-theme-update-error', response ); 984 }; 985 986 /** 987 * Sends an Ajax request to the server to install a theme. 988 * 989 * @since 4.6.0 990 * 991 * @param {object} args 992 * @param {string} args.slug Theme stylesheet. 993 * @param {installThemeSuccess=} args.success Optional. Success callback. Default: wp.updates.installThemeSuccess 994 * @param {installThemeError=} args.error Optional. Error callback. Default: wp.updates.installThemeError 995 * @return {$.promise} A jQuery promise that represents the request, 996 * decorated with an abort() method. 997 */ 998 wp.updates.installTheme = function( args ) { 999 var $message = $( '.theme-install[data-slug="' + args.slug + '"]' ); 1000 1001 args = _.extend( { 1002 success: wp.updates.installThemeSuccess, 1003 error: wp.updates.installThemeError 1004 }, args ); 1005 1006 $message.addClass( 'updating-message' ); 1007 $message.parents( '.theme' ).addClass( 'focus' ); 1008 if ( $message.html() !== wp.updates.l10n.installing ) { 1009 $message.data( 'originaltext', $message.html() ); 1010 } 1011 1012 $message.text( wp.updates.l10n.installing ); 1013 wp.a11y.speak( wp.updates.l10n.installingMsg, 'polite' ); 1014 1015 // Remove previous error messages, if any. 1016 $( '.install-theme-info, [data-slug="' + args.slug + '"]' ).removeClass( 'theme-install-failed' ).find( '.notice.notice-error' ).remove(); 1017 1018 return wp.updates.ajax( 'install-theme', args ); 1019 }; 1020 1021 /** 1022 * Updates the UI appropriately after a successful theme install. 1023 * 1024 * @since 4.6.0 1025 * 1026 * @typedef {object} installThemeSuccess 1027 * @param {object} response Response from the server. 1028 * @param {string} response.slug Slug of the theme to be installed. 1029 * @param {string} response.customizeUrl URL to the Customizer for the just installed theme. 1030 * @param {string} response.activateUrl URL to activate the just installed theme. 1031 */ 1032 wp.updates.installThemeSuccess = function( response ) { 1033 var $card = $( '.wp-full-overlay-header, [data-slug=' + response.slug + ']' ), 1034 $message; 1035 1036 $document.trigger( 'wp-install-theme-success', response ); 1037 1038 $message = $card.find( '.button-primary' ) 1039 .removeClass( 'updating-message' ) 1040 .addClass( 'updated-message disabled' ) 1041 .text( wp.updates.l10n.installed ); 1042 1043 wp.a11y.speak( wp.updates.l10n.installedMsg, 'polite' ); 1044 1045 setTimeout( function() { 1046 1047 if ( response.activateUrl ) { 1048 1049 // Transform the 'Install' button into an 'Activate' button. 1050 $message 1051 .attr( 'href', response.activateUrl ) 1052 .removeClass( 'theme-install updated-message disabled' ) 1053 .addClass( 'activate' ) 1054 .text( wp.updates.l10n.activateTheme ); 1055 } 1056 1057 if ( response.customizeUrl ) { 1058 1059 // Transform the 'Preview' button into a 'Live Preview' button. 1060 $message.siblings( '.preview' ).replaceWith( function () { 1061 return $( '<a>' ) 1062 .attr( 'href', response.customizeUrl ) 1063 .addClass( 'button button-secondary load-customize' ) 1064 .text( wp.updates.l10n.livePreview ); 1065 } ); 1066 } 1067 }, 1000 ); 1068 }; 1069 1070 /** 1071 * Updates the UI appropriately after a failed theme install. 1072 * 1073 * @since 4.6.0 1074 * 1075 * @typedef {object} installThemeError 1076 * @param {object} response Response from the server. 1077 * @param {string} response.slug Slug of the theme to be installed. 1078 * @param {string} response.errorCode Error code for the error that occurred. 1079 * @param {string} response.errorMessage The error that occurred. 1080 */ 1081 wp.updates.installThemeError = function( response ) { 1082 var $card, $button, 1083 errorMessage = wp.updates.l10n.installFailed.replace( '%s', response.errorMessage ), 1084 $message = wp.updates.adminNotice( { 1085 className: 'update-message notice-error notice-alt', 1086 message: errorMessage 1087 } ); 1088 1089 if ( ! wp.updates.isValidResponse( response, 'install' ) ) { 1090 return; 1091 } 1092 1093 if ( wp.updates.maybeHandleCredentialError( response, 'install-theme' ) ) { 1094 return; 1095 } 1096 1097 if ( $document.find( 'body' ).hasClass( 'full-overlay-active' ) ) { 1098 $button = $( '.theme-install[data-slug="' + response.slug + '"]' ); 1099 $card = $( '.install-theme-info' ).prepend( $message ); 1100 } else { 1101 $card = $( '[data-slug="' + response.slug + '"]' ).removeClass( 'focus' ).addClass( 'theme-install-failed' ).append( $message ); 1102 $button = $card.find( '.theme-install' ); 1103 } 1104 1105 $button 1106 .removeClass( 'updating-message' ) 1107 .attr( 'aria-label', wp.updates.l10n.installFailedLabel.replace( '%s', $card.find( '.theme-name' ).text() ) ) 1108 .text( wp.updates.l10n.installFailedShort ); 1109 1110 wp.a11y.speak( errorMessage, 'assertive' ); 1111 1112 $document.trigger( 'wp-theme-install-error', response ); 1113 }; 1114 1115 /** 1116 * Sends an Ajax request to the server to install a theme. 1117 * 1118 * @since 4.6.0 1119 * 1120 * @param {object} args 1121 * @param {string} args.slug Theme stylesheet. 1122 * @param {deleteThemeSuccess=} args.success Optional. Success callback. Default: wp.updates.deleteThemeSuccess 1123 * @param {deleteThemeError=} args.error Optional. Error callback. Default: wp.updates.deleteThemeError 1124 * @return {$.promise} A jQuery promise that represents the request, 1125 * decorated with an abort() method. 1126 */ 1127 wp.updates.deleteTheme = function( args ) { 1128 var $button = $( '.theme-actions .delete-theme' ); 1129 1130 args = _.extend( { 1131 success: wp.updates.deleteThemeSuccess, 1132 error: wp.updates.deleteThemeError 1133 }, args ); 1134 1135 if ( $button.html() !== wp.updates.l10n.deleting ) { 1136 $button.data( 'originaltext', $button.html() ); 1137 } 1138 1139 $button.text( wp.updates.l10n.deleting ); 1140 wp.a11y.speak( wp.updates.l10n.deleting, 'polite' ); 1141 1142 // Remove previous error messages, if any. 1143 $( '.theme-info .update-message' ).remove(); 1144 1145 return wp.updates.ajax( 'delete-theme', args ); 1146 }; 1147 1148 /** 1149 * Updates the UI appropriately after a successful theme deletion. 1150 * 1151 * @since 4.6.0 1152 * 1153 * @typedef {object} deleteThemeSuccess 1154 * @param {object} response Response from the server. 1155 * @param {string} response.slug Slug of the theme that was deleted. 1156 */ 1157 wp.updates.deleteThemeSuccess = function( response ) { 1158 var $themeRows = $( '[data-slug="' + response.slug + '"]' ); 1159 1160 if ( 'themes-network' === pagenow ) { 1161 1162 // Removes the theme and updates rows. 1163 $themeRows.css( { backgroundColor: '#faafaa' } ).fadeOut( 350, function() { 1164 var $views = $( '.subsubsub' ), 1165 $themeRow = $( this ), 1166 totals = settings.totals, 1167 deletedRow = wp.template( 'item-deleted-row' ); 1168 1169 if ( ! $themeRow.hasClass( 'plugin-update-tr' ) ) { 1170 $themeRow.after( 1171 deletedRow( { 1172 slug: response.slug, 1173 colspan: $( '#bulk-action-form' ).find( 'thead th:not(.hidden), thead td' ).length, 1174 name: $themeRow.find( '.theme-title strong' ).text() 1175 } ) 1176 ); 1177 } 1178 1179 $themeRow.remove(); 1180 1181 // Remove theme from update count. 1182 if ( $themeRow.hasClass( 'update' ) ) { 1183 totals.upgrade--; 1184 wp.updates.decrementCount( 'theme' ); 1185 } 1186 1187 // Remove from views. 1188 if ( $themeRow.hasClass( 'inactive' ) ) { 1189 totals.disabled--; 1190 if ( totals.disabled ) { 1191 $views.find( '.disabled .count' ).text( '(' + totals.disabled + ')' ); 1192 } else { 1193 $views.find( '.disabled' ).remove(); 1194 } 1195 } 1196 1197 // There is always at least one theme available. 1198 $views.find( '.all .count' ).text( '(' + --totals.all + ')' ); 1199 } ); 1200 } 1201 1202 wp.a11y.speak( wp.updates.l10n.deleted, 'polite' ); 1203 1204 $document.trigger( 'wp-delete-theme-success', response ); 1205 }; 1206 1207 /** 1208 * Updates the UI appropriately after a failed theme deletion. 1209 * 1210 * @since 4.6.0 1211 * 1212 * @typedef {object} deleteThemeError 1213 * @param {object} response Response from the server. 1214 * @param {string} response.slug Slug of the theme to be deleted. 1215 * @param {string} response.errorCode Error code for the error that occurred. 1216 * @param {string} response.errorMessage The error that occurred. 1217 */ 1218 wp.updates.deleteThemeError = function( response ) { 1219 var $themeRow = $( 'tr.inactive[data-slug="' + response.slug + '"]' ), 1220 $button = $( '.theme-actions .delete-theme' ), 1221 updateRow = wp.template( 'item-update-row' ), 1222 $updateRow = $themeRow.siblings( '#' + response.slug + '-update' ), 1223 errorMessage = wp.updates.l10n.deleteFailed.replace( '%s', response.errorMessage ), 1224 $message = wp.updates.adminNotice( { 1225 className: 'update-message notice-error notice-alt', 1226 message: errorMessage 1227 } ); 1228 1229 if ( wp.updates.maybeHandleCredentialError( response, 'delete-theme' ) ) { 1230 return; 1231 } 1232 1233 if ( 'themes-network' === pagenow ) { 1234 if ( ! $updateRow.length ) { 1235 $themeRow.addClass( 'update' ).after( 1236 updateRow( { 1237 slug: response.slug, 1238 colspan: $( '#bulk-action-form' ).find( 'thead th:not(.hidden), thead td' ).length, 1239 content: $message 1240 } ) 1241 ); 1242 } else { 1243 // Remove previous error messages, if any. 1244 $updateRow.find( '.notice-error' ).remove(); 1245 $updateRow.find( '.plugin-update' ).append( $message ); 1246 } 1247 } else { 1248 $( '.theme-info .theme-description' ).before( $message ); 1249 } 1250 1251 $button.html( $button.data( 'originaltext' ) ); 1252 1253 wp.a11y.speak( errorMessage, 'assertive' ); 1254 1255 $document.trigger( 'wp-theme-delete-error', response ); 1256 }; 1257 1258 /** 1259 * Adds the appropriate callback based on the type of action and the current page. 1260 * 1261 * @since 4.6.0 1262 * @private 1263 * 1264 * @param {object} data AJAX payload. 1265 * @param {string} action The type of request to perform. 1266 * @return {object} The AJAX payload with the appropriate callbacks. 1267 */ 1268 wp.updates._addCallbacks = function( data, action ) { 1269 if ( 'import' === pagenow && 'install-plugin' === action ) { 1270 data.success = wp.updates.installImporterSuccess; 1271 data.error = wp.updates.installImporterError; 1272 } 1273 1274 return data; 1275 }; 1276 1277 /** 1278 * Pulls available jobs from the queue and runs them. 1279 * 312 1280 * @since 4.2.0 313 */ 314 wp.updates.showErrorInCredentialsForm = function( message ) { 315 var $modal = $( '.notification-dialog' ); 316 317 // Remove any existing error. 318 $modal.find( '.error' ).remove(); 319 320 $modal.find( 'h3' ).after( '<div class="error">' + message + '</div>' ); 321 }; 322 323 /** 324 * Events that need to happen when there is a credential error 1281 * @since 4.6.0 Can handle multiple job types. 1282 */ 1283 wp.updates.queueChecker = function() { 1284 var job; 1285 1286 if ( wp.updates.ajaxLocked || ! wp.updates.queue.length ) { 1287 return; 1288 } 1289 1290 job = wp.updates.queue.shift(); 1291 1292 // Handle a queue job. 1293 switch ( job.action ) { 1294 case 'install-plugin': 1295 wp.updates.installPlugin( job.data ); 1296 break; 1297 1298 case 'update-plugin': 1299 wp.updates.updatePlugin( job.data ); 1300 break; 1301 1302 case 'delete-plugin': 1303 wp.updates.deletePlugin( job.data ); 1304 break; 1305 1306 case 'install-theme': 1307 wp.updates.installTheme( job.data ); 1308 break; 1309 1310 case 'update-theme': 1311 wp.updates.updateTheme( job.data ); 1312 break; 1313 1314 case 'delete-theme': 1315 wp.updates.deleteTheme( job.data ); 1316 break; 1317 1318 default: 1319 window.console.error( 'Failed to execute queued update job.', job ); 1320 break; 1321 } 1322 }; 1323 1324 /** 1325 * Requests the users filesystem credentials if they aren't already known. 325 1326 * 326 1327 * @since 4.2.0 327 */ 328 wp.updates.credentialError = function( response, type ) { 329 wp.updates.updateQueue.push( { 330 'type': type, 331 'data': { 332 // Not cool that we're depending on response for this data. 333 // This would feel more whole in a view all tied together. 334 plugin: response.plugin, 335 slug: response.slug 336 } 337 } ); 338 wp.updates.showErrorInCredentialsForm( response.error ); 339 wp.updates.requestFilesystemCredentials(); 340 }; 341 342 /** 343 * If an update job has been placed in the queue, queueChecker pulls it out and runs it. 1328 * 1329 * @param {Event=} event Optional. Event interface. 1330 */ 1331 wp.updates.requestFilesystemCredentials = function( event ) { 1332 if ( false === wp.updates.filesystemCredentials.available ) { 1333 /* 1334 * After exiting the credentials request modal, 1335 * return the focus to the element triggering the request. 1336 */ 1337 if ( event && ! wp.updates.$elToReturnFocusToFromCredentialsModal ) { 1338 wp.updates.$elToReturnFocusToFromCredentialsModal = $( event.target ); 1339 } 1340 1341 wp.updates.ajaxLocked = true; 1342 wp.updates.requestForCredentialsModalOpen(); 1343 } 1344 }; 1345 1346 /** 1347 * Requests the users filesystem credentials if needed and there is no lock. 1348 * 1349 * @since 4.6.0 1350 * 1351 * @param {Event=} event Optional. Event interface. 1352 */ 1353 wp.updates.maybeRequestFilesystemCredentials = function( event ) { 1354 if ( wp.updates.shouldRequestFilesystemCredentials && ! wp.updates.ajaxLocked ) { 1355 wp.updates.requestFilesystemCredentials( event ); 1356 } 1357 }; 1358 1359 /** 1360 * Keydown handler for the request for credentials modal. 1361 * 1362 * Closes the modal when the escape key is pressed and 1363 * constrains keyboard navigation to inside the modal. 344 1364 * 345 1365 * @since 4.2.0 346 */ 347 wp.updates.queueChecker = function() { 348 if ( wp.updates.updateLock || wp.updates.updateQueue.length <= 0 ) { 349 return; 350 } 351 352 var job = wp.updates.updateQueue.shift(); 353 354 wp.updates.updatePlugin( job.data.plugin, job.data.slug ); 355 }; 356 357 358 /** 359 * Request the users filesystem credentials if we don't have them already. 360 * 361 * @since 4.2.0 362 */ 363 wp.updates.requestFilesystemCredentials = function( event ) { 364 if ( wp.updates.updateDoneSuccessfully === false ) { 365 /* 366 * For the plugin install screen, return the focus to the install button 367 * after exiting the credentials request modal. 368 */ 369 if ( 'plugin-install' === pagenow && event ) { 370 wp.updates.$elToReturnFocusToFromCredentialsModal = $( event.target ); 371 } 372 373 wp.updates.updateLock = true; 374 375 wp.updates.requestForCredentialsModalOpen(); 376 } 377 }; 378 379 /** 380 * Keydown handler for the request for credentials modal. 381 * 382 * Close the modal when the escape key is pressed. 383 * Constrain keyboard navigation to inside the modal. 384 * 385 * @since 4.2.0 1366 * 1367 * @param {Event} event Event interface. 386 1368 */ 387 1369 wp.updates.keydown = function( event ) { … … 389 1371 wp.updates.requestForCredentialsModalCancel(); 390 1372 } else if ( 9 === event.keyCode ) { 391 // #upgrade button must always be the last focusable element in the dialog. 392 if ( event.target.id === 'upgrade' && ! event.shiftKey ) { 1373 1374 // #upgrade button must always be the last focus-able element in the dialog. 1375 if ( 'upgrade' === event.target.id && ! event.shiftKey ) { 393 1376 $( '#hostname' ).focus(); 1377 394 1378 event.preventDefault(); 395 } else if ( event.target.id === 'hostname'&& event.shiftKey ) {1379 } else if ( 'hostname' === event.target.id && event.shiftKey ) { 396 1380 $( '#upgrade' ).focus(); 1381 397 1382 event.preventDefault(); 398 1383 } … … 401 1386 402 1387 /** 403 * Open the request for credentials modal.1388 * Opens the request for credentials modal. 404 1389 * 405 1390 * @since 4.2.0 … … 407 1392 wp.updates.requestForCredentialsModalOpen = function() { 408 1393 var $modal = $( '#request-filesystem-credentials-dialog' ); 1394 409 1395 $( 'body' ).addClass( 'modal-open' ); 410 1396 $modal.show(); 411 412 1397 $modal.find( 'input:enabled:first' ).focus(); 413 $modal. keydown(wp.updates.keydown );414 }; 415 416 /** 417 * Close the request for credentials modal.1398 $modal.on( 'keydown', wp.updates.keydown ); 1399 }; 1400 1401 /** 1402 * Closes the request for credentials modal. 418 1403 * 419 1404 * @since 4.2.0 … … 422 1407 $( '#request-filesystem-credentials-dialog' ).hide(); 423 1408 $( 'body' ).removeClass( 'modal-open' ); 424 wp.updates.$elToReturnFocusToFromCredentialsModal.focus(); 425 }; 426 427 /** 428 * The steps that need to happen when the modal is canceled out 1409 1410 if ( wp.updates.$elToReturnFocusToFromCredentialsModal ) { 1411 wp.updates.$elToReturnFocusToFromCredentialsModal.focus(); 1412 } 1413 }; 1414 1415 /** 1416 * Takes care of the steps that need to happen when the modal is canceled out. 429 1417 * 430 1418 * @since 4.2.0 1419 * @since 4.6.0 Triggers an event for callbacks to listen to and add their actions. 431 1420 */ 432 1421 wp.updates.requestForCredentialsModalCancel = function() { 433 // no updateLock and no updateQueue means we already have cleared things up 434 var data, $message; 435 436 if( wp.updates.updateLock === false && wp.updates.updateQueue.length === 0 ){ 1422 1423 // Not ajaxLocked and no queue means we already have cleared things up. 1424 if ( ! wp.updates.ajaxLocked && ! wp.updates.queue.length ) { 437 1425 return; 438 1426 } 439 1427 440 data = wp.updates.updateQueue[0].data; 441 442 // remove the lock, and clear the queue 443 wp.updates.updateLock = false; 444 wp.updates.updateQueue = []; 1428 _.each( wp.updates.queue, function( job ) { 1429 $document.trigger( 'credential-modal-cancel', job ); 1430 } ); 1431 1432 // Remove the lock, and clear the queue. 1433 wp.updates.ajaxLocked = false; 1434 wp.updates.queue = []; 445 1435 446 1436 wp.updates.requestForCredentialsModalClose(); 447 if ( 'plugins' === pagenow || 'plugins-network' === pagenow ) { 448 $message = $( '[data-plugin="' + data.plugin + '"]' ).next().find( '.update-message' ); 449 } else if ( 'plugin-install' === pagenow ) { 450 $message = $( '.plugin-card-' + data.slug ).find( '.update-now' ); 451 } 452 453 $message.removeClass( 'updating-message' ); 454 $message.html( $message.data( 'originaltext' ) ); 455 wp.a11y.speak( wp.updates.l10n.updateCancel ); 456 }; 457 /** 458 * Potentially add an AYS to a user attempting to leave the page 1437 }; 1438 1439 /** 1440 * Displays an error message in the request for credentials form. 1441 * 1442 * @since 4.2.0 1443 * 1444 * @param {string} message Error message. 1445 */ 1446 wp.updates.showErrorInCredentialsForm = function( message ) { 1447 var $modal = $( '#request-filesystem-credentials-form' ); 1448 1449 // Remove any existing error. 1450 $modal.find( '.notice' ).remove(); 1451 $modal.find( '#request-filesystem-credentials-title' ).after( '<div class="notice notice-alt notice-error"><p>' + message + '</p></div>' ); 1452 }; 1453 1454 /** 1455 * Handles credential errors and runs events that need to happen in that case. 1456 * 1457 * @since 4.2.0 1458 * 1459 * @param {object} response Ajax response. 1460 * @param {string} action The type of request to perform. 1461 */ 1462 wp.updates.credentialError = function( response, action ) { 1463 1464 // Restore callbacks. 1465 response = wp.updates._addCallbacks( response, action ); 1466 1467 wp.updates.queue.push( { 1468 action: action, 1469 1470 /* 1471 * Not cool that we're depending on response for this data. 1472 * This would feel more whole in a view all tied together. 1473 */ 1474 data: response 1475 } ); 1476 1477 wp.updates.filesystemCredentials.available = false; 1478 wp.updates.showErrorInCredentialsForm( response.errorMessage ); 1479 wp.updates.requestFilesystemCredentials(); 1480 }; 1481 1482 /** 1483 * Handles credentials errors if it could not connect to the filesystem. 1484 * 1485 * @since 4.6.0 1486 * 1487 * @typedef {object} maybeHandleCredentialError 1488 * @param {object} response Response from the server. 1489 * @param {string} response.errorCode Error code for the error that occurred. 1490 * @param {string} response.errorMessage The error that occurred. 1491 * @param {string} action The type of request to perform. 1492 * @returns {boolean} Whether there is an error that needs to be handled or not. 1493 */ 1494 wp.updates.maybeHandleCredentialError = function( response, action ) { 1495 if ( response.errorCode && 'unable_to_connect_to_filesystem' === response.errorCode ) { 1496 wp.updates.credentialError( response, action ); 1497 return true; 1498 } 1499 1500 return false; 1501 }; 1502 1503 /** 1504 * Validates an AJAX response to ensure it's a proper object. 1505 * 1506 * If the response deems to be invalid, an admin notice is being displayed. 1507 * 1508 * @param {(object|string)} response Response from the server. 1509 * @param {function=} response.always Optional. Callback for when the Deferred is resolved or rejected. 1510 * @param {string=} response.statusText Optional. Status message corresponding to the status code. 1511 * @param {string=} response.responseText Optional. Request response as text. 1512 * @param {string} action Type of action the response is referring to. Can be 'delete', 1513 * 'update' or 'install'. 1514 */ 1515 wp.updates.isValidResponse = function( response, action ) { 1516 var error = wp.updates.l10n.unknownError, 1517 errorMessage; 1518 1519 // Make sure the response is a valid data object and not a Promise object. 1520 if ( _.isObject( response ) && ! _.isFunction( response.always ) ) { 1521 return true; 1522 } 1523 1524 if ( _.isString( response ) ) { 1525 error = response; 1526 } else if ( _.isString( response.responseText ) && '' !== response.responseText ) { 1527 error = response.responseText; 1528 } else if ( _.isString( response.statusText ) ) { 1529 error = response.statusText; 1530 } 1531 1532 switch ( action ) { 1533 case 'update': 1534 errorMessage = wp.updates.l10n.updateFailed; 1535 break; 1536 1537 case 'install': 1538 errorMessage = wp.updates.l10n.installFailed; 1539 break; 1540 1541 case 'delete': 1542 errorMessage = wp.updates.l10n.deleteFailed; 1543 break; 1544 } 1545 1546 errorMessage = errorMessage.replace( '%s', error ); 1547 1548 // Add admin notice. 1549 wp.updates.addAdminNotice( { 1550 id: 'unknown_error', 1551 className: 'notice-error is-dismissible', 1552 message: errorMessage 1553 } ); 1554 1555 // Remove the lock, and clear the queue. 1556 wp.updates.ajaxLocked = false; 1557 wp.updates.queue = []; 1558 1559 // Change buttons of all running updates. 1560 $( '.button.updating-message' ) 1561 .removeClass( 'updating-message' ) 1562 .attr( 'aria-label', wp.updates.l10n.updateFailedShort ) 1563 .prop( 'disabled', true ) 1564 .text( wp.updates.l10n.updateFailedShort ); 1565 1566 $( '.updating-message:not(.button):not(.thickbox)' ) 1567 .removeClass( 'updating-message notice-warning' ) 1568 .addClass( 'notice-error' ) 1569 .find( 'p' ).text( errorMessage ); 1570 1571 wp.a11y.speak( errorMessage, 'assertive' ); 1572 1573 return false; 1574 }; 1575 1576 /** 1577 * Potentially adds an AYS to a user attempting to leave the page. 459 1578 * 460 1579 * If an update is on-going and a user attempts to leave the page, 461 * open an "Are you sure?" alert.1580 * opens an "Are you sure?" alert. 462 1581 * 463 1582 * @since 4.2.0 464 1583 */ 465 466 1584 wp.updates.beforeunload = function() { 467 if ( wp.updates. updateLock) {1585 if ( wp.updates.ajaxLocked ) { 468 1586 return wp.updates.l10n.beforeunload; 469 1587 } 470 1588 }; 471 1589 472 473 $( document ).ready( function() {474 // Set initial focus on the first empty form field.475 $( '#request-filesystem-credentials-form input[value=""]:first' ).focus();1590 $( function() { 1591 var $pluginFilter = $( '#plugin-filter' ), 1592 $bulkActionForm = $( '#bulk-action-form' ), 1593 $filesystemModal = $( '#request-filesystem-credentials-dialog' ); 476 1594 477 1595 /* 478 * Check whether a user needs to submit filesystem credentials based on whether 479 * the form was output on the page server-side. 1596 * Whether a user needs to submit filesystem credentials. 1597 * 1598 * This is based on whether the form was output on the page server-side. 480 1599 * 481 1600 * @see {wp_print_request_filesystem_credentials_modal() in PHP} 482 1601 */ 483 wp.updates.shouldRequestFilesystemCredentials = ( $( '#request-filesystem-credentials-dialog' ).length <= 0 ) ? false : true; 484 485 // File system credentials form submit noop-er / handler. 486 $( '#request-filesystem-credentials-dialog form' ).on( 'submit', function() { 1602 wp.updates.shouldRequestFilesystemCredentials = $filesystemModal.length > 0; 1603 1604 /** 1605 * File system credentials form submit noop-er / handler. 1606 * 1607 * @since 4.2.0 1608 */ 1609 $filesystemModal.on( 'submit', 'form', function( event ) { 1610 event.preventDefault(); 1611 487 1612 // Persist the credentials input by the user for the duration of the page load. 488 wp.updates.filesystemCredentials.ftp.hostname = $('#hostname').val(); 489 wp.updates.filesystemCredentials.ftp.username = $('#username').val(); 490 wp.updates.filesystemCredentials.ftp.password = $('#password').val(); 491 wp.updates.filesystemCredentials.ftp.connectionType = $('input[name="connection_type"]:checked').val(); 492 wp.updates.filesystemCredentials.ssh.publicKey = $('#public_key').val(); 493 wp.updates.filesystemCredentials.ssh.privateKey = $('#private_key').val(); 1613 wp.updates.filesystemCredentials.ftp.hostname = $( '#hostname' ).val(); 1614 wp.updates.filesystemCredentials.ftp.username = $( '#username' ).val(); 1615 wp.updates.filesystemCredentials.ftp.password = $( '#password' ).val(); 1616 wp.updates.filesystemCredentials.ftp.connectionType = $( 'input[name="connection_type"]:checked' ).val(); 1617 wp.updates.filesystemCredentials.ssh.publicKey = $( '#public_key' ).val(); 1618 wp.updates.filesystemCredentials.ssh.privateKey = $( '#private_key' ).val(); 1619 wp.updates.filesystemCredentials.available = true; 1620 1621 // Unlock and invoke the queue. 1622 wp.updates.ajaxLocked = false; 1623 wp.updates.queueChecker(); 494 1624 495 1625 wp.updates.requestForCredentialsModalClose(); 496 497 // Unlock and invoke the queue. 498 wp.updates.updateLock = false;499 wp.updates.queueChecker();500 501 return false;502 });503 504 // Close the request credentials modal when 505 $( '#request-filesystem-credentials-dialog [data-js-action="close"], .notification-dialog-background' ).on( 'click', function() {506 wp.updates.requestForCredentialsModalCancel();507 });508 509 // Hide SSH fields when not selected.510 $ ( '#request-filesystem-credentials-form input[name="connection_type"]' ).on( 'change', function() {1626 } ); 1627 1628 /** 1629 * Closes the request credentials modal when clicking the 'Cancel' button or outside of the modal. 1630 * 1631 * @since 4.2.0 1632 */ 1633 $filesystemModal.on( 'click', '[data-js-action="close"], .notification-dialog-background', wp.updates.requestForCredentialsModalCancel ); 1634 1635 /** 1636 * Hide SSH fields when not selected. 1637 * 1638 * @since 4.2.0 1639 */ 1640 $filesystemModal.on( 'change', 'input[name="connection_type"]', function() { 511 1641 $( '#ssh-keys' ).toggleClass( 'hidden', ( 'ssh' !== $( this ).val() ) ); 512 }); 513 514 // Click handler for plugin updates in List Table view. 515 $( '.plugin-update-tr' ).on( 'click', '.update-link', function( e ) { 516 e.preventDefault(); 517 if ( wp.updates.shouldRequestFilesystemCredentials && ! wp.updates.updateLock ) { 518 wp.updates.requestFilesystemCredentials( e ); 519 } 520 var updateRow = $( e.target ).parents( '.plugin-update-tr' ); 1642 } ).change(); 1643 1644 /** 1645 * Handles events after the credential modal was closed. 1646 * 1647 * @since 4.6.0 1648 * 1649 * @param {Event} event Event interface. 1650 * @param {string} job The install/update.delete request. 1651 */ 1652 $document.on( 'credential-modal-cancel', function( event, job ) { 1653 var $updatingMessage = $( '.updating-message' ), 1654 $message, originalText; 1655 1656 if ( 'import' === pagenow ) { 1657 $updatingMessage.removeClass( 'updating-message' ); 1658 } else if ( 'plugins' === pagenow || 'plugins-network' === pagenow ) { 1659 $message = $( 'tr[data-plugin="' + job.data.plugin + '"]' ).find( '.update-message' ); 1660 } else if ( 'plugin-install' === pagenow || 'plugin-install-network' === pagenow ) { 1661 $message = $( '.update-now.updating-message' ); 1662 } else { 1663 $message = $updatingMessage; 1664 } 1665 1666 if ( $message ) { 1667 originalText = $message.data( 'originaltext' ); 1668 1669 if ( 'undefined' === typeof originalText ) { 1670 originalText = $( '<p>' ).html( $message.find( 'p' ).data( 'originaltext' ) ); 1671 } 1672 1673 $message 1674 .removeClass( 'updating-message' ) 1675 .html( originalText ); 1676 } 1677 1678 wp.a11y.speak( wp.updates.l10n.updateCancel, 'polite' ); 1679 } ); 1680 1681 /** 1682 * Click handler for plugin updates in List Table view. 1683 * 1684 * @since 4.2.0 1685 * 1686 * @param {Event} event Event interface. 1687 */ 1688 $bulkActionForm.on( 'click', '[data-plugin] .update-link', function( event ) { 1689 var $message = $( event.target ), 1690 $pluginRow = $message.parents( 'tr' ); 1691 1692 event.preventDefault(); 1693 1694 if ( $message.hasClass( 'updating-message' ) || $message.hasClass( 'button-disabled' ) ) { 1695 return; 1696 } 1697 1698 wp.updates.maybeRequestFilesystemCredentials( event ); 1699 521 1700 // Return the user to the input box of the plugin's table row after closing the modal. 522 wp.updates.$elToReturnFocusToFromCredentialsModal = updateRow.prev().find( '.check-column input' ); 523 wp.updates.updatePlugin( updateRow.data( 'plugin' ), updateRow.data( 'slug' ) ); 524 } ); 525 526 $( '.plugin-card' ).on( 'click', '.update-now', function( e ) { 527 e.preventDefault(); 528 var $button = $( e.target ); 529 530 // Do nothing while updating and when the button is disabled. 1701 wp.updates.$elToReturnFocusToFromCredentialsModal = $pluginRow.find( '.check-column input' ); 1702 wp.updates.updatePlugin( { 1703 plugin: $pluginRow.data( 'plugin' ), 1704 slug: $pluginRow.data( 'slug' ) 1705 } ); 1706 } ); 1707 1708 /** 1709 * Click handler for plugin updates in plugin install view. 1710 * 1711 * @since 4.2.0 1712 * 1713 * @param {Event} event Event interface. 1714 */ 1715 $pluginFilter.on( 'click', '.update-now', function( event ) { 1716 var $button = $( event.target ); 1717 event.preventDefault(); 1718 531 1719 if ( $button.hasClass( 'updating-message' ) || $button.hasClass( 'button-disabled' ) ) { 532 1720 return; 533 1721 } 534 1722 535 if ( wp.updates.shouldRequestFilesystemCredentials && ! wp.updates.updateLock ) { 536 wp.updates.requestFilesystemCredentials( e ); 537 } 538 539 wp.updates.updatePlugin( $button.data( 'plugin' ), $button.data( 'slug' ) ); 540 } ); 541 542 $( '#plugin_update_from_iframe' ).on( 'click' , function( e ) { 543 var target, job; 544 545 target = window.parent == window ? null : window.parent, 1723 wp.updates.maybeRequestFilesystemCredentials( event ); 1724 1725 wp.updates.updatePlugin( { 1726 plugin: $button.data( 'plugin' ), 1727 slug: $button.data( 'slug' ) 1728 } ); 1729 } ); 1730 1731 /** 1732 * Click handler for plugin installs in plugin install view. 1733 * 1734 * @since 4.6.0 1735 * 1736 * @param {Event} event Event interface. 1737 */ 1738 $pluginFilter.on( 'click', '.install-now', function( event ) { 1739 var $button = $( event.target ); 1740 event.preventDefault(); 1741 1742 if ( $button.hasClass( 'updating-message' ) || $button.hasClass( 'button-disabled' ) ) { 1743 return; 1744 } 1745 1746 if ( wp.updates.shouldRequestFilesystemCredentials && ! wp.updates.ajaxLocked ) { 1747 wp.updates.requestFilesystemCredentials( event ); 1748 1749 $document.on( 'credential-modal-cancel', function() { 1750 var $message = $( '.install-now.updating-message' ); 1751 1752 $message 1753 .removeClass( 'updating-message' ) 1754 .text( wp.updates.l10n.installNow ); 1755 1756 wp.a11y.speak( wp.updates.l10n.updateCancel, 'polite' ); 1757 } ); 1758 } 1759 1760 wp.updates.installPlugin( { 1761 slug: $button.data( 'slug' ) 1762 } ); 1763 } ); 1764 1765 /** 1766 * Click handler for plugin deletions. 1767 * 1768 * @since 4.6.0 1769 * 1770 * @param {Event} event Event interface. 1771 */ 1772 $bulkActionForm.on( 'click', '[data-plugin] a.delete', function( event ) { 1773 var $pluginRow = $( event.target ).parents( 'tr' ); 1774 1775 event.preventDefault(); 1776 1777 if ( ! window.confirm( wp.updates.l10n.aysDeleteUninstall.replace( '%s', $pluginRow.find( '.plugin-title strong' ).text() ) ) ) { 1778 return; 1779 } 1780 1781 wp.updates.maybeRequestFilesystemCredentials( event ); 1782 1783 wp.updates.deletePlugin( { 1784 plugin: $pluginRow.data( 'plugin' ), 1785 slug: $pluginRow.data( 'slug' ) 1786 } ); 1787 1788 } ); 1789 1790 /** 1791 * Click handler for theme updates. 1792 * 1793 * @since 4.6.0 1794 * 1795 * @param {Event} event Event interface. 1796 */ 1797 $document.on( 'click', '.themes-php.network-admin .update-link', function( event ) { 1798 var $message = $( event.target ), 1799 $themeRow = $message.parents( 'tr' ); 1800 1801 event.preventDefault(); 1802 1803 if ( $message.hasClass( 'updating-message' ) || $message.hasClass( 'button-disabled' ) ) { 1804 return; 1805 } 1806 1807 wp.updates.maybeRequestFilesystemCredentials( event ); 1808 1809 // Return the user to the input box of the theme's table row after closing the modal. 1810 wp.updates.$elToReturnFocusToFromCredentialsModal = $themeRow.find( '.check-column input' ); 1811 wp.updates.updateTheme( { 1812 slug: $themeRow.data( 'slug' ) 1813 } ); 1814 } ); 1815 1816 /** 1817 * Click handler for theme deletions. 1818 * 1819 * @since 4.6.0 1820 * 1821 * @param {Event} event Event interface. 1822 */ 1823 $document.on( 'click', '.themes-php.network-admin a.delete', function( event ) { 1824 var $themeRow = $( event.target ).parents( 'tr' ); 1825 1826 event.preventDefault(); 1827 1828 if ( ! window.confirm( wp.updates.l10n.aysDelete.replace( '%s', $themeRow.find( '.theme-title strong' ).text() ) ) ) { 1829 return; 1830 } 1831 1832 wp.updates.maybeRequestFilesystemCredentials( event ); 1833 1834 wp.updates.deleteTheme( { 1835 slug: $themeRow.data( 'slug' ) 1836 } ); 1837 } ); 1838 1839 /** 1840 * Bulk action handler for plugins and themes. 1841 * 1842 * Handles both deletions and updates. 1843 * 1844 * @since 4.6.0 1845 * 1846 * @param {Event} event Event interface. 1847 */ 1848 $bulkActionForm.on( 'click', '[type="submit"]', function( event ) { 1849 var bulkAction = $( event.target ).siblings( 'select' ).val(), 1850 itemsSelected = $bulkActionForm.find( 'input[name="checked[]"]:checked' ), 1851 success = 0, 1852 error = 0, 1853 errorMessages = [], 1854 type, action; 1855 1856 // Determine which type of item we're dealing with. 1857 switch ( pagenow ) { 1858 case 'plugins': 1859 case 'plugins-network': 1860 type = 'plugin'; 1861 break; 1862 1863 case 'themes-network': 1864 type = 'theme'; 1865 break; 1866 1867 default: 1868 window.console.error( 'The page "%s" is not white-listed for bulk action handling.', pagenow ); 1869 return; 1870 } 1871 1872 // Bail if there were no items selected. 1873 if ( ! itemsSelected.length ) { 1874 event.preventDefault(); 1875 $( 'html, body' ).animate( { scrollTop: 0 } ); 1876 1877 return wp.updates.addAdminNotice( { 1878 id: 'no-items-selected', 1879 className: 'notice-error is-dismissible', 1880 message: wp.updates.l10n.noItemsSelected 1881 } ); 1882 } 1883 1884 // Determine the type of request we're dealing with. 1885 switch ( bulkAction ) { 1886 case 'update-selected': 1887 action = bulkAction.replace( 'selected', type ); 1888 break; 1889 1890 case 'delete-selected': 1891 if ( ! window.confirm( 'plugin' === type ? wp.updates.l10n.aysBulkDelete : wp.updates.l10n.aysBulkDeleteThemes ) ) { 1892 event.preventDefault(); 1893 return; 1894 } 1895 1896 action = bulkAction.replace( 'selected', type ); 1897 break; 1898 1899 default: 1900 window.console.error( 'Failed to identify bulk action: %s', bulkAction ); 1901 return; 1902 } 1903 1904 wp.updates.maybeRequestFilesystemCredentials( event ); 1905 1906 event.preventDefault(); 1907 1908 // Un-check the bulk checkboxes. 1909 $bulkActionForm.find( '.manage-column [type="checkbox"]' ).prop( 'checked', false ); 1910 1911 // Find all the checkboxes which have been checked. 1912 itemsSelected.each( function( index, element ) { 1913 var $checkbox = $( element ), 1914 $itemRow = $checkbox.parents( 'tr' ); 1915 1916 // Un-check the box. 1917 $checkbox.prop( 'checked', false ); 1918 1919 // Only add update-able items to the update queue. 1920 if ( 'update-selected' === bulkAction && ( ! $itemRow.hasClass( 'update' ) || $itemRow.find( 'notice-error' ).length ) ) { 1921 return; 1922 } 1923 1924 // Add it to the queue. 1925 wp.updates.queue.push( { 1926 action: action, 1927 data: { 1928 plugin: $itemRow.data( 'plugin' ), 1929 slug: $itemRow.data( 'slug' ) 1930 } 1931 } ); 1932 } ); 1933 1934 // Display bulk notification for updates of any kind. 1935 $document.on( 'wp-plugin-update-success wp-plugin-update-error wp-theme-update-success wp-theme-update-error', function( event, response ) { 1936 var $bulkActionNotice, itemName; 1937 1938 if ( 'wp-' + response.update + '-update-success' === event.type ) { 1939 success++; 1940 } else { 1941 itemName = response.pluginName ? response.pluginName : $( '[data-slug="' + response.slug + '"]' ).find( '.theme-title strong' ).text(); 1942 1943 error++; 1944 errorMessages.push( itemName + ': ' + response.errorMessage ); 1945 } 1946 1947 wp.updates.adminNotice = wp.template( 'wp-bulk-updates-admin-notice' ); 1948 1949 wp.updates.addAdminNotice( { 1950 id: 'bulk-action-notice', 1951 successes: success, 1952 errors: error, 1953 errorMessages: errorMessages, 1954 type: response.update 1955 } ); 1956 1957 $bulkActionNotice = $( '#bulk-action-notice' ).on( 'click', 'button', function() { 1958 $bulkActionNotice.find( 'ul' ).toggleClass( 'hidden' ); 1959 } ); 1960 1961 if ( error > 0 && ! wp.updates.queue.length ) { 1962 $( 'html, body' ).animate( { scrollTop: 0 } ); 1963 } 1964 } ); 1965 1966 // Reset admin notice template after #bulk-action-notice was added. 1967 $document.on( 'wp-updates-notice-added', function() { 1968 wp.updates.adminNotice = wp.template( 'wp-updates-admin-notice' ); 1969 } ); 1970 1971 // Check the queue, now that the event handlers have been added. 1972 wp.updates.queueChecker(); 1973 } ); 1974 1975 /** 1976 * Handles changes to the plugin search box on the new-plugin page, 1977 * searching the repository dynamically. 1978 * 1979 * @since 4.6.0 1980 */ 1981 $( 'input.wp-filter-search, .wp-filter input[name="s"]' ).on( 'keyup search', _.debounce( function() { 1982 var $form = $( '#plugin-filter' ).empty(), 1983 data = _.extend( { 1984 _ajax_nonce: wp.updates.ajaxNonce, 1985 s: $( '<p />' ).html( $( this ).val() ).text(), 1986 tab: 'search', 1987 type: $( '#typeselector' ).val() 1988 }, { type: 'term' } ); 1989 1990 if ( wp.updates.searchTerm === data.s ) { 1991 return; 1992 } else { 1993 wp.updates.searchTerm = data.s; 1994 } 1995 1996 history.pushState( null, '', location.href.split( '?' )[0] + '?' + $.param( _.omit( data, '_ajax_nonce' ) ) ); 1997 1998 if ( 'undefined' !== typeof wp.updates.searchRequest ) { 1999 wp.updates.searchRequest.abort(); 2000 } 2001 $( 'body' ).addClass( 'loading-content' ); 2002 2003 wp.updates.searchRequest = wp.ajax.post( 'search-install-plugins', data ).done( function( response ) { 2004 $( 'body' ).removeClass( 'loading-content' ); 2005 $form.append( response.items ); 2006 delete wp.updates.searchRequest; 2007 } ); 2008 }, 500 ) ); 2009 2010 /** 2011 * Handles changes to the plugin search box on the Installed Plugins screen, 2012 * searching the plugin list dynamically. 2013 * 2014 * @since 4.6.0 2015 */ 2016 $( '#plugin-search-input' ).on( 'keyup search', _.debounce( function() { 2017 var data = { 2018 _ajax_nonce: wp.updates.ajaxNonce, 2019 s: $( '<p />' ).html( $( this ).val() ).text() 2020 }; 2021 2022 if ( wp.updates.searchTerm === data.s ) { 2023 return; 2024 } else { 2025 wp.updates.searchTerm = data.s; 2026 } 2027 2028 history.pushState( null, '', location.href.split( '?' )[0] + '?s=' + data.s ); 2029 2030 if ( 'undefined' !== typeof wp.updates.searchRequest ) { 2031 wp.updates.searchRequest.abort(); 2032 } 2033 2034 $bulkActionForm.empty(); 2035 $( 'body' ).addClass( 'loading-content' ); 2036 2037 wp.updates.searchRequest = wp.ajax.post( 'search-plugins', data ).done( function( response ) { 2038 2039 // Can we just ditch this whole subtitle business? 2040 var $subTitle = $( '<span />' ).addClass( 'subtitle' ).html( wp.updates.l10n.searchResults.replace( '%s', data.s ) ), 2041 $oldSubTitle = $( '.wrap .subtitle' ); 2042 2043 if ( ! data.s.length ) { 2044 $oldSubTitle.remove(); 2045 } else if ( $oldSubTitle.length ) { 2046 $oldSubTitle.replaceWith( $subTitle ); 2047 } else { 2048 $( '.wrap h1' ).append( $subTitle ); 2049 } 2050 2051 $( 'body' ).removeClass( 'loading-content' ); 2052 $bulkActionForm.append( response.items ); 2053 delete wp.updates.searchRequest; 2054 } ); 2055 }, 500 ) ); 2056 2057 /** 2058 * Trigger a search event when the search form gets submitted. 2059 * 2060 * @since 4.6.0 2061 */ 2062 $document.on( 'submit', '.search-plugins', function( event ) { 2063 event.preventDefault(); 2064 2065 $( 'input.wp-filter-search' ).trigger( 'search' ); 2066 } ); 2067 2068 /** 2069 * Trigger a search event when the search type gets changed. 2070 * 2071 * @since 4.6.0 2072 */ 2073 $( '#typeselector' ).on( 'change', function() { 2074 $( 'input[name="s"]' ).trigger( 'search' ); 2075 } ); 2076 2077 /** 2078 * Click handler for updating a plugin from the details modal on `plugin-install.php`. 2079 * 2080 * @since 4.2.0 2081 * 2082 * @param {Event} event Event interface. 2083 */ 2084 $( '#plugin_update_from_iframe' ).on( 'click', function( event ) { 2085 var target = window.parent === window ? null : window.parent, 2086 update; 2087 546 2088 $.support.postMessage = !! window.postMessage; 547 2089 548 if ( $.support.postMessage === false || target === null || window.parent.location.pathname.indexOf( 'update-core.php' ) !== -1 )2090 if ( false === $.support.postMessage || null === target ) { 549 2091 return; 550 551 e.preventDefault(); 552 553 job = { 554 action: 'updatePlugin',555 type: 'update-plugin',556 data: {2092 } 2093 2094 event.preventDefault(); 2095 2096 update = { 2097 action: 'update-plugin', 2098 data: { 557 2099 plugin: $( this ).data( 'plugin' ), 2100 slug: $( this ).data( 'slug' ) 2101 } 2102 }; 2103 2104 target.postMessage( JSON.stringify( update ), window.location.origin ); 2105 } ); 2106 2107 /** 2108 * Click handler for installing a plugin from the details modal on `plugin-install.php`. 2109 * 2110 * @since 4.6.0 2111 * 2112 * @param {Event} event Event interface. 2113 */ 2114 $( '#plugin_install_from_iframe' ).on( 'click', function( event ) { 2115 var target = window.parent === window ? null : window.parent, 2116 install; 2117 2118 $.support.postMessage = !! window.postMessage; 2119 2120 if ( false === $.support.postMessage || null === target ) { 2121 return; 2122 } 2123 2124 event.preventDefault(); 2125 2126 install = { 2127 action: 'install-plugin', 2128 data: { 558 2129 slug: $( this ).data( 'slug' ) 559 2130 } 560 2131 }; 561 2132 562 target.postMessage( JSON.stringify( job ), window.location.origin ); 563 }); 564 2133 target.postMessage( JSON.stringify( install ), window.location.origin ); 2134 } ); 2135 2136 /** 2137 * Handles postMessage events. 2138 * 2139 * @since 4.2.0 2140 * @since 4.6.0 Switched `update-plugin` action to use the queue. 2141 * 2142 * @param {Event} event Event interface. 2143 */ 2144 $( window ).on( 'message', function( event ) { 2145 var originalEvent = event.originalEvent, 2146 expectedOrigin = document.location.protocol + '//' + document.location.hostname, 2147 message; 2148 2149 if ( originalEvent.origin !== expectedOrigin ) { 2150 return; 2151 } 2152 2153 message = $.parseJSON( originalEvent.data ); 2154 2155 if ( 'undefined' === typeof message.action ) { 2156 return; 2157 } 2158 2159 switch ( message.action ) { 2160 2161 // Called from `wp-admin/includes/class-wp-upgrader-skins.php`. 2162 case 'decrementUpdateCount': 2163 /** @property {string} message.upgradeType */ 2164 wp.updates.decrementCount( message.upgradeType ); 2165 break; 2166 2167 case 'install-plugin': 2168 case 'update-plugin': 2169 /* jscs:disable requireCamelCaseOrUpperCaseIdentifiers */ 2170 window.tb_remove(); 2171 /* jscs:enable */ 2172 2173 message.data = wp.updates._addCallbacks( message.data, message.action ); 2174 2175 wp.updates.queue.push( message ); 2176 wp.updates.queueChecker(); 2177 break; 2178 } 2179 } ); 2180 2181 /** 2182 * Adds a callback to display a warning before leaving the page. 2183 * 2184 * @since 4.2.0 2185 */ 2186 $( window ).on( 'beforeunload', wp.updates.beforeunload ); 565 2187 } ); 566 567 $( window ).on( 'message', function( e ) { 568 var event = e.originalEvent, 569 message, 570 loc = document.location, 571 expectedOrigin = loc.protocol + '//' + loc.hostname; 572 573 if ( event.origin !== expectedOrigin ) { 574 return; 575 } 576 577 message = $.parseJSON( event.data ); 578 579 if ( typeof message.action === 'undefined' ) { 580 return; 581 } 582 583 switch (message.action){ 584 case 'decrementUpdateCount' : 585 wp.updates.decrementCount( message.upgradeType ); 586 break; 587 case 'updatePlugin' : 588 tb_remove(); 589 590 wp.updates.updateQueue.push( message ); 591 wp.updates.queueChecker(); 592 break; 593 } 594 595 } ); 596 597 $( window ).on( 'beforeunload', wp.updates.beforeunload ); 598 599 })( jQuery, window.wp, window.pagenow, window.ajaxurl ); 2188 })( jQuery, window.wp, _.extend( window._wpUpdatesSettings, window._wpUpdatesItemCounts || {} ) ); -
trunk/src/wp-admin/network/themes.php
r37202 r37714 229 229 $parent_file = 'themes.php'; 230 230 231 wp_enqueue_script( 'updates' ); 231 232 wp_enqueue_script( 'theme-preview' ); 232 233 … … 288 289 ?> 289 290 290 <form method="post">291 <form id="bulk-action-form" method="post"> 291 292 <input type="hidden" name="theme_status" value="<?php echo esc_attr($status) ?>" /> 292 293 <input type="hidden" name="paged" value="<?php echo esc_attr($page) ?>" /> … … 298 299 299 300 <?php 301 wp_print_request_filesystem_credentials_modal(); 302 wp_print_admin_notice_templates(); 303 wp_print_update_row_templates(); 304 300 305 include(ABSPATH . 'wp-admin/admin-footer.php'); -
trunk/src/wp-admin/plugin-install.php
r37680 r37714 149 149 */ 150 150 do_action( "install_plugins_$tab", $paged ); ?> 151 152 <span class="spinner"></span> 151 153 </div> 152 154 153 155 <?php 154 156 wp_print_request_filesystem_credentials_modal(); 157 wp_print_admin_notice_templates(); 155 158 156 159 /** -
trunk/src/wp-admin/plugins.php
r37221 r37714 508 508 <?php $wp_list_table->views(); ?> 509 509 510 <form method="get">510 <form class="search-form search-plugins" method="get"> 511 511 <?php $wp_list_table->search_box( __( 'Search Installed Plugins' ), 'plugin' ); ?> 512 512 </form> … … 520 520 </form> 521 521 522 <span class="spinner"></span> 522 523 </div> 523 524 524 525 <?php 525 526 wp_print_request_filesystem_credentials_modal(); 527 wp_print_admin_notice_templates(); 528 wp_print_update_row_templates(); 526 529 527 530 include(ABSPATH . 'wp-admin/admin-footer.php'); -
trunk/src/wp-admin/theme-install.php
r37488 r37714 59 59 60 60 wp_enqueue_script( 'theme' ); 61 wp_enqueue_script( 'updates' ); 61 62 62 63 if ( $tab ) { … … 235 236 <# } #> 236 237 <span class="more-details"><?php _ex( 'Details & Preview', 'theme' ); ?></span> 237 <div class="theme-author"><?php printf( __( 'By %s' ), '{{ data.author }}' ); ?></div> 238 <div class="theme-author"> 239 <?php 240 /* translators: %s: Theme author name */ 241 printf( __( 'By %s' ), '{{ data.author }}' ); 242 ?> 243 </div> 238 244 <h3 class="theme-name">{{ data.name }}</h3> 239 245 240 246 <div class="theme-actions"> 241 <a class="button button-primary" href="{{ data.install_url }}"><?php esc_html_e( 'Install' ); ?></a> 242 <a class="button button-secondary preview install-theme-preview" href="#"><?php esc_html_e( 'Preview' ); ?></a> 247 <# if ( data.installed ) { #> 248 <# if ( data.activate_url ) { #> 249 <a class="button button-primary activate" href="{{ data.activate_url }}"><?php esc_html_e( 'Activate' ); ?></a> 250 <# } #> 251 <# if ( data.customize_url ) { #> 252 <a class="button button-secondary load-customize" href="{{ data.customize_url }}"><?php esc_html_e( 'Live Preview' ); ?></a> 253 <# } else { #> 254 <button class="button-secondary preview install-theme-preview"><?php esc_html_e( 'Preview' ); ?></button> 255 <# } #> 256 <# } else { #> 257 <a class="button button-primary theme-install" data-slug="{{ data.id }}" href="{{ data.install_url }}"><?php esc_html_e( 'Install' ); ?></a> 258 <button class="button-secondary preview install-theme-preview"><?php esc_html_e( 'Preview' ); ?></button> 259 <# } #> 243 260 </div> 244 261 245 262 <# if ( data.installed ) { #> 246 <div class=" theme-installed"><?php _ex( 'Already Installed', 'theme' ); ?></div>263 <div class="notice notice-success notice-alt"><p><?php _ex( 'Installed', 'theme' ); ?></p></div> 247 264 <# } #> 248 265 </script> … … 251 268 <div class="wp-full-overlay-sidebar"> 252 269 <div class="wp-full-overlay-header"> 253 < a href="#" class="close-full-overlay"><span class="screen-reader-text"><?php _e( 'Close' ); ?></span></a>254 < a href="#" class="previous-theme"><span class="screen-reader-text"><?php _ex( 'Previous', 'Button label for a theme' ); ?></span></a>255 < a href="#" class="next-theme"><span class="screen-reader-text"><?php _ex( 'Next', 'Button label for a theme' ); ?></span></a>256 <# if ( data.installed ) { #>257 <a href="#" class="button button-primary theme-install disabled"><?php _ex( 'Installed', 'theme' ); ?></a>258 <# } else { #>259 <a href="{{ data.install_url }}" class="button button-primary theme-install"><?php _e( 'Install' ); ?></a>260 <# } #>270 <button class="close-full-overlay"><span class="screen-reader-text"><?php _e( 'Close' ); ?></span></button> 271 <button class="previous-theme"><span class="screen-reader-text"><?php _ex( 'Previous', 'Button label for a theme' ); ?></span></button> 272 <button class="next-theme"><span class="screen-reader-text"><?php _ex( 'Next', 'Button label for a theme' ); ?></span></button> 273 <# if ( data.installed ) { #> 274 <a class="button button-primary activate" href="{{ data.activate_url }}"><?php esc_html_e( 'Activate' ); ?></a> 275 <# } else { #> 276 <a href="{{ data.install_url }}" class="button button-primary theme-install" data-slug="{{ data.id }}"><?php _e( 'Install' ); ?></a> 277 <# } #> 261 278 </div> 262 279 <div class="wp-full-overlay-sidebar-content"> 263 280 <div class="install-theme-info"> 264 281 <h3 class="theme-name">{{ data.name }}</h3> 265 <span class="theme-by"><?php printf( __( 'By %s' ), '{{ data.author }}' ); ?></span> 266 267 <img class="theme-screenshot" src="{{ data.screenshot_url }}" alt="" /> 268 269 <div class="theme-details"> 270 <# if ( data.rating ) { #> 271 <div class="theme-rating"> 272 {{{ data.stars }}} 273 <span class="num-ratings" aria-hidden="true">({{ data.num_ratings }})</span> 282 <span class="theme-by"> 283 <?php 284 /* translators: %s: Theme author name */ 285 printf( __( 'By %s' ), '{{ data.author }}' ); 286 ?> 287 </span> 288 289 <img class="theme-screenshot" src="{{ data.screenshot_url }}" alt="" /> 290 291 <div class="theme-details"> 292 <# if ( data.rating ) { #> 293 <div class="theme-rating"> 294 {{{ data.stars }}} 295 <span class="num-ratings">({{ data.num_ratings }})</span> 296 </div> 297 <# } else { #> 298 <span class="no-rating"><?php _e( 'This theme has not been rated yet.' ); ?></span> 299 <# } #> 300 <div class="theme-version"> 301 <?php 302 /* translators: %s: Theme version */ 303 printf( __( 'Version: %s' ), '{{ data.version }}' ); 304 ?> 274 305 </div> 275 <# } else { #> 276 <span class="no-rating"><?php _e( 'This theme has not been rated yet.' ); ?></span> 277 <# } #> 278 <div class="theme-version"><?php printf( __( 'Version: %s' ), '{{ data.version }}' ); ?></div> 279 <div class="theme-description">{{{ data.description }}}</div> 306 <div class="theme-description">{{{ data.description }}}</div> 307 </div> 280 308 </div> 281 309 </div> 282 </div> 283 <div class="wp-full-overlay-footer"> 284 <div class="devices"> 285 <button type="button" class="preview-desktop active" aria-pressed="true" data-device="desktop"><span class="screen-reader-text"><?php _e( 'Enter desktop preview mode' ); ?></span></button> 286 <button type="button" class="preview-tablet" aria-pressed="false" data-device="tablet"><span class="screen-reader-text"><?php _e( 'Enter tablet preview mode' ); ?></span></button> 287 <button type="button" class="preview-mobile" aria-pressed="false" data-device="mobile"><span class="screen-reader-text"><?php _e( 'Enter mobile preview mode' ); ?></span></button> 310 <div class="wp-full-overlay-footer"> 311 <button type="button" class="collapse-sidebar button-secondary" aria-expanded="true" aria-label="<?php esc_attr_e( 'Collapse Sidebar' ); ?>"> 312 <span class="collapse-sidebar-arrow"></span> 313 <span class="collapse-sidebar-label"><?php _e( 'Collapse' ); ?></span> 314 </button> 288 315 </div> 289 <button type="button" class="collapse-sidebar button-secondary" aria-expanded="true" aria-label="<?php esc_attr_e( 'Collapse Sidebar' ); ?>"> 290 <span class="collapse-sidebar-arrow"></span> 291 <span class="collapse-sidebar-label"><?php _e( 'Collapse' ); ?></span> 292 </button> 293 </div> 294 </div> 295 <div class="wp-full-overlay-main"> 296 <iframe src="{{ data.preview_url }}" title="<?php esc_attr_e( 'Preview' ); ?>" /> 316 </div> 317 <div class="wp-full-overlay-main"> 318 <iframe src="{{ data.preview_url }}" title="<?php esc_attr_e( 'Preview' ); ?>"></iframe> 297 319 </div> 298 320 </script> 299 321 300 322 <?php 323 wp_print_request_filesystem_credentials_modal(); 324 wp_print_admin_notice_templates(); 325 301 326 include(ABSPATH . 'wp-admin/admin-footer.php'); -
trunk/src/wp-admin/themes.php
r37297 r37714 146 146 add_thickbox(); 147 147 wp_enqueue_script( 'theme' ); 148 wp_enqueue_script( 'updates' ); 148 149 wp_enqueue_script( 'customize-loader' ); 149 150 … … 249 250 <div class="theme-screenshot blank"></div> 250 251 <?php } ?> 252 253 <?php if ( $theme['hasUpdate'] ) : ?> 254 <div class="update-message notice inline notice-warning notice-alt"> 255 <p><?php _e( 'New version available. <button class="button-link" type="button">Update now</button>' ); ?></p> 256 </div> 257 <?php endif; ?> 258 251 259 <span class="more-details" id="<?php echo $aria_action; ?>"><?php _e( 'Theme Details' ); ?></span> 252 260 <div class="theme-author"><?php printf( __( 'By %s' ), $theme['author'] ); ?></div> … … 277 285 278 286 </div> 279 280 <?php if ( $theme['hasUpdate'] ) { ?>281 <div class="theme-update"><?php _e( 'Update Available' ); ?></div>282 <?php } ?>283 287 </div> 284 288 <?php endforeach; ?> … … 369 373 <div class="theme-screenshot blank"></div> 370 374 <# } #> 375 376 <# if ( data.hasUpdate ) { #> 377 <div class="update-message notice inline notice-warning notice-alt"><p><?php _e( 'New version available. <button class="button-link" type="button">Update now</button>' ); ?></p></div> 378 <# } #> 379 371 380 <span class="more-details" id="{{ data.id }}-action"><?php _e( 'Theme Details' ); ?></span> 372 <div class="theme-author"><?php printf( __( 'By %s' ), '{{{ data.author }}}' ); ?></div> 381 <div class="theme-author"> 382 <?php 383 /* translators: %s: Theme author name */ 384 printf( __( 'By %s' ), '{{{ data.author }}}' ); 385 ?> 386 </div> 373 387 374 388 <# if ( data.active ) { #> 375 389 <h2 class="theme-name" id="{{ data.id }}-name"> 376 390 <?php 377 /* translators: %s: theme name */391 /* translators: %s: Theme name */ 378 392 printf( __( '<span>Active:</span> %s' ), '{{{ data.name }}}' ); 379 393 ?> … … 384 398 385 399 <div class="theme-actions"> 386 387 <# if ( data.active ) { #> 388 <# if ( data.actions.customize ) { #> 389 <a class="button button-primary customize load-customize hide-if-no-customize" href="{{{ data.actions.customize }}}"><?php _e( 'Customize' ); ?></a> 400 <# if ( data.active ) { #> 401 <# if ( data.actions.customize ) { #> 402 <a class="button button-primary customize load-customize hide-if-no-customize" href="{{{ data.actions.customize }}}"><?php _e( 'Customize' ); ?></a> 403 <# } #> 404 <# } else { #> 405 <a class="button button-secondary activate" href="{{{ data.actions.activate }}}"><?php _e( 'Activate' ); ?></a> 406 <a class="button button-primary load-customize hide-if-no-customize" href="{{{ data.actions.customize }}}"><?php _e( 'Live Preview' ); ?></a> 390 407 <# } #> 391 <# } else { #>392 <a class="button button-secondary activate" href="{{{ data.actions.activate }}}"><?php _e( 'Activate' ); ?></a>393 <a class="button button-primary load-customize hide-if-no-customize" href="{{{ data.actions.customize }}}"><?php _e( 'Live Preview' ); ?></a>394 <# } #>395 396 408 </div> 397 398 <# if ( data.hasUpdate ) { #>399 <div class="theme-update"><?php _e( 'Update Available' ); ?></div>400 <# } #>401 409 </script> 402 410 … … 462 470 </script> 463 471 464 <?php require( ABSPATH . 'wp-admin/admin-footer.php' ); 472 <?php 473 wp_print_request_filesystem_credentials_modal(); 474 wp_print_admin_notice_templates(); 475 wp_print_update_row_templates(); 476 477 require( ABSPATH . 'wp-admin/admin-footer.php' ); -
trunk/src/wp-content/themes/twentyten/languages/twentyten.pot
r37168 r37714 1 # Copyright (C) 201 6the WordPress team1 # Copyright (C) 2015 the WordPress team 2 2 # This file is distributed under the GNU General Public License v2 or later. 3 3 msgid "" … … 5 5 "Project-Id-Version: Twenty Ten 2.1\n" 6 6 "Report-Msgid-Bugs-To: https://wordpress.org/support/theme/twentyten\n" 7 "POT-Creation-Date: 201 6-04-05 09:48:39+00:00\n"7 "POT-Creation-Date: 2015-12-08 15:15:12+00:00\n" 8 8 "MIME-Version: 1.0\n" 9 9 "Content-Type: text/plain; charset=UTF-8\n" 10 10 "Content-Transfer-Encoding: 8bit\n" 11 "PO-Revision-Date: 201 6-MO-DA HO:MI+ZONE\n"11 "PO-Revision-Date: 2015-MO-DA HO:MI+ZONE\n" 12 12 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" 13 13 "Language-Team: LANGUAGE <LL@li.org>\n" -
trunk/src/wp-includes/js/wp-util.js
r37431 r37714 49 49 * Sends a POST request to WordPress. 50 50 * 51 * @param {string} action The slug of the action to fire in WordPress. 52 * @param {object} data The data to populate $_POST with. 51 * @param {(string|object)} action The slug of the action to fire in WordPress or options passed 52 * to jQuery.ajax. 53 * @param {object=} data Optional. The data to populate $_POST with. 53 54 * @return {$.promise} A jQuery promise that represents the request, 54 55 * decorated with an abort() method. … … 65 66 * Sends a POST request to WordPress. 66 67 * 67 * @param {string} action The slug of the action to fire in WordPress. 68 * @param {object} options The options passed to jQuery.ajax. 68 * @param {(string|object)} action The slug of the action to fire in WordPress or options passed 69 * to jQuery.ajax. 70 * @param {object=} options Optional. The options passed to jQuery.ajax. 69 71 * @return {$.promise} A jQuery promise that represents the request, 70 72 * decorated with an abort() method. -
trunk/src/wp-includes/script-loader.php
r37526 r37714 596 596 'ajax_nonce' => wp_create_nonce( 'updates' ), 597 597 'l10n' => array( 598 'updating' => __( 'Updating...' ), // no ellipsis 599 'updated' => __( 'Updated!' ), 600 'updateFailedShort' => __( 'Update Failed!' ), 598 /* translators: %s: Search string */ 599 'searchResults' => __( 'Search results for “%s”' ), 600 'noPlugins' => __( 'You do not appear to have any plugins available at this time.' ), 601 'noItemsSelected' => __( 'Please select at least one item to perform this action on.' ), 602 'updating' => __( 'Updating...' ), // No ellipsis. 603 'updated' => __( 'Updated!' ), 604 'update' => __( 'Update' ), 605 'updateNow' => __( 'Update Now' ), 606 'updateFailedShort' => __( 'Update Failed!' ), 601 607 /* translators: Error string for a failed update */ 602 'updateFailed' => __( 'Update Failed: %s' ),608 'updateFailed' => __( 'Update Failed: %s' ), 603 609 /* translators: Plugin name and version */ 604 'updatingLabel' => __( 'Updating %s...' ), // no ellipsis610 'updatingLabel' => __( 'Updating %s...' ), // No ellipsis. 605 611 /* translators: Plugin name and version */ 606 'updatedLabel' => __( '%s updated!' ),612 'updatedLabel' => __( '%s updated!' ), 607 613 /* translators: Plugin name and version */ 608 'updateFailedLabel' => __( '%s update failed' ),614 'updateFailedLabel' => __( '%s update failed' ), 609 615 /* translators: JavaScript accessible string */ 610 'updatingMsg' &nb