[ 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"><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              isInModal = 'plugin-information-footer' === $message.parent().attr( 'id' ),
1116              buttonText = _x( 'Activated!', 'plugin' ),
1117              ariaLabel = sprintf(
1118                  /* translators: %s: The plugin name. */
1119                  '%s activated successfully.',
1120                  response.pluginName
1121              ),
1122              noticeData = {
1123                  id: 'plugin-activated-successfully',
1124                  className: 'notice-success',
1125                  message: sprintf(
1126                      /* translators: %s: The refresh link's attributes. */
1127                      __( 'Plugin activated. Some changes may not occur until you refresh the page. <a %s>Refresh Now</a>' ),
1128                      'href="#" class="button button-secondary refresh-page"'
1129                  ),
1130                  slug: response.slug
1131              },
1132              noticeTarget;
1133  
1134          wp.a11y.speak( __( 'Activation completed successfully. Some changes may not occur until you refresh the page.' ) );
1135          $document.trigger( 'wp-plugin-activate-success', response );
1136  
1137          $message
1138              .removeClass( 'activating-message' )
1139              .addClass( 'activated-message button-disabled' )
1140              .attr( 'aria-label', ariaLabel )
1141              .text( buttonText );
1142  
1143          if ( isInModal ) {
1144              wp.updates.setCardButtonStatus(
1145                  {
1146                      status: 'activated-plugin',
1147                      slug: response.slug,
1148                      removeClasses: 'activating-message',
1149                      addClasses: 'activated-message button-disabled',
1150                      text: buttonText,
1151                      ariaLabel: ariaLabel
1152                  }
1153              );
1154  
1155              // Add a notice to the modal's footer.
1156              $message.replaceWith( wp.updates.adminNotice( noticeData ) );
1157  
1158              // Send notice information back to the parent screen.
1159              noticeTarget = window.parent === window ? null : window.parent;
1160              $.support.postMessage = !! window.postMessage;
1161              if ( false !== $.support.postMessage && null !== noticeTarget && -1 === window.parent.location.pathname.indexOf( 'index.php' ) ) {
1162                  noticeTarget.postMessage(
1163                      JSON.stringify( noticeData ),
1164                      window.location.origin
1165                  );
1166              }
1167          } else {
1168              // Add a notice to the top of the screen.
1169              wp.updates.addAdminNotice( noticeData );
1170          }
1171  
1172          setTimeout( function() {
1173              if ( isInModal ) {
1174                  wp.updates.setCardButtonStatus(
1175                      {
1176                          status: 'plugin-active',
1177                          slug: response.slug,
1178                          removeClasses: 'activated-message',
1179                          text: _x( 'Active', 'plugin' ),
1180                          ariaLabel: sprintf(
1181                              /* translators: %s: The plugin name. */
1182                              '%s is active.',
1183                              response.pluginName
1184                          )
1185                      }
1186                  );
1187              } else {
1188                  $message.removeClass( 'activated-message' ).text( _x( 'Active', 'plugin' ) );
1189              }
1190          }, 1000 );
1191      };
1192  
1193      /**
1194       * Updates the UI appropriately after a failed plugin activation.
1195       *
1196       * @since 6.5.0
1197       *
1198       * @param {Object}  response              Response from the server.
1199       * @param {string}  response.slug         Slug of the plugin to be activated.
1200       * @param {string=} response.pluginName   Optional. Name of the plugin to be activated.
1201       * @param {string}  response.errorCode    Error code for the error that occurred.
1202       * @param {string}  response.errorMessage The error that occurred.
1203       */
1204      wp.updates.activatePluginError = function( response ) {
1205          var $message = $( '.plugin-card-' + response.slug + ', #plugin-information-footer' ).find( '.activating-message' ),
1206              buttonText = __( 'Activation failed.' ),
1207              ariaLabel = sprintf(
1208                  /* translators: %s: Plugin name. */
1209                  _x( '%s activation failed', 'plugin' ),
1210                  response.pluginName
1211              ),
1212              errorMessage;
1213  
1214          if ( ! wp.updates.isValidResponse( response, 'activate' ) ) {
1215              return;
1216          }
1217  
1218          errorMessage = sprintf(
1219              /* translators: %s: Error string for a failed activation. */
1220              __( 'Activation failed: %s' ),
1221              response.errorMessage
1222          );
1223  
1224          wp.a11y.speak( errorMessage, 'assertive' );
1225          $document.trigger( 'wp-plugin-activate-error', response );
1226  
1227          $message
1228              .removeClass( 'install-now installed activating-message' )
1229              .addClass( 'button-disabled' )
1230              .attr( 'aria-label', ariaLabel )
1231              .text( buttonText );
1232  
1233          if ( 'plugin-information-footer' === $message.parent().attr( 'id' ) ) {
1234              wp.updates.setCardButtonStatus(
1235                  {
1236                      status: 'plugin-activation-failed',
1237                      slug: response.slug,
1238                      removeClasses: 'install-now installed activating-message',
1239                      addClasses: 'button-disabled',
1240                      text: buttonText,
1241                      ariaLabel: ariaLabel
1242                  }
1243              );
1244          }
1245      };
1246  
1247      /**
1248       * Updates the UI appropriately after a successful importer install.
1249       *
1250       * @since 4.6.0
1251       *
1252       * @param {Object} response             Response from the server.
1253       * @param {string} response.slug        Slug of the installed plugin.
1254       * @param {string} response.pluginName  Name of the installed plugin.
1255       * @param {string} response.activateUrl URL to activate the just installed plugin.
1256       */
1257      wp.updates.installImporterSuccess = function( response ) {
1258          wp.updates.addAdminNotice( {
1259              id:        'install-success',
1260              className: 'notice-success is-dismissible',
1261              message:   sprintf(
1262                  /* translators: %s: Activation URL. */
1263                  __( 'Importer installed successfully. <a href="%s">Run importer</a>' ),
1264                  response.activateUrl + '&from=import'
1265              )
1266          } );
1267  
1268          $( '[data-slug="' + response.slug + '"]' )
1269              .removeClass( 'install-now updating-message' )
1270              .addClass( 'activate-now' )
1271              .attr({
1272                  'href': response.activateUrl + '&from=import',
1273                  'aria-label':sprintf(
1274                      /* translators: %s: Importer name. */
1275                      __( 'Run %s' ),
1276                      response.pluginName
1277                  )
1278              })
1279              .text( __( 'Run Importer' ) );
1280  
1281          wp.a11y.speak( __( 'Installation completed successfully.' ) );
1282  
1283          $document.trigger( 'wp-importer-install-success', response );
1284      };
1285  
1286      /**
1287       * Updates the UI appropriately after a failed importer install.
1288       *
1289       * @since 4.6.0
1290       *
1291       * @param {Object}  response              Response from the server.
1292       * @param {string}  response.slug         Slug of the plugin to be installed.
1293       * @param {string=} response.pluginName   Optional. Name of the plugin to be installed.
1294       * @param {string}  response.errorCode    Error code for the error that occurred.
1295       * @param {string}  response.errorMessage The error that occurred.
1296       */
1297      wp.updates.installImporterError = function( response ) {
1298          var errorMessage = sprintf(
1299                  /* translators: %s: Error string for a failed installation. */
1300                  __( 'Installation failed: %s' ),
1301                  response.errorMessage
1302              ),
1303              $installLink = $( '[data-slug="' + response.slug + '"]' ),
1304              pluginName = $installLink.data( 'name' );
1305  
1306          if ( ! wp.updates.isValidResponse( response, 'install' ) ) {
1307              return;
1308          }
1309  
1310          if ( wp.updates.maybeHandleCredentialError( response, 'install-plugin' ) ) {
1311              return;
1312          }
1313  
1314          wp.updates.addAdminNotice( {
1315              id:        response.errorCode,
1316              className: 'notice-error is-dismissible',
1317              message:   errorMessage
1318          } );
1319  
1320          $installLink
1321              .removeClass( 'updating-message' )
1322              .attr(
1323                  'aria-label',
1324                  sprintf(
1325                      /* translators: %s: Plugin name. */
1326                      _x( 'Install %s now', 'plugin' ),
1327                      pluginName
1328                  )
1329              )
1330              .text( _x( 'Install Now', 'plugin' ) );
1331  
1332          wp.a11y.speak( errorMessage, 'assertive' );
1333  
1334          $document.trigger( 'wp-importer-install-error', response );
1335      };
1336  
1337      /**
1338       * Sends an Ajax request to the server to delete a plugin.
1339       *
1340       * @since 4.6.0
1341       *
1342       * @param {Object}               args         Arguments.
1343       * @param {string}               args.plugin  Basename of the plugin to be deleted.
1344       * @param {string}               args.slug    Slug of the plugin to be deleted.
1345       * @param {deletePluginSuccess=} args.success Optional. Success callback. Default: wp.updates.deletePluginSuccess
1346       * @param {deletePluginError=}   args.error   Optional. Error callback. Default: wp.updates.deletePluginError
1347       * @return {$.promise} A jQuery promise that represents the request,
1348       *                     decorated with an abort() method.
1349       */
1350      wp.updates.deletePlugin = function( args ) {
1351          var $link = $( '[data-plugin="' + args.plugin + '"]' ).find( '.row-actions a.delete' );
1352  
1353          args = _.extend( {
1354              success: wp.updates.deletePluginSuccess,
1355              error: wp.updates.deletePluginError
1356          }, args );
1357  
1358          if ( $link.html() !== __( 'Deleting...' ) ) {
1359              $link
1360                  .data( 'originaltext', $link.html() )
1361                  .text( __( 'Deleting...' ) );
1362          }
1363  
1364          wp.a11y.speak( __( 'Deleting...' ) );
1365  
1366          $document.trigger( 'wp-plugin-deleting', args );
1367  
1368          return wp.updates.ajax( 'delete-plugin', args );
1369      };
1370  
1371      /**
1372       * Updates the UI appropriately after a successful plugin deletion.
1373       *
1374       * @since 4.6.0
1375       *
1376       * @param {Object} response            Response from the server.
1377       * @param {string} response.slug       Slug of the plugin that was deleted.
1378       * @param {string} response.plugin     Base name of the plugin that was deleted.
1379       * @param {string} response.pluginName Name of the plugin that was deleted.
1380       */
1381      wp.updates.deletePluginSuccess = function( response ) {
1382  
1383          // Removes the plugin and updates rows.
1384          $( '[data-plugin="' + response.plugin + '"]' ).css( { backgroundColor: '#faafaa' } ).fadeOut( 350, function() {
1385              var $form            = $( '#bulk-action-form' ),
1386                  $views           = $( '.subsubsub' ),
1387                  $pluginRow       = $( this ),
1388                  $currentView     = $views.find( '[aria-current="page"]' ),
1389                  $itemsCount      = $( '.displaying-num' ),
1390                  columnCount      = $form.find( 'thead th:not(.hidden), thead td' ).length,
1391                  pluginDeletedRow = wp.template( 'item-deleted-row' ),
1392                  /**
1393                   * Plugins Base names of plugins in their different states.
1394                   *
1395                   * @type {Object}
1396                   */
1397                  plugins          = settings.plugins,
1398                  remainingCount;
1399  
1400              // Add a success message after deleting a plugin.
1401              if ( ! $pluginRow.hasClass( 'plugin-update-tr' ) ) {
1402                  $pluginRow.after(
1403                      pluginDeletedRow( {
1404                          slug:    response.slug,
1405                          plugin:  response.plugin,
1406                          colspan: columnCount,
1407                          name:    response.pluginName
1408                      } )
1409                  );
1410              }
1411  
1412              $pluginRow.remove();
1413  
1414              // Remove plugin from update count.
1415              if ( -1 !== _.indexOf( plugins.upgrade, response.plugin ) ) {
1416                  plugins.upgrade = _.without( plugins.upgrade, response.plugin );
1417                  wp.updates.decrementCount( 'plugin' );
1418              }
1419  
1420              // Remove from views.
1421              if ( -1 !== _.indexOf( plugins.inactive, response.plugin ) ) {
1422                  plugins.inactive = _.without( plugins.inactive, response.plugin );
1423                  if ( plugins.inactive.length ) {
1424                      $views.find( '.inactive .count' ).text( '(' + plugins.inactive.length + ')' );
1425                  } else {
1426                      $views.find( '.inactive' ).remove();
1427                  }
1428              }
1429  
1430              if ( -1 !== _.indexOf( plugins.active, response.plugin ) ) {
1431                  plugins.active = _.without( plugins.active, response.plugin );
1432                  if ( plugins.active.length ) {
1433                      $views.find( '.active .count' ).text( '(' + plugins.active.length + ')' );
1434                  } else {
1435                      $views.find( '.active' ).remove();
1436                  }
1437              }
1438  
1439              if ( -1 !== _.indexOf( plugins.recently_activated, response.plugin ) ) {
1440                  plugins.recently_activated = _.without( plugins.recently_activated, response.plugin );
1441                  if ( plugins.recently_activated.length ) {
1442                      $views.find( '.recently_activated .count' ).text( '(' + plugins.recently_activated.length + ')' );
1443                  } else {
1444                      $views.find( '.recently_activated' ).remove();
1445                  }
1446              }
1447  
1448              if ( -1 !== _.indexOf( plugins['auto-update-enabled'], response.plugin ) ) {
1449                  plugins['auto-update-enabled'] = _.without( plugins['auto-update-enabled'], response.plugin );
1450                  if ( plugins['auto-update-enabled'].length ) {
1451                      $views.find( '.auto-update-enabled .count' ).text( '(' + plugins['auto-update-enabled'].length + ')' );
1452                  } else {
1453                      $views.find( '.auto-update-enabled' ).remove();
1454                  }
1455              }
1456  
1457              if ( -1 !== _.indexOf( plugins['auto-update-disabled'], response.plugin ) ) {
1458                  plugins['auto-update-disabled'] = _.without( plugins['auto-update-disabled'], response.plugin );
1459                  if ( plugins['auto-update-disabled'].length ) {
1460                      $views.find( '.auto-update-disabled .count' ).text( '(' + plugins['auto-update-disabled'].length + ')' );
1461                  } else {
1462                      $views.find( '.auto-update-disabled' ).remove();
1463                  }
1464              }
1465  
1466              plugins.all = _.without( plugins.all, response.plugin );
1467  
1468              if ( plugins.all.length ) {
1469                  $views.find( '.all .count' ).text( '(' + plugins.all.length + ')' );
1470              } else {
1471                  $form.find( '.tablenav' ).css( { visibility: 'hidden' } );
1472                  $views.find( '.all' ).remove();
1473  
1474                  if ( ! $form.find( 'tr.no-items' ).length ) {
1475                      $form.find( '#the-list' ).append( '<tr class="no-items"><td class="colspanchange" colspan="' + columnCount + '">' + __( 'No plugins are currently available.' ) + '</td></tr>' );
1476                  }
1477              }
1478  
1479              if ( $itemsCount.length && $currentView.length ) {
1480                  remainingCount = plugins[ $currentView.parent( 'li' ).attr('class') ].length;
1481                  $itemsCount.text(
1482                      sprintf(
1483                          /* translators: %s: The remaining number of plugins. */
1484                          _nx( '%s item', '%s items', remainingCount, 'plugin/plugins'  ),
1485                          remainingCount
1486                      )
1487                  );
1488              }
1489          } );
1490  
1491          wp.a11y.speak( _x( 'Deleted!', 'plugin' ) );
1492  
1493          $document.trigger( 'wp-plugin-delete-success', response );
1494      };
1495  
1496      /**
1497       * Updates the UI appropriately after a failed plugin deletion.
1498       *
1499       * @since 4.6.0
1500       *
1501       * @param {Object}  response              Response from the server.
1502       * @param {string}  response.slug         Slug of the plugin to be deleted.
1503       * @param {string}  response.plugin       Base name of the plugin to be deleted
1504       * @param {string=} response.pluginName   Optional. Name of the plugin to be deleted.
1505       * @param {string}  response.errorCode    Error code for the error that occurred.
1506       * @param {string}  response.errorMessage The error that occurred.
1507       */
1508      wp.updates.deletePluginError = function( response ) {
1509          var $plugin, $pluginUpdateRow,
1510              pluginUpdateRow  = wp.template( 'item-update-row' ),
1511              noticeContent    = wp.updates.adminNotice( {
1512                  className: 'update-message notice-error notice-alt',
1513                  message:   response.errorMessage
1514              } );
1515  
1516          if ( response.plugin ) {
1517              $plugin          = $( 'tr.inactive[data-plugin="' + response.plugin + '"]' );
1518              $pluginUpdateRow = $plugin.siblings( '[data-plugin="' + response.plugin + '"]' );
1519          } else {
1520              $plugin          = $( 'tr.inactive[data-slug="' + response.slug + '"]' );
1521              $pluginUpdateRow = $plugin.siblings( '[data-slug="' + response.slug + '"]' );
1522          }
1523  
1524          if ( ! wp.updates.isValidResponse( response, 'delete' ) ) {
1525              return;
1526          }
1527  
1528          if ( wp.updates.maybeHandleCredentialError( response, 'delete-plugin' ) ) {
1529              return;
1530          }
1531  
1532          // Add a plugin update row if it doesn't exist yet.
1533          if ( ! $pluginUpdateRow.length ) {
1534              $plugin.addClass( 'update' ).after(
1535                  pluginUpdateRow( {
1536                      slug:    response.slug,
1537                      plugin:  response.plugin || response.slug,
1538                      colspan: $( '#bulk-action-form' ).find( 'thead th:not(.hidden), thead td' ).length,
1539                      content: noticeContent
1540                  } )
1541              );
1542          } else {
1543  
1544              // Remove previous error messages, if any.
1545              $pluginUpdateRow.find( '.notice-error' ).remove();
1546  
1547              $pluginUpdateRow.find( '.plugin-update' ).append( noticeContent );
1548          }
1549  
1550          $document.trigger( 'wp-plugin-delete-error', response );
1551      };
1552  
1553      /**
1554       * Sends an Ajax request to the server to update a theme.
1555       *
1556       * @since 4.6.0
1557       *
1558       * @param {Object}              args         Arguments.
1559       * @param {string}              args.slug    Theme stylesheet.
1560       * @param {updateThemeSuccess=} args.success Optional. Success callback. Default: wp.updates.updateThemeSuccess
1561       * @param {updateThemeError=}   args.error   Optional. Error callback. Default: wp.updates.updateThemeError
1562       * @return {$.promise} A jQuery promise that represents the request,
1563       *                     decorated with an abort() method.
1564       */
1565      wp.updates.updateTheme = function( args ) {
1566          var $notice;
1567  
1568          args = _.extend( {
1569              success: wp.updates.updateThemeSuccess,
1570              error: wp.updates.updateThemeError
1571          }, args );
1572  
1573          if ( 'themes-network' === pagenow ) {
1574              $notice = $( '[data-slug="' + args.slug + '"]' ).find( '.update-message' ).removeClass( 'notice-error' ).addClass( 'updating-message notice-warning' ).find( 'p' );
1575  
1576          } else if ( 'customize' === pagenow ) {
1577  
1578              // Update the theme details UI.
1579              $notice = $( '[data-slug="' + args.slug + '"].notice' ).removeClass( 'notice-large' );
1580  
1581              $notice.find( 'h3' ).remove();
1582  
1583              // Add the top-level UI, and update both.
1584              $notice = $notice.add( $( '#customize-control-installed_theme_' + args.slug ).find( '.update-message' ) );
1585              $notice = $notice.addClass( 'updating-message' ).find( 'p' );
1586  
1587          } else {
1588              $notice = $( '#update-theme' ).closest( '.notice' ).removeClass( 'notice-large' );
1589  
1590              $notice.find( 'h3' ).remove();
1591  
1592              $notice = $notice.add( $( '[data-slug="' + args.slug + '"]' ).find( '.update-message' ) );
1593              $notice = $notice.addClass( 'updating-message' ).find( 'p' );
1594          }
1595  
1596          if ( $notice.html() !== __( 'Updating...' ) ) {
1597              $notice.data( 'originaltext', $notice.html() );
1598          }
1599  
1600          wp.a11y.speak( __( 'Updating... please wait.' ) );
1601          $notice.text( __( 'Updating...' ) );
1602  
1603          $document.trigger( 'wp-theme-updating', args );
1604  
1605          return wp.updates.ajax( 'update-theme', args );
1606      };
1607  
1608      /**
1609       * Updates the UI appropriately after a successful theme update.
1610       *
1611       * @since 4.6.0
1612       * @since 5.5.0 Auto-update "time to next update" text cleared.
1613       *
1614       * @param {Object} response
1615       * @param {string} response.slug       Slug of the theme to be updated.
1616       * @param {Object} response.theme      Updated theme.
1617       * @param {string} response.oldVersion Old version of the theme.
1618       * @param {string} response.newVersion New version of the theme.
1619       */
1620      wp.updates.updateThemeSuccess = function( response ) {
1621          var isModalOpen    = $( 'body.modal-open' ).length,
1622              $theme         = $( '[data-slug="' + response.slug + '"]' ),
1623              updatedMessage = {
1624                  className: 'updated-message notice-success notice-alt',
1625                  message:   _x( 'Updated!', 'theme' )
1626              },
1627              $notice, newText;
1628  
1629          if ( 'customize' === pagenow ) {
1630              $theme = $( '.updating-message' ).siblings( '.theme-name' );
1631  
1632              if ( $theme.length ) {
1633  
1634                  // Update the version number in the row.
1635                  newText = $theme.html().replace( response.oldVersion, response.newVersion );
1636                  $theme.html( newText );
1637              }
1638  
1639              $notice = $( '.theme-info .notice' ).add( wp.customize.control( 'installed_theme_' + response.slug ).container.find( '.theme' ).find( '.update-message' ) );
1640          } else if ( 'themes-network' === pagenow ) {
1641              $notice = $theme.find( '.update-message' );
1642  
1643              // Update the version number in the row.
1644              newText = $theme.find( '.theme-version-author-uri' ).html().replace( response.oldVersion, response.newVersion );
1645              $theme.find( '.theme-version-author-uri' ).html( newText );
1646  
1647              // Clear the "time to next auto-update" text.
1648              $theme.find( '.auto-update-time' ).empty();
1649          } else {
1650              $notice = $( '.theme-info .notice' ).add( $theme.find( '.update-message' ) );
1651  
1652              // Focus on Customize button after updating.
1653              if ( isModalOpen ) {
1654                  $( '.load-customize:visible' ).trigger( 'focus' );
1655                  $( '.theme-info .theme-autoupdate' ).find( '.auto-update-time' ).empty();
1656              } else {
1657                  $theme.find( '.load-customize' ).trigger( 'focus' );
1658              }
1659          }
1660  
1661          wp.updates.addAdminNotice( _.extend( { selector: $notice }, updatedMessage ) );
1662          wp.a11y.speak( __( 'Update completed successfully.' ) );
1663  
1664          wp.updates.decrementCount( 'theme' );
1665  
1666          $document.trigger( 'wp-theme-update-success', response );
1667  
1668          // Show updated message after modal re-rendered.
1669          if ( isModalOpen && 'customize' !== pagenow ) {
1670              $( '.theme-info .theme-author' ).after( wp.updates.adminNotice( updatedMessage ) );
1671          }
1672      };
1673  
1674      /**
1675       * Updates the UI appropriately after a failed theme update.
1676       *
1677       * @since 4.6.0
1678       *
1679       * @param {Object} response              Response from the server.
1680       * @param {string} response.slug         Slug of the theme to be updated.
1681       * @param {string} response.errorCode    Error code for the error that occurred.
1682       * @param {string} response.errorMessage The error that occurred.
1683       */
1684      wp.updates.updateThemeError = function( response ) {
1685          var $theme       = $( '[data-slug="' + response.slug + '"]' ),
1686              errorMessage = sprintf(
1687                  /* translators: %s: Error string for a failed update. */
1688                   __( 'Update failed: %s' ),
1689                  response.errorMessage
1690              ),
1691              $notice;
1692  
1693          if ( ! wp.updates.isValidResponse( response, 'update' ) ) {
1694              return;
1695          }
1696  
1697          if ( wp.updates.maybeHandleCredentialError( response, 'update-theme' ) ) {
1698              return;
1699          }
1700  
1701          if ( 'customize' === pagenow ) {
1702              $theme = wp.customize.control( 'installed_theme_' + response.slug ).container.find( '.theme' );
1703          }
1704  
1705          if ( 'themes-network' === pagenow ) {
1706              $notice = $theme.find( '.update-message ' );
1707          } else {
1708              $notice = $( '.theme-info .notice' ).add( $theme.find( '.notice' ) );
1709  
1710              $( 'body.modal-open' ).length ? $( '.load-customize:visible' ).trigger( 'focus' ) : $theme.find( '.load-customize' ).trigger( 'focus');
1711          }
1712  
1713          wp.updates.addAdminNotice( {
1714              selector:  $notice,
1715              className: 'update-message notice-error notice-alt is-dismissible',
1716              message:   errorMessage
1717          } );
1718  
1719          wp.a11y.speak( errorMessage );
1720  
1721          $document.trigger( 'wp-theme-update-error', response );
1722      };
1723  
1724      /**
1725       * Sends an Ajax request to the server to install a theme.
1726       *
1727       * @since 4.6.0
1728       *
1729       * @param {Object}               args
1730       * @param {string}               args.slug    Theme stylesheet.
1731       * @param {installThemeSuccess=} args.success Optional. Success callback. Default: wp.updates.installThemeSuccess
1732       * @param {installThemeError=}   args.error   Optional. Error callback. Default: wp.updates.installThemeError
1733       * @return {$.promise} A jQuery promise that represents the request,
1734       *                     decorated with an abort() method.
1735       */
1736      wp.updates.installTheme = function( args ) {
1737          var $message = $( '.theme-install[data-slug="' + args.slug + '"]' );
1738  
1739          args = _.extend( {
1740              success: wp.updates.installThemeSuccess,
1741              error: wp.updates.installThemeError
1742          }, args );
1743  
1744          $message.addClass( 'updating-message' );
1745          $message.parents( '.theme' ).addClass( 'focus' );
1746          if ( $message.html() !== __( 'Installing...' ) ) {
1747              $message.data( 'originaltext', $message.html() );
1748          }
1749  
1750          $message
1751              .attr(
1752                  'aria-label',
1753                  sprintf(
1754                      /* translators: %s: Theme name and version. */
1755                      _x( 'Installing %s...', 'theme' ),
1756                      $message.data( 'name' )
1757                  )
1758              )
1759              .text( __( 'Installing...' ) );
1760  
1761          wp.a11y.speak( __( 'Installing... please wait.' ) );
1762  
1763          // Remove previous error messages, if any.
1764          $( '.install-theme-info, [data-slug="' + args.slug + '"]' ).removeClass( 'theme-install-failed' ).find( '.notice.notice-error' ).remove();
1765  
1766          $document.trigger( 'wp-theme-installing', args );
1767  
1768          return wp.updates.ajax( 'install-theme', args );
1769      };
1770  
1771      /**
1772       * Updates the UI appropriately after a successful theme install.
1773       *
1774       * @since 4.6.0
1775       *
1776       * @param {Object} response              Response from the server.
1777       * @param {string} response.slug         Slug of the theme to be installed.
1778       * @param {string} response.customizeUrl URL to the Customizer for the just installed theme.
1779       * @param {string} response.activateUrl  URL to activate the just installed theme.
1780       */
1781      wp.updates.installThemeSuccess = function( response ) {
1782          var $card = $( '.wp-full-overlay-header, [data-slug=' + response.slug + ']' ),
1783              $message;
1784  
1785          $document.trigger( 'wp-theme-install-success', response );
1786  
1787          $message = $card.find( '.button-primary' )
1788              .removeClass( 'updating-message' )
1789              .addClass( 'updated-message disabled' )
1790              .attr(
1791                  'aria-label',
1792                  sprintf(
1793                      /* translators: %s: Theme name and version. */
1794                      _x( '%s installed!', 'theme' ),
1795                      response.themeName
1796                  )
1797              )
1798              .text( _x( 'Installed!', 'theme' ) );
1799  
1800          wp.a11y.speak( __( 'Installation completed successfully.' ) );
1801  
1802          setTimeout( function() {
1803  
1804              if ( response.activateUrl ) {
1805  
1806                  // Transform the 'Install' button into an 'Activate' button.
1807                  $message
1808                      .attr( 'href', response.activateUrl )
1809                      .removeClass( 'theme-install updated-message disabled' )
1810                      .addClass( 'activate' );
1811  
1812                  if ( 'themes-network' === pagenow ) {
1813                      $message
1814                          .attr(
1815                              'aria-label',
1816                              sprintf(
1817                                  /* translators: %s: Theme name. */
1818                                  _x( 'Network Activate %s', 'theme' ),
1819                                  response.themeName
1820                              )
1821                          )
1822                          .text( __( 'Network Enable' ) );
1823                  } else {
1824                      $message
1825                          .attr(
1826                              'aria-label',
1827                              sprintf(
1828                                  /* translators: %s: Theme name. */
1829                                  _x( 'Activate %s', 'theme' ),
1830                                  response.themeName
1831                              )
1832                          )
1833                          .text( _x( 'Activate', 'theme' ) );
1834                  }
1835              }
1836  
1837              if ( response.customizeUrl ) {
1838  
1839                  // Transform the 'Preview' button into a 'Live Preview' button.
1840                  $message.siblings( '.preview' ).replaceWith( function () {
1841                      return $( '<a>' )
1842                          .attr( 'href', response.customizeUrl )
1843                          .addClass( 'button load-customize' )
1844                          .text( __( 'Live Preview' ) );
1845                  } );
1846              }
1847          }, 1000 );
1848      };
1849  
1850      /**
1851       * Updates the UI appropriately after a failed theme install.
1852       *
1853       * @since 4.6.0
1854       *
1855       * @param {Object} response              Response from the server.
1856       * @param {string} response.slug         Slug of the theme to be installed.
1857       * @param {string} response.errorCode    Error code for the error that occurred.
1858       * @param {string} response.errorMessage The error that occurred.
1859       */
1860      wp.updates.installThemeError = function( response ) {
1861          var $card, $button,
1862              errorMessage = sprintf(
1863                  /* translators: %s: Error string for a failed installation. */
1864                  __( 'Installation failed: %s' ),
1865                  response.errorMessage
1866              ),
1867              $message     = wp.updates.adminNotice( {
1868                  className: 'update-message notice-error notice-alt',
1869                  message:   errorMessage
1870              } );
1871  
1872          if ( ! wp.updates.isValidResponse( response, 'install' ) ) {
1873              return;
1874          }
1875  
1876          if ( wp.updates.maybeHandleCredentialError( response, 'install-theme' ) ) {
1877              return;
1878          }
1879  
1880          if ( 'customize' === pagenow ) {
1881              if ( $document.find( 'body' ).hasClass( 'modal-open' ) ) {
1882                  $button = $( '.theme-install[data-slug="' + response.slug + '"]' );
1883                  $card   = $( '.theme-overlay .theme-info' ).prepend( $message );
1884              } else {
1885                  $button = $( '.theme-install[data-slug="' + response.slug + '"]' );
1886                  $card   = $button.closest( '.theme' ).addClass( 'theme-install-failed' ).append( $message );
1887              }
1888              wp.customize.notifications.remove( 'theme_installing' );
1889          } else {
1890              if ( $document.find( 'body' ).hasClass( 'full-overlay-active' ) ) {
1891                  $button = $( '.theme-install[data-slug="' + response.slug + '"]' );
1892                  $card   = $( '.install-theme-info' ).prepend( $message );
1893              } else {
1894                  $card   = $( '[data-slug="' + response.slug + '"]' ).removeClass( 'focus' ).addClass( 'theme-install-failed' ).append( $message );
1895                  $button = $card.find( '.theme-install' );
1896              }
1897          }
1898  
1899          $button
1900              .removeClass( 'updating-message' )
1901              .attr(
1902                  'aria-label',
1903                  sprintf(
1904                      /* translators: %s: Theme name and version. */
1905                      _x( '%s installation failed', 'theme' ),
1906                      $button.data( 'name' )
1907                  )
1908              )
1909              .text( __( 'Installation failed.' ) );
1910  
1911          wp.a11y.speak( errorMessage, 'assertive' );
1912  
1913          $document.trigger( 'wp-theme-install-error', response );
1914      };
1915  
1916      /**
1917       * Sends an Ajax request to the server to delete a theme.
1918       *
1919       * @since 4.6.0
1920       *
1921       * @param {Object}              args
1922       * @param {string}              args.slug    Theme stylesheet.
1923       * @param {deleteThemeSuccess=} args.success Optional. Success callback. Default: wp.updates.deleteThemeSuccess
1924       * @param {deleteThemeError=}   args.error   Optional. Error callback. Default: wp.updates.deleteThemeError
1925       * @return {$.promise} A jQuery promise that represents the request,
1926       *                     decorated with an abort() method.
1927       */
1928      wp.updates.deleteTheme = function( args ) {
1929          var $button;
1930  
1931          if ( 'themes' === pagenow ) {
1932              $button = $( '.theme-actions .delete-theme' );
1933          } else if ( 'themes-network' === pagenow ) {
1934              $button = $( '[data-slug="' + args.slug + '"]' ).find( '.row-actions a.delete' );
1935          }
1936  
1937          args = _.extend( {
1938              success: wp.updates.deleteThemeSuccess,
1939              error: wp.updates.deleteThemeError
1940          }, args );
1941  
1942          if ( $button && $button.html() !== __( 'Deleting...' ) ) {
1943              $button
1944                  .data( 'originaltext', $button.html() )
1945                  .text( __( 'Deleting...' ) );
1946          }
1947  
1948          wp.a11y.speak( __( 'Deleting...' ) );
1949  
1950          // Remove previous error messages, if any.
1951          $( '.theme-info .update-message' ).remove();
1952  
1953          $document.trigger( 'wp-theme-deleting', args );
1954  
1955          return wp.updates.ajax( 'delete-theme', args );
1956      };
1957  
1958      /**
1959       * Updates the UI appropriately after a successful theme deletion.
1960       *
1961       * @since 4.6.0
1962       *
1963       * @param {Object} response      Response from the server.
1964       * @param {string} response.slug Slug of the theme that was deleted.
1965       */
1966      wp.updates.deleteThemeSuccess = function( response ) {
1967          var $themeRows = $( '[data-slug="' + response.slug + '"]' );
1968  
1969          if ( 'themes-network' === pagenow ) {
1970  
1971              // Removes the theme and updates rows.
1972              $themeRows.css( { backgroundColor: '#faafaa' } ).fadeOut( 350, function() {
1973                  var $views     = $( '.subsubsub' ),
1974                      $themeRow  = $( this ),
1975                      themes     = settings.themes,
1976                      deletedRow = wp.template( 'item-deleted-row' );
1977  
1978                  if ( ! $themeRow.hasClass( 'plugin-update-tr' ) ) {
1979                      $themeRow.after(
1980                          deletedRow( {
1981                              slug:    response.slug,
1982                              colspan: $( '#bulk-action-form' ).find( 'thead th:not(.hidden), thead td' ).length,
1983                              name:    $themeRow.find( '.theme-title strong' ).text()
1984                          } )
1985                      );
1986                  }
1987  
1988                  $themeRow.remove();
1989  
1990                  // Remove theme from update count.
1991                  if ( -1 !== _.indexOf( themes.upgrade, response.slug ) ) {
1992                      themes.upgrade = _.without( themes.upgrade, response.slug );
1993                      wp.updates.decrementCount( 'theme' );
1994                  }
1995  
1996                  // Remove from views.
1997                  if ( -1 !== _.indexOf( themes.disabled, response.slug ) ) {
1998                      themes.disabled = _.without( themes.disabled, response.slug );
1999                      if ( themes.disabled.length ) {
2000                          $views.find( '.disabled .count' ).text( '(' + themes.disabled.length + ')' );
2001                      } else {
2002                          $views.find( '.disabled' ).remove();
2003                      }
2004                  }
2005  
2006                  if ( -1 !== _.indexOf( themes['auto-update-enabled'], response.slug ) ) {
2007                      themes['auto-update-enabled'] = _.without( themes['auto-update-enabled'], response.slug );
2008                      if ( themes['auto-update-enabled'].length ) {
2009                          $views.find( '.auto-update-enabled .count' ).text( '(' + themes['auto-update-enabled'].length + ')' );
2010                      } else {
2011                          $views.find( '.auto-update-enabled' ).remove();
2012                      }
2013                  }
2014  
2015                  if ( -1 !== _.indexOf( themes['auto-update-disabled'], response.slug ) ) {
2016                      themes['auto-update-disabled'] = _.without( themes['auto-update-disabled'], response.slug );
2017                      if ( themes['auto-update-disabled'].length ) {
2018                          $views.find( '.auto-update-disabled .count' ).text( '(' + themes['auto-update-disabled'].length + ')' );
2019                      } else {
2020                          $views.find( '.auto-update-disabled' ).remove();
2021                      }
2022                  }
2023  
2024                  themes.all = _.without( themes.all, response.slug );
2025  
2026                  // There is always at least one theme available.
2027                  $views.find( '.all .count' ).text( '(' + themes.all.length + ')' );
2028              } );
2029          }
2030  
2031          // DecrementCount from update count.
2032          if ( 'themes' === pagenow ) {
2033              var theme = _.find( _wpThemeSettings.themes, { id: response.slug } );
2034              if ( theme.hasUpdate ) {
2035                  wp.updates.decrementCount( 'theme' );
2036              }
2037          }
2038  
2039          wp.a11y.speak( _x( 'Deleted!', 'theme' ) );
2040  
2041          $document.trigger( 'wp-theme-delete-success', response );
2042      };
2043  
2044      /**
2045       * Updates the UI appropriately after a failed theme deletion.
2046       *
2047       * @since 4.6.0
2048       *
2049       * @param {Object} response              Response from the server.
2050       * @param {string} response.slug         Slug of the theme to be deleted.
2051       * @param {string} response.errorCode    Error code for the error that occurred.
2052       * @param {string} response.errorMessage The error that occurred.
2053       */
2054      wp.updates.deleteThemeError = function( response ) {
2055          var $themeRow    = $( 'tr.inactive[data-slug="' + response.slug + '"]' ),
2056              $button      = $( '.theme-actions .delete-theme' ),
2057              updateRow    = wp.template( 'item-update-row' ),
2058              $updateRow   = $themeRow.siblings( '#' + response.slug + '-update' ),
2059              errorMessage = sprintf(
2060                  /* translators: %s: Error string for a failed deletion. */
2061                  __( 'Deletion failed: %s' ),
2062                  response.errorMessage
2063              ),
2064              $message     = wp.updates.adminNotice( {
2065                  className: 'update-message notice-error notice-alt',
2066                  message:   errorMessage
2067              } );
2068  
2069          if ( wp.updates.maybeHandleCredentialError( response, 'delete-theme' ) ) {
2070              return;
2071          }
2072  
2073          if ( 'themes-network' === pagenow ) {
2074              if ( ! $updateRow.length ) {
2075                  $themeRow.addClass( 'update' ).after(
2076                      updateRow( {
2077                          slug: response.slug,
2078                          colspan: $( '#bulk-action-form' ).find( 'thead th:not(.hidden), thead td' ).length,
2079                          content: $message
2080                      } )
2081                  );
2082              } else {
2083                  // Remove previous error messages, if any.
2084                  $updateRow.find( '.notice-error' ).remove();
2085                  $updateRow.find( '.plugin-update' ).append( $message );
2086              }
2087          } else {
2088              $( '.theme-info .theme-description' ).before( $message );
2089          }
2090  
2091          $button.html( $button.data( 'originaltext' ) );
2092  
2093          wp.a11y.speak( errorMessage, 'assertive' );
2094  
2095          $document.trigger( 'wp-theme-delete-error', response );
2096      };
2097  
2098      /**
2099       * Adds the appropriate callback based on the type of action and the current page.
2100       *
2101       * @since 4.6.0
2102       * @private
2103       *
2104       * @param {Object} data   Ajax payload.
2105       * @param {string} action The type of request to perform.
2106       * @return {Object} The Ajax payload with the appropriate callbacks.
2107       */
2108      wp.updates._addCallbacks = function( data, action ) {
2109          if ( 'import' === pagenow && 'install-plugin' === action ) {
2110              data.success = wp.updates.installImporterSuccess;
2111              data.error   = wp.updates.installImporterError;
2112          }
2113  
2114          return data;
2115      };
2116  
2117      /**
2118       * Pulls available jobs from the queue and runs them.
2119       *
2120       * @since 4.2.0
2121       * @since 4.6.0 Can handle multiple job types.
2122       */
2123      wp.updates.queueChecker = function() {
2124          var job;
2125  
2126          if ( wp.updates.ajaxLocked || ! wp.updates.queue.length ) {
2127              return;
2128          }
2129  
2130          job = wp.updates.queue.shift();
2131  
2132          // Handle a queue job.
2133          switch ( job.action ) {
2134              case 'install-plugin':
2135                  wp.updates.installPlugin( job.data );
2136                  break;
2137  
2138              case 'update-plugin':
2139                  wp.updates.updatePlugin( job.data );
2140                  break;
2141  
2142              case 'delete-plugin':
2143                  wp.updates.deletePlugin( job.data );
2144                  break;
2145  
2146              case 'install-theme':
2147                  wp.updates.installTheme( job.data );
2148                  break;
2149  
2150              case 'update-theme':
2151                  wp.updates.updateTheme( job.data );
2152                  break;
2153  
2154              case 'delete-theme':
2155                  wp.updates.deleteTheme( job.data );
2156                  break;
2157  
2158              default:
2159                  break;
2160          }
2161      };
2162  
2163      /**
2164       * Requests the users filesystem credentials if they aren't already known.
2165       *
2166       * @since 4.2.0
2167       *
2168       * @param {Event=} event Optional. Event interface.
2169       */
2170      wp.updates.requestFilesystemCredentials = function( event ) {
2171          if ( false === wp.updates.filesystemCredentials.available ) {
2172              /*
2173               * After exiting the credentials request modal,
2174               * return the focus to the element triggering the request.
2175               */
2176              if ( event && ! wp.updates.$elToReturnFocusToFromCredentialsModal ) {
2177                  wp.updates.$elToReturnFocusToFromCredentialsModal = $( event.target );
2178              }
2179  
2180              wp.updates.ajaxLocked = true;
2181              wp.updates.requestForCredentialsModalOpen();
2182          }
2183      };
2184  
2185      /**
2186       * Requests the users filesystem credentials if needed and there is no lock.
2187       *
2188       * @since 4.6.0
2189       *
2190       * @param {Event=} event Optional. Event interface.
2191       */
2192      wp.updates.maybeRequestFilesystemCredentials = function( event ) {
2193          if ( wp.updates.shouldRequestFilesystemCredentials && ! wp.updates.ajaxLocked ) {
2194              wp.updates.requestFilesystemCredentials( event );
2195          }
2196      };
2197  
2198      /**
2199       * Keydown handler for the request for credentials modal.
2200       *
2201       * Closes the modal when the escape key is pressed and
2202       * constrains keyboard navigation to inside the modal.
2203       *
2204       * @since 4.2.0
2205       *
2206       * @param {Event} event Event interface.
2207       */
2208      wp.updates.keydown = function( event ) {
2209          if ( 27 === event.keyCode ) {
2210              wp.updates.requestForCredentialsModalCancel();
2211          } else if ( 9 === event.keyCode ) {
2212  
2213              // #upgrade button must always be the last focus-able element in the dialog.
2214              if ( 'upgrade' === event.target.id && ! event.shiftKey ) {
2215                  $( '#hostname' ).trigger( 'focus' );
2216  
2217                  event.preventDefault();
2218              } else if ( 'hostname' === event.target.id && event.shiftKey ) {
2219                  $( '#upgrade' ).trigger( 'focus' );
2220  
2221                  event.preventDefault();
2222              }
2223          }
2224      };
2225  
2226      /**
2227       * Opens the request for credentials modal.
2228       *
2229       * @since 4.2.0
2230       */
2231      wp.updates.requestForCredentialsModalOpen = function() {
2232          var $modal = $( '#request-filesystem-credentials-dialog' );
2233  
2234          $( 'body' ).addClass( 'modal-open' );
2235          $modal.show();
2236          $modal.find( 'input:enabled:first' ).trigger( 'focus' );
2237          $modal.on( 'keydown', wp.updates.keydown );
2238      };
2239  
2240      /**
2241       * Closes the request for credentials modal.
2242       *
2243       * @since 4.2.0
2244       */
2245      wp.updates.requestForCredentialsModalClose = function() {
2246          $( '#request-filesystem-credentials-dialog' ).hide();
2247          $( 'body' ).removeClass( 'modal-open' );
2248  
2249          if ( wp.updates.$elToReturnFocusToFromCredentialsModal ) {
2250              wp.updates.$elToReturnFocusToFromCredentialsModal.trigger( 'focus' );
2251          }
2252      };
2253  
2254      /**
2255       * Takes care of the steps that need to happen when the modal is canceled out.
2256       *
2257       * @since 4.2.0
2258       * @since 4.6.0 Triggers an event for callbacks to listen to and add their actions.
2259       */
2260      wp.updates.requestForCredentialsModalCancel = function() {
2261  
2262          // Not ajaxLocked and no queue means we already have cleared things up.
2263          if ( ! wp.updates.ajaxLocked && ! wp.updates.queue.length ) {
2264              return;
2265          }
2266  
2267          _.each( wp.updates.queue, function( job ) {
2268              $document.trigger( 'credential-modal-cancel', job );
2269          } );
2270  
2271          // Remove the lock, and clear the queue.
2272          wp.updates.ajaxLocked = false;
2273          wp.updates.queue = [];
2274  
2275          wp.updates.requestForCredentialsModalClose();
2276      };
2277  
2278      /**
2279       * Displays an error message in the request for credentials form.
2280       *
2281       * @since 4.2.0
2282       *
2283       * @param {string} message Error message.
2284       */
2285      wp.updates.showErrorInCredentialsForm = function( message ) {
2286          var $filesystemForm = $( '#request-filesystem-credentials-form' );
2287  
2288          // Remove any existing error.
2289          $filesystemForm.find( '.notice' ).remove();
2290          $filesystemForm.find( '#request-filesystem-credentials-title' ).after( '<div class="notice notice-alt notice-error"><p>' + message + '</p></div>' );
2291      };
2292  
2293      /**
2294       * Handles credential errors and runs events that need to happen in that case.
2295       *
2296       * @since 4.2.0
2297       *
2298       * @param {Object} response Ajax response.
2299       * @param {string} action   The type of request to perform.
2300       */
2301      wp.updates.credentialError = function( response, action ) {
2302  
2303          // Restore callbacks.
2304          response = wp.updates._addCallbacks( response, action );
2305  
2306          wp.updates.queue.unshift( {
2307              action: action,
2308  
2309              /*
2310               * Not cool that we're depending on response for this data.
2311               * This would feel more whole in a view all tied together.
2312               */
2313              data: response
2314          } );
2315  
2316          wp.updates.filesystemCredentials.available = false;
2317          wp.updates.showErrorInCredentialsForm( response.errorMessage );
2318          wp.updates.requestFilesystemCredentials();
2319      };
2320  
2321      /**
2322       * Handles credentials errors if it could not connect to the filesystem.
2323       *
2324       * @since 4.6.0
2325       *
2326       * @param {Object} response              Response from the server.
2327       * @param {string} response.errorCode    Error code for the error that occurred.
2328       * @param {string} response.errorMessage The error that occurred.
2329       * @param {string} action                The type of request to perform.
2330       * @return {boolean} Whether there is an error that needs to be handled or not.
2331       */
2332      wp.updates.maybeHandleCredentialError = function( response, action ) {
2333          if ( wp.updates.shouldRequestFilesystemCredentials && response.errorCode && 'unable_to_connect_to_filesystem' === response.errorCode ) {
2334              wp.updates.credentialError( response, action );
2335              return true;
2336          }
2337  
2338          return false;
2339      };
2340  
2341      /**
2342       * Validates an Ajax response to ensure it's a proper object.
2343       *
2344       * If the response deems to be invalid, an admin notice is being displayed.
2345       *
2346       * @param {(Object|string)} response              Response from the server.
2347       * @param {function=}       response.always       Optional. Callback for when the Deferred is resolved or rejected.
2348       * @param {string=}         response.statusText   Optional. Status message corresponding to the status code.
2349       * @param {string=}         response.responseText Optional. Request response as text.
2350       * @param {string}          action                Type of action the response is referring to. Can be 'delete',
2351       *                                                'update' or 'install'.
2352       */
2353      wp.updates.isValidResponse = function( response, action ) {
2354          var error = __( 'Something went wrong.' ),
2355              errorMessage;
2356  
2357          // Make sure the response is a valid data object and not a Promise object.
2358          if ( _.isObject( response ) && ! _.isFunction( response.always ) ) {
2359              return true;
2360          }
2361  
2362          if ( _.isString( response ) && '-1' === response ) {
2363              error = __( 'An error has occurred. Please reload the page and try again.' );
2364          } else if ( _.isString( response ) ) {
2365              error = response;
2366          } else if ( 'undefined' !== typeof response.readyState && 0 === response.readyState ) {
2367              error = __( 'Connection lost or the server is busy. Please try again later.' );
2368          } else if ( _.isString( response.responseText ) && '' !== response.responseText ) {
2369              error = response.responseText;
2370          } else if ( _.isString( response.statusText ) ) {
2371              error = response.statusText;
2372          }
2373  
2374          switch ( action ) {
2375              case 'update':
2376                  /* translators: %s: Error string for a failed update. */
2377                  errorMessage = __( 'Update failed: %s' );
2378                  break;
2379  
2380              case 'install':
2381                  /* translators: %s: Error string for a failed installation. */
2382                  errorMessage = __( 'Installation failed: %s' );
2383                  break;
2384  
2385              case 'check-dependencies':
2386                  /* translators: %s: Error string for a failed dependencies check. */
2387                  errorMessage = __( 'Dependencies check failed: %s' );
2388                  break;
2389  
2390              case 'activate':
2391                  /* translators: %s: Error string for a failed activation. */
2392                  errorMessage = __( 'Activation failed: %s' );
2393                  break;
2394  
2395              case 'delete':
2396                  /* translators: %s: Error string for a failed deletion. */
2397                  errorMessage = __( 'Deletion failed: %s' );
2398                  break;
2399          }
2400  
2401          // Messages are escaped, remove HTML tags to make them more readable.
2402          error = error.replace( /<[\/a-z][^<>]*>/gi, '' );
2403          errorMessage = errorMessage.replace( '%s', error );
2404  
2405          // Add admin notice.
2406          wp.updates.addAdminNotice( {
2407              id:        'unknown_error',
2408              className: 'notice-error is-dismissible',
2409              message:   _.escape( errorMessage )
2410          } );
2411  
2412          // Remove the lock, and clear the queue.
2413          wp.updates.ajaxLocked = false;
2414          wp.updates.queue      = [];
2415  
2416          // Change buttons of all running updates.
2417          $( '.button.updating-message' )
2418              .removeClass( 'updating-message' )
2419              .removeAttr( 'aria-label' )
2420              .prop( 'disabled', true )
2421              .text( __( 'Update failed.' ) );
2422  
2423          $( '.updating-message:not(.button):not(.thickbox)' )
2424              .removeClass( 'updating-message notice-warning' )
2425              .addClass( 'notice-error' )
2426              .find( 'p' )
2427                  .removeAttr( 'aria-label' )
2428                  .text( errorMessage );
2429  
2430          wp.a11y.speak( errorMessage, 'assertive' );
2431  
2432          return false;
2433      };
2434  
2435      /**
2436       * Potentially adds an AYS to a user attempting to leave the page.
2437       *
2438       * If an update is on-going and a user attempts to leave the page,
2439       * opens an "Are you sure?" alert.
2440       *
2441       * @since 4.2.0
2442       */
2443      wp.updates.beforeunload = function() {
2444          if ( wp.updates.ajaxLocked ) {
2445              return __( 'Updates may not complete if you navigate away from this page.' );
2446          }
2447      };
2448  
2449      $( function() {
2450          var $pluginFilter        = $( '#plugin-filter, #plugin-information-footer' ),
2451              $bulkActionForm      = $( '#bulk-action-form' ),
2452              $filesystemForm      = $( '#request-filesystem-credentials-form' ),
2453              $filesystemModal     = $( '#request-filesystem-credentials-dialog' ),
2454              $pluginSearch        = $( '.plugins-php .wp-filter-search' ),
2455              $pluginInstallSearch = $( '.plugin-install-php .wp-filter-search' );
2456  
2457          settings = _.extend( settings, window._wpUpdatesItemCounts || {} );
2458  
2459          if ( settings.totals ) {
2460              wp.updates.refreshCount();
2461          }
2462  
2463          /*
2464           * Whether a user needs to submit filesystem credentials.
2465           *
2466           * This is based on whether the form was output on the page server-side.
2467           *
2468           * @see {wp_print_request_filesystem_credentials_modal() in PHP}
2469           */
2470          wp.updates.shouldRequestFilesystemCredentials = $filesystemModal.length > 0;
2471  
2472          /**
2473           * File system credentials form submit noop-er / handler.
2474           *
2475           * @since 4.2.0
2476           */
2477          $filesystemModal.on( 'submit', 'form', function( event ) {
2478              event.preventDefault();
2479  
2480              // Persist the credentials input by the user for the duration of the page load.
2481              wp.updates.filesystemCredentials.ftp.hostname       = $( '#hostname' ).val();
2482              wp.updates.filesystemCredentials.ftp.username       = $( '#username' ).val();
2483              wp.updates.filesystemCredentials.ftp.password       = $( '#password' ).val();
2484              wp.updates.filesystemCredentials.ftp.connectionType = $( 'input[name="connection_type"]:checked' ).val();
2485              wp.updates.filesystemCredentials.ssh.publicKey      = $( '#public_key' ).val();
2486              wp.updates.filesystemCredentials.ssh.privateKey     = $( '#private_key' ).val();
2487              wp.updates.filesystemCredentials.fsNonce            = $( '#_fs_nonce' ).val();
2488              wp.updates.filesystemCredentials.available          = true;
2489  
2490              // Unlock and invoke the queue.
2491              wp.updates.ajaxLocked = false;
2492              wp.updates.queueChecker();
2493  
2494              wp.updates.requestForCredentialsModalClose();
2495          } );
2496  
2497          /**
2498           * Closes the request credentials modal when clicking the 'Cancel' button or outside of the modal.
2499           *
2500           * @since 4.2.0
2501           */
2502          $filesystemModal.on( 'click', '[data-js-action="close"], .notification-dialog-background', wp.updates.requestForCredentialsModalCancel );
2503  
2504          /**
2505           * Hide SSH fields when not selected.
2506           *
2507           * @since 4.2.0
2508           */
2509          $filesystemForm.on( 'change', 'input[name="connection_type"]', function() {
2510              $( '#ssh-keys' ).toggleClass( 'hidden', ( 'ssh' !== $( this ).val() ) );
2511          } ).trigger( 'change' );
2512  
2513          /**
2514           * Handles events after the credential modal was closed.
2515           *
2516           * @since 4.6.0
2517           *
2518           * @param {Event}  event Event interface.
2519           * @param {string} job   The install/update.delete request.
2520           */
2521          $document.on( 'credential-modal-cancel', function( event, job ) {
2522              var $updatingMessage = $( '.updating-message' ),
2523                  $message, originalText;
2524  
2525              if ( 'import' === pagenow ) {
2526                  $updatingMessage.removeClass( 'updating-message' );
2527              } else if ( 'plugins' === pagenow || 'plugins-network' === pagenow ) {
2528                  if ( 'update-plugin' === job.action ) {
2529                      $message = $( 'tr[data-plugin="' + job.data.plugin + '"]' ).find( '.update-message' );
2530                  } else if ( 'delete-plugin' === job.action ) {
2531                      $message = $( '[data-plugin="' + job.data.plugin + '"]' ).find( '.row-actions a.delete' );
2532                  }
2533              } else if ( 'themes' === pagenow || 'themes-network' === pagenow ) {
2534                  if ( 'update-theme' === job.action ) {
2535                      $message = $( '[data-slug="' + job.data.slug + '"]' ).find( '.update-message' );
2536                  } else if ( 'delete-theme' === job.action && 'themes-network' === pagenow ) {
2537                      $message = $( '[data-slug="' + job.data.slug + '"]' ).find( '.row-actions a.delete' );
2538                  } else if ( 'delete-theme' === job.action && 'themes' === pagenow ) {
2539                      $message = $( '.theme-actions .delete-theme' );
2540                  }
2541              } else {
2542                  $message = $updatingMessage;
2543              }
2544  
2545              if ( $message && $message.hasClass( 'updating-message' ) ) {
2546                  originalText = $message.data( 'originaltext' );
2547  
2548                  if ( 'undefined' === typeof originalText ) {
2549                      originalText = $( '<p>' ).html( $message.find( 'p' ).data( 'originaltext' ) );
2550                  }
2551  
2552                  $message
2553                      .removeClass( 'updating-message' )
2554                      .html( originalText );
2555  
2556                  if ( 'plugin-install' === pagenow || 'plugin-install-network' === pagenow ) {
2557                      if ( 'update-plugin' === job.action ) {
2558                          $message.attr(
2559                              'aria-label',
2560                              sprintf(
2561                                  /* translators: %s: Plugin name and version. */
2562                                  _x( 'Update %s now', 'plugin' ),
2563                                  $message.data( 'name' )
2564                              )
2565                          );
2566                      } else if ( 'install-plugin' === job.action ) {
2567                          $message.attr(
2568                              'aria-label',
2569                              sprintf(
2570                                  /* translators: %s: Plugin name. */
2571                                  _x( 'Install %s now', 'plugin' ),
2572                                  $message.data( 'name' )
2573                              )
2574                          );
2575                      }
2576                  }
2577              }
2578  
2579              wp.a11y.speak( __( 'Update canceled.' ) );
2580          } );
2581  
2582          /**
2583           * Click handler for plugin updates in List Table view.
2584           *
2585           * @since 4.2.0
2586           *
2587           * @param {Event} event Event interface.
2588           */
2589          $bulkActionForm.on( 'click', '[data-plugin] .update-link', function( event ) {
2590              var $message   = $( event.target ),
2591                  $pluginRow = $message.parents( 'tr' );
2592  
2593              event.preventDefault();
2594  
2595              if ( $message.hasClass( 'updating-message' ) || $message.hasClass( 'button-disabled' ) ) {
2596                  return;
2597              }
2598  
2599              wp.updates.maybeRequestFilesystemCredentials( event );
2600  
2601              // Return the user to the input box of the plugin's table row after closing the modal.
2602              wp.updates.$elToReturnFocusToFromCredentialsModal = $pluginRow.find( '.check-column input' );
2603              wp.updates.updatePlugin( {
2604                  plugin: $pluginRow.data( 'plugin' ),
2605                  slug:   $pluginRow.data( 'slug' )
2606              } );
2607          } );
2608  
2609          /**
2610           * Click handler for plugin updates in plugin install view.
2611           *
2612           * @since 4.2.0
2613           *
2614           * @param {Event} event Event interface.
2615           */
2616          $pluginFilter.on( 'click', '.update-now', function( event ) {
2617              var $button = $( event.target );
2618              event.preventDefault();
2619  
2620              if ( $button.hasClass( 'updating-message' ) || $button.hasClass( 'button-disabled' ) ) {
2621                  return;
2622              }
2623  
2624              wp.updates.maybeRequestFilesystemCredentials( event );
2625  
2626              wp.updates.updatePlugin( {
2627                  plugin: $button.data( 'plugin' ),
2628                  slug:   $button.data( 'slug' )
2629              } );
2630          } );
2631  
2632          /**
2633           * Click handler for plugin installs in plugin install view.
2634           *
2635           * @since 4.6.0
2636           *
2637           * @param {Event} event Event interface.
2638           */
2639          $pluginFilter.on( 'click', '.install-now', function( event ) {
2640              var $button = $( event.target );
2641              event.preventDefault();
2642  
2643              if ( $button.hasClass( 'updating-message' ) || $button.hasClass( 'button-disabled' ) ) {
2644                  return;
2645              }
2646  
2647              if ( wp.updates.shouldRequestFilesystemCredentials && ! wp.updates.ajaxLocked ) {
2648                  wp.updates.requestFilesystemCredentials( event );
2649  
2650                  $document.on( 'credential-modal-cancel', function() {
2651                      var $message = $( '.install-now.updating-message' );
2652  
2653                      $message
2654                          .removeClass( 'updating-message' )
2655                          .text( _x( 'Install Now', 'plugin' ) );
2656  
2657                      wp.a11y.speak( __( 'Update canceled.' ) );
2658                  } );
2659              }
2660  
2661              wp.updates.installPlugin( {
2662                  slug: $button.data( 'slug' )
2663              } );
2664          } );
2665  
2666          /**
2667           * Click handler for plugin activations in plugin activation view.
2668           *
2669           * @since 6.5.0
2670           *
2671           * @param {Event} event Event interface.
2672           */
2673          $pluginFilter.on( 'click', '.activate-now', function( event ) {
2674              var $activateButton = $( event.target );
2675  
2676              event.preventDefault();
2677  
2678              if ( $activateButton.hasClass( 'activating-message' ) || $activateButton.hasClass( 'button-disabled' ) ) {
2679                  return;
2680              }
2681  
2682              $activateButton
2683                  .removeClass( 'activate-now button-primary' )
2684                  .addClass( 'activating-message' )
2685                  .attr(
2686                      'aria-label',
2687                      sprintf(
2688                          /* translators: %s: Plugin name. */
2689                          _x( 'Activating %s', 'plugin' ),
2690                          $activateButton.data( 'name' )
2691                      )
2692                  )
2693                  .text( __( 'Activating...' ) );
2694  
2695              wp.updates.activatePlugin(
2696                  {
2697                      name: $activateButton.data( 'name' ),
2698                      slug: $activateButton.data( 'slug' ),
2699                      plugin: $activateButton.data( 'plugin' )
2700                  }
2701              );
2702          });
2703  
2704          /**
2705           * Click handler for importer plugins installs in the Import screen.
2706           *
2707           * @since 4.6.0
2708           *
2709           * @param {Event} event Event interface.
2710           */
2711          $document.on( 'click', '.importer-item .install-now', function( event ) {
2712              var $button = $( event.target ),
2713                  pluginName = $( this ).data( 'name' );
2714  
2715              event.preventDefault();
2716  
2717              if ( $button.hasClass( 'updating-message' ) ) {
2718                  return;
2719              }
2720  
2721              if ( wp.updates.shouldRequestFilesystemCredentials && ! wp.updates.ajaxLocked ) {
2722                  wp.updates.requestFilesystemCredentials( event );
2723  
2724                  $document.on( 'credential-modal-cancel', function() {
2725  
2726                      $button
2727                          .removeClass( 'updating-message' )
2728                          .attr(
2729                              'aria-label',
2730                              sprintf(
2731                                  /* translators: %s: Plugin name. */
2732                                  _x( 'Install %s now', 'plugin' ),
2733                                  pluginName
2734                              )
2735                          )
2736                          .text( _x( 'Install Now', 'plugin' ) );
2737  
2738                      wp.a11y.speak( __( 'Update canceled.' ) );
2739                  } );
2740              }
2741  
2742              wp.updates.installPlugin( {
2743                  slug:    $button.data( 'slug' ),
2744                  pagenow: pagenow,
2745                  success: wp.updates.installImporterSuccess,
2746                  error:   wp.updates.installImporterError
2747              } );
2748          } );
2749  
2750          /**
2751           * Click handler for plugin deletions.
2752           *
2753           * @since 4.6.0
2754           *
2755           * @param {Event} event Event interface.
2756           */
2757          $bulkActionForm.on( 'click', '[data-plugin] a.delete', function( event ) {
2758              var $pluginRow = $( event.target ).parents( 'tr' ),
2759                  confirmMessage;
2760  
2761              if ( $pluginRow.hasClass( 'is-uninstallable' ) ) {
2762                  confirmMessage = sprintf(
2763                      /* translators: %s: Plugin name. */
2764                      __( 'Are you sure you want to delete %s and its data?' ),
2765                      $pluginRow.find( '.plugin-title strong' ).text()
2766                  );
2767              } else {
2768                  confirmMessage = sprintf(
2769                      /* translators: %s: Plugin name. */
2770                      __( 'Are you sure you want to delete %s?' ),
2771                      $pluginRow.find( '.plugin-title strong' ).text()
2772                  );
2773              }
2774  
2775              event.preventDefault();
2776  
2777              if ( ! window.confirm( confirmMessage ) ) {
2778                  return;
2779              }
2780  
2781              wp.updates.maybeRequestFilesystemCredentials( event );
2782  
2783              wp.updates.deletePlugin( {
2784                  plugin: $pluginRow.data( 'plugin' ),
2785                  slug:   $pluginRow.data( 'slug' )
2786              } );
2787  
2788          } );
2789  
2790          /**
2791           * Click handler for theme updates.
2792           *
2793           * @since 4.6.0
2794           *
2795           * @param {Event} event Event interface.
2796           */
2797          $document.on( 'click', '.themes-php.network-admin .update-link', function( event ) {
2798              var $message  = $( event.target ),
2799                  $themeRow = $message.parents( 'tr' );
2800  
2801              event.preventDefault();
2802  
2803              if ( $message.hasClass( 'updating-message' ) || $message.hasClass( 'button-disabled' ) ) {
2804                  return;
2805              }
2806  
2807              wp.updates.maybeRequestFilesystemCredentials( event );
2808  
2809              // Return the user to the input box of the theme's table row after closing the modal.
2810              wp.updates.$elToReturnFocusToFromCredentialsModal = $themeRow.find( '.check-column input' );
2811              wp.updates.updateTheme( {
2812                  slug: $themeRow.data( 'slug' )
2813              } );
2814          } );
2815  
2816          /**
2817           * Click handler for theme deletions.
2818           *
2819           * @since 4.6.0
2820           *
2821           * @param {Event} event Event interface.
2822           */
2823          $document.on( 'click', '.themes-php.network-admin a.delete', function( event ) {
2824              var $themeRow = $( event.target ).parents( 'tr' ),
2825                  confirmMessage = sprintf(
2826                      /* translators: %s: Theme name. */
2827                      __( 'Are you sure you want to delete %s?' ),
2828                      $themeRow.find( '.theme-title strong' ).text()
2829                  );
2830  
2831              event.preventDefault();
2832  
2833              if ( ! window.confirm( confirmMessage ) ) {
2834                  return;
2835              }
2836  
2837              wp.updates.maybeRequestFilesystemCredentials( event );
2838  
2839              wp.updates.deleteTheme( {
2840                  slug: $themeRow.data( 'slug' )
2841              } );
2842          } );
2843  
2844          /**
2845           * Bulk action handler for plugins and themes.
2846           *
2847           * Handles both deletions and updates.
2848           *
2849           * @since 4.6.0
2850           *
2851           * @param {Event} event Event interface.
2852           */
2853          $bulkActionForm.on( 'click', '[type="submit"]:not([name="clear-recent-list"])', function( event ) {
2854              var bulkAction    = $( event.target ).siblings( 'select' ).val(),
2855                  itemsSelected = $bulkActionForm.find( 'input[name="checked[]"]:checked' ),
2856                  success       = 0,
2857                  error         = 0,
2858                  errorMessages = [],
2859                  type, action;
2860  
2861              // Determine which type of item we're dealing with.
2862              switch ( pagenow ) {
2863                  case 'plugins':
2864                  case 'plugins-network':
2865                      type = 'plugin';
2866                      break;
2867  
2868                  case 'themes-network':
2869                      type = 'theme';
2870                      break;
2871  
2872                  default:
2873                      return;
2874              }
2875  
2876              // Bail if there were no items selected.
2877              if ( ! itemsSelected.length ) {
2878                  event.preventDefault();
2879                  $( 'html, body' ).animate( { scrollTop: 0 } );
2880  
2881                  return wp.updates.addAdminNotice( {
2882                      id:        'no-items-selected',
2883                      className: 'notice-error is-dismissible',
2884                      message:   __( 'Please select at least one item to perform this action on.' )
2885                  } );
2886              }
2887  
2888              // Determine the type of request we're dealing with.
2889              switch ( bulkAction ) {
2890                  case 'update-selected':
2891                      action = bulkAction.replace( 'selected', type );
2892                      break;
2893  
2894                  case 'delete-selected':
2895                      var confirmMessage = 'plugin' === type ?
2896                          __( 'Are you sure you want to delete the selected plugins and their data?' ) :
2897                          __( 'Caution: These themes may be active on other sites in the network. Are you sure you want to proceed?' );
2898  
2899                      if ( ! window.confirm( confirmMessage ) ) {
2900                          event.preventDefault();
2901                          return;
2902                      }
2903  
2904                      action = bulkAction.replace( 'selected', type );
2905                      break;
2906  
2907                  default:
2908                      return;
2909              }
2910  
2911              wp.updates.maybeRequestFilesystemCredentials( event );
2912  
2913              event.preventDefault();
2914  
2915              // Un-check the bulk checkboxes.
2916              $bulkActionForm.find( '.manage-column [type="checkbox"]' ).prop( 'checked', false );
2917  
2918              $document.trigger( 'wp-' + type + '-bulk-' + bulkAction, itemsSelected );
2919  
2920              // Find all the checkboxes which have been checked.
2921              itemsSelected.each( function( index, element ) {
2922                  var $checkbox = $( element ),
2923                      $itemRow = $checkbox.parents( 'tr' );
2924  
2925                  // Only add update-able items to the update queue.
2926                  if ( 'update-selected' === bulkAction && ( ! $itemRow.hasClass( 'update' ) || $itemRow.find( 'notice-error' ).length ) ) {
2927  
2928                      // Un-check the box.
2929                      $checkbox.prop( 'checked', false );
2930                      return;
2931                  }
2932  
2933                  // Don't add items to the update queue again, even if the user clicks the update button several times.
2934                  if ( 'update-selected' === bulkAction && $itemRow.hasClass( 'is-enqueued' ) ) {
2935                      return;
2936                  }
2937  
2938                  $itemRow.addClass( 'is-enqueued' );
2939  
2940                  // Add it to the queue.
2941                  wp.updates.queue.push( {
2942                      action: action,
2943                      data:   {
2944                          plugin: $itemRow.data( 'plugin' ),
2945                          slug:   $itemRow.data( 'slug' )
2946                      }
2947                  } );
2948              } );
2949  
2950              // Display bulk notification for updates of any kind.
2951              $document.on( 'wp-plugin-update-success wp-plugin-update-error wp-theme-update-success wp-theme-update-error', function( event, response ) {
2952                  var $itemRow = $( '[data-slug="' + response.slug + '"]' ),
2953                      $bulkActionNotice, itemName;
2954  
2955                  if ( 'wp-' + response.update + '-update-success' === event.type ) {
2956                      success++;
2957                  } else {
2958                      itemName = response.pluginName ? response.pluginName : $itemRow.find( '.column-primary strong' ).text();
2959  
2960                      error++;
2961                      errorMessages.push( itemName + ': ' + response.errorMessage );
2962                  }
2963  
2964                  $itemRow.find( 'input[name="checked[]"]:checked' ).prop( 'checked', false );
2965  
2966                  wp.updates.adminNotice = wp.template( 'wp-bulk-updates-admin-notice' );
2967  
2968                  var successMessage = null;
2969  
2970                  if ( success ) {
2971                      if ( 'plugin' === response.update ) {
2972                          successMessage = sprintf(
2973                              /* translators: %s: Number of plugins. */
2974                              _n( '%s plugin successfully updated.', '%s plugins successfully updated.', success ),
2975                              success
2976                          );
2977                      } else {
2978                          successMessage = sprintf(
2979                              /* translators: %s: Number of themes. */
2980                              _n( '%s theme successfully updated.', '%s themes successfully updated.', success ),
2981                              success
2982                          );
2983                      }
2984                  }
2985  
2986                  var errorMessage = null;
2987  
2988                  if ( error ) {
2989                      errorMessage = sprintf(
2990                          /* translators: %s: Number of failed updates. */
2991                          _n( '%s update failed.', '%s updates failed.', error ),
2992                          error
2993                      );
2994                  }
2995  
2996                  wp.updates.addAdminNotice( {
2997                      id:            'bulk-action-notice',
2998                      className:     'bulk-action-notice',
2999                      successMessage: successMessage,
3000                      errorMessage:   errorMessage,
3001                      errorMessages:  errorMessages,
3002                      type:           response.update
3003                  } );
3004  
3005                  $bulkActionNotice = $( '#bulk-action-notice' ).on( 'click', 'button', function() {
3006                      // $( this ) is the clicked button, no need to get it again.
3007                      $( this )
3008                          .toggleClass( 'bulk-action-errors-collapsed' )
3009                          .attr( 'aria-expanded', ! $( this ).hasClass( 'bulk-action-errors-collapsed' ) );
3010                      // Show the errors list.
3011                      $bulkActionNotice.find( '.bulk-action-errors' ).toggleClass( 'hidden' );
3012                  } );
3013  
3014                  if ( error > 0 && ! wp.updates.queue.length ) {
3015                      $( 'html, body' ).animate( { scrollTop: 0 } );
3016                  }
3017              } );
3018  
3019              // Reset admin notice template after #bulk-action-notice was added.
3020              $document.on( 'wp-updates-notice-added', function() {
3021                  wp.updates.adminNotice = wp.template( 'wp-updates-admin-notice' );
3022              } );
3023  
3024              // Check the queue, now that the event handlers have been added.
3025              wp.updates.queueChecker();
3026          } );
3027  
3028          if ( $pluginInstallSearch.length ) {
3029              $pluginInstallSearch.attr( 'aria-describedby', 'live-search-desc' );
3030          }
3031  
3032          /**
3033           * Handles changes to the plugin search box on the new-plugin page,
3034           * searching the repository dynamically.
3035           *
3036           * @since 4.6.0
3037           */
3038          $pluginInstallSearch.on( 'keyup input', _.debounce( function( event, eventtype ) {
3039              var $searchTab = $( '.plugin-install-search' ), data, searchLocation;
3040  
3041              data = {
3042                  _ajax_nonce: wp.updates.ajaxNonce,
3043                  s:           encodeURIComponent( event.target.value ),
3044                  tab:         'search',
3045                  type:        $( '#typeselector' ).val(),
3046                  pagenow:     pagenow
3047              };
3048              searchLocation = location.href.split( '?' )[ 0 ] + '?' + $.param( _.omit( data, [ '_ajax_nonce', 'pagenow' ] ) );
3049  
3050              // Clear on escape.
3051              if ( 'keyup' === event.type && 27 === event.which ) {
3052                  event.target.value = '';
3053              }
3054  
3055              if ( wp.updates.searchTerm === data.s && 'typechange' !== eventtype ) {
3056                  return;
3057              } else {
3058                  $pluginFilter.empty();
3059                  wp.updates.searchTerm = data.s;
3060              }
3061  
3062              if ( window.history && window.history.replaceState ) {
3063                  window.history.replaceState( null, '', searchLocation );
3064              }
3065  
3066              if ( ! $searchTab.length ) {
3067                  $searchTab = $( '<li class="plugin-install-search" />' )
3068                      .append( $( '<a />', {
3069                          'class': 'current',
3070                          'href': searchLocation,
3071                          'text': __( 'Search Results' )
3072                      } ) );
3073  
3074                  $( '.wp-filter .filter-links .current' )
3075                      .removeClass( 'current' )
3076                      .parents( '.filter-links' )
3077                      .prepend( $searchTab );
3078  
3079                  $pluginFilter.prev( 'p' ).remove();
3080                  $( '.plugins-popular-tags-wrapper' ).remove();
3081              }
3082  
3083              if ( 'undefined' !== typeof wp.updates.searchRequest ) {
3084                  wp.updates.searchRequest.abort();
3085              }
3086              $( 'body' ).addClass( 'loading-content' );
3087  
3088              wp.updates.searchRequest = wp.ajax.post( 'search-install-plugins', data ).done( function( response ) {
3089                  $( 'body' ).removeClass( 'loading-content' );
3090                  $pluginFilter.append( response.items );
3091                  delete wp.updates.searchRequest;
3092  
3093                  if ( 0 === response.count ) {
3094                      wp.a11y.speak( __( 'You do not appear to have any plugins available at this time.' ) );
3095                  } else {
3096                      wp.a11y.speak(
3097                          sprintf(
3098                              /* translators: %s: Number of plugins. */
3099                              __( 'Number of plugins found: %d' ),
3100                              response.count
3101                          )
3102                      );
3103                  }
3104              } );
3105          }, 1000 ) );
3106  
3107          if ( $pluginSearch.length ) {
3108              $pluginSearch.attr( 'aria-describedby', 'live-search-desc' );
3109          }
3110  
3111          /**
3112           * Handles changes to the plugin search box on the Installed Plugins screen,
3113           * searching the plugin list dynamically.
3114           *
3115           * @since 4.6.0
3116           */
3117          $pluginSearch.on( 'keyup input', _.debounce( function( event ) {
3118              var data = {
3119                  _ajax_nonce:   wp.updates.ajaxNonce,
3120                  s:             encodeURIComponent( event.target.value ),
3121                  pagenow:       pagenow,
3122                  plugin_status: 'all'
3123              },
3124              queryArgs;
3125  
3126              // Clear on escape.
3127              if ( 'keyup' === event.type && 27 === event.which ) {
3128                  event.target.value = '';
3129              }
3130  
3131              if ( wp.updates.searchTerm === data.s ) {
3132                  return;
3133              } else {
3134                  wp.updates.searchTerm = data.s;
3135              }
3136  
3137              queryArgs = _.object( _.compact( _.map( location.search.slice( 1 ).split( '&' ), function( item ) {
3138                  if ( item ) return item.split( '=' );
3139              } ) ) );
3140  
3141              data.plugin_status = queryArgs.plugin_status || 'all';
3142  
3143              if ( window.history && window.history.replaceState ) {
3144                  window.history.replaceState( null, '', location.href.split( '?' )[ 0 ] + '?s=' + data.s + '&plugin_status=' + data.plugin_status );
3145              }
3146  
3147              if ( 'undefined' !== typeof wp.updates.searchRequest ) {
3148                  wp.updates.searchRequest.abort();
3149              }
3150  
3151              $bulkActionForm.empty();
3152              $( 'body' ).addClass( 'loading-content' );
3153              $( '.subsubsub .current' ).removeClass( 'current' );
3154  
3155              wp.updates.searchRequest = wp.ajax.post( 'search-plugins', data ).done( function( response ) {
3156  
3157                  // Can we just ditch this whole subtitle business?
3158                  var $subTitle    = $( '<span />' ).addClass( 'subtitle' ).html(
3159                      sprintf(
3160                          /* translators: %s: Search query. */
3161                          __( 'Search results for: %s' ),
3162                          '<strong>' + _.escape( decodeURIComponent( data.s ) ) + '</strong>'
3163                      ) ),
3164                      $oldSubTitle = $( '.wrap .subtitle' );
3165  
3166                  if ( ! data.s.length ) {
3167                      $oldSubTitle.remove();
3168                      $( '.subsubsub .' + data.plugin_status + ' a' ).addClass( 'current' );
3169                  } else if ( $oldSubTitle.length ) {
3170                      $oldSubTitle.replaceWith( $subTitle );
3171                  } else {
3172                      $( '.wp-header-end' ).before( $subTitle );
3173                  }
3174  
3175                  $( 'body' ).removeClass( 'loading-content' );
3176                  $bulkActionForm.append( response.items );
3177                  delete wp.updates.searchRequest;
3178  
3179                  if ( 0 === response.count ) {
3180                      wp.a11y.speak( __( 'No plugins found. Try a different search.'  ) );
3181                  } else {
3182                      wp.a11y.speak(
3183                          sprintf(
3184                              /* translators: %s: Number of plugins. */
3185                              __( 'Number of plugins found: %d' ),
3186                              response.count
3187                          )
3188                      );
3189                  }
3190              } );
3191          }, 500 ) );
3192  
3193          /**
3194           * Trigger a search event when the search form gets submitted.
3195           *
3196           * @since 4.6.0
3197           */
3198          $document.on( 'submit', '.search-plugins', function( event ) {
3199              event.preventDefault();
3200  
3201              $( 'input.wp-filter-search' ).trigger( 'input' );
3202          } );
3203  
3204          /**
3205           * Trigger a search event when the "Try Again" button is clicked.
3206           *
3207           * @since 4.9.0
3208           */
3209          $document.on( 'click', '.try-again', function( event ) {
3210              event.preventDefault();
3211              $pluginInstallSearch.trigger( 'input' );
3212          } );
3213  
3214          /**
3215           * Trigger a search event when the search type gets changed.
3216           *
3217           * @since 4.6.0
3218           */
3219          $( '#typeselector' ).on( 'change', function() {
3220              var $search = $( 'input[name="s"]' );
3221  
3222              if ( $search.val().length ) {
3223                  $search.trigger( 'input', 'typechange' );
3224              }
3225          } );
3226  
3227          /**
3228           * Click handler for updating a plugin from the details modal on `plugin-install.php`.
3229           *
3230           * @since 4.2.0
3231           *
3232           * @param {Event} event Event interface.
3233           */
3234          $( '#plugin_update_from_iframe' ).on( 'click', function( event ) {
3235              var target = window.parent === window ? null : window.parent,
3236                  update;
3237  
3238              $.support.postMessage = !! window.postMessage;
3239  
3240              if ( false === $.support.postMessage || null === target || -1 !== window.parent.location.pathname.indexOf( 'update-core.php' ) ) {
3241                  return;
3242              }
3243  
3244              event.preventDefault();
3245  
3246              update = {
3247                  action: 'update-plugin',
3248                  data:   {
3249                      plugin: $( this ).data( 'plugin' ),
3250                      slug:   $( this ).data( 'slug' )
3251                  }
3252              };
3253  
3254              target.postMessage( JSON.stringify( update ), window.location.origin );
3255          } );
3256  
3257          /**
3258           * Handles postMessage events.
3259           *
3260           * @since 4.2.0
3261           * @since 4.6.0 Switched `update-plugin` action to use the queue.
3262           *
3263           * @param {Event} event Event interface.
3264           */
3265          $( window ).on( 'message', function( event ) {
3266              var originalEvent  = event.originalEvent,
3267                  expectedOrigin = document.location.protocol + '//' + document.location.host,
3268                  message;
3269  
3270              if ( originalEvent.origin !== expectedOrigin ) {
3271                  return;
3272              }
3273  
3274              try {
3275                  message = JSON.parse( originalEvent.data );
3276              } catch ( e ) {
3277                  return;
3278              }
3279  
3280              if ( ! message ) {
3281                  return;
3282              }
3283  
3284              if ( 'undefined' !== typeof message.id && 'plugin-activated-successfully' === message.id ) {
3285                  wp.updates.addAdminNotice( message );
3286                  return;
3287              }
3288  
3289              if (
3290                  'undefined' !== typeof message.status &&
3291                  'undefined' !== typeof message.slug &&
3292                  'undefined' !== typeof message.text &&
3293                  'undefined' !== typeof message.ariaLabel
3294              ) {
3295                  var $card = $( '.plugin-card-' + message.slug ),
3296                      $message = $card.find( '[data-slug="' + message.slug + '"]' );
3297  
3298                  if ( 'undefined' !== typeof message.removeClasses ) {
3299                      $message.removeClass( message.removeClasses );
3300                  }
3301  
3302                  if ( 'undefined' !== typeof message.addClasses ) {
3303                      $message.addClass( message.addClasses );
3304                  }
3305  
3306                  if ( '' === message.ariaLabel ) {
3307                      $message.removeAttr( 'aria-label' );
3308                  } else {
3309                      $message.attr( 'aria-label', message.ariaLabel );
3310                  }
3311  
3312                  if ( 'dependencies-check-success' === message.status ) {
3313                      $message
3314                          .attr( 'data-name', message.pluginName )
3315                          .attr( 'data-slug', message.slug )
3316                          .attr( 'data-plugin', message.plugin )
3317                          .attr( 'href', message.href );
3318                  }
3319  
3320                  $message.text( message.text );
3321              }
3322  
3323              if ( 'undefined' === typeof message.action ) {
3324                  return;
3325              }
3326  
3327              switch ( message.action ) {
3328  
3329                  // Called from `wp-admin/includes/class-wp-upgrader-skins.php`.
3330                  case 'decrementUpdateCount':
3331                      /** @property {string} message.upgradeType */
3332                      wp.updates.decrementCount( message.upgradeType );
3333                      break;
3334  
3335                  case 'install-plugin':
3336                  case 'update-plugin':
3337                      if ( 'undefined' === typeof message.data || 'undefined' === typeof message.data.slug ) {
3338                          return;
3339                      }
3340  
3341                      message.data = wp.updates._addCallbacks( message.data, message.action );
3342  
3343                      wp.updates.queue.push( message );
3344                      wp.updates.queueChecker();
3345                      break;
3346              }
3347          } );
3348  
3349          /**
3350           * Adds a callback to display a warning before leaving the page.
3351           *
3352           * @since 4.2.0
3353           */
3354          $( window ).on( 'beforeunload', wp.updates.beforeunload );
3355  
3356          /**
3357           * Prevents the page form scrolling when activating auto-updates with the Spacebar key.
3358           *
3359           * @since 5.5.0
3360           */
3361          $document.on( 'keydown', '.column-auto-updates .toggle-auto-update, .theme-overlay .toggle-auto-update', function( event ) {
3362              if ( 32 === event.which ) {
3363                  event.preventDefault();
3364              }
3365          } );
3366  
3367          /**
3368           * Click and keyup handler for enabling and disabling plugin and theme auto-updates.
3369           *
3370           * These controls can be either links or buttons. When JavaScript is enabled,
3371           * we want them to behave like buttons. An ARIA role `button` is added via
3372           * the JavaScript that targets elements with the CSS class `aria-button-if-js`.
3373           *
3374           * @since 5.5.0
3375           */
3376          $document.on( 'click keyup', '.column-auto-updates .toggle-auto-update, .theme-overlay .toggle-auto-update', function( event ) {
3377              var data, asset, type, $parent,
3378                  $toggler = $( this ),
3379                  action = $toggler.attr( 'data-wp-action' ),
3380                  $label = $toggler.find( '.label' );
3381  
3382              if ( 'keyup' === event.type && 32 !== event.which ) {
3383                  return;
3384              }
3385  
3386              if ( 'themes' !== pagenow ) {
3387                  $parent = $toggler.closest( '.column-auto-updates' );
3388              } else {
3389                  $parent = $toggler.closest( '.theme-autoupdate' );
3390              }
3391  
3392              event.preventDefault();
3393  
3394              // Prevent multiple simultaneous requests.
3395              if ( $toggler.attr( 'data-doing-ajax' ) === 'yes' ) {
3396                  return;
3397              }
3398  
3399              $toggler.attr( 'data-doing-ajax', 'yes' );
3400  
3401              switch ( pagenow ) {
3402                  case 'plugins':
3403                  case 'plugins-network':
3404                      type = 'plugin';
3405                      asset = $toggler.closest( 'tr' ).attr( 'data-plugin' );
3406                      break;
3407                  case 'themes-network':
3408                      type = 'theme';
3409                      asset = $toggler.closest( 'tr' ).attr( 'data-slug' );
3410                      break;
3411                  case 'themes':
3412                      type = 'theme';
3413                      asset = $toggler.attr( 'data-slug' );
3414                      break;
3415              }
3416  
3417              // Clear any previous errors.
3418              $parent.find( '.notice.notice-error' ).addClass( 'hidden' );
3419  
3420              // Show loading status.
3421              if ( 'enable' === action ) {
3422                  $label.text( __( 'Enabling...' ) );
3423              } else {
3424                  $label.text( __( 'Disabling...' ) );
3425              }
3426  
3427              $toggler.find( '.dashicons-update' ).removeClass( 'hidden' );
3428  
3429              data = {
3430                  action: 'toggle-auto-updates',
3431                  _ajax_nonce: settings.ajax_nonce,
3432                  state: action,
3433                  type: type,
3434                  asset: asset
3435              };
3436  
3437              $.post( window.ajaxurl, data )
3438                  .done( function( response ) {
3439                      var $enabled, $disabled, enabledNumber, disabledNumber, errorMessage,
3440                          href = $toggler.attr( 'href' );
3441  
3442                      if ( ! response.success ) {
3443                          // if WP returns 0 for response (which can happen in a few cases),
3444                          // output the general error message since we won't have response.data.error.
3445                          if ( response.data && response.data.error ) {
3446                              errorMessage = response.data.error;
3447                          } else {
3448                              errorMessage = __( 'The request could not be completed.' );
3449                          }
3450  
3451                          $parent.find( '.notice.notice-error' ).removeClass( 'hidden' ).find( 'p' ).text( errorMessage );
3452                          wp.a11y.speak( errorMessage, 'assertive' );
3453                          return;
3454                      }
3455  
3456                      // Update the counts in the enabled/disabled views if on a screen
3457                      // with a list table.
3458                      if ( 'themes' !== pagenow ) {
3459                          $enabled       = $( '.auto-update-enabled span' );
3460                          $disabled      = $( '.auto-update-disabled span' );
3461                          enabledNumber  = parseInt( $enabled.text().replace( /[^\d]+/g, '' ), 10 ) || 0;
3462                          disabledNumber = parseInt( $disabled.text().replace( /[^\d]+/g, '' ), 10 ) || 0;
3463  
3464                          switch ( action ) {
3465                              case 'enable':
3466                                  ++enabledNumber;
3467                                  --disabledNumber;
3468                                  break;
3469                              case 'disable':
3470                                  --enabledNumber;
3471                                  ++disabledNumber;
3472                                  break;
3473                          }
3474  
3475                          enabledNumber = Math.max( 0, enabledNumber );
3476                          disabledNumber = Math.max( 0, disabledNumber );
3477  
3478                          $enabled.text( '(' + enabledNumber + ')' );
3479                          $disabled.text( '(' + disabledNumber + ')' );
3480                      }
3481  
3482                      if ( 'enable' === action ) {
3483                          // The toggler control can be either a link or a button.
3484                          if ( $toggler[ 0 ].hasAttribute( 'href' ) ) {
3485                              href = href.replace( 'action=enable-auto-update', 'action=disable-auto-update' );
3486                              $toggler.attr( 'href', href );
3487                          }
3488                          $toggler.attr( 'data-wp-action', 'disable' );
3489  
3490                          $label.text( __( 'Disable auto-updates' ) );
3491                          $parent.find( '.auto-update-time' ).removeClass( 'hidden' );
3492                          wp.a11y.speak( __( 'Auto-updates enabled' ) );
3493                      } else {
3494                          // The toggler control can be either a link or a button.
3495                          if ( $toggler[ 0 ].hasAttribute( 'href' ) ) {
3496                              href = href.replace( 'action=disable-auto-update', 'action=enable-auto-update' );
3497                              $toggler.attr( 'href', href );
3498                          }
3499                          $toggler.attr( 'data-wp-action', 'enable' );
3500  
3501                          $label.text( __( 'Enable auto-updates' ) );
3502                          $parent.find( '.auto-update-time' ).addClass( 'hidden' );
3503                          wp.a11y.speak( __( 'Auto-updates disabled' ) );
3504                      }
3505  
3506                      $document.trigger( 'wp-auto-update-setting-changed', { state: action, type: type, asset: asset } );
3507                  } )
3508                  .fail( function() {
3509                      $parent.find( '.notice.notice-error' )
3510                          .removeClass( 'hidden' )
3511                          .find( 'p' )
3512                          .text( __( 'The request could not be completed.' ) );
3513  
3514                      wp.a11y.speak( __( 'The request could not be completed.' ), 'assertive' );
3515                  } )
3516                  .always( function() {
3517                      $toggler.removeAttr( 'data-doing-ajax' ).find( '.dashicons-update' ).addClass( 'hidden' );
3518                  } );
3519              }
3520          );
3521  
3522          /**
3523           * Click handler for page refresh link.
3524           *
3525           * @since 6.5.3
3526           *
3527           * @param {Event} event Event interface.
3528           */
3529          $document.on( 'click', '.refresh-page', function( event ) {
3530              event.preventDefault();
3531  
3532              if ( window.parent === window ) {
3533                  window.location.reload();
3534              } else {
3535                  window.parent.location.reload();
3536              }
3537          } );
3538      } );
3539  })( jQuery, window.wp, window._wpUpdatesSettings );


Generated : Fri May 10 08:20:01 2024 Cross-referenced by PHPXref