|
1 /* global tb_remove */ |
|
2 window.wp = window.wp || {}; |
|
3 |
|
4 (function( $, wp, pagenow ) { |
|
5 wp.updates = {}; |
|
6 |
|
7 /** |
|
8 * User nonce for ajax calls. |
|
9 * |
|
10 * @since 4.2.0 |
|
11 * |
|
12 * @var string |
|
13 */ |
|
14 wp.updates.ajaxNonce = window._wpUpdatesSettings.ajax_nonce; |
|
15 |
|
16 /** |
|
17 * Localized strings. |
|
18 * |
|
19 * @since 4.2.0 |
|
20 * |
|
21 * @var object |
|
22 */ |
|
23 wp.updates.l10n = window._wpUpdatesSettings.l10n; |
|
24 |
|
25 /** |
|
26 * Whether filesystem credentials need to be requested from the user. |
|
27 * |
|
28 * @since 4.2.0 |
|
29 * |
|
30 * @var bool |
|
31 */ |
|
32 wp.updates.shouldRequestFilesystemCredentials = null; |
|
33 |
|
34 /** |
|
35 * Filesystem credentials to be packaged along with the request. |
|
36 * |
|
37 * @since 4.2.0 |
|
38 * |
|
39 * @var object |
|
40 */ |
|
41 wp.updates.filesystemCredentials = { |
|
42 ftp: { |
|
43 host: null, |
|
44 username: null, |
|
45 password: null, |
|
46 connectionType: null |
|
47 }, |
|
48 ssh: { |
|
49 publicKey: null, |
|
50 privateKey: null |
|
51 } |
|
52 }; |
|
53 |
|
54 /** |
|
55 * Flag if we're waiting for an update to complete. |
|
56 * |
|
57 * @since 4.2.0 |
|
58 * |
|
59 * @var bool |
|
60 */ |
|
61 wp.updates.updateLock = false; |
|
62 |
|
63 /** |
|
64 * * Flag if we've done an update successfully. |
|
65 * |
|
66 * @since 4.2.0 |
|
67 * |
|
68 * @var bool |
|
69 */ |
|
70 wp.updates.updateDoneSuccessfully = false; |
|
71 |
|
72 /** |
|
73 * If the user tries to update a plugin while an update is |
|
74 * already happening, it can be placed in this queue to perform later. |
|
75 * |
|
76 * @since 4.2.0 |
|
77 * |
|
78 * @var array |
|
79 */ |
|
80 wp.updates.updateQueue = []; |
|
81 |
|
82 /** |
|
83 * Store a jQuery reference to return focus to when exiting the request credentials modal. |
|
84 * |
|
85 * @since 4.2.0 |
|
86 * |
|
87 * @var jQuery object |
|
88 */ |
|
89 wp.updates.$elToReturnFocusToFromCredentialsModal = null; |
|
90 |
|
91 /** |
|
92 * Decrement update counts throughout the various menus. |
|
93 * |
|
94 * @since 3.9.0 |
|
95 * |
|
96 * @param {string} updateType |
|
97 */ |
|
98 wp.updates.decrementCount = function( upgradeType ) { |
|
99 var count, |
|
100 pluginCount, |
|
101 $adminBarUpdateCount = $( '#wp-admin-bar-updates .ab-label' ), |
|
102 $dashboardNavMenuUpdateCount = $( 'a[href="update-core.php"] .update-plugins' ), |
|
103 $pluginsMenuItem = $( '#menu-plugins' ); |
|
104 |
|
105 |
|
106 count = $adminBarUpdateCount.text(); |
|
107 count = parseInt( count, 10 ) - 1; |
|
108 if ( count < 0 || isNaN( count ) ) { |
|
109 return; |
|
110 } |
|
111 $( '#wp-admin-bar-updates .ab-item' ).removeAttr( 'title' ); |
|
112 $adminBarUpdateCount.text( count ); |
|
113 |
|
114 |
|
115 $dashboardNavMenuUpdateCount.each( function( index, elem ) { |
|
116 elem.className = elem.className.replace( /count-\d+/, 'count-' + count ); |
|
117 } ); |
|
118 $dashboardNavMenuUpdateCount.removeAttr( 'title' ); |
|
119 $dashboardNavMenuUpdateCount.find( '.update-count' ).text( count ); |
|
120 |
|
121 if ( 'plugin' === upgradeType ) { |
|
122 pluginCount = $pluginsMenuItem.find( '.plugin-count' ).eq(0).text(); |
|
123 pluginCount = parseInt( pluginCount, 10 ) - 1; |
|
124 if ( pluginCount < 0 || isNaN( pluginCount ) ) { |
|
125 return; |
|
126 } |
|
127 $pluginsMenuItem.find( '.plugin-count' ).text( pluginCount ); |
|
128 $pluginsMenuItem.find( '.update-plugins' ).each( function( index, elem ) { |
|
129 elem.className = elem.className.replace( /count-\d+/, 'count-' + pluginCount ); |
|
130 } ); |
|
131 |
|
132 if (pluginCount > 0 ) { |
|
133 $( '.subsubsub .upgrade .count' ).text( '(' + pluginCount + ')' ); |
|
134 } else { |
|
135 $( '.subsubsub .upgrade' ).remove(); |
|
136 } |
|
137 } |
|
138 }; |
|
139 |
|
140 /** |
|
141 * Send an Ajax request to the server to update a plugin. |
|
142 * |
|
143 * @since 4.2.0 |
|
144 * |
|
145 * @param {string} plugin |
|
146 * @param {string} slug |
|
147 */ |
|
148 wp.updates.updatePlugin = function( plugin, slug ) { |
|
149 var $message, name; |
|
150 if ( 'plugins' === pagenow || 'plugins-network' === pagenow ) { |
|
151 $message = $( '[data-slug="' + slug + '"]' ).next().find( '.update-message' ); |
|
152 } else if ( 'plugin-install' === pagenow ) { |
|
153 $message = $( '.plugin-card-' + slug ).find( '.update-now' ); |
|
154 name = $message.data( 'name' ); |
|
155 $message.attr( 'aria-label', wp.updates.l10n.updatingLabel.replace( '%s', name ) ); |
|
156 } |
|
157 |
|
158 $message.addClass( 'updating-message' ); |
|
159 if ( $message.html() !== wp.updates.l10n.updating ){ |
|
160 $message.data( 'originaltext', $message.html() ); |
|
161 } |
|
162 |
|
163 $message.text( wp.updates.l10n.updating ); |
|
164 wp.a11y.speak( wp.updates.l10n.updatingMsg ); |
|
165 |
|
166 if ( wp.updates.updateLock ) { |
|
167 wp.updates.updateQueue.push( { |
|
168 type: 'update-plugin', |
|
169 data: { |
|
170 plugin: plugin, |
|
171 slug: slug |
|
172 } |
|
173 } ); |
|
174 return; |
|
175 } |
|
176 |
|
177 wp.updates.updateLock = true; |
|
178 |
|
179 var data = { |
|
180 _ajax_nonce: wp.updates.ajaxNonce, |
|
181 plugin: plugin, |
|
182 slug: slug, |
|
183 username: wp.updates.filesystemCredentials.ftp.username, |
|
184 password: wp.updates.filesystemCredentials.ftp.password, |
|
185 hostname: wp.updates.filesystemCredentials.ftp.hostname, |
|
186 connection_type: wp.updates.filesystemCredentials.ftp.connectionType, |
|
187 public_key: wp.updates.filesystemCredentials.ssh.publicKey, |
|
188 private_key: wp.updates.filesystemCredentials.ssh.privateKey |
|
189 }; |
|
190 |
|
191 wp.ajax.post( 'update-plugin', data ) |
|
192 .done( wp.updates.updateSuccess ) |
|
193 .fail( wp.updates.updateError ); |
|
194 }; |
|
195 |
|
196 /** |
|
197 * On a successful plugin update, update the UI with the result. |
|
198 * |
|
199 * @since 4.2.0 |
|
200 * |
|
201 * @param {object} response |
|
202 */ |
|
203 wp.updates.updateSuccess = function( response ) { |
|
204 var $updateMessage, name, $pluginRow, newText; |
|
205 if ( 'plugins' === pagenow || 'plugins-network' === pagenow ) { |
|
206 $pluginRow = $( '[data-slug="' + response.slug + '"]' ).first(); |
|
207 $updateMessage = $pluginRow.next().find( '.update-message' ); |
|
208 $pluginRow.addClass( 'updated' ).removeClass( 'update' ); |
|
209 |
|
210 // Update the version number in the row. |
|
211 newText = $pluginRow.find('.plugin-version-author-uri').html().replace( response.oldVersion, response.newVersion ); |
|
212 $pluginRow.find('.plugin-version-author-uri').html( newText ); |
|
213 |
|
214 // Add updated class to update message parent tr |
|
215 $pluginRow.next().addClass( 'updated' ); |
|
216 } else if ( 'plugin-install' === pagenow ) { |
|
217 $updateMessage = $( '.plugin-card-' + response.slug ).find( '.update-now' ); |
|
218 $updateMessage.addClass( 'button-disabled' ); |
|
219 name = $updateMessage.data( 'name' ); |
|
220 $updateMessage.attr( 'aria-label', wp.updates.l10n.updatedLabel.replace( '%s', name ) ); |
|
221 } |
|
222 |
|
223 $updateMessage.removeClass( 'updating-message' ).addClass( 'updated-message' ); |
|
224 $updateMessage.text( wp.updates.l10n.updated ); |
|
225 wp.a11y.speak( wp.updates.l10n.updatedMsg ); |
|
226 |
|
227 wp.updates.decrementCount( 'plugin' ); |
|
228 |
|
229 wp.updates.updateDoneSuccessfully = true; |
|
230 |
|
231 /* |
|
232 * The lock can be released since the update was successful, |
|
233 * and any other updates can commence. |
|
234 */ |
|
235 wp.updates.updateLock = false; |
|
236 |
|
237 $(document).trigger( 'wp-plugin-update-success', response ); |
|
238 |
|
239 wp.updates.queueChecker(); |
|
240 }; |
|
241 |
|
242 |
|
243 /** |
|
244 * On a plugin update error, update the UI appropriately. |
|
245 * |
|
246 * @since 4.2.0 |
|
247 * |
|
248 * @param {object} response |
|
249 */ |
|
250 wp.updates.updateError = function( response ) { |
|
251 var $message, name; |
|
252 wp.updates.updateDoneSuccessfully = false; |
|
253 if ( response.errorCode && response.errorCode == 'unable_to_connect_to_filesystem' ) { |
|
254 wp.updates.credentialError( response, 'update-plugin' ); |
|
255 return; |
|
256 } |
|
257 if ( 'plugins' === pagenow || 'plugins-network' === pagenow ) { |
|
258 $message = $( '[data-slug="' + response.slug + '"]' ).next().find( '.update-message' ); |
|
259 } else if ( 'plugin-install' === pagenow ) { |
|
260 $message = $( '.plugin-card-' + response.slug ).find( '.update-now' ); |
|
261 |
|
262 name = $message.data( 'name' ); |
|
263 $message.attr( 'aria-label', wp.updates.l10n.updateFailedLabel.replace( '%s', name ) ); |
|
264 } |
|
265 $message.removeClass( 'updating-message' ); |
|
266 $message.text( wp.updates.l10n.updateFailed ); |
|
267 wp.a11y.speak( wp.updates.l10n.updateFailed ); |
|
268 |
|
269 $(document).trigger( 'wp-plugin-update-error', response ); |
|
270 }; |
|
271 |
|
272 /** |
|
273 * Show an error message in the request for credentials form. |
|
274 * |
|
275 * @param {string} message |
|
276 * @since 4.2.0 |
|
277 */ |
|
278 wp.updates.showErrorInCredentialsForm = function( message ) { |
|
279 var $modal = $( '.notification-dialog' ); |
|
280 |
|
281 // Remove any existing error. |
|
282 $modal.find( '.error' ).remove(); |
|
283 |
|
284 $modal.find( 'h3' ).after( '<div class="error">' + message + '</div>' ); |
|
285 }; |
|
286 |
|
287 /** |
|
288 * Events that need to happen when there is a credential error |
|
289 * |
|
290 * @since 4.2.0 |
|
291 */ |
|
292 wp.updates.credentialError = function( response, type ) { |
|
293 wp.updates.updateQueue.push( { |
|
294 'type': type, |
|
295 'data': { |
|
296 // Not cool that we're depending on response for this data. |
|
297 // This would feel more whole in a view all tied together. |
|
298 plugin: response.plugin, |
|
299 slug: response.slug |
|
300 } |
|
301 } ); |
|
302 wp.updates.showErrorInCredentialsForm( response.error ); |
|
303 wp.updates.requestFilesystemCredentials(); |
|
304 }; |
|
305 |
|
306 /** |
|
307 * If an update job has been placed in the queue, queueChecker pulls it out and runs it. |
|
308 * |
|
309 * @since 4.2.0 |
|
310 */ |
|
311 wp.updates.queueChecker = function() { |
|
312 if ( wp.updates.updateLock || wp.updates.updateQueue.length <= 0 ) { |
|
313 return; |
|
314 } |
|
315 |
|
316 var job = wp.updates.updateQueue.shift(); |
|
317 |
|
318 wp.updates.updatePlugin( job.data.plugin, job.data.slug ); |
|
319 }; |
|
320 |
|
321 |
|
322 /** |
|
323 * Request the users filesystem credentials if we don't have them already. |
|
324 * |
|
325 * @since 4.2.0 |
|
326 */ |
|
327 wp.updates.requestFilesystemCredentials = function( event ) { |
|
328 if ( wp.updates.updateDoneSuccessfully === false ) { |
|
329 /* |
|
330 * For the plugin install screen, return the focus to the install button |
|
331 * after exiting the credentials request modal. |
|
332 */ |
|
333 if ( 'plugin-install' === pagenow && event ) { |
|
334 wp.updates.$elToReturnFocusToFromCredentialsModal = $( event.target ); |
|
335 } |
|
336 |
|
337 wp.updates.updateLock = true; |
|
338 |
|
339 wp.updates.requestForCredentialsModalOpen(); |
|
340 } |
|
341 }; |
|
342 |
|
343 /** |
|
344 * Keydown handler for the request for credentials modal. |
|
345 * |
|
346 * Close the modal when the escape key is pressed. |
|
347 * Constrain keyboard navigation to inside the modal. |
|
348 * |
|
349 * @since 4.2.0 |
|
350 */ |
|
351 wp.updates.keydown = function( event ) { |
|
352 if ( 27 === event.keyCode ) { |
|
353 wp.updates.requestForCredentialsModalCancel(); |
|
354 } else if ( 9 === event.keyCode ) { |
|
355 // #upgrade button must always be the last focusable element in the dialog. |
|
356 if ( event.target.id === 'upgrade' && ! event.shiftKey ) { |
|
357 $( '#hostname' ).focus(); |
|
358 event.preventDefault(); |
|
359 } else if ( event.target.id === 'hostname' && event.shiftKey ) { |
|
360 $( '#upgrade' ).focus(); |
|
361 event.preventDefault(); |
|
362 } |
|
363 } |
|
364 }; |
|
365 |
|
366 /** |
|
367 * Open the request for credentials modal. |
|
368 * |
|
369 * @since 4.2.0 |
|
370 */ |
|
371 wp.updates.requestForCredentialsModalOpen = function() { |
|
372 var $modal = $( '#request-filesystem-credentials-dialog' ); |
|
373 $( 'body' ).addClass( 'modal-open' ); |
|
374 $modal.show(); |
|
375 |
|
376 $modal.find( 'input:enabled:first' ).focus(); |
|
377 $modal.keydown( wp.updates.keydown ); |
|
378 }; |
|
379 |
|
380 /** |
|
381 * Close the request for credentials modal. |
|
382 * |
|
383 * @since 4.2.0 |
|
384 */ |
|
385 wp.updates.requestForCredentialsModalClose = function() { |
|
386 $( '#request-filesystem-credentials-dialog' ).hide(); |
|
387 $( 'body' ).removeClass( 'modal-open' ); |
|
388 wp.updates.$elToReturnFocusToFromCredentialsModal.focus(); |
|
389 }; |
|
390 |
|
391 /** |
|
392 * The steps that need to happen when the modal is canceled out |
|
393 * |
|
394 * @since 4.2.0 |
|
395 */ |
|
396 wp.updates.requestForCredentialsModalCancel = function() { |
|
397 // no updateLock and no updateQueue means we already have cleared things up |
|
398 var slug, $message; |
|
399 |
|
400 if( wp.updates.updateLock === false && wp.updates.updateQueue.length === 0 ){ |
|
401 return; |
|
402 } |
|
403 |
|
404 slug = wp.updates.updateQueue[0].data.slug, |
|
405 |
|
406 // remove the lock, and clear the queue |
|
407 wp.updates.updateLock = false; |
|
408 wp.updates.updateQueue = []; |
|
409 |
|
410 wp.updates.requestForCredentialsModalClose(); |
|
411 if ( 'plugins' === pagenow || 'plugins-network' === pagenow ) { |
|
412 $message = $( '[data-slug="' + slug + '"]' ).next().find( '.update-message' ); |
|
413 } else if ( 'plugin-install' === pagenow ) { |
|
414 $message = $( '.plugin-card-' + slug ).find( '.update-now' ); |
|
415 } |
|
416 |
|
417 $message.removeClass( 'updating-message' ); |
|
418 $message.html( $message.data( 'originaltext' ) ); |
|
419 wp.a11y.speak( wp.updates.l10n.updateCancel ); |
|
420 }; |
|
421 /** |
|
422 * Potentially add an AYS to a user attempting to leave the page |
|
423 * |
|
424 * If an update is on-going and a user attempts to leave the page, |
|
425 * open an "Are you sure?" alert. |
|
426 * |
|
427 * @since 4.2.0 |
|
428 */ |
|
429 |
|
430 wp.updates.beforeunload = function() { |
|
431 if ( wp.updates.updateLock ) { |
|
432 return wp.updates.l10n.beforeunload; |
|
433 } |
|
434 }; |
|
435 |
|
436 |
|
437 $( document ).ready( function() { |
|
438 /* |
|
439 * Check whether a user needs to submit filesystem credentials based on whether |
|
440 * the form was output on the page server-side. |
|
441 * |
|
442 * @see {wp_print_request_filesystem_credentials_modal() in PHP} |
|
443 */ |
|
444 wp.updates.shouldRequestFilesystemCredentials = ( $( '#request-filesystem-credentials-dialog' ).length <= 0 ) ? false : true; |
|
445 |
|
446 // File system credentials form submit noop-er / handler. |
|
447 $( '#request-filesystem-credentials-dialog form' ).on( 'submit', function() { |
|
448 // Persist the credentials input by the user for the duration of the page load. |
|
449 wp.updates.filesystemCredentials.ftp.hostname = $('#hostname').val(); |
|
450 wp.updates.filesystemCredentials.ftp.username = $('#username').val(); |
|
451 wp.updates.filesystemCredentials.ftp.password = $('#password').val(); |
|
452 wp.updates.filesystemCredentials.ftp.connectionType = $('input[name="connection_type"]:checked').val(); |
|
453 wp.updates.filesystemCredentials.ssh.publicKey = $('#public_key').val(); |
|
454 wp.updates.filesystemCredentials.ssh.privateKey = $('#private_key').val(); |
|
455 |
|
456 wp.updates.requestForCredentialsModalClose(); |
|
457 |
|
458 // Unlock and invoke the queue. |
|
459 wp.updates.updateLock = false; |
|
460 wp.updates.queueChecker(); |
|
461 |
|
462 return false; |
|
463 }); |
|
464 |
|
465 // Close the request credentials modal when |
|
466 $( '#request-filesystem-credentials-dialog [data-js-action="close"], .notification-dialog-background' ).on( 'click', function() { |
|
467 wp.updates.requestForCredentialsModalCancel(); |
|
468 }); |
|
469 |
|
470 // Click handler for plugin updates in List Table view. |
|
471 $( '.plugin-update-tr' ).on( 'click', '.update-link', function( e ) { |
|
472 e.preventDefault(); |
|
473 if ( wp.updates.shouldRequestFilesystemCredentials && ! wp.updates.updateLock ) { |
|
474 wp.updates.requestFilesystemCredentials( e ); |
|
475 } |
|
476 var updateRow = $( e.target ).parents( '.plugin-update-tr' ); |
|
477 // Return the user to the input box of the plugin's table row after closing the modal. |
|
478 wp.updates.$elToReturnFocusToFromCredentialsModal = $( '#' + updateRow.data( 'slug' ) ).find( '.check-column input' ); |
|
479 wp.updates.updatePlugin( updateRow.data( 'plugin' ), updateRow.data( 'slug' ) ); |
|
480 } ); |
|
481 |
|
482 $( '.plugin-card' ).on( 'click', '.update-now', function( e ) { |
|
483 e.preventDefault(); |
|
484 var $button = $( e.target ); |
|
485 |
|
486 if ( wp.updates.shouldRequestFilesystemCredentials && ! wp.updates.updateLock ) { |
|
487 wp.updates.requestFilesystemCredentials( e ); |
|
488 } |
|
489 |
|
490 wp.updates.updatePlugin( $button.data( 'plugin' ), $button.data( 'slug' ) ); |
|
491 } ); |
|
492 |
|
493 // |
|
494 $( '#plugin_update_from_iframe' ).on( 'click' , function( e ) { |
|
495 var target, data; |
|
496 |
|
497 target = window.parent == window ? null : window.parent, |
|
498 $.support.postMessage = !! window.postMessage; |
|
499 |
|
500 if ( $.support.postMessage === false || target === null || window.parent.location.pathname.indexOf( 'update-core.php' ) !== -1 ) |
|
501 return; |
|
502 |
|
503 e.preventDefault(); |
|
504 |
|
505 data = { |
|
506 'action' : 'updatePlugin', |
|
507 'slug' : $(this).data('slug') |
|
508 }; |
|
509 |
|
510 target.postMessage( JSON.stringify( data ), window.location.origin ); |
|
511 }); |
|
512 |
|
513 } ); |
|
514 |
|
515 $( window ).on( 'message', function( e ) { |
|
516 var event = e.originalEvent, |
|
517 message, |
|
518 loc = document.location, |
|
519 expectedOrigin = loc.protocol + '//' + loc.hostname; |
|
520 |
|
521 if ( event.origin !== expectedOrigin ) { |
|
522 return; |
|
523 } |
|
524 |
|
525 message = $.parseJSON( event.data ); |
|
526 |
|
527 if ( typeof message.action === 'undefined' ) { |
|
528 return; |
|
529 } |
|
530 |
|
531 switch (message.action){ |
|
532 case 'decrementUpdateCount' : |
|
533 wp.updates.decrementCount( message.upgradeType ); |
|
534 break; |
|
535 case 'updatePlugin' : |
|
536 tb_remove(); |
|
537 if ( 'plugins' === pagenow || 'plugins-network' === pagenow ) { |
|
538 // Return the user to the input box of the plugin's table row after closing the modal. |
|
539 $( '#' + message.slug ).find( '.check-column input' ).focus(); |
|
540 // trigger the update |
|
541 $( '.plugin-update-tr[data-slug="' + message.slug + '"]' ).find( '.update-link' ).trigger( 'click' ); |
|
542 } else if ( 'plugin-install' === pagenow ) { |
|
543 $( '.plugin-card-' + message.slug ).find( 'h4 a' ).focus(); |
|
544 $( '.plugin-card-' + message.slug ).find( '[data-slug="' + message.slug + '"]' ).trigger( 'click' ); |
|
545 } |
|
546 break; |
|
547 } |
|
548 |
|
549 } ); |
|
550 |
|
551 $( window ).on( 'beforeunload', wp.updates.beforeunload ); |
|
552 |
|
553 })( jQuery, window.wp, window.pagenow, window.ajaxurl ); |