diff --git a/src/wp-admin/css/forms.css b/src/wp-admin/css/forms.css index 256a51f795..b58cf7d4cc 100644 --- a/src/wp-admin/css/forms.css +++ b/src/wp-admin/css/forms.css @@ -850,6 +850,26 @@ table.form-table td .updated p { margin-bottom: 5px; } +/*------------------------------------------------------------------------------ + Credentials check dialog for Install and Updates +------------------------------------------------------------------------------*/ + +.request-filesystem-credentials-dialog { + display: none; +} + +.request-filesystem-credentials-dialog .notification-dialog{ + top: 15% +} + +.request-filesystem-credentials-dialog-content{ + margin: 25px; +} +.request-filesystem-credentials-dialog-content input[type="text"], +.request-filesystem-credentials-dialog-content input[type="password"]{ + width:85%; +} + /* =Media Queries -------------------------------------------------------------- */ diff --git a/src/wp-admin/includes/ajax-actions.php b/src/wp-admin/includes/ajax-actions.php index 81ba091519..430ae97ac9 100644 --- a/src/wp-admin/includes/ajax-actions.php +++ b/src/wp-admin/includes/ajax-actions.php @@ -2913,6 +2913,10 @@ function wp_ajax_install_plugin() { if ( is_wp_error( $result ) ) { $status['error'] = $result->get_error_message(); wp_send_json_error( $status ); + } else if ( is_null( $result ) ) { + $status['errorCode'] = __( 'unable_to_connect_to_filesystem' ); + $status['error'] = __( 'Unable to connect to the filesystem. Please confirm your credentials.' ); + wp_send_json_error( $status ); } $plugin_status = install_plugin_install_status( $api ); @@ -2954,17 +2958,16 @@ function wp_ajax_update_plugin() { $upgrader = new Plugin_Upgrader( new Automatic_Upgrader_Skin() ); $result = $upgrader->bulk_upgrade( array( $plugin ) ); - if ( is_array( $result ) ) { - $result = $result[ $plugin ]; - } - - if ( is_wp_error( $result ) ) { + wp_send_json_success( $status ); + } else if ( is_wp_error( $result ) ) { $status['error'] = $result->get_error_message(); wp_send_json_error( $status ); + } else if ( is_bool( $result ) && ! $result ) { + $status['errorCode'] = __( 'unable_to_connect_to_filesystem' ); + $status['error'] = __( 'Unable to connect to the filesystem. Please confirm your credentials.' ); + wp_send_json_error( $status ); } - - wp_send_json_success( $status ); } /** diff --git a/src/wp-admin/js/updates.js b/src/wp-admin/js/updates.js index 109a3dccdd..47c4a960d2 100644 --- a/src/wp-admin/js/updates.js +++ b/src/wp-admin/js/updates.js @@ -21,6 +21,35 @@ window.wp = window.wp || {}; */ wp.updates.l10n = window._wpUpdatesSettings.l10n; + /** + * Whether filesystem credentials need to be requested from the user. + * + * @since 4.2.0 + * + * @var bool + */ + wp.updates.shouldRequestFilesystemCredentials = window._wpUpdatesSettings.requestFilesystemCredentials; + + /** + * Filesystem credentials to be packaged along with the request. + * + * @since 4.2.0 + * + * @var object + */ + wp.updates.filesystemCredentials = { + ftp: { + host: null, + username: null, + password: null, + connectionType: null + }, + ssh: { + publicKey: null, + privateKey: null + } + }; + /** * Flag if we're waiting for an install/update to complete. * @@ -30,6 +59,15 @@ window.wp = window.wp || {}; */ wp.updates.updateLock = false; + /** + * Flag if we've done an install or update successfully. + * + * @since 4.2.0 + * + * @var bool + */ + wp.updates.updateDoneSuccessfully = false; + /** * If the user tries to install/update a plugin while an install/update is * already happening, it can be placed in this queue to perform later. @@ -124,8 +162,14 @@ window.wp = window.wp || {}; var data = { '_ajax_nonce': wp.updates.ajaxNonce, - 'plugin': plugin, - 'slug': slug + 'plugin': plugin, + 'slug': slug, + username: wp.updates.filesystemCredentials.ftp.username, + password: wp.updates.filesystemCredentials.ftp.password, + hostname: wp.updates.filesystemCredentials.ftp.hostname, + connection_type: wp.updates.filesystemCredentials.ftp.connectionType, + public_key: wp.updates.filesystemCredentials.ssh.publicKey, + private_key: wp.updates.filesystemCredentials.ssh.privateKey }; wp.ajax.post( 'update-plugin', data ) @@ -157,6 +201,8 @@ window.wp = window.wp || {}; wp.a11y.speak( wp.updates.l10n.updatedMsg ); wp.updates.decrementCount( 'plugin' ); + + wp.updates.updateDoneSuccessfully = true; }; /** @@ -168,6 +214,11 @@ window.wp = window.wp || {}; */ wp.updates.updateError = function( response ) { var $message; + wp.updates.updateDoneSuccessfully = false; + if ( response.errorCode && response.errorCode == 'unable_to_connect_to_filesystem' ) { + wp.updates.credentialError( response, 'update-plugin' ); + return; + } if ( 'plugins' === pagenow || 'plugins-network' === pagenow ) { $message = $( '#' + response.slug ).next().find( '.update-message' ); } else if ( 'plugin-install' === pagenow ) { @@ -178,6 +229,21 @@ window.wp = window.wp || {}; wp.a11y.speak( wp.updates.l10n.updateFailed ); }; + /** + * Show an + * + * @param {string} message + * @since 4.2.0 + */ + wp.updates.showErrorInCredentialsForm = function( message ) { + var $notificationDialog = $( '.notification-dialog' ); + + // Remove any existing error + $notificationDialog.find( '.error' ).remove(); + + $notificationDialog.find( 'h3' ).after( '
' + message + '
' ); + }; + /** * After an update attempt has completed, check the queue. * @@ -216,8 +282,14 @@ window.wp = window.wp || {}; wp.updates.updateLock = true; var data = { - '_ajax_nonce': wp.updates.ajaxNonce, - 'slug': slug + '_ajax_nonce': wp.updates.ajaxNonce, + 'slug': slug, + 'username': wp.updates.filesystemCredentials.ftp.username, + 'password': wp.updates.filesystemCredentials.ftp.password, + 'hostname': wp.updates.filesystemCredentials.ftp.hostname, + 'connection_type': wp.updates.filesystemCredentials.ftp.connectionType, + 'public_key': wp.updates.filesystemCredentials.ssh.publicKey, + 'private_key': wp.updates.filesystemCredentials.ssh.privateKey }; wp.ajax.post( 'install-plugin', data ) @@ -239,6 +311,7 @@ window.wp = window.wp || {}; $message.removeClass( 'updating-message' ).addClass( 'updated-message button-disabled' ); $message.text( wp.updates.l10n.installed ); wp.a11y.speak( wp.updates.l10n.installedMsg ); + wp.updates.updateDoneSuccessfully = true; }; /** @@ -250,11 +323,35 @@ window.wp = window.wp || {}; */ wp.updates.installError = function( response ) { var $message = $( '.plugin-card-' + response.slug ).find( '.install-now' ); + wp.updates.updateDoneSuccessfully = false; + if ( response.errorCode && response.errorCode == 'unable_to_connect_to_filesystem' ) { + wp.updates.credentialError( response, 'update-plugin' ); + return; + } $message.removeClass( 'updating-message' ); $message.text( wp.updates.l10n.installNow ); }; + /** + * Events that need to happen when there is a credential error + * + * @since 4.2.0 + */ + wp.updates.credentialError = function( response, type ) { + wp.updates.updateQueue.push( { + 'type': type, + 'data': { + // Not cool that we're depending on response for this data. + // This would feel more whole in a view all tied together. + plugin: response.plugin, + slug: response.slug + } + } ); + wp.updates.showErrorInCredentialsForm( response.error ); + wp.updates.requestFilesystemCredentials(); + }; + /** * If an install/update job has been placed in the queue, queueChecker pulls it out and runs it. @@ -281,10 +378,45 @@ window.wp = window.wp || {}; break; } }; + /** + * Request the users filesystem credentials if we don't have them already + * + * @since 4.2.0 + */ + wp.updates.requestFilesystemCredentials = function() { + if ( wp.updates.updateDoneSuccessfully === false ) { + wp.updates.updateLock = true; + $('#request-filesystem-credentials-dialog').show(); + } + }; + // Bind various click handlers. $( document ).ready( function() { + // File system credentials form submit noop-er / handler. + $('#request-filesystem-credentials-dialog form').on( 'submit', function() { + // 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(); + + $('#request-filesystem-credentials-dialog').hide(); + + // Unlock and invoke the queue. + wp.updates.updateLock = false; + wp.updates.queueChecker(); + + return false; + }); + + // Click handler for plugin updates in List Table view. $( '.plugin-update-tr .update-link' ).on( 'click', function( e ) { e.preventDefault(); + if ( wp.updates.shouldRequestFilesystemCredentials === '1' && ! wp.updates.updateLock ) { + wp.updates.requestFilesystemCredentials(); + } var $row = $( e.target ).parents( '.plugin-update-tr' ); wp.updates.updatePlugin( $row.data( 'plugin' ), $row.data( 'slug' ) ); } ); @@ -315,6 +447,10 @@ window.wp = window.wp || {}; $( '.plugin-card .install-now' ).on( 'click', function( e ) { e.preventDefault(); + if ( wp.updates.shouldRequestFilesystemCredentials === '1' && ! wp.updates.updateLock ) { + wp.updates.requestFilesystemCredentials(); + } + var $button = $( e.target ); if ( $button.hasClass( 'button-disabled' ) ) { return; diff --git a/src/wp-admin/plugin-install.php b/src/wp-admin/plugin-install.php index 03085c7813..3c20127109 100644 --- a/src/wp-admin/plugin-install.php +++ b/src/wp-admin/plugin-install.php @@ -128,6 +128,16 @@ if ( $tab !== 'upload' ) { */ do_action( "install_plugins_$tab", $paged ); ?> + +
+
+
+
+ +
+
+
+ +
+
+
+
+ +
+
+
add( 'updates', "/wp-admin/js/updates$suffix.js", array( 'jquery', 'wp-util', 'wp-a11y' ) ); + + /* + * Determine whether the user will need to enter filesystem credentials + * on the front-end. + */ + require_once(ABSPATH . 'wp-admin/includes/file.php'); + $filesystem_method = get_filesystem_method(); + ob_start(); + $filesystem_credentials_are_stored = request_filesystem_credentials( self_admin_url() ); + ob_end_clean(); + $request_filesystem_credentials = ( $filesystem_method != 'direct' && ! $filesystem_credentials_are_stored ) ? 1 : 0; + did_action( 'init' ) && $scripts->localize( 'updates', '_wpUpdatesSettings', array( 'ajax_nonce' => wp_create_nonce( 'updates' ), + 'requestFilesystemCredentials' => $request_filesystem_credentials, 'l10n' => array( 'updating' => __( 'Updating...' ), 'updated' => __( 'Updated!' ),