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