[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

Search

title

Body

[close]

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

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


Generated : Mon Jul 15 08:20:02 2024 Cross-referenced by PHPXref