' )
						.attr( 'href', response.customizeUrl )
						.addClass( 'button load-customize' )
						.text( __( 'Live Preview' ) );
				} );
			}
		}, 1000 );
	};
	/**
	 * Updates the UI appropriately after a failed theme install.
	 *
	 * @since 4.6.0
	 *
	 * @param {Object} response              Response from the server.
	 * @param {string} response.slug         Slug of the theme to be installed.
	 * @param {string} response.errorCode    Error code for the error that occurred.
	 * @param {string} response.errorMessage The error that occurred.
	 */
	wp.updates.installThemeError = function( response ) {
		var $card, $button,
			errorMessage = sprintf(
				/* translators: %s: Error string for a failed installation. */
				__( 'Installation failed: %s' ),
				response.errorMessage
			),
			$message     = wp.updates.adminNotice( {
				className: 'update-message notice-error notice-alt',
				message:   errorMessage
			} );
		if ( ! wp.updates.isValidResponse( response, 'install' ) ) {
			return;
		}
		if ( wp.updates.maybeHandleCredentialError( response, 'install-theme' ) ) {
			return;
		}
		if ( 'customize' === pagenow ) {
			if ( $document.find( 'body' ).hasClass( 'modal-open' ) ) {
				$button = $( '.theme-install[data-slug="' + response.slug + '"]' );
				$card   = $( '.theme-overlay .theme-info' ).prepend( $message );
			} else {
				$button = $( '.theme-install[data-slug="' + response.slug + '"]' );
				$card   = $button.closest( '.theme' ).addClass( 'theme-install-failed' ).append( $message );
			}
			wp.customize.notifications.remove( 'theme_installing' );
		} else {
			if ( $document.find( 'body' ).hasClass( 'full-overlay-active' ) ) {
				$button = $( '.theme-install[data-slug="' + response.slug + '"]' );
				$card   = $( '.install-theme-info' ).prepend( $message );
			} else {
				$card   = $( '[data-slug="' + response.slug + '"]' ).removeClass( 'focus' ).addClass( 'theme-install-failed' ).append( $message );
				$button = $card.find( '.theme-install' );
			}
		}
		$button
			.removeClass( 'updating-message' )
			.attr(
				'aria-label',
				sprintf(
					/* translators: %s: Theme name and version. */
					_x( '%s installation failed', 'theme' ),
					$button.data( 'name' )
				)
			)
			.text( __( 'Installation failed.' ) );
		wp.a11y.speak( errorMessage, 'assertive' );
		$document.trigger( 'wp-theme-install-error', response );
	};
	/**
	 * Sends an Ajax request to the server to delete a theme.
	 *
	 * @since 4.6.0
	 *
	 * @param {Object}              args
	 * @param {string}              args.slug    Theme stylesheet.
	 * @param {deleteThemeSuccess=} args.success Optional. Success callback. Default: wp.updates.deleteThemeSuccess
	 * @param {deleteThemeError=}   args.error   Optional. Error callback. Default: wp.updates.deleteThemeError
	 * @return {$.promise} A jQuery promise that represents the request,
	 *                     decorated with an abort() method.
	 */
	wp.updates.deleteTheme = function( args ) {
		var $button;
		if ( 'themes' === pagenow ) {
			$button = $( '.theme-actions .delete-theme' );
		} else if ( 'themes-network' === pagenow ) {
			$button = $( '[data-slug="' + args.slug + '"]' ).find( '.row-actions a.delete' );
		}
		args = _.extend( {
			success: wp.updates.deleteThemeSuccess,
			error: wp.updates.deleteThemeError
		}, args );
		if ( $button && $button.html() !== __( 'Deleting...' ) ) {
			$button
				.data( 'originaltext', $button.html() )
				.text( __( 'Deleting...' ) );
		}
		wp.a11y.speak( __( 'Deleting...' ) );
		// Remove previous error messages, if any.
		$( '.theme-info .update-message' ).remove();
		$document.trigger( 'wp-theme-deleting', args );
		return wp.updates.ajax( 'delete-theme', args );
	};
	/**
	 * Updates the UI appropriately after a successful theme deletion.
	 *
	 * @since 4.6.0
	 *
	 * @param {Object} response      Response from the server.
	 * @param {string} response.slug Slug of the theme that was deleted.
	 */
	wp.updates.deleteThemeSuccess = function( response ) {
		var $themeRows = $( '[data-slug="' + response.slug + '"]' );
		if ( 'themes-network' === pagenow ) {
			// Removes the theme and updates rows.
			$themeRows.css( { backgroundColor: '#faafaa' } ).fadeOut( 350, function() {
				var $views     = $( '.subsubsub' ),
					$themeRow  = $( this ),
					themes     = settings.themes,
					deletedRow = wp.template( 'item-deleted-row' );
				if ( ! $themeRow.hasClass( 'plugin-update-tr' ) ) {
					$themeRow.after(
						deletedRow( {
							slug:    response.slug,
							colspan: $( '#bulk-action-form' ).find( 'thead th:not(.hidden), thead td' ).length,
							name:    $themeRow.find( '.theme-title strong' ).text()
						} )
					);
				}
				$themeRow.remove();
				// Remove theme from update count.
				if ( -1 !== _.indexOf( themes.upgrade, response.slug ) ) {
					themes.upgrade = _.without( themes.upgrade, response.slug );
					wp.updates.decrementCount( 'theme' );
				}
				// Remove from views.
				if ( -1 !== _.indexOf( themes.disabled, response.slug ) ) {
					themes.disabled = _.without( themes.disabled, response.slug );
					if ( themes.disabled.length ) {
						$views.find( '.disabled .count' ).text( '(' + themes.disabled.length + ')' );
					} else {
						$views.find( '.disabled' ).remove();
					}
				}
				if ( -1 !== _.indexOf( themes['auto-update-enabled'], response.slug ) ) {
					themes['auto-update-enabled'] = _.without( themes['auto-update-enabled'], response.slug );
					if ( themes['auto-update-enabled'].length ) {
						$views.find( '.auto-update-enabled .count' ).text( '(' + themes['auto-update-enabled'].length + ')' );
					} else {
						$views.find( '.auto-update-enabled' ).remove();
					}
				}
				if ( -1 !== _.indexOf( themes['auto-update-disabled'], response.slug ) ) {
					themes['auto-update-disabled'] = _.without( themes['auto-update-disabled'], response.slug );
					if ( themes['auto-update-disabled'].length ) {
						$views.find( '.auto-update-disabled .count' ).text( '(' + themes['auto-update-disabled'].length + ')' );
					} else {
						$views.find( '.auto-update-disabled' ).remove();
					}
				}
				themes.all = _.without( themes.all, response.slug );
				// There is always at least one theme available.
				$views.find( '.all .count' ).text( '(' + themes.all.length + ')' );
			} );
		}
		// DecrementCount from update count.
		if ( 'themes' === pagenow ) {
		    var theme = _.find( _wpThemeSettings.themes, { id: response.slug } );
		    if ( theme.hasUpdate ) {
		        wp.updates.decrementCount( 'theme' );
		    }
		}
		wp.a11y.speak( _x( 'Deleted!', 'theme' ) );
		$document.trigger( 'wp-theme-delete-success', response );
	};
	/**
	 * Updates the UI appropriately after a failed theme deletion.
	 *
	 * @since 4.6.0
	 *
	 * @param {Object} response              Response from the server.
	 * @param {string} response.slug         Slug of the theme to be deleted.
	 * @param {string} response.errorCode    Error code for the error that occurred.
	 * @param {string} response.errorMessage The error that occurred.
	 */
	wp.updates.deleteThemeError = function( response ) {
		var $themeRow    = $( 'tr.inactive[data-slug="' + response.slug + '"]' ),
			$button      = $( '.theme-actions .delete-theme' ),
			updateRow    = wp.template( 'item-update-row' ),
			$updateRow   = $themeRow.siblings( '#' + response.slug + '-update' ),
			errorMessage = sprintf(
				/* translators: %s: Error string for a failed deletion. */
				__( 'Deletion failed: %s' ),
				response.errorMessage
			),
			$message     = wp.updates.adminNotice( {
				className: 'update-message notice-error notice-alt',
				message:   errorMessage
			} );
		if ( wp.updates.maybeHandleCredentialError( response, 'delete-theme' ) ) {
			return;
		}
		if ( 'themes-network' === pagenow ) {
			if ( ! $updateRow.length ) {
				$themeRow.addClass( 'update' ).after(
					updateRow( {
						slug: response.slug,
						colspan: $( '#bulk-action-form' ).find( 'thead th:not(.hidden), thead td' ).length,
						content: $message
					} )
				);
			} else {
				// Remove previous error messages, if any.
				$updateRow.find( '.notice-error' ).remove();
				$updateRow.find( '.plugin-update' ).append( $message );
			}
		} else {
			$( '.theme-info .theme-description' ).before( $message );
		}
		$button.html( $button.data( 'originaltext' ) );
		wp.a11y.speak( errorMessage, 'assertive' );
		$document.trigger( 'wp-theme-delete-error', response );
	};
	/**
	 * Adds the appropriate callback based on the type of action and the current page.
	 *
	 * @since 4.6.0
	 * @private
	 *
	 * @param {Object} data   Ajax payload.
	 * @param {string} action The type of request to perform.
	 * @return {Object} The Ajax payload with the appropriate callbacks.
	 */
	wp.updates._addCallbacks = function( data, action ) {
		if ( 'import' === pagenow && 'install-plugin' === action ) {
			data.success = wp.updates.installImporterSuccess;
			data.error   = wp.updates.installImporterError;
		}
		return data;
	};
	/**
	 * Pulls available jobs from the queue and runs them.
	 *
	 * @since 4.2.0
	 * @since 4.6.0 Can handle multiple job types.
	 */
	wp.updates.queueChecker = function() {
		var job;
		if ( wp.updates.ajaxLocked || ! wp.updates.queue.length ) {
			return;
		}
		job = wp.updates.queue.shift();
		// Handle a queue job.
		switch ( job.action ) {
			case 'install-plugin':
				wp.updates.installPlugin( job.data );
				break;
			case 'update-plugin':
				wp.updates.updatePlugin( job.data );
				break;
			case 'delete-plugin':
				wp.updates.deletePlugin( job.data );
				break;
			case 'install-theme':
				wp.updates.installTheme( job.data );
				break;
			case 'update-theme':
				wp.updates.updateTheme( job.data );
				break;
			case 'delete-theme':
				wp.updates.deleteTheme( job.data );
				break;
			default:
				break;
		}
	};
	/**
	 * Requests the users filesystem credentials if they aren't already known.
	 *
	 * @since 4.2.0
	 *
	 * @param {Event=} event Optional. Event interface.
	 */
	wp.updates.requestFilesystemCredentials = function( event ) {
		if ( false === wp.updates.filesystemCredentials.available ) {
			/*
			 * After exiting the credentials request modal,
			 * return the focus to the element triggering the request.
			 */
			if ( event && ! wp.updates.$elToReturnFocusToFromCredentialsModal ) {
				wp.updates.$elToReturnFocusToFromCredentialsModal = $( event.target );
			}
			wp.updates.ajaxLocked = true;
			wp.updates.requestForCredentialsModalOpen();
		}
	};
	/**
	 * Requests the users filesystem credentials if needed and there is no lock.
	 *
	 * @since 4.6.0
	 *
	 * @param {Event=} event Optional. Event interface.
	 */
	wp.updates.maybeRequestFilesystemCredentials = function( event ) {
		if ( wp.updates.shouldRequestFilesystemCredentials && ! wp.updates.ajaxLocked ) {
			wp.updates.requestFilesystemCredentials( event );
		}
	};
	/**
	 * Keydown handler for the request for credentials modal.
	 *
	 * Closes the modal when the escape key is pressed and
	 * constrains keyboard navigation to inside the modal.
	 *
	 * @since 4.2.0
	 *
	 * @param {Event} event Event interface.
	 */
	wp.updates.keydown = function( event ) {
		if ( 27 === event.keyCode ) {
			wp.updates.requestForCredentialsModalCancel();
		} else if ( 9 === event.keyCode ) {
			// #upgrade button must always be the last focus-able element in the dialog.
			if ( 'upgrade' === event.target.id && ! event.shiftKey ) {
				$( '#hostname' ).trigger( 'focus' );
				event.preventDefault();
			} else if ( 'hostname' === event.target.id && event.shiftKey ) {
				$( '#upgrade' ).trigger( 'focus' );
				event.preventDefault();
			}
		}
	};
	/**
	 * Opens the request for credentials modal.
	 *
	 * @since 4.2.0
	 */
	wp.updates.requestForCredentialsModalOpen = function() {
		var $modal = $( '#request-filesystem-credentials-dialog' );
		$( 'body' ).addClass( 'modal-open' );
		$modal.show();
		$modal.find( 'input:enabled:first' ).trigger( 'focus' );
		$modal.on( 'keydown', wp.updates.keydown );
	};
	/**
	 * Closes the request for credentials modal.
	 *
	 * @since 4.2.0
	 */
	wp.updates.requestForCredentialsModalClose = function() {
		$( '#request-filesystem-credentials-dialog' ).hide();
		$( 'body' ).removeClass( 'modal-open' );
		if ( wp.updates.$elToReturnFocusToFromCredentialsModal ) {
			wp.updates.$elToReturnFocusToFromCredentialsModal.trigger( 'focus' );
		}
	};
	/**
	 * Takes care of the steps that need to happen when the modal is canceled out.
	 *
	 * @since 4.2.0
	 * @since 4.6.0 Triggers an event for callbacks to listen to and add their actions.
	 */
	wp.updates.requestForCredentialsModalCancel = function() {
		// Not ajaxLocked and no queue means we already have cleared things up.
		if ( ! wp.updates.ajaxLocked && ! wp.updates.queue.length ) {
			return;
		}
		_.each( wp.updates.queue, function( job ) {
			$document.trigger( 'credential-modal-cancel', job );
		} );
		// Remove the lock, and clear the queue.
		wp.updates.ajaxLocked = false;
		wp.updates.queue = [];
		wp.updates.requestForCredentialsModalClose();
	};
	/**
	 * Displays an error message in the request for credentials form.
	 *
	 * @since 4.2.0
	 *
	 * @param {string} message Error message.
	 */
	wp.updates.showErrorInCredentialsForm = function( message ) {
		var $filesystemForm = $( '#request-filesystem-credentials-form' );
		// Remove any existing error.
		$filesystemForm.find( '.notice' ).remove();
		$filesystemForm.find( '#request-filesystem-credentials-title' ).after( '' );
	};
	/**
	 * Handles credential errors and runs events that need to happen in that case.
	 *
	 * @since 4.2.0
	 *
	 * @param {Object} response Ajax response.
	 * @param {string} action   The type of request to perform.
	 */
	wp.updates.credentialError = function( response, action ) {
		// Restore callbacks.
		response = wp.updates._addCallbacks( response, action );
		wp.updates.queue.unshift( {
			action: action,
			/*
			 * Not cool that we're depending on response for this data.
			 * This would feel more whole in a view all tied together.
			 */
			data: response
		} );
		wp.updates.filesystemCredentials.available = false;
		wp.updates.showErrorInCredentialsForm( response.errorMessage );
		wp.updates.requestFilesystemCredentials();
	};
	/**
	 * Handles credentials errors if it could not connect to the filesystem.
	 *
	 * @since 4.6.0
	 *
	 * @param {Object} response              Response from the server.
	 * @param {string} response.errorCode    Error code for the error that occurred.
	 * @param {string} response.errorMessage The error that occurred.
	 * @param {string} action                The type of request to perform.
	 * @return {boolean} Whether there is an error that needs to be handled or not.
	 */
	wp.updates.maybeHandleCredentialError = function( response, action ) {
		if ( wp.updates.shouldRequestFilesystemCredentials && response.errorCode && 'unable_to_connect_to_filesystem' === response.errorCode ) {
			wp.updates.credentialError( response, action );
			return true;
		}
		return false;
	};
	/**
	 * Validates an Ajax response to ensure it's a proper object.
	 *
	 * If the response deems to be invalid, an admin notice is being displayed.
	 *
	 * @param {(Object|string)} response              Response from the server.
	 * @param {function=}       response.always       Optional. Callback for when the Deferred is resolved or rejected.
	 * @param {string=}         response.statusText   Optional. Status message corresponding to the status code.
	 * @param {string=}         response.responseText Optional. Request response as text.
	 * @param {string}          action                Type of action the response is referring to. Can be 'delete',
	 *                                                'update' or 'install'.
	 */
	wp.updates.isValidResponse = function( response, action ) {
		var error = __( 'An error occurred during the update process. Please try again.' ),
			errorMessage;
		// Make sure the response is a valid data object and not a Promise object.
		if ( _.isObject( response ) && ! _.isFunction( response.always ) ) {
			return true;
		}
		if ( _.isString( response ) && '-1' === response ) {
			error = __( 'An error has occurred. Please reload the page and try again.' );
		} else if ( _.isString( response ) ) {
			error = response;
		} else if ( 'undefined' !== typeof response.readyState && 0 === response.readyState ) {
			error = __( 'Connection lost or the server is busy. Please try again later.' );
		} else if ( _.isString( response.responseText ) && '' !== response.responseText ) {
			error = response.responseText;
		} else if ( _.isString( response.statusText ) ) {
			error = response.statusText;
		}
		switch ( action ) {
			case 'update':
				/* translators: %s: Error string for a failed update. */
				errorMessage = __( 'Update failed: %s' );
				break;
			case 'install':
				/* translators: %s: Error string for a failed installation. */
				errorMessage = __( 'Installation failed: %s' );
				break;
			case 'check-dependencies':
				/* translators: %s: Error string for a failed dependencies check. */
				errorMessage = __( 'Dependencies check failed: %s' );
				break;
			case 'activate':
				/* translators: %s: Error string for a failed activation. */
				errorMessage = __( 'Activation failed: %s' );
				break;
			case 'delete':
				/* translators: %s: Error string for a failed deletion. */
				errorMessage = __( 'Deletion failed: %s' );
				break;
		}
		// Messages are escaped, remove HTML tags to make them more readable.
		error = error.replace( /<[\/a-z][^<>]*>/gi, '' );
		errorMessage = errorMessage.replace( '%s', error );
		// Add admin notice.
		wp.updates.addAdminNotice( {
			id:        'unknown_error',
			className: 'notice-error is-dismissible',
			message:   _.escape( errorMessage )
		} );
		// Remove the lock, and clear the queue.
		wp.updates.ajaxLocked = false;
		wp.updates.queue      = [];
		// Change buttons of all running updates.
		$( '.button.updating-message' )
			.removeClass( 'updating-message' )
			.removeAttr( 'aria-label' )
			.prop( 'disabled', true )
			.text( __( 'Update failed.' ) );
		$( '.updating-message:not(.button):not(.thickbox)' )
			.removeClass( 'updating-message notice-warning' )
			.addClass( 'notice-error' )
			.find( 'p' )
				.removeAttr( 'aria-label' )
				.text( errorMessage );
		wp.a11y.speak( errorMessage, 'assertive' );
		return false;
	};
	/**
	 * Potentially adds an AYS to a user attempting to leave the page.
	 *
	 * If an update is on-going and a user attempts to leave the page,
	 * opens an "Are you sure?" alert.
	 *
	 * @since 4.2.0
	 */
	wp.updates.beforeunload = function() {
		if ( wp.updates.ajaxLocked ) {
			return __( 'Updates may not complete if you navigate away from this page.' );
		}
	};
	$( function() {
		var $pluginFilter        = $( '#plugin-filter, #plugin-information-footer' ),
			$bulkActionForm      = $( '#bulk-action-form' ),
			$filesystemForm      = $( '#request-filesystem-credentials-form' ),
			$filesystemModal     = $( '#request-filesystem-credentials-dialog' ),
			$pluginSearch        = $( '.plugins-php .wp-filter-search' ),
			$pluginInstallSearch = $( '.plugin-install-php .wp-filter-search' );
		settings = _.extend( settings, window._wpUpdatesItemCounts || {} );
		if ( settings.totals ) {
			wp.updates.refreshCount();
		}
		/*
		 * Whether a user needs to submit filesystem credentials.
		 *
		 * This is based on whether the form was output on the page server-side.
		 *
		 * @see {wp_print_request_filesystem_credentials_modal() in PHP}
		 */
		wp.updates.shouldRequestFilesystemCredentials = $filesystemModal.length > 0;
		/**
		 * File system credentials form submit noop-er / handler.
		 *
		 * @since 4.2.0
		 */
		$filesystemModal.on( 'submit', 'form', function( event ) {
			event.preventDefault();
			// Persist the credentials input by the user for the duration of the page load.
			wp.updates.filesystemCredentials.ftp.hostname       = $( '#hostname' ).val();
			wp.updates.filesystemCredentials.ftp.username       = $( '#username' ).val();
			wp.updates.filesystemCredentials.ftp.password       = $( '#password' ).val();
			wp.updates.filesystemCredentials.ftp.connectionType = $( 'input[name="connection_type"]:checked' ).val();
			wp.updates.filesystemCredentials.ssh.publicKey      = $( '#public_key' ).val();
			wp.updates.filesystemCredentials.ssh.privateKey     = $( '#private_key' ).val();
			wp.updates.filesystemCredentials.fsNonce            = $( '#_fs_nonce' ).val();
			wp.updates.filesystemCredentials.available          = true;
			// Unlock and invoke the queue.
			wp.updates.ajaxLocked = false;
			wp.updates.queueChecker();
			wp.updates.requestForCredentialsModalClose();
		} );
		/**
		 * Closes the request credentials modal when clicking the 'Cancel' button or outside of the modal.
		 *
		 * @since 4.2.0
		 */
		$filesystemModal.on( 'click', '[data-js-action="close"], .notification-dialog-background', wp.updates.requestForCredentialsModalCancel );
		/**
		 * Hide SSH fields when not selected.
		 *
		 * @since 4.2.0
		 */
		$filesystemForm.on( 'change', 'input[name="connection_type"]', function() {
			$( '#ssh-keys' ).toggleClass( 'hidden', ( 'ssh' !== $( this ).val() ) );
		} ).trigger( 'change' );
		/**
		 * Handles events after the credential modal was closed.
		 *
		 * @since 4.6.0
		 *
		 * @param {Event}  event Event interface.
		 * @param {string} job   The install/update.delete request.
		 */
		$document.on( 'credential-modal-cancel', function( event, job ) {
			var $updatingMessage = $( '.updating-message' ),
				$message, originalText;
			if ( 'import' === pagenow ) {
				$updatingMessage.removeClass( 'updating-message' );
			} else if ( 'plugins' === pagenow || 'plugins-network' === pagenow ) {
				if ( 'update-plugin' === job.action ) {
					$message = $( 'tr[data-plugin="' + job.data.plugin + '"]' ).find( '.update-message' );
				} else if ( 'delete-plugin' === job.action ) {
					$message = $( '[data-plugin="' + job.data.plugin + '"]' ).find( '.row-actions a.delete' );
				}
			} else if ( 'themes' === pagenow || 'themes-network' === pagenow ) {
				if ( 'update-theme' === job.action ) {
					$message = $( '[data-slug="' + job.data.slug + '"]' ).find( '.update-message' );
				} else if ( 'delete-theme' === job.action && 'themes-network' === pagenow ) {
					$message = $( '[data-slug="' + job.data.slug + '"]' ).find( '.row-actions a.delete' );
				} else if ( 'delete-theme' === job.action && 'themes' === pagenow ) {
					$message = $( '.theme-actions .delete-theme' );
				}
			} else {
				$message = $updatingMessage;
			}
			if ( $message && $message.hasClass( 'updating-message' ) ) {
				originalText = $message.data( 'originaltext' );
				if ( 'undefined' === typeof originalText ) {
					originalText = $( '' ).html( $message.find( 'p' ).data( 'originaltext' ) );
				}
				$message
					.removeClass( 'updating-message' )
					.html( originalText );
				if ( 'plugin-install' === pagenow || 'plugin-install-network' === pagenow ) {
					if ( 'update-plugin' === job.action ) {
						$message.attr(
							'aria-label',
							sprintf(
								/* translators: %s: Plugin name and version. */
								_x( 'Update %s now', 'plugin' ),
								$message.data( 'name' )
							)
						);
					} else if ( 'install-plugin' === job.action ) {
						$message.attr(
							'aria-label',
							sprintf(
								/* translators: %s: Plugin name. */
								_x( 'Install %s now', 'plugin' ),
								$message.data( 'name' )
							)
						);
					}
				}
			}
			wp.a11y.speak( __( 'Update canceled.' ) );
		} );
		/**
		 * Click handler for plugin updates in List Table view.
		 *
		 * @since 4.2.0
		 *
		 * @param {Event} event Event interface.
		 */
		$bulkActionForm.on( 'click', '[data-plugin] .update-link', function( event ) {
			var $message   = $( event.target ),
				$pluginRow = $message.parents( 'tr' );
			event.preventDefault();
			if ( $message.hasClass( 'updating-message' ) || $message.hasClass( 'button-disabled' ) ) {
				return;
			}
			wp.updates.maybeRequestFilesystemCredentials( event );
			// Return the user to the input box of the plugin's table row after closing the modal.
			wp.updates.$elToReturnFocusToFromCredentialsModal = $pluginRow.find( '.check-column input' );
			wp.updates.updatePlugin( {
				plugin: $pluginRow.data( 'plugin' ),
				slug:   $pluginRow.data( 'slug' )
			} );
		} );
		/**
		 * Click handler for plugin updates in plugin install view.
		 *
		 * @since 4.2.0
		 *
		 * @param {Event} event Event interface.
		 */
		$pluginFilter.on( 'click', '.update-now', function( event ) {
			var $button = $( event.target );
			event.preventDefault();
			if ( $button.hasClass( 'updating-message' ) || $button.hasClass( 'button-disabled' ) ) {
				return;
			}
			wp.updates.maybeRequestFilesystemCredentials( event );
			wp.updates.updatePlugin( {
				plugin: $button.data( 'plugin' ),
				slug:   $button.data( 'slug' )
			} );
		} );
		/**
		 * Click handler for plugin installs in plugin install view.
		 *
		 * @since 4.6.0
		 *
		 * @param {Event} event Event interface.
		 */
		$pluginFilter.on( 'click', '.install-now', function( event ) {
			var $button = $( event.target );
			event.preventDefault();
			if ( $button.hasClass( 'updating-message' ) || $button.hasClass( 'button-disabled' ) ) {
				return;
			}
			if ( wp.updates.shouldRequestFilesystemCredentials && ! wp.updates.ajaxLocked ) {
				wp.updates.requestFilesystemCredentials( event );
				$document.on( 'credential-modal-cancel', function() {
					var $message = $( '.install-now.updating-message' );
					$message
						.removeClass( 'updating-message' )
						.text( _x( 'Install Now', 'plugin' ) );
					wp.a11y.speak( __( 'Update canceled.' ) );
				} );
			}
			wp.updates.installPlugin( {
				slug: $button.data( 'slug' )
			} );
		} );
		/**
		 * Click handler for plugin activations in plugin activation modal view.
		 *
		 * @since 6.5.0
		 * @since 6.5.4 Redirect the parent window to the activation URL.
		 *
		 * @param {Event} event Event interface.
		 */
		$document.on( 'click', '#plugin-information-footer .activate-now', function( event ) {
			event.preventDefault();
			window.parent.location.href = $( event.target ).attr( 'href' );
		});
		/**
		 * Click handler for importer plugins installs in the Import screen.
		 *
		 * @since 4.6.0
		 *
		 * @param {Event} event Event interface.
		 */
		$document.on( 'click', '.importer-item .install-now', function( event ) {
			var $button = $( event.target ),
				pluginName = $( this ).data( 'name' );
			event.preventDefault();
			if ( $button.hasClass( 'updating-message' ) ) {
				return;
			}
			if ( wp.updates.shouldRequestFilesystemCredentials && ! wp.updates.ajaxLocked ) {
				wp.updates.requestFilesystemCredentials( event );
				$document.on( 'credential-modal-cancel', function() {
					$button
						.removeClass( 'updating-message' )
						.attr(
							'aria-label',
							sprintf(
								/* translators: %s: Plugin name. */
								_x( 'Install %s now', 'plugin' ),
								pluginName
							)
						)
						.text( _x( 'Install Now', 'plugin' ) );
					wp.a11y.speak( __( 'Update canceled.' ) );
				} );
			}
			wp.updates.installPlugin( {
				slug:    $button.data( 'slug' ),
				pagenow: pagenow,
				success: wp.updates.installImporterSuccess,
				error:   wp.updates.installImporterError
			} );
		} );
		/**
		 * Click handler for plugin deletions.
		 *
		 * @since 4.6.0
		 *
		 * @param {Event} event Event interface.
		 */
		$bulkActionForm.on( 'click', '[data-plugin] a.delete', function( event ) {
			var $pluginRow = $( event.target ).parents( 'tr' ),
				confirmMessage;
			if ( $pluginRow.hasClass( 'is-uninstallable' ) ) {
				confirmMessage = sprintf(
					/* translators: %s: Plugin name. */
					__( 'Are you sure you want to delete %s and its data?' ),
					$pluginRow.find( '.plugin-title strong' ).text()
				);
			} else {
				confirmMessage = sprintf(
					/* translators: %s: Plugin name. */
					__( 'Are you sure you want to delete %s?' ),
					$pluginRow.find( '.plugin-title strong' ).text()
				);
			}
			event.preventDefault();
			if ( ! window.confirm( confirmMessage ) ) {
				return;
			}
			wp.updates.maybeRequestFilesystemCredentials( event );
			wp.updates.deletePlugin( {
				plugin: $pluginRow.data( 'plugin' ),
				slug:   $pluginRow.data( 'slug' )
			} );
		} );
		/**
		 * Click handler for theme updates.
		 *
		 * @since 4.6.0
		 *
		 * @param {Event} event Event interface.
		 */
		$document.on( 'click', '.themes-php.network-admin .update-link', function( event ) {
			var $message  = $( event.target ),
				$themeRow = $message.parents( 'tr' );
			event.preventDefault();
			if ( $message.hasClass( 'updating-message' ) || $message.hasClass( 'button-disabled' ) ) {
				return;
			}
			wp.updates.maybeRequestFilesystemCredentials( event );
			// Return the user to the input box of the theme's table row after closing the modal.
			wp.updates.$elToReturnFocusToFromCredentialsModal = $themeRow.find( '.check-column input' );
			wp.updates.updateTheme( {
				slug: $themeRow.data( 'slug' )
			} );
		} );
		/**
		 * Click handler for theme deletions.
		 *
		 * @since 4.6.0
		 *
		 * @param {Event} event Event interface.
		 */
		$document.on( 'click', '.themes-php.network-admin a.delete', function( event ) {
			var $themeRow = $( event.target ).parents( 'tr' ),
				confirmMessage = sprintf(
					/* translators: %s: Theme name. */
					__( 'Are you sure you want to delete %s?' ),
					$themeRow.find( '.theme-title strong' ).text()
				);
			event.preventDefault();
			if ( ! window.confirm( confirmMessage ) ) {
				return;
			}
			wp.updates.maybeRequestFilesystemCredentials( event );
			wp.updates.deleteTheme( {
				slug: $themeRow.data( 'slug' )
			} );
		} );
		/**
		 * Bulk action handler for plugins and themes.
		 *
		 * Handles both deletions and updates.
		 *
		 * @since 4.6.0
		 *
		 * @param {Event} event Event interface.
		 */
		$bulkActionForm.on( 'click', '[type="submit"]:not([name="clear-recent-list"])', function( event ) {
			var bulkAction    = $( event.target ).siblings( 'select' ).val(),
				itemsSelected = $bulkActionForm.find( 'input[name="checked[]"]:checked' ),
				success       = 0,
				error         = 0,
				errorMessages = [],
				type, action;
			// Determine which type of item we're dealing with.
			switch ( pagenow ) {
				case 'plugins':
				case 'plugins-network':
					type = 'plugin';
					break;
				case 'themes-network':
					type = 'theme';
					break;
				default:
					return;
			}
			// Bail if there were no items selected.
			if ( ! itemsSelected.length ) {
				bulkAction = false;
			}
			// Determine the type of request we're dealing with.
			switch ( bulkAction ) {
				case 'update-selected':
					action = bulkAction.replace( 'selected', type );
					break;
				case 'delete-selected':
					var confirmMessage = 'plugin' === type ?
						__( 'Are you sure you want to delete the selected plugins and their data?' ) :
						__( 'Caution: These themes may be active on other sites in the network. Are you sure you want to proceed?' );
					if ( ! window.confirm( confirmMessage ) ) {
						event.preventDefault();
						return;
					}
					action = bulkAction.replace( 'selected', type );
					break;
				default:
					return;
			}
			wp.updates.maybeRequestFilesystemCredentials( event );
			event.preventDefault();
			// Un-check the bulk checkboxes.
			$bulkActionForm.find( '.manage-column [type="checkbox"]' ).prop( 'checked', false );
			$document.trigger( 'wp-' + type + '-bulk-' + bulkAction, itemsSelected );
			// Find all the checkboxes which have been checked.
			itemsSelected.each( function( index, element ) {
				var $checkbox = $( element ),
					$itemRow = $checkbox.parents( 'tr' );
				// Only add update-able items to the update queue.
				if ( 'update-selected' === bulkAction && ( ! $itemRow.hasClass( 'update' ) || $itemRow.find( 'notice-error' ).length ) ) {
					// Un-check the box.
					$checkbox.prop( 'checked', false );
					return;
				}
				// Don't add items to the update queue again, even if the user clicks the update button several times.
				if ( 'update-selected' === bulkAction && $itemRow.hasClass( 'is-enqueued' ) ) {
					return;
				}
				$itemRow.addClass( 'is-enqueued' );
				// Add it to the queue.
				wp.updates.queue.push( {
					action: action,
					data:   {
						plugin: $itemRow.data( 'plugin' ),
						slug:   $itemRow.data( 'slug' )
					}
				} );
			} );
			// Display bulk notification for updates of any kind.
			$document.on( 'wp-plugin-update-success wp-plugin-update-error wp-theme-update-success wp-theme-update-error', function( event, response ) {
				var $itemRow = $( '[data-slug="' + response.slug + '"]' ),
					$bulkActionNotice, itemName;
				if ( 'wp-' + response.update + '-update-success' === event.type ) {
					success++;
				} else {
					itemName = response.pluginName ? response.pluginName : $itemRow.find( '.column-primary strong' ).text();
					error++;
					errorMessages.push( itemName + ': ' + response.errorMessage );
				}
				$itemRow.find( 'input[name="checked[]"]:checked' ).prop( 'checked', false );
				wp.updates.adminNotice = wp.template( 'wp-bulk-updates-admin-notice' );
				var successMessage = null;
				if ( success ) {
					if ( 'plugin' === response.update ) {
						successMessage = sprintf(
							/* translators: %s: Number of plugins. */
							_n( '%s plugin successfully updated.', '%s plugins successfully updated.', success ),
							success
						);
					} else {
						successMessage = sprintf(
							/* translators: %s: Number of themes. */
							_n( '%s theme successfully updated.', '%s themes successfully updated.', success ),
							success
						);
					}
				}
				var errorMessage = null;
				if ( error ) {
					errorMessage = sprintf(
						/* translators: %s: Number of failed updates. */
						_n( '%s update failed.', '%s updates failed.', error ),
						error
					);
				}
				wp.updates.addAdminNotice( {
					id:            'bulk-action-notice',
					className:     'bulk-action-notice',
					successMessage: successMessage,
					errorMessage:   errorMessage,
					errorMessages:  errorMessages,
					type:           response.update
				} );
				$bulkActionNotice = $( '#bulk-action-notice' ).on( 'click', 'button', function() {
					// $( this ) is the clicked button, no need to get it again.
					$( this )
						.toggleClass( 'bulk-action-errors-collapsed' )
						.attr( 'aria-expanded', ! $( this ).hasClass( 'bulk-action-errors-collapsed' ) );
					// Show the errors list.
					$bulkActionNotice.find( '.bulk-action-errors' ).toggleClass( 'hidden' );
				} );
				if ( error > 0 && ! wp.updates.queue.length ) {
					$( 'html, body' ).animate( { scrollTop: 0 } );
				}
			} );
			// Reset admin notice template after #bulk-action-notice was added.
			$document.on( 'wp-updates-notice-added', function() {
				wp.updates.adminNotice = wp.template( 'wp-updates-admin-notice' );
			} );
			// Check the queue, now that the event handlers have been added.
			wp.updates.queueChecker();
		} );
		if ( $pluginInstallSearch.length ) {
			$pluginInstallSearch.attr( 'aria-describedby', 'live-search-desc' );
		}
		// Track the previous search string length.
		var previousSearchStringLength = 0;
		wp.updates.shouldSearch = function( searchStringLength ) {
			var shouldSearch = searchStringLength >= wp.updates.searchMinCharacters ||
				previousSearchStringLength > wp.updates.searchMinCharacters;
			previousSearchStringLength = searchStringLength;
			return shouldSearch;
		};
		/**
		 * Handles changes to the plugin search box on the new-plugin page,
		 * searching the repository dynamically.
		 *
		 * @since 4.6.0
		 */
		$pluginInstallSearch.on( 'keyup input', _.debounce( function( event, eventtype ) {
			var $searchTab = $( '.plugin-install-search' ), data, searchLocation,
				searchStringLength = $pluginInstallSearch.val().length;
			data = {
				_ajax_nonce: wp.updates.ajaxNonce,
				s:           encodeURIComponent( event.target.value ),
				tab:         'search',
				type:        $( '#typeselector' ).val(),
				pagenow:     pagenow
			};
			searchLocation = location.href.split( '?' )[ 0 ] + '?' + $.param( _.omit( data, [ '_ajax_nonce', 'pagenow' ] ) );
			// Set the autocomplete attribute, turning off autocomplete 1 character before ajax search kicks in.
			if ( wp.updates.shouldSearch( searchStringLength ) ) {
				$pluginInstallSearch.attr( 'autocomplete', 'off' );
			} else {
				$pluginInstallSearch.attr( 'autocomplete', 'on' );
				return;
			}
			// Clear on escape.
			if ( 'keyup' === event.type && 27 === event.which ) {
				event.target.value = '';
			}
			if ( wp.updates.searchTerm === data.s && 'typechange' !== eventtype ) {
				return;
			} else {
				$pluginFilter.empty();
				wp.updates.searchTerm = data.s;
			}
			if ( window.history && window.history.replaceState ) {
				window.history.replaceState( null, '', searchLocation );
			}
			if ( ! $searchTab.length ) {
				$searchTab = $( '
' )
					.append( $( '