Diff: STRATO-apps/wordpress_03/app/wp-admin/js/updates.js

Keine Baseline-Datei – Diff nur gegen leer.
Zur Liste
1 -
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 +