[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

Search

title

Body

[close]

/wp-admin/js/ -> updates.js (source)

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


Generated : Sun Oct 4 08:20:33 2026 Cross-referenced by PHPXref