+ /**
+ * Takes care of the steps that need to happen when the modal is canceled out.
+ *
+ * @since 4.2.0
+ * @since 4.6.0 Triggers an event for callbacks to listen to and add their actions.
+ */
+ wp.updates.requestForCredentialsModalCancel = function() {
+
+ // Not ajaxLocked and no queue means we already have cleared things up.
+ if ( ! wp.updates.ajaxLocked && ! wp.updates.queue.length ) {
+ return;
+ }
+
+ _.each( wp.updates.queue, function( job ) {
+ $document.trigger( 'credential-modal-cancel', job );
+ } );
+
+ // Remove the lock, and clear the queue.
+ wp.updates.ajaxLocked = false;
+ wp.updates.queue = [];
+
+ wp.updates.requestForCredentialsModalClose();
+ };
+
+ /**
+ * Displays an error message in the request for credentials form.
+ *
+ * @since 4.2.0
+ *
+ * @param {string} message Error message.
+ */
+ wp.updates.showErrorInCredentialsForm = function( message ) {
+ var $modal = $( '#request-filesystem-credentials-form' );
+
+ // Remove any existing error.
+ $modal.find( '.notice' ).remove();
+ $modal.find( '#request-filesystem-credentials-title' ).after( '<div class="notice notice-alt notice-error"><p>' + message + '</p></div>' );
+ };
+
+ /**
+ * Handles credential errors and runs events that need to happen in that case.
+ *
+ * @since 4.2.0
+ *
+ * @param {object} response Ajax response.
+ * @param {string} action The type of request to perform.
+ */
+ wp.updates.credentialError = function( response, action ) {
+
+ // Restore callbacks.
+ response = wp.updates._addCallbacks( response, action );
+
+ wp.updates.queue.unshift( {
+ action: action,
+
+ /*
+ * Not cool that we're depending on response for this data.
+ * This would feel more whole in a view all tied together.
+ */
+ data: response
+ } );
+
+ wp.updates.filesystemCredentials.available = false;
+ wp.updates.showErrorInCredentialsForm( response.errorMessage );
+ wp.updates.requestFilesystemCredentials();
+ };
+
+ /**
+ * Handles credentials errors if it could not connect to the filesystem.
+ *
+ * @since 4.6.0
+ *
+ * @typedef {object} maybeHandleCredentialError
+ * @param {object} response Response from the server.
+ * @param {string} response.errorCode Error code for the error that occurred.
+ * @param {string} response.errorMessage The error that occurred.
+ * @param {string} action The type of request to perform.
+ * @returns {boolean} Whether there is an error that needs to be handled or not.
+ */
+ wp.updates.maybeHandleCredentialError = function( response, action ) {
+ if ( wp.updates.shouldRequestFilesystemCredentials && response.errorCode && 'unable_to_connect_to_filesystem' === response.errorCode ) {
+ wp.updates.credentialError( response, action );
+ return true;
+ }
+
+ return false;
+ };
+
+ /**
+ * Validates an AJAX response to ensure it's a proper object.
+ *
+ * If the response deems to be invalid, an admin notice is being displayed.
+ *
+ * @param {(object|string)} response Response from the server.
+ * @param {function=} response.always Optional. Callback for when the Deferred is resolved or rejected.
+ * @param {string=} response.statusText Optional. Status message corresponding to the status code.
+ * @param {string=} response.responseText Optional. Request response as text.
+ * @param {string} action Type of action the response is referring to. Can be 'delete',
+ * 'update' or 'install'.
+ */
+ wp.updates.isValidResponse = function( response, action ) {
+ var error = wp.updates.l10n.unknownError,
+ errorMessage;
+
+ // Make sure the response is a valid data object and not a Promise object.
+ if ( _.isObject( response ) && ! _.isFunction( response.always ) ) {
+ return true;
+ }
+
+ if ( _.isString( response ) && '-1' === response ) {
+ error = wp.updates.l10n.nonceError;
+ } else if ( _.isString( response ) ) {
+ error = response;
+ } else if ( 'undefined' !== typeof response.readyState && 0 === response.readyState ) {
+ error = wp.updates.l10n.connectionError;
+ } else if ( _.isString( response.responseText ) && '' !== response.responseText ) {
+ error = response.responseText;
+ } else if ( _.isString( response.statusText ) ) {
+ error = response.statusText;
+ }
+
+ switch ( action ) {
+ case 'update':
+ errorMessage = wp.updates.l10n.updateFailed;
+ break;
+
+ case 'install':
+ errorMessage = wp.updates.l10n.installFailed;
+ break;
+
+ case 'delete':
+ errorMessage = wp.updates.l10n.deleteFailed;
+ break;
+ }
+
+ // Messages are escaped, remove HTML tags to make them more readable.
+ error = error.replace( /<[\/a-z][^<>]*>/gi, '' );
+ errorMessage = errorMessage.replace( '%s', error );
+
+ // Add admin notice.
+ wp.updates.addAdminNotice( {
+ id: 'unknown_error',
+ className: 'notice-error is-dismissible',
+ message: _.escape( errorMessage )
+ } );
+
+ // Remove the lock, and clear the queue.
+ wp.updates.ajaxLocked = false;
+ wp.updates.queue = [];
+
+ // Change buttons of all running updates.
+ $( '.button.updating-message' )
+ .removeClass( 'updating-message' )
+ .removeAttr( 'aria-label' )
+ .prop( 'disabled', true )
+ .text( wp.updates.l10n.updateFailedShort );
+
+ $( '.updating-message:not(.button):not(.thickbox)' )
+ .removeClass( 'updating-message notice-warning' )
+ .addClass( 'notice-error' )
+ .find( 'p' )
+ .removeAttr( 'aria-label' )
+ .text( errorMessage );
+
+ wp.a11y.speak( errorMessage, 'assertive' );
+
+ return false;
+ };
+
+ /**
+ * Potentially adds an AYS to a user attempting to leave the page.
+ *
+ * If an update is on-going and a user attempts to leave the page,
+ * opens an "Are you sure?" alert.
+ *
+ * @since 4.2.0
+ */
+ wp.updates.beforeunload = function() {
+ if ( wp.updates.ajaxLocked ) {
+ return wp.updates.l10n.beforeunload;
+ }
+ };
+
+ $( function() {
+ var $pluginFilter = $( '#plugin-filter' ),
+ $bulkActionForm = $( '#bulk-action-form' ),
+ $filesystemModal = $( '#request-filesystem-credentials-dialog' ),
+ $pluginSearch = $( '.plugins-php .wp-filter-search' ),
+ $pluginInstallSearch = $( '.plugin-install-php .wp-filter-search' );
+
+ /*
+ * Whether a user needs to submit filesystem credentials.
+ *
+ * This is based on whether the form was output on the page server-side.
+ *
+ * @see {wp_print_request_filesystem_credentials_modal() in PHP}
+ */
+ wp.updates.shouldRequestFilesystemCredentials = $filesystemModal.length > 0;
+
+ /**
+ * File system credentials form submit noop-er / handler.
+ *
+ * @since 4.2.0
+ */
+ $filesystemModal.on( 'submit', 'form', function( event ) {
+ event.preventDefault();
+
+ // Persist the credentials input by the user for the duration of the page load.
+ wp.updates.filesystemCredentials.ftp.hostname = $( '#hostname' ).val();
+ wp.updates.filesystemCredentials.ftp.username = $( '#username' ).val();
+ wp.updates.filesystemCredentials.ftp.password = $( '#password' ).val();
+ wp.updates.filesystemCredentials.ftp.connectionType = $( 'input[name="connection_type"]:checked' ).val();
+ wp.updates.filesystemCredentials.ssh.publicKey = $( '#public_key' ).val();
+ wp.updates.filesystemCredentials.ssh.privateKey = $( '#private_key' ).val();
+ wp.updates.filesystemCredentials.available = true;
+
+ // Unlock and invoke the queue.
+ wp.updates.ajaxLocked = false;
+ wp.updates.queueChecker();
+
+ wp.updates.requestForCredentialsModalClose();
+ } );
+
+ /**
+ * Closes the request credentials modal when clicking the 'Cancel' button or outside of the modal.
+ *
+ * @since 4.2.0
+ */
+ $filesystemModal.on( 'click', '[data-js-action="close"], .notification-dialog-background', wp.updates.requestForCredentialsModalCancel );
+
+ /**
+ * Hide SSH fields when not selected.
+ *
+ * @since 4.2.0
+ */
+ $filesystemModal.on( 'change', 'input[name="connection_type"]', function() {
+ $( '#ssh-keys' ).toggleClass( 'hidden', ( 'ssh' !== $( this ).val() ) );
+ } ).change();
+
+ /**
+ * Handles events after the credential modal was closed.
+ *
+ * @since 4.6.0
+ *
+ * @param {Event} event Event interface.
+ * @param {string} job The install/update.delete request.
+ */
+ $document.on( 'credential-modal-cancel', function( event, job ) {
+ var $updatingMessage = $( '.updating-message' ),
+ $message, originalText;
+
+ if ( 'import' === pagenow ) {
+ $updatingMessage.removeClass( 'updating-message' );
+ } else if ( 'plugins' === pagenow || 'plugins-network' === pagenow ) {
+ if ( 'update-plugin' === job.action ) {
+ $message = $( 'tr[data-plugin="' + job.data.plugin + '"]' ).find( '.update-message' );
+ } else if ( 'delete-plugin' === job.action ) {
+ $message = $( '[data-plugin="' + job.data.plugin + '"]' ).find( '.row-actions a.delete' );
+ }
+ } else if ( 'themes' === pagenow || 'themes-network' === pagenow ) {
+ if ( 'update-theme' === job.action ) {
+ $message = $( '[data-slug="' + job.data.slug + '"]' ).find( '.update-message' );
+ } else if ( 'delete-theme' === job.action && 'themes-network' === pagenow ) {
+ $message = $( '[data-slug="' + job.data.slug + '"]' ).find( '.row-actions a.delete' );
+ } else if ( 'delete-theme' === job.action && 'themes' === pagenow ) {
+ $message = $( '.theme-actions .delete-theme' );
+ }
+ } else {
+ $message = $updatingMessage;
+ }
+
+ if ( $message && $message.hasClass( 'updating-message' ) ) {
+ originalText = $message.data( 'originaltext' );
+
+ if ( 'undefined' === typeof originalText ) {
+ originalText = $( '<p>' ).html( $message.find( 'p' ).data( 'originaltext' ) );
+ }
+
+ $message
+ .removeClass( 'updating-message' )
+ .html( originalText );
+
+ if ( 'plugin-install' === pagenow || 'plugin-install-network' === pagenow ) {
+ if ( 'update-plugin' === job.action ) {
+ $message.attr( 'aria-label', wp.updates.l10n.updateNowLabel.replace( '%s', $message.data( 'name' ) ) );
+ } else if ( 'install-plugin' === job.action ) {
+ $message.attr( 'aria-label', wp.updates.l10n.installNowLabel.replace( '%s', $message.data( 'name' ) ) );
+ }
+ }
+ }
+
+ wp.a11y.speak( wp.updates.l10n.updateCancel, 'polite' );
+ } );
+
+ /**
+ * Click handler for plugin updates in List Table view.
+ *
+ * @since 4.2.0
+ *
+ * @param {Event} event Event interface.
+ */
+ $bulkActionForm.on( 'click', '[data-plugin] .update-link', function( event ) {
+ var $message = $( event.target ),
+ $pluginRow = $message.parents( 'tr' );
+
+ event.preventDefault();
+
+ if ( $message.hasClass( 'updating-message' ) || $message.hasClass( 'button-disabled' ) ) {
+ return;
+ }
+
+ wp.updates.maybeRequestFilesystemCredentials( event );
+
+ // Return the user to the input box of the plugin's table row after closing the modal.
+ wp.updates.$elToReturnFocusToFromCredentialsModal = $pluginRow.find( '.check-column input' );
+ wp.updates.updatePlugin( {
+ plugin: $pluginRow.data( 'plugin' ),
+ slug: $pluginRow.data( 'slug' )
+ } );
+ } );
+
+ /**
+ * Click handler for plugin updates in plugin install view.
+ *
+ * @since 4.2.0
+ *
+ * @param {Event} event Event interface.
+ */
+ $pluginFilter.on( 'click', '.update-now', function( event ) {
+ var $button = $( event.target );
+ event.preventDefault();
+
+ if ( $button.hasClass( 'updating-message' ) || $button.hasClass( 'button-disabled' ) ) {
+ return;
+ }
+
+ wp.updates.maybeRequestFilesystemCredentials( event );
+
+ wp.updates.updatePlugin( {
+ plugin: $button.data( 'plugin' ),
+ slug: $button.data( 'slug' )
+ } );
+ } );
+
+ /**
+ * Click handler for plugin installs in plugin install view.
+ *
+ * @since 4.6.0
+ *
+ * @param {Event} event Event interface.
+ */
+ $pluginFilter.on( 'click', '.install-now', function( event ) {
+ var $button = $( event.target );
+ event.preventDefault();
+
+ if ( $button.hasClass( 'updating-message' ) || $button.hasClass( 'button-disabled' ) ) {
+ return;
+ }
+
+ if ( wp.updates.shouldRequestFilesystemCredentials && ! wp.updates.ajaxLocked ) {
+ wp.updates.requestFilesystemCredentials( event );
+
+ $document.on( 'credential-modal-cancel', function() {
+ var $message = $( '.install-now.updating-message' );
+
+ $message
+ .removeClass( 'updating-message' )
+ .text( wp.updates.l10n.installNow );
+
+ wp.a11y.speak( wp.updates.l10n.updateCancel, 'polite' );
+ } );
+ }
+
+ wp.updates.installPlugin( {
+ slug: $button.data( 'slug' )
+ } );
+ } );
+
+ /**
+ * Click handler for importer plugins installs in the Import screen.
+ *
+ * @since 4.6.0
+ *
+ * @param {Event} event Event interface.
+ */
+ $document.on( 'click', '.importer-item .install-now', function( event ) {
+ var $button = $( event.target ),
+ pluginName = $( this ).data( 'name' );
+
+ event.preventDefault();
+
+ if ( $button.hasClass( 'updating-message' ) ) {
+ return;
+ }
+
+ if ( wp.updates.shouldRequestFilesystemCredentials && ! wp.updates.ajaxLocked ) {
+ wp.updates.requestFilesystemCredentials( event );
+
+ $document.on( 'credential-modal-cancel', function() {
+
+ $button
+ .removeClass( 'updating-message' )
+ .text( wp.updates.l10n.installNow )
+ .attr( 'aria-label', wp.updates.l10n.installNowLabel.replace( '%s', pluginName ) );
+
+ wp.a11y.speak( wp.updates.l10n.updateCancel, 'polite' );
+ } );
+ }
+
+ wp.updates.installPlugin( {
+ slug: $button.data( 'slug' ),
+ success: wp.updates.installImporterSuccess,
+ error: wp.updates.installImporterError
+ } );
+ } );
+
+ /**
+ * Click handler for plugin deletions.
+ *
+ * @since 4.6.0
+ *
+ * @param {Event} event Event interface.
+ */
+ $bulkActionForm.on( 'click', '[data-plugin] a.delete', function( event ) {
+ var $pluginRow = $( event.target ).parents( 'tr' );
+
+ event.preventDefault();
+
+ if ( ! window.confirm( wp.updates.l10n.aysDeleteUninstall.replace( '%s', $pluginRow.find( '.plugin-title strong' ).text() ) ) ) {
+ return;
+ }
+
+ wp.updates.maybeRequestFilesystemCredentials( event );
+
+ wp.updates.deletePlugin( {
+ plugin: $pluginRow.data( 'plugin' ),
+ slug: $pluginRow.data( 'slug' )
+ } );
+
+ } );
+
+ /**
+ * Click handler for theme updates.
+ *
+ * @since 4.6.0
+ *
+ * @param {Event} event Event interface.
+ */
+ $document.on( 'click', '.themes-php.network-admin .update-link', function( event ) {
+ var $message = $( event.target ),
+ $themeRow = $message.parents( 'tr' );
+
+ event.preventDefault();
+
+ if ( $message.hasClass( 'updating-message' ) || $message.hasClass( 'button-disabled' ) ) {
+ return;
+ }
+
+ wp.updates.maybeRequestFilesystemCredentials( event );
+
+ // Return the user to the input box of the theme's table row after closing the modal.
+ wp.updates.$elToReturnFocusToFromCredentialsModal = $themeRow.find( '.check-column input' );
+ wp.updates.updateTheme( {
+ slug: $themeRow.data( 'slug' )
+ } );
+ } );
+
+ /**
+ * Click handler for theme deletions.
+ *
+ * @since 4.6.0
+ *
+ * @param {Event} event Event interface.
+ */
+ $document.on( 'click', '.themes-php.network-admin a.delete', function( event ) {
+ var $themeRow = $( event.target ).parents( 'tr' );
+
+ event.preventDefault();
+
+ if ( ! window.confirm( wp.updates.l10n.aysDelete.replace( '%s', $themeRow.find( '.theme-title strong' ).text() ) ) ) {
+ return;
+ }
+
+ wp.updates.maybeRequestFilesystemCredentials( event );
+
+ wp.updates.deleteTheme( {
+ slug: $themeRow.data( 'slug' )
+ } );
+ } );
+
+ /**
+ * Bulk action handler for plugins and themes.
+ *
+ * Handles both deletions and updates.
+ *
+ * @since 4.6.0
+ *
+ * @param {Event} event Event interface.
+ */
+ $bulkActionForm.on( 'click', '[type="submit"]', function( event ) {
+ var bulkAction = $( event.target ).siblings( 'select' ).val(),
+ itemsSelected = $bulkActionForm.find( 'input[name="checked[]"]:checked' ),
+ success = 0,
+ error = 0,
+ errorMessages = [],
+ type, action;
+
+ // Determine which type of item we're dealing with.
+ switch ( pagenow ) {
+ case 'plugins':
+ case 'plugins-network':
+ type = 'plugin';
+ break;
+
+ case 'themes-network':
+ type = 'theme';
+ break;
+
+ default:
+ return;
+ }
+
+ // Bail if there were no items selected.
+ if ( ! itemsSelected.length ) {
+ event.preventDefault();
+ $( 'html, body' ).animate( { scrollTop: 0 } );
+
+ return wp.updates.addAdminNotice( {
+ id: 'no-items-selected',
+ className: 'notice-error is-dismissible',
+ message: wp.updates.l10n.noItemsSelected
+ } );
+ }
+
+ // Determine the type of request we're dealing with.
+ switch ( bulkAction ) {
+ case 'update-selected':
+ action = bulkAction.replace( 'selected', type );
+ break;
+
+ case 'delete-selected':
+ if ( ! window.confirm( 'plugin' === type ? wp.updates.l10n.aysBulkDelete : wp.updates.l10n.aysBulkDeleteThemes ) ) {
+ event.preventDefault();
+ return;
+ }
+
+ action = bulkAction.replace( 'selected', type );
+ break;
+
+ default:
+ return;
+ }
+
+ wp.updates.maybeRequestFilesystemCredentials( event );
+
+ event.preventDefault();
+
+ // Un-check the bulk checkboxes.
+ $bulkActionForm.find( '.manage-column [type="checkbox"]' ).prop( 'checked', false );
+
+ $document.trigger( 'wp-' + type + '-bulk-' + bulkAction, itemsSelected );
+
+ // Find all the checkboxes which have been checked.
+ itemsSelected.each( function( index, element ) {
+ var $checkbox = $( element ),
+ $itemRow = $checkbox.parents( 'tr' );
+
+ // Only add update-able items to the update queue.
+ if ( 'update-selected' === bulkAction && ( ! $itemRow.hasClass( 'update' ) || $itemRow.find( 'notice-error' ).length ) ) {
+
+ // Un-check the box.
+ $checkbox.prop( 'checked', false );
+ return;
+ }
+
+ // Add it to the queue.
+ wp.updates.queue.push( {
+ action: action,
+ data: {
+ plugin: $itemRow.data( 'plugin' ),
+ slug: $itemRow.data( 'slug' )
+ }
+ } );
+ } );
+
+ // Display bulk notification for updates of any kind.
+ $document.on( 'wp-plugin-update-success wp-plugin-update-error wp-theme-update-success wp-theme-update-error', function( event, response ) {
+ var $itemRow = $( '[data-slug="' + response.slug + '"]' ),
+ $bulkActionNotice, itemName;
+
+ if ( 'wp-' + response.update + '-update-success' === event.type ) {
+ success++;
+ } else {
+ itemName = response.pluginName ? response.pluginName : $itemRow.find( '.column-primary strong' ).text();
+
+ error++;
+ errorMessages.push( itemName + ': ' + response.errorMessage );
+ }
+
+ $itemRow.find( 'input[name="checked[]"]:checked' ).prop( 'checked', false );
+
+ wp.updates.adminNotice = wp.template( 'wp-bulk-updates-admin-notice' );
+
+ wp.updates.addAdminNotice( {
+ id: 'bulk-action-notice',
+ className: 'bulk-action-notice',
+ successes: success,
+ errors: error,
+ errorMessages: errorMessages,
+ type: response.update
+ } );
+
+ $bulkActionNotice = $( '#bulk-action-notice' ).on( 'click', 'button', function() {
+ // $( this ) is the clicked button, no need to get it again.
+ $( this )
+ .toggleClass( 'bulk-action-errors-collapsed' )
+ .attr( 'aria-expanded', ! $( this ).hasClass( 'bulk-action-errors-collapsed' ) );
+ // Show the errors list.
+ $bulkActionNotice.find( '.bulk-action-errors' ).toggleClass( 'hidden' );
+ } );
+
+ if ( error > 0 && ! wp.updates.queue.length ) {
+ $( 'html, body' ).animate( { scrollTop: 0 } );
+ }
+ } );
+
+ // Reset admin notice template after #bulk-action-notice was added.
+ $document.on( 'wp-updates-notice-added', function() {
+ wp.updates.adminNotice = wp.template( 'wp-updates-admin-notice' );
+ } );
+
+ // Check the queue, now that the event handlers have been added.
+ wp.updates.queueChecker();
+ } );
+
+ if ( $pluginInstallSearch.length ) {
+ $pluginInstallSearch.attr( 'aria-describedby', 'live-search-desc' );
+ }
+
+ /**
+ * Handles changes to the plugin search box on the new-plugin page,
+ * searching the repository dynamically.
+ *
+ * @since 4.6.0
+ */
+ $pluginInstallSearch.on( 'keyup input', _.debounce( function( event, eventtype ) {
+ var $searchTab = $( '.plugin-install-search' ), data, searchLocation;
+
+ data = {
+ _ajax_nonce: wp.updates.ajaxNonce,
+ s: event.target.value,
+ tab: 'search',
+ type: $( '#typeselector' ).val(),
+ pagenow: pagenow
+ };
+ searchLocation = location.href.split( '?' )[ 0 ] + '?' + $.param( _.omit( data, [ '_ajax_nonce', 'pagenow' ] ) );
+
+ // Clear on escape.
+ if ( 'keyup' === event.type && 27 === event.which ) {
+ event.target.value = '';
+ }
+
+ if ( wp.updates.searchTerm === data.s && 'typechange' !== eventtype ) {
+ return;
+ } else {
+ $pluginFilter.empty();
+ wp.updates.searchTerm = data.s;
+ }
+
+ if ( window.history && window.history.replaceState ) {
+ window.history.replaceState( null, '', searchLocation );
+ }
+
+ if ( ! $searchTab.length ) {
+ $searchTab = $( '<li class="plugin-install-search" />' )
+ .append( $( '<a />', {
+ 'class': 'current',
+ 'href': searchLocation,
+ 'text': wp.updates.l10n.searchResultsLabel
+ } ) );
+
+ $( '.wp-filter .filter-links .current' )
+ .removeClass( 'current' )
+ .parents( '.filter-links' )
+ .prepend( $searchTab );
+
+ $pluginFilter.prev( 'p' ).remove();
+ $( '.plugins-popular-tags-wrapper' ).remove();
+ }
+
+ if ( 'undefined' !== typeof wp.updates.searchRequest ) {
+ wp.updates.searchRequest.abort();
+ }
+ $( 'body' ).addClass( 'loading-content' );
+
+ wp.updates.searchRequest = wp.ajax.post( 'search-install-plugins', data ).done( function( response ) {
+ $( 'body' ).removeClass( 'loading-content' );
+ $pluginFilter.append( response.items );
+ delete wp.updates.searchRequest;
+
+ if ( 0 === response.count ) {
+ wp.a11y.speak( wp.updates.l10n.noPluginsFound );
+ } else {
+ wp.a11y.speak( wp.updates.l10n.pluginsFound.replace( '%d', response.count ) );
+ }
+ } );
+ }, 500 ) );
+
+ if ( $pluginSearch.length ) {
+ $pluginSearch.attr( 'aria-describedby', 'live-search-desc' );
+ }
+
+ /**
+ * Handles changes to the plugin search box on the Installed Plugins screen,
+ * searching the plugin list dynamically.
+ *
+ * @since 4.6.0
+ */
+ $pluginSearch.on( 'keyup input', _.debounce( function( event ) {
+ var data = {
+ _ajax_nonce: wp.updates.ajaxNonce,
+ s: event.target.value,
+ pagenow: pagenow
+ };
+
+ // Clear on escape.
+ if ( 'keyup' === event.type && 27 === event.which ) {
+ event.target.value = '';
+ }
+
+ if ( wp.updates.searchTerm === data.s ) {
+ return;
+ } else {
+ wp.updates.searchTerm = data.s;
+ }
+
+ if ( window.history && window.history.replaceState ) {
+ window.history.replaceState( null, '', location.href.split( '?' )[ 0 ] + '?s=' + data.s );
+ }
+
+ if ( 'undefined' !== typeof wp.updates.searchRequest ) {
+ wp.updates.searchRequest.abort();
+ }
+
+ $bulkActionForm.empty();
+ $( 'body' ).addClass( 'loading-content' );
+
+ wp.updates.searchRequest = wp.ajax.post( 'search-plugins', data ).done( function( response ) {
+
+ // Can we just ditch this whole subtitle business?
+ var $subTitle = $( '<span />' ).addClass( 'subtitle' ).html( wp.updates.l10n.searchResults.replace( '%s', _.escape( data.s ) ) ),
+ $oldSubTitle = $( '.wrap .subtitle' );
+
+ if ( ! data.s.length ) {
+ $oldSubTitle.remove();
+ } else if ( $oldSubTitle.length ) {
+ $oldSubTitle.replaceWith( $subTitle );
+ } else {
+ $( '.wrap h1' ).append( $subTitle );
+ }
+
+ $( 'body' ).removeClass( 'loading-content' );
+ $bulkActionForm.append( response.items );
+ delete wp.updates.searchRequest;
+
+ if ( 0 === response.count ) {
+ wp.a11y.speak( wp.updates.l10n.noPluginsFound );
+ } else {
+ wp.a11y.speak( wp.updates.l10n.pluginsFound.replace( '%d', response.count ) );
+ }
+ } );
+ }, 500 ) );
+
+ /**
+ * Trigger a search event when the search form gets submitted.
+ *
+ * @since 4.6.0
+ */
+ $document.on( 'submit', '.search-plugins', function( event ) {
+ event.preventDefault();
+
+ $( 'input.wp-filter-search' ).trigger( 'input' );
+ } );
+
+ /**
+ * Trigger a search event when the search type gets changed.
+ *
+ * @since 4.6.0
+ */
+ $( '#typeselector' ).on( 'change', function() {
+ var $search = $( 'input[name="s"]' );
+
+ if ( $search.val().length ) {
+ $search.trigger( 'input', 'typechange' );
+ }
+ } );
+
+ /**
+ * Click handler for updating a plugin from the details modal on `plugin-install.php`.
+ *
+ * @since 4.2.0
+ *
+ * @param {Event} event Event interface.
+ */
+ $( '#plugin_update_from_iframe' ).on( 'click', function( event ) {
+ var target = window.parent === window ? null : window.parent,
+ update;
+
+ $.support.postMessage = !! window.postMessage;
+
+ if ( false === $.support.postMessage || null === target || -1 !== window.parent.location.pathname.indexOf( 'update-core.php' ) ) {
+ return;
+ }
+
+ event.preventDefault();
+
+ update = {
+ action: 'update-plugin',
+ data: {
+ plugin: $( this ).data( 'plugin' ),
+ slug: $( this ).data( 'slug' )
+ }
+ };
+
+ target.postMessage( JSON.stringify( update ), window.location.origin );
+ } );
+
+ /**
+ * Click handler for installing a plugin from the details modal on `plugin-install.php`.
+ *
+ * @since 4.6.0
+ *
+ * @param {Event} event Event interface.
+ */
+ $( '#plugin_install_from_iframe' ).on( 'click', function( event ) {
+ var target = window.parent === window ? null : window.parent,
+ install;
+
+ $.support.postMessage = !! window.postMessage;
+
+ if ( false === $.support.postMessage || null === target || -1 !== window.parent.location.pathname.indexOf( 'index.php' ) ) {
+ return;
+ }
+
+ event.preventDefault();
+
+ install = {
+ action: 'install-plugin',
+ data: {
+ slug: $( this ).data( 'slug' )
+ }
+ };
+
+ target.postMessage( JSON.stringify( install ), window.location.origin );
+ } );
+
+ /**
+ * Handles postMessage events.
+ *
+ * @since 4.2.0
+ * @since 4.6.0 Switched `update-plugin` action to use the queue.
+ *
+ * @param {Event} event Event interface.
+ */
+ $( window ).on( 'message', function( event ) {
+ var originalEvent = event.originalEvent,
+ expectedOrigin = document.location.protocol + '//' + document.location.hostname,
+ message;
+
+ if ( originalEvent.origin !== expectedOrigin ) {
+ return;
+ }
+
+ try {
+ message = $.parseJSON( originalEvent.data );
+ } catch ( e ) {
+ return;
+ }
+
+ if ( 'undefined' === typeof message.action ) {
+ return;
+ }
+
+ switch ( message.action ) {
+
+ // Called from `wp-admin/includes/class-wp-upgrader-skins.php`.
+ case 'decrementUpdateCount':
+ /** @property {string} message.upgradeType */
+ wp.updates.decrementCount( message.upgradeType );
+ break;
+
+ case 'install-plugin':
+ case 'update-plugin':
+ /* jscs:disable requireCamelCaseOrUpperCaseIdentifiers */
+ window.tb_remove();
+ /* jscs:enable */
+
+ message.data = wp.updates._addCallbacks( message.data, message.action );
+
+ wp.updates.queue.push( message );
+ wp.updates.queueChecker();
+ break;
+ }
+ } );
+
+ /**
+ * Adds a callback to display a warning before leaving the page.
+ *
+ * @since 4.2.0
+ */
+ $( window ).on( 'beforeunload', wp.updates.beforeunload );
+ } );
+})( jQuery, window.wp, _.extend( window._wpUpdatesSettings, window._wpUpdatesItemCounts || {} ) );