[ Index ]

PHP Cross Reference of WordPress

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


Generated: Sun May 31 01:00:03 2020 Cross-referenced by PHPXref 0.7.1