14
wp.updates.ajaxNonce = window._wpUpdatesSettings.ajax_nonce;
50
wp.updates.ajaxNonce = settings.ajax_nonce;
17
53
* Localized strings.
23
wp.updates.l10n = window._wpUpdatesSettings.l10n;
59
wp.updates.l10n = settings.l10n;
62
* Current search term.
68
wp.updates.searchTerm = '';
26
71
* Whether filesystem credentials need to be requested from the user.
32
wp.updates.shouldRequestFilesystemCredentials = null;
77
wp.updates.shouldRequestFilesystemCredentials = false;
35
80
* Filesystem credentials to be packaged along with the request.
83
* @since 4.6.0 Added `available` property to indicate whether credentials have been provided.
85
* @type {object} filesystemCredentials Holds filesystem credentials.
86
* @type {object} filesystemCredentials.ftp Holds FTP credentials.
87
* @type {string} filesystemCredentials.ftp.host FTP host. Default empty string.
88
* @type {string} filesystemCredentials.ftp.username FTP user name. Default empty string.
89
* @type {string} filesystemCredentials.ftp.password FTP password. Default empty string.
90
* @type {string} filesystemCredentials.ftp.connectionType Type of FTP connection. 'ssh', 'ftp', or 'ftps'.
91
* Default empty string.
92
* @type {object} filesystemCredentials.ssh Holds SSH credentials.
93
* @type {string} filesystemCredentials.ssh.publicKey The public key. Default empty string.
94
* @type {string} filesystemCredentials.ssh.privateKey The private key. Default empty string.
95
* @type {bool} filesystemCredentials.available Whether filesystem credentials have been provided.
41
98
wp.updates.filesystemCredentials = {
55
* Flag if we're waiting for an update to complete.
61
wp.updates.updateLock = false;
64
* * Flag if we've done an update successfully.
70
wp.updates.updateDoneSuccessfully = false;
113
* Whether we're waiting for an Ajax request to complete.
116
* @since 4.6.0 More accurately named `ajaxLocked`.
120
wp.updates.ajaxLocked = false;
123
* Admin notice template.
127
* @type {function} A function that lazily-compiles the template requested.
129
wp.updates.adminNotice = wp.template( 'wp-updates-admin-notice' );
73
134
* If the user tries to update a plugin while an update is
74
135
* already happening, it can be placed in this queue to perform later.
138
* @since 4.6.0 More accurately named `queue`.
140
* @type {Array.object}
80
wp.updates.updateQueue = [];
142
wp.updates.queue = [];
83
* Store a jQuery reference to return focus to when exiting the request credentials modal.
145
* Holds a jQuery reference to return focus to when exiting the request credentials modal.
89
wp.updates.$elToReturnFocusToFromCredentialsModal = null;
92
* Decrement update counts throughout the various menus.
151
wp.updates.$elToReturnFocusToFromCredentialsModal = undefined;
154
* Adds or updates an admin notice.
158
* @param {object} data
159
* @param {*=} data.selector Optional. Selector of an element to be replaced with the admin notice.
160
* @param {string=} data.id Optional. Unique id that will be used as the notice's id attribute.
161
* @param {string=} data.className Optional. Class names that will be used in the admin notice.
162
* @param {string=} data.message Optional. The message displayed in the notice.
163
* @param {number=} data.successes Optional. The amount of successful operations.
164
* @param {number=} data.errors Optional. The amount of failed operations.
165
* @param {Array=} data.errorMessages Optional. Error messages of failed operations.
168
wp.updates.addAdminNotice = function( data ) {
169
var $notice = $( data.selector ), $adminNotice;
171
delete data.selector;
172
$adminNotice = wp.updates.adminNotice( data );
174
// Check if this admin notice already exists.
175
if ( ! $notice.length ) {
176
$notice = $( '#' + data.id );
179
if ( $notice.length ) {
180
$notice.replaceWith( $adminNotice );
182
$( '.wrap' ).find( '> h1' ).after( $adminNotice );
185
$document.trigger( 'wp-updates-notice-added' );
189
* Handles Ajax requests to WordPress.
193
* @param {string} action The type of Ajax request ('update-plugin', 'install-theme', etc).
194
* @param {object} data Data that needs to be passed to the ajax callback.
195
* @return {$.promise} A jQuery promise that represents the request,
196
* decorated with an abort() method.
198
wp.updates.ajax = function( action, data ) {
201
if ( wp.updates.ajaxLocked ) {
202
wp.updates.queue.push( {
207
// Return a Deferred object so callbacks can always be registered.
211
wp.updates.ajaxLocked = true;
213
if ( data.success ) {
214
options.success = data.success;
219
options.error = data.error;
223
options.data = _.extend( data, {
225
_ajax_nonce: wp.updates.ajaxNonce,
226
username: wp.updates.filesystemCredentials.ftp.username,
227
password: wp.updates.filesystemCredentials.ftp.password,
228
hostname: wp.updates.filesystemCredentials.ftp.hostname,
229
connection_type: wp.updates.filesystemCredentials.ftp.connectionType,
230
public_key: wp.updates.filesystemCredentials.ssh.publicKey,
231
private_key: wp.updates.filesystemCredentials.ssh.privateKey
234
return wp.ajax.send( options ).always( wp.updates.ajaxAlways );
238
* Actions performed after every Ajax request.
242
* @param {object} response
243
* @param {array=} response.debug Optional. Debug information.
244
* @param {string=} response.errorCode Optional. Error code for an error that occurred.
246
wp.updates.ajaxAlways = function( response ) {
247
if ( ! response.errorCode || 'unable_to_connect_to_filesystem' !== response.errorCode ) {
248
wp.updates.ajaxLocked = false;
249
wp.updates.queueChecker();
252
if ( 'undefined' !== typeof response.debug && window.console && window.console.log ) {
253
_.map( response.debug, function( message ) {
254
window.console.log( $( '<p />' ).html( message ).text() );
260
* Decrements the update counts throughout the various menus.
262
* This includes the toolbar, the "Updates" menu item and the menu items
263
* for plugins and themes.
96
* @param {string} upgradeType
267
* @param {string} type The type of item that was updated or deleted.
268
* Can be 'plugin', 'theme'.
98
wp.updates.decrementCount = function( upgradeType ) {
101
$adminBarUpdateCount = $( '#wp-admin-bar-updates .ab-label' ),
270
wp.updates.decrementCount = function( type ) {
271
var $adminBarUpdates = $( '#wp-admin-bar-updates' ),
102
272
$dashboardNavMenuUpdateCount = $( 'a[href="update-core.php"] .update-plugins' ),
103
$pluginsMenuItem = $( '#menu-plugins' );
106
count = $adminBarUpdateCount.text();
273
count = $adminBarUpdates.find( '.ab-label' ).text(),
274
$menuItem, $itemCount, itemCount;
107
276
count = parseInt( count, 10 ) - 1;
108
278
if ( count < 0 || isNaN( count ) ) {
111
$( '#wp-admin-bar-updates .ab-item' ).removeAttr( 'title' );
112
$adminBarUpdateCount.text( count );
115
$dashboardNavMenuUpdateCount.each( function( index, elem ) {
116
elem.className = elem.className.replace( /count-\d+/, 'count-' + count );
282
$adminBarUpdates.find( '.ab-item' ).removeAttr( 'title' );
283
$adminBarUpdates.find( '.ab-label' ).text( count );
285
// Remove the update count from the toolbar if it's zero.
287
$adminBarUpdates.find( '.ab-label' ).parents( 'li' ).remove();
290
// Update the "Updates" menu item.
291
$dashboardNavMenuUpdateCount.each( function( index, element ) {
292
element.className = element.className.replace( /count-\d+/, 'count-' + count );
118
295
$dashboardNavMenuUpdateCount.removeAttr( 'title' );
119
296
$dashboardNavMenuUpdateCount.find( '.update-count' ).text( count );
121
if ( 'plugin' === upgradeType ) {
122
pluginCount = $pluginsMenuItem.find( '.plugin-count' ).eq(0).text();
123
pluginCount = parseInt( pluginCount, 10 ) - 1;
124
if ( pluginCount < 0 || isNaN( pluginCount ) ) {
127
$pluginsMenuItem.find( '.plugin-count' ).text( pluginCount );
128
$pluginsMenuItem.find( '.update-plugins' ).each( function( index, elem ) {
129
elem.className = elem.className.replace( /count-\d+/, 'count-' + pluginCount );
298
if ( 'plugin' === type ) {
299
$menuItem = $( '#menu-plugins' );
300
$itemCount = $menuItem.find( '.plugin-count' );
301
} else if ( 'theme' === type ) {
302
$menuItem = $( '#menu-appearance' );
303
$itemCount = $menuItem.find( '.theme-count' );
306
// Decrement the counter of the other menu items.
308
itemCount = $itemCount.eq( 0 ).text();
309
itemCount = parseInt( itemCount, 10 ) - 1;
312
if ( itemCount < 0 || isNaN( itemCount ) ) {
316
if ( itemCount > 0 ) {
317
$( '.subsubsub .upgrade .count' ).text( '(' + itemCount + ')' );
319
$itemCount.text( itemCount );
320
$menuItem.find( '.update-plugins' ).each( function( index, element ) {
321
element.className = element.className.replace( /count-\d+/, 'count-' + itemCount );
132
if (pluginCount > 0 ) {
133
$( '.subsubsub .upgrade .count' ).text( '(' + pluginCount + ')' );
135
$( '.subsubsub .upgrade' ).remove();
324
$( '.subsubsub .upgrade' ).remove();
325
$menuItem.find( '.update-plugins' ).remove();
141
* Send an Ajax request to the server to update a plugin.
330
* Sends an Ajax request to the server to update a plugin.
333
* @since 4.6.0 More accurately named `updatePlugin`.
145
* @param {string} plugin
146
* @param {string} slug
335
* @param {object} args Arguments.
336
* @param {string} args.plugin Plugin basename.
337
* @param {string} args.slug Plugin slug.
338
* @param {updatePluginSuccess=} args.success Optional. Success callback. Default: wp.updates.updatePluginSuccess
339
* @param {updatePluginError=} args.error Optional. Error callback. Default: wp.updates.updatePluginError
340
* @return {$.promise} A jQuery promise that represents the request,
341
* decorated with an abort() method.
148
wp.updates.updatePlugin = function( plugin, slug ) {
150
$card = $( '.plugin-card-' + slug );
343
wp.updates.updatePlugin = function( args ) {
344
var $updateRow, $card, $message, message;
347
success: wp.updates.updatePluginSuccess,
348
error: wp.updates.updatePluginError
152
351
if ( 'plugins' === pagenow || 'plugins-network' === pagenow ) {
153
$message = $( '[data-plugin="' + plugin + '"]' ).next().find( '.update-message' );
154
} else if ( 'plugin-install' === pagenow ) {
155
$message = $card.find( '.update-now' );
156
name = $message.data( 'name' );
157
$message.attr( 'aria-label', wp.updates.l10n.updatingLabel.replace( '%s', name ) );
352
$updateRow = $( 'tr[data-plugin="' + args.plugin + '"]' );
353
$message = $updateRow.find( '.update-message' ).removeClass( 'notice-error' ).addClass( 'updating-message notice-warning' ).find( 'p' );
354
message = wp.updates.l10n.updatingLabel.replace( '%s', $updateRow.find( '.plugin-title strong' ).text() );
355
} else if ( 'plugin-install' === pagenow || 'plugin-install-network' === pagenow ) {
356
$card = $( '.plugin-card-' + args.slug );
357
$message = $card.find( '.update-now' ).addClass( 'updating-message' );
358
message = wp.updates.l10n.updatingLabel.replace( '%s', $message.data( 'name' ) );
158
360
// Remove previous error messages, if any.
159
361
$card.removeClass( 'plugin-card-update-failed' ).find( '.notice.notice-error' ).remove();
162
$message.addClass( 'updating-message' );
163
if ( $message.html() !== wp.updates.l10n.updating ){
364
if ( $message.html() !== wp.updates.l10n.updating ) {
164
365
$message.data( 'originaltext', $message.html() );
167
$message.text( wp.updates.l10n.updating );
168
wp.a11y.speak( wp.updates.l10n.updatingMsg );
170
if ( wp.updates.updateLock ) {
171
wp.updates.updateQueue.push( {
172
type: 'update-plugin',
181
wp.updates.updateLock = true;
184
_ajax_nonce: wp.updates.ajaxNonce,
187
username: wp.updates.filesystemCredentials.ftp.username,
188
password: wp.updates.filesystemCredentials.ftp.password,
189
hostname: wp.updates.filesystemCredentials.ftp.hostname,
190
connection_type: wp.updates.filesystemCredentials.ftp.connectionType,
191
public_key: wp.updates.filesystemCredentials.ssh.publicKey,
192
private_key: wp.updates.filesystemCredentials.ssh.privateKey
195
wp.ajax.post( 'update-plugin', data )
196
.done( wp.updates.updateSuccess )
197
.fail( wp.updates.updateError );
369
.attr( 'aria-label', message )
370
.text( wp.updates.l10n.updating );
372
$document.trigger( 'wp-plugin-updating', args );
374
return wp.updates.ajax( 'update-plugin', args );
201
* On a successful plugin update, update the UI with the result.
378
* Updates the UI appropriately after a successful plugin update.
381
* @since 4.6.0 More accurately named `updatePluginSuccess`.
205
* @param {object} response
383
* @typedef {object} updatePluginSuccess
384
* @param {object} response Response from the server.
385
* @param {string} response.slug Slug of the plugin to be updated.
386
* @param {string} response.plugin Basename of the plugin to be updated.
387
* @param {string} response.pluginName Name of the plugin to be updated.
388
* @param {string} response.oldVersion Old version of the plugin.
389
* @param {string} response.newVersion New version of the plugin.
207
wp.updates.updateSuccess = function( response ) {
208
var $updateMessage, name, $pluginRow, newText;
391
wp.updates.updatePluginSuccess = function( response ) {
392
var $pluginRow, $updateMessage, newText;
209
394
if ( 'plugins' === pagenow || 'plugins-network' === pagenow ) {
210
$pluginRow = $( '[data-plugin="' + response.plugin + '"]' ).first();
211
$updateMessage = $pluginRow.next().find( '.update-message' );
212
$pluginRow.addClass( 'updated' ).removeClass( 'update' );
395
$pluginRow = $( 'tr[data-plugin="' + response.plugin + '"]' )
396
.removeClass( 'update' )
397
.addClass( 'updated' );
398
$updateMessage = $pluginRow.find( '.update-message' )
399
.removeClass( 'updating-message notice-warning' )
400
.addClass( 'updated-message notice-success' ).find( 'p' );
214
402
// Update the version number in the row.
215
newText = $pluginRow.find('.plugin-version-author-uri').html().replace( response.oldVersion, response.newVersion );
216
$pluginRow.find('.plugin-version-author-uri').html( newText );
218
// Add updated class to update message parent tr
219
$pluginRow.next().addClass( 'updated' );
220
} else if ( 'plugin-install' === pagenow ) {
221
$updateMessage = $( '.plugin-card-' + response.slug ).find( '.update-now' );
222
$updateMessage.addClass( 'button-disabled' );
223
name = $updateMessage.data( 'name' );
224
$updateMessage.attr( 'aria-label', wp.updates.l10n.updatedLabel.replace( '%s', name ) );
403
newText = $pluginRow.find( '.plugin-version-author-uri' ).html().replace( response.oldVersion, response.newVersion );
404
$pluginRow.find( '.plugin-version-author-uri' ).html( newText );
405
} else if ( 'plugin-install' === pagenow || 'plugin-install-network' === pagenow ) {
406
$updateMessage = $( '.plugin-card-' + response.slug ).find( '.update-now' )
407
.removeClass( 'updating-message' )
408
.addClass( 'button-disabled updated-message' );
227
$updateMessage.removeClass( 'updating-message' ).addClass( 'updated-message' );
228
$updateMessage.text( wp.updates.l10n.updated );
229
wp.a11y.speak( wp.updates.l10n.updatedMsg );
412
.attr( 'aria-label', wp.updates.l10n.updatedLabel.replace( '%s', response.pluginName ) )
413
.text( wp.updates.l10n.updated );
415
wp.a11y.speak( wp.updates.l10n.updatedMsg, 'polite' );
231
417
wp.updates.decrementCount( 'plugin' );
233
wp.updates.updateDoneSuccessfully = true;
236
* The lock can be released since the update was successful,
237
* and any other updates can commence.
239
wp.updates.updateLock = false;
241
$(document).trigger( 'wp-plugin-update-success', response );
243
wp.updates.queueChecker();
419
$document.trigger( 'wp-plugin-update-success', response );
248
* On a plugin update error, update the UI appropriately.
423
* Updates the UI appropriately after a failed plugin update.
426
* @since 4.6.0 More accurately named `updatePluginError`.
252
* @param {object} response
428
* @typedef {object} updatePluginError
429
* @param {object} response Response from the server.
430
* @param {string} response.slug Slug of the plugin to be updated.
431
* @param {string} response.plugin Basename of the plugin to be updated.
432
* @param {string=} response.pluginName Optional. Name of the plugin to be updated.
433
* @param {string} response.errorCode Error code for the error that occurred.
434
* @param {string} response.errorMessage The error that occurred.
254
wp.updates.updateError = function( response ) {
255
var $card = $( '.plugin-card-' + response.slug ),
261
wp.updates.updateDoneSuccessfully = false;
263
if ( response.errorCode && response.errorCode == 'unable_to_connect_to_filesystem' && wp.updates.shouldRequestFilesystemCredentials ) {
264
wp.updates.credentialError( response, 'update-plugin' );
268
error_message = wp.updates.l10n.updateFailed.replace( '%s', response.error );
436
wp.updates.updatePluginError = function( response ) {
437
var $card, $message, errorMessage;
439
if ( ! wp.updates.isValidResponse( response, 'update' ) ) {
443
if ( wp.updates.maybeHandleCredentialError( response, 'update-plugin' ) ) {
447
errorMessage = wp.updates.l10n.updateFailed.replace( '%s', response.errorMessage );
270
449
if ( 'plugins' === pagenow || 'plugins-network' === pagenow ) {
271
$message = $( '[data-plugin="' + response.plugin + '"]' ).next().find( '.update-message' );
272
$message.html( error_message ).removeClass( 'updating-message' );
273
} else if ( 'plugin-install' === pagenow ) {
274
$button = $card.find( '.update-now' );
275
name = $button.data( 'name' );
450
if ( response.plugin ) {
451
$message = $( 'tr[data-plugin="' + response.plugin + '"]' ).find( '.update-message' );
453
$message = $( 'tr[data-slug="' + response.slug + '"]' ).find( '.update-message' );
455
$message.removeClass( 'updating-message notice-warning' ).addClass( 'notice-error' ).find( 'p' ).html( errorMessage );
457
if ( response.pluginName ) {
459
.attr( 'aria-label', wp.updates.l10n.updateFailedLabel.replace( '%s', response.pluginName ) );
461
$message.find( 'p' ).removeAttr( 'aria-label' );
463
} else if ( 'plugin-install' === pagenow || 'plugin-install-network' === pagenow ) {
464
$card = $( '.plugin-card-' + response.slug )
278
465
.addClass( 'plugin-card-update-failed' )
279
.append( '<div class="notice notice-error is-dismissible"><p>' + error_message + '</p></div>' );
282
.attr( 'aria-label', wp.updates.l10n.updateFailedLabel.replace( '%s', name ) )
283
.html( wp.updates.l10n.updateFailedShort ).removeClass( 'updating-message' );
466
.append( wp.updates.adminNotice( {
467
className: 'update-message notice-error notice-alt is-dismissible',
468
message: errorMessage
471
$card.find( '.update-now' )
472
.text( wp.updates.l10n.updateFailedShort ).removeClass( 'updating-message' );
474
if ( response.pluginName ) {
475
$card.find( '.update-now' )
476
.attr( 'aria-label', wp.updates.l10n.updateFailedLabel.replace( '%s', response.pluginName ) );
478
$card.find( '.update-now' ).removeAttr( 'aria-label' );
285
481
$card.on( 'click', '.notice.is-dismissible .notice-dismiss', function() {
286
483
// Use same delay as the total duration of the notice fadeTo + slideUp animation.
287
484
setTimeout( function() {
289
486
.removeClass( 'plugin-card-update-failed' )
290
487
.find( '.column-name a' ).focus();
489
$card.find( '.update-now' )
490
.attr( 'aria-label', false )
491
.text( wp.updates.l10n.updateNow );
295
wp.a11y.speak( error_message, 'assertive' );
298
* The lock can be released since this failure was
299
* after the credentials form.
301
wp.updates.updateLock = false;
303
$(document).trigger( 'wp-plugin-update-error', response );
305
wp.updates.queueChecker();
309
* Show an error message in the request for credentials form.
311
* @param {string} message
314
wp.updates.showErrorInCredentialsForm = function( message ) {
315
var $modal = $( '.notification-dialog' );
317
// Remove any existing error.
318
$modal.find( '.error' ).remove();
320
$modal.find( 'h3' ).after( '<div class="error">' + message + '</div>' );
324
* Events that need to happen when there is a credential error
328
wp.updates.credentialError = function( response, type ) {
329
wp.updates.updateQueue.push( {
332
// Not cool that we're depending on response for this data.
333
// This would feel more whole in a view all tied together.
334
plugin: response.plugin,
338
wp.updates.showErrorInCredentialsForm( response.error );
339
wp.updates.requestFilesystemCredentials();
343
* If an update job has been placed in the queue, queueChecker pulls it out and runs it.
496
wp.a11y.speak( errorMessage, 'assertive' );
498
$document.trigger( 'wp-plugin-update-error', response );
502
* Sends an Ajax request to the server to install a plugin.
506
* @param {object} args Arguments.
507
* @param {string} args.slug Plugin identifier in the WordPress.org Plugin repository.
508
* @param {installPluginSuccess=} args.success Optional. Success callback. Default: wp.updates.installPluginSuccess
509
* @param {installPluginError=} args.error Optional. Error callback. Default: wp.updates.installPluginError
510
* @return {$.promise} A jQuery promise that represents the request,
511
* decorated with an abort() method.
513
wp.updates.installPlugin = function( args ) {
514
var $card = $( '.plugin-card-' + args.slug ),
515
$message = $card.find( '.install-now' );
518
success: wp.updates.installPluginSuccess,
519
error: wp.updates.installPluginError
522
if ( 'import' === pagenow ) {
523
$message = $( '[data-slug="' + args.slug + '"]' );
526
if ( $message.html() !== wp.updates.l10n.installing ) {
527
$message.data( 'originaltext', $message.html() );
531
.addClass( 'updating-message' )
532
.attr( 'aria-label', wp.updates.l10n.pluginInstallingLabel.replace( '%s', $message.data( 'name' ) ) )
533
.text( wp.updates.l10n.installing );
535
wp.a11y.speak( wp.updates.l10n.installingMsg, 'polite' );
537
// Remove previous error messages, if any.
538
$card.removeClass( 'plugin-card-install-failed' ).find( '.notice.notice-error' ).remove();
540
$document.trigger( 'wp-plugin-installing', args );
542
return wp.updates.ajax( 'install-plugin', args );
546
* Updates the UI appropriately after a successful plugin install.
550
* @typedef {object} installPluginSuccess
551
* @param {object} response Response from the server.
552
* @param {string} response.slug Slug of the installed plugin.
553
* @param {string} response.pluginName Name of the installed plugin.
554
* @param {string} response.activateUrl URL to activate the just installed plugin.
556
wp.updates.installPluginSuccess = function( response ) {
557
var $message = $( '.plugin-card-' + response.slug ).find( '.install-now' );
560
.removeClass( 'updating-message' )
561
.addClass( 'updated-message installed button-disabled' )
562
.attr( 'aria-label', wp.updates.l10n.pluginInstalledLabel.replace( '%s', response.pluginName ) )
563
.text( wp.updates.l10n.installed );
565
wp.a11y.speak( wp.updates.l10n.installedMsg, 'polite' );
567
$document.trigger( 'wp-plugin-install-success', response );
569
if ( response.activateUrl ) {
570
setTimeout( function() {
572
// Transform the 'Install' button into an 'Activate' button.
573
$message.removeClass( 'install-now installed button-disabled updated-message' ).addClass( 'activate-now button-primary' )
574
.attr( 'href', response.activateUrl )
575
.attr( 'aria-label', wp.updates.l10n.activatePluginLabel.replace( '%s', response.pluginName ) )
576
.text( wp.updates.l10n.activatePlugin );
582
* Updates the UI appropriately after a failed plugin install.
586
* @typedef {object} installPluginError
587
* @param {object} response Response from the server.
588
* @param {string} response.slug Slug of the plugin to be installed.
589
* @param {string=} response.pluginName Optional. Name of the plugin to be installed.
590
* @param {string} response.errorCode Error code for the error that occurred.
591
* @param {string} response.errorMessage The error that occurred.
593
wp.updates.installPluginError = function( response ) {
594
var $card = $( '.plugin-card-' + response.slug ),
595
$button = $card.find( '.install-now' ),
598
if ( ! wp.updates.isValidResponse( response, 'install' ) ) {
602
if ( wp.updates.maybeHandleCredentialError( response, 'install-plugin' ) ) {
606
errorMessage = wp.updates.l10n.installFailed.replace( '%s', response.errorMessage );
609
.addClass( 'plugin-card-update-failed' )
610
.append( '<div class="notice notice-error notice-alt is-dismissible"><p>' + errorMessage + '</p></div>' );
612
$card.on( 'click', '.notice.is-dismissible .notice-dismiss', function() {
614
// Use same delay as the total duration of the notice fadeTo + slideUp animation.
615
setTimeout( function() {
617
.removeClass( 'plugin-card-update-failed' )
618
.find( '.column-name a' ).focus();
623
.removeClass( 'updating-message' ).addClass( 'button-disabled' )
624
.attr( 'aria-label', wp.updates.l10n.pluginInstallFailedLabel.replace( '%s', $button.data( 'name' ) ) )
625
.text( wp.updates.l10n.installFailedShort );
627
wp.a11y.speak( errorMessage, 'assertive' );
629
$document.trigger( 'wp-plugin-install-error', response );
633
* Updates the UI appropriately after a successful importer install.
637
* @typedef {object} installImporterSuccess
638
* @param {object} response Response from the server.
639
* @param {string} response.slug Slug of the installed plugin.
640
* @param {string} response.pluginName Name of the installed plugin.
641
* @param {string} response.activateUrl URL to activate the just installed plugin.
643
wp.updates.installImporterSuccess = function( response ) {
644
wp.updates.addAdminNotice( {
645
id: 'install-success',
646
className: 'notice-success is-dismissible',
647
message: wp.updates.l10n.importerInstalledMsg.replace( '%s', response.activateUrl + '&from=import' )
650
$( '[data-slug="' + response.slug + '"]' )
651
.removeClass( 'install-now updating-message' )
652
.addClass( 'activate-now' )
654
'href': response.activateUrl + '&from=import',
655
'aria-label': wp.updates.l10n.activateImporterLabel.replace( '%s', response.pluginName )
657
.text( wp.updates.l10n.activateImporter );
659
wp.a11y.speak( wp.updates.l10n.installedMsg, 'polite' );
661
$document.trigger( 'wp-importer-install-success', response );
665
* Updates the UI appropriately after a failed importer install.
669
* @typedef {object} installImporterError
670
* @param {object} response Response from the server.
671
* @param {string} response.slug Slug of the plugin to be installed.
672
* @param {string=} response.pluginName Optional. Name of the plugin to be installed.
673
* @param {string} response.errorCode Error code for the error that occurred.
674
* @param {string} response.errorMessage The error that occurred.
676
wp.updates.installImporterError = function( response ) {
677
var errorMessage = wp.updates.l10n.installFailed.replace( '%s', response.errorMessage ),
678
$installLink = $( '[data-slug="' + response.slug + '"]' ),
679
pluginName = $installLink.data( 'name' );
681
if ( ! wp.updates.isValidResponse( response, 'install' ) ) {
685
if ( wp.updates.maybeHandleCredentialError( response, 'install-plugin' ) ) {
689
wp.updates.addAdminNotice( {
690
id: response.errorCode,
691
className: 'notice-error is-dismissible',
692
message: errorMessage
696
.removeClass( 'updating-message' )
697
.text( wp.updates.l10n.installNow )
698
.attr( 'aria-label', wp.updates.l10n.installNowLabel.replace( '%s', pluginName ) );
700
wp.a11y.speak( errorMessage, 'assertive' );
702
$document.trigger( 'wp-importer-install-error', response );
706
* Sends an Ajax request to the server to delete a plugin.
710
* @param {object} args Arguments.
711
* @param {string} args.plugin Basename of the plugin to be deleted.
712
* @param {string} args.slug Slug of the plugin to be deleted.
713
* @param {deletePluginSuccess=} args.success Optional. Success callback. Default: wp.updates.deletePluginSuccess
714
* @param {deletePluginError=} args.error Optional. Error callback. Default: wp.updates.deletePluginError
715
* @return {$.promise} A jQuery promise that represents the request,
716
* decorated with an abort() method.
718
wp.updates.deletePlugin = function( args ) {
719
var $link = $( '[data-plugin="' + args.plugin + '"]' ).find( '.row-actions a.delete' );
722
success: wp.updates.deletePluginSuccess,
723
error: wp.updates.deletePluginError
726
if ( $link.html() !== wp.updates.l10n.deleting ) {
728
.data( 'originaltext', $link.html() )
729
.text( wp.updates.l10n.deleting );
732
wp.a11y.speak( wp.updates.l10n.deleting, 'polite' );
734
$document.trigger( 'wp-plugin-deleting', args );
736
return wp.updates.ajax( 'delete-plugin', args );
740
* Updates the UI appropriately after a successful plugin deletion.
744
* @typedef {object} deletePluginSuccess
745
* @param {object} response Response from the server.
746
* @param {string} response.slug Slug of the plugin that was deleted.
747
* @param {string} response.plugin Base name of the plugin that was deleted.
748
* @param {string} response.pluginName Name of the plugin that was deleted.
750
wp.updates.deletePluginSuccess = function( response ) {
752
// Removes the plugin and updates rows.
753
$( '[data-plugin="' + response.plugin + '"]' ).css( { backgroundColor: '#faafaa' } ).fadeOut( 350, function() {
754
var $form = $( '#bulk-action-form' ),
755
$views = $( '.subsubsub' ),
756
$pluginRow = $( this ),
757
columnCount = $form.find( 'thead th:not(.hidden), thead td' ).length,
758
pluginDeletedRow = wp.template( 'item-deleted-row' ),
759
/** @type {object} plugins Base names of plugins in their different states. */
760
plugins = settings.plugins;
762
// Add a success message after deleting a plugin.
763
if ( ! $pluginRow.hasClass( 'plugin-update-tr' ) ) {
767
plugin: response.plugin,
768
colspan: columnCount,
769
name: response.pluginName
776
// Remove plugin from update count.
777
if ( -1 !== _.indexOf( plugins.upgrade, response.plugin ) ) {
778
plugins.upgrade = _.without( plugins.upgrade, response.plugin );
779
wp.updates.decrementCount( 'plugin' );
782
// Remove from views.
783
if ( -1 !== _.indexOf( plugins.inactive, response.plugin ) ) {
784
plugins.inactive = _.without( plugins.inactive, response.plugin );
785
if ( plugins.inactive.length ) {
786
$views.find( '.inactive .count' ).text( '(' + plugins.inactive.length + ')' );
788
$views.find( '.inactive' ).remove();
792
if ( -1 !== _.indexOf( plugins.active, response.plugin ) ) {
793
plugins.active = _.without( plugins.active, response.plugin );
794
if ( plugins.active.length ) {
795
$views.find( '.active .count' ).text( '(' + plugins.active.length + ')' );
797
$views.find( '.active' ).remove();
801
if ( -1 !== _.indexOf( plugins.recently_activated, response.plugin ) ) {
802
plugins.recently_activated = _.without( plugins.recently_activated, response.plugin );
803
if ( plugins.recently_activated.length ) {
804
$views.find( '.recently_activated .count' ).text( '(' + plugins.recently_activated.length + ')' );
806
$views.find( '.recently_activated' ).remove();
810
plugins.all = _.without( plugins.all, response.plugin );
812
if ( plugins.all.length ) {
813
$views.find( '.all .count' ).text( '(' + plugins.all.length + ')' );
815
$form.find( '.tablenav' ).css( { visibility: 'hidden' } );
816
$views.find( '.all' ).remove();
818
if ( ! $form.find( 'tr.no-items' ).length ) {
819
$form.find( '#the-list' ).append( '<tr class="no-items"><td class="colspanchange" colspan="' + columnCount + '">' + wp.updates.l10n.noPlugins + '</td></tr>' );
824
wp.a11y.speak( wp.updates.l10n.deleted, 'polite' );
826
$document.trigger( 'wp-plugin-delete-success', response );
830
* Updates the UI appropriately after a failed plugin deletion.
834
* @typedef {object} deletePluginError
835
* @param {object} response Response from the server.
836
* @param {string} response.slug Slug of the plugin to be deleted.
837
* @param {string} response.plugin Base name of the plugin to be deleted
838
* @param {string=} response.pluginName Optional. Name of the plugin to be deleted.
839
* @param {string} response.errorCode Error code for the error that occurred.
840
* @param {string} response.errorMessage The error that occurred.
842
wp.updates.deletePluginError = function( response ) {
843
var $plugin, $pluginUpdateRow,
844
pluginUpdateRow = wp.template( 'item-update-row' ),
845
noticeContent = wp.updates.adminNotice( {
846
className: 'update-message notice-error notice-alt',
847
message: response.errorMessage
850
if ( response.plugin ) {
851
$plugin = $( 'tr.inactive[data-plugin="' + response.plugin + '"]' );
852
$pluginUpdateRow = $plugin.siblings( '[data-plugin="' + response.plugin + '"]' );
854
$plugin = $( 'tr.inactive[data-slug="' + response.slug + '"]' );
855
$pluginUpdateRow = $plugin.siblings( '[data-slug="' + response.slug + '"]' );
858
if ( ! wp.updates.isValidResponse( response, 'delete' ) ) {
862
if ( wp.updates.maybeHandleCredentialError( response, 'delete-plugin' ) ) {
866
// Add a plugin update row if it doesn't exist yet.
867
if ( ! $pluginUpdateRow.length ) {
868
$plugin.addClass( 'update' ).after(
871
plugin: response.plugin || response.slug,
872
colspan: $( '#bulk-action-form' ).find( 'thead th:not(.hidden), thead td' ).length,
873
content: noticeContent
878
// Remove previous error messages, if any.
879
$pluginUpdateRow.find( '.notice-error' ).remove();
881
$pluginUpdateRow.find( '.plugin-update' ).append( noticeContent );
884
$document.trigger( 'wp-plugin-delete-error', response );
888
* Sends an Ajax request to the server to update a theme.
892
* @param {object} args Arguments.
893
* @param {string} args.slug Theme stylesheet.
894
* @param {updateThemeSuccess=} args.success Optional. Success callback. Default: wp.updates.updateThemeSuccess
895
* @param {updateThemeError=} args.error Optional. Error callback. Default: wp.updates.updateThemeError
896
* @return {$.promise} A jQuery promise that represents the request,
897
* decorated with an abort() method.
899
wp.updates.updateTheme = function( args ) {
903
success: wp.updates.updateThemeSuccess,
904
error: wp.updates.updateThemeError
907
if ( 'themes-network' === pagenow ) {
908
$notice = $( '[data-slug="' + args.slug + '"]' ).find( '.update-message' ).removeClass( 'notice-error' ).addClass( 'updating-message notice-warning' ).find( 'p' );
911
$notice = $( '#update-theme' ).closest( '.notice' ).removeClass( 'notice-large' );
913
$notice.find( 'h3' ).remove();
915
$notice = $notice.add( $( '[data-slug="' + args.slug + '"]' ).find( '.update-message' ) );
916
$notice = $notice.addClass( 'updating-message' ).find( 'p' );
919
if ( $notice.html() !== wp.updates.l10n.updating ) {
920
$notice.data( 'originaltext', $notice.html() );
923
wp.a11y.speak( wp.updates.l10n.updatingMsg, 'polite' );
924
$notice.text( wp.updates.l10n.updating );
926
$document.trigger( 'wp-theme-updating', args );
928
return wp.updates.ajax( 'update-theme', args );
932
* Updates the UI appropriately after a successful theme update.
936
* @typedef {object} updateThemeSuccess
937
* @param {object} response
938
* @param {string} response.slug Slug of the theme to be updated.
939
* @param {object} response.theme Updated theme.
940
* @param {string} response.oldVersion Old version of the theme.
941
* @param {string} response.newVersion New version of the theme.
943
wp.updates.updateThemeSuccess = function( response ) {
944
var isModalOpen = $( 'body.modal-open' ).length,
945
$theme = $( '[data-slug="' + response.slug + '"]' ),
947
className: 'updated-message notice-success notice-alt',
948
message: wp.updates.l10n.updated
952
if ( 'themes-network' === pagenow ) {
953
$notice = $theme.find( '.update-message' );
955
// Update the version number in the row.
956
newText = $theme.find( '.theme-version-author-uri' ).html().replace( response.oldVersion, response.newVersion );
957
$theme.find( '.theme-version-author-uri' ).html( newText );
959
$notice = $( '.theme-info .notice' ).add( $theme.find( '.update-message' ) );
961
// Focus on Customize button after updating.
963
$( '.load-customize:visible' ).focus();
965
$theme.find( '.load-customize' ).focus();
969
wp.updates.addAdminNotice( _.extend( { selector: $notice }, updatedMessage ) );
970
wp.a11y.speak( wp.updates.l10n.updatedMsg, 'polite' );
972
wp.updates.decrementCount( 'theme' );
974
$document.trigger( 'wp-theme-update-success', response );
976
// Show updated message after modal re-rendered.
978
$( '.theme-info .theme-author' ).after( wp.updates.adminNotice( updatedMessage ) );
983
* Updates the UI appropriately after a failed theme update.
987
* @typedef {object} updateThemeError
988
* @param {object} response Response from the server.
989
* @param {string} response.slug Slug of the theme to be updated.
990
* @param {string} response.errorCode Error code for the error that occurred.
991
* @param {string} response.errorMessage The error that occurred.
993
wp.updates.updateThemeError = function( response ) {
994
var $theme = $( '[data-slug="' + response.slug + '"]' ),
995
errorMessage = wp.updates.l10n.updateFailed.replace( '%s', response.errorMessage ),
998
if ( ! wp.updates.isValidResponse( response, 'update' ) ) {
1002
if ( wp.updates.maybeHandleCredentialError( response, 'update-theme' ) ) {
1006
if ( 'themes-network' === pagenow ) {
1007
$notice = $theme.find( '.update-message ' );
1009
$notice = $( '.theme-info .notice' ).add( $theme.find( '.notice' ) );
1011
$( 'body.modal-open' ).length ? $( '.load-customize:visible' ).focus() : $theme.find( '.load-customize' ).focus();
1014
wp.updates.addAdminNotice( {
1016
className: 'update-message notice-error notice-alt is-dismissible',
1017
message: errorMessage
1020
wp.a11y.speak( errorMessage, 'polite' );
1022
$document.trigger( 'wp-theme-update-error', response );
1026
* Sends an Ajax request to the server to install a theme.
1030
* @param {object} args
1031
* @param {string} args.slug Theme stylesheet.
1032
* @param {installThemeSuccess=} args.success Optional. Success callback. Default: wp.updates.installThemeSuccess
1033
* @param {installThemeError=} args.error Optional. Error callback. Default: wp.updates.installThemeError
1034
* @return {$.promise} A jQuery promise that represents the request,
1035
* decorated with an abort() method.
1037
wp.updates.installTheme = function( args ) {
1038
var $message = $( '.theme-install[data-slug="' + args.slug + '"]' );
1041
success: wp.updates.installThemeSuccess,
1042
error: wp.updates.installThemeError
1045
$message.addClass( 'updating-message' );
1046
$message.parents( '.theme' ).addClass( 'focus' );
1047
if ( $message.html() !== wp.updates.l10n.installing ) {
1048
$message.data( 'originaltext', $message.html() );
1052
.text( wp.updates.l10n.installing )
1053
.attr( 'aria-label', wp.updates.l10n.themeInstallingLabel.replace( '%s', $message.data( 'name' ) ) );
1054
wp.a11y.speak( wp.updates.l10n.installingMsg, 'polite' );
1056
// Remove previous error messages, if any.
1057
$( '.install-theme-info, [data-slug="' + args.slug + '"]' ).removeClass( 'theme-install-failed' ).find( '.notice.notice-error' ).remove();
1059
$document.trigger( 'wp-theme-installing', args );
1061
return wp.updates.ajax( 'install-theme', args );
1065
* Updates the UI appropriately after a successful theme install.
1069
* @typedef {object} installThemeSuccess
1070
* @param {object} response Response from the server.
1071
* @param {string} response.slug Slug of the theme to be installed.
1072
* @param {string} response.customizeUrl URL to the Customizer for the just installed theme.
1073
* @param {string} response.activateUrl URL to activate the just installed theme.
1075
wp.updates.installThemeSuccess = function( response ) {
1076
var $card = $( '.wp-full-overlay-header, [data-slug=' + response.slug + ']' ),
1079
$document.trigger( 'wp-theme-install-success', response );
1081
$message = $card.find( '.button-primary' )
1082
.removeClass( 'updating-message' )
1083
.addClass( 'updated-message disabled' )
1084
.attr( 'aria-label', wp.updates.l10n.themeInstalledLabel.replace( '%s', response.themeName ) )
1085
.text( wp.updates.l10n.installed );
1087
wp.a11y.speak( wp.updates.l10n.installedMsg, 'polite' );
1089
setTimeout( function() {
1091
if ( response.activateUrl ) {
1093
// Transform the 'Install' button into an 'Activate' button.
1095
.attr( 'href', response.activateUrl )
1096
.removeClass( 'theme-install updated-message disabled' )
1097
.addClass( 'activate' )
1098
.attr( 'aria-label', wp.updates.l10n.activateThemeLabel.replace( '%s', response.themeName ) )
1099
.text( wp.updates.l10n.activateTheme );
1102
if ( response.customizeUrl ) {
1104
// Transform the 'Preview' button into a 'Live Preview' button.
1105
$message.siblings( '.preview' ).replaceWith( function () {
1107
.attr( 'href', response.customizeUrl )
1108
.addClass( 'button button-secondary load-customize' )
1109
.text( wp.updates.l10n.livePreview );
1116
* Updates the UI appropriately after a failed theme install.
1120
* @typedef {object} installThemeError
1121
* @param {object} response Response from the server.
1122
* @param {string} response.slug Slug of the theme to be installed.
1123
* @param {string} response.errorCode Error code for the error that occurred.
1124
* @param {string} response.errorMessage The error that occurred.
1126
wp.updates.installThemeError = function( response ) {
1128
errorMessage = wp.updates.l10n.installFailed.replace( '%s', response.errorMessage ),
1129
$message = wp.updates.adminNotice( {
1130
className: 'update-message notice-error notice-alt',
1131
message: errorMessage
1134
if ( ! wp.updates.isValidResponse( response, 'install' ) ) {
1138
if ( wp.updates.maybeHandleCredentialError( response, 'install-theme' ) ) {
1142
if ( $document.find( 'body' ).hasClass( 'full-overlay-active' ) ) {
1143
$button = $( '.theme-install[data-slug="' + response.slug + '"]' );
1144
$card = $( '.install-theme-info' ).prepend( $message );
1146
$card = $( '[data-slug="' + response.slug + '"]' ).removeClass( 'focus' ).addClass( 'theme-install-failed' ).append( $message );
1147
$button = $card.find( '.theme-install' );
1151
.removeClass( 'updating-message' )
1152
.attr( 'aria-label', wp.updates.l10n.themeInstallFailedLabel.replace( '%s', $button.data( 'name' ) ) )
1153
.text( wp.updates.l10n.installFailedShort );
1155
wp.a11y.speak( errorMessage, 'assertive' );
1157
$document.trigger( 'wp-theme-install-error', response );
1161
* Sends an Ajax request to the server to install a theme.
1165
* @param {object} args
1166
* @param {string} args.slug Theme stylesheet.
1167
* @param {deleteThemeSuccess=} args.success Optional. Success callback. Default: wp.updates.deleteThemeSuccess
1168
* @param {deleteThemeError=} args.error Optional. Error callback. Default: wp.updates.deleteThemeError
1169
* @return {$.promise} A jQuery promise that represents the request,
1170
* decorated with an abort() method.
1172
wp.updates.deleteTheme = function( args ) {
1175
if ( 'themes' === pagenow ) {
1176
$button = $( '.theme-actions .delete-theme' );
1177
} else if ( 'themes-network' === pagenow ) {
1178
$button = $( '[data-slug="' + args.slug + '"]' ).find( '.row-actions a.delete' );
1182
success: wp.updates.deleteThemeSuccess,
1183
error: wp.updates.deleteThemeError
1186
if ( $button && $button.html() !== wp.updates.l10n.deleting ) {
1188
.data( 'originaltext', $button.html() )
1189
.text( wp.updates.l10n.deleting );
1192
wp.a11y.speak( wp.updates.l10n.deleting, 'polite' );
1194
// Remove previous error messages, if any.
1195
$( '.theme-info .update-message' ).remove();
1197
$document.trigger( 'wp-theme-deleting', args );
1199
return wp.updates.ajax( 'delete-theme', args );
1203
* Updates the UI appropriately after a successful theme deletion.
1207
* @typedef {object} deleteThemeSuccess
1208
* @param {object} response Response from the server.
1209
* @param {string} response.slug Slug of the theme that was deleted.
1211
wp.updates.deleteThemeSuccess = function( response ) {
1212
var $themeRows = $( '[data-slug="' + response.slug + '"]' );
1214
if ( 'themes-network' === pagenow ) {
1216
// Removes the theme and updates rows.
1217
$themeRows.css( { backgroundColor: '#faafaa' } ).fadeOut( 350, function() {
1218
var $views = $( '.subsubsub' ),
1219
$themeRow = $( this ),
1220
totals = settings.totals,
1221
deletedRow = wp.template( 'item-deleted-row' );
1223
if ( ! $themeRow.hasClass( 'plugin-update-tr' ) ) {
1226
slug: response.slug,
1227
colspan: $( '#bulk-action-form' ).find( 'thead th:not(.hidden), thead td' ).length,
1228
name: $themeRow.find( '.theme-title strong' ).text()
1235
// Remove theme from update count.
1236
if ( $themeRow.hasClass( 'update' ) ) {
1238
wp.updates.decrementCount( 'theme' );
1241
// Remove from views.
1242
if ( $themeRow.hasClass( 'inactive' ) ) {
1244
if ( totals.disabled ) {
1245
$views.find( '.disabled .count' ).text( '(' + totals.disabled + ')' );
1247
$views.find( '.disabled' ).remove();
1251
// There is always at least one theme available.
1252
$views.find( '.all .count' ).text( '(' + --totals.all + ')' );
1256
wp.a11y.speak( wp.updates.l10n.deleted, 'polite' );
1258
$document.trigger( 'wp-theme-delete-success', response );
1262
* Updates the UI appropriately after a failed theme deletion.
1266
* @typedef {object} deleteThemeError
1267
* @param {object} response Response from the server.
1268
* @param {string} response.slug Slug of the theme to be deleted.
1269
* @param {string} response.errorCode Error code for the error that occurred.
1270
* @param {string} response.errorMessage The error that occurred.
1272
wp.updates.deleteThemeError = function( response ) {
1273
var $themeRow = $( 'tr.inactive[data-slug="' + response.slug + '"]' ),
1274
$button = $( '.theme-actions .delete-theme' ),
1275
updateRow = wp.template( 'item-update-row' ),
1276
$updateRow = $themeRow.siblings( '#' + response.slug + '-update' ),
1277
errorMessage = wp.updates.l10n.deleteFailed.replace( '%s', response.errorMessage ),
1278
$message = wp.updates.adminNotice( {
1279
className: 'update-message notice-error notice-alt',
1280
message: errorMessage
1283
if ( wp.updates.maybeHandleCredentialError( response, 'delete-theme' ) ) {
1287
if ( 'themes-network' === pagenow ) {
1288
if ( ! $updateRow.length ) {
1289
$themeRow.addClass( 'update' ).after(
1291
slug: response.slug,
1292
colspan: $( '#bulk-action-form' ).find( 'thead th:not(.hidden), thead td' ).length,
1297
// Remove previous error messages, if any.
1298
$updateRow.find( '.notice-error' ).remove();
1299
$updateRow.find( '.plugin-update' ).append( $message );
1302
$( '.theme-info .theme-description' ).before( $message );
1305
$button.html( $button.data( 'originaltext' ) );
1307
wp.a11y.speak( errorMessage, 'assertive' );
1309
$document.trigger( 'wp-theme-delete-error', response );
1313
* Adds the appropriate callback based on the type of action and the current page.
1318
* @param {object} data AJAX payload.
1319
* @param {string} action The type of request to perform.
1320
* @return {object} The AJAX payload with the appropriate callbacks.
1322
wp.updates._addCallbacks = function( data, action ) {
1323
if ( 'import' === pagenow && 'install-plugin' === action ) {
1324
data.success = wp.updates.installImporterSuccess;
1325
data.error = wp.updates.installImporterError;
1332
* Pulls available jobs from the queue and runs them.
1335
* @since 4.6.0 Can handle multiple job types.
347
1337
wp.updates.queueChecker = function() {
348
if ( wp.updates.updateLock || wp.updates.updateQueue.length <= 0 ) {
1340
if ( wp.updates.ajaxLocked || ! wp.updates.queue.length ) {
352
var job = wp.updates.updateQueue.shift();
354
wp.updates.updatePlugin( job.data.plugin, job.data.slug );
1344
job = wp.updates.queue.shift();
1346
// Handle a queue job.
1347
switch ( job.action ) {
1348
case 'install-plugin':
1349
wp.updates.installPlugin( job.data );
1352
case 'update-plugin':
1353
wp.updates.updatePlugin( job.data );
1356
case 'delete-plugin':
1357
wp.updates.deletePlugin( job.data );
1360
case 'install-theme':
1361
wp.updates.installTheme( job.data );
1364
case 'update-theme':
1365
wp.updates.updateTheme( job.data );
1368
case 'delete-theme':
1369
wp.updates.deleteTheme( job.data );
359
* Request the users filesystem credentials if we don't have them already.
1378
* Requests the users filesystem credentials if they aren't already known.
1382
* @param {Event=} event Optional. Event interface.
363
1384
wp.updates.requestFilesystemCredentials = function( event ) {
364
if ( wp.updates.updateDoneSuccessfully === false ) {
1385
if ( false === wp.updates.filesystemCredentials.available ) {
366
* For the plugin install screen, return the focus to the install button
367
* after exiting the credentials request modal.
1387
* After exiting the credentials request modal,
1388
* return the focus to the element triggering the request.
369
if ( 'plugin-install' === pagenow && event ) {
1390
if ( event && ! wp.updates.$elToReturnFocusToFromCredentialsModal ) {
370
1391
wp.updates.$elToReturnFocusToFromCredentialsModal = $( event.target );
373
wp.updates.updateLock = true;
1394
wp.updates.ajaxLocked = true;
375
1395
wp.updates.requestForCredentialsModalOpen();
1400
* Requests the users filesystem credentials if needed and there is no lock.
1404
* @param {Event=} event Optional. Event interface.
1406
wp.updates.maybeRequestFilesystemCredentials = function( event ) {
1407
if ( wp.updates.shouldRequestFilesystemCredentials && ! wp.updates.ajaxLocked ) {
1408
wp.updates.requestFilesystemCredentials( event );
380
1413
* Keydown handler for the request for credentials modal.
382
* Close the modal when the escape key is pressed.
383
* Constrain keyboard navigation to inside the modal.
1415
* Closes the modal when the escape key is pressed and
1416
* constrains keyboard navigation to inside the modal.
1420
* @param {Event} event Event interface.
387
1422
wp.updates.keydown = function( event ) {
388
1423
if ( 27 === event.keyCode ) {
389
1424
wp.updates.requestForCredentialsModalCancel();
390
1425
} else if ( 9 === event.keyCode ) {
391
// #upgrade button must always be the last focusable element in the dialog.
392
if ( event.target.id === 'upgrade' && ! event.shiftKey ) {
1427
// #upgrade button must always be the last focus-able element in the dialog.
1428
if ( 'upgrade' === event.target.id && ! event.shiftKey ) {
393
1429
$( '#hostname' ).focus();
394
1431
event.preventDefault();
395
} else if ( event.target.id === 'hostname' && event.shiftKey ) {
1432
} else if ( 'hostname' === event.target.id && event.shiftKey ) {
396
1433
$( '#upgrade' ).focus();
397
1435
event.preventDefault();
403
* Open the request for credentials modal.
1441
* Opens the request for credentials modal.
407
1445
wp.updates.requestForCredentialsModalOpen = function() {
408
1446
var $modal = $( '#request-filesystem-credentials-dialog' );
409
1448
$( 'body' ).addClass( 'modal-open' );
412
1450
$modal.find( 'input:enabled:first' ).focus();
413
$modal.keydown( wp.updates.keydown );
1451
$modal.on( 'keydown', wp.updates.keydown );
417
* Close the request for credentials modal.
1455
* Closes the request for credentials modal.
421
1459
wp.updates.requestForCredentialsModalClose = function() {
422
1460
$( '#request-filesystem-credentials-dialog' ).hide();
423
1461
$( 'body' ).removeClass( 'modal-open' );
424
wp.updates.$elToReturnFocusToFromCredentialsModal.focus();
1463
if ( wp.updates.$elToReturnFocusToFromCredentialsModal ) {
1464
wp.updates.$elToReturnFocusToFromCredentialsModal.focus();
428
* The steps that need to happen when the modal is canceled out
1469
* Takes care of the steps that need to happen when the modal is canceled out.
1472
* @since 4.6.0 Triggers an event for callbacks to listen to and add their actions.
432
1474
wp.updates.requestForCredentialsModalCancel = function() {
433
// no updateLock and no updateQueue means we already have cleared things up
436
if( wp.updates.updateLock === false && wp.updates.updateQueue.length === 0 ){
1476
// Not ajaxLocked and no queue means we already have cleared things up.
1477
if ( ! wp.updates.ajaxLocked && ! wp.updates.queue.length ) {
440
data = wp.updates.updateQueue[0].data;
1481
_.each( wp.updates.queue, function( job ) {
1482
$document.trigger( 'credential-modal-cancel', job );
442
// remove the lock, and clear the queue
443
wp.updates.updateLock = false;
444
wp.updates.updateQueue = [];
1485
// Remove the lock, and clear the queue.
1486
wp.updates.ajaxLocked = false;
1487
wp.updates.queue = [];
446
1489
wp.updates.requestForCredentialsModalClose();
447
if ( 'plugins' === pagenow || 'plugins-network' === pagenow ) {
448
$message = $( '[data-plugin="' + data.plugin + '"]' ).next().find( '.update-message' );
449
} else if ( 'plugin-install' === pagenow ) {
450
$message = $( '.plugin-card-' + data.slug ).find( '.update-now' );
453
$message.removeClass( 'updating-message' );
454
$message.html( $message.data( 'originaltext' ) );
455
wp.a11y.speak( wp.updates.l10n.updateCancel );
458
* Potentially add an AYS to a user attempting to leave the page
1493
* Displays an error message in the request for credentials form.
1497
* @param {string} message Error message.
1499
wp.updates.showErrorInCredentialsForm = function( message ) {
1500
var $modal = $( '#request-filesystem-credentials-form' );
1502
// Remove any existing error.
1503
$modal.find( '.notice' ).remove();
1504
$modal.find( '#request-filesystem-credentials-title' ).after( '<div class="notice notice-alt notice-error"><p>' + message + '</p></div>' );
1508
* Handles credential errors and runs events that need to happen in that case.
1512
* @param {object} response Ajax response.
1513
* @param {string} action The type of request to perform.
1515
wp.updates.credentialError = function( response, action ) {
1517
// Restore callbacks.
1518
response = wp.updates._addCallbacks( response, action );
1520
wp.updates.queue.unshift( {
1524
* Not cool that we're depending on response for this data.
1525
* This would feel more whole in a view all tied together.
1530
wp.updates.filesystemCredentials.available = false;
1531
wp.updates.showErrorInCredentialsForm( response.errorMessage );
1532
wp.updates.requestFilesystemCredentials();
1536
* Handles credentials errors if it could not connect to the filesystem.
1540
* @typedef {object} maybeHandleCredentialError
1541
* @param {object} response Response from the server.
1542
* @param {string} response.errorCode Error code for the error that occurred.
1543
* @param {string} response.errorMessage The error that occurred.
1544
* @param {string} action The type of request to perform.
1545
* @returns {boolean} Whether there is an error that needs to be handled or not.
1547
wp.updates.maybeHandleCredentialError = function( response, action ) {
1548
if ( wp.updates.shouldRequestFilesystemCredentials && response.errorCode && 'unable_to_connect_to_filesystem' === response.errorCode ) {
1549
wp.updates.credentialError( response, action );
1557
* Validates an AJAX response to ensure it's a proper object.
1559
* If the response deems to be invalid, an admin notice is being displayed.
1561
* @param {(object|string)} response Response from the server.
1562
* @param {function=} response.always Optional. Callback for when the Deferred is resolved or rejected.
1563
* @param {string=} response.statusText Optional. Status message corresponding to the status code.
1564
* @param {string=} response.responseText Optional. Request response as text.
1565
* @param {string} action Type of action the response is referring to. Can be 'delete',
1566
* 'update' or 'install'.
1568
wp.updates.isValidResponse = function( response, action ) {
1569
var error = wp.updates.l10n.unknownError,
1572
// Make sure the response is a valid data object and not a Promise object.
1573
if ( _.isObject( response ) && ! _.isFunction( response.always ) ) {
1577
if ( _.isString( response ) && '-1' === response ) {
1578
error = wp.updates.l10n.nonceError;
1579
} else if ( _.isString( response ) ) {
1581
} else if ( 'undefined' !== typeof response.readyState && 0 === response.readyState ) {
1582
error = wp.updates.l10n.connectionError;
1583
} else if ( _.isString( response.responseText ) && '' !== response.responseText ) {
1584
error = response.responseText;
1585
} else if ( _.isString( response.statusText ) ) {
1586
error = response.statusText;
1591
errorMessage = wp.updates.l10n.updateFailed;
1595
errorMessage = wp.updates.l10n.installFailed;
1599
errorMessage = wp.updates.l10n.deleteFailed;
1603
// Messages are escaped, remove HTML tags to make them more readable.
1604
error = error.replace( /<[\/a-z][^<>]*>/gi, '' );
1605
errorMessage = errorMessage.replace( '%s', error );
1607
// Add admin notice.
1608
wp.updates.addAdminNotice( {
1609
id: 'unknown_error',
1610
className: 'notice-error is-dismissible',
1611
message: _.escape( errorMessage )
1614
// Remove the lock, and clear the queue.
1615
wp.updates.ajaxLocked = false;
1616
wp.updates.queue = [];
1618
// Change buttons of all running updates.
1619
$( '.button.updating-message' )
1620
.removeClass( 'updating-message' )
1621
.removeAttr( 'aria-label' )
1622
.prop( 'disabled', true )
1623
.text( wp.updates.l10n.updateFailedShort );
1625
$( '.updating-message:not(.button):not(.thickbox)' )
1626
.removeClass( 'updating-message notice-warning' )
1627
.addClass( 'notice-error' )
1629
.removeAttr( 'aria-label' )
1630
.text( errorMessage );
1632
wp.a11y.speak( errorMessage, 'assertive' );
1638
* Potentially adds an AYS to a user attempting to leave the page.
460
1640
* If an update is on-going and a user attempts to leave the page,
461
* open an "Are you sure?" alert.
1641
* opens an "Are you sure?" alert.
466
1645
wp.updates.beforeunload = function() {
467
if ( wp.updates.updateLock ) {
1646
if ( wp.updates.ajaxLocked ) {
468
1647
return wp.updates.l10n.beforeunload;
1652
var $pluginFilter = $( '#plugin-filter' ),
1653
$bulkActionForm = $( '#bulk-action-form' ),
1654
$filesystemModal = $( '#request-filesystem-credentials-dialog' ),
1655
$pluginSearch = $( '.plugins-php .wp-filter-search' ),
1656
$pluginInstallSearch = $( '.plugin-install-php .wp-filter-search' );
473
$( document ).ready( function() {
475
* Check whether a user needs to submit filesystem credentials based on whether
476
* the form was output on the page server-side.
1659
* Whether a user needs to submit filesystem credentials.
1661
* This is based on whether the form was output on the page server-side.
478
1663
* @see {wp_print_request_filesystem_credentials_modal() in PHP}
480
wp.updates.shouldRequestFilesystemCredentials = ( $( '#request-filesystem-credentials-dialog' ).length <= 0 ) ? false : true;
482
// File system credentials form submit noop-er / handler.
483
$( '#request-filesystem-credentials-dialog form' ).on( 'submit', function() {
1665
wp.updates.shouldRequestFilesystemCredentials = $filesystemModal.length > 0;
1668
* File system credentials form submit noop-er / handler.
1672
$filesystemModal.on( 'submit', 'form', function( event ) {
1673
event.preventDefault();
484
1675
// Persist the credentials input by the user for the duration of the page load.
485
wp.updates.filesystemCredentials.ftp.hostname = $('#hostname').val();
486
wp.updates.filesystemCredentials.ftp.username = $('#username').val();
487
wp.updates.filesystemCredentials.ftp.password = $('#password').val();
488
wp.updates.filesystemCredentials.ftp.connectionType = $('input[name="connection_type"]:checked').val();
489
wp.updates.filesystemCredentials.ssh.publicKey = $('#public_key').val();
490
wp.updates.filesystemCredentials.ssh.privateKey = $('#private_key').val();
1676
wp.updates.filesystemCredentials.ftp.hostname = $( '#hostname' ).val();
1677
wp.updates.filesystemCredentials.ftp.username = $( '#username' ).val();
1678
wp.updates.filesystemCredentials.ftp.password = $( '#password' ).val();
1679
wp.updates.filesystemCredentials.ftp.connectionType = $( 'input[name="connection_type"]:checked' ).val();
1680
wp.updates.filesystemCredentials.ssh.publicKey = $( '#public_key' ).val();
1681
wp.updates.filesystemCredentials.ssh.privateKey = $( '#private_key' ).val();
1682
wp.updates.filesystemCredentials.available = true;
1684
// Unlock and invoke the queue.
1685
wp.updates.ajaxLocked = false;
1686
wp.updates.queueChecker();
492
1688
wp.updates.requestForCredentialsModalClose();
494
// Unlock and invoke the queue.
495
wp.updates.updateLock = false;
496
wp.updates.queueChecker();
501
// Close the request credentials modal when
502
$( '#request-filesystem-credentials-dialog [data-js-action="close"], .notification-dialog-background' ).on( 'click', function() {
503
wp.updates.requestForCredentialsModalCancel();
506
// Hide SSH fields when not selected
507
$( '#request-filesystem-credentials-dialog input[name="connection_type"]' ).on( 'change', function() {
508
$( this ).parents( 'form' ).find( '#private_key, #public_key' ).parents( 'label' ).toggle( ( 'ssh' == $( this ).val() ) );
511
// Click handler for plugin updates in List Table view.
512
$( '.plugin-update-tr' ).on( 'click', '.update-link', function( e ) {
514
if ( wp.updates.shouldRequestFilesystemCredentials && ! wp.updates.updateLock ) {
515
wp.updates.requestFilesystemCredentials( e );
517
var updateRow = $( e.target ).parents( '.plugin-update-tr' );
1692
* Closes the request credentials modal when clicking the 'Cancel' button or outside of the modal.
1696
$filesystemModal.on( 'click', '[data-js-action="close"], .notification-dialog-background', wp.updates.requestForCredentialsModalCancel );
1699
* Hide SSH fields when not selected.
1703
$filesystemModal.on( 'change', 'input[name="connection_type"]', function() {
1704
$( '#ssh-keys' ).toggleClass( 'hidden', ( 'ssh' !== $( this ).val() ) );
1708
* Handles events after the credential modal was closed.
1712
* @param {Event} event Event interface.
1713
* @param {string} job The install/update.delete request.
1715
$document.on( 'credential-modal-cancel', function( event, job ) {
1716
var $updatingMessage = $( '.updating-message' ),
1717
$message, originalText;
1719
if ( 'import' === pagenow ) {
1720
$updatingMessage.removeClass( 'updating-message' );
1721
} else if ( 'plugins' === pagenow || 'plugins-network' === pagenow ) {
1722
if ( 'update-plugin' === job.action ) {
1723
$message = $( 'tr[data-plugin="' + job.data.plugin + '"]' ).find( '.update-message' );
1724
} else if ( 'delete-plugin' === job.action ) {
1725
$message = $( '[data-plugin="' + job.data.plugin + '"]' ).find( '.row-actions a.delete' );
1727
} else if ( 'themes' === pagenow || 'themes-network' === pagenow ) {
1728
if ( 'update-theme' === job.action ) {
1729
$message = $( '[data-slug="' + job.data.slug + '"]' ).find( '.update-message' );
1730
} else if ( 'delete-theme' === job.action && 'themes-network' === pagenow ) {
1731
$message = $( '[data-slug="' + job.data.slug + '"]' ).find( '.row-actions a.delete' );
1732
} else if ( 'delete-theme' === job.action && 'themes' === pagenow ) {
1733
$message = $( '.theme-actions .delete-theme' );
1736
$message = $updatingMessage;
1739
if ( $message && $message.hasClass( 'updating-message' ) ) {
1740
originalText = $message.data( 'originaltext' );
1742
if ( 'undefined' === typeof originalText ) {
1743
originalText = $( '<p>' ).html( $message.find( 'p' ).data( 'originaltext' ) );
1747
.removeClass( 'updating-message' )
1748
.html( originalText );
1750
if ( 'plugin-install' === pagenow || 'plugin-install-network' === pagenow ) {
1751
if ( 'update-plugin' === job.action ) {
1752
$message.attr( 'aria-label', wp.updates.l10n.updateNowLabel.replace( '%s', $message.data( 'name' ) ) );
1753
} else if ( 'install-plugin' === job.action ) {
1754
$message.attr( 'aria-label', wp.updates.l10n.installNowLabel.replace( '%s', $message.data( 'name' ) ) );
1759
wp.a11y.speak( wp.updates.l10n.updateCancel, 'polite' );
1763
* Click handler for plugin updates in List Table view.
1767
* @param {Event} event Event interface.
1769
$bulkActionForm.on( 'click', '[data-plugin] .update-link', function( event ) {
1770
var $message = $( event.target ),
1771
$pluginRow = $message.parents( 'tr' );
1773
event.preventDefault();
1775
if ( $message.hasClass( 'updating-message' ) || $message.hasClass( 'button-disabled' ) ) {
1779
wp.updates.maybeRequestFilesystemCredentials( event );
518
1781
// Return the user to the input box of the plugin's table row after closing the modal.
519
wp.updates.$elToReturnFocusToFromCredentialsModal = updateRow.prev().find( '.check-column input' );
520
wp.updates.updatePlugin( updateRow.data( 'plugin' ), updateRow.data( 'slug' ) );
523
$( '.plugin-card' ).on( 'click', '.update-now', function( e ) {
525
var $button = $( e.target );
527
// Do nothing while updating and when the button is disabled.
528
if ( $button.hasClass( 'updating-message' ) || $button.hasClass( 'button-disabled' ) ) {
532
if ( wp.updates.shouldRequestFilesystemCredentials && ! wp.updates.updateLock ) {
533
wp.updates.requestFilesystemCredentials( e );
536
wp.updates.updatePlugin( $button.data( 'plugin' ), $button.data( 'slug' ) );
539
$( '#plugin_update_from_iframe' ).on( 'click' , function( e ) {
542
target = window.parent == window ? null : window.parent,
1782
wp.updates.$elToReturnFocusToFromCredentialsModal = $pluginRow.find( '.check-column input' );
1783
wp.updates.updatePlugin( {
1784
plugin: $pluginRow.data( 'plugin' ),
1785
slug: $pluginRow.data( 'slug' )
1790
* Click handler for plugin updates in plugin install view.
1794
* @param {Event} event Event interface.
1796
$pluginFilter.on( 'click', '.update-now', function( event ) {
1797
var $button = $( event.target );
1798
event.preventDefault();
1800
if ( $button.hasClass( 'updating-message' ) || $button.hasClass( 'button-disabled' ) ) {
1804
wp.updates.maybeRequestFilesystemCredentials( event );
1806
wp.updates.updatePlugin( {
1807
plugin: $button.data( 'plugin' ),
1808
slug: $button.data( 'slug' )
1813
* Click handler for plugin installs in plugin install view.
1817
* @param {Event} event Event interface.
1819
$pluginFilter.on( 'click', '.install-now', function( event ) {
1820
var $button = $( event.target );
1821
event.preventDefault();
1823
if ( $button.hasClass( 'updating-message' ) || $button.hasClass( 'button-disabled' ) ) {
1827
if ( wp.updates.shouldRequestFilesystemCredentials && ! wp.updates.ajaxLocked ) {
1828
wp.updates.requestFilesystemCredentials( event );
1830
$document.on( 'credential-modal-cancel', function() {
1831
var $message = $( '.install-now.updating-message' );
1834
.removeClass( 'updating-message' )
1835
.text( wp.updates.l10n.installNow );
1837
wp.a11y.speak( wp.updates.l10n.updateCancel, 'polite' );
1841
wp.updates.installPlugin( {
1842
slug: $button.data( 'slug' )
1847
* Click handler for importer plugins installs in the Import screen.
1851
* @param {Event} event Event interface.
1853
$document.on( 'click', '.importer-item .install-now', function( event ) {
1854
var $button = $( event.target ),
1855
pluginName = $( this ).data( 'name' );
1857
event.preventDefault();
1859
if ( $button.hasClass( 'updating-message' ) ) {
1863
if ( wp.updates.shouldRequestFilesystemCredentials && ! wp.updates.ajaxLocked ) {
1864
wp.updates.requestFilesystemCredentials( event );
1866
$document.on( 'credential-modal-cancel', function() {
1869
.removeClass( 'updating-message' )
1870
.text( wp.updates.l10n.installNow )
1871
.attr( 'aria-label', wp.updates.l10n.installNowLabel.replace( '%s', pluginName ) );
1873
wp.a11y.speak( wp.updates.l10n.updateCancel, 'polite' );
1877
wp.updates.installPlugin( {
1878
slug: $button.data( 'slug' ),
1879
success: wp.updates.installImporterSuccess,
1880
error: wp.updates.installImporterError
1885
* Click handler for plugin deletions.
1889
* @param {Event} event Event interface.
1891
$bulkActionForm.on( 'click', '[data-plugin] a.delete', function( event ) {
1892
var $pluginRow = $( event.target ).parents( 'tr' );
1894
event.preventDefault();
1896
if ( ! window.confirm( wp.updates.l10n.aysDeleteUninstall.replace( '%s', $pluginRow.find( '.plugin-title strong' ).text() ) ) ) {
1900
wp.updates.maybeRequestFilesystemCredentials( event );
1902
wp.updates.deletePlugin( {
1903
plugin: $pluginRow.data( 'plugin' ),
1904
slug: $pluginRow.data( 'slug' )
1910
* Click handler for theme updates.
1914
* @param {Event} event Event interface.
1916
$document.on( 'click', '.themes-php.network-admin .update-link', function( event ) {
1917
var $message = $( event.target ),
1918
$themeRow = $message.parents( 'tr' );
1920
event.preventDefault();
1922
if ( $message.hasClass( 'updating-message' ) || $message.hasClass( 'button-disabled' ) ) {
1926
wp.updates.maybeRequestFilesystemCredentials( event );
1928
// Return the user to the input box of the theme's table row after closing the modal.
1929
wp.updates.$elToReturnFocusToFromCredentialsModal = $themeRow.find( '.check-column input' );
1930
wp.updates.updateTheme( {
1931
slug: $themeRow.data( 'slug' )
1936
* Click handler for theme deletions.
1940
* @param {Event} event Event interface.
1942
$document.on( 'click', '.themes-php.network-admin a.delete', function( event ) {
1943
var $themeRow = $( event.target ).parents( 'tr' );
1945
event.preventDefault();
1947
if ( ! window.confirm( wp.updates.l10n.aysDelete.replace( '%s', $themeRow.find( '.theme-title strong' ).text() ) ) ) {
1951
wp.updates.maybeRequestFilesystemCredentials( event );
1953
wp.updates.deleteTheme( {
1954
slug: $themeRow.data( 'slug' )
1959
* Bulk action handler for plugins and themes.
1961
* Handles both deletions and updates.
1965
* @param {Event} event Event interface.
1967
$bulkActionForm.on( 'click', '[type="submit"]', function( event ) {
1968
var bulkAction = $( event.target ).siblings( 'select' ).val(),
1969
itemsSelected = $bulkActionForm.find( 'input[name="checked[]"]:checked' ),
1975
// Determine which type of item we're dealing with.
1976
switch ( pagenow ) {
1978
case 'plugins-network':
1982
case 'themes-network':
1990
// Bail if there were no items selected.
1991
if ( ! itemsSelected.length ) {
1992
event.preventDefault();
1993
$( 'html, body' ).animate( { scrollTop: 0 } );
1995
return wp.updates.addAdminNotice( {
1996
id: 'no-items-selected',
1997
className: 'notice-error is-dismissible',
1998
message: wp.updates.l10n.noItemsSelected
2002
// Determine the type of request we're dealing with.
2003
switch ( bulkAction ) {
2004
case 'update-selected':
2005
action = bulkAction.replace( 'selected', type );
2008
case 'delete-selected':
2009
if ( ! window.confirm( 'plugin' === type ? wp.updates.l10n.aysBulkDelete : wp.updates.l10n.aysBulkDeleteThemes ) ) {
2010
event.preventDefault();
2014
action = bulkAction.replace( 'selected', type );
2021
wp.updates.maybeRequestFilesystemCredentials( event );
2023
event.preventDefault();
2025
// Un-check the bulk checkboxes.
2026
$bulkActionForm.find( '.manage-column [type="checkbox"]' ).prop( 'checked', false );
2028
$document.trigger( 'wp-' + type + '-bulk-' + bulkAction, itemsSelected );
2030
// Find all the checkboxes which have been checked.
2031
itemsSelected.each( function( index, element ) {
2032
var $checkbox = $( element ),
2033
$itemRow = $checkbox.parents( 'tr' );
2035
// Only add update-able items to the update queue.
2036
if ( 'update-selected' === bulkAction && ( ! $itemRow.hasClass( 'update' ) || $itemRow.find( 'notice-error' ).length ) ) {
2038
// Un-check the box.
2039
$checkbox.prop( 'checked', false );
2043
// Add it to the queue.
2044
wp.updates.queue.push( {
2047
plugin: $itemRow.data( 'plugin' ),
2048
slug: $itemRow.data( 'slug' )
2053
// Display bulk notification for updates of any kind.
2054
$document.on( 'wp-plugin-update-success wp-plugin-update-error wp-theme-update-success wp-theme-update-error', function( event, response ) {
2055
var $itemRow = $( '[data-slug="' + response.slug + '"]' ),
2056
$bulkActionNotice, itemName;
2058
if ( 'wp-' + response.update + '-update-success' === event.type ) {
2061
itemName = response.pluginName ? response.pluginName : $itemRow.find( '.column-primary strong' ).text();
2064
errorMessages.push( itemName + ': ' + response.errorMessage );
2067
$itemRow.find( 'input[name="checked[]"]:checked' ).prop( 'checked', false );
2069
wp.updates.adminNotice = wp.template( 'wp-bulk-updates-admin-notice' );
2071
wp.updates.addAdminNotice( {
2072
id: 'bulk-action-notice',
2073
className: 'bulk-action-notice',
2076
errorMessages: errorMessages,
2077
type: response.update
2080
$bulkActionNotice = $( '#bulk-action-notice' ).on( 'click', 'button', function() {
2081
// $( this ) is the clicked button, no need to get it again.
2083
.toggleClass( 'bulk-action-errors-collapsed' )
2084
.attr( 'aria-expanded', ! $( this ).hasClass( 'bulk-action-errors-collapsed' ) );
2085
// Show the errors list.
2086
$bulkActionNotice.find( '.bulk-action-errors' ).toggleClass( 'hidden' );
2089
if ( error > 0 && ! wp.updates.queue.length ) {
2090
$( 'html, body' ).animate( { scrollTop: 0 } );
2094
// Reset admin notice template after #bulk-action-notice was added.
2095
$document.on( 'wp-updates-notice-added', function() {
2096
wp.updates.adminNotice = wp.template( 'wp-updates-admin-notice' );
2099
// Check the queue, now that the event handlers have been added.
2100
wp.updates.queueChecker();
2103
if ( $pluginInstallSearch.length ) {
2104
$pluginInstallSearch.attr( 'aria-describedby', 'live-search-desc' );
2108
* Handles changes to the plugin search box on the new-plugin page,
2109
* searching the repository dynamically.
2113
$pluginInstallSearch.on( 'keyup input', _.debounce( function( event, eventtype ) {
2114
var $searchTab = $( '.plugin-install-search' ), data, searchLocation;
2117
_ajax_nonce: wp.updates.ajaxNonce,
2118
s: event.target.value,
2120
type: $( '#typeselector' ).val(),
2123
searchLocation = location.href.split( '?' )[ 0 ] + '?' + $.param( _.omit( data, [ '_ajax_nonce', 'pagenow' ] ) );
2126
if ( 'keyup' === event.type && 27 === event.which ) {
2127
event.target.value = '';
2130
if ( wp.updates.searchTerm === data.s && 'typechange' !== eventtype ) {
2133
$pluginFilter.empty();
2134
wp.updates.searchTerm = data.s;
2137
if ( window.history && window.history.replaceState ) {
2138
window.history.replaceState( null, '', searchLocation );
2141
if ( ! $searchTab.length ) {
2142
$searchTab = $( '<li class="plugin-install-search" />' )
2143
.append( $( '<a />', {
2145
'href': searchLocation,
2146
'text': wp.updates.l10n.searchResultsLabel
2149
$( '.wp-filter .filter-links .current' )
2150
.removeClass( 'current' )
2151
.parents( '.filter-links' )
2152
.prepend( $searchTab );
2154
$pluginFilter.prev( 'p' ).remove();
2155
$( '.plugins-popular-tags-wrapper' ).remove();
2158
if ( 'undefined' !== typeof wp.updates.searchRequest ) {
2159
wp.updates.searchRequest.abort();
2161
$( 'body' ).addClass( 'loading-content' );
2163
wp.updates.searchRequest = wp.ajax.post( 'search-install-plugins', data ).done( function( response ) {
2164
$( 'body' ).removeClass( 'loading-content' );
2165
$pluginFilter.append( response.items );
2166
delete wp.updates.searchRequest;
2168
if ( 0 === response.count ) {
2169
wp.a11y.speak( wp.updates.l10n.noPluginsFound );
2171
wp.a11y.speak( wp.updates.l10n.pluginsFound.replace( '%d', response.count ) );
2176
if ( $pluginSearch.length ) {
2177
$pluginSearch.attr( 'aria-describedby', 'live-search-desc' );
2181
* Handles changes to the plugin search box on the Installed Plugins screen,
2182
* searching the plugin list dynamically.
2186
$pluginSearch.on( 'keyup input', _.debounce( function( event ) {
2188
_ajax_nonce: wp.updates.ajaxNonce,
2189
s: event.target.value,
2194
if ( 'keyup' === event.type && 27 === event.which ) {
2195
event.target.value = '';
2198
if ( wp.updates.searchTerm === data.s ) {
2201
wp.updates.searchTerm = data.s;
2204
if ( window.history && window.history.replaceState ) {
2205
window.history.replaceState( null, '', location.href.split( '?' )[ 0 ] + '?s=' + data.s );
2208
if ( 'undefined' !== typeof wp.updates.searchRequest ) {
2209
wp.updates.searchRequest.abort();
2212
$bulkActionForm.empty();
2213
$( 'body' ).addClass( 'loading-content' );
2215
wp.updates.searchRequest = wp.ajax.post( 'search-plugins', data ).done( function( response ) {
2217
// Can we just ditch this whole subtitle business?
2218
var $subTitle = $( '<span />' ).addClass( 'subtitle' ).html( wp.updates.l10n.searchResults.replace( '%s', _.escape( data.s ) ) ),
2219
$oldSubTitle = $( '.wrap .subtitle' );
2221
if ( ! data.s.length ) {
2222
$oldSubTitle.remove();
2223
} else if ( $oldSubTitle.length ) {
2224
$oldSubTitle.replaceWith( $subTitle );
2226
$( '.wrap h1' ).append( $subTitle );
2229
$( 'body' ).removeClass( 'loading-content' );
2230
$bulkActionForm.append( response.items );
2231
delete wp.updates.searchRequest;
2233
if ( 0 === response.count ) {
2234
wp.a11y.speak( wp.updates.l10n.noPluginsFound );
2236
wp.a11y.speak( wp.updates.l10n.pluginsFound.replace( '%d', response.count ) );
2242
* Trigger a search event when the search form gets submitted.
2246
$document.on( 'submit', '.search-plugins', function( event ) {
2247
event.preventDefault();
2249
$( 'input.wp-filter-search' ).trigger( 'input' );
2253
* Trigger a search event when the search type gets changed.
2257
$( '#typeselector' ).on( 'change', function() {
2258
var $search = $( 'input[name="s"]' );
2260
if ( $search.val().length ) {
2261
$search.trigger( 'input', 'typechange' );
2266
* Click handler for updating a plugin from the details modal on `plugin-install.php`.
2270
* @param {Event} event Event interface.
2272
$( '#plugin_update_from_iframe' ).on( 'click', function( event ) {
2273
var target = window.parent === window ? null : window.parent,
543
2276
$.support.postMessage = !! window.postMessage;
545
if ( $.support.postMessage === false || target === null || window.parent.location.pathname.indexOf( 'update-core.php' ) !== -1 )
2278
if ( false === $.support.postMessage || null === target || -1 !== window.parent.location.pathname.indexOf( 'update-core.php' ) ) {
551
action: 'updatePlugin',
552
type: 'update-plugin',
2282
event.preventDefault();
2285
action: 'update-plugin',
554
2287
plugin: $( this ).data( 'plugin' ),
2288
slug: $( this ).data( 'slug' )
2292
target.postMessage( JSON.stringify( update ), window.location.origin );
2296
* Click handler for installing a plugin from the details modal on `plugin-install.php`.
2300
* @param {Event} event Event interface.
2302
$( '#plugin_install_from_iframe' ).on( 'click', function( event ) {
2303
var target = window.parent === window ? null : window.parent,
2306
$.support.postMessage = !! window.postMessage;
2308
if ( false === $.support.postMessage || null === target || -1 !== window.parent.location.pathname.indexOf( 'index.php' ) ) {
2312
event.preventDefault();
2315
action: 'install-plugin',
555
2317
slug: $( this ).data( 'slug' )
559
target.postMessage( JSON.stringify( job ), window.location.origin );
564
$( window ).on( 'message', function( e ) {
565
var event = e.originalEvent,
567
loc = document.location,
568
expectedOrigin = loc.protocol + '//' + loc.hostname;
570
if ( event.origin !== expectedOrigin ) {
574
message = $.parseJSON( event.data );
576
if ( typeof message.action === 'undefined' ) {
580
switch (message.action){
581
case 'decrementUpdateCount' :
582
wp.updates.decrementCount( message.upgradeType );
584
case 'updatePlugin' :
587
wp.updates.updateQueue.push( message );
588
wp.updates.queueChecker();
594
$( window ).on( 'beforeunload', wp.updates.beforeunload );
596
})( jQuery, window.wp, window.pagenow, window.ajaxurl );
2321
target.postMessage( JSON.stringify( install ), window.location.origin );
2325
* Handles postMessage events.
2328
* @since 4.6.0 Switched `update-plugin` action to use the queue.
2330
* @param {Event} event Event interface.
2332
$( window ).on( 'message', function( event ) {
2333
var originalEvent = event.originalEvent,
2334
expectedOrigin = document.location.protocol + '//' + document.location.hostname,
2337
if ( originalEvent.origin !== expectedOrigin ) {
2342
message = $.parseJSON( originalEvent.data );
2347
if ( 'undefined' === typeof message.action ) {
2351
switch ( message.action ) {
2353
// Called from `wp-admin/includes/class-wp-upgrader-skins.php`.
2354
case 'decrementUpdateCount':
2355
/** @property {string} message.upgradeType */
2356
wp.updates.decrementCount( message.upgradeType );
2359
case 'install-plugin':
2360
case 'update-plugin':
2361
/* jscs:disable requireCamelCaseOrUpperCaseIdentifiers */
2365
message.data = wp.updates._addCallbacks( message.data, message.action );
2367
wp.updates.queue.push( message );
2368
wp.updates.queueChecker();
2374
* Adds a callback to display a warning before leaving the page.
2378
$( window ).on( 'beforeunload', wp.updates.beforeunload );
2380
})( jQuery, window.wp, _.extend( window._wpUpdatesSettings, window._wpUpdatesItemCounts || {} ) );