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


Generated : Thu Oct 24 08:20:01 2024 Cross-referenced by PHPXref