: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
// DecrementCount from update count.
if ( 'themes' === pagenow ) {
var theme = _.find( _wpThemeSettings.themes, { id: response.slug } );
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.
* @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' ),
/* translators: %s: Error string for a failed deletion. */
__( 'Deletion failed: %s' ),
$message = wp.updates.adminNotice( {
className: 'update-message notice-error notice-alt',
if ( wp.updates.maybeHandleCredentialError( response, 'delete-theme' ) ) {
if ( 'themes-network' === pagenow ) {
if ( ! $updateRow.length ) {
$themeRow.addClass( 'update' ).after(
colspan: $( '#bulk-action-form' ).find( 'thead th:not(.hidden), thead td' ).length,
// Remove previous error messages, if any.
$updateRow.find( '.notice-error' ).remove();
$updateRow.find( '.plugin-update' ).append( $message );
$( '.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.
* @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;
* Pulls available jobs from the queue and runs them.
* @since 4.6.0 Can handle multiple job types.
wp.updates.queueChecker = function() {
if ( wp.updates.ajaxLocked || ! wp.updates.queue.length ) {
job = wp.updates.queue.shift();
wp.updates.installPlugin( job.data );
wp.updates.updatePlugin( job.data );
wp.updates.deletePlugin( job.data );
wp.updates.installTheme( job.data );
wp.updates.updateTheme( job.data );
wp.updates.deleteTheme( job.data );
* Requests the users filesystem credentials if they aren't already known.
* @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.
* @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.
* @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' );
} else if ( 'hostname' === event.target.id && event.shiftKey ) {
$( '#upgrade' ).trigger( 'focus' );
* Opens the request for credentials modal.
wp.updates.requestForCredentialsModalOpen = function() {
var $modal = $( '#request-filesystem-credentials-dialog' );
$( 'body' ).addClass( 'modal-open' );
$modal.find( 'input:enabled:first' ).trigger( 'focus' );
$modal.on( 'keydown', wp.updates.keydown );
* Closes the request for credentials modal.
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.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 ) {
_.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.requestForCredentialsModalClose();
* Displays an error message in the request for credentials form.
* @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( '<div class="notice notice-alt notice-error" role="alert"><p>' + message + '</p></div>' );
* Handles credential errors and runs events that need to happen in that case.
* @param {Object} response Ajax response.
* @param {string} action The type of request to perform.
wp.updates.credentialError = function( response, action ) {
response = wp.updates._addCallbacks( response, action );
wp.updates.queue.unshift( {
* Not cool that we're depending on response for this data.
* This would feel more whole in a view all tied together.
wp.updates.filesystemCredentials.available = false;
wp.updates.showErrorInCredentialsForm( response.errorMessage );
wp.updates.requestFilesystemCredentials();
* Handles credentials errors if it could not connect to the filesystem.
* @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 );
* 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',
wp.updates.isValidResponse = function( response, action ) {
var error = __( 'Something went wrong.' ),
// Make sure the response is a valid data object and not a Promise object.
if ( _.isObject( response ) && ! _.isFunction( response.always ) ) {
if ( _.isString( response ) && '-1' === response ) {
error = __( 'An error has occurred. Please reload the page and try again.' );
} else if ( _.isString( 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;
/* translators: %s: Error string for a failed update. */
errorMessage = __( 'Update failed: %s' );
/* translators: %s: Error string for a failed installation. */
errorMessage = __( 'Installation failed: %s' );
case 'check-dependencies':
/* translators: %s: Error string for a failed dependencies check. */
errorMessage = __( 'Dependencies check failed: %s' );
/* translators: %s: Error string for a failed activation. */
errorMessage = __( 'Activation failed: %s' );
/* translators: %s: Error string for a failed deletion. */
errorMessage = __( 'Deletion failed: %s' );
// Messages are escaped, remove HTML tags to make them more readable.
error = error.replace( /<[\/a-z][^<>]*>/gi, '' );
errorMessage = errorMessage.replace( '%s', error );
wp.updates.addAdminNotice( {
className: 'notice-error is-dismissible',
message: _.escape( errorMessage )
// Remove the lock, and clear the queue.
wp.updates.ajaxLocked = false;
// 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' )
.removeAttr( 'aria-label' )
wp.a11y.speak( errorMessage, 'assertive' );
* 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.
wp.updates.beforeunload = function() {
if ( wp.updates.ajaxLocked ) {
return __( 'Updates may not complete if you navigate away from this page.' );
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 || {} );
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.
$filesystemModal.on( 'submit', 'form', function( event ) {
// 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.
$filesystemModal.on( 'click', '[data-js-action="close"], .notification-dialog-background', wp.updates.requestForCredentialsModalCancel );
* Hide SSH fields when not selected.
$filesystemForm.on( 'change', 'input[name="connection_type"]', function() {
$( '#ssh-keys' ).toggleClass( 'hidden', ( 'ssh' !== $( this ).val() ) );
* Handles events after the credential modal was closed.
* @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' ),
if ( 'import' === pagenow ) {
$updatingMessage.removeClass( 'updating-message' );
} else if ( 'plugins' === pagenow || 'plugins-network' === pagenow ) {