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