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