[ Index ] |
PHP Cross Reference of WordPress Trunk (Updated Daily) |
[Summary view] [Print] [Text view]
1 /** 2 * Functions for ajaxified updates, deletions and installs inside the WordPress admin. 3 * 4 * @version 4.2.0 5 * @output wp-admin/js/updates.js 6 */ 7 8 /* global pagenow, _wpThemeSettings */ 9 10 /** 11 * @param {jQuery} $ jQuery object. 12 * @param {object} wp WP object. 13 * @param {object} settings WP Updates settings. 14 * @param {string} settings.ajax_nonce Ajax nonce. 15 * @param {object=} settings.plugins Base names of plugins in their different states. 16 * @param {Array} settings.plugins.all Base names of all plugins. 17 * @param {Array} settings.plugins.active Base names of active plugins. 18 * @param {Array} settings.plugins.inactive Base names of inactive plugins. 19 * @param {Array} settings.plugins.upgrade Base names of plugins with updates available. 20 * @param {Array} settings.plugins.recently_activated Base names of recently activated plugins. 21 * @param {Array} settings.plugins['auto-update-enabled'] Base names of plugins set to auto-update. 22 * @param {Array} settings.plugins['auto-update-disabled'] Base names of plugins set to not auto-update. 23 * @param {object=} settings.themes Slugs of themes in their different states. 24 * @param {Array} settings.themes.all Slugs of all themes. 25 * @param {Array} settings.themes.upgrade Slugs of themes with updates available. 26 * @param {Arrat} settings.themes.disabled Slugs of disabled themes. 27 * @param {Array} settings.themes['auto-update-enabled'] Slugs of themes set to auto-update. 28 * @param {Array} settings.themes['auto-update-disabled'] Slugs of themes set to not auto-update. 29 * @param {object=} settings.totals Combined information for available update counts. 30 * @param {number} settings.totals.count Holds the amount of available updates. 31 */ 32 (function( $, wp, settings ) { 33 var $document = $( document ), 34 __ = wp.i18n.__, 35 _x = wp.i18n._x, 36 _n = wp.i18n._n, 37 _nx = wp.i18n._nx, 38 sprintf = wp.i18n.sprintf; 39 40 wp = wp || {}; 41 42 /** 43 * The WP Updates object. 44 * 45 * @since 4.2.0 46 * 47 * @namespace wp.updates 48 */ 49 wp.updates = {}; 50 51 /** 52 * Removed in 5.5.0, needed for back-compatibility. 53 * 54 * @since 4.2.0 55 * @deprecated 5.5.0 56 * 57 * @type {object} 58 */ 59 wp.updates.l10n = { 60 searchResults: '', 61 searchResultsLabel: '', 62 noPlugins: '', 63 noItemsSelected: '', 64 updating: '', 65 pluginUpdated: '', 66 themeUpdated: '', 67 update: '', 68 updateNow: '', 69 pluginUpdateNowLabel: '', 70 updateFailedShort: '', 71 updateFailed: '', 72 pluginUpdatingLabel: '', 73 pluginUpdatedLabel: '', 74 pluginUpdateFailedLabel: '', 75 updatingMsg: '', 76 updatedMsg: '', 77 updateCancel: '', 78 beforeunload: '', 79 installNow: '', 80 pluginInstallNowLabel: '', 81 installing: '', 82 pluginInstalled: '', 83 themeInstalled: '', 84 installFailedShort: '', 85 installFailed: '', 86 pluginInstallingLabel: '', 87 themeInstallingLabel: '', 88 pluginInstalledLabel: '', 89 themeInstalledLabel: '', 90 pluginInstallFailedLabel: '', 91 themeInstallFailedLabel: '', 92 installingMsg: '', 93 installedMsg: '', 94 importerInstalledMsg: '', 95 aysDelete: '', 96 aysDeleteUninstall: '', 97 aysBulkDelete: '', 98 aysBulkDeleteThemes: '', 99 deleting: '', 100 deleteFailed: '', 101 pluginDeleted: '', 102 themeDeleted: '', 103 livePreview: '', 104 activatePlugin: '', 105 activateTheme: '', 106 activatePluginLabel: '', 107 activateThemeLabel: '', 108 activateImporter: '', 109 activateImporterLabel: '', 110 unknownError: '', 111 connectionError: '', 112 nonceError: '', 113 pluginsFound: '', 114 noPluginsFound: '', 115 autoUpdatesEnable: '', 116 autoUpdatesEnabling: '', 117 autoUpdatesEnabled: '', 118 autoUpdatesDisable: '', 119 autoUpdatesDisabling: '', 120 autoUpdatesDisabled: '', 121 autoUpdatesError: '' 122 }; 123 124 wp.updates.l10n = window.wp.deprecateL10nObject( 'wp.updates.l10n', wp.updates.l10n, '5.5.0' ); 125 126 /** 127 * User nonce for ajax calls. 128 * 129 * @since 4.2.0 130 * 131 * @type {string} 132 */ 133 wp.updates.ajaxNonce = settings.ajax_nonce; 134 135 /** 136 * Current search term. 137 * 138 * @since 4.6.0 139 * 140 * @type {string} 141 */ 142 wp.updates.searchTerm = ''; 143 144 /** 145 * Whether filesystem credentials need to be requested from the user. 146 * 147 * @since 4.2.0 148 * 149 * @type {bool} 150 */ 151 wp.updates.shouldRequestFilesystemCredentials = false; 152 153 /** 154 * Filesystem credentials to be packaged along with the request. 155 * 156 * @since 4.2.0 157 * @since 4.6.0 Added `available` property to indicate whether credentials have been provided. 158 * 159 * @type {Object} 160 * @property {Object} filesystemCredentials.ftp Holds FTP credentials. 161 * @property {string} filesystemCredentials.ftp.host FTP host. Default empty string. 162 * @property {string} filesystemCredentials.ftp.username FTP user name. Default empty string. 163 * @property {string} filesystemCredentials.ftp.password FTP password. Default empty string. 164 * @property {string} filesystemCredentials.ftp.connectionType Type of FTP connection. 'ssh', 'ftp', or 'ftps'. 165 * Default empty string. 166 * @property {Object} filesystemCredentials.ssh Holds SSH credentials. 167 * @property {string} filesystemCredentials.ssh.publicKey The public key. Default empty string. 168 * @property {string} filesystemCredentials.ssh.privateKey The private key. Default empty string. 169 * @property {string} filesystemCredentials.fsNonce Filesystem credentials form nonce. 170 * @property {bool} filesystemCredentials.available Whether filesystem credentials have been provided. 171 * Default 'false'. 172 */ 173 wp.updates.filesystemCredentials = { 174 ftp: { 175 host: '', 176 username: '', 177 password: '', 178 connectionType: '' 179 }, 180 ssh: { 181 publicKey: '', 182 privateKey: '' 183 }, 184 fsNonce: '', 185 available: false 186 }; 187 188 /** 189 * Whether we're waiting for an Ajax request to complete. 190 * 191 * @since 4.2.0 192 * @since 4.6.0 More accurately named `ajaxLocked`. 193 * 194 * @type {bool} 195 */ 196 wp.updates.ajaxLocked = false; 197 198 /** 199 * Admin notice template. 200 * 201 * @since 4.6.0 202 * 203 * @type {function} 204 */ 205 wp.updates.adminNotice = wp.template( 'wp-updates-admin-notice' ); 206 207 /** 208 * Update queue. 209 * 210 * If the user tries to update a plugin while an update is 211 * already happening, it can be placed in this queue to perform later. 212 * 213 * @since 4.2.0 214 * @since 4.6.0 More accurately named `queue`. 215 * 216 * @type {Array.object} 217 */ 218 wp.updates.queue = []; 219 220 /** 221 * Holds a jQuery reference to return focus to when exiting the request credentials modal. 222 * 223 * @since 4.2.0 224 * 225 * @type {jQuery} 226 */ 227 wp.updates.$elToReturnFocusToFromCredentialsModal = undefined; 228 229 /** 230 * Adds or updates an admin notice. 231 * 232 * @since 4.6.0 233 * 234 * @param {Object} data 235 * @param {*=} data.selector Optional. Selector of an element to be replaced with the admin notice. 236 * @param {string=} data.id Optional. Unique id that will be used as the notice's id attribute. 237 * @param {string=} data.className Optional. Class names that will be used in the admin notice. 238 * @param {string=} data.message Optional. The message displayed in the notice. 239 * @param {number=} data.successes Optional. The amount of successful operations. 240 * @param {number=} data.errors Optional. The amount of failed operations. 241 * @param {Array=} data.errorMessages Optional. Error messages of failed operations. 242 * 243 */ 244 wp.updates.addAdminNotice = function( data ) { 245 var $notice = $( data.selector ), 246 $headerEnd = $( '.wp-header-end' ), 247 $adminNotice; 248 249 delete data.selector; 250 $adminNotice = wp.updates.adminNotice( data ); 251 252 // Check if this admin notice already exists. 253 if ( ! $notice.length ) { 254 $notice = $( '#' + data.id ); 255 } 256 257 if ( $notice.length ) { 258 $notice.replaceWith( $adminNotice ); 259 } else if ( $headerEnd.length ) { 260 $headerEnd.after( $adminNotice ); 261 } else { 262 if ( 'customize' === pagenow ) { 263 $( '.customize-themes-notifications' ).append( $adminNotice ); 264 } else { 265 $( '.wrap' ).find( '> h1' ).after( $adminNotice ); 266 } 267 } 268 269 $document.trigger( 'wp-updates-notice-added' ); 270 }; 271 272 /** 273 * Handles Ajax requests to WordPress. 274 * 275 * @since 4.6.0 276 * 277 * @param {string} action The type of Ajax request ('update-plugin', 'install-theme', etc). 278 * @param {Object} data Data that needs to be passed to the ajax callback. 279 * @return {$.promise} A jQuery promise that represents the request, 280 * decorated with an abort() method. 281 */ 282 wp.updates.ajax = function( action, data ) { 283 var options = {}; 284 285 if ( wp.updates.ajaxLocked ) { 286 wp.updates.queue.push( { 287 action: action, 288 data: data 289 } ); 290 291 // Return a Deferred object so callbacks can always be registered. 292 return $.Deferred(); 293 } 294 295 wp.updates.ajaxLocked = true; 296 297 if ( data.success ) { 298 options.success = data.success; 299 delete data.success; 300 } 301 302 if ( data.error ) { 303 options.error = data.error; 304 delete data.error; 305 } 306 307 options.data = _.extend( data, { 308 action: action, 309 _ajax_nonce: wp.updates.ajaxNonce, 310 _fs_nonce: wp.updates.filesystemCredentials.fsNonce, 311 username: wp.updates.filesystemCredentials.ftp.username, 312 password: wp.updates.filesystemCredentials.ftp.password, 313 hostname: wp.updates.filesystemCredentials.ftp.hostname, 314 connection_type: wp.updates.filesystemCredentials.ftp.connectionType, 315 public_key: wp.updates.filesystemCredentials.ssh.publicKey, 316 private_key: wp.updates.filesystemCredentials.ssh.privateKey 317 } ); 318 319 return wp.ajax.send( options ).always( wp.updates.ajaxAlways ); 320 }; 321 322 /** 323 * Actions performed after every Ajax request. 324 * 325 * @since 4.6.0 326 * 327 * @param {Object} response 328 * @param {Array=} response.debug Optional. Debug information. 329 * @param {string=} response.errorCode Optional. Error code for an error that occurred. 330 */ 331 wp.updates.ajaxAlways = function( response ) { 332 if ( ! response.errorCode || 'unable_to_connect_to_filesystem' !== response.errorCode ) { 333 wp.updates.ajaxLocked = false; 334 wp.updates.queueChecker(); 335 } 336 337 if ( 'undefined' !== typeof response.debug && window.console && window.console.log ) { 338 _.map( response.debug, function( message ) { 339 // Remove all HTML tags and write a message to the console. 340 window.console.log( wp.sanitize.stripTagsAndEncodeText( message ) ); 341 } ); 342 } 343 }; 344 345 /** 346 * Refreshes update counts everywhere on the screen. 347 * 348 * @since 4.7.0 349 */ 350 wp.updates.refreshCount = function() { 351 var $adminBarUpdates = $( '#wp-admin-bar-updates' ), 352 $dashboardNavMenuUpdateCount = $( 'a[href="update-core.php"] .update-plugins' ), 353 $pluginsNavMenuUpdateCount = $( 'a[href="plugins.php"] .update-plugins' ), 354 $appearanceNavMenuUpdateCount = $( 'a[href="themes.php"] .update-plugins' ), 355 itemCount; 356 357 $adminBarUpdates.find( '.ab-label' ).text( settings.totals.counts.total ); 358 $adminBarUpdates.find( '.updates-available-text' ).text( 359 sprintf( 360 /* translators: %s: Total number of updates available. */ 361 _n( '%s update available', '%s updates available', settings.totals.counts.total ), 362 settings.totals.counts.total 363 ) 364 ); 365 366 // Remove the update count from the toolbar if it's zero. 367 if ( 0 === settings.totals.counts.total ) { 368 $adminBarUpdates.find( '.ab-label' ).parents( 'li' ).remove(); 369 } 370 371 // Update the "Updates" menu item. 372 $dashboardNavMenuUpdateCount.each( function( index, element ) { 373 element.className = element.className.replace( /count-\d+/, 'count-' + settings.totals.counts.total ); 374 } ); 375 if ( settings.totals.counts.total > 0 ) { 376 $dashboardNavMenuUpdateCount.find( '.update-count' ).text( settings.totals.counts.total ); 377 } else { 378 $dashboardNavMenuUpdateCount.remove(); 379 } 380 381 // Update the "Plugins" menu item. 382 $pluginsNavMenuUpdateCount.each( function( index, element ) { 383 element.className = element.className.replace( /count-\d+/, 'count-' + settings.totals.counts.plugins ); 384 } ); 385 if ( settings.totals.counts.total > 0 ) { 386 $pluginsNavMenuUpdateCount.find( '.plugin-count' ).text( settings.totals.counts.plugins ); 387 } else { 388 $pluginsNavMenuUpdateCount.remove(); 389 } 390 391 // Update the "Appearance" menu item. 392 $appearanceNavMenuUpdateCount.each( function( index, element ) { 393 element.className = element.className.replace( /count-\d+/, 'count-' + settings.totals.counts.themes ); 394 } ); 395 if ( settings.totals.counts.total > 0 ) { 396 $appearanceNavMenuUpdateCount.find( '.theme-count' ).text( settings.totals.counts.themes ); 397 } else { 398 $appearanceNavMenuUpdateCount.remove(); 399 } 400 401 // Update list table filter navigation. 402 if ( 'plugins' === pagenow || 'plugins-network' === pagenow ) { 403 itemCount = settings.totals.counts.plugins; 404 } else if ( 'themes' === pagenow || 'themes-network' === pagenow ) { 405 itemCount = settings.totals.counts.themes; 406 } 407 408 if ( itemCount > 0 ) { 409 $( '.subsubsub .upgrade .count' ).text( '(' + itemCount + ')' ); 410 } else { 411 $( '.subsubsub .upgrade' ).remove(); 412 $( '.subsubsub li:last' ).html( function() { return $( this ).children(); } ); 413 } 414 }; 415 416 /** 417 * Sends a message from a modal to the main screen to update buttons in plugin cards. 418 * 419 * @since 6.5.0 420 * 421 * @param {Object} data An object of data to use for the button. 422 * @param {string} data.slug The plugin's slug. 423 * @param {string} data.text The text to use for the button. 424 * @param {string} data.ariaLabel The value for the button's aria-label attribute. An empty string removes the attribute. 425 * @param {string=} data.status Optional. An identifier for the status. 426 * @param {string=} data.removeClasses Optional. A space-separated list of classes to remove from the button. 427 * @param {string=} data.addClasses Optional. A space-separated list of classes to add to the button. 428 * @param {string=} data.href Optional. The button's URL. 429 * @param {string=} data.pluginName Optional. The plugin's name. 430 * @param {string=} data.plugin Optional. The plugin file, relative to the plugins directory. 431 */ 432 wp.updates.setCardButtonStatus = function( data ) { 433 var target = window.parent === window ? null : window.parent; 434 435 $.support.postMessage = !! window.postMessage; 436 if ( false !== $.support.postMessage && null !== target && -1 === window.parent.location.pathname.indexOf( 'index.php' ) ) { 437 target.postMessage( JSON.stringify( data ), window.location.origin ); 438 } 439 }; 440 441 /** 442 * Decrements the update counts throughout the various menus. 443 * 444 * This includes the toolbar, the "Updates" menu item and the menu items 445 * for plugins and themes. 446 * 447 * @since 3.9.0 448 * 449 * @param {string} type The type of item that was updated or deleted. 450 * Can be 'plugin', 'theme'. 451 */ 452 wp.updates.decrementCount = function( type ) { 453 settings.totals.counts.total = Math.max( --settings.totals.counts.total, 0 ); 454 455 if ( 'plugin' === type ) { 456 settings.totals.counts.plugins = Math.max( --settings.totals.counts.plugins, 0 ); 457 } else if ( 'theme' === type ) { 458 settings.totals.counts.themes = Math.max( --settings.totals.counts.themes, 0 ); 459 } 460 461 wp.updates.refreshCount( type ); 462 }; 463 464 /** 465 * Sends an Ajax request to the server to update a plugin. 466 * 467 * @since 4.2.0 468 * @since 4.6.0 More accurately named `updatePlugin`. 469 * 470 * @param {Object} args Arguments. 471 * @param {string} args.plugin Plugin basename. 472 * @param {string} args.slug Plugin slug. 473 * @param {updatePluginSuccess=} args.success Optional. Success callback. Default: wp.updates.updatePluginSuccess 474 * @param {updatePluginError=} args.error Optional. Error callback. Default: wp.updates.updatePluginError 475 * @return {$.promise} A jQuery promise that represents the request, 476 * decorated with an abort() method. 477 */ 478 wp.updates.updatePlugin = function( args ) { 479 var $updateRow, $card, $message, message, 480 $adminBarUpdates = $( '#wp-admin-bar-updates' ), 481 buttonText = __( 'Updating...' ), 482 isPluginInstall = 'plugin-install' === pagenow || 'plugin-install-network' === pagenow; 483 484 args = _.extend( { 485 success: wp.updates.updatePluginSuccess, 486 error: wp.updates.updatePluginError 487 }, args ); 488 489 if ( 'plugins' === pagenow || 'plugins-network' === pagenow ) { 490 $updateRow = $( 'tr[data-plugin="' + args.plugin + '"]' ); 491 $message = $updateRow.find( '.update-message' ).removeClass( 'notice-error' ).addClass( 'updating-message notice-warning' ).find( 'p' ); 492 message = sprintf( 493 /* translators: %s: Plugin name and version. */ 494 _x( 'Updating %s...', 'plugin' ), 495 $updateRow.find( '.plugin-title strong' ).text() 496 ); 497 } else if ( isPluginInstall ) { 498 $card = $( '.plugin-card-' + args.slug + ', #plugin-information-footer' ); 499 $message = $card.find( '.update-now' ).addClass( 'updating-message' ); 500 message = sprintf( 501 /* translators: %s: Plugin name and version. */ 502 _x( 'Updating %s...', 'plugin' ), 503 $message.data( 'name' ) 504 ); 505 506 // Remove previous error messages, if any. 507 $card.removeClass( 'plugin-card-update-failed' ).find( '.notice.notice-error' ).remove(); 508 } 509 510 $adminBarUpdates.addClass( 'spin' ); 511 512 if ( $message.html() !== __( 'Updating...' ) ) { 513 $message.data( 'originaltext', $message.html() ); 514 } 515 516 $message 517 .attr( 'aria-label', message ) 518 .text( buttonText ); 519 520 $document.trigger( 'wp-plugin-updating', args ); 521 522 if ( isPluginInstall && 'plugin-information-footer' === $card.attr( 'id' ) ) { 523 wp.updates.setCardButtonStatus( 524 { 525 status: 'updating-plugin', 526 slug: args.slug, 527 addClasses: 'updating-message', 528 text: buttonText, 529 ariaLabel: message 530 } 531 ); 532 } 533 534 return wp.updates.ajax( 'update-plugin', args ); 535 }; 536 537 /** 538 * Updates the UI appropriately after a successful plugin update. 539 * 540 * @since 4.2.0 541 * @since 4.6.0 More accurately named `updatePluginSuccess`. 542 * @since 5.5.0 Auto-update "time to next update" text cleared. 543 * 544 * @param {Object} response Response from the server. 545 * @param {string} response.slug Slug of the plugin to be updated. 546 * @param {string} response.plugin Basename of the plugin to be updated. 547 * @param {string} response.pluginName Name of the plugin to be updated. 548 * @param {string} response.oldVersion Old version of the plugin. 549 * @param {string} response.newVersion New version of the plugin. 550 */ 551 wp.updates.updatePluginSuccess = function( response ) { 552 var $pluginRow, $updateMessage, newText, 553 $adminBarUpdates = $( '#wp-admin-bar-updates' ), 554 buttonText = _x( 'Updated!', 'plugin' ), 555 ariaLabel = sprintf( 556 /* translators: %s: Plugin name and version. */ 557 _x( '%s updated!', 'plugin' ), 558 response.pluginName 559 ); 560 561 if ( 'plugins' === pagenow || 'plugins-network' === pagenow ) { 562 $pluginRow = $( 'tr[data-plugin="' + response.plugin + '"]' ) 563 .removeClass( 'update is-enqueued' ) 564 .addClass( 'updated' ); 565 $updateMessage = $pluginRow.find( '.update-message' ) 566 .removeClass( 'updating-message notice-warning' ) 567 .addClass( 'updated-message notice-success' ).find( 'p' ); 568 569 // Update the version number in the row. 570 newText = $pluginRow.find( '.plugin-version-author-uri' ).html().replace( response.oldVersion, response.newVersion ); 571 $pluginRow.find( '.plugin-version-author-uri' ).html( newText ); 572 573 // Clear the "time to next auto-update" text. 574 $pluginRow.find( '.auto-update-time' ).empty(); 575 } else if ( 'plugin-install' === pagenow || 'plugin-install-network' === pagenow ) { 576 $updateMessage = $( '.plugin-card-' + response.slug + ', #plugin-information-footer' ).find( '.update-now' ) 577 .removeClass( 'updating-message' ) 578 .addClass( 'button-disabled updated-message' ); 579 } 580 581 $adminBarUpdates.removeClass( 'spin' ); 582 583 $updateMessage 584 .attr( 'aria-label', ariaLabel ) 585 .text( buttonText ); 586 587 wp.a11y.speak( __( 'Update completed successfully.' ) ); 588 589 if ( 'plugin_install_from_iframe' !== $updateMessage.attr( 'id' ) ) { 590 wp.updates.decrementCount( 'plugin' ); 591 } else { 592 wp.updates.setCardButtonStatus( 593 { 594 status: 'updated-plugin', 595 slug: response.slug, 596 removeClasses: 'updating-message', 597 addClasses: 'button-disabled updated-message', 598 text: buttonText, 599 ariaLabel: ariaLabel 600 } 601 ); 602 } 603 604 $document.trigger( 'wp-plugin-update-success', response ); 605 }; 606 607 /** 608 * Updates the UI appropriately after a failed plugin update. 609 * 610 * @since 4.2.0 611 * @since 4.6.0 More accurately named `updatePluginError`. 612 * 613 * @param {Object} response Response from the server. 614 * @param {string} response.slug Slug of the plugin to be updated. 615 * @param {string} response.plugin Basename of the plugin to be updated. 616 * @param {string=} response.pluginName Optional. Name of the plugin to be updated. 617 * @param {string} response.errorCode Error code for the error that occurred. 618 * @param {string} response.errorMessage The error that occurred. 619 */ 620 wp.updates.updatePluginError = function( response ) { 621 var $pluginRow, $card, $message, errorMessage, buttonText, ariaLabel, 622 $adminBarUpdates = $( '#wp-admin-bar-updates' ); 623 624 if ( ! wp.updates.isValidResponse( response, 'update' ) ) { 625 return; 626 } 627 628 if ( wp.updates.maybeHandleCredentialError( response, 'update-plugin' ) ) { 629 return; 630 } 631 632 errorMessage = sprintf( 633 /* translators: %s: Error string for a failed update. */ 634 __( 'Update failed: %s' ), 635 response.errorMessage 636 ); 637 638 if ( 'plugins' === pagenow || 'plugins-network' === pagenow ) { 639 $pluginRow = $( 'tr[data-plugin="' + response.plugin + '"]' ).removeClass( 'is-enqueued' ); 640 641 if ( response.plugin ) { 642 $message = $( 'tr[data-plugin="' + response.plugin + '"]' ).find( '.update-message' ); 643 } else { 644 $message = $( 'tr[data-slug="' + response.slug + '"]' ).find( '.update-message' ); 645 } 646 $message.removeClass( 'updating-message notice-warning' ).addClass( 'notice-error' ).find( 'p' ).html( errorMessage ); 647 648 if ( response.pluginName ) { 649 $message.find( 'p' ) 650 .attr( 651 'aria-label', 652 sprintf( 653 /* translators: %s: Plugin name and version. */ 654 _x( '%s update failed.', 'plugin' ), 655 response.pluginName 656 ) 657 ); 658 } else { 659 $message.find( 'p' ).removeAttr( 'aria-label' ); 660 } 661 } else if ( 'plugin-install' === pagenow || 'plugin-install-network' === pagenow ) { 662 buttonText = __( 'Update failed.' ); 663 664 $card = $( '.plugin-card-' + response.slug + ', #plugin-information-footer' ) 665 .append( wp.updates.adminNotice( { 666 className: 'update-message notice-error notice-alt is-dismissible', 667 message: errorMessage 668 } ) ); 669 670 if ( $card.hasClass( 'plugin-card-' + response.slug ) ) { 671 $card.addClass( 'plugin-card-update-failed' ); 672 } 673 674 $card.find( '.update-now' ) 675 .text( buttonText ) 676 .removeClass( 'updating-message' ); 677 678 if ( response.pluginName ) { 679 ariaLabel = sprintf( 680 /* translators: %s: Plugin name and version. */ 681 _x( '%s update failed.', 'plugin' ), 682 response.pluginName 683 ); 684 685 $card.find( '.update-now' ).attr( 'aria-label', ariaLabel ); 686 } else { 687 ariaLabel = ''; 688 $card.find( '.update-now' ).removeAttr( 'aria-label' ); 689 } 690 691 $card.on( 'click', '.notice.is-dismissible .notice-dismiss', function() { 692 693 // Use same delay as the total duration of the notice fadeTo + slideUp animation. 694 setTimeout( function() { 695 $card 696 .removeClass( 'plugin-card-update-failed' ) 697 .find( '.column-name a' ).trigger( 'focus' ); 698 699 $card.find( '.update-now' ) 700 .attr( 'aria-label', false ) 701 .text( __( 'Update Now' ) ); 702 }, 200 ); 703 } ); 704 } 705 706 $adminBarUpdates.removeClass( 'spin' ); 707 708 wp.a11y.speak( errorMessage, 'assertive' ); 709 710 if ( 'plugin-information-footer' === $card.attr('id' ) ) { 711 wp.updates.setCardButtonStatus( 712 { 713 status: 'plugin-update-failed', 714 slug: response.slug, 715 removeClasses: 'updating-message', 716 text: buttonText, 717 ariaLabel: ariaLabel 718 } 719 ); 720 } 721 722 $document.trigger( 'wp-plugin-update-error', response ); 723 }; 724 725 /** 726 * Sends an Ajax request to the server to install a plugin. 727 * 728 * @since 4.6.0 729 * 730 * @param {Object} args Arguments. 731 * @param {string} args.slug Plugin identifier in the WordPress.org Plugin repository. 732 * @param {installPluginSuccess=} args.success Optional. Success callback. Default: wp.updates.installPluginSuccess 733 * @param {installPluginError=} args.error Optional. Error callback. Default: wp.updates.installPluginError 734 * @return {$.promise} A jQuery promise that represents the request, 735 * decorated with an abort() method. 736 */ 737 wp.updates.installPlugin = function( args ) { 738 var $card = $( '.plugin-card-' + args.slug + ', #plugin-information-footer' ), 739 $message = $card.find( '.install-now' ), 740 buttonText = __( 'Installing...' ), 741 ariaLabel; 742 743 args = _.extend( { 744 success: wp.updates.installPluginSuccess, 745 error: wp.updates.installPluginError 746 }, args ); 747 748 if ( 'import' === pagenow ) { 749 $message = $( '[data-slug="' + args.slug + '"]' ); 750 } 751 752 if ( $message.html() !== __( 'Installing...' ) ) { 753 $message.data( 'originaltext', $message.html() ); 754 } 755 756 ariaLabel = sprintf( 757 /* translators: %s: Plugin name and version. */ 758 _x( 'Installing %s...', 'plugin' ), 759 $message.data( 'name' ) 760 ); 761 762 $message 763 .addClass( 'updating-message' ) 764 .attr( 'aria-label', ariaLabel ) 765 .text( buttonText ); 766 767 wp.a11y.speak( __( 'Installing... please wait.' ) ); 768 769 // Remove previous error messages, if any. 770 $card.removeClass( 'plugin-card-install-failed' ).find( '.notice.notice-error' ).remove(); 771 772 $document.trigger( 'wp-plugin-installing', args ); 773 774 if ( 'plugin-information-footer' === $message.parent().attr( 'id' ) ) { 775 wp.updates.setCardButtonStatus( 776 { 777 status: 'installing-plugin', 778 slug: args.slug, 779 addClasses: 'updating-message', 780 text: buttonText, 781 ariaLabel: ariaLabel 782 } 783 ); 784 } 785 786 return wp.updates.ajax( 'install-plugin', args ); 787 }; 788 789 /** 790 * Updates the UI appropriately after a successful plugin install. 791 * 792 * @since 4.6.0 793 * 794 * @param {Object} response Response from the server. 795 * @param {string} response.slug Slug of the installed plugin. 796 * @param {string} response.pluginName Name of the installed plugin. 797 * @param {string} response.activateUrl URL to activate the just installed plugin. 798 */ 799 wp.updates.installPluginSuccess = function( response ) { 800 var $message = $( '.plugin-card-' + response.slug + ', #plugin-information-footer' ).find( '.install-now' ), 801 buttonText = _x( 'Installed!', 'plugin' ), 802 ariaLabel = sprintf( 803 /* translators: %s: Plugin name and version. */ 804 _x( '%s installed!', 'plugin' ), 805 response.pluginName 806 ); 807 808 $message 809 .removeClass( 'updating-message' ) 810 .addClass( 'updated-message installed button-disabled' ) 811 .attr( 'aria-label', ariaLabel ) 812 .text( buttonText ); 813 814 wp.a11y.speak( __( 'Installation completed successfully.' ) ); 815 816 $document.trigger( 'wp-plugin-install-success', response ); 817 818 if ( response.activateUrl ) { 819 setTimeout( function() { 820 wp.updates.checkPluginDependencies( { 821 slug: response.slug 822 } ); 823 }, 1000 ); 824 } 825 826 if ( 'plugin-information-footer' === $message.parent().attr( 'id' ) ) { 827 wp.updates.setCardButtonStatus( 828 { 829 status: 'installed-plugin', 830 slug: response.slug, 831 removeClasses: 'updating-message', 832 addClasses: 'updated-message installed button-disabled', 833 text: buttonText, 834 ariaLabel: ariaLabel 835 } 836 ); 837 } 838 }; 839 840 /** 841 * Updates the UI appropriately after a failed plugin install. 842 * 843 * @since 4.6.0 844 * 845 * @param {Object} response Response from the server. 846 * @param {string} response.slug Slug of the plugin to be installed. 847 * @param {string=} response.pluginName Optional. Name of the plugin to be installed. 848 * @param {string} response.errorCode Error code for the error that occurred. 849 * @param {string} response.errorMessage The error that occurred. 850 */ 851 wp.updates.installPluginError = function( response ) { 852 var $card = $( '.plugin-card-' + response.slug + ', #plugin-information-footer' ), 853 $button = $card.find( '.install-now' ), 854 buttonText = __( 'Installation failed.' ), 855 ariaLabel = sprintf( 856 /* translators: %s: Plugin name and version. */ 857 _x( '%s installation failed', 'plugin' ), 858 $button.data( 'name' ) 859 ), 860 errorMessage; 861 862 if ( ! wp.updates.isValidResponse( response, 'install' ) ) { 863 return; 864 } 865 866 if ( wp.updates.maybeHandleCredentialError( response, 'install-plugin' ) ) { 867 return; 868 } 869 870 errorMessage = sprintf( 871 /* translators: %s: Error string for a failed installation. */ 872 __( 'Installation failed: %s' ), 873 response.errorMessage 874 ); 875 876 $card 877 .addClass( 'plugin-card-update-failed' ) 878 .append( '<div class="notice notice-error notice-alt is-dismissible"><p>' + errorMessage + '</p></div>' ); 879 880 $card.on( 'click', '.notice.is-dismissible .notice-dismiss', function() { 881 882 // Use same delay as the total duration of the notice fadeTo + slideUp animation. 883 setTimeout( function() { 884 $card 885 .removeClass( 'plugin-card-update-failed' ) 886 .find( '.column-name a' ).trigger( 'focus' ); 887 }, 200 ); 888 } ); 889 890 $button 891 .removeClass( 'updating-message' ).addClass( 'button-disabled' ) 892 .attr( 'aria-label', ariaLabel ) 893 .text( buttonText ); 894 895 wp.a11y.speak( errorMessage, 'assertive' ); 896 897 wp.updates.setCardButtonStatus( 898 { 899 status: 'plugin-install-failed', 900 slug: response.slug, 901 removeClasses: 'updating-message', 902 addClasses: 'button-disabled', 903 text: buttonText, 904 ariaLabel: ariaLabel 905 } 906 ); 907 908 $document.trigger( 'wp-plugin-install-error', response ); 909 }; 910 911 /** 912 * Sends an Ajax request to the server to check a plugin's dependencies. 913 * 914 * @since 6.5.0 915 * 916 * @param {Object} args Arguments. 917 * @param {string} args.slug Plugin identifier in the WordPress.org Plugin repository. 918 * @param {checkPluginDependenciesSuccess=} args.success Optional. Success callback. Default: wp.updates.checkPluginDependenciesSuccess 919 * @param {checkPluginDependenciesError=} args.error Optional. Error callback. Default: wp.updates.checkPluginDependenciesError 920 * @return {$.promise} A jQuery promise that represents the request, 921 * decorated with an abort() method. 922 */ 923 wp.updates.checkPluginDependencies = function( args ) { 924 args = _.extend( { 925 success: wp.updates.checkPluginDependenciesSuccess, 926 error: wp.updates.checkPluginDependenciesError 927 }, args ); 928 929 wp.a11y.speak( __( 'Checking plugin dependencies... please wait.' ) ); 930 $document.trigger( 'wp-checking-plugin-dependencies', args ); 931 932 return wp.updates.ajax( 'check_plugin_dependencies', args ); 933 }; 934 935 /** 936 * Updates the UI appropriately after a successful plugin dependencies check. 937 * 938 * @since 6.5.0 939 * 940 * @param {Object} response Response from the server. 941 * @param {string} response.slug Slug of the checked plugin. 942 * @param {string} response.pluginName Name of the checked plugin. 943 * @param {string} response.plugin The plugin file, relative to the plugins directory. 944 * @param {string} response.activateUrl URL to activate the just checked plugin. 945 */ 946 wp.updates.checkPluginDependenciesSuccess = function( response ) { 947 var $message = $( '.plugin-card-' + response.slug + ', #plugin-information-footer' ).find( '.install-now' ), 948 buttonText, ariaLabel; 949 950 // Transform the 'Install' button into an 'Activate' button. 951 $message 952 .removeClass( 'install-now installed button-disabled updated-message' ) 953 .addClass( 'activate-now button-primary' ) 954 .attr( 'href', response.activateUrl ); 955 956 wp.a11y.speak( __( 'Plugin dependencies check completed successfully.' ) ); 957 $document.trigger( 'wp-check-plugin-dependencies-success', response ); 958 959 if ( 'plugins-network' === pagenow ) { 960 buttonText = _x( 'Network Activate', 'plugin' ); 961 ariaLabel = sprintf( 962 /* translators: %s: Plugin name. */ 963 _x( 'Network Activate %s', 'plugin' ), 964 response.pluginName 965 ); 966 967 $message 968 .attr( 'aria-label', ariaLabel ) 969 .text( buttonText ); 970 } else { 971 buttonText = _x( 'Activate', 'plugin' ); 972 ariaLabel = sprintf( 973 /* translators: %s: Plugin name. */ 974 _x( 'Activate %s', 'plugin' ), 975 response.pluginName 976 ); 977 978 $message 979 .attr( 'aria-label', ariaLabel ) 980 .attr( 'data-name', response.pluginName ) 981 .attr( 'data-slug', response.slug ) 982 .attr( 'data-plugin', response.plugin ) 983 .text( buttonText ); 984 } 985 986 if ( 'plugin-information-footer' === $message.parent().attr( 'id' ) ) { 987 wp.updates.setCardButtonStatus( 988 { 989 status: 'dependencies-check-success', 990 slug: response.slug, 991 removeClasses: 'install-now installed button-disabled updated-message', 992 addClasses: 'activate-now button-primary', 993 text: buttonText, 994 ariaLabel: ariaLabel, 995 pluginName: response.pluginName, 996 plugin: response.plugin, 997 href: response.activateUrl 998 } 999 ); 1000 } 1001 }; 1002 1003 /** 1004 * Updates the UI appropriately after a failed plugin dependencies check. 1005 * 1006 * @since 6.5.0 1007 * 1008 * @param {Object} response Response from the server. 1009 * @param {string} response.slug Slug of the plugin to be checked. 1010 * @param {string=} response.pluginName Optional. Name of the plugin to be checked. 1011 * @param {string} response.errorCode Error code for the error that occurred. 1012 * @param {string} response.errorMessage The error that occurred. 1013 */ 1014 wp.updates.checkPluginDependenciesError = function( response ) { 1015 var $message = $( '.plugin-card-' + response.slug + ', #plugin-information-footer' ).find( '.install-now' ), 1016 buttonText = _x( 'Activate', 'plugin' ), 1017 ariaLabel = sprintf( 1018 /* translators: 1: Plugin name, 2. The reason the plugin cannot be activated. */ 1019 _x( 'Cannot activate %1$s. %2$s', 'plugin' ), 1020 response.pluginName, 1021 response.errorMessage 1022 ), 1023 errorMessage; 1024 1025 if ( ! wp.updates.isValidResponse( response, 'check-dependencies' ) ) { 1026 return; 1027 } 1028 1029 errorMessage = sprintf( 1030 /* translators: %s: Error string for a failed activation. */ 1031 __( 'Activation failed: %s' ), 1032 response.errorMessage 1033 ); 1034 1035 wp.a11y.speak( errorMessage, 'assertive' ); 1036 $document.trigger( 'wp-check-plugin-dependencies-error', response ); 1037 1038 $message 1039 .removeClass( 'install-now installed updated-message' ) 1040 .addClass( 'activate-now button-primary' ) 1041 .attr( 'aria-label', ariaLabel ) 1042 .text( buttonText ); 1043 1044 if ( 'plugin-information-footer' === $message.parent().attr('id' ) ) { 1045 wp.updates.setCardButtonStatus( 1046 { 1047 status: 'dependencies-check-failed', 1048 slug: response.slug, 1049 removeClasses: 'install-now installed updated-message', 1050 addClasses: 'activate-now button-primary', 1051 text: buttonText, 1052 ariaLabel: ariaLabel 1053 } 1054 ); 1055 } 1056 }; 1057 1058 /** 1059 * Sends an Ajax request to the server to activate a plugin. 1060 * 1061 * @since 6.5.0 1062 * 1063 * @param {Object} args Arguments. 1064 * @param {string} args.name The name of the plugin. 1065 * @param {string} args.slug Plugin identifier in the WordPress.org Plugin repository. 1066 * @param {string} args.plugin The plugin file, relative to the plugins directory. 1067 * @param {activatePluginSuccess=} args.success Optional. Success callback. Default: wp.updates.activatePluginSuccess 1068 * @param {activatePluginError=} args.error Optional. Error callback. Default: wp.updates.activatePluginError 1069 * @return {$.promise} A jQuery promise that represents the request, 1070 * decorated with an abort() method. 1071 */ 1072 wp.updates.activatePlugin = function( args ) { 1073 var $message = $( '.plugin-card-' + args.slug + ', #plugin-information-footer' ).find( '.activate-now, .activating-message' ); 1074 1075 args = _.extend( { 1076 success: wp.updates.activatePluginSuccess, 1077 error: wp.updates.activatePluginError 1078 }, args ); 1079 1080 wp.a11y.speak( __( 'Activating... please wait.' ) ); 1081 $document.trigger( 'wp-activating-plugin', args ); 1082 1083 if ( 'plugin-information-footer' === $message.parent().attr( 'id' ) ) { 1084 wp.updates.setCardButtonStatus( 1085 { 1086 status: 'activating-plugin', 1087 slug: args.slug, 1088 removeClasses: 'installed updated-message button-primary', 1089 addClasses: 'activating-message', 1090 text: __( 'Activating...' ), 1091 ariaLabel: sprintf( 1092 /* translators: %s: Plugin name. */ 1093 _x( 'Activating %s', 'plugin' ), 1094 args.name 1095 ) 1096 } 1097 ); 1098 } 1099 1100 return wp.updates.ajax( 'activate-plugin', args ); 1101 }; 1102 1103 /** 1104 * Updates the UI appropriately after a successful plugin activation. 1105 * 1106 * @since 6.5.0 1107 * 1108 * @param {Object} response Response from the server. 1109 * @param {string} response.slug Slug of the activated plugin. 1110 * @param {string} response.pluginName Name of the activated plugin. 1111 * @param {string} response.plugin The plugin file, relative to the plugins directory. 1112 */ 1113 wp.updates.activatePluginSuccess = function( response ) { 1114 var $message = $( '.plugin-card-' + response.slug + ', #plugin-information-footer' ).find( '.activating-message' ), 1115 isInModal = 'plugin-information-footer' === $message.parent().attr( 'id' ), 1116 buttonText = _x( 'Activated!', 'plugin' ), 1117 ariaLabel = sprintf( 1118 /* translators: %s: The plugin name. */ 1119 '%s activated successfully.', 1120 response.pluginName 1121 ), 1122 noticeData = { 1123 id: 'plugin-activated-successfully', 1124 className: 'notice-success', 1125 message: sprintf( 1126 /* translators: %s: The refresh link's attributes. */ 1127 __( 'Plugin activated. Some changes may not occur until you refresh the page. <a %s>Refresh Now</a>' ), 1128 'href="#" class="button button-secondary refresh-page"' 1129 ), 1130 slug: response.slug 1131 }, 1132 noticeTarget; 1133 1134 wp.a11y.speak( __( 'Activation completed successfully. Some changes may not occur until you refresh the page.' ) ); 1135 $document.trigger( 'wp-plugin-activate-success', response ); 1136 1137 $message 1138 .removeClass( 'activating-message' ) 1139 .addClass( 'activated-message button-disabled' ) 1140 .attr( 'aria-label', ariaLabel ) 1141 .text( buttonText ); 1142 1143 if ( isInModal ) { 1144 wp.updates.setCardButtonStatus( 1145 { 1146 status: 'activated-plugin', 1147 slug: response.slug, 1148 removeClasses: 'activating-message', 1149 addClasses: 'activated-message button-disabled', 1150 text: buttonText, 1151 ariaLabel: ariaLabel 1152 } 1153 ); 1154 1155 // Add a notice to the modal's footer. 1156 $message.replaceWith( wp.updates.adminNotice( noticeData ) ); 1157 1158 // Send notice information back to the parent screen. 1159 noticeTarget = window.parent === window ? null : window.parent; 1160 $.support.postMessage = !! window.postMessage; 1161 if ( false !== $.support.postMessage && null !== noticeTarget && -1 === window.parent.location.pathname.indexOf( 'index.php' ) ) { 1162 noticeTarget.postMessage( 1163 JSON.stringify( noticeData ), 1164 window.location.origin 1165 ); 1166 } 1167 } else { 1168 // Add a notice to the top of the screen. 1169 wp.updates.addAdminNotice( noticeData ); 1170 } 1171 1172 setTimeout( function() { 1173 if ( isInModal ) { 1174 wp.updates.setCardButtonStatus( 1175 { 1176 status: 'plugin-active', 1177 slug: response.slug, 1178 removeClasses: 'activated-message', 1179 text: _x( 'Active', 'plugin' ), 1180 ariaLabel: sprintf( 1181 /* translators: %s: The plugin name. */ 1182 '%s is active.', 1183 response.pluginName 1184 ) 1185 } 1186 ); 1187 } else { 1188 $message.removeClass( 'activated-message' ).text( _x( 'Active', 'plugin' ) ); 1189 } 1190 }, 1000 ); 1191 }; 1192 1193 /** 1194 * Updates the UI appropriately after a failed plugin activation. 1195 * 1196 * @since 6.5.0 1197 * 1198 * @param {Object} response Response from the server. 1199 * @param {string} response.slug Slug of the plugin to be activated. 1200 * @param {string=} response.pluginName Optional. Name of the plugin to be activated. 1201 * @param {string} response.errorCode Error code for the error that occurred. 1202 * @param {string} response.errorMessage The error that occurred. 1203 */ 1204 wp.updates.activatePluginError = function( response ) { 1205 var $message = $( '.plugin-card-' + response.slug + ', #plugin-information-footer' ).find( '.activating-message' ), 1206 buttonText = __( 'Activation failed.' ), 1207 ariaLabel = sprintf( 1208 /* translators: %s: Plugin name. */ 1209 _x( '%s activation failed', 'plugin' ), 1210 response.pluginName 1211 ), 1212 errorMessage; 1213 1214 if ( ! wp.updates.isValidResponse( response, 'activate' ) ) { 1215 return; 1216 } 1217 1218 errorMessage = sprintf( 1219 /* translators: %s: Error string for a failed activation. */ 1220 __( 'Activation failed: %s' ), 1221 response.errorMessage 1222 ); 1223 1224 wp.a11y.speak( errorMessage, 'assertive' ); 1225 $document.trigger( 'wp-plugin-activate-error', response ); 1226 1227 $message 1228 .removeClass( 'install-now installed activating-message' ) 1229 .addClass( 'button-disabled' ) 1230 .attr( 'aria-label', ariaLabel ) 1231 .text( buttonText ); 1232 1233 if ( 'plugin-information-footer' === $message.parent().attr( 'id' ) ) { 1234 wp.updates.setCardButtonStatus( 1235 { 1236 status: 'plugin-activation-failed', 1237 slug: response.slug, 1238 removeClasses: 'install-now installed activating-message', 1239 addClasses: 'button-disabled', 1240 text: buttonText, 1241 ariaLabel: ariaLabel 1242 } 1243 ); 1244 } 1245 }; 1246 1247 /** 1248 * Updates the UI appropriately after a successful importer install. 1249 * 1250 * @since 4.6.0 1251 * 1252 * @param {Object} response Response from the server. 1253 * @param {string} response.slug Slug of the installed plugin. 1254 * @param {string} response.pluginName Name of the installed plugin. 1255 * @param {string} response.activateUrl URL to activate the just installed plugin. 1256 */ 1257 wp.updates.installImporterSuccess = function( response ) { 1258 wp.updates.addAdminNotice( { 1259 id: 'install-success', 1260 className: 'notice-success is-dismissible', 1261 message: sprintf( 1262 /* translators: %s: Activation URL. */ 1263 __( 'Importer installed successfully. <a href="%s">Run importer</a>' ), 1264 response.activateUrl + '&from=import' 1265 ) 1266 } ); 1267 1268 $( '[data-slug="' + response.slug + '"]' ) 1269 .removeClass( 'install-now updating-message' ) 1270 .addClass( 'activate-now' ) 1271 .attr({ 1272 'href': response.activateUrl + '&from=import', 1273 'aria-label':sprintf( 1274 /* translators: %s: Importer name. */ 1275 __( 'Run %s' ), 1276 response.pluginName 1277 ) 1278 }) 1279 .text( __( 'Run Importer' ) ); 1280 1281 wp.a11y.speak( __( 'Installation completed successfully.' ) ); 1282 1283 $document.trigger( 'wp-importer-install-success', response ); 1284 }; 1285 1286 /** 1287 * Updates the UI appropriately after a failed importer install. 1288 * 1289 * @since 4.6.0 1290 * 1291 * @param {Object} response Response from the server. 1292 * @param {string} response.slug Slug of the plugin to be installed. 1293 * @param {string=} response.pluginName Optional. Name of the plugin to be installed. 1294 * @param {string} response.errorCode Error code for the error that occurred. 1295 * @param {string} response.errorMessage The error that occurred. 1296 */ 1297 wp.updates.installImporterError = function( response ) { 1298 var errorMessage = sprintf( 1299 /* translators: %s: Error string for a failed installation. */ 1300 __( 'Installation failed: %s' ), 1301 response.errorMessage 1302 ), 1303 $installLink = $( '[data-slug="' + response.slug + '"]' ), 1304 pluginName = $installLink.data( 'name' ); 1305 1306 if ( ! wp.updates.isValidResponse( response, 'install' ) ) { 1307 return; 1308 } 1309 1310 if ( wp.updates.maybeHandleCredentialError( response, 'install-plugin' ) ) { 1311 return; 1312 } 1313 1314 wp.updates.addAdminNotice( { 1315 id: response.errorCode, 1316 className: 'notice-error is-dismissible', 1317 message: errorMessage 1318 } ); 1319 1320 $installLink 1321 .removeClass( 'updating-message' ) 1322 .attr( 1323 'aria-label', 1324 sprintf( 1325 /* translators: %s: Plugin name. */ 1326 _x( 'Install %s now', 'plugin' ), 1327 pluginName 1328 ) 1329 ) 1330 .text( _x( 'Install Now', 'plugin' ) ); 1331 1332 wp.a11y.speak( errorMessage, 'assertive' ); 1333 1334 $document.trigger( 'wp-importer-install-error', response ); 1335 }; 1336 1337 /** 1338 * Sends an Ajax request to the server to delete a plugin. 1339 * 1340 * @since 4.6.0 1341 * 1342 * @param {Object} args Arguments. 1343 * @param {string} args.plugin Basename of the plugin to be deleted. 1344 * @param {string} args.slug Slug of the plugin to be deleted. 1345 * @param {deletePluginSuccess=} args.success Optional. Success callback. Default: wp.updates.deletePluginSuccess 1346 * @param {deletePluginError=} args.error Optional. Error callback. Default: wp.updates.deletePluginError 1347 * @return {$.promise} A jQuery promise that represents the request, 1348 * decorated with an abort() method. 1349 */ 1350 wp.updates.deletePlugin = function( args ) { 1351 var $link = $( '[data-plugin="' + args.plugin + '"]' ).find( '.row-actions a.delete' ); 1352 1353 args = _.extend( { 1354 success: wp.updates.deletePluginSuccess, 1355 error: wp.updates.deletePluginError 1356 }, args ); 1357 1358 if ( $link.html() !== __( 'Deleting...' ) ) { 1359 $link 1360 .data( 'originaltext', $link.html() ) 1361 .text( __( 'Deleting...' ) ); 1362 } 1363 1364 wp.a11y.speak( __( 'Deleting...' ) ); 1365 1366 $document.trigger( 'wp-plugin-deleting', args ); 1367 1368 return wp.updates.ajax( 'delete-plugin', args ); 1369 }; 1370 1371 /** 1372 * Updates the UI appropriately after a successful plugin deletion. 1373 * 1374 * @since 4.6.0 1375 * 1376 * @param {Object} response Response from the server. 1377 * @param {string} response.slug Slug of the plugin that was deleted. 1378 * @param {string} response.plugin Base name of the plugin that was deleted. 1379 * @param {string} response.pluginName Name of the plugin that was deleted. 1380 */ 1381 wp.updates.deletePluginSuccess = function( response ) { 1382 1383 // Removes the plugin and updates rows. 1384 $( '[data-plugin="' + response.plugin + '"]' ).css( { backgroundColor: '#faafaa' } ).fadeOut( 350, function() { 1385 var $form = $( '#bulk-action-form' ), 1386 $views = $( '.subsubsub' ), 1387 $pluginRow = $( this ), 1388 $currentView = $views.find( '[aria-current="page"]' ), 1389 $itemsCount = $( '.displaying-num' ), 1390 columnCount = $form.find( 'thead th:not(.hidden), thead td' ).length, 1391 pluginDeletedRow = wp.template( 'item-deleted-row' ), 1392 /** 1393 * Plugins Base names of plugins in their different states. 1394 * 1395 * @type {Object} 1396 */ 1397 plugins = settings.plugins, 1398 remainingCount; 1399 1400 // Add a success message after deleting a plugin. 1401 if ( ! $pluginRow.hasClass( 'plugin-update-tr' ) ) { 1402 $pluginRow.after( 1403 pluginDeletedRow( { 1404 slug: response.slug, 1405 plugin: response.plugin, 1406 colspan: columnCount, 1407 name: response.pluginName 1408 } ) 1409 ); 1410 } 1411 1412 $pluginRow.remove(); 1413 1414 // Remove plugin from update count. 1415 if ( -1 !== _.indexOf( plugins.upgrade, response.plugin ) ) { 1416 plugins.upgrade = _.without( plugins.upgrade, response.plugin ); 1417 wp.updates.decrementCount( 'plugin' ); 1418 } 1419 1420 // Remove from views. 1421 if ( -1 !== _.indexOf( plugins.inactive, response.plugin ) ) { 1422 plugins.inactive = _.without( plugins.inactive, response.plugin ); 1423 if ( plugins.inactive.length ) { 1424 $views.find( '.inactive .count' ).text( '(' + plugins.inactive.length + ')' ); 1425 } else { 1426 $views.find( '.inactive' ).remove(); 1427 } 1428 } 1429 1430 if ( -1 !== _.indexOf( plugins.active, response.plugin ) ) { 1431 plugins.active = _.without( plugins.active, response.plugin ); 1432 if ( plugins.active.length ) { 1433 $views.find( '.active .count' ).text( '(' + plugins.active.length + ')' ); 1434 } else { 1435 $views.find( '.active' ).remove(); 1436 } 1437 } 1438 1439 if ( -1 !== _.indexOf( plugins.recently_activated, response.plugin ) ) { 1440 plugins.recently_activated = _.without( plugins.recently_activated, response.plugin ); 1441 if ( plugins.recently_activated.length ) { 1442 $views.find( '.recently_activated .count' ).text( '(' + plugins.recently_activated.length + ')' ); 1443 } else { 1444 $views.find( '.recently_activated' ).remove(); 1445 } 1446 } 1447 1448 if ( -1 !== _.indexOf( plugins['auto-update-enabled'], response.plugin ) ) { 1449 plugins['auto-update-enabled'] = _.without( plugins['auto-update-enabled'], response.plugin ); 1450 if ( plugins['auto-update-enabled'].length ) { 1451 $views.find( '.auto-update-enabled .count' ).text( '(' + plugins['auto-update-enabled'].length + ')' ); 1452 } else { 1453 $views.find( '.auto-update-enabled' ).remove(); 1454 } 1455 } 1456 1457 if ( -1 !== _.indexOf( plugins['auto-update-disabled'], response.plugin ) ) { 1458 plugins['auto-update-disabled'] = _.without( plugins['auto-update-disabled'], response.plugin ); 1459 if ( plugins['auto-update-disabled'].length ) { 1460 $views.find( '.auto-update-disabled .count' ).text( '(' + plugins['auto-update-disabled'].length + ')' ); 1461 } else { 1462 $views.find( '.auto-update-disabled' ).remove(); 1463 } 1464 } 1465 1466 plugins.all = _.without( plugins.all, response.plugin ); 1467 1468 if ( plugins.all.length ) { 1469 $views.find( '.all .count' ).text( '(' + plugins.all.length + ')' ); 1470 } else { 1471 $form.find( '.tablenav' ).css( { visibility: 'hidden' } ); 1472 $views.find( '.all' ).remove(); 1473 1474 if ( ! $form.find( 'tr.no-items' ).length ) { 1475 $form.find( '#the-list' ).append( '<tr class="no-items"><td class="colspanchange" colspan="' + columnCount + '">' + __( 'No plugins are currently available.' ) + '</td></tr>' ); 1476 } 1477 } 1478 1479 if ( $itemsCount.length && $currentView.length ) { 1480 remainingCount = plugins[ $currentView.parent( 'li' ).attr('class') ].length; 1481 $itemsCount.text( 1482 sprintf( 1483 /* translators: %s: The remaining number of plugins. */ 1484 _nx( '%s item', '%s items', remainingCount, 'plugin/plugins' ), 1485 remainingCount 1486 ) 1487 ); 1488 } 1489 } ); 1490 1491 wp.a11y.speak( _x( 'Deleted!', 'plugin' ) ); 1492 1493 $document.trigger( 'wp-plugin-delete-success', response ); 1494 }; 1495 1496 /** 1497 * Updates the UI appropriately after a failed plugin deletion. 1498 * 1499 * @since 4.6.0 1500 * 1501 * @param {Object} response Response from the server. 1502 * @param {string} response.slug Slug of the plugin to be deleted. 1503 * @param {string} response.plugin Base name of the plugin to be deleted 1504 * @param {string=} response.pluginName Optional. Name of the plugin to be deleted. 1505 * @param {string} response.errorCode Error code for the error that occurred. 1506 * @param {string} response.errorMessage The error that occurred. 1507 */ 1508 wp.updates.deletePluginError = function( response ) { 1509 var $plugin, $pluginUpdateRow, 1510 pluginUpdateRow = wp.template( 'item-update-row' ), 1511 noticeContent = wp.updates.adminNotice( { 1512 className: 'update-message notice-error notice-alt', 1513 message: response.errorMessage 1514 } ); 1515 1516 if ( response.plugin ) { 1517 $plugin = $( 'tr.inactive[data-plugin="' + response.plugin + '"]' ); 1518 $pluginUpdateRow = $plugin.siblings( '[data-plugin="' + response.plugin + '"]' ); 1519 } else { 1520 $plugin = $( 'tr.inactive[data-slug="' + response.slug + '"]' ); 1521 $pluginUpdateRow = $plugin.siblings( '[data-slug="' + response.slug + '"]' ); 1522 } 1523 1524 if ( ! wp.updates.isValidResponse( response, 'delete' ) ) { 1525 return; 1526 } 1527 1528 if ( wp.updates.maybeHandleCredentialError( response, 'delete-plugin' ) ) { 1529 return; 1530 } 1531 1532 // Add a plugin update row if it doesn't exist yet. 1533 if ( ! $pluginUpdateRow.length ) { 1534 $plugin.addClass( 'update' ).after( 1535 pluginUpdateRow( { 1536 slug: response.slug, 1537 plugin: response.plugin || response.slug, 1538 colspan: $( '#bulk-action-form' ).find( 'thead th:not(.hidden), thead td' ).length, 1539 content: noticeContent 1540 } ) 1541 ); 1542 } else { 1543 1544 // Remove previous error messages, if any. 1545 $pluginUpdateRow.find( '.notice-error' ).remove(); 1546 1547 $pluginUpdateRow.find( '.plugin-update' ).append( noticeContent ); 1548 } 1549 1550 $document.trigger( 'wp-plugin-delete-error', response ); 1551 }; 1552 1553 /** 1554 * Sends an Ajax request to the server to update a theme. 1555 * 1556 * @since 4.6.0 1557 * 1558 * @param {Object} args Arguments. 1559 * @param {string} args.slug Theme stylesheet. 1560 * @param {updateThemeSuccess=} args.success Optional. Success callback. Default: wp.updates.updateThemeSuccess 1561 * @param {updateThemeError=} args.error Optional. Error callback. Default: wp.updates.updateThemeError 1562 * @return {$.promise} A jQuery promise that represents the request, 1563 * decorated with an abort() method. 1564 */ 1565 wp.updates.updateTheme = function( args ) { 1566 var $notice; 1567 1568 args = _.extend( { 1569 success: wp.updates.updateThemeSuccess, 1570 error: wp.updates.updateThemeError 1571 }, args ); 1572 1573 if ( 'themes-network' === pagenow ) { 1574 $notice = $( '[data-slug="' + args.slug + '"]' ).find( '.update-message' ).removeClass( 'notice-error' ).addClass( 'updating-message notice-warning' ).find( 'p' ); 1575 1576 } else if ( 'customize' === pagenow ) { 1577 1578 // Update the theme details UI. 1579 $notice = $( '[data-slug="' + args.slug + '"].notice' ).removeClass( 'notice-large' ); 1580 1581 $notice.find( 'h3' ).remove(); 1582 1583 // Add the top-level UI, and update both. 1584 $notice = $notice.add( $( '#customize-control-installed_theme_' + args.slug ).find( '.update-message' ) ); 1585 $notice = $notice.addClass( 'updating-message' ).find( 'p' ); 1586 1587 } else { 1588 $notice = $( '#update-theme' ).closest( '.notice' ).removeClass( 'notice-large' ); 1589 1590 $notice.find( 'h3' ).remove(); 1591 1592 $notice = $notice.add( $( '[data-slug="' + args.slug + '"]' ).find( '.update-message' ) ); 1593 $notice = $notice.addClass( 'updating-message' ).find( 'p' ); 1594 } 1595 1596 if ( $notice.html() !== __( 'Updating...' ) ) { 1597 $notice.data( 'originaltext', $notice.html() ); 1598 } 1599 1600 wp.a11y.speak( __( 'Updating... please wait.' ) ); 1601 $notice.text( __( 'Updating...' ) ); 1602 1603 $document.trigger( 'wp-theme-updating', args ); 1604 1605 return wp.updates.ajax( 'update-theme', args ); 1606 }; 1607 1608 /** 1609 * Updates the UI appropriately after a successful theme update. 1610 * 1611 * @since 4.6.0 1612 * @since 5.5.0 Auto-update "time to next update" text cleared. 1613 * 1614 * @param {Object} response 1615 * @param {string} response.slug Slug of the theme to be updated. 1616 * @param {Object} response.theme Updated theme. 1617 * @param {string} response.oldVersion Old version of the theme. 1618 * @param {string} response.newVersion New version of the theme. 1619 */ 1620 wp.updates.updateThemeSuccess = function( response ) { 1621 var isModalOpen = $( 'body.modal-open' ).length, 1622 $theme = $( '[data-slug="' + response.slug + '"]' ), 1623 updatedMessage = { 1624 className: 'updated-message notice-success notice-alt', 1625 message: _x( 'Updated!', 'theme' ) 1626 }, 1627 $notice, newText; 1628 1629 if ( 'customize' === pagenow ) { 1630 $theme = $( '.updating-message' ).siblings( '.theme-name' ); 1631 1632 if ( $theme.length ) { 1633 1634 // Update the version number in the row. 1635 newText = $theme.html().replace( response.oldVersion, response.newVersion ); 1636 $theme.html( newText ); 1637 } 1638 1639 $notice = $( '.theme-info .notice' ).add( wp.customize.control( 'installed_theme_' + response.slug ).container.find( '.theme' ).find( '.update-message' ) ); 1640 } else if ( 'themes-network' === pagenow ) { 1641 $notice = $theme.find( '.update-message' ); 1642 1643 // Update the version number in the row. 1644 newText = $theme.find( '.theme-version-author-uri' ).html().replace( response.oldVersion, response.newVersion ); 1645 $theme.find( '.theme-version-author-uri' ).html( newText ); 1646 1647 // Clear the "time to next auto-update" text. 1648 $theme.find( '.auto-update-time' ).empty(); 1649 } else { 1650 $notice = $( '.theme-info .notice' ).add( $theme.find( '.update-message' ) ); 1651 1652 // Focus on Customize button after updating. 1653 if ( isModalOpen ) { 1654 $( '.load-customize:visible' ).trigger( 'focus' ); 1655 $( '.theme-info .theme-autoupdate' ).find( '.auto-update-time' ).empty(); 1656 } else { 1657 $theme.find( '.load-customize' ).trigger( 'focus' ); 1658 } 1659 } 1660 1661 wp.updates.addAdminNotice( _.extend( { selector: $notice }, updatedMessage ) ); 1662 wp.a11y.speak( __( 'Update completed successfully.' ) ); 1663 1664 wp.updates.decrementCount( 'theme' ); 1665 1666 $document.trigger( 'wp-theme-update-success', response ); 1667 1668 // Show updated message after modal re-rendered. 1669 if ( isModalOpen && 'customize' !== pagenow ) { 1670 $( '.theme-info .theme-author' ).after( wp.updates.adminNotice( updatedMessage ) ); 1671 } 1672 }; 1673 1674 /** 1675 * Updates the UI appropriately after a failed theme update. 1676 * 1677 * @since 4.6.0 1678 * 1679 * @param {Object} response Response from the server. 1680 * @param {string} response.slug Slug of the theme to be updated. 1681 * @param {string} response.errorCode Error code for the error that occurred. 1682 * @param {string} response.errorMessage The error that occurred. 1683 */ 1684 wp.updates.updateThemeError = function( response ) { 1685 var $theme = $( '[data-slug="' + response.slug + '"]' ), 1686 errorMessage = sprintf( 1687 /* translators: %s: Error string for a failed update. */ 1688 __( 'Update failed: %s' ), 1689 response.errorMessage 1690 ), 1691 $notice; 1692 1693 if ( ! wp.updates.isValidResponse( response, 'update' ) ) { 1694 return; 1695 } 1696 1697 if ( wp.updates.maybeHandleCredentialError( response, 'update-theme' ) ) { 1698 return; 1699 } 1700 1701 if ( 'customize' === pagenow ) { 1702 $theme = wp.customize.control( 'installed_theme_' + response.slug ).container.find( '.theme' ); 1703 } 1704 1705 if ( 'themes-network' === pagenow ) { 1706 $notice = $theme.find( '.update-message ' ); 1707 } else { 1708 $notice = $( '.theme-info .notice' ).add( $theme.find( '.notice' ) ); 1709 1710 $( 'body.modal-open' ).length ? $( '.load-customize:visible' ).trigger( 'focus' ) : $theme.find( '.load-customize' ).trigger( 'focus'); 1711 } 1712 1713 wp.updates.addAdminNotice( { 1714 selector: $notice, 1715 className: 'update-message notice-error notice-alt is-dismissible', 1716 message: errorMessage 1717 } ); 1718 1719 wp.a11y.speak( errorMessage ); 1720 1721 $document.trigger( 'wp-theme-update-error', response ); 1722 }; 1723 1724 /** 1725 * Sends an Ajax request to the server to install a theme. 1726 * 1727 * @since 4.6.0 1728 * 1729 * @param {Object} args 1730 * @param {string} args.slug Theme stylesheet. 1731 * @param {installThemeSuccess=} args.success Optional. Success callback. Default: wp.updates.installThemeSuccess 1732 * @param {installThemeError=} args.error Optional. Error callback. Default: wp.updates.installThemeError 1733 * @return {$.promise} A jQuery promise that represents the request, 1734 * decorated with an abort() method. 1735 */ 1736 wp.updates.installTheme = function( args ) { 1737 var $message = $( '.theme-install[data-slug="' + args.slug + '"]' ); 1738 1739 args = _.extend( { 1740 success: wp.updates.installThemeSuccess, 1741 error: wp.updates.installThemeError 1742 }, args ); 1743 1744 $message.addClass( 'updating-message' ); 1745 $message.parents( '.theme' ).addClass( 'focus' ); 1746 if ( $message.html() !== __( 'Installing...' ) ) { 1747 $message.data( 'originaltext', $message.html() ); 1748 } 1749 1750 $message 1751 .attr( 1752 'aria-label', 1753 sprintf( 1754 /* translators: %s: Theme name and version. */ 1755 _x( 'Installing %s...', 'theme' ), 1756 $message.data( 'name' ) 1757 ) 1758 ) 1759 .text( __( 'Installing...' ) ); 1760 1761 wp.a11y.speak( __( 'Installing... please wait.' ) ); 1762 1763 // Remove previous error messages, if any. 1764 $( '.install-theme-info, [data-slug="' + args.slug + '"]' ).removeClass( 'theme-install-failed' ).find( '.notice.notice-error' ).remove(); 1765 1766 $document.trigger( 'wp-theme-installing', args ); 1767 1768 return wp.updates.ajax( 'install-theme', args ); 1769 }; 1770 1771 /** 1772 * Updates the UI appropriately after a successful theme install. 1773 * 1774 * @since 4.6.0 1775 * 1776 * @param {Object} response Response from the server. 1777 * @param {string} response.slug Slug of the theme to be installed. 1778 * @param {string} response.customizeUrl URL to the Customizer for the just installed theme. 1779 * @param {string} response.activateUrl URL to activate the just installed theme. 1780 */ 1781 wp.updates.installThemeSuccess = function( response ) { 1782 var $card = $( '.wp-full-overlay-header, [data-slug=' + response.slug + ']' ), 1783 $message; 1784 1785 $document.trigger( 'wp-theme-install-success', response ); 1786 1787 $message = $card.find( '.button-primary' ) 1788 .removeClass( 'updating-message' ) 1789 .addClass( 'updated-message disabled' ) 1790 .attr( 1791 'aria-label', 1792 sprintf( 1793 /* translators: %s: Theme name and version. */ 1794 _x( '%s installed!', 'theme' ), 1795 response.themeName 1796 ) 1797 ) 1798 .text( _x( 'Installed!', 'theme' ) ); 1799 1800 wp.a11y.speak( __( 'Installation completed successfully.' ) ); 1801 1802 setTimeout( function() { 1803 1804 if ( response.activateUrl ) { 1805 1806 // Transform the 'Install' button into an 'Activate' button. 1807 $message 1808 .attr( 'href', response.activateUrl ) 1809 .removeClass( 'theme-install updated-message disabled' ) 1810 .addClass( 'activate' ); 1811 1812 if ( 'themes-network' === pagenow ) { 1813 $message 1814 .attr( 1815 'aria-label', 1816 sprintf( 1817 /* translators: %s: Theme name. */ 1818 _x( 'Network Activate %s', 'theme' ), 1819 response.themeName 1820 ) 1821 ) 1822 .text( __( 'Network Enable' ) ); 1823 } else { 1824 $message 1825 .attr( 1826 'aria-label', 1827 sprintf( 1828 /* translators: %s: Theme name. */ 1829 _x( 'Activate %s', 'theme' ), 1830 response.themeName 1831 ) 1832 ) 1833 .text( _x( 'Activate', 'theme' ) ); 1834 } 1835 } 1836 1837 if ( response.customizeUrl ) { 1838 1839 // Transform the 'Preview' button into a 'Live Preview' button. 1840 $message.siblings( '.preview' ).replaceWith( function () { 1841 return $( '<a>' ) 1842 .attr( 'href', response.customizeUrl ) 1843 .addClass( 'button load-customize' ) 1844 .text( __( 'Live Preview' ) ); 1845 } ); 1846 } 1847 }, 1000 ); 1848 }; 1849 1850 /** 1851 * Updates the UI appropriately after a failed theme install. 1852 * 1853 * @since 4.6.0 1854 * 1855 * @param {Object} response Response from the server. 1856 * @param {string} response.slug Slug of the theme to be installed. 1857 * @param {string} response.errorCode Error code for the error that occurred. 1858 * @param {string} response.errorMessage The error that occurred. 1859 */ 1860 wp.updates.installThemeError = function( response ) { 1861 var $card, $button, 1862 errorMessage = sprintf( 1863 /* translators: %s: Error string for a failed installation. */ 1864 __( 'Installation failed: %s' ), 1865 response.errorMessage 1866 ), 1867 $message = wp.updates.adminNotice( { 1868 className: 'update-message notice-error notice-alt', 1869 message: errorMessage 1870 } ); 1871 1872 if ( ! wp.updates.isValidResponse( response, 'install' ) ) { 1873 return; 1874 } 1875 1876 if ( wp.updates.maybeHandleCredentialError( response, 'install-theme' ) ) { 1877 return; 1878 } 1879 1880 if ( 'customize' === pagenow ) { 1881 if ( $document.find( 'body' ).hasClass( 'modal-open' ) ) { 1882 $button = $( '.theme-install[data-slug="' + response.slug + '"]' ); 1883 $card = $( '.theme-overlay .theme-info' ).prepend( $message ); 1884 } else { 1885 $button = $( '.theme-install[data-slug="' + response.slug + '"]' ); 1886 $card = $button.closest( '.theme' ).addClass( 'theme-install-failed' ).append( $message ); 1887 } 1888 wp.customize.notifications.remove( 'theme_installing' ); 1889 } else { 1890 if ( $document.find( 'body' ).hasClass( 'full-overlay-active' ) ) { 1891 $button = $( '.theme-install[data-slug="' + response.slug + '"]' ); 1892 $card = $( '.install-theme-info' ).prepend( $message ); 1893 } else { 1894 $card = $( '[data-slug="' + response.slug + '"]' ).removeClass( 'focus' ).addClass( 'theme-install-failed' ).append( $message ); 1895 $button = $card.find( '.theme-install' ); 1896 } 1897 } 1898 1899 $button 1900 .removeClass( 'updating-message' ) 1901 .attr( 1902 'aria-label', 1903 sprintf( 1904 /* translators: %s: Theme name and version. */ 1905 _x( '%s installation failed', 'theme' ), 1906 $button.data( 'name' ) 1907 ) 1908 ) 1909 .text( __( 'Installation failed.' ) ); 1910 1911 wp.a11y.speak( errorMessage, 'assertive' ); 1912 1913 $document.trigger( 'wp-theme-install-error', response ); 1914 }; 1915 1916 /** 1917 * Sends an Ajax request to the server to delete a theme. 1918 * 1919 * @since 4.6.0 1920 * 1921 * @param {Object} args 1922 * @param {string} args.slug Theme stylesheet. 1923 * @param {deleteThemeSuccess=} args.success Optional. Success callback. Default: wp.updates.deleteThemeSuccess 1924 * @param {deleteThemeError=} args.error Optional. Error callback. Default: wp.updates.deleteThemeError 1925 * @return {$.promise} A jQuery promise that represents the request, 1926 * decorated with an abort() method. 1927 */ 1928 wp.updates.deleteTheme = function( args ) { 1929 var $button; 1930 1931 if ( 'themes' === pagenow ) { 1932 $button = $( '.theme-actions .delete-theme' ); 1933 } else if ( 'themes-network' === pagenow ) { 1934 $button = $( '[data-slug="' + args.slug + '"]' ).find( '.row-actions a.delete' ); 1935 } 1936 1937 args = _.extend( { 1938 success: wp.updates.deleteThemeSuccess, 1939 error: wp.updates.deleteThemeError 1940 }, args ); 1941 1942 if ( $button && $button.html() !== __( 'Deleting...' ) ) { 1943 $button 1944 .data( 'originaltext', $button.html() ) 1945 .text( __( 'Deleting...' ) ); 1946 } 1947 1948 wp.a11y.speak( __( 'Deleting...' ) ); 1949 1950 // Remove previous error messages, if any. 1951 $( '.theme-info .update-message' ).remove(); 1952 1953 $document.trigger( 'wp-theme-deleting', args ); 1954 1955 return wp.updates.ajax( 'delete-theme', args ); 1956 }; 1957 1958 /** 1959 * Updates the UI appropriately after a successful theme deletion. 1960 * 1961 * @since 4.6.0 1962 * 1963 * @param {Object} response Response from the server. 1964 * @param {string} response.slug Slug of the theme that was deleted. 1965 */ 1966 wp.updates.deleteThemeSuccess = function( response ) { 1967 var $themeRows = $( '[data-slug="' + response.slug + '"]' ); 1968 1969 if ( 'themes-network' === pagenow ) { 1970 1971 // Removes the theme and updates rows. 1972 $themeRows.css( { backgroundColor: '#faafaa' } ).fadeOut( 350, function() { 1973 var $views = $( '.subsubsub' ), 1974 $themeRow = $( this ), 1975 themes = settings.themes, 1976 deletedRow = wp.template( 'item-deleted-row' ); 1977 1978 if ( ! $themeRow.hasClass( 'plugin-update-tr' ) ) { 1979 $themeRow.after( 1980 deletedRow( { 1981 slug: response.slug, 1982 colspan: $( '#bulk-action-form' ).find( 'thead th:not(.hidden), thead td' ).length, 1983 name: $themeRow.find( '.theme-title strong' ).text() 1984 } ) 1985 ); 1986 } 1987 1988 $themeRow.remove(); 1989 1990 // Remove theme from update count. 1991 if ( -1 !== _.indexOf( themes.upgrade, response.slug ) ) { 1992 themes.upgrade = _.without( themes.upgrade, response.slug ); 1993 wp.updates.decrementCount( 'theme' ); 1994 } 1995 1996 // Remove from views. 1997 if ( -1 !== _.indexOf( themes.disabled, response.slug ) ) { 1998 themes.disabled = _.without( themes.disabled, response.slug ); 1999 if ( themes.disabled.length ) { 2000 $views.find( '.disabled .count' ).text( '(' + themes.disabled.length + ')' ); 2001 } else { 2002 $views.find( '.disabled' ).remove(); 2003 } 2004 } 2005 2006 if ( -1 !== _.indexOf( themes['auto-update-enabled'], response.slug ) ) { 2007 themes['auto-update-enabled'] = _.without( themes['auto-update-enabled'], response.slug ); 2008 if ( themes['auto-update-enabled'].length ) { 2009 $views.find( '.auto-update-enabled .count' ).text( '(' + themes['auto-update-enabled'].length + ')' ); 2010 } else { 2011 $views.find( '.auto-update-enabled' ).remove(); 2012 } 2013 } 2014 2015 if ( -1 !== _.indexOf( themes['auto-update-disabled'], response.slug ) ) { 2016 themes['auto-update-disabled'] = _.without( themes['auto-update-disabled'], response.slug ); 2017 if ( themes['auto-update-disabled'].length ) { 2018 $views.find( '.auto-update-disabled .count' ).text( '(' + themes['auto-update-disabled'].length + ')' ); 2019 } else { 2020 $views.find( '.auto-update-disabled' ).remove(); 2021 } 2022 } 2023 2024 themes.all = _.without( themes.all, response.slug ); 2025 2026 // There is always at least one theme available. 2027 $views.find( '.all .count' ).text( '(' + themes.all.length + ')' ); 2028 } ); 2029 } 2030 2031 // DecrementCount from update count. 2032 if ( 'themes' === pagenow ) { 2033 var theme = _.find( _wpThemeSettings.themes, { id: response.slug } ); 2034 if ( theme.hasUpdate ) { 2035 wp.updates.decrementCount( 'theme' ); 2036 } 2037 } 2038 2039 wp.a11y.speak( _x( 'Deleted!', 'theme' ) ); 2040 2041 $document.trigger( 'wp-theme-delete-success', response ); 2042 }; 2043 2044 /** 2045 * Updates the UI appropriately after a failed theme deletion. 2046 * 2047 * @since 4.6.0 2048 * 2049 * @param {Object} response Response from the server. 2050 * @param {string} response.slug Slug of the theme to be deleted. 2051 * @param {string} response.errorCode Error code for the error that occurred. 2052 * @param {string} response.errorMessage The error that occurred. 2053 */ 2054 wp.updates.deleteThemeError = function( response ) { 2055 var $themeRow = $( 'tr.inactive[data-slug="' + response.slug + '"]' ), 2056 $button = $( '.theme-actions .delete-theme' ), 2057 updateRow = wp.template( 'item-update-row' ), 2058 $updateRow = $themeRow.siblings( '#' + response.slug + '-update' ), 2059 errorMessage = sprintf( 2060 /* translators: %s: Error string for a failed deletion. */ 2061 __( 'Deletion failed: %s' ), 2062 response.errorMessage 2063 ), 2064 $message = wp.updates.adminNotice( { 2065 className: 'update-message notice-error notice-alt', 2066 message: errorMessage 2067 } ); 2068 2069 if ( wp.updates.maybeHandleCredentialError( response, 'delete-theme' ) ) { 2070 return; 2071 } 2072 2073 if ( 'themes-network' === pagenow ) { 2074 if ( ! $updateRow.length ) { 2075 $themeRow.addClass( 'update' ).after( 2076 updateRow( { 2077 slug: response.slug, 2078 colspan: $( '#bulk-action-form' ).find( 'thead th:not(.hidden), thead td' ).length, 2079 content: $message 2080 } ) 2081 ); 2082 } else { 2083 // Remove previous error messages, if any. 2084 $updateRow.find( '.notice-error' ).remove(); 2085 $updateRow.find( '.plugin-update' ).append( $message ); 2086 } 2087 } else { 2088 $( '.theme-info .theme-description' ).before( $message ); 2089 } 2090 2091 $button.html( $button.data( 'originaltext' ) ); 2092 2093 wp.a11y.speak( errorMessage, 'assertive' ); 2094 2095 $document.trigger( 'wp-theme-delete-error', response ); 2096 }; 2097 2098 /** 2099 * Adds the appropriate callback based on the type of action and the current page. 2100 * 2101 * @since 4.6.0 2102 * @private 2103 * 2104 * @param {Object} data Ajax payload. 2105 * @param {string} action The type of request to perform. 2106 * @return {Object} The Ajax payload with the appropriate callbacks. 2107 */ 2108 wp.updates._addCallbacks = function( data, action ) { 2109 if ( 'import' === pagenow && 'install-plugin' === action ) { 2110 data.success = wp.updates.installImporterSuccess; 2111 data.error = wp.updates.installImporterError; 2112 } 2113 2114 return data; 2115 }; 2116 2117 /** 2118 * Pulls available jobs from the queue and runs them. 2119 * 2120 * @since 4.2.0 2121 * @since 4.6.0 Can handle multiple job types. 2122 */ 2123 wp.updates.queueChecker = function() { 2124 var job; 2125 2126 if ( wp.updates.ajaxLocked || ! wp.updates.queue.length ) { 2127 return; 2128 } 2129 2130 job = wp.updates.queue.shift(); 2131 2132 // Handle a queue job. 2133 switch ( job.action ) { 2134 case 'install-plugin': 2135 wp.updates.installPlugin( job.data ); 2136 break; 2137 2138 case 'update-plugin': 2139 wp.updates.updatePlugin( job.data ); 2140 break; 2141 2142 case 'delete-plugin': 2143 wp.updates.deletePlugin( job.data ); 2144 break; 2145 2146 case 'install-theme': 2147 wp.updates.installTheme( job.data ); 2148 break; 2149 2150 case 'update-theme': 2151 wp.updates.updateTheme( job.data ); 2152 break; 2153 2154 case 'delete-theme': 2155 wp.updates.deleteTheme( job.data ); 2156 break; 2157 2158 default: 2159 break; 2160 } 2161 }; 2162 2163 /** 2164 * Requests the users filesystem credentials if they aren't already known. 2165 * 2166 * @since 4.2.0 2167 * 2168 * @param {Event=} event Optional. Event interface. 2169 */ 2170 wp.updates.requestFilesystemCredentials = function( event ) { 2171 if ( false === wp.updates.filesystemCredentials.available ) { 2172 /* 2173 * After exiting the credentials request modal, 2174 * return the focus to the element triggering the request. 2175 */ 2176 if ( event && ! wp.updates.$elToReturnFocusToFromCredentialsModal ) { 2177 wp.updates.$elToReturnFocusToFromCredentialsModal = $( event.target ); 2178 } 2179 2180 wp.updates.ajaxLocked = true; 2181 wp.updates.requestForCredentialsModalOpen(); 2182 } 2183 }; 2184 2185 /** 2186 * Requests the users filesystem credentials if needed and there is no lock. 2187 * 2188 * @since 4.6.0 2189 * 2190 * @param {Event=} event Optional. Event interface. 2191 */ 2192 wp.updates.maybeRequestFilesystemCredentials = function( event ) { 2193 if ( wp.updates.shouldRequestFilesystemCredentials && ! wp.updates.ajaxLocked ) { 2194 wp.updates.requestFilesystemCredentials( event ); 2195 } 2196 }; 2197 2198 /** 2199 * Keydown handler for the request for credentials modal. 2200 * 2201 * Closes the modal when the escape key is pressed and 2202 * constrains keyboard navigation to inside the modal. 2203 * 2204 * @since 4.2.0 2205 * 2206 * @param {Event} event Event interface. 2207 */ 2208 wp.updates.keydown = function( event ) { 2209 if ( 27 === event.keyCode ) { 2210 wp.updates.requestForCredentialsModalCancel(); 2211 } else if ( 9 === event.keyCode ) { 2212 2213 // #upgrade button must always be the last focus-able element in the dialog. 2214 if ( 'upgrade' === event.target.id && ! event.shiftKey ) { 2215 $( '#hostname' ).trigger( 'focus' ); 2216 2217 event.preventDefault(); 2218 } else if ( 'hostname' === event.target.id && event.shiftKey ) { 2219 $( '#upgrade' ).trigger( 'focus' ); 2220 2221 event.preventDefault(); 2222 } 2223 } 2224 }; 2225 2226 /** 2227 * Opens the request for credentials modal. 2228 * 2229 * @since 4.2.0 2230 */ 2231 wp.updates.requestForCredentialsModalOpen = function() { 2232 var $modal = $( '#request-filesystem-credentials-dialog' ); 2233 2234 $( 'body' ).addClass( 'modal-open' ); 2235 $modal.show(); 2236 $modal.find( 'input:enabled:first' ).trigger( 'focus' ); 2237 $modal.on( 'keydown', wp.updates.keydown ); 2238 }; 2239 2240 /** 2241 * Closes the request for credentials modal. 2242 * 2243 * @since 4.2.0 2244 */ 2245 wp.updates.requestForCredentialsModalClose = function() { 2246 $( '#request-filesystem-credentials-dialog' ).hide(); 2247 $( 'body' ).removeClass( 'modal-open' ); 2248 2249 if ( wp.updates.$elToReturnFocusToFromCredentialsModal ) { 2250 wp.updates.$elToReturnFocusToFromCredentialsModal.trigger( 'focus' ); 2251 } 2252 }; 2253 2254 /** 2255 * Takes care of the steps that need to happen when the modal is canceled out. 2256 * 2257 * @since 4.2.0 2258 * @since 4.6.0 Triggers an event for callbacks to listen to and add their actions. 2259 */ 2260 wp.updates.requestForCredentialsModalCancel = function() { 2261 2262 // Not ajaxLocked and no queue means we already have cleared things up. 2263 if ( ! wp.updates.ajaxLocked && ! wp.updates.queue.length ) { 2264 return; 2265 } 2266 2267 _.each( wp.updates.queue, function( job ) { 2268 $document.trigger( 'credential-modal-cancel', job ); 2269 } ); 2270 2271 // Remove the lock, and clear the queue. 2272 wp.updates.ajaxLocked = false; 2273 wp.updates.queue = []; 2274 2275 wp.updates.requestForCredentialsModalClose(); 2276 }; 2277 2278 /** 2279 * Displays an error message in the request for credentials form. 2280 * 2281 * @since 4.2.0 2282 * 2283 * @param {string} message Error message. 2284 */ 2285 wp.updates.showErrorInCredentialsForm = function( message ) { 2286 var $filesystemForm = $( '#request-filesystem-credentials-form' ); 2287 2288 // Remove any existing error. 2289 $filesystemForm.find( '.notice' ).remove(); 2290 $filesystemForm.find( '#request-filesystem-credentials-title' ).after( '<div class="notice notice-alt notice-error"><p>' + message + '</p></div>' ); 2291 }; 2292 2293 /** 2294 * Handles credential errors and runs events that need to happen in that case. 2295 * 2296 * @since 4.2.0 2297 * 2298 * @param {Object} response Ajax response. 2299 * @param {string} action The type of request to perform. 2300 */ 2301 wp.updates.credentialError = function( response, action ) { 2302 2303 // Restore callbacks. 2304 response = wp.updates._addCallbacks( response, action ); 2305 2306 wp.updates.queue.unshift( { 2307 action: action, 2308 2309 /* 2310 * Not cool that we're depending on response for this data. 2311 * This would feel more whole in a view all tied together. 2312 */ 2313 data: response 2314 } ); 2315 2316 wp.updates.filesystemCredentials.available = false; 2317 wp.updates.showErrorInCredentialsForm( response.errorMessage ); 2318 wp.updates.requestFilesystemCredentials(); 2319 }; 2320 2321 /** 2322 * Handles credentials errors if it could not connect to the filesystem. 2323 * 2324 * @since 4.6.0 2325 * 2326 * @param {Object} response Response from the server. 2327 * @param {string} response.errorCode Error code for the error that occurred. 2328 * @param {string} response.errorMessage The error that occurred. 2329 * @param {string} action The type of request to perform. 2330 * @return {boolean} Whether there is an error that needs to be handled or not. 2331 */ 2332 wp.updates.maybeHandleCredentialError = function( response, action ) { 2333 if ( wp.updates.shouldRequestFilesystemCredentials && response.errorCode && 'unable_to_connect_to_filesystem' === response.errorCode ) { 2334 wp.updates.credentialError( response, action ); 2335 return true; 2336 } 2337 2338 return false; 2339 }; 2340 2341 /** 2342 * Validates an Ajax response to ensure it's a proper object. 2343 * 2344 * If the response deems to be invalid, an admin notice is being displayed. 2345 * 2346 * @param {(Object|string)} response Response from the server. 2347 * @param {function=} response.always Optional. Callback for when the Deferred is resolved or rejected. 2348 * @param {string=} response.statusText Optional. Status message corresponding to the status code. 2349 * @param {string=} response.responseText Optional. Request response as text. 2350 * @param {string} action Type of action the response is referring to. Can be 'delete', 2351 * 'update' or 'install'. 2352 */ 2353 wp.updates.isValidResponse = function( response, action ) { 2354 var error = __( 'Something went wrong.' ), 2355 errorMessage; 2356 2357 // Make sure the response is a valid data object and not a Promise object. 2358 if ( _.isObject( response ) && ! _.isFunction( response.always ) ) { 2359 return true; 2360 } 2361 2362 if ( _.isString( response ) && '-1' === response ) { 2363 error = __( 'An error has occurred. Please reload the page and try again.' ); 2364 } else if ( _.isString( response ) ) { 2365 error = response; 2366 } else if ( 'undefined' !== typeof response.readyState && 0 === response.readyState ) { 2367 error = __( 'Connection lost or the server is busy. Please try again later.' ); 2368 } else if ( _.isString( response.responseText ) && '' !== response.responseText ) { 2369 error = response.responseText; 2370 } else if ( _.isString( response.statusText ) ) { 2371 error = response.statusText; 2372 } 2373 2374 switch ( action ) { 2375 case 'update': 2376 /* translators: %s: Error string for a failed update. */ 2377 errorMessage = __( 'Update failed: %s' ); 2378 break; 2379 2380 case 'install': 2381 /* translators: %s: Error string for a failed installation. */ 2382 errorMessage = __( 'Installation failed: %s' ); 2383 break; 2384 2385 case 'check-dependencies': 2386 /* translators: %s: Error string for a failed dependencies check. */ 2387 errorMessage = __( 'Dependencies check failed: %s' ); 2388 break; 2389 2390 case 'activate': 2391 /* translators: %s: Error string for a failed activation. */ 2392 errorMessage = __( 'Activation failed: %s' ); 2393 break; 2394 2395 case 'delete': 2396 /* translators: %s: Error string for a failed deletion. */ 2397 errorMessage = __( 'Deletion failed: %s' ); 2398 break; 2399 } 2400 2401 // Messages are escaped, remove HTML tags to make them more readable. 2402 error = error.replace( /<[\/a-z][^<>]*>/gi, '' ); 2403 errorMessage = errorMessage.replace( '%s', error ); 2404 2405 // Add admin notice. 2406 wp.updates.addAdminNotice( { 2407 id: 'unknown_error', 2408 className: 'notice-error is-dismissible', 2409 message: _.escape( errorMessage ) 2410 } ); 2411 2412 // Remove the lock, and clear the queue. 2413 wp.updates.ajaxLocked = false; 2414 wp.updates.queue = []; 2415 2416 // Change buttons of all running updates. 2417 $( '.button.updating-message' ) 2418 .removeClass( 'updating-message' ) 2419 .removeAttr( 'aria-label' ) 2420 .prop( 'disabled', true ) 2421 .text( __( 'Update failed.' ) ); 2422 2423 $( '.updating-message:not(.button):not(.thickbox)' ) 2424 .removeClass( 'updating-message notice-warning' ) 2425 .addClass( 'notice-error' ) 2426 .find( 'p' ) 2427 .removeAttr( 'aria-label' ) 2428 .text( errorMessage ); 2429 2430 wp.a11y.speak( errorMessage, 'assertive' ); 2431 2432 return false; 2433 }; 2434 2435 /** 2436 * Potentially adds an AYS to a user attempting to leave the page. 2437 * 2438 * If an update is on-going and a user attempts to leave the page, 2439 * opens an "Are you sure?" alert. 2440 * 2441 * @since 4.2.0 2442 */ 2443 wp.updates.beforeunload = function() { 2444 if ( wp.updates.ajaxLocked ) { 2445 return __( 'Updates may not complete if you navigate away from this page.' ); 2446 } 2447 }; 2448 2449 $( function() { 2450 var $pluginFilter = $( '#plugin-filter, #plugin-information-footer' ), 2451 $bulkActionForm = $( '#bulk-action-form' ), 2452 $filesystemForm = $( '#request-filesystem-credentials-form' ), 2453 $filesystemModal = $( '#request-filesystem-credentials-dialog' ), 2454 $pluginSearch = $( '.plugins-php .wp-filter-search' ), 2455 $pluginInstallSearch = $( '.plugin-install-php .wp-filter-search' ); 2456 2457 settings = _.extend( settings, window._wpUpdatesItemCounts || {} ); 2458 2459 if ( settings.totals ) { 2460 wp.updates.refreshCount(); 2461 } 2462 2463 /* 2464 * Whether a user needs to submit filesystem credentials. 2465 * 2466 * This is based on whether the form was output on the page server-side. 2467 * 2468 * @see {wp_print_request_filesystem_credentials_modal() in PHP} 2469 */ 2470 wp.updates.shouldRequestFilesystemCredentials = $filesystemModal.length > 0; 2471 2472 /** 2473 * File system credentials form submit noop-er / handler. 2474 * 2475 * @since 4.2.0 2476 */ 2477 $filesystemModal.on( 'submit', 'form', function( event ) { 2478 event.preventDefault(); 2479 2480 // Persist the credentials input by the user for the duration of the page load. 2481 wp.updates.filesystemCredentials.ftp.hostname = $( '#hostname' ).val(); 2482 wp.updates.filesystemCredentials.ftp.username = $( '#username' ).val(); 2483 wp.updates.filesystemCredentials.ftp.password = $( '#password' ).val(); 2484 wp.updates.filesystemCredentials.ftp.connectionType = $( 'input[name="connection_type"]:checked' ).val(); 2485 wp.updates.filesystemCredentials.ssh.publicKey = $( '#public_key' ).val(); 2486 wp.updates.filesystemCredentials.ssh.privateKey = $( '#private_key' ).val(); 2487 wp.updates.filesystemCredentials.fsNonce = $( '#_fs_nonce' ).val(); 2488 wp.updates.filesystemCredentials.available = true; 2489 2490 // Unlock and invoke the queue. 2491 wp.updates.ajaxLocked = false; 2492 wp.updates.queueChecker(); 2493 2494 wp.updates.requestForCredentialsModalClose(); 2495 } ); 2496 2497 /** 2498 * Closes the request credentials modal when clicking the 'Cancel' button or outside of the modal. 2499 * 2500 * @since 4.2.0 2501 */ 2502 $filesystemModal.on( 'click', '[data-js-action="close"], .notification-dialog-background', wp.updates.requestForCredentialsModalCancel ); 2503 2504 /** 2505 * Hide SSH fields when not selected. 2506 * 2507 * @since 4.2.0 2508 */ 2509 $filesystemForm.on( 'change', 'input[name="connection_type"]', function() { 2510 $( '#ssh-keys' ).toggleClass( 'hidden', ( 'ssh' !== $( this ).val() ) ); 2511 } ).trigger( 'change' ); 2512 2513 /** 2514 * Handles events after the credential modal was closed. 2515 * 2516 * @since 4.6.0 2517 * 2518 * @param {Event} event Event interface. 2519 * @param {string} job The install/update.delete request. 2520 */ 2521 $document.on( 'credential-modal-cancel', function( event, job ) { 2522 var $updatingMessage = $( '.updating-message' ), 2523 $message, originalText; 2524 2525 if ( 'import' === pagenow ) { 2526 $updatingMessage.removeClass( 'updating-message' ); 2527 } else if ( 'plugins' === pagenow || 'plugins-network' === pagenow ) { 2528 if ( 'update-plugin' === job.action ) { 2529 $message = $( 'tr[data-plugin="' + job.data.plugin + '"]' ).find( '.update-message' ); 2530 } else if ( 'delete-plugin' === job.action ) { 2531 $message = $( '[data-plugin="' + job.data.plugin + '"]' ).find( '.row-actions a.delete' ); 2532 } 2533 } else if ( 'themes' === pagenow || 'themes-network' === pagenow ) { 2534 if ( 'update-theme' === job.action ) { 2535 $message = $( '[data-slug="' + job.data.slug + '"]' ).find( '.update-message' ); 2536 } else if ( 'delete-theme' === job.action && 'themes-network' === pagenow ) { 2537 $message = $( '[data-slug="' + job.data.slug + '"]' ).find( '.row-actions a.delete' ); 2538 } else if ( 'delete-theme' === job.action && 'themes' === pagenow ) { 2539 $message = $( '.theme-actions .delete-theme' ); 2540 } 2541 } else { 2542 $message = $updatingMessage; 2543 } 2544 2545 if ( $message && $message.hasClass( 'updating-message' ) ) { 2546 originalText = $message.data( 'originaltext' ); 2547 2548 if ( 'undefined' === typeof originalText ) { 2549 originalText = $( '<p>' ).html( $message.find( 'p' ).data( 'originaltext' ) ); 2550 } 2551 2552 $message 2553 .removeClass( 'updating-message' ) 2554 .html( originalText ); 2555 2556 if ( 'plugin-install' === pagenow || 'plugin-install-network' === pagenow ) { 2557 if ( 'update-plugin' === job.action ) { 2558 $message.attr( 2559 'aria-label', 2560 sprintf( 2561 /* translators: %s: Plugin name and version. */ 2562 _x( 'Update %s now', 'plugin' ), 2563 $message.data( 'name' ) 2564 ) 2565 ); 2566 } else if ( 'install-plugin' === job.action ) { 2567 $message.attr( 2568 'aria-label', 2569 sprintf( 2570 /* translators: %s: Plugin name. */ 2571 _x( 'Install %s now', 'plugin' ), 2572 $message.data( 'name' ) 2573 ) 2574 ); 2575 } 2576 } 2577 } 2578 2579 wp.a11y.speak( __( 'Update canceled.' ) ); 2580 } ); 2581 2582 /** 2583 * Click handler for plugin updates in List Table view. 2584 * 2585 * @since 4.2.0 2586 * 2587 * @param {Event} event Event interface. 2588 */ 2589 $bulkActionForm.on( 'click', '[data-plugin] .update-link', function( event ) { 2590 var $message = $( event.target ), 2591 $pluginRow = $message.parents( 'tr' ); 2592 2593 event.preventDefault(); 2594 2595 if ( $message.hasClass( 'updating-message' ) || $message.hasClass( 'button-disabled' ) ) { 2596 return; 2597 } 2598 2599 wp.updates.maybeRequestFilesystemCredentials( event ); 2600 2601 // Return the user to the input box of the plugin's table row after closing the modal. 2602 wp.updates.$elToReturnFocusToFromCredentialsModal = $pluginRow.find( '.check-column input' ); 2603 wp.updates.updatePlugin( { 2604 plugin: $pluginRow.data( 'plugin' ), 2605 slug: $pluginRow.data( 'slug' ) 2606 } ); 2607 } ); 2608 2609 /** 2610 * Click handler for plugin updates in plugin install view. 2611 * 2612 * @since 4.2.0 2613 * 2614 * @param {Event} event Event interface. 2615 */ 2616 $pluginFilter.on( 'click', '.update-now', function( event ) { 2617 var $button = $( event.target ); 2618 event.preventDefault(); 2619 2620 if ( $button.hasClass( 'updating-message' ) || $button.hasClass( 'button-disabled' ) ) { 2621 return; 2622 } 2623 2624 wp.updates.maybeRequestFilesystemCredentials( event ); 2625 2626 wp.updates.updatePlugin( { 2627 plugin: $button.data( 'plugin' ), 2628 slug: $button.data( 'slug' ) 2629 } ); 2630 } ); 2631 2632 /** 2633 * Click handler for plugin installs in plugin install view. 2634 * 2635 * @since 4.6.0 2636 * 2637 * @param {Event} event Event interface. 2638 */ 2639 $pluginFilter.on( 'click', '.install-now', function( event ) { 2640 var $button = $( event.target ); 2641 event.preventDefault(); 2642 2643 if ( $button.hasClass( 'updating-message' ) || $button.hasClass( 'button-disabled' ) ) { 2644 return; 2645 } 2646 2647 if ( wp.updates.shouldRequestFilesystemCredentials && ! wp.updates.ajaxLocked ) { 2648 wp.updates.requestFilesystemCredentials( event ); 2649 2650 $document.on( 'credential-modal-cancel', function() { 2651 var $message = $( '.install-now.updating-message' ); 2652 2653 $message 2654 .removeClass( 'updating-message' ) 2655 .text( _x( 'Install Now', 'plugin' ) ); 2656 2657 wp.a11y.speak( __( 'Update canceled.' ) ); 2658 } ); 2659 } 2660 2661 wp.updates.installPlugin( { 2662 slug: $button.data( 'slug' ) 2663 } ); 2664 } ); 2665 2666 /** 2667 * Click handler for plugin activations in plugin activation view. 2668 * 2669 * @since 6.5.0 2670 * 2671 * @param {Event} event Event interface. 2672 */ 2673 $pluginFilter.on( 'click', '.activate-now', function( event ) { 2674 var $activateButton = $( event.target ); 2675 2676 event.preventDefault(); 2677 2678 if ( $activateButton.hasClass( 'activating-message' ) || $activateButton.hasClass( 'button-disabled' ) ) { 2679 return; 2680 } 2681 2682 $activateButton 2683 .removeClass( 'activate-now button-primary' ) 2684 .addClass( 'activating-message' ) 2685 .attr( 2686 'aria-label', 2687 sprintf( 2688 /* translators: %s: Plugin name. */ 2689 _x( 'Activating %s', 'plugin' ), 2690 $activateButton.data( 'name' ) 2691 ) 2692 ) 2693 .text( __( 'Activating...' ) ); 2694 2695 wp.updates.activatePlugin( 2696 { 2697 name: $activateButton.data( 'name' ), 2698 slug: $activateButton.data( 'slug' ), 2699 plugin: $activateButton.data( 'plugin' ) 2700 } 2701 ); 2702 }); 2703 2704 /** 2705 * Click handler for importer plugins installs in the Import screen. 2706 * 2707 * @since 4.6.0 2708 * 2709 * @param {Event} event Event interface. 2710 */ 2711 $document.on( 'click', '.importer-item .install-now', function( event ) { 2712 var $button = $( event.target ), 2713 pluginName = $( this ).data( 'name' ); 2714 2715 event.preventDefault(); 2716 2717 if ( $button.hasClass( 'updating-message' ) ) { 2718 return; 2719 } 2720 2721 if ( wp.updates.shouldRequestFilesystemCredentials && ! wp.updates.ajaxLocked ) { 2722 wp.updates.requestFilesystemCredentials( event ); 2723 2724 $document.on( 'credential-modal-cancel', function() { 2725 2726 $button 2727 .removeClass( 'updating-message' ) 2728 .attr( 2729 'aria-label', 2730 sprintf( 2731 /* translators: %s: Plugin name. */ 2732 _x( 'Install %s now', 'plugin' ), 2733 pluginName 2734 ) 2735 ) 2736 .text( _x( 'Install Now', 'plugin' ) ); 2737 2738 wp.a11y.speak( __( 'Update canceled.' ) ); 2739 } ); 2740 } 2741 2742 wp.updates.installPlugin( { 2743 slug: $button.data( 'slug' ), 2744 pagenow: pagenow, 2745 success: wp.updates.installImporterSuccess, 2746 error: wp.updates.installImporterError 2747 } ); 2748 } ); 2749 2750 /** 2751 * Click handler for plugin deletions. 2752 * 2753 * @since 4.6.0 2754 * 2755 * @param {Event} event Event interface. 2756 */ 2757 $bulkActionForm.on( 'click', '[data-plugin] a.delete', function( event ) { 2758 var $pluginRow = $( event.target ).parents( 'tr' ), 2759 confirmMessage; 2760 2761 if ( $pluginRow.hasClass( 'is-uninstallable' ) ) { 2762 confirmMessage = sprintf( 2763 /* translators: %s: Plugin name. */ 2764 __( 'Are you sure you want to delete %s and its data?' ), 2765 $pluginRow.find( '.plugin-title strong' ).text() 2766 ); 2767 } else { 2768 confirmMessage = sprintf( 2769 /* translators: %s: Plugin name. */ 2770 __( 'Are you sure you want to delete %s?' ), 2771 $pluginRow.find( '.plugin-title strong' ).text() 2772 ); 2773 } 2774 2775 event.preventDefault(); 2776 2777 if ( ! window.confirm( confirmMessage ) ) { 2778 return; 2779 } 2780 2781 wp.updates.maybeRequestFilesystemCredentials( event ); 2782 2783 wp.updates.deletePlugin( { 2784 plugin: $pluginRow.data( 'plugin' ), 2785 slug: $pluginRow.data( 'slug' ) 2786 } ); 2787 2788 } ); 2789 2790 /** 2791 * Click handler for theme updates. 2792 * 2793 * @since 4.6.0 2794 * 2795 * @param {Event} event Event interface. 2796 */ 2797 $document.on( 'click', '.themes-php.network-admin .update-link', function( event ) { 2798 var $message = $( event.target ), 2799 $themeRow = $message.parents( 'tr' ); 2800 2801 event.preventDefault(); 2802 2803 if ( $message.hasClass( 'updating-message' ) || $message.hasClass( 'button-disabled' ) ) { 2804 return; 2805 } 2806 2807 wp.updates.maybeRequestFilesystemCredentials( event ); 2808 2809 // Return the user to the input box of the theme's table row after closing the modal. 2810 wp.updates.$elToReturnFocusToFromCredentialsModal = $themeRow.find( '.check-column input' ); 2811 wp.updates.updateTheme( { 2812 slug: $themeRow.data( 'slug' ) 2813 } ); 2814 } ); 2815 2816 /** 2817 * Click handler for theme deletions. 2818 * 2819 * @since 4.6.0 2820 * 2821 * @param {Event} event Event interface. 2822 */ 2823 $document.on( 'click', '.themes-php.network-admin a.delete', function( event ) { 2824 var $themeRow = $( event.target ).parents( 'tr' ), 2825 confirmMessage = sprintf( 2826 /* translators: %s: Theme name. */ 2827 __( 'Are you sure you want to delete %s?' ), 2828 $themeRow.find( '.theme-title strong' ).text() 2829 ); 2830 2831 event.preventDefault(); 2832 2833 if ( ! window.confirm( confirmMessage ) ) { 2834 return; 2835 } 2836 2837 wp.updates.maybeRequestFilesystemCredentials( event ); 2838 2839 wp.updates.deleteTheme( { 2840 slug: $themeRow.data( 'slug' ) 2841 } ); 2842 } ); 2843 2844 /** 2845 * Bulk action handler for plugins and themes. 2846 * 2847 * Handles both deletions and updates. 2848 * 2849 * @since 4.6.0 2850 * 2851 * @param {Event} event Event interface. 2852 */ 2853 $bulkActionForm.on( 'click', '[type="submit"]:not([name="clear-recent-list"])', function( event ) { 2854 var bulkAction = $( event.target ).siblings( 'select' ).val(), 2855 itemsSelected = $bulkActionForm.find( 'input[name="checked[]"]:checked' ), 2856 success = 0, 2857 error = 0, 2858 errorMessages = [], 2859 type, action; 2860 2861 // Determine which type of item we're dealing with. 2862 switch ( pagenow ) { 2863 case 'plugins': 2864 case 'plugins-network': 2865 type = 'plugin'; 2866 break; 2867 2868 case 'themes-network': 2869 type = 'theme'; 2870 break; 2871 2872 default: 2873 return; 2874 } 2875 2876 // Bail if there were no items selected. 2877 if ( ! itemsSelected.length ) { 2878 event.preventDefault(); 2879 $( 'html, body' ).animate( { scrollTop: 0 } ); 2880 2881 return wp.updates.addAdminNotice( { 2882 id: 'no-items-selected', 2883 className: 'notice-error is-dismissible', 2884 message: __( 'Please select at least one item to perform this action on.' ) 2885 } ); 2886 } 2887 2888 // Determine the type of request we're dealing with. 2889 switch ( bulkAction ) { 2890 case 'update-selected': 2891 action = bulkAction.replace( 'selected', type ); 2892 break; 2893 2894 case 'delete-selected': 2895 var confirmMessage = 'plugin' === type ? 2896 __( 'Are you sure you want to delete the selected plugins and their data?' ) : 2897 __( 'Caution: These themes may be active on other sites in the network. Are you sure you want to proceed?' ); 2898 2899 if ( ! window.confirm( confirmMessage ) ) { 2900 event.preventDefault(); 2901 return; 2902 } 2903 2904 action = bulkAction.replace( 'selected', type ); 2905 break; 2906 2907 default: 2908 return; 2909 } 2910 2911 wp.updates.maybeRequestFilesystemCredentials( event ); 2912 2913 event.preventDefault(); 2914 2915 // Un-check the bulk checkboxes. 2916 $bulkActionForm.find( '.manage-column [type="checkbox"]' ).prop( 'checked', false ); 2917 2918 $document.trigger( 'wp-' + type + '-bulk-' + bulkAction, itemsSelected ); 2919 2920 // Find all the checkboxes which have been checked. 2921 itemsSelected.each( function( index, element ) { 2922 var $checkbox = $( element ), 2923 $itemRow = $checkbox.parents( 'tr' ); 2924 2925 // Only add update-able items to the update queue. 2926 if ( 'update-selected' === bulkAction && ( ! $itemRow.hasClass( 'update' ) || $itemRow.find( 'notice-error' ).length ) ) { 2927 2928 // Un-check the box. 2929 $checkbox.prop( 'checked', false ); 2930 return; 2931 } 2932 2933 // Don't add items to the update queue again, even if the user clicks the update button several times. 2934 if ( 'update-selected' === bulkAction && $itemRow.hasClass( 'is-enqueued' ) ) { 2935 return; 2936 } 2937 2938 $itemRow.addClass( 'is-enqueued' ); 2939 2940 // Add it to the queue. 2941 wp.updates.queue.push( { 2942 action: action, 2943 data: { 2944 plugin: $itemRow.data( 'plugin' ), 2945 slug: $itemRow.data( 'slug' ) 2946 } 2947 } ); 2948 } ); 2949 2950 // Display bulk notification for updates of any kind. 2951 $document.on( 'wp-plugin-update-success wp-plugin-update-error wp-theme-update-success wp-theme-update-error', function( event, response ) { 2952 var $itemRow = $( '[data-slug="' + response.slug + '"]' ), 2953 $bulkActionNotice, itemName; 2954 2955 if ( 'wp-' + response.update + '-update-success' === event.type ) { 2956 success++; 2957 } else { 2958 itemName = response.pluginName ? response.pluginName : $itemRow.find( '.column-primary strong' ).text(); 2959 2960 error++; 2961 errorMessages.push( itemName + ': ' + response.errorMessage ); 2962 } 2963 2964 $itemRow.find( 'input[name="checked[]"]:checked' ).prop( 'checked', false ); 2965 2966 wp.updates.adminNotice = wp.template( 'wp-bulk-updates-admin-notice' ); 2967 2968 var successMessage = null; 2969 2970 if ( success ) { 2971 if ( 'plugin' === response.update ) { 2972 successMessage = sprintf( 2973 /* translators: %s: Number of plugins. */ 2974 _n( '%s plugin successfully updated.', '%s plugins successfully updated.', success ), 2975 success 2976 ); 2977 } else { 2978 successMessage = sprintf( 2979 /* translators: %s: Number of themes. */ 2980 _n( '%s theme successfully updated.', '%s themes successfully updated.', success ), 2981 success 2982 ); 2983 } 2984 } 2985 2986 var errorMessage = null; 2987 2988 if ( error ) { 2989 errorMessage = sprintf( 2990 /* translators: %s: Number of failed updates. */ 2991 _n( '%s update failed.', '%s updates failed.', error ), 2992 error 2993 ); 2994 } 2995 2996 wp.updates.addAdminNotice( { 2997 id: 'bulk-action-notice', 2998 className: 'bulk-action-notice', 2999 successMessage: successMessage, 3000 errorMessage: errorMessage, 3001 errorMessages: errorMessages, 3002 type: response.update 3003 } ); 3004 3005 $bulkActionNotice = $( '#bulk-action-notice' ).on( 'click', 'button', function() { 3006 // $( this ) is the clicked button, no need to get it again. 3007 $( this ) 3008 .toggleClass( 'bulk-action-errors-collapsed' ) 3009 .attr( 'aria-expanded', ! $( this ).hasClass( 'bulk-action-errors-collapsed' ) ); 3010 // Show the errors list. 3011 $bulkActionNotice.find( '.bulk-action-errors' ).toggleClass( 'hidden' ); 3012 } ); 3013 3014 if ( error > 0 && ! wp.updates.queue.length ) { 3015 $( 'html, body' ).animate( { scrollTop: 0 } ); 3016 } 3017 } ); 3018 3019 // Reset admin notice template after #bulk-action-notice was added. 3020 $document.on( 'wp-updates-notice-added', function() { 3021 wp.updates.adminNotice = wp.template( 'wp-updates-admin-notice' ); 3022 } ); 3023 3024 // Check the queue, now that the event handlers have been added. 3025 wp.updates.queueChecker(); 3026 } ); 3027 3028 if ( $pluginInstallSearch.length ) { 3029 $pluginInstallSearch.attr( 'aria-describedby', 'live-search-desc' ); 3030 } 3031 3032 /** 3033 * Handles changes to the plugin search box on the new-plugin page, 3034 * searching the repository dynamically. 3035 * 3036 * @since 4.6.0 3037 */ 3038 $pluginInstallSearch.on( 'keyup input', _.debounce( function( event, eventtype ) { 3039 var $searchTab = $( '.plugin-install-search' ), data, searchLocation; 3040 3041 data = { 3042 _ajax_nonce: wp.updates.ajaxNonce, 3043 s: encodeURIComponent( event.target.value ), 3044 tab: 'search', 3045 type: $( '#typeselector' ).val(), 3046 pagenow: pagenow 3047 }; 3048 searchLocation = location.href.split( '?' )[ 0 ] + '?' + $.param( _.omit( data, [ '_ajax_nonce', 'pagenow' ] ) ); 3049 3050 // Clear on escape. 3051 if ( 'keyup' === event.type && 27 === event.which ) { 3052 event.target.value = ''; 3053 } 3054 3055 if ( wp.updates.searchTerm === data.s && 'typechange' !== eventtype ) { 3056 return; 3057 } else { 3058 $pluginFilter.empty(); 3059 wp.updates.searchTerm = data.s; 3060 } 3061 3062 if ( window.history && window.history.replaceState ) { 3063 window.history.replaceState( null, '', searchLocation ); 3064 } 3065 3066 if ( ! $searchTab.length ) { 3067 $searchTab = $( '<li class="plugin-install-search" />' ) 3068 .append( $( '<a />', { 3069 'class': 'current', 3070 'href': searchLocation, 3071 'text': __( 'Search Results' ) 3072 } ) ); 3073 3074 $( '.wp-filter .filter-links .current' ) 3075 .removeClass( 'current' ) 3076 .parents( '.filter-links' ) 3077 .prepend( $searchTab ); 3078 3079 $pluginFilter.prev( 'p' ).remove(); 3080 $( '.plugins-popular-tags-wrapper' ).remove(); 3081 } 3082 3083 if ( 'undefined' !== typeof wp.updates.searchRequest ) { 3084 wp.updates.searchRequest.abort(); 3085 } 3086 $( 'body' ).addClass( 'loading-content' ); 3087 3088 wp.updates.searchRequest = wp.ajax.post( 'search-install-plugins', data ).done( function( response ) { 3089 $( 'body' ).removeClass( 'loading-content' ); 3090 $pluginFilter.append( response.items ); 3091 delete wp.updates.searchRequest; 3092 3093 if ( 0 === response.count ) { 3094 wp.a11y.speak( __( 'You do not appear to have any plugins available at this time.' ) ); 3095 } else { 3096 wp.a11y.speak( 3097 sprintf( 3098 /* translators: %s: Number of plugins. */ 3099 __( 'Number of plugins found: %d' ), 3100 response.count 3101 ) 3102 ); 3103 } 3104 } ); 3105 }, 1000 ) ); 3106 3107 if ( $pluginSearch.length ) { 3108 $pluginSearch.attr( 'aria-describedby', 'live-search-desc' ); 3109 } 3110 3111 /** 3112 * Handles changes to the plugin search box on the Installed Plugins screen, 3113 * searching the plugin list dynamically. 3114 * 3115 * @since 4.6.0 3116 */ 3117 $pluginSearch.on( 'keyup input', _.debounce( function( event ) { 3118 var data = { 3119 _ajax_nonce: wp.updates.ajaxNonce, 3120 s: encodeURIComponent( event.target.value ), 3121 pagenow: pagenow, 3122 plugin_status: 'all' 3123 }, 3124 queryArgs; 3125 3126 // Clear on escape. 3127 if ( 'keyup' === event.type && 27 === event.which ) { 3128 event.target.value = ''; 3129 } 3130 3131 if ( wp.updates.searchTerm === data.s ) { 3132 return; 3133 } else { 3134 wp.updates.searchTerm = data.s; 3135 } 3136 3137 queryArgs = _.object( _.compact( _.map( location.search.slice( 1 ).split( '&' ), function( item ) { 3138 if ( item ) return item.split( '=' ); 3139 } ) ) ); 3140 3141 data.plugin_status = queryArgs.plugin_status || 'all'; 3142 3143 if ( window.history && window.history.replaceState ) { 3144 window.history.replaceState( null, '', location.href.split( '?' )[ 0 ] + '?s=' + data.s + '&plugin_status=' + data.plugin_status ); 3145 } 3146 3147 if ( 'undefined' !== typeof wp.updates.searchRequest ) { 3148 wp.updates.searchRequest.abort(); 3149 } 3150 3151 $bulkActionForm.empty(); 3152 $( 'body' ).addClass( 'loading-content' ); 3153 $( '.subsubsub .current' ).removeClass( 'current' ); 3154 3155 wp.updates.searchRequest = wp.ajax.post( 'search-plugins', data ).done( function( response ) { 3156 3157 // Can we just ditch this whole subtitle business? 3158 var $subTitle = $( '<span />' ).addClass( 'subtitle' ).html( 3159 sprintf( 3160 /* translators: %s: Search query. */ 3161 __( 'Search results for: %s' ), 3162 '<strong>' + _.escape( decodeURIComponent( data.s ) ) + '</strong>' 3163 ) ), 3164 $oldSubTitle = $( '.wrap .subtitle' ); 3165 3166 if ( ! data.s.length ) { 3167 $oldSubTitle.remove(); 3168 $( '.subsubsub .' + data.plugin_status + ' a' ).addClass( 'current' ); 3169 } else if ( $oldSubTitle.length ) { 3170 $oldSubTitle.replaceWith( $subTitle ); 3171 } else { 3172 $( '.wp-header-end' ).before( $subTitle ); 3173 } 3174 3175 $( 'body' ).removeClass( 'loading-content' ); 3176 $bulkActionForm.append( response.items ); 3177 delete wp.updates.searchRequest; 3178 3179 if ( 0 === response.count ) { 3180 wp.a11y.speak( __( 'No plugins found. Try a different search.' ) ); 3181 } else { 3182 wp.a11y.speak( 3183 sprintf( 3184 /* translators: %s: Number of plugins. */ 3185 __( 'Number of plugins found: %d' ), 3186 response.count 3187 ) 3188 ); 3189 } 3190 } ); 3191 }, 500 ) ); 3192 3193 /** 3194 * Trigger a search event when the search form gets submitted. 3195 * 3196 * @since 4.6.0 3197 */ 3198 $document.on( 'submit', '.search-plugins', function( event ) { 3199 event.preventDefault(); 3200 3201 $( 'input.wp-filter-search' ).trigger( 'input' ); 3202 } ); 3203 3204 /** 3205 * Trigger a search event when the "Try Again" button is clicked. 3206 * 3207 * @since 4.9.0 3208 */ 3209 $document.on( 'click', '.try-again', function( event ) { 3210 event.preventDefault(); 3211 $pluginInstallSearch.trigger( 'input' ); 3212 } ); 3213 3214 /** 3215 * Trigger a search event when the search type gets changed. 3216 * 3217 * @since 4.6.0 3218 */ 3219 $( '#typeselector' ).on( 'change', function() { 3220 var $search = $( 'input[name="s"]' ); 3221 3222 if ( $search.val().length ) { 3223 $search.trigger( 'input', 'typechange' ); 3224 } 3225 } ); 3226 3227 /** 3228 * Click handler for updating a plugin from the details modal on `plugin-install.php`. 3229 * 3230 * @since 4.2.0 3231 * 3232 * @param {Event} event Event interface. 3233 */ 3234 $( '#plugin_update_from_iframe' ).on( 'click', function( event ) { 3235 var target = window.parent === window ? null : window.parent, 3236 update; 3237 3238 $.support.postMessage = !! window.postMessage; 3239 3240 if ( false === $.support.postMessage || null === target || -1 !== window.parent.location.pathname.indexOf( 'update-core.php' ) ) { 3241 return; 3242 } 3243 3244 event.preventDefault(); 3245 3246 update = { 3247 action: 'update-plugin', 3248 data: { 3249 plugin: $( this ).data( 'plugin' ), 3250 slug: $( this ).data( 'slug' ) 3251 } 3252 }; 3253 3254 target.postMessage( JSON.stringify( update ), window.location.origin ); 3255 } ); 3256 3257 /** 3258 * Handles postMessage events. 3259 * 3260 * @since 4.2.0 3261 * @since 4.6.0 Switched `update-plugin` action to use the queue. 3262 * 3263 * @param {Event} event Event interface. 3264 */ 3265 $( window ).on( 'message', function( event ) { 3266 var originalEvent = event.originalEvent, 3267 expectedOrigin = document.location.protocol + '//' + document.location.host, 3268 message; 3269 3270 if ( originalEvent.origin !== expectedOrigin ) { 3271 return; 3272 } 3273 3274 try { 3275 message = JSON.parse( originalEvent.data ); 3276 } catch ( e ) { 3277 return; 3278 } 3279 3280 if ( ! message ) { 3281 return; 3282 } 3283 3284 if ( 'undefined' !== typeof message.id && 'plugin-activated-successfully' === message.id ) { 3285 wp.updates.addAdminNotice( message ); 3286 return; 3287 } 3288 3289 if ( 3290 'undefined' !== typeof message.status && 3291 'undefined' !== typeof message.slug && 3292 'undefined' !== typeof message.text && 3293 'undefined' !== typeof message.ariaLabel 3294 ) { 3295 var $card = $( '.plugin-card-' + message.slug ), 3296 $message = $card.find( '[data-slug="' + message.slug + '"]' ); 3297 3298 if ( 'undefined' !== typeof message.removeClasses ) { 3299 $message.removeClass( message.removeClasses ); 3300 } 3301 3302 if ( 'undefined' !== typeof message.addClasses ) { 3303 $message.addClass( message.addClasses ); 3304 } 3305 3306 if ( '' === message.ariaLabel ) { 3307 $message.removeAttr( 'aria-label' ); 3308 } else { 3309 $message.attr( 'aria-label', message.ariaLabel ); 3310 } 3311 3312 if ( 'dependencies-check-success' === message.status ) { 3313 $message 3314 .attr( 'data-name', message.pluginName ) 3315 .attr( 'data-slug', message.slug ) 3316 .attr( 'data-plugin', message.plugin ) 3317 .attr( 'href', message.href ); 3318 } 3319 3320 $message.text( message.text ); 3321 } 3322 3323 if ( 'undefined' === typeof message.action ) { 3324 return; 3325 } 3326 3327 switch ( message.action ) { 3328 3329 // Called from `wp-admin/includes/class-wp-upgrader-skins.php`. 3330 case 'decrementUpdateCount': 3331 /** @property {string} message.upgradeType */ 3332 wp.updates.decrementCount( message.upgradeType ); 3333 break; 3334 3335 case 'install-plugin': 3336 case 'update-plugin': 3337 if ( 'undefined' === typeof message.data || 'undefined' === typeof message.data.slug ) { 3338 return; 3339 } 3340 3341 message.data = wp.updates._addCallbacks( message.data, message.action ); 3342 3343 wp.updates.queue.push( message ); 3344 wp.updates.queueChecker(); 3345 break; 3346 } 3347 } ); 3348 3349 /** 3350 * Adds a callback to display a warning before leaving the page. 3351 * 3352 * @since 4.2.0 3353 */ 3354 $( window ).on( 'beforeunload', wp.updates.beforeunload ); 3355 3356 /** 3357 * Prevents the page form scrolling when activating auto-updates with the Spacebar key. 3358 * 3359 * @since 5.5.0 3360 */ 3361 $document.on( 'keydown', '.column-auto-updates .toggle-auto-update, .theme-overlay .toggle-auto-update', function( event ) { 3362 if ( 32 === event.which ) { 3363 event.preventDefault(); 3364 } 3365 } ); 3366 3367 /** 3368 * Click and keyup handler for enabling and disabling plugin and theme auto-updates. 3369 * 3370 * These controls can be either links or buttons. When JavaScript is enabled, 3371 * we want them to behave like buttons. An ARIA role `button` is added via 3372 * the JavaScript that targets elements with the CSS class `aria-button-if-js`. 3373 * 3374 * @since 5.5.0 3375 */ 3376 $document.on( 'click keyup', '.column-auto-updates .toggle-auto-update, .theme-overlay .toggle-auto-update', function( event ) { 3377 var data, asset, type, $parent, 3378 $toggler = $( this ), 3379 action = $toggler.attr( 'data-wp-action' ), 3380 $label = $toggler.find( '.label' ); 3381 3382 if ( 'keyup' === event.type && 32 !== event.which ) { 3383 return; 3384 } 3385 3386 if ( 'themes' !== pagenow ) { 3387 $parent = $toggler.closest( '.column-auto-updates' ); 3388 } else { 3389 $parent = $toggler.closest( '.theme-autoupdate' ); 3390 } 3391 3392 event.preventDefault(); 3393 3394 // Prevent multiple simultaneous requests. 3395 if ( $toggler.attr( 'data-doing-ajax' ) === 'yes' ) { 3396 return; 3397 } 3398 3399 $toggler.attr( 'data-doing-ajax', 'yes' ); 3400 3401 switch ( pagenow ) { 3402 case 'plugins': 3403 case 'plugins-network': 3404 type = 'plugin'; 3405 asset = $toggler.closest( 'tr' ).attr( 'data-plugin' ); 3406 break; 3407 case 'themes-network': 3408 type = 'theme'; 3409 asset = $toggler.closest( 'tr' ).attr( 'data-slug' ); 3410 break; 3411 case 'themes': 3412 type = 'theme'; 3413 asset = $toggler.attr( 'data-slug' ); 3414 break; 3415 } 3416 3417 // Clear any previous errors. 3418 $parent.find( '.notice.notice-error' ).addClass( 'hidden' ); 3419 3420 // Show loading status. 3421 if ( 'enable' === action ) { 3422 $label.text( __( 'Enabling...' ) ); 3423 } else { 3424 $label.text( __( 'Disabling...' ) ); 3425 } 3426 3427 $toggler.find( '.dashicons-update' ).removeClass( 'hidden' ); 3428 3429 data = { 3430 action: 'toggle-auto-updates', 3431 _ajax_nonce: settings.ajax_nonce, 3432 state: action, 3433 type: type, 3434 asset: asset 3435 }; 3436 3437 $.post( window.ajaxurl, data ) 3438 .done( function( response ) { 3439 var $enabled, $disabled, enabledNumber, disabledNumber, errorMessage, 3440 href = $toggler.attr( 'href' ); 3441 3442 if ( ! response.success ) { 3443 // if WP returns 0 for response (which can happen in a few cases), 3444 // output the general error message since we won't have response.data.error. 3445 if ( response.data && response.data.error ) { 3446 errorMessage = response.data.error; 3447 } else { 3448 errorMessage = __( 'The request could not be completed.' ); 3449 } 3450 3451 $parent.find( '.notice.notice-error' ).removeClass( 'hidden' ).find( 'p' ).text( errorMessage ); 3452 wp.a11y.speak( errorMessage, 'assertive' ); 3453 return; 3454 } 3455 3456 // Update the counts in the enabled/disabled views if on a screen 3457 // with a list table. 3458 if ( 'themes' !== pagenow ) { 3459 $enabled = $( '.auto-update-enabled span' ); 3460 $disabled = $( '.auto-update-disabled span' ); 3461 enabledNumber = parseInt( $enabled.text().replace( /[^\d]+/g, '' ), 10 ) || 0; 3462 disabledNumber = parseInt( $disabled.text().replace( /[^\d]+/g, '' ), 10 ) || 0; 3463 3464 switch ( action ) { 3465 case 'enable': 3466 ++enabledNumber; 3467 --disabledNumber; 3468 break; 3469 case 'disable': 3470 --enabledNumber; 3471 ++disabledNumber; 3472 break; 3473 } 3474 3475 enabledNumber = Math.max( 0, enabledNumber ); 3476 disabledNumber = Math.max( 0, disabledNumber ); 3477 3478 $enabled.text( '(' + enabledNumber + ')' ); 3479 $disabled.text( '(' + disabledNumber + ')' ); 3480 } 3481 3482 if ( 'enable' === action ) { 3483 // The toggler control can be either a link or a button. 3484 if ( $toggler[ 0 ].hasAttribute( 'href' ) ) { 3485 href = href.replace( 'action=enable-auto-update', 'action=disable-auto-update' ); 3486 $toggler.attr( 'href', href ); 3487 } 3488 $toggler.attr( 'data-wp-action', 'disable' ); 3489 3490 $label.text( __( 'Disable auto-updates' ) ); 3491 $parent.find( '.auto-update-time' ).removeClass( 'hidden' ); 3492 wp.a11y.speak( __( 'Auto-updates enabled' ) ); 3493 } else { 3494 // The toggler control can be either a link or a button. 3495 if ( $toggler[ 0 ].hasAttribute( 'href' ) ) { 3496 href = href.replace( 'action=disable-auto-update', 'action=enable-auto-update' ); 3497 $toggler.attr( 'href', href ); 3498 } 3499 $toggler.attr( 'data-wp-action', 'enable' ); 3500 3501 $label.text( __( 'Enable auto-updates' ) ); 3502 $parent.find( '.auto-update-time' ).addClass( 'hidden' ); 3503 wp.a11y.speak( __( 'Auto-updates disabled' ) ); 3504 } 3505 3506 $document.trigger( 'wp-auto-update-setting-changed', { state: action, type: type, asset: asset } ); 3507 } ) 3508 .fail( function() { 3509 $parent.find( '.notice.notice-error' ) 3510 .removeClass( 'hidden' ) 3511 .find( 'p' ) 3512 .text( __( 'The request could not be completed.' ) ); 3513 3514 wp.a11y.speak( __( 'The request could not be completed.' ), 'assertive' ); 3515 } ) 3516 .always( function() { 3517 $toggler.removeAttr( 'data-doing-ajax' ).find( '.dashicons-update' ).addClass( 'hidden' ); 3518 } ); 3519 } 3520 ); 3521 3522 /** 3523 * Click handler for page refresh link. 3524 * 3525 * @since 6.5.3 3526 * 3527 * @param {Event} event Event interface. 3528 */ 3529 $document.on( 'click', '.refresh-page', function( event ) { 3530 event.preventDefault(); 3531 3532 if ( window.parent === window ) { 3533 window.location.reload(); 3534 } else { 3535 window.parent.location.reload(); 3536 } 3537 } ); 3538 } ); 3539 })( jQuery, window.wp, window._wpUpdatesSettings );
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated : Fri May 10 08:20:01 2024 | Cross-referenced by PHPXref |