From b857ff60834a5dd89b66f0532edd3d70ca21edde Mon Sep 17 00:00:00 2001 From: Aaron Jorbin Date: Thu, 12 Mar 2015 08:52:42 +0000 Subject: [PATCH] Request FTP and SSH credentials when needed during shiny updates This is a first pass at requesting FTP and SSH credentials when needed during shiny updates. Styling and some UX improvements are still needed, but we do show the prompt and use the passed data when doing plugin installs and updates for shiny updates. There are also a couple of areas that we could improve code wise such how we create the requestFilesystemCredentials part of the localized _wpUpdatesSettings. Over the past half century, we've split the atom, we've spliced the gene and we've roamed Tranquility Base. We've reached for the stars and never have we been closer to having them in our grasp. That has nothing to do with shiny updates. Props ericlewis, jorbin, and drewapicture for testing Fixes #31528 git-svn-id: https://develop.svn.wordpress.org/trunk@31749 602fd350-edb4-49c9-b593-d223f7449a82 --- src/wp-admin/css/forms.css | 20 ++++ src/wp-admin/includes/ajax-actions.php | 17 +-- src/wp-admin/js/updates.js | 144 ++++++++++++++++++++++++- src/wp-admin/plugin-install.php | 10 ++ src/wp-admin/plugins.php | 8 ++ src/wp-includes/script-loader.php | 13 +++ 6 files changed, 201 insertions(+), 11 deletions(-) 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!' ),