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