~canonical-sysadmins/wordpress/4.8.1

« back to all changes in this revision

Viewing changes to wp-admin/js/updates.js

  • Committer: Barry Price
  • Date: 2016-08-17 04:50:12 UTC
  • mfrom: (1.1.18 upstream)
  • Revision ID: barry.price@canonical.com-20160817045012-qfui81zhqnqv2ba9
Merge WP4.6 from upstream

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
/* global tb_remove */
2
 
window.wp = window.wp || {};
3
 
 
4
 
(function( $, wp, pagenow ) {
 
1
/**
 
2
 * Functions for ajaxified updates, deletions and installs inside the WordPress admin.
 
3
 *
 
4
 * @version 4.2.0
 
5
 *
 
6
 * @package WordPress
 
7
 * @subpackage Administration
 
8
 */
 
9
 
 
10
/* global pagenow */
 
11
 
 
12
/**
 
13
 * @param {jQuery}  $                                   jQuery object.
 
14
 * @param {object}  wp                                  WP object.
 
15
 * @param {object}  settings                            WP Updates settings.
 
16
 * @param {string}  settings.ajax_nonce                 AJAX nonce.
 
17
 * @param {object}  settings.l10n                       Translation strings.
 
18
 * @param {object=} settings.plugins                    Base names of plugins in their different states.
 
19
 * @param {Array}   settings.plugins.all                Base names of all plugins.
 
20
 * @param {Array}   settings.plugins.active             Base names of active plugins.
 
21
 * @param {Array}   settings.plugins.inactive           Base names of inactive plugins.
 
22
 * @param {Array}   settings.plugins.upgrade            Base names of plugins with updates available.
 
23
 * @param {Array}   settings.plugins.recently_activated Base names of recently activated plugins.
 
24
 * @param {object=} settings.totals                     Plugin/theme status information or null.
 
25
 * @param {number}  settings.totals.all                 Amount of all plugins or themes.
 
26
 * @param {number}  settings.totals.upgrade             Amount of plugins or themes with updates available.
 
27
 * @param {number}  settings.totals.disabled            Amount of disabled themes.
 
28
 */
 
29
(function( $, wp, settings ) {
 
30
        var $document = $( document );
 
31
 
 
32
        wp = wp || {};
 
33
 
 
34
        /**
 
35
         * The WP Updates object.
 
36
         *
 
37
         * @since 4.2.0
 
38
         *
 
39
         * @type {object}
 
40
         */
5
41
        wp.updates = {};
6
42
 
7
43
        /**
9
45
         *
10
46
         * @since 4.2.0
11
47
         *
12
 
         * @var string
 
48
         * @type {string}
13
49
         */
14
 
        wp.updates.ajaxNonce = window._wpUpdatesSettings.ajax_nonce;
 
50
        wp.updates.ajaxNonce = settings.ajax_nonce;
15
51
 
16
52
        /**
17
53
         * Localized strings.
18
54
         *
19
55
         * @since 4.2.0
20
56
         *
21
 
         * @var object
22
 
         */
23
 
        wp.updates.l10n = window._wpUpdatesSettings.l10n;
 
57
         * @type {object}
 
58
         */
 
59
        wp.updates.l10n = settings.l10n;
 
60
 
 
61
        /**
 
62
         * Current search term.
 
63
         *
 
64
         * @since 4.6.0
 
65
         *
 
66
         * @type {string}
 
67
         */
 
68
        wp.updates.searchTerm = '';
24
69
 
25
70
        /**
26
71
         * Whether filesystem credentials need to be requested from the user.
27
72
         *
28
73
         * @since 4.2.0
29
74
         *
30
 
         * @var bool
 
75
         * @type {bool}
31
76
         */
32
 
        wp.updates.shouldRequestFilesystemCredentials = null;
 
77
        wp.updates.shouldRequestFilesystemCredentials = false;
33
78
 
34
79
        /**
35
80
         * Filesystem credentials to be packaged along with the request.
36
81
         *
37
82
         * @since 4.2.0
 
83
         * @since 4.6.0 Added `available` property to indicate whether credentials have been provided.
38
84
         *
39
 
         * @var object
 
85
         * @type {object} filesystemCredentials                    Holds filesystem credentials.
 
86
         * @type {object} filesystemCredentials.ftp                Holds FTP credentials.
 
87
         * @type {string} filesystemCredentials.ftp.host           FTP host. Default empty string.
 
88
         * @type {string} filesystemCredentials.ftp.username       FTP user name. Default empty string.
 
89
         * @type {string} filesystemCredentials.ftp.password       FTP password. Default empty string.
 
90
         * @type {string} filesystemCredentials.ftp.connectionType Type of FTP connection. 'ssh', 'ftp', or 'ftps'.
 
91
         *                                                         Default empty string.
 
92
         * @type {object} filesystemCredentials.ssh                Holds SSH credentials.
 
93
         * @type {string} filesystemCredentials.ssh.publicKey      The public key. Default empty string.
 
94
         * @type {string} filesystemCredentials.ssh.privateKey     The private key. Default empty string.
 
95
         * @type {bool}   filesystemCredentials.available          Whether filesystem credentials have been provided.
 
96
         *                                                         Default 'false'.
40
97
         */
41
98
        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
 
                }
 
99
                ftp:       {
 
100
                        host:           '',
 
101
                        username:       '',
 
102
                        password:       '',
 
103
                        connectionType: ''
 
104
                },
 
105
                ssh:       {
 
106
                        publicKey:  '',
 
107
                        privateKey: ''
 
108
                },
 
109
                available: false
52
110
        };
53
111
 
54
112
        /**
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
 
        /**
 
113
         * Whether we're waiting for an Ajax request to complete.
 
114
         *
 
115
         * @since 4.2.0
 
116
         * @since 4.6.0 More accurately named `ajaxLocked`.
 
117
         *
 
118
         * @type {bool}
 
119
         */
 
120
        wp.updates.ajaxLocked = false;
 
121
 
 
122
        /**
 
123
         * Admin notice template.
 
124
         *
 
125
         * @since 4.6.0
 
126
         *
 
127
         * @type {function} A function that lazily-compiles the template requested.
 
128
         */
 
129
        wp.updates.adminNotice = wp.template( 'wp-updates-admin-notice' );
 
130
 
 
131
        /**
 
132
         * Update queue.
 
133
         *
73
134
         * If the user tries to update a plugin while an update is
74
135
         * already happening, it can be placed in this queue to perform later.
75
136
         *
76
137
         * @since 4.2.0
 
138
         * @since 4.6.0 More accurately named `queue`.
77
139
         *
78
 
         * @var array
 
140
         * @type {Array.object}
79
141
         */
80
 
        wp.updates.updateQueue = [];
 
142
        wp.updates.queue = [];
81
143
 
82
144
        /**
83
 
         * Store a jQuery reference to return focus to when exiting the request credentials modal.
 
145
         * Holds a jQuery reference to return focus to when exiting the request credentials modal.
84
146
         *
85
147
         * @since 4.2.0
86
148
         *
87
 
         * @var jQuery object
88
 
         */
89
 
        wp.updates.$elToReturnFocusToFromCredentialsModal = null;
90
 
 
91
 
        /**
92
 
         * Decrement update counts throughout the various menus.
 
149
         * @type {jQuery}
 
150
         */
 
151
        wp.updates.$elToReturnFocusToFromCredentialsModal = undefined;
 
152
 
 
153
        /**
 
154
         * Adds or updates an admin notice.
 
155
         *
 
156
         * @since 4.6.0
 
157
         *
 
158
         * @param {object}  data
 
159
         * @param {*=}      data.selector      Optional. Selector of an element to be replaced with the admin notice.
 
160
         * @param {string=} data.id            Optional. Unique id that will be used as the notice's id attribute.
 
161
         * @param {string=} data.className     Optional. Class names that will be used in the admin notice.
 
162
         * @param {string=} data.message       Optional. The message displayed in the notice.
 
163
         * @param {number=} data.successes     Optional. The amount of successful operations.
 
164
         * @param {number=} data.errors        Optional. The amount of failed operations.
 
165
         * @param {Array=}  data.errorMessages Optional. Error messages of failed operations.
 
166
         *
 
167
         */
 
168
        wp.updates.addAdminNotice = function( data ) {
 
169
                var $notice = $( data.selector ), $adminNotice;
 
170
 
 
171
                delete data.selector;
 
172
                $adminNotice = wp.updates.adminNotice( data );
 
173
 
 
174
                // Check if this admin notice already exists.
 
175
                if ( ! $notice.length ) {
 
176
                        $notice = $( '#' + data.id );
 
177
                }
 
178
 
 
179
                if ( $notice.length ) {
 
180
                        $notice.replaceWith( $adminNotice );
 
181
                } else {
 
182
                        $( '.wrap' ).find( '> h1' ).after( $adminNotice );
 
183
                }
 
184
 
 
185
                $document.trigger( 'wp-updates-notice-added' );
 
186
        };
 
187
 
 
188
        /**
 
189
         * Handles Ajax requests to WordPress.
 
190
         *
 
191
         * @since 4.6.0
 
192
         *
 
193
         * @param {string} action The type of Ajax request ('update-plugin', 'install-theme', etc).
 
194
         * @param {object} data   Data that needs to be passed to the ajax callback.
 
195
         * @return {$.promise}    A jQuery promise that represents the request,
 
196
         *                        decorated with an abort() method.
 
197
         */
 
198
        wp.updates.ajax = function( action, data ) {
 
199
                var options = {};
 
200
 
 
201
                if ( wp.updates.ajaxLocked ) {
 
202
                        wp.updates.queue.push( {
 
203
                                action: action,
 
204
                                data:   data
 
205
                        } );
 
206
 
 
207
                        // Return a Deferred object so callbacks can always be registered.
 
208
                        return $.Deferred();
 
209
                }
 
210
 
 
211
                wp.updates.ajaxLocked = true;
 
212
 
 
213
                if ( data.success ) {
 
214
                        options.success = data.success;
 
215
                        delete data.success;
 
216
                }
 
217
 
 
218
                if ( data.error ) {
 
219
                        options.error = data.error;
 
220
                        delete data.error;
 
221
                }
 
222
 
 
223
                options.data = _.extend( data, {
 
224
                        action:          action,
 
225
                        _ajax_nonce:     wp.updates.ajaxNonce,
 
226
                        username:        wp.updates.filesystemCredentials.ftp.username,
 
227
                        password:        wp.updates.filesystemCredentials.ftp.password,
 
228
                        hostname:        wp.updates.filesystemCredentials.ftp.hostname,
 
229
                        connection_type: wp.updates.filesystemCredentials.ftp.connectionType,
 
230
                        public_key:      wp.updates.filesystemCredentials.ssh.publicKey,
 
231
                        private_key:     wp.updates.filesystemCredentials.ssh.privateKey
 
232
                } );
 
233
 
 
234
                return wp.ajax.send( options ).always( wp.updates.ajaxAlways );
 
235
        };
 
236
 
 
237
        /**
 
238
         * Actions performed after every Ajax request.
 
239
         *
 
240
         * @since 4.6.0
 
241
         *
 
242
         * @param {object}  response
 
243
         * @param {array=}  response.debug     Optional. Debug information.
 
244
         * @param {string=} response.errorCode Optional. Error code for an error that occurred.
 
245
         */
 
246
        wp.updates.ajaxAlways = function( response ) {
 
247
                if ( ! response.errorCode || 'unable_to_connect_to_filesystem' !== response.errorCode ) {
 
248
                        wp.updates.ajaxLocked = false;
 
249
                        wp.updates.queueChecker();
 
250
                }
 
251
 
 
252
                if ( 'undefined' !== typeof response.debug && window.console && window.console.log ) {
 
253
                        _.map( response.debug, function( message ) {
 
254
                                window.console.log( $( '<p />' ).html( message ).text() );
 
255
                        } );
 
256
                }
 
257
        };
 
258
 
 
259
        /**
 
260
         * Decrements the update counts throughout the various menus.
 
261
         *
 
262
         * This includes the toolbar, the "Updates" menu item and the menu items
 
263
         * for plugins and themes.
93
264
         *
94
265
         * @since 3.9.0
95
266
         *
96
 
         * @param {string} upgradeType
 
267
         * @param {string} type The type of item that was updated or deleted.
 
268
         *                      Can be 'plugin', 'theme'.
97
269
         */
98
 
        wp.updates.decrementCount = function( upgradeType ) {
99
 
                var count,
100
 
                        pluginCount,
101
 
                        $adminBarUpdateCount = $( '#wp-admin-bar-updates .ab-label' ),
 
270
        wp.updates.decrementCount = function( type ) {
 
271
                var $adminBarUpdates             = $( '#wp-admin-bar-updates' ),
102
272
                        $dashboardNavMenuUpdateCount = $( 'a[href="update-core.php"] .update-plugins' ),
103
 
                        $pluginsMenuItem = $( '#menu-plugins' );
104
 
 
105
 
 
106
 
                count = $adminBarUpdateCount.text();
 
273
                        count                        = $adminBarUpdates.find( '.ab-label' ).text(),
 
274
                        $menuItem, $itemCount, itemCount;
 
275
 
107
276
                count = parseInt( count, 10 ) - 1;
 
277
 
108
278
                if ( count < 0 || isNaN( count ) ) {
109
279
                        return;
110
280
                }
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 );
 
281
 
 
282
                $adminBarUpdates.find( '.ab-item' ).removeAttr( 'title' );
 
283
                $adminBarUpdates.find( '.ab-label' ).text( count );
 
284
 
 
285
                // Remove the update count from the toolbar if it's zero.
 
286
                if ( ! count ) {
 
287
                        $adminBarUpdates.find( '.ab-label' ).parents( 'li' ).remove();
 
288
                }
 
289
 
 
290
                // Update the "Updates" menu item.
 
291
                $dashboardNavMenuUpdateCount.each( function( index, element ) {
 
292
                        element.className = element.className.replace( /count-\d+/, 'count-' + count );
117
293
                } );
 
294
 
118
295
                $dashboardNavMenuUpdateCount.removeAttr( 'title' );
119
296
                $dashboardNavMenuUpdateCount.find( '.update-count' ).text( count );
120
297
 
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 );
 
298
                if ( 'plugin' === type ) {
 
299
                        $menuItem  = $( '#menu-plugins' );
 
300
                        $itemCount = $menuItem.find( '.plugin-count' );
 
301
                } else if ( 'theme' === type ) {
 
302
                        $menuItem  = $( '#menu-appearance' );
 
303
                        $itemCount = $menuItem.find( '.theme-count' );
 
304
                }
 
305
 
 
306
                // Decrement the counter of the other menu items.
 
307
                if ( $itemCount ) {
 
308
                        itemCount = $itemCount.eq( 0 ).text();
 
309
                        itemCount = parseInt( itemCount, 10 ) - 1;
 
310
                }
 
311
 
 
312
                if ( itemCount < 0 || isNaN( itemCount ) ) {
 
313
                        return;
 
314
                }
 
315
 
 
316
                if ( itemCount > 0 ) {
 
317
                        $( '.subsubsub .upgrade .count' ).text( '(' + itemCount + ')' );
 
318
 
 
319
                        $itemCount.text( itemCount );
 
320
                        $menuItem.find( '.update-plugins' ).each( function( index, element ) {
 
321
                                element.className = element.className.replace( /count-\d+/, 'count-' + itemCount );
130
322
                        } );
131
 
 
132
 
                        if (pluginCount > 0 ) {
133
 
                                $( '.subsubsub .upgrade .count' ).text( '(' + pluginCount + ')' );
134
 
                        } else {
135
 
                                $( '.subsubsub .upgrade' ).remove();
136
 
                        }
 
323
                } else {
 
324
                        $( '.subsubsub .upgrade' ).remove();
 
325
                        $menuItem.find( '.update-plugins' ).remove();
137
326
                }
138
327
        };
139
328
 
140
329
        /**
141
 
         * Send an Ajax request to the server to update a plugin.
 
330
         * Sends an Ajax request to the server to update a plugin.
142
331
         *
143
332
         * @since 4.2.0
 
333
         * @since 4.6.0 More accurately named `updatePlugin`.
144
334
         *
145
 
         * @param {string} plugin
146
 
         * @param {string} slug
 
335
         * @param {object}               args         Arguments.
 
336
         * @param {string}               args.plugin  Plugin basename.
 
337
         * @param {string}               args.slug    Plugin slug.
 
338
         * @param {updatePluginSuccess=} args.success Optional. Success callback. Default: wp.updates.updatePluginSuccess
 
339
         * @param {updatePluginError=}   args.error   Optional. Error callback. Default: wp.updates.updatePluginError
 
340
         * @return {$.promise} A jQuery promise that represents the request,
 
341
         *                     decorated with an abort() method.
147
342
         */
148
 
        wp.updates.updatePlugin = function( plugin, slug ) {
149
 
                var $message, name,
150
 
                        $card = $( '.plugin-card-' + slug );
 
343
        wp.updates.updatePlugin = function( args ) {
 
344
                var $updateRow, $card, $message, message;
 
345
 
 
346
                args = _.extend( {
 
347
                        success: wp.updates.updatePluginSuccess,
 
348
                        error: wp.updates.updatePluginError
 
349
                }, args );
151
350
 
152
351
                if ( 'plugins' === pagenow || 'plugins-network' === pagenow ) {
153
 
                        $message = $( '[data-plugin="' + plugin + '"]' ).next().find( '.update-message' );
154
 
                } else if ( 'plugin-install' === pagenow ) {
155
 
                        $message = $card.find( '.update-now' );
156
 
                        name = $message.data( 'name' );
157
 
                        $message.attr( 'aria-label', wp.updates.l10n.updatingLabel.replace( '%s', name ) );
 
352
                        $updateRow = $( 'tr[data-plugin="' + args.plugin + '"]' );
 
353
                        $message   = $updateRow.find( '.update-message' ).removeClass( 'notice-error' ).addClass( 'updating-message notice-warning' ).find( 'p' );
 
354
                        message    = wp.updates.l10n.updatingLabel.replace( '%s', $updateRow.find( '.plugin-title strong' ).text() );
 
355
                } else if ( 'plugin-install' === pagenow || 'plugin-install-network' === pagenow ) {
 
356
                        $card    = $( '.plugin-card-' + args.slug );
 
357
                        $message = $card.find( '.update-now' ).addClass( 'updating-message' );
 
358
                        message  = wp.updates.l10n.updatingLabel.replace( '%s', $message.data( 'name' ) );
 
359
 
158
360
                        // Remove previous error messages, if any.
159
361
                        $card.removeClass( 'plugin-card-update-failed' ).find( '.notice.notice-error' ).remove();
160
362
                }
161
363
 
162
 
                $message.addClass( 'updating-message' );
163
 
                if ( $message.html() !== wp.updates.l10n.updating ){
 
364
                if ( $message.html() !== wp.updates.l10n.updating ) {
164
365
                        $message.data( 'originaltext', $message.html() );
165
366
                }
166
367
 
167
 
                $message.text( wp.updates.l10n.updating );
168
 
                wp.a11y.speak( wp.updates.l10n.updatingMsg );
169
 
 
170
 
                if ( wp.updates.updateLock ) {
171
 
                        wp.updates.updateQueue.push( {
172
 
                                type: 'update-plugin',
173
 
                                data: {
174
 
                                        plugin: plugin,
175
 
                                        slug: slug
176
 
                                }
177
 
                        } );
178
 
                        return;
179
 
                }
180
 
 
181
 
                wp.updates.updateLock = true;
182
 
 
183
 
                var data = {
184
 
                        _ajax_nonce:     wp.updates.ajaxNonce,
185
 
                        plugin:          plugin,
186
 
                        slug:            slug,
187
 
                        username:        wp.updates.filesystemCredentials.ftp.username,
188
 
                        password:        wp.updates.filesystemCredentials.ftp.password,
189
 
                        hostname:        wp.updates.filesystemCredentials.ftp.hostname,
190
 
                        connection_type: wp.updates.filesystemCredentials.ftp.connectionType,
191
 
                        public_key:      wp.updates.filesystemCredentials.ssh.publicKey,
192
 
                        private_key:     wp.updates.filesystemCredentials.ssh.privateKey
193
 
                };
194
 
 
195
 
                wp.ajax.post( 'update-plugin', data )
196
 
                        .done( wp.updates.updateSuccess )
197
 
                        .fail( wp.updates.updateError );
 
368
                $message
 
369
                        .attr( 'aria-label', message )
 
370
                        .text( wp.updates.l10n.updating );
 
371
 
 
372
                $document.trigger( 'wp-plugin-updating', args );
 
373
 
 
374
                return wp.updates.ajax( 'update-plugin', args );
198
375
        };
199
376
 
200
377
        /**
201
 
         * On a successful plugin update, update the UI with the result.
 
378
         * Updates the UI appropriately after a successful plugin update.
202
379
         *
203
380
         * @since 4.2.0
 
381
         * @since 4.6.0 More accurately named `updatePluginSuccess`.
204
382
         *
205
 
         * @param {object} response
 
383
         * @typedef {object} updatePluginSuccess
 
384
         * @param {object} response            Response from the server.
 
385
         * @param {string} response.slug       Slug of the plugin to be updated.
 
386
         * @param {string} response.plugin     Basename of the plugin to be updated.
 
387
         * @param {string} response.pluginName Name of the plugin to be updated.
 
388
         * @param {string} response.oldVersion Old version of the plugin.
 
389
         * @param {string} response.newVersion New version of the plugin.
206
390
         */
207
 
        wp.updates.updateSuccess = function( response ) {
208
 
                var $updateMessage, name, $pluginRow, newText;
 
391
        wp.updates.updatePluginSuccess = function( response ) {
 
392
                var $pluginRow, $updateMessage, newText;
 
393
 
209
394
                if ( 'plugins' === pagenow || 'plugins-network' === pagenow ) {
210
 
                        $pluginRow = $( '[data-plugin="' + response.plugin + '"]' ).first();
211
 
                        $updateMessage = $pluginRow.next().find( '.update-message' );
212
 
                        $pluginRow.addClass( 'updated' ).removeClass( 'update' );
 
395
                        $pluginRow     = $( 'tr[data-plugin="' + response.plugin + '"]' )
 
396
                                .removeClass( 'update' )
 
397
                                .addClass( 'updated' );
 
398
                        $updateMessage = $pluginRow.find( '.update-message' )
 
399
                                .removeClass( 'updating-message notice-warning' )
 
400
                                .addClass( 'updated-message notice-success' ).find( 'p' );
213
401
 
214
402
                        // Update the version number in the row.
215
 
                        newText = $pluginRow.find('.plugin-version-author-uri').html().replace( response.oldVersion, response.newVersion );
216
 
                        $pluginRow.find('.plugin-version-author-uri').html( newText );
217
 
 
218
 
                        // Add updated class to update message parent tr
219
 
                        $pluginRow.next().addClass( 'updated' );
220
 
                } else if ( 'plugin-install' === pagenow ) {
221
 
                        $updateMessage = $( '.plugin-card-' + response.slug ).find( '.update-now' );
222
 
                        $updateMessage.addClass( 'button-disabled' );
223
 
                        name = $updateMessage.data( 'name' );
224
 
                        $updateMessage.attr( 'aria-label', wp.updates.l10n.updatedLabel.replace( '%s', name ) );
 
403
                        newText = $pluginRow.find( '.plugin-version-author-uri' ).html().replace( response.oldVersion, response.newVersion );
 
404
                        $pluginRow.find( '.plugin-version-author-uri' ).html( newText );
 
405
                } else if ( 'plugin-install' === pagenow || 'plugin-install-network' === pagenow ) {
 
406
                        $updateMessage = $( '.plugin-card-' + response.slug ).find( '.update-now' )
 
407
                                .removeClass( 'updating-message' )
 
408
                                .addClass( 'button-disabled updated-message' );
225
409
                }
226
410
 
227
 
                $updateMessage.removeClass( 'updating-message' ).addClass( 'updated-message' );
228
 
                $updateMessage.text( wp.updates.l10n.updated );
229
 
                wp.a11y.speak( wp.updates.l10n.updatedMsg );
 
411
                $updateMessage
 
412
                        .attr( 'aria-label', wp.updates.l10n.updatedLabel.replace( '%s', response.pluginName ) )
 
413
                        .text( wp.updates.l10n.updated );
 
414
 
 
415
                wp.a11y.speak( wp.updates.l10n.updatedMsg, 'polite' );
230
416
 
231
417
                wp.updates.decrementCount( 'plugin' );
232
418
 
233
 
                wp.updates.updateDoneSuccessfully = true;
234
 
 
235
 
                /*
236
 
                 * The lock can be released since the update was successful,
237
 
                 * and any other updates can commence.
238
 
                 */
239
 
                wp.updates.updateLock = false;
240
 
 
241
 
                $(document).trigger( 'wp-plugin-update-success', response );
242
 
 
243
 
                wp.updates.queueChecker();
 
419
                $document.trigger( 'wp-plugin-update-success', response );
244
420
        };
245
421
 
246
 
 
247
422
        /**
248
 
         * On a plugin update error, update the UI appropriately.
 
423
         * Updates the UI appropriately after a failed plugin update.
249
424
         *
250
425
         * @since 4.2.0
 
426
         * @since 4.6.0 More accurately named `updatePluginError`.
251
427
         *
252
 
         * @param {object} response
 
428
         * @typedef {object} updatePluginError
 
429
         * @param {object}  response              Response from the server.
 
430
         * @param {string}  response.slug         Slug of the plugin to be updated.
 
431
         * @param {string}  response.plugin       Basename of the plugin to be updated.
 
432
         * @param {string=} response.pluginName   Optional. Name of the plugin to be updated.
 
433
         * @param {string}  response.errorCode    Error code for the error that occurred.
 
434
         * @param {string}  response.errorMessage The error that occurred.
253
435
         */
254
 
        wp.updates.updateError = function( response ) {
255
 
                var $card = $( '.plugin-card-' + response.slug ),
256
 
                        $message,
257
 
                        $button,
258
 
                        name,
259
 
                        error_message;
260
 
 
261
 
                wp.updates.updateDoneSuccessfully = false;
262
 
 
263
 
                if ( response.errorCode && response.errorCode == 'unable_to_connect_to_filesystem' && wp.updates.shouldRequestFilesystemCredentials ) {
264
 
                        wp.updates.credentialError( response, 'update-plugin' );
265
 
                        return;
266
 
                }
267
 
 
268
 
                error_message = wp.updates.l10n.updateFailed.replace( '%s', response.error );
 
436
        wp.updates.updatePluginError = function( response ) {
 
437
                var $card, $message, errorMessage;
 
438
 
 
439
                if ( ! wp.updates.isValidResponse( response, 'update' ) ) {
 
440
                        return;
 
441
                }
 
442
 
 
443
                if ( wp.updates.maybeHandleCredentialError( response, 'update-plugin' ) ) {
 
444
                        return;
 
445
                }
 
446
 
 
447
                errorMessage = wp.updates.l10n.updateFailed.replace( '%s', response.errorMessage );
269
448
 
270
449
                if ( 'plugins' === pagenow || 'plugins-network' === pagenow ) {
271
 
                        $message = $( '[data-plugin="' + response.plugin + '"]' ).next().find( '.update-message' );
272
 
                        $message.html( error_message ).removeClass( 'updating-message' );
273
 
                } else if ( 'plugin-install' === pagenow ) {
274
 
                        $button = $card.find( '.update-now' );
275
 
                        name = $button.data( 'name' );
 
450
                        if ( response.plugin ) {
 
451
                                $message = $( 'tr[data-plugin="' + response.plugin + '"]' ).find( '.update-message' );
 
452
                        } else {
 
453
                                $message = $( 'tr[data-slug="' + response.slug + '"]' ).find( '.update-message' );
 
454
                        }
 
455
                        $message.removeClass( 'updating-message notice-warning' ).addClass( 'notice-error' ).find( 'p' ).html( errorMessage );
276
456
 
277
 
                        $card
 
457
                        if ( response.pluginName ) {
 
458
                                $message.find( 'p' )
 
459
                                        .attr( 'aria-label', wp.updates.l10n.updateFailedLabel.replace( '%s', response.pluginName ) );
 
460
                        } else {
 
461
                                $message.find( 'p' ).removeAttr( 'aria-label' );
 
462
                        }
 
463
                } else if ( 'plugin-install' === pagenow || 'plugin-install-network' === pagenow ) {
 
464
                        $card = $( '.plugin-card-' + response.slug )
278
465
                                .addClass( 'plugin-card-update-failed' )
279
 
                                .append( '<div class="notice notice-error is-dismissible"><p>' + error_message + '</p></div>' );
280
 
 
281
 
                        $button
282
 
                                .attr( 'aria-label', wp.updates.l10n.updateFailedLabel.replace( '%s', name ) )
283
 
                                .html( wp.updates.l10n.updateFailedShort ).removeClass( 'updating-message' );
 
466
                                .append( wp.updates.adminNotice( {
 
467
                                        className: 'update-message notice-error notice-alt is-dismissible',
 
468
                                        message:   errorMessage
 
469
                                } ) );
 
470
 
 
471
                        $card.find( '.update-now' )
 
472
                                .text( wp.updates.l10n.updateFailedShort ).removeClass( 'updating-message' );
 
473
 
 
474
                        if ( response.pluginName ) {
 
475
                                $card.find( '.update-now' )
 
476
                                        .attr( 'aria-label', wp.updates.l10n.updateFailedLabel.replace( '%s', response.pluginName ) );
 
477
                        } else {
 
478
                                $card.find( '.update-now' ).removeAttr( 'aria-label' );
 
479
                        }
284
480
 
285
481
                        $card.on( 'click', '.notice.is-dismissible .notice-dismiss', function() {
 
482
 
286
483
                                // Use same delay as the total duration of the notice fadeTo + slideUp animation.
287
484
                                setTimeout( function() {
288
485
                                        $card
289
486
                                                .removeClass( 'plugin-card-update-failed' )
290
487
                                                .find( '.column-name a' ).focus();
 
488
 
 
489
                                        $card.find( '.update-now' )
 
490
                                                .attr( 'aria-label', false )
 
491
                                                .text( wp.updates.l10n.updateNow );
291
492
                                }, 200 );
292
 
                        });
293
 
                }
294
 
 
295
 
                wp.a11y.speak( error_message, 'assertive' );
296
 
 
297
 
                /*
298
 
                 * The lock can be released since this failure was
299
 
                 * after the credentials form.
300
 
                 */
301
 
                wp.updates.updateLock = false;
302
 
 
303
 
                $(document).trigger( 'wp-plugin-update-error', response );
304
 
 
305
 
                wp.updates.queueChecker();
306
 
        };
307
 
 
308
 
        /**
309
 
         * Show an error message in the request for credentials form.
310
 
         *
311
 
         * @param {string} message
312
 
         * @since 4.2.0
313
 
         */
314
 
        wp.updates.showErrorInCredentialsForm = function( message ) {
315
 
                var $modal = $( '.notification-dialog' );
316
 
 
317
 
                // Remove any existing error.
318
 
                $modal.find( '.error' ).remove();
319
 
 
320
 
                $modal.find( 'h3' ).after( '<div class="error">' + message + '</div>' );
321
 
        };
322
 
 
323
 
        /**
324
 
         * Events that need to happen when there is a credential error
325
 
         *
326
 
         * @since 4.2.0
327
 
         */
328
 
        wp.updates.credentialError = function( response, type ) {
329
 
                wp.updates.updateQueue.push( {
330
 
                        'type': type,
331
 
                        'data': {
332
 
                                // Not cool that we're depending on response for this data.
333
 
                                // This would feel more whole in a view all tied together.
334
 
                                plugin: response.plugin,
335
 
                                slug: response.slug
336
 
                        }
337
 
                } );
338
 
                wp.updates.showErrorInCredentialsForm( response.error );
339
 
                wp.updates.requestFilesystemCredentials();
340
 
        };
341
 
 
342
 
        /**
343
 
         * If an update job has been placed in the queue, queueChecker pulls it out and runs it.
344
 
         *
345
 
         * @since 4.2.0
 
493
                        } );
 
494
                }
 
495
 
 
496
                wp.a11y.speak( errorMessage, 'assertive' );
 
497
 
 
498
                $document.trigger( 'wp-plugin-update-error', response );
 
499
        };
 
500
 
 
501
        /**
 
502
         * Sends an Ajax request to the server to install a plugin.
 
503
         *
 
504
         * @since 4.6.0
 
505
         *
 
506
         * @param {object}                args         Arguments.
 
507
         * @param {string}                args.slug    Plugin identifier in the WordPress.org Plugin repository.
 
508
         * @param {installPluginSuccess=} args.success Optional. Success callback. Default: wp.updates.installPluginSuccess
 
509
         * @param {installPluginError=}   args.error   Optional. Error callback. Default: wp.updates.installPluginError
 
510
         * @return {$.promise} A jQuery promise that represents the request,
 
511
         *                     decorated with an abort() method.
 
512
         */
 
513
        wp.updates.installPlugin = function( args ) {
 
514
                var $card    = $( '.plugin-card-' + args.slug ),
 
515
                        $message = $card.find( '.install-now' );
 
516
 
 
517
                args = _.extend( {
 
518
                        success: wp.updates.installPluginSuccess,
 
519
                        error: wp.updates.installPluginError
 
520
                }, args );
 
521
 
 
522
                if ( 'import' === pagenow ) {
 
523
                        $message = $( '[data-slug="' + args.slug + '"]' );
 
524
                }
 
525
 
 
526
                if ( $message.html() !== wp.updates.l10n.installing ) {
 
527
                        $message.data( 'originaltext', $message.html() );
 
528
                }
 
529
 
 
530
                $message
 
531
                        .addClass( 'updating-message' )
 
532
                        .attr( 'aria-label', wp.updates.l10n.pluginInstallingLabel.replace( '%s', $message.data( 'name' ) ) )
 
533
                        .text( wp.updates.l10n.installing );
 
534
 
 
535
                wp.a11y.speak( wp.updates.l10n.installingMsg, 'polite' );
 
536
 
 
537
                // Remove previous error messages, if any.
 
538
                $card.removeClass( 'plugin-card-install-failed' ).find( '.notice.notice-error' ).remove();
 
539
 
 
540
                $document.trigger( 'wp-plugin-installing', args );
 
541
 
 
542
                return wp.updates.ajax( 'install-plugin', args );
 
543
        };
 
544
 
 
545
        /**
 
546
         * Updates the UI appropriately after a successful plugin install.
 
547
         *
 
548
         * @since 4.6.0
 
549
         *
 
550
         * @typedef {object} installPluginSuccess
 
551
         * @param {object} response             Response from the server.
 
552
         * @param {string} response.slug        Slug of the installed plugin.
 
553
         * @param {string} response.pluginName  Name of the installed plugin.
 
554
         * @param {string} response.activateUrl URL to activate the just installed plugin.
 
555
         */
 
556
        wp.updates.installPluginSuccess = function( response ) {
 
557
                var $message = $( '.plugin-card-' + response.slug ).find( '.install-now' );
 
558
 
 
559
                $message
 
560
                        .removeClass( 'updating-message' )
 
561
                        .addClass( 'updated-message installed button-disabled' )
 
562
                        .attr( 'aria-label', wp.updates.l10n.pluginInstalledLabel.replace( '%s', response.pluginName ) )
 
563
                        .text( wp.updates.l10n.installed );
 
564
 
 
565
                wp.a11y.speak( wp.updates.l10n.installedMsg, 'polite' );
 
566
 
 
567
                $document.trigger( 'wp-plugin-install-success', response );
 
568
 
 
569
                if ( response.activateUrl ) {
 
570
                        setTimeout( function() {
 
571
 
 
572
                                // Transform the 'Install' button into an 'Activate' button.
 
573
                                $message.removeClass( 'install-now installed button-disabled updated-message' ).addClass( 'activate-now button-primary' )
 
574
                                        .attr( 'href', response.activateUrl )
 
575
                                        .attr( 'aria-label', wp.updates.l10n.activatePluginLabel.replace( '%s', response.pluginName ) )
 
576
                                        .text( wp.updates.l10n.activatePlugin );
 
577
                        }, 1000 );
 
578
                }
 
579
        };
 
580
 
 
581
        /**
 
582
         * Updates the UI appropriately after a failed plugin install.
 
583
         *
 
584
         * @since 4.6.0
 
585
         *
 
586
         * @typedef {object} installPluginError
 
587
         * @param {object}  response              Response from the server.
 
588
         * @param {string}  response.slug         Slug of the plugin to be installed.
 
589
         * @param {string=} response.pluginName   Optional. Name of the plugin to be installed.
 
590
         * @param {string}  response.errorCode    Error code for the error that occurred.
 
591
         * @param {string}  response.errorMessage The error that occurred.
 
592
         */
 
593
        wp.updates.installPluginError = function( response ) {
 
594
                var $card   = $( '.plugin-card-' + response.slug ),
 
595
                        $button = $card.find( '.install-now' ),
 
596
                        errorMessage;
 
597
 
 
598
                if ( ! wp.updates.isValidResponse( response, 'install' ) ) {
 
599
                        return;
 
600
                }
 
601
 
 
602
                if ( wp.updates.maybeHandleCredentialError( response, 'install-plugin' ) ) {
 
603
                        return;
 
604
                }
 
605
 
 
606
                errorMessage = wp.updates.l10n.installFailed.replace( '%s', response.errorMessage );
 
607
 
 
608
                $card
 
609
                        .addClass( 'plugin-card-update-failed' )
 
610
                        .append( '<div class="notice notice-error notice-alt is-dismissible"><p>' + errorMessage + '</p></div>' );
 
611
 
 
612
                $card.on( 'click', '.notice.is-dismissible .notice-dismiss', function() {
 
613
 
 
614
                        // Use same delay as the total duration of the notice fadeTo + slideUp animation.
 
615
                        setTimeout( function() {
 
616
                                $card
 
617
                                        .removeClass( 'plugin-card-update-failed' )
 
618
                                        .find( '.column-name a' ).focus();
 
619
                        }, 200 );
 
620
                } );
 
621
 
 
622
                $button
 
623
                        .removeClass( 'updating-message' ).addClass( 'button-disabled' )
 
624
                        .attr( 'aria-label', wp.updates.l10n.pluginInstallFailedLabel.replace( '%s', $button.data( 'name' ) ) )
 
625
                        .text( wp.updates.l10n.installFailedShort );
 
626
 
 
627
                wp.a11y.speak( errorMessage, 'assertive' );
 
628
 
 
629
                $document.trigger( 'wp-plugin-install-error', response );
 
630
        };
 
631
 
 
632
        /**
 
633
         * Updates the UI appropriately after a successful importer install.
 
634
         *
 
635
         * @since 4.6.0
 
636
         *
 
637
         * @typedef {object} installImporterSuccess
 
638
         * @param {object} response             Response from the server.
 
639
         * @param {string} response.slug        Slug of the installed plugin.
 
640
         * @param {string} response.pluginName  Name of the installed plugin.
 
641
         * @param {string} response.activateUrl URL to activate the just installed plugin.
 
642
         */
 
643
        wp.updates.installImporterSuccess = function( response ) {
 
644
                wp.updates.addAdminNotice( {
 
645
                        id:        'install-success',
 
646
                        className: 'notice-success is-dismissible',
 
647
                        message:   wp.updates.l10n.importerInstalledMsg.replace( '%s', response.activateUrl + '&from=import' )
 
648
                } );
 
649
 
 
650
                $( '[data-slug="' + response.slug + '"]' )
 
651
                        .removeClass( 'install-now updating-message' )
 
652
                        .addClass( 'activate-now' )
 
653
                        .attr({
 
654
                                'href': response.activateUrl + '&from=import',
 
655
                                'aria-label': wp.updates.l10n.activateImporterLabel.replace( '%s', response.pluginName )
 
656
                        })
 
657
                        .text( wp.updates.l10n.activateImporter );
 
658
 
 
659
                wp.a11y.speak( wp.updates.l10n.installedMsg, 'polite' );
 
660
 
 
661
                $document.trigger( 'wp-importer-install-success', response );
 
662
        };
 
663
 
 
664
        /**
 
665
         * Updates the UI appropriately after a failed importer install.
 
666
         *
 
667
         * @since 4.6.0
 
668
         *
 
669
         * @typedef {object} installImporterError
 
670
         * @param {object}  response              Response from the server.
 
671
         * @param {string}  response.slug         Slug of the plugin to be installed.
 
672
         * @param {string=} response.pluginName   Optional. Name of the plugin to be installed.
 
673
         * @param {string}  response.errorCode    Error code for the error that occurred.
 
674
         * @param {string}  response.errorMessage The error that occurred.
 
675
         */
 
676
        wp.updates.installImporterError = function( response ) {
 
677
                var errorMessage = wp.updates.l10n.installFailed.replace( '%s', response.errorMessage ),
 
678
                        $installLink = $( '[data-slug="' + response.slug + '"]' ),
 
679
                        pluginName = $installLink.data( 'name' );
 
680
 
 
681
                if ( ! wp.updates.isValidResponse( response, 'install' ) ) {
 
682
                        return;
 
683
                }
 
684
 
 
685
                if ( wp.updates.maybeHandleCredentialError( response, 'install-plugin' ) ) {
 
686
                        return;
 
687
                }
 
688
 
 
689
                wp.updates.addAdminNotice( {
 
690
                        id:        response.errorCode,
 
691
                        className: 'notice-error is-dismissible',
 
692
                        message:   errorMessage
 
693
                } );
 
694
 
 
695
                $installLink
 
696
                        .removeClass( 'updating-message' )
 
697
                        .text( wp.updates.l10n.installNow )
 
698
                        .attr( 'aria-label', wp.updates.l10n.installNowLabel.replace( '%s', pluginName ) );
 
699
 
 
700
                wp.a11y.speak( errorMessage, 'assertive' );
 
701
 
 
702
                $document.trigger( 'wp-importer-install-error', response );
 
703
        };
 
704
 
 
705
        /**
 
706
         * Sends an Ajax request to the server to delete a plugin.
 
707
         *
 
708
         * @since 4.6.0
 
709
         *
 
710
         * @param {object}               args         Arguments.
 
711
         * @param {string}               args.plugin  Basename of the plugin to be deleted.
 
712
         * @param {string}               args.slug    Slug of the plugin to be deleted.
 
713
         * @param {deletePluginSuccess=} args.success Optional. Success callback. Default: wp.updates.deletePluginSuccess
 
714
         * @param {deletePluginError=}   args.error   Optional. Error callback. Default: wp.updates.deletePluginError
 
715
         * @return {$.promise} A jQuery promise that represents the request,
 
716
         *                     decorated with an abort() method.
 
717
         */
 
718
        wp.updates.deletePlugin = function( args ) {
 
719
                var $link = $( '[data-plugin="' + args.plugin + '"]' ).find( '.row-actions a.delete' );
 
720
 
 
721
                args = _.extend( {
 
722
                        success: wp.updates.deletePluginSuccess,
 
723
                        error: wp.updates.deletePluginError
 
724
                }, args );
 
725
 
 
726
                if ( $link.html() !== wp.updates.l10n.deleting ) {
 
727
                        $link
 
728
                                .data( 'originaltext', $link.html() )
 
729
                                .text( wp.updates.l10n.deleting );
 
730
                }
 
731
 
 
732
                wp.a11y.speak( wp.updates.l10n.deleting, 'polite' );
 
733
 
 
734
                $document.trigger( 'wp-plugin-deleting', args );
 
735
 
 
736
                return wp.updates.ajax( 'delete-plugin', args );
 
737
        };
 
738
 
 
739
        /**
 
740
         * Updates the UI appropriately after a successful plugin deletion.
 
741
         *
 
742
         * @since 4.6.0
 
743
         *
 
744
         * @typedef {object} deletePluginSuccess
 
745
         * @param {object} response            Response from the server.
 
746
         * @param {string} response.slug       Slug of the plugin that was deleted.
 
747
         * @param {string} response.plugin     Base name of the plugin that was deleted.
 
748
         * @param {string} response.pluginName Name of the plugin that was deleted.
 
749
         */
 
750
        wp.updates.deletePluginSuccess = function( response ) {
 
751
 
 
752
                // Removes the plugin and updates rows.
 
753
                $( '[data-plugin="' + response.plugin + '"]' ).css( { backgroundColor: '#faafaa' } ).fadeOut( 350, function() {
 
754
                        var $form            = $( '#bulk-action-form' ),
 
755
                                $views           = $( '.subsubsub' ),
 
756
                                $pluginRow       = $( this ),
 
757
                                columnCount      = $form.find( 'thead th:not(.hidden), thead td' ).length,
 
758
                                pluginDeletedRow = wp.template( 'item-deleted-row' ),
 
759
                                /** @type {object} plugins Base names of plugins in their different states. */
 
760
                                plugins          = settings.plugins;
 
761
 
 
762
                        // Add a success message after deleting a plugin.
 
763
                        if ( ! $pluginRow.hasClass( 'plugin-update-tr' ) ) {
 
764
                                $pluginRow.after(
 
765
                                        pluginDeletedRow( {
 
766
                                                slug:    response.slug,
 
767
                                                plugin:  response.plugin,
 
768
                                                colspan: columnCount,
 
769
                                                name:    response.pluginName
 
770
                                        } )
 
771
                                );
 
772
                        }
 
773
 
 
774
                        $pluginRow.remove();
 
775
 
 
776
                        // Remove plugin from update count.
 
777
                        if ( -1 !== _.indexOf( plugins.upgrade, response.plugin ) ) {
 
778
                                plugins.upgrade = _.without( plugins.upgrade, response.plugin );
 
779
                                wp.updates.decrementCount( 'plugin' );
 
780
                        }
 
781
 
 
782
                        // Remove from views.
 
783
                        if ( -1 !== _.indexOf( plugins.inactive, response.plugin ) ) {
 
784
                                plugins.inactive = _.without( plugins.inactive, response.plugin );
 
785
                                if ( plugins.inactive.length ) {
 
786
                                        $views.find( '.inactive .count' ).text( '(' + plugins.inactive.length + ')' );
 
787
                                } else {
 
788
                                        $views.find( '.inactive' ).remove();
 
789
                                }
 
790
                        }
 
791
 
 
792
                        if ( -1 !== _.indexOf( plugins.active, response.plugin ) ) {
 
793
                                plugins.active = _.without( plugins.active, response.plugin );
 
794
                                if ( plugins.active.length ) {
 
795
                                        $views.find( '.active .count' ).text( '(' + plugins.active.length + ')' );
 
796
                                } else {
 
797
                                        $views.find( '.active' ).remove();
 
798
                                }
 
799
                        }
 
800
 
 
801
                        if ( -1 !== _.indexOf( plugins.recently_activated, response.plugin ) ) {
 
802
                                plugins.recently_activated = _.without( plugins.recently_activated, response.plugin );
 
803
                                if ( plugins.recently_activated.length ) {
 
804
                                        $views.find( '.recently_activated .count' ).text( '(' + plugins.recently_activated.length + ')' );
 
805
                                } else {
 
806
                                        $views.find( '.recently_activated' ).remove();
 
807
                                }
 
808
                        }
 
809
 
 
810
                        plugins.all = _.without( plugins.all, response.plugin );
 
811
 
 
812
                        if ( plugins.all.length ) {
 
813
                                $views.find( '.all .count' ).text( '(' + plugins.all.length + ')' );
 
814
                        } else {
 
815
                                $form.find( '.tablenav' ).css( { visibility: 'hidden' } );
 
816
                                $views.find( '.all' ).remove();
 
817
 
 
818
                                if ( ! $form.find( 'tr.no-items' ).length ) {
 
819
                                        $form.find( '#the-list' ).append( '<tr class="no-items"><td class="colspanchange" colspan="' + columnCount + '">' + wp.updates.l10n.noPlugins + '</td></tr>' );
 
820
                                }
 
821
                        }
 
822
                } );
 
823
 
 
824
                wp.a11y.speak( wp.updates.l10n.deleted, 'polite' );
 
825
 
 
826
                $document.trigger( 'wp-plugin-delete-success', response );
 
827
        };
 
828
 
 
829
        /**
 
830
         * Updates the UI appropriately after a failed plugin deletion.
 
831
         *
 
832
         * @since 4.6.0
 
833
         *
 
834
         * @typedef {object} deletePluginError
 
835
         * @param {object}  response              Response from the server.
 
836
         * @param {string}  response.slug         Slug of the plugin to be deleted.
 
837
         * @param {string}  response.plugin       Base name of the plugin to be deleted
 
838
         * @param {string=} response.pluginName   Optional. Name of the plugin to be deleted.
 
839
         * @param {string}  response.errorCode    Error code for the error that occurred.
 
840
         * @param {string}  response.errorMessage The error that occurred.
 
841
         */
 
842
        wp.updates.deletePluginError = function( response ) {
 
843
                var $plugin, $pluginUpdateRow,
 
844
                        pluginUpdateRow  = wp.template( 'item-update-row' ),
 
845
                        noticeContent    = wp.updates.adminNotice( {
 
846
                                className: 'update-message notice-error notice-alt',
 
847
                                message:   response.errorMessage
 
848
                        } );
 
849
 
 
850
                if ( response.plugin ) {
 
851
                        $plugin          = $( 'tr.inactive[data-plugin="' + response.plugin + '"]' );
 
852
                        $pluginUpdateRow = $plugin.siblings( '[data-plugin="' + response.plugin + '"]' );
 
853
                } else {
 
854
                        $plugin          = $( 'tr.inactive[data-slug="' + response.slug + '"]' );
 
855
                        $pluginUpdateRow = $plugin.siblings( '[data-slug="' + response.slug + '"]' );
 
856
                }
 
857
 
 
858
                if ( ! wp.updates.isValidResponse( response, 'delete' ) ) {
 
859
                        return;
 
860
                }
 
861
 
 
862
                if ( wp.updates.maybeHandleCredentialError( response, 'delete-plugin' ) ) {
 
863
                        return;
 
864
                }
 
865
 
 
866
                // Add a plugin update row if it doesn't exist yet.
 
867
                if ( ! $pluginUpdateRow.length ) {
 
868
                        $plugin.addClass( 'update' ).after(
 
869
                                pluginUpdateRow( {
 
870
                                        slug:    response.slug,
 
871
                                        plugin:  response.plugin || response.slug,
 
872
                                        colspan: $( '#bulk-action-form' ).find( 'thead th:not(.hidden), thead td' ).length,
 
873
                                        content: noticeContent
 
874
                                } )
 
875
                        );
 
876
                } else {
 
877
 
 
878
                        // Remove previous error messages, if any.
 
879
                        $pluginUpdateRow.find( '.notice-error' ).remove();
 
880
 
 
881
                        $pluginUpdateRow.find( '.plugin-update' ).append( noticeContent );
 
882
                }
 
883
 
 
884
                $document.trigger( 'wp-plugin-delete-error', response );
 
885
        };
 
886
 
 
887
        /**
 
888
         * Sends an Ajax request to the server to update a theme.
 
889
         *
 
890
         * @since 4.6.0
 
891
         *
 
892
         * @param {object}              args         Arguments.
 
893
         * @param {string}              args.slug    Theme stylesheet.
 
894
         * @param {updateThemeSuccess=} args.success Optional. Success callback. Default: wp.updates.updateThemeSuccess
 
895
         * @param {updateThemeError=}   args.error   Optional. Error callback. Default: wp.updates.updateThemeError
 
896
         * @return {$.promise} A jQuery promise that represents the request,
 
897
         *                     decorated with an abort() method.
 
898
         */
 
899
        wp.updates.updateTheme = function( args ) {
 
900
                var $notice;
 
901
 
 
902
                args = _.extend( {
 
903
                        success: wp.updates.updateThemeSuccess,
 
904
                        error: wp.updates.updateThemeError
 
905
                }, args );
 
906
 
 
907
                if ( 'themes-network' === pagenow ) {
 
908
                        $notice = $( '[data-slug="' + args.slug + '"]' ).find( '.update-message' ).removeClass( 'notice-error' ).addClass( 'updating-message notice-warning' ).find( 'p' );
 
909
 
 
910
                } else {
 
911
                        $notice = $( '#update-theme' ).closest( '.notice' ).removeClass( 'notice-large' );
 
912
 
 
913
                        $notice.find( 'h3' ).remove();
 
914
 
 
915
                        $notice = $notice.add( $( '[data-slug="' + args.slug + '"]' ).find( '.update-message' ) );
 
916
                        $notice = $notice.addClass( 'updating-message' ).find( 'p' );
 
917
                }
 
918
 
 
919
                if ( $notice.html() !== wp.updates.l10n.updating ) {
 
920
                        $notice.data( 'originaltext', $notice.html() );
 
921
                }
 
922
 
 
923
                wp.a11y.speak( wp.updates.l10n.updatingMsg, 'polite' );
 
924
                $notice.text( wp.updates.l10n.updating );
 
925
 
 
926
                $document.trigger( 'wp-theme-updating', args );
 
927
 
 
928
                return wp.updates.ajax( 'update-theme', args );
 
929
        };
 
930
 
 
931
        /**
 
932
         * Updates the UI appropriately after a successful theme update.
 
933
         *
 
934
         * @since 4.6.0
 
935
         *
 
936
         * @typedef {object} updateThemeSuccess
 
937
         * @param {object} response
 
938
         * @param {string} response.slug       Slug of the theme to be updated.
 
939
         * @param {object} response.theme      Updated theme.
 
940
         * @param {string} response.oldVersion Old version of the theme.
 
941
         * @param {string} response.newVersion New version of the theme.
 
942
         */
 
943
        wp.updates.updateThemeSuccess = function( response ) {
 
944
                var isModalOpen    = $( 'body.modal-open' ).length,
 
945
                        $theme         = $( '[data-slug="' + response.slug + '"]' ),
 
946
                        updatedMessage = {
 
947
                                className: 'updated-message notice-success notice-alt',
 
948
                                message:   wp.updates.l10n.updated
 
949
                        },
 
950
                        $notice, newText;
 
951
 
 
952
                if ( 'themes-network' === pagenow ) {
 
953
                        $notice = $theme.find( '.update-message' );
 
954
 
 
955
                        // Update the version number in the row.
 
956
                        newText = $theme.find( '.theme-version-author-uri' ).html().replace( response.oldVersion, response.newVersion );
 
957
                        $theme.find( '.theme-version-author-uri' ).html( newText );
 
958
                } else {
 
959
                        $notice = $( '.theme-info .notice' ).add( $theme.find( '.update-message' ) );
 
960
 
 
961
                        // Focus on Customize button after updating.
 
962
                        if ( isModalOpen ) {
 
963
                                $( '.load-customize:visible' ).focus();
 
964
                        } else {
 
965
                                $theme.find( '.load-customize' ).focus();
 
966
                        }
 
967
                }
 
968
 
 
969
                wp.updates.addAdminNotice( _.extend( { selector: $notice }, updatedMessage ) );
 
970
                wp.a11y.speak( wp.updates.l10n.updatedMsg, 'polite' );
 
971
 
 
972
                wp.updates.decrementCount( 'theme' );
 
973
 
 
974
                $document.trigger( 'wp-theme-update-success', response );
 
975
 
 
976
                // Show updated message after modal re-rendered.
 
977
                if ( isModalOpen ) {
 
978
                        $( '.theme-info .theme-author' ).after( wp.updates.adminNotice( updatedMessage ) );
 
979
                }
 
980
        };
 
981
 
 
982
        /**
 
983
         * Updates the UI appropriately after a failed theme update.
 
984
         *
 
985
         * @since 4.6.0
 
986
         *
 
987
         * @typedef {object} updateThemeError
 
988
         * @param {object} response              Response from the server.
 
989
         * @param {string} response.slug         Slug of the theme to be updated.
 
990
         * @param {string} response.errorCode    Error code for the error that occurred.
 
991
         * @param {string} response.errorMessage The error that occurred.
 
992
         */
 
993
        wp.updates.updateThemeError = function( response ) {
 
994
                var $theme       = $( '[data-slug="' + response.slug + '"]' ),
 
995
                        errorMessage = wp.updates.l10n.updateFailed.replace( '%s', response.errorMessage ),
 
996
                        $notice;
 
997
 
 
998
                if ( ! wp.updates.isValidResponse( response, 'update' ) ) {
 
999
                        return;
 
1000
                }
 
1001
 
 
1002
                if ( wp.updates.maybeHandleCredentialError( response, 'update-theme' ) ) {
 
1003
                        return;
 
1004
                }
 
1005
 
 
1006
                if ( 'themes-network' === pagenow ) {
 
1007
                        $notice = $theme.find( '.update-message ' );
 
1008
                } else {
 
1009
                        $notice = $( '.theme-info .notice' ).add( $theme.find( '.notice' ) );
 
1010
 
 
1011
                        $( 'body.modal-open' ).length ? $( '.load-customize:visible' ).focus() : $theme.find( '.load-customize' ).focus();
 
1012
                }
 
1013
 
 
1014
                wp.updates.addAdminNotice( {
 
1015
                        selector:  $notice,
 
1016
                        className: 'update-message notice-error notice-alt is-dismissible',
 
1017
                        message:   errorMessage
 
1018
                } );
 
1019
 
 
1020
                wp.a11y.speak( errorMessage, 'polite' );
 
1021
 
 
1022
                $document.trigger( 'wp-theme-update-error', response );
 
1023
        };
 
1024
 
 
1025
        /**
 
1026
         * Sends an Ajax request to the server to install a theme.
 
1027
         *
 
1028
         * @since 4.6.0
 
1029
         *
 
1030
         * @param {object}               args
 
1031
         * @param {string}               args.slug    Theme stylesheet.
 
1032
         * @param {installThemeSuccess=} args.success Optional. Success callback. Default: wp.updates.installThemeSuccess
 
1033
         * @param {installThemeError=}   args.error   Optional. Error callback. Default: wp.updates.installThemeError
 
1034
         * @return {$.promise} A jQuery promise that represents the request,
 
1035
         *                     decorated with an abort() method.
 
1036
         */
 
1037
        wp.updates.installTheme = function( args ) {
 
1038
                var $message = $( '.theme-install[data-slug="' + args.slug + '"]' );
 
1039
 
 
1040
                args = _.extend( {
 
1041
                        success: wp.updates.installThemeSuccess,
 
1042
                        error: wp.updates.installThemeError
 
1043
                }, args );
 
1044
 
 
1045
                $message.addClass( 'updating-message' );
 
1046
                $message.parents( '.theme' ).addClass( 'focus' );
 
1047
                if ( $message.html() !== wp.updates.l10n.installing ) {
 
1048
                        $message.data( 'originaltext', $message.html() );
 
1049
                }
 
1050
 
 
1051
                $message
 
1052
                        .text( wp.updates.l10n.installing )
 
1053
                        .attr( 'aria-label', wp.updates.l10n.themeInstallingLabel.replace( '%s', $message.data( 'name' ) ) );
 
1054
                wp.a11y.speak( wp.updates.l10n.installingMsg, 'polite' );
 
1055
 
 
1056
                // Remove previous error messages, if any.
 
1057
                $( '.install-theme-info, [data-slug="' + args.slug + '"]' ).removeClass( 'theme-install-failed' ).find( '.notice.notice-error' ).remove();
 
1058
 
 
1059
                $document.trigger( 'wp-theme-installing', args );
 
1060
 
 
1061
                return wp.updates.ajax( 'install-theme', args );
 
1062
        };
 
1063
 
 
1064
        /**
 
1065
         * Updates the UI appropriately after a successful theme install.
 
1066
         *
 
1067
         * @since 4.6.0
 
1068
         *
 
1069
         * @typedef {object} installThemeSuccess
 
1070
         * @param {object} response              Response from the server.
 
1071
         * @param {string} response.slug         Slug of the theme to be installed.
 
1072
         * @param {string} response.customizeUrl URL to the Customizer for the just installed theme.
 
1073
         * @param {string} response.activateUrl  URL to activate the just installed theme.
 
1074
         */
 
1075
        wp.updates.installThemeSuccess = function( response ) {
 
1076
                var $card = $( '.wp-full-overlay-header, [data-slug=' + response.slug + ']' ),
 
1077
                        $message;
 
1078
 
 
1079
                $document.trigger( 'wp-theme-install-success', response );
 
1080
 
 
1081
                $message = $card.find( '.button-primary' )
 
1082
                        .removeClass( 'updating-message' )
 
1083
                        .addClass( 'updated-message disabled' )
 
1084
                        .attr( 'aria-label', wp.updates.l10n.themeInstalledLabel.replace( '%s', response.themeName ) )
 
1085
                        .text( wp.updates.l10n.installed );
 
1086
 
 
1087
                wp.a11y.speak( wp.updates.l10n.installedMsg, 'polite' );
 
1088
 
 
1089
                setTimeout( function() {
 
1090
 
 
1091
                        if ( response.activateUrl ) {
 
1092
 
 
1093
                                // Transform the 'Install' button into an 'Activate' button.
 
1094
                                $message
 
1095
                                        .attr( 'href', response.activateUrl )
 
1096
                                        .removeClass( 'theme-install updated-message disabled' )
 
1097
                                        .addClass( 'activate' )
 
1098
                                        .attr( 'aria-label', wp.updates.l10n.activateThemeLabel.replace( '%s', response.themeName ) )
 
1099
                                        .text( wp.updates.l10n.activateTheme );
 
1100
                        }
 
1101
 
 
1102
                        if ( response.customizeUrl ) {
 
1103
 
 
1104
                                // Transform the 'Preview' button into a 'Live Preview' button.
 
1105
                                $message.siblings( '.preview' ).replaceWith( function () {
 
1106
                                        return $( '<a>' )
 
1107
                                                .attr( 'href', response.customizeUrl )
 
1108
                                                .addClass( 'button button-secondary load-customize' )
 
1109
                                                .text( wp.updates.l10n.livePreview );
 
1110
                                } );
 
1111
                        }
 
1112
                }, 1000 );
 
1113
        };
 
1114
 
 
1115
        /**
 
1116
         * Updates the UI appropriately after a failed theme install.
 
1117
         *
 
1118
         * @since 4.6.0
 
1119
         *
 
1120
         * @typedef {object} installThemeError
 
1121
         * @param {object} response              Response from the server.
 
1122
         * @param {string} response.slug         Slug of the theme to be installed.
 
1123
         * @param {string} response.errorCode    Error code for the error that occurred.
 
1124
         * @param {string} response.errorMessage The error that occurred.
 
1125
         */
 
1126
        wp.updates.installThemeError = function( response ) {
 
1127
                var $card, $button,
 
1128
                        errorMessage = wp.updates.l10n.installFailed.replace( '%s', response.errorMessage ),
 
1129
                        $message     = wp.updates.adminNotice( {
 
1130
                                className: 'update-message notice-error notice-alt',
 
1131
                                message:   errorMessage
 
1132
                        } );
 
1133
 
 
1134
                if ( ! wp.updates.isValidResponse( response, 'install' ) ) {
 
1135
                        return;
 
1136
                }
 
1137
 
 
1138
                if ( wp.updates.maybeHandleCredentialError( response, 'install-theme' ) ) {
 
1139
                        return;
 
1140
                }
 
1141
 
 
1142
                if ( $document.find( 'body' ).hasClass( 'full-overlay-active' ) ) {
 
1143
                        $button = $( '.theme-install[data-slug="' + response.slug + '"]' );
 
1144
                        $card   = $( '.install-theme-info' ).prepend( $message );
 
1145
                } else {
 
1146
                        $card   = $( '[data-slug="' + response.slug + '"]' ).removeClass( 'focus' ).addClass( 'theme-install-failed' ).append( $message );
 
1147
                        $button = $card.find( '.theme-install' );
 
1148
                }
 
1149
 
 
1150
                $button
 
1151
                        .removeClass( 'updating-message' )
 
1152
                        .attr( 'aria-label', wp.updates.l10n.themeInstallFailedLabel.replace( '%s', $button.data( 'name' ) ) )
 
1153
                        .text( wp.updates.l10n.installFailedShort );
 
1154
 
 
1155
                wp.a11y.speak( errorMessage, 'assertive' );
 
1156
 
 
1157
                $document.trigger( 'wp-theme-install-error', response );
 
1158
        };
 
1159
 
 
1160
        /**
 
1161
         * Sends an Ajax request to the server to install a theme.
 
1162
         *
 
1163
         * @since 4.6.0
 
1164
         *
 
1165
         * @param {object}              args
 
1166
         * @param {string}              args.slug    Theme stylesheet.
 
1167
         * @param {deleteThemeSuccess=} args.success Optional. Success callback. Default: wp.updates.deleteThemeSuccess
 
1168
         * @param {deleteThemeError=}   args.error   Optional. Error callback. Default: wp.updates.deleteThemeError
 
1169
         * @return {$.promise} A jQuery promise that represents the request,
 
1170
         *                     decorated with an abort() method.
 
1171
         */
 
1172
        wp.updates.deleteTheme = function( args ) {
 
1173
                var $button;
 
1174
 
 
1175
                if ( 'themes' === pagenow ) {
 
1176
                        $button = $( '.theme-actions .delete-theme' );
 
1177
                } else if ( 'themes-network' === pagenow ) {
 
1178
                        $button = $( '[data-slug="' + args.slug + '"]' ).find( '.row-actions a.delete' );
 
1179
                }
 
1180
 
 
1181
                args = _.extend( {
 
1182
                        success: wp.updates.deleteThemeSuccess,
 
1183
                        error: wp.updates.deleteThemeError
 
1184
                }, args );
 
1185
 
 
1186
                if ( $button && $button.html() !== wp.updates.l10n.deleting ) {
 
1187
                        $button
 
1188
                                .data( 'originaltext', $button.html() )
 
1189
                                .text( wp.updates.l10n.deleting );
 
1190
                }
 
1191
 
 
1192
                wp.a11y.speak( wp.updates.l10n.deleting, 'polite' );
 
1193
 
 
1194
                // Remove previous error messages, if any.
 
1195
                $( '.theme-info .update-message' ).remove();
 
1196
 
 
1197
                $document.trigger( 'wp-theme-deleting', args );
 
1198
 
 
1199
                return wp.updates.ajax( 'delete-theme', args );
 
1200
        };
 
1201
 
 
1202
        /**
 
1203
         * Updates the UI appropriately after a successful theme deletion.
 
1204
         *
 
1205
         * @since 4.6.0
 
1206
         *
 
1207
         * @typedef {object} deleteThemeSuccess
 
1208
         * @param {object} response      Response from the server.
 
1209
         * @param {string} response.slug Slug of the theme that was deleted.
 
1210
         */
 
1211
        wp.updates.deleteThemeSuccess = function( response ) {
 
1212
                var $themeRows = $( '[data-slug="' + response.slug + '"]' );
 
1213
 
 
1214
                if ( 'themes-network' === pagenow ) {
 
1215
 
 
1216
                        // Removes the theme and updates rows.
 
1217
                        $themeRows.css( { backgroundColor: '#faafaa' } ).fadeOut( 350, function() {
 
1218
                                var $views     = $( '.subsubsub' ),
 
1219
                                        $themeRow  = $( this ),
 
1220
                                        totals     = settings.totals,
 
1221
                                        deletedRow = wp.template( 'item-deleted-row' );
 
1222
 
 
1223
                                if ( ! $themeRow.hasClass( 'plugin-update-tr' ) ) {
 
1224
                                        $themeRow.after(
 
1225
                                                deletedRow( {
 
1226
                                                        slug:    response.slug,
 
1227
                                                        colspan: $( '#bulk-action-form' ).find( 'thead th:not(.hidden), thead td' ).length,
 
1228
                                                        name:    $themeRow.find( '.theme-title strong' ).text()
 
1229
                                                } )
 
1230
                                        );
 
1231
                                }
 
1232
 
 
1233
                                $themeRow.remove();
 
1234
 
 
1235
                                // Remove theme from update count.
 
1236
                                if ( $themeRow.hasClass( 'update' ) ) {
 
1237
                                        totals.upgrade--;
 
1238
                                        wp.updates.decrementCount( 'theme' );
 
1239
                                }
 
1240
 
 
1241
                                // Remove from views.
 
1242
                                if ( $themeRow.hasClass( 'inactive' ) ) {
 
1243
                                        totals.disabled--;
 
1244
                                        if ( totals.disabled ) {
 
1245
                                                $views.find( '.disabled .count' ).text( '(' + totals.disabled + ')' );
 
1246
                                        } else {
 
1247
                                                $views.find( '.disabled' ).remove();
 
1248
                                        }
 
1249
                                }
 
1250
 
 
1251
                                // There is always at least one theme available.
 
1252
                                $views.find( '.all .count' ).text( '(' + --totals.all + ')' );
 
1253
                        } );
 
1254
                }
 
1255
 
 
1256
                wp.a11y.speak( wp.updates.l10n.deleted, 'polite' );
 
1257
 
 
1258
                $document.trigger( 'wp-theme-delete-success', response );
 
1259
        };
 
1260
 
 
1261
        /**
 
1262
         * Updates the UI appropriately after a failed theme deletion.
 
1263
         *
 
1264
         * @since 4.6.0
 
1265
         *
 
1266
         * @typedef {object} deleteThemeError
 
1267
         * @param {object} response              Response from the server.
 
1268
         * @param {string} response.slug         Slug of the theme to be deleted.
 
1269
         * @param {string} response.errorCode    Error code for the error that occurred.
 
1270
         * @param {string} response.errorMessage The error that occurred.
 
1271
         */
 
1272
        wp.updates.deleteThemeError = function( response ) {
 
1273
                var $themeRow    = $( 'tr.inactive[data-slug="' + response.slug + '"]' ),
 
1274
                        $button      = $( '.theme-actions .delete-theme' ),
 
1275
                        updateRow    = wp.template( 'item-update-row' ),
 
1276
                        $updateRow   = $themeRow.siblings( '#' + response.slug + '-update' ),
 
1277
                        errorMessage = wp.updates.l10n.deleteFailed.replace( '%s', response.errorMessage ),
 
1278
                        $message     = wp.updates.adminNotice( {
 
1279
                                className: 'update-message notice-error notice-alt',
 
1280
                                message:   errorMessage
 
1281
                        } );
 
1282
 
 
1283
                if ( wp.updates.maybeHandleCredentialError( response, 'delete-theme' ) ) {
 
1284
                        return;
 
1285
                }
 
1286
 
 
1287
                if ( 'themes-network' === pagenow ) {
 
1288
                        if ( ! $updateRow.length ) {
 
1289
                                $themeRow.addClass( 'update' ).after(
 
1290
                                        updateRow( {
 
1291
                                                slug: response.slug,
 
1292
                                                colspan: $( '#bulk-action-form' ).find( 'thead th:not(.hidden), thead td' ).length,
 
1293
                                                content: $message
 
1294
                                        } )
 
1295
                                );
 
1296
                        } else {
 
1297
                                // Remove previous error messages, if any.
 
1298
                                $updateRow.find( '.notice-error' ).remove();
 
1299
                                $updateRow.find( '.plugin-update' ).append( $message );
 
1300
                        }
 
1301
                } else {
 
1302
                        $( '.theme-info .theme-description' ).before( $message );
 
1303
                }
 
1304
 
 
1305
                $button.html( $button.data( 'originaltext' ) );
 
1306
 
 
1307
                wp.a11y.speak( errorMessage, 'assertive' );
 
1308
 
 
1309
                $document.trigger( 'wp-theme-delete-error', response );
 
1310
        };
 
1311
 
 
1312
        /**
 
1313
         * Adds the appropriate callback based on the type of action and the current page.
 
1314
         *
 
1315
         * @since 4.6.0
 
1316
         * @private
 
1317
         *
 
1318
         * @param {object} data   AJAX payload.
 
1319
         * @param {string} action The type of request to perform.
 
1320
         * @return {object} The AJAX payload with the appropriate callbacks.
 
1321
         */
 
1322
        wp.updates._addCallbacks = function( data, action ) {
 
1323
                if ( 'import' === pagenow && 'install-plugin' === action ) {
 
1324
                        data.success = wp.updates.installImporterSuccess;
 
1325
                        data.error   = wp.updates.installImporterError;
 
1326
                }
 
1327
 
 
1328
                return data;
 
1329
        };
 
1330
 
 
1331
        /**
 
1332
         * Pulls available jobs from the queue and runs them.
 
1333
         *
 
1334
         * @since 4.2.0
 
1335
         * @since 4.6.0 Can handle multiple job types.
346
1336
         */
347
1337
        wp.updates.queueChecker = function() {
348
 
                if ( wp.updates.updateLock || wp.updates.updateQueue.length <= 0 ) {
 
1338
                var job;
 
1339
 
 
1340
                if ( wp.updates.ajaxLocked || ! wp.updates.queue.length ) {
349
1341
                        return;
350
1342
                }
351
1343
 
352
 
                var job = wp.updates.updateQueue.shift();
353
 
 
354
 
                wp.updates.updatePlugin( job.data.plugin, job.data.slug );
 
1344
                job = wp.updates.queue.shift();
 
1345
 
 
1346
                // Handle a queue job.
 
1347
                switch ( job.action ) {
 
1348
                        case 'install-plugin':
 
1349
                                wp.updates.installPlugin( job.data );
 
1350
                                break;
 
1351
 
 
1352
                        case 'update-plugin':
 
1353
                                wp.updates.updatePlugin( job.data );
 
1354
                                break;
 
1355
 
 
1356
                        case 'delete-plugin':
 
1357
                                wp.updates.deletePlugin( job.data );
 
1358
                                break;
 
1359
 
 
1360
                        case 'install-theme':
 
1361
                                wp.updates.installTheme( job.data );
 
1362
                                break;
 
1363
 
 
1364
                        case 'update-theme':
 
1365
                                wp.updates.updateTheme( job.data );
 
1366
                                break;
 
1367
 
 
1368
                        case 'delete-theme':
 
1369
                                wp.updates.deleteTheme( job.data );
 
1370
                                break;
 
1371
 
 
1372
                        default:
 
1373
                                break;
 
1374
                }
355
1375
        };
356
1376
 
357
 
 
358
1377
        /**
359
 
         * Request the users filesystem credentials if we don't have them already.
 
1378
         * Requests the users filesystem credentials if they aren't already known.
360
1379
         *
361
1380
         * @since 4.2.0
 
1381
         *
 
1382
         * @param {Event=} event Optional. Event interface.
362
1383
         */
363
1384
        wp.updates.requestFilesystemCredentials = function( event ) {
364
 
                if ( wp.updates.updateDoneSuccessfully === false ) {
 
1385
                if ( false === wp.updates.filesystemCredentials.available ) {
365
1386
                        /*
366
 
                         * For the plugin install screen, return the focus to the install button
367
 
                         * after exiting the credentials request modal.
 
1387
                         * After exiting the credentials request modal,
 
1388
                         * return the focus to the element triggering the request.
368
1389
                         */
369
 
                        if ( 'plugin-install' === pagenow && event ) {
 
1390
                        if ( event && ! wp.updates.$elToReturnFocusToFromCredentialsModal ) {
370
1391
                                wp.updates.$elToReturnFocusToFromCredentialsModal = $( event.target );
371
1392
                        }
372
1393
 
373
 
                        wp.updates.updateLock = true;
374
 
 
 
1394
                        wp.updates.ajaxLocked = true;
375
1395
                        wp.updates.requestForCredentialsModalOpen();
376
1396
                }
377
1397
        };
378
1398
 
379
1399
        /**
 
1400
         * Requests the users filesystem credentials if needed and there is no lock.
 
1401
         *
 
1402
         * @since 4.6.0
 
1403
         *
 
1404
         * @param {Event=} event Optional. Event interface.
 
1405
         */
 
1406
        wp.updates.maybeRequestFilesystemCredentials = function( event ) {
 
1407
                if ( wp.updates.shouldRequestFilesystemCredentials && ! wp.updates.ajaxLocked ) {
 
1408
                        wp.updates.requestFilesystemCredentials( event );
 
1409
                }
 
1410
        };
 
1411
 
 
1412
        /**
380
1413
         * Keydown handler for the request for credentials modal.
381
1414
         *
382
 
         * Close the modal when the escape key is pressed.
383
 
         * Constrain keyboard navigation to inside the modal.
 
1415
         * Closes the modal when the escape key is pressed and
 
1416
         * constrains keyboard navigation to inside the modal.
384
1417
         *
385
1418
         * @since 4.2.0
 
1419
         *
 
1420
         * @param {Event} event Event interface.
386
1421
         */
387
1422
        wp.updates.keydown = function( event ) {
388
1423
                if ( 27 === event.keyCode ) {
389
1424
                        wp.updates.requestForCredentialsModalCancel();
390
1425
                } else if ( 9 === event.keyCode ) {
391
 
                        // #upgrade button must always be the last focusable element in the dialog.
392
 
                        if ( event.target.id === 'upgrade' && ! event.shiftKey ) {
 
1426
 
 
1427
                        // #upgrade button must always be the last focus-able element in the dialog.
 
1428
                        if ( 'upgrade' === event.target.id && ! event.shiftKey ) {
393
1429
                                $( '#hostname' ).focus();
 
1430
 
394
1431
                                event.preventDefault();
395
 
                        } else if ( event.target.id === 'hostname' && event.shiftKey ) {
 
1432
                        } else if ( 'hostname' === event.target.id && event.shiftKey ) {
396
1433
                                $( '#upgrade' ).focus();
 
1434
 
397
1435
                                event.preventDefault();
398
1436
                        }
399
1437
                }
400
1438
        };
401
1439
 
402
1440
        /**
403
 
         * Open the request for credentials modal.
 
1441
         * Opens the request for credentials modal.
404
1442
         *
405
1443
         * @since 4.2.0
406
1444
         */
407
1445
        wp.updates.requestForCredentialsModalOpen = function() {
408
1446
                var $modal = $( '#request-filesystem-credentials-dialog' );
 
1447
 
409
1448
                $( 'body' ).addClass( 'modal-open' );
410
1449
                $modal.show();
411
 
 
412
1450
                $modal.find( 'input:enabled:first' ).focus();
413
 
                $modal.keydown( wp.updates.keydown );
 
1451
                $modal.on( 'keydown', wp.updates.keydown );
414
1452
        };
415
1453
 
416
1454
        /**
417
 
         * Close the request for credentials modal.
 
1455
         * Closes the request for credentials modal.
418
1456
         *
419
1457
         * @since 4.2.0
420
1458
         */
421
1459
        wp.updates.requestForCredentialsModalClose = function() {
422
1460
                $( '#request-filesystem-credentials-dialog' ).hide();
423
1461
                $( 'body' ).removeClass( 'modal-open' );
424
 
                wp.updates.$elToReturnFocusToFromCredentialsModal.focus();
 
1462
 
 
1463
                if ( wp.updates.$elToReturnFocusToFromCredentialsModal ) {
 
1464
                        wp.updates.$elToReturnFocusToFromCredentialsModal.focus();
 
1465
                }
425
1466
        };
426
1467
 
427
1468
        /**
428
 
         * The steps that need to happen when the modal is canceled out
 
1469
         * Takes care of the steps that need to happen when the modal is canceled out.
429
1470
         *
430
1471
         * @since 4.2.0
 
1472
         * @since 4.6.0 Triggers an event for callbacks to listen to and add their actions.
431
1473
         */
432
1474
        wp.updates.requestForCredentialsModalCancel = function() {
433
 
                // no updateLock and no updateQueue means we already have cleared things up
434
 
                var data, $message;
435
1475
 
436
 
                if( wp.updates.updateLock === false && wp.updates.updateQueue.length === 0 ){
 
1476
                // Not ajaxLocked and no queue means we already have cleared things up.
 
1477
                if ( ! wp.updates.ajaxLocked && ! wp.updates.queue.length ) {
437
1478
                        return;
438
1479
                }
439
1480
 
440
 
                data = wp.updates.updateQueue[0].data;
 
1481
                _.each( wp.updates.queue, function( job ) {
 
1482
                        $document.trigger( 'credential-modal-cancel', job );
 
1483
                } );
441
1484
 
442
 
                // remove the lock, and clear the queue
443
 
                wp.updates.updateLock = false;
444
 
                wp.updates.updateQueue = [];
 
1485
                // Remove the lock, and clear the queue.
 
1486
                wp.updates.ajaxLocked = false;
 
1487
                wp.updates.queue = [];
445
1488
 
446
1489
                wp.updates.requestForCredentialsModalClose();
447
 
                if ( 'plugins' === pagenow || 'plugins-network' === pagenow ) {
448
 
                        $message = $( '[data-plugin="' + data.plugin + '"]' ).next().find( '.update-message' );
449
 
                } else if ( 'plugin-install' === pagenow ) {
450
 
                        $message = $( '.plugin-card-' + data.slug ).find( '.update-now' );
451
 
                }
452
 
 
453
 
                $message.removeClass( 'updating-message' );
454
 
                $message.html( $message.data( 'originaltext' ) );
455
 
                wp.a11y.speak( wp.updates.l10n.updateCancel );
456
 
        };
457
 
        /**
458
 
         * Potentially add an AYS to a user attempting to leave the page
 
1490
        };
 
1491
 
 
1492
        /**
 
1493
         * Displays an error message in the request for credentials form.
 
1494
         *
 
1495
         * @since 4.2.0
 
1496
         *
 
1497
         * @param {string} message Error message.
 
1498
         */
 
1499
        wp.updates.showErrorInCredentialsForm = function( message ) {
 
1500
                var $modal = $( '#request-filesystem-credentials-form' );
 
1501
 
 
1502
                // Remove any existing error.
 
1503
                $modal.find( '.notice' ).remove();
 
1504
                $modal.find( '#request-filesystem-credentials-title' ).after( '<div class="notice notice-alt notice-error"><p>' + message + '</p></div>' );
 
1505
        };
 
1506
 
 
1507
        /**
 
1508
         * Handles credential errors and runs events that need to happen in that case.
 
1509
         *
 
1510
         * @since 4.2.0
 
1511
         *
 
1512
         * @param {object} response Ajax response.
 
1513
         * @param {string} action   The type of request to perform.
 
1514
         */
 
1515
        wp.updates.credentialError = function( response, action ) {
 
1516
 
 
1517
                // Restore callbacks.
 
1518
                response = wp.updates._addCallbacks( response, action );
 
1519
 
 
1520
                wp.updates.queue.unshift( {
 
1521
                        action: action,
 
1522
 
 
1523
                        /*
 
1524
                         * Not cool that we're depending on response for this data.
 
1525
                         * This would feel more whole in a view all tied together.
 
1526
                         */
 
1527
                        data: response
 
1528
                } );
 
1529
 
 
1530
                wp.updates.filesystemCredentials.available = false;
 
1531
                wp.updates.showErrorInCredentialsForm( response.errorMessage );
 
1532
                wp.updates.requestFilesystemCredentials();
 
1533
        };
 
1534
 
 
1535
        /**
 
1536
         * Handles credentials errors if it could not connect to the filesystem.
 
1537
         *
 
1538
         * @since 4.6.0
 
1539
         *
 
1540
         * @typedef {object} maybeHandleCredentialError
 
1541
         * @param {object} response              Response from the server.
 
1542
         * @param {string} response.errorCode    Error code for the error that occurred.
 
1543
         * @param {string} response.errorMessage The error that occurred.
 
1544
         * @param {string} action                The type of request to perform.
 
1545
         * @returns {boolean} Whether there is an error that needs to be handled or not.
 
1546
         */
 
1547
        wp.updates.maybeHandleCredentialError = function( response, action ) {
 
1548
                if ( wp.updates.shouldRequestFilesystemCredentials && response.errorCode && 'unable_to_connect_to_filesystem' === response.errorCode ) {
 
1549
                        wp.updates.credentialError( response, action );
 
1550
                        return true;
 
1551
                }
 
1552
 
 
1553
                return false;
 
1554
        };
 
1555
 
 
1556
        /**
 
1557
         * Validates an AJAX response to ensure it's a proper object.
 
1558
         *
 
1559
         * If the response deems to be invalid, an admin notice is being displayed.
 
1560
         *
 
1561
         * @param {(object|string)} response              Response from the server.
 
1562
         * @param {function=}       response.always       Optional. Callback for when the Deferred is resolved or rejected.
 
1563
         * @param {string=}         response.statusText   Optional. Status message corresponding to the status code.
 
1564
         * @param {string=}         response.responseText Optional. Request response as text.
 
1565
         * @param {string}          action                Type of action the response is referring to. Can be 'delete',
 
1566
         *                                                'update' or 'install'.
 
1567
         */
 
1568
        wp.updates.isValidResponse = function( response, action ) {
 
1569
                var error = wp.updates.l10n.unknownError,
 
1570
                    errorMessage;
 
1571
 
 
1572
                // Make sure the response is a valid data object and not a Promise object.
 
1573
                if ( _.isObject( response ) && ! _.isFunction( response.always ) ) {
 
1574
                        return true;
 
1575
                }
 
1576
 
 
1577
                if ( _.isString( response ) && '-1' === response ) {
 
1578
                        error = wp.updates.l10n.nonceError;
 
1579
                } else if ( _.isString( response ) ) {
 
1580
                        error = response;
 
1581
                } else if ( 'undefined' !== typeof response.readyState && 0 === response.readyState ) {
 
1582
                        error = wp.updates.l10n.connectionError;
 
1583
                } else if ( _.isString( response.responseText ) && '' !== response.responseText ) {
 
1584
                        error = response.responseText;
 
1585
                } else if ( _.isString( response.statusText ) ) {
 
1586
                        error = response.statusText;
 
1587
                }
 
1588
 
 
1589
                switch ( action ) {
 
1590
                        case 'update':
 
1591
                                errorMessage = wp.updates.l10n.updateFailed;
 
1592
                                break;
 
1593
 
 
1594
                        case 'install':
 
1595
                                errorMessage = wp.updates.l10n.installFailed;
 
1596
                                break;
 
1597
 
 
1598
                        case 'delete':
 
1599
                                errorMessage = wp.updates.l10n.deleteFailed;
 
1600
                                break;
 
1601
                }
 
1602
 
 
1603
                // Messages are escaped, remove HTML tags to make them more readable.
 
1604
                error = error.replace( /<[\/a-z][^<>]*>/gi, '' );
 
1605
                errorMessage = errorMessage.replace( '%s', error );
 
1606
 
 
1607
                // Add admin notice.
 
1608
                wp.updates.addAdminNotice( {
 
1609
                        id:        'unknown_error',
 
1610
                        className: 'notice-error is-dismissible',
 
1611
                        message:   _.escape( errorMessage )
 
1612
                } );
 
1613
 
 
1614
                // Remove the lock, and clear the queue.
 
1615
                wp.updates.ajaxLocked = false;
 
1616
                wp.updates.queue      = [];
 
1617
 
 
1618
                // Change buttons of all running updates.
 
1619
                $( '.button.updating-message' )
 
1620
                        .removeClass( 'updating-message' )
 
1621
                        .removeAttr( 'aria-label' )
 
1622
                        .prop( 'disabled', true )
 
1623
                        .text( wp.updates.l10n.updateFailedShort );
 
1624
 
 
1625
                $( '.updating-message:not(.button):not(.thickbox)' )
 
1626
                        .removeClass( 'updating-message notice-warning' )
 
1627
                        .addClass( 'notice-error' )
 
1628
                        .find( 'p' )
 
1629
                                .removeAttr( 'aria-label' )
 
1630
                                .text( errorMessage );
 
1631
 
 
1632
                wp.a11y.speak( errorMessage, 'assertive' );
 
1633
 
 
1634
                return false;
 
1635
        };
 
1636
 
 
1637
        /**
 
1638
         * Potentially adds an AYS to a user attempting to leave the page.
459
1639
         *
460
1640
         * If an update is on-going and a user attempts to leave the page,
461
 
         * open an "Are you sure?" alert.
 
1641
         * opens an "Are you sure?" alert.
462
1642
         *
463
1643
         * @since 4.2.0
464
1644
         */
465
 
 
466
1645
        wp.updates.beforeunload = function() {
467
 
                if ( wp.updates.updateLock ) {
 
1646
                if ( wp.updates.ajaxLocked ) {
468
1647
                        return wp.updates.l10n.beforeunload;
469
1648
                }
470
1649
        };
471
1650
 
 
1651
        $( function() {
 
1652
                var $pluginFilter        = $( '#plugin-filter' ),
 
1653
                        $bulkActionForm      = $( '#bulk-action-form' ),
 
1654
                        $filesystemModal     = $( '#request-filesystem-credentials-dialog' ),
 
1655
                        $pluginSearch        = $( '.plugins-php .wp-filter-search' ),
 
1656
                        $pluginInstallSearch = $( '.plugin-install-php .wp-filter-search' );
472
1657
 
473
 
        $( document ).ready( function() {
474
1658
                /*
475
 
                 * Check whether a user needs to submit filesystem credentials based on whether
476
 
                 * the form was output on the page server-side.
 
1659
                 * Whether a user needs to submit filesystem credentials.
 
1660
                 *
 
1661
                 * This is based on whether the form was output on the page server-side.
477
1662
                 *
478
1663
                 * @see {wp_print_request_filesystem_credentials_modal() in PHP}
479
1664
                 */
480
 
                wp.updates.shouldRequestFilesystemCredentials = ( $( '#request-filesystem-credentials-dialog' ).length <= 0 ) ? false : true;
481
 
 
482
 
                // File system credentials form submit noop-er / handler.
483
 
                $( '#request-filesystem-credentials-dialog form' ).on( 'submit', function() {
 
1665
                wp.updates.shouldRequestFilesystemCredentials = $filesystemModal.length > 0;
 
1666
 
 
1667
                /**
 
1668
                 * File system credentials form submit noop-er / handler.
 
1669
                 *
 
1670
                 * @since 4.2.0
 
1671
                 */
 
1672
                $filesystemModal.on( 'submit', 'form', function( event ) {
 
1673
                        event.preventDefault();
 
1674
 
484
1675
                        // Persist the credentials input by the user for the duration of the page load.
485
 
                        wp.updates.filesystemCredentials.ftp.hostname = $('#hostname').val();
486
 
                        wp.updates.filesystemCredentials.ftp.username = $('#username').val();
487
 
                        wp.updates.filesystemCredentials.ftp.password = $('#password').val();
488
 
                        wp.updates.filesystemCredentials.ftp.connectionType = $('input[name="connection_type"]:checked').val();
489
 
                        wp.updates.filesystemCredentials.ssh.publicKey = $('#public_key').val();
490
 
                        wp.updates.filesystemCredentials.ssh.privateKey = $('#private_key').val();
 
1676
                        wp.updates.filesystemCredentials.ftp.hostname       = $( '#hostname' ).val();
 
1677
                        wp.updates.filesystemCredentials.ftp.username       = $( '#username' ).val();
 
1678
                        wp.updates.filesystemCredentials.ftp.password       = $( '#password' ).val();
 
1679
                        wp.updates.filesystemCredentials.ftp.connectionType = $( 'input[name="connection_type"]:checked' ).val();
 
1680
                        wp.updates.filesystemCredentials.ssh.publicKey      = $( '#public_key' ).val();
 
1681
                        wp.updates.filesystemCredentials.ssh.privateKey     = $( '#private_key' ).val();
 
1682
                        wp.updates.filesystemCredentials.available          = true;
 
1683
 
 
1684
                        // Unlock and invoke the queue.
 
1685
                        wp.updates.ajaxLocked = false;
 
1686
                        wp.updates.queueChecker();
491
1687
 
492
1688
                        wp.updates.requestForCredentialsModalClose();
493
 
 
494
 
                        // Unlock and invoke the queue.
495
 
                        wp.updates.updateLock = false;
496
 
                        wp.updates.queueChecker();
497
 
 
498
 
                        return false;
499
 
                });
500
 
 
501
 
                // Close the request credentials modal when
502
 
                $( '#request-filesystem-credentials-dialog [data-js-action="close"], .notification-dialog-background' ).on( 'click', function() {
503
 
                        wp.updates.requestForCredentialsModalCancel();
504
 
                });
505
 
 
506
 
                // Hide SSH fields when not selected
507
 
                $( '#request-filesystem-credentials-dialog input[name="connection_type"]' ).on( 'change', function() {
508
 
                        $( this ).parents( 'form' ).find( '#private_key, #public_key' ).parents( 'label' ).toggle( ( 'ssh' == $( this ).val() ) );
509
 
                }).change();
510
 
 
511
 
                // Click handler for plugin updates in List Table view.
512
 
                $( '.plugin-update-tr' ).on( 'click', '.update-link', function( e ) {
513
 
                        e.preventDefault();
514
 
                        if ( wp.updates.shouldRequestFilesystemCredentials && ! wp.updates.updateLock ) {
515
 
                                wp.updates.requestFilesystemCredentials( e );
516
 
                        }
517
 
                        var updateRow = $( e.target ).parents( '.plugin-update-tr' );
 
1689
                } );
 
1690
 
 
1691
                /**
 
1692
                 * Closes the request credentials modal when clicking the 'Cancel' button or outside of the modal.
 
1693
                 *
 
1694
                 * @since 4.2.0
 
1695
                 */
 
1696
                $filesystemModal.on( 'click', '[data-js-action="close"], .notification-dialog-background', wp.updates.requestForCredentialsModalCancel );
 
1697
 
 
1698
                /**
 
1699
                 * Hide SSH fields when not selected.
 
1700
                 *
 
1701
                 * @since 4.2.0
 
1702
                 */
 
1703
                $filesystemModal.on( 'change', 'input[name="connection_type"]', function() {
 
1704
                        $( '#ssh-keys' ).toggleClass( 'hidden', ( 'ssh' !== $( this ).val() ) );
 
1705
                } ).change();
 
1706
 
 
1707
                /**
 
1708
                 * Handles events after the credential modal was closed.
 
1709
                 *
 
1710
                 * @since 4.6.0
 
1711
                 *
 
1712
                 * @param {Event}  event Event interface.
 
1713
                 * @param {string} job   The install/update.delete request.
 
1714
                 */
 
1715
                $document.on( 'credential-modal-cancel', function( event, job ) {
 
1716
                        var $updatingMessage = $( '.updating-message' ),
 
1717
                                $message, originalText;
 
1718
 
 
1719
                        if ( 'import' === pagenow ) {
 
1720
                                $updatingMessage.removeClass( 'updating-message' );
 
1721
                        } else if ( 'plugins' === pagenow || 'plugins-network' === pagenow ) {
 
1722
                                if ( 'update-plugin' === job.action ) {
 
1723
                                        $message = $( 'tr[data-plugin="' + job.data.plugin + '"]' ).find( '.update-message' );
 
1724
                                } else if ( 'delete-plugin' === job.action ) {
 
1725
                                        $message = $( '[data-plugin="' + job.data.plugin + '"]' ).find( '.row-actions a.delete' );
 
1726
                                }
 
1727
                        } else if ( 'themes' === pagenow || 'themes-network' === pagenow ) {
 
1728
                                if ( 'update-theme' === job.action ) {
 
1729
                                        $message = $( '[data-slug="' + job.data.slug + '"]' ).find( '.update-message' );
 
1730
                                } else if ( 'delete-theme' === job.action && 'themes-network' === pagenow ) {
 
1731
                                        $message = $( '[data-slug="' + job.data.slug + '"]' ).find( '.row-actions a.delete' );
 
1732
                                } else if ( 'delete-theme' === job.action && 'themes' === pagenow ) {
 
1733
                                        $message = $( '.theme-actions .delete-theme' );
 
1734
                                }
 
1735
                        } else {
 
1736
                                $message = $updatingMessage;
 
1737
                        }
 
1738
 
 
1739
                        if ( $message && $message.hasClass( 'updating-message' ) ) {
 
1740
                                originalText = $message.data( 'originaltext' );
 
1741
 
 
1742
                                if ( 'undefined' === typeof originalText ) {
 
1743
                                        originalText = $( '<p>' ).html( $message.find( 'p' ).data( 'originaltext' ) );
 
1744
                                }
 
1745
 
 
1746
                                $message
 
1747
                                        .removeClass( 'updating-message' )
 
1748
                                        .html( originalText );
 
1749
 
 
1750
                                if ( 'plugin-install' === pagenow || 'plugin-install-network' === pagenow ) {
 
1751
                                        if ( 'update-plugin' === job.action ) {
 
1752
                                                $message.attr( 'aria-label', wp.updates.l10n.updateNowLabel.replace( '%s', $message.data( 'name' ) ) );
 
1753
                                        } else if ( 'install-plugin' === job.action ) {
 
1754
                                                $message.attr( 'aria-label', wp.updates.l10n.installNowLabel.replace( '%s', $message.data( 'name' ) ) );
 
1755
                                        }
 
1756
                                }
 
1757
                        }
 
1758
 
 
1759
                        wp.a11y.speak( wp.updates.l10n.updateCancel, 'polite' );
 
1760
                } );
 
1761
 
 
1762
                /**
 
1763
                 * Click handler for plugin updates in List Table view.
 
1764
                 *
 
1765
                 * @since 4.2.0
 
1766
                 *
 
1767
                 * @param {Event} event Event interface.
 
1768
                 */
 
1769
                $bulkActionForm.on( 'click', '[data-plugin] .update-link', function( event ) {
 
1770
                        var $message   = $( event.target ),
 
1771
                                $pluginRow = $message.parents( 'tr' );
 
1772
 
 
1773
                        event.preventDefault();
 
1774
 
 
1775
                        if ( $message.hasClass( 'updating-message' ) || $message.hasClass( 'button-disabled' ) ) {
 
1776
                                return;
 
1777
                        }
 
1778
 
 
1779
                        wp.updates.maybeRequestFilesystemCredentials( event );
 
1780
 
518
1781
                        // Return the user to the input box of the plugin's table row after closing the modal.
519
 
                        wp.updates.$elToReturnFocusToFromCredentialsModal = updateRow.prev().find( '.check-column input' );
520
 
                        wp.updates.updatePlugin( updateRow.data( 'plugin' ), updateRow.data( 'slug' ) );
521
 
                } );
522
 
 
523
 
                $( '.plugin-card' ).on( 'click', '.update-now', function( e ) {
524
 
                        e.preventDefault();
525
 
                        var $button = $( e.target );
526
 
 
527
 
                        // Do nothing while updating and when the button is disabled.
528
 
                        if ( $button.hasClass( 'updating-message' ) || $button.hasClass( 'button-disabled' ) ) {
529
 
                                return;
530
 
                        }
531
 
 
532
 
                        if ( wp.updates.shouldRequestFilesystemCredentials && ! wp.updates.updateLock ) {
533
 
                                wp.updates.requestFilesystemCredentials( e );
534
 
                        }
535
 
 
536
 
                        wp.updates.updatePlugin( $button.data( 'plugin' ), $button.data( 'slug' ) );
537
 
                } );
538
 
 
539
 
                $( '#plugin_update_from_iframe' ).on( 'click' , function( e ) {
540
 
                        var target, job;
541
 
 
542
 
                        target = window.parent == window ? null : window.parent,
 
1782
                        wp.updates.$elToReturnFocusToFromCredentialsModal = $pluginRow.find( '.check-column input' );
 
1783
                        wp.updates.updatePlugin( {
 
1784
                                plugin: $pluginRow.data( 'plugin' ),
 
1785
                                slug:   $pluginRow.data( 'slug' )
 
1786
                        } );
 
1787
                } );
 
1788
 
 
1789
                /**
 
1790
                 * Click handler for plugin updates in plugin install view.
 
1791
                 *
 
1792
                 * @since 4.2.0
 
1793
                 *
 
1794
                 * @param {Event} event Event interface.
 
1795
                 */
 
1796
                $pluginFilter.on( 'click', '.update-now', function( event ) {
 
1797
                        var $button = $( event.target );
 
1798
                        event.preventDefault();
 
1799
 
 
1800
                        if ( $button.hasClass( 'updating-message' ) || $button.hasClass( 'button-disabled' ) ) {
 
1801
                                return;
 
1802
                        }
 
1803
 
 
1804
                        wp.updates.maybeRequestFilesystemCredentials( event );
 
1805
 
 
1806
                        wp.updates.updatePlugin( {
 
1807
                                plugin: $button.data( 'plugin' ),
 
1808
                                slug:   $button.data( 'slug' )
 
1809
                        } );
 
1810
                } );
 
1811
 
 
1812
                /**
 
1813
                 * Click handler for plugin installs in plugin install view.
 
1814
                 *
 
1815
                 * @since 4.6.0
 
1816
                 *
 
1817
                 * @param {Event} event Event interface.
 
1818
                 */
 
1819
                $pluginFilter.on( 'click', '.install-now', function( event ) {
 
1820
                        var $button = $( event.target );
 
1821
                        event.preventDefault();
 
1822
 
 
1823
                        if ( $button.hasClass( 'updating-message' ) || $button.hasClass( 'button-disabled' ) ) {
 
1824
                                return;
 
1825
                        }
 
1826
 
 
1827
                        if ( wp.updates.shouldRequestFilesystemCredentials && ! wp.updates.ajaxLocked ) {
 
1828
                                wp.updates.requestFilesystemCredentials( event );
 
1829
 
 
1830
                                $document.on( 'credential-modal-cancel', function() {
 
1831
                                        var $message = $( '.install-now.updating-message' );
 
1832
 
 
1833
                                        $message
 
1834
                                                .removeClass( 'updating-message' )
 
1835
                                                .text( wp.updates.l10n.installNow );
 
1836
 
 
1837
                                        wp.a11y.speak( wp.updates.l10n.updateCancel, 'polite' );
 
1838
                                } );
 
1839
                        }
 
1840
 
 
1841
                        wp.updates.installPlugin( {
 
1842
                                slug: $button.data( 'slug' )
 
1843
                        } );
 
1844
                } );
 
1845
 
 
1846
                /**
 
1847
                 * Click handler for importer plugins installs in the Import screen.
 
1848
                 *
 
1849
                 * @since 4.6.0
 
1850
                 *
 
1851
                 * @param {Event} event Event interface.
 
1852
                 */
 
1853
                $document.on( 'click', '.importer-item .install-now', function( event ) {
 
1854
                        var $button = $( event.target ),
 
1855
                                pluginName = $( this ).data( 'name' );
 
1856
 
 
1857
                        event.preventDefault();
 
1858
 
 
1859
                        if ( $button.hasClass( 'updating-message' ) ) {
 
1860
                                return;
 
1861
                        }
 
1862
 
 
1863
                        if ( wp.updates.shouldRequestFilesystemCredentials && ! wp.updates.ajaxLocked ) {
 
1864
                                wp.updates.requestFilesystemCredentials( event );
 
1865
 
 
1866
                                $document.on( 'credential-modal-cancel', function() {
 
1867
 
 
1868
                                        $button
 
1869
                                                .removeClass( 'updating-message' )
 
1870
                                                .text( wp.updates.l10n.installNow )
 
1871
                                                .attr( 'aria-label', wp.updates.l10n.installNowLabel.replace( '%s', pluginName ) );
 
1872
 
 
1873
                                        wp.a11y.speak( wp.updates.l10n.updateCancel, 'polite' );
 
1874
                                } );
 
1875
                        }
 
1876
 
 
1877
                        wp.updates.installPlugin( {
 
1878
                                slug:    $button.data( 'slug' ),
 
1879
                                success: wp.updates.installImporterSuccess,
 
1880
                                error:   wp.updates.installImporterError
 
1881
                        } );
 
1882
                } );
 
1883
 
 
1884
                /**
 
1885
                 * Click handler for plugin deletions.
 
1886
                 *
 
1887
                 * @since 4.6.0
 
1888
                 *
 
1889
                 * @param {Event} event Event interface.
 
1890
                 */
 
1891
                $bulkActionForm.on( 'click', '[data-plugin] a.delete', function( event ) {
 
1892
                        var $pluginRow = $( event.target ).parents( 'tr' );
 
1893
 
 
1894
                        event.preventDefault();
 
1895
 
 
1896
                        if ( ! window.confirm( wp.updates.l10n.aysDeleteUninstall.replace( '%s', $pluginRow.find( '.plugin-title strong' ).text() ) ) ) {
 
1897
                                return;
 
1898
                        }
 
1899
 
 
1900
                        wp.updates.maybeRequestFilesystemCredentials( event );
 
1901
 
 
1902
                        wp.updates.deletePlugin( {
 
1903
                                plugin: $pluginRow.data( 'plugin' ),
 
1904
                                slug:   $pluginRow.data( 'slug' )
 
1905
                        } );
 
1906
 
 
1907
                } );
 
1908
 
 
1909
                /**
 
1910
                 * Click handler for theme updates.
 
1911
                 *
 
1912
                 * @since 4.6.0
 
1913
                 *
 
1914
                 * @param {Event} event Event interface.
 
1915
                 */
 
1916
                $document.on( 'click', '.themes-php.network-admin .update-link', function( event ) {
 
1917
                        var $message  = $( event.target ),
 
1918
                                $themeRow = $message.parents( 'tr' );
 
1919
 
 
1920
                        event.preventDefault();
 
1921
 
 
1922
                        if ( $message.hasClass( 'updating-message' ) || $message.hasClass( 'button-disabled' ) ) {
 
1923
                                return;
 
1924
                        }
 
1925
 
 
1926
                        wp.updates.maybeRequestFilesystemCredentials( event );
 
1927
 
 
1928
                        // Return the user to the input box of the theme's table row after closing the modal.
 
1929
                        wp.updates.$elToReturnFocusToFromCredentialsModal = $themeRow.find( '.check-column input' );
 
1930
                        wp.updates.updateTheme( {
 
1931
                                slug: $themeRow.data( 'slug' )
 
1932
                        } );
 
1933
                } );
 
1934
 
 
1935
                /**
 
1936
                 * Click handler for theme deletions.
 
1937
                 *
 
1938
                 * @since 4.6.0
 
1939
                 *
 
1940
                 * @param {Event} event Event interface.
 
1941
                 */
 
1942
                $document.on( 'click', '.themes-php.network-admin a.delete', function( event ) {
 
1943
                        var $themeRow = $( event.target ).parents( 'tr' );
 
1944
 
 
1945
                        event.preventDefault();
 
1946
 
 
1947
                        if ( ! window.confirm( wp.updates.l10n.aysDelete.replace( '%s', $themeRow.find( '.theme-title strong' ).text() ) ) ) {
 
1948
                                return;
 
1949
                        }
 
1950
 
 
1951
                        wp.updates.maybeRequestFilesystemCredentials( event );
 
1952
 
 
1953
                        wp.updates.deleteTheme( {
 
1954
                                slug: $themeRow.data( 'slug' )
 
1955
                        } );
 
1956
                } );
 
1957
 
 
1958
                /**
 
1959
                 * Bulk action handler for plugins and themes.
 
1960
                 *
 
1961
                 * Handles both deletions and updates.
 
1962
                 *
 
1963
                 * @since 4.6.0
 
1964
                 *
 
1965
                 * @param {Event} event Event interface.
 
1966
                 */
 
1967
                $bulkActionForm.on( 'click', '[type="submit"]', function( event ) {
 
1968
                        var bulkAction    = $( event.target ).siblings( 'select' ).val(),
 
1969
                                itemsSelected = $bulkActionForm.find( 'input[name="checked[]"]:checked' ),
 
1970
                                success       = 0,
 
1971
                                error         = 0,
 
1972
                                errorMessages = [],
 
1973
                                type, action;
 
1974
 
 
1975
                        // Determine which type of item we're dealing with.
 
1976
                        switch ( pagenow ) {
 
1977
                                case 'plugins':
 
1978
                                case 'plugins-network':
 
1979
                                        type = 'plugin';
 
1980
                                        break;
 
1981
 
 
1982
                                case 'themes-network':
 
1983
                                        type = 'theme';
 
1984
                                        break;
 
1985
 
 
1986
                                default:
 
1987
                                        return;
 
1988
                        }
 
1989
 
 
1990
                        // Bail if there were no items selected.
 
1991
                        if ( ! itemsSelected.length ) {
 
1992
                                event.preventDefault();
 
1993
                                $( 'html, body' ).animate( { scrollTop: 0 } );
 
1994
 
 
1995
                                return wp.updates.addAdminNotice( {
 
1996
                                        id:        'no-items-selected',
 
1997
                                        className: 'notice-error is-dismissible',
 
1998
                                        message:   wp.updates.l10n.noItemsSelected
 
1999
                                } );
 
2000
                        }
 
2001
 
 
2002
                        // Determine the type of request we're dealing with.
 
2003
                        switch ( bulkAction ) {
 
2004
                                case 'update-selected':
 
2005
                                        action = bulkAction.replace( 'selected', type );
 
2006
                                        break;
 
2007
 
 
2008
                                case 'delete-selected':
 
2009
                                        if ( ! window.confirm( 'plugin' === type ? wp.updates.l10n.aysBulkDelete : wp.updates.l10n.aysBulkDeleteThemes ) ) {
 
2010
                                                event.preventDefault();
 
2011
                                                return;
 
2012
                                        }
 
2013
 
 
2014
                                        action = bulkAction.replace( 'selected', type );
 
2015
                                        break;
 
2016
 
 
2017
                                default:
 
2018
                                        return;
 
2019
                        }
 
2020
 
 
2021
                        wp.updates.maybeRequestFilesystemCredentials( event );
 
2022
 
 
2023
                        event.preventDefault();
 
2024
 
 
2025
                        // Un-check the bulk checkboxes.
 
2026
                        $bulkActionForm.find( '.manage-column [type="checkbox"]' ).prop( 'checked', false );
 
2027
 
 
2028
                        $document.trigger( 'wp-' + type + '-bulk-' + bulkAction, itemsSelected );
 
2029
 
 
2030
                        // Find all the checkboxes which have been checked.
 
2031
                        itemsSelected.each( function( index, element ) {
 
2032
                                var $checkbox = $( element ),
 
2033
                                        $itemRow = $checkbox.parents( 'tr' );
 
2034
 
 
2035
                                // Only add update-able items to the update queue.
 
2036
                                if ( 'update-selected' === bulkAction && ( ! $itemRow.hasClass( 'update' ) || $itemRow.find( 'notice-error' ).length ) ) {
 
2037
 
 
2038
                                        // Un-check the box.
 
2039
                                        $checkbox.prop( 'checked', false );
 
2040
                                        return;
 
2041
                                }
 
2042
 
 
2043
                                // Add it to the queue.
 
2044
                                wp.updates.queue.push( {
 
2045
                                        action: action,
 
2046
                                        data:   {
 
2047
                                                plugin: $itemRow.data( 'plugin' ),
 
2048
                                                slug:   $itemRow.data( 'slug' )
 
2049
                                        }
 
2050
                                } );
 
2051
                        } );
 
2052
 
 
2053
                        // Display bulk notification for updates of any kind.
 
2054
                        $document.on( 'wp-plugin-update-success wp-plugin-update-error wp-theme-update-success wp-theme-update-error', function( event, response ) {
 
2055
                                var $itemRow = $( '[data-slug="' + response.slug + '"]' ),
 
2056
                                        $bulkActionNotice, itemName;
 
2057
 
 
2058
                                if ( 'wp-' + response.update + '-update-success' === event.type ) {
 
2059
                                        success++;
 
2060
                                } else {
 
2061
                                        itemName = response.pluginName ? response.pluginName : $itemRow.find( '.column-primary strong' ).text();
 
2062
 
 
2063
                                        error++;
 
2064
                                        errorMessages.push( itemName + ': ' + response.errorMessage );
 
2065
                                }
 
2066
 
 
2067
                                $itemRow.find( 'input[name="checked[]"]:checked' ).prop( 'checked', false );
 
2068
 
 
2069
                                wp.updates.adminNotice = wp.template( 'wp-bulk-updates-admin-notice' );
 
2070
 
 
2071
                                wp.updates.addAdminNotice( {
 
2072
                                        id:            'bulk-action-notice',
 
2073
                                        className:     'bulk-action-notice',
 
2074
                                        successes:     success,
 
2075
                                        errors:        error,
 
2076
                                        errorMessages: errorMessages,
 
2077
                                        type:          response.update
 
2078
                                } );
 
2079
 
 
2080
                                $bulkActionNotice = $( '#bulk-action-notice' ).on( 'click', 'button', function() {
 
2081
                                        // $( this ) is the clicked button, no need to get it again.
 
2082
                                        $( this )
 
2083
                                                .toggleClass( 'bulk-action-errors-collapsed' )
 
2084
                                                .attr( 'aria-expanded', ! $( this ).hasClass( 'bulk-action-errors-collapsed' ) );
 
2085
                                        // Show the errors list.
 
2086
                                        $bulkActionNotice.find( '.bulk-action-errors' ).toggleClass( 'hidden' );
 
2087
                                } );
 
2088
 
 
2089
                                if ( error > 0 && ! wp.updates.queue.length ) {
 
2090
                                        $( 'html, body' ).animate( { scrollTop: 0 } );
 
2091
                                }
 
2092
                        } );
 
2093
 
 
2094
                        // Reset admin notice template after #bulk-action-notice was added.
 
2095
                        $document.on( 'wp-updates-notice-added', function() {
 
2096
                                wp.updates.adminNotice = wp.template( 'wp-updates-admin-notice' );
 
2097
                        } );
 
2098
 
 
2099
                        // Check the queue, now that the event handlers have been added.
 
2100
                        wp.updates.queueChecker();
 
2101
                } );
 
2102
 
 
2103
                if ( $pluginInstallSearch.length ) {
 
2104
                        $pluginInstallSearch.attr( 'aria-describedby', 'live-search-desc' );
 
2105
                }
 
2106
 
 
2107
                /**
 
2108
                 * Handles changes to the plugin search box on the new-plugin page,
 
2109
                 * searching the repository dynamically.
 
2110
                 *
 
2111
                 * @since 4.6.0
 
2112
                 */
 
2113
                $pluginInstallSearch.on( 'keyup input', _.debounce( function( event, eventtype ) {
 
2114
                        var $searchTab = $( '.plugin-install-search' ), data, searchLocation;
 
2115
 
 
2116
                        data = {
 
2117
                                _ajax_nonce: wp.updates.ajaxNonce,
 
2118
                                s:           event.target.value,
 
2119
                                tab:         'search',
 
2120
                                type:        $( '#typeselector' ).val(),
 
2121
                                pagenow:     pagenow
 
2122
                        };
 
2123
                        searchLocation = location.href.split( '?' )[ 0 ] + '?' + $.param( _.omit( data, [ '_ajax_nonce', 'pagenow' ] ) );
 
2124
 
 
2125
                        // Clear on escape.
 
2126
                        if ( 'keyup' === event.type && 27 === event.which ) {
 
2127
                                event.target.value = '';
 
2128
                        }
 
2129
 
 
2130
                        if ( wp.updates.searchTerm === data.s && 'typechange' !== eventtype ) {
 
2131
                                return;
 
2132
                        } else {
 
2133
                                $pluginFilter.empty();
 
2134
                                wp.updates.searchTerm = data.s;
 
2135
                        }
 
2136
 
 
2137
                        if ( window.history && window.history.replaceState ) {
 
2138
                                window.history.replaceState( null, '', searchLocation );
 
2139
                        }
 
2140
 
 
2141
                        if ( ! $searchTab.length ) {
 
2142
                                $searchTab = $( '<li class="plugin-install-search" />' )
 
2143
                                        .append( $( '<a />', {
 
2144
                                                'class': 'current',
 
2145
                                                'href': searchLocation,
 
2146
                                                'text': wp.updates.l10n.searchResultsLabel
 
2147
                                        } ) );
 
2148
 
 
2149
                                $( '.wp-filter .filter-links .current' )
 
2150
                                        .removeClass( 'current' )
 
2151
                                        .parents( '.filter-links' )
 
2152
                                        .prepend( $searchTab );
 
2153
 
 
2154
                                $pluginFilter.prev( 'p' ).remove();
 
2155
                                $( '.plugins-popular-tags-wrapper' ).remove();
 
2156
                        }
 
2157
 
 
2158
                        if ( 'undefined' !== typeof wp.updates.searchRequest ) {
 
2159
                                wp.updates.searchRequest.abort();
 
2160
                        }
 
2161
                        $( 'body' ).addClass( 'loading-content' );
 
2162
 
 
2163
                        wp.updates.searchRequest = wp.ajax.post( 'search-install-plugins', data ).done( function( response ) {
 
2164
                                $( 'body' ).removeClass( 'loading-content' );
 
2165
                                $pluginFilter.append( response.items );
 
2166
                                delete wp.updates.searchRequest;
 
2167
 
 
2168
                                if ( 0 === response.count ) {
 
2169
                                        wp.a11y.speak( wp.updates.l10n.noPluginsFound );
 
2170
                                } else {
 
2171
                                        wp.a11y.speak( wp.updates.l10n.pluginsFound.replace( '%d', response.count ) );
 
2172
                                }
 
2173
                        } );
 
2174
                }, 500 ) );
 
2175
 
 
2176
                if ( $pluginSearch.length ) {
 
2177
                        $pluginSearch.attr( 'aria-describedby', 'live-search-desc' );
 
2178
                }
 
2179
 
 
2180
                /**
 
2181
                 * Handles changes to the plugin search box on the Installed Plugins screen,
 
2182
                 * searching the plugin list dynamically.
 
2183
                 *
 
2184
                 * @since 4.6.0
 
2185
                 */
 
2186
                $pluginSearch.on( 'keyup input', _.debounce( function( event ) {
 
2187
                        var data = {
 
2188
                                _ajax_nonce: wp.updates.ajaxNonce,
 
2189
                                s:           event.target.value,
 
2190
                                pagenow:     pagenow
 
2191
                        };
 
2192
 
 
2193
                        // Clear on escape.
 
2194
                        if ( 'keyup' === event.type && 27 === event.which ) {
 
2195
                                event.target.value = '';
 
2196
                        }
 
2197
 
 
2198
                        if ( wp.updates.searchTerm === data.s ) {
 
2199
                                return;
 
2200
                        } else {
 
2201
                                wp.updates.searchTerm = data.s;
 
2202
                        }
 
2203
 
 
2204
                        if ( window.history && window.history.replaceState ) {
 
2205
                                window.history.replaceState( null, '', location.href.split( '?' )[ 0 ] + '?s=' + data.s );
 
2206
                        }
 
2207
 
 
2208
                        if ( 'undefined' !== typeof wp.updates.searchRequest ) {
 
2209
                                wp.updates.searchRequest.abort();
 
2210
                        }
 
2211
 
 
2212
                        $bulkActionForm.empty();
 
2213
                        $( 'body' ).addClass( 'loading-content' );
 
2214
 
 
2215
                        wp.updates.searchRequest = wp.ajax.post( 'search-plugins', data ).done( function( response ) {
 
2216
 
 
2217
                                // Can we just ditch this whole subtitle business?
 
2218
                                var $subTitle    = $( '<span />' ).addClass( 'subtitle' ).html( wp.updates.l10n.searchResults.replace( '%s', _.escape( data.s ) ) ),
 
2219
                                        $oldSubTitle = $( '.wrap .subtitle' );
 
2220
 
 
2221
                                if ( ! data.s.length ) {
 
2222
                                        $oldSubTitle.remove();
 
2223
                                } else if ( $oldSubTitle.length ) {
 
2224
                                        $oldSubTitle.replaceWith( $subTitle );
 
2225
                                } else {
 
2226
                                        $( '.wrap h1' ).append( $subTitle );
 
2227
                                }
 
2228
 
 
2229
                                $( 'body' ).removeClass( 'loading-content' );
 
2230
                                $bulkActionForm.append( response.items );
 
2231
                                delete wp.updates.searchRequest;
 
2232
 
 
2233
                                if ( 0 === response.count ) {
 
2234
                                        wp.a11y.speak( wp.updates.l10n.noPluginsFound );
 
2235
                                } else {
 
2236
                                        wp.a11y.speak( wp.updates.l10n.pluginsFound.replace( '%d', response.count ) );
 
2237
                                }
 
2238
                        } );
 
2239
                }, 500 ) );
 
2240
 
 
2241
                /**
 
2242
                 * Trigger a search event when the search form gets submitted.
 
2243
                 *
 
2244
                 * @since 4.6.0
 
2245
                 */
 
2246
                $document.on( 'submit', '.search-plugins', function( event ) {
 
2247
                        event.preventDefault();
 
2248
 
 
2249
                        $( 'input.wp-filter-search' ).trigger( 'input' );
 
2250
                } );
 
2251
 
 
2252
                /**
 
2253
                 * Trigger a search event when the search type gets changed.
 
2254
                 *
 
2255
                 * @since 4.6.0
 
2256
                 */
 
2257
                $( '#typeselector' ).on( 'change', function() {
 
2258
                        var $search = $( 'input[name="s"]' );
 
2259
 
 
2260
                        if ( $search.val().length ) {
 
2261
                                $search.trigger( 'input', 'typechange' );
 
2262
                        }
 
2263
                } );
 
2264
 
 
2265
                /**
 
2266
                 * Click handler for updating a plugin from the details modal on `plugin-install.php`.
 
2267
                 *
 
2268
                 * @since 4.2.0
 
2269
                 *
 
2270
                 * @param {Event} event Event interface.
 
2271
                 */
 
2272
                $( '#plugin_update_from_iframe' ).on( 'click', function( event ) {
 
2273
                        var target = window.parent === window ? null : window.parent,
 
2274
                                update;
 
2275
 
543
2276
                        $.support.postMessage = !! window.postMessage;
544
2277
 
545
 
                        if ( $.support.postMessage === false || target === null || window.parent.location.pathname.indexOf( 'update-core.php' ) !== -1 )
 
2278
                        if ( false === $.support.postMessage || null === target || -1 !== window.parent.location.pathname.indexOf( 'update-core.php' ) ) {
546
2279
                                return;
547
 
 
548
 
                        e.preventDefault();
549
 
 
550
 
                        job = {
551
 
                                action: 'updatePlugin',
552
 
                                type: 'update-plugin',
553
 
                                data: {
 
2280
                        }
 
2281
 
 
2282
                        event.preventDefault();
 
2283
 
 
2284
                        update = {
 
2285
                                action: 'update-plugin',
 
2286
                                data:   {
554
2287
                                        plugin: $( this ).data( 'plugin' ),
 
2288
                                        slug:   $( this ).data( 'slug' )
 
2289
                                }
 
2290
                        };
 
2291
 
 
2292
                        target.postMessage( JSON.stringify( update ), window.location.origin );
 
2293
                } );
 
2294
 
 
2295
                /**
 
2296
                 * Click handler for installing a plugin from the details modal on `plugin-install.php`.
 
2297
                 *
 
2298
                 * @since 4.6.0
 
2299
                 *
 
2300
                 * @param {Event} event Event interface.
 
2301
                 */
 
2302
                $( '#plugin_install_from_iframe' ).on( 'click', function( event ) {
 
2303
                        var target = window.parent === window ? null : window.parent,
 
2304
                                install;
 
2305
 
 
2306
                        $.support.postMessage = !! window.postMessage;
 
2307
 
 
2308
                        if ( false === $.support.postMessage || null === target || -1 !== window.parent.location.pathname.indexOf( 'index.php' ) ) {
 
2309
                                return;
 
2310
                        }
 
2311
 
 
2312
                        event.preventDefault();
 
2313
 
 
2314
                        install = {
 
2315
                                action: 'install-plugin',
 
2316
                                data:   {
555
2317
                                        slug: $( this ).data( 'slug' )
556
2318
                                }
557
2319
                        };
558
2320
 
559
 
                        target.postMessage( JSON.stringify( job ), window.location.origin );
560
 
                });
561
 
 
562
 
        } );
563
 
 
564
 
        $( window ).on( 'message', function( e ) {
565
 
                var event = e.originalEvent,
566
 
                        message,
567
 
                        loc = document.location,
568
 
                        expectedOrigin = loc.protocol + '//' + loc.hostname;
569
 
 
570
 
                if ( event.origin !== expectedOrigin ) {
571
 
                        return;
572
 
                }
573
 
 
574
 
                message = $.parseJSON( event.data );
575
 
 
576
 
                if ( typeof message.action === 'undefined' ) {
577
 
                        return;
578
 
                }
579
 
 
580
 
                switch (message.action){
581
 
                        case 'decrementUpdateCount' :
582
 
                                wp.updates.decrementCount( message.upgradeType );
583
 
                                break;
584
 
                        case 'updatePlugin' :
585
 
                                tb_remove();
586
 
 
587
 
                                wp.updates.updateQueue.push( message );
588
 
                                wp.updates.queueChecker();
589
 
                                break;
590
 
                }
591
 
 
592
 
        } );
593
 
 
594
 
        $( window ).on( 'beforeunload', wp.updates.beforeunload );
595
 
 
596
 
})( jQuery, window.wp, window.pagenow, window.ajaxurl );
 
2321
                        target.postMessage( JSON.stringify( install ), window.location.origin );
 
2322
                } );
 
2323
 
 
2324
                /**
 
2325
                 * Handles postMessage events.
 
2326
                 *
 
2327
                 * @since 4.2.0
 
2328
                 * @since 4.6.0 Switched `update-plugin` action to use the queue.
 
2329
                 *
 
2330
                 * @param {Event} event Event interface.
 
2331
                 */
 
2332
                $( window ).on( 'message', function( event ) {
 
2333
                        var originalEvent  = event.originalEvent,
 
2334
                                expectedOrigin = document.location.protocol + '//' + document.location.hostname,
 
2335
                                message;
 
2336
 
 
2337
                        if ( originalEvent.origin !== expectedOrigin ) {
 
2338
                                return;
 
2339
                        }
 
2340
 
 
2341
                        try {
 
2342
                                message = $.parseJSON( originalEvent.data );
 
2343
                        } catch ( e ) {
 
2344
                                return;
 
2345
                        }
 
2346
 
 
2347
                        if ( 'undefined' === typeof message.action ) {
 
2348
                                return;
 
2349
                        }
 
2350
 
 
2351
                        switch ( message.action ) {
 
2352
 
 
2353
                                // Called from `wp-admin/includes/class-wp-upgrader-skins.php`.
 
2354
                                case 'decrementUpdateCount':
 
2355
                                        /** @property {string} message.upgradeType */
 
2356
                                        wp.updates.decrementCount( message.upgradeType );
 
2357
                                        break;
 
2358
 
 
2359
                                case 'install-plugin':
 
2360
                                case 'update-plugin':
 
2361
                                        /* jscs:disable requireCamelCaseOrUpperCaseIdentifiers */
 
2362
                                        window.tb_remove();
 
2363
                                        /* jscs:enable */
 
2364
 
 
2365
                                        message.data = wp.updates._addCallbacks( message.data, message.action );
 
2366
 
 
2367
                                        wp.updates.queue.push( message );
 
2368
                                        wp.updates.queueChecker();
 
2369
                                        break;
 
2370
                        }
 
2371
                } );
 
2372
 
 
2373
                /**
 
2374
                 * Adds a callback to display a warning before leaving the page.
 
2375
                 *
 
2376
                 * @since 4.2.0
 
2377
                 */
 
2378
                $( window ).on( 'beforeunload', wp.updates.beforeunload );
 
2379
        } );
 
2380
})( jQuery, window.wp, _.extend( window._wpUpdatesSettings, window._wpUpdatesItemCounts || {} ) );