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