' )
.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 = __( 'Something went wrong.' ),
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( $( '