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

Erat himenaeos neque id sagittis massa. Hac suscipit pulvinar dignissim platea magnis eu. Don tellus a pharetra inceptos efficitur dui pulvinar. Feugiat facilisis penatibus pulvinar nunc dictumst donec odio platea habitasse. Lacus porta dolor purus elit ante bibendum tortor netus taciti nullam cubilia. Erat per suspendisse placerat morbi egestas pulvinar bibendum sollicitudin nec. Euismod cubilia eleifend velit himenaeos sodales lectus. Leo maximus cras ac porttitor aliquam torquent pulvinar odio volutpat parturient. Quisque risus finibus suspendisse mus purus magnis facilisi condimentum consectetur dui. Curae elit suspendisse cursus vehicula.

Turpis taciti class non vel pretium quis pulvinar tempor lobortis nunc. Libero phasellus parturient sapien volutpat malesuada ornare. Cubilia dignissim sollicitudin rhoncus lacinia maximus. Cras lorem fermentum bibendum pellentesque nisl etiam ligula enim cubilia. Vulputate pede sapien torquent montes tempus malesuada in mattis dis turpis vitae. Porta est tempor ex eget feugiat vulputate ipsum. Justo nec iaculis habitant diam arcu fermentum.

We offer comprehen sive emplo ment services such as assistance wit employer compliance.Our company is your strategic HR partner as instead of HR. john smithson

Cubilia dignissim sollicitudin rhoncus lacinia maximus. Cras lorem fermentum bibendum pellentesque nisl etiam ligula enim cubilia. Vulputate pede sapien torquent montes tempus malesuada in mattis dis turpis vitae.

Exploring Learning Landscapes in Academic

Feugiat facilisis penatibus pulvinar nunc dictumst donec odio platea habitasse. Lacus porta dolor purus elit ante bibendum tortor netus taciti nullam cubilia. Erat per suspendisse placerat morbi egestas pulvinar bibendum sollicitudin nec. Euismod cubilia eleifend velit himenaeos sodales lectus. Leo maximus cras ac porttitor aliquam torquent.

]]>
https://cardgames4educators.com/masters-in-english-how-english-speaker/feed/ 1