From 289c28637ccaa75b5711b82904fff05e2c89bd36 Mon Sep 17 00:00:00 2001 From: Andrew Ozz Date: Tue, 7 Jul 2020 17:47:37 +0000 Subject: [PATCH] Upgrade/install: Allow plugin and theme updates from a uploaded .zip file. Props mariovalney, cyberhobo, imath, shaunandrews, mariovalney, earnjam, desrosj, dd32, folletto, swissspidy, melchoyce, pento, joshuawold, psykro, clorith, ahortin, galbaras, pingram3541, joyously, doobeedoo, karmatosed, poena, whyisjake, earnjam, sergeybiryukov, audrasjb, azaozz. Fixes #9757. git-svn-id: https://develop.svn.wordpress.org/trunk@48390 602fd350-edb4-49c9-b593-d223f7449a82 --- src/wp-admin/css/themes.css | 56 ++++- .../includes/class-plugin-installer-skin.php | 205 +++++++++++++++- .../includes/class-plugin-upgrader.php | 58 ++++- .../includes/class-theme-installer-skin.php | 220 +++++++++++++++++- .../includes/class-theme-upgrader.php | 65 +++++- .../includes/class-wp-upgrader-skin.php | 12 + src/wp-admin/includes/class-wp-upgrader.php | 5 +- src/wp-admin/includes/plugin-install.php | 2 +- src/wp-admin/includes/theme-install.php | 2 +- src/wp-admin/update.php | 18 +- 10 files changed, 594 insertions(+), 49 deletions(-) diff --git a/src/wp-admin/css/themes.css b/src/wp-admin/css/themes.css index 127ce2ebca..6136155021 100644 --- a/src/wp-admin/css/themes.css +++ b/src/wp-admin/css/themes.css @@ -984,10 +984,15 @@ body.folded .theme-browser ~ .theme-overlay .theme-wrap { 16.2 - Install Themes ------------------------------------------------------------------------------*/ +.update-php .wrap { + max-width: 40rem; +} + /* Already installed theme */ .theme-browser .theme .theme-installed { background: #0073aa; } + .theme-browser .theme .notice-success p:before { color: #79ba49; content: "\f147"; @@ -1030,10 +1035,7 @@ body.folded .theme-browser ~ .theme-overlay .theme-wrap { overflow: hidden; position: relative; top: 10px; -} - -.upload-plugin-wrap { - display: none; + text-align: center; } .show-upload-view .upload-theme, @@ -1049,12 +1051,16 @@ body.folded .theme-browser ~ .theme-overlay .theme-wrap { border: 1px solid #ccd0d4; padding: 30px; margin: 30px auto; - max-width: 380px; - display: flex; + display: inline-flex; justify-content: space-between; align-items: center; } +.upload-theme .wp-upload-form input[type="file"], +.upload-plugin .wp-upload-form input[type="file"] { + margin-right: 10px; +} + .upload-theme .install-help, .upload-plugin .install-help { color: #555d66; /* #f1f1f1 background */ @@ -1093,7 +1099,6 @@ p.no-themes-local { .upload-theme .install-help { font-size: 15px; padding: 20px 0 0; - text-align: left; } } @@ -1116,6 +1121,43 @@ p.no-themes-local { line-height: 1.9; } +.update-from-upload-comparison { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + text-align: left; + margin: 1rem 0 1.4rem; + border-collapse: collapse; + width: 100%; +} + +.update-from-upload-comparison tr:last-child td { + height: 1.4rem; + vertical-align: top; +} + +.update-from-upload-comparison tr:first-child th { + font-weight: bold; + height: 1.4rem; + vertical-align: bottom; +} + +.update-from-upload-comparison td.name-label { + text-align: right; +} + +.update-from-upload-comparison td, +.update-from-upload-comparison th { + padding: 0.4rem 1.4rem; +} + +.update-from-upload-comparison td.warning { + color: #a00; +} + +.update-from-upload-actions { + margin-top: 1.4rem; +} + /*------------------------------------------------------------------------------ 16.3 - Custom Header Screen ------------------------------------------------------------------------------*/ diff --git a/src/wp-admin/includes/class-plugin-installer-skin.php b/src/wp-admin/includes/class-plugin-installer-skin.php index cb44919c66..9cc5646f0b 100644 --- a/src/wp-admin/includes/class-plugin-installer-skin.php +++ b/src/wp-admin/includes/class-plugin-installer-skin.php @@ -18,22 +18,29 @@ class Plugin_Installer_Skin extends WP_Upgrader_Skin { public $api; public $type; + public $url; + public $overwrite; + + private $is_downgrading = false; /** * @param array $args */ public function __construct( $args = array() ) { $defaults = array( - 'type' => 'web', - 'url' => '', - 'plugin' => '', - 'nonce' => '', - 'title' => '', + 'type' => 'web', + 'url' => '', + 'plugin' => '', + 'nonce' => '', + 'title' => '', + 'overwrite' => '', ); $args = wp_parse_args( $args, $defaults ); - $this->type = $args['type']; - $this->api = isset( $args['api'] ) ? $args['api'] : array(); + $this->type = $args['type']; + $this->url = $args['url']; + $this->api = isset( $args['api'] ) ? $args['api'] : array(); + $this->overwrite = $args['overwrite']; parent::__construct( $args ); } @@ -43,17 +50,41 @@ class Plugin_Installer_Skin extends WP_Upgrader_Skin { public function before() { if ( ! empty( $this->api ) ) { $this->upgrader->strings['process_success'] = sprintf( - /* translators: 1: Plugin name, 2: Plugin version. */ - __( 'Successfully installed the plugin %1$s %2$s.' ), + $this->upgrader->strings['process_success_specific'], $this->api->name, $this->api->version ); } } + /** + * Hides the `process_failed` error when updating a plugin by uploading a zip file. + * + * @since 5.5.0 + * + * @param $wp_error WP_Error. + * @return bool + */ + public function hide_process_failed( $wp_error ) { + if ( + 'upload' === $this->type && + '' === $this->overwrite && + $wp_error->get_error_code() === 'folder_exists' + ) { + return true; + } + + return false; + } + /** */ public function after() { + // Check if the plugin can be overwritten and output the HTML. + if ( $this->do_overwrite() ) { + return; + } + $plugin_file = $this->upgrader->plugin_info(); $install_actions = array(); @@ -117,7 +148,7 @@ class Plugin_Installer_Skin extends WP_Upgrader_Skin { if ( ! $this->result || is_wp_error( $this->result ) ) { unset( $install_actions['activate_plugin'], $install_actions['network_activate'] ); - } elseif ( ! current_user_can( 'activate_plugin', $plugin_file ) ) { + } elseif ( ! current_user_can( 'activate_plugin', $plugin_file ) || is_plugin_active( $plugin_file ) ) { unset( $install_actions['activate_plugin'] ); } @@ -138,4 +169,158 @@ class Plugin_Installer_Skin extends WP_Upgrader_Skin { $this->feedback( implode( ' ', (array) $install_actions ) ); } } + + /** + * Check if the plugin can be overwritten and output the HTML for overwriting a plugin on upload. + * + * @since 5.5.0 + * + * @return bool Whether the plugin can be overwritten and HTML was outputted. + */ + private function do_overwrite() { + if ( 'upload' !== $this->type || ! is_wp_error( $this->result ) || 'folder_exists' !== $this->result->get_error_code() ) { + return false; + } + + $folder = $this->result->get_error_data( 'folder_exists' ); + $folder = ltrim( substr( $folder, strlen( WP_PLUGIN_DIR ) ), '/' ); + + $current_plugin_data = false; + foreach ( get_plugins() as $plugin => $plugin_data ) { + if ( strrpos( $plugin, $folder ) !== 0 ) { + continue; + } + + $current_plugin_data = $plugin_data; + } + + if ( empty( $current_plugin_data ) || empty( $this->upgrader->new_plugin_data ) ) { + return false; + } + + echo '

' . esc_html( __( 'This plugin is already installed.' ) ) . '

'; + + $this->is_downgrading = version_compare( $current_plugin_data['Version'], $this->upgrader->new_plugin_data['Version'], '>' ); + + $rows = array( + 'Name' => __( 'Plugin name' ), + 'Version' => __( 'Version' ), + 'Author' => __( 'Author' ), + 'RequiresWP' => __( 'Required WordPress version' ), + 'RequiresPHP' => __( 'Required PHP version' ), + ); + + $table = ''; + $table .= ''; + $table .= ''; + + $is_same_plugin = true; // Let's consider only these rows + foreach ( $rows as $field => $label ) { + $old_value = ! empty( $current_plugin_data[ $field ] ) ? $current_plugin_data[ $field ] : '-'; + $new_value = ! empty( $this->upgrader->new_plugin_data[ $field ] ) ? $this->upgrader->new_plugin_data[ $field ] : '-'; + + $is_same_plugin = $is_same_plugin && ( $old_value === $new_value ); + + $diff_field = ( 'Version' !== $field && $new_value !== $old_value ); + $diff_version = ( 'Version' === $field && $this->is_downgrading ); + + $table .= ''; + $table .= ( $diff_field || $diff_version ) ? ''; + } + + $table .= '
' . esc_html( __( 'Current' ) ) . '' . esc_html( __( 'Uploaded' ) ) . '
' . $label . '' . esc_html( $old_value ) . '' : ''; + $table .= esc_html( $new_value ) . '
'; + + /** + * Filters the compare table output for overwrite a plugin package on upload. + * + * @since 5.5.0 + * + * @param string $table The output table with Name, Version, Author, RequiresWP and RequiresPHP info. + * @param array $current_plugin_data Array with current plugin data. + * @param array $new_plugin_data Array with uploaded plugin data. + */ + echo apply_filters( 'install_plugin_ovewrite_comparison', $table, $current_plugin_data, $this->upgrader->new_plugin_data ); + + $install_actions = array(); + $can_update = true; + + $blocked_message = '

' . esc_html( __( 'The plugin cannot be updated due to the following:' ) ) . '

'; + $blocked_message .= ''; + + if ( $can_update ) { + if ( $this->is_downgrading ) { + $warning = __( 'You are uploading an older version of a current plugin. You can continue to install the older version, but be sure to backup your database and files first.' ); + } else { + $warning = __( 'You are updating a plugin. Be sure to backup your database and files first.' ); + } + + echo '

' . $warning . '

'; + + $overwrite = $this->is_downgrading ? 'downgrade-plugin' : 'update-plugin'; + + $install_actions['ovewrite_plugin'] = sprintf( + '%s', + wp_nonce_url( add_query_arg( 'overwrite', $overwrite, $this->url ), 'plugin-upload' ), + esc_html( __( 'Replace current with uploaded' ) ) + ); + } else { + echo $blocked_message; + } + + $install_actions['plugins_page'] = sprintf( + '%s', + self_admin_url( 'plugin-install.php' ), + __( 'Cancel and go back' ) + ); + + /** + * Filters the list of action links available following a single plugin installation failed but ovewrite is allowed. + * + * @since 5.5.0 + * + * @param string[] $install_actions Array of plugin action links. + * @param object $api Object containing WordPress.org API plugin data. + * @param array $new_plugin_data Array with uploaded plugin data. + */ + $install_actions = apply_filters( 'install_plugin_ovewrite_actions', $install_actions, $this->api, $this->upgrader->new_plugin_data ); + + if ( ! empty( $install_actions ) ) { + echo '

' . implode( ' ', (array) $install_actions ) . '

'; + } + + return true; + } } diff --git a/src/wp-admin/includes/class-plugin-upgrader.php b/src/wp-admin/includes/class-plugin-upgrader.php index 361b7a5c8d..56417a58e0 100644 --- a/src/wp-admin/includes/class-plugin-upgrader.php +++ b/src/wp-admin/includes/class-plugin-upgrader.php @@ -38,6 +38,16 @@ class Plugin_Upgrader extends WP_Upgrader { */ public $bulk = false; + /** + * New plugin info. + * + * @since 5.5.0 + * @var array $new_plugin_data + * + * @see check_package() + */ + public $new_plugin_data = array(); + /** * Initialize the upgrade strings. * @@ -54,6 +64,9 @@ class Plugin_Upgrader extends WP_Upgrader { $this->strings['process_failed'] = __( 'Plugin update failed.' ); $this->strings['process_success'] = __( 'Plugin updated successfully.' ); $this->strings['process_bulk_success'] = __( 'Plugins updated successfully.' ); + + /* translators: 1: Plugin name, 2: Plugin version. */ + $this->strings['process_success_specific'] = __( 'Successfully installed the plugin %1$s %2$s.' ); } /** @@ -67,9 +80,25 @@ class Plugin_Upgrader extends WP_Upgrader { $this->strings['downloading_package'] = sprintf( __( 'Downloading installation package from %s…' ), '%s' ); $this->strings['unpack_package'] = __( 'Unpacking the package…' ); $this->strings['installing_package'] = __( 'Installing the plugin…' ); + $this->strings['remove_old'] = __( 'Removing the current plugin…' ); + $this->strings['remove_old_failed'] = __( 'Could not remove the current plugin.' ); $this->strings['no_files'] = __( 'The plugin contains no files.' ); $this->strings['process_failed'] = __( 'Plugin installation failed.' ); $this->strings['process_success'] = __( 'Plugin installed successfully.' ); + + if ( ! empty( $this->skin->overwrite ) ) { + if ( 'update-plugin' === $this->skin->overwrite ) { + $this->strings['installing_package'] = __( 'Updating the plugin…' ); + $this->strings['process_failed'] = __( 'Plugin update failed.' ); + $this->strings['process_success'] = __( 'Plugin updated successfully.' ); + } + + if ( 'downgrade-plugin' === $this->skin->overwrite ) { + $this->strings['installing_package'] = __( 'Downgrading the plugin…' ); + $this->strings['process_failed'] = __( 'Plugin downgrade failed.' ); + $this->strings['process_success'] = __( 'Plugin downgraded successfully.' ); + } + } } /** @@ -88,9 +117,9 @@ class Plugin_Upgrader extends WP_Upgrader { * @return bool|WP_Error True if the installation was successful, false or a WP_Error otherwise. */ public function install( $package, $args = array() ) { - $defaults = array( 'clear_update_cache' => true, + 'overwrite_package' => false, // Do not overwrite files. ); $parsed_args = wp_parse_args( $args, $defaults ); @@ -107,7 +136,7 @@ class Plugin_Upgrader extends WP_Upgrader { array( 'package' => $package, 'destination' => WP_PLUGIN_DIR, - 'clear_destination' => false, // Do not overwrite files. + 'clear_destination' => $parsed_args['overwrite_package'], 'clear_working' => true, 'hook_extra' => array( 'type' => 'plugin', @@ -126,6 +155,20 @@ class Plugin_Upgrader extends WP_Upgrader { // Force refresh of plugin update information. wp_clean_plugins_cache( $parsed_args['clear_update_cache'] ); + if ( $parsed_args['overwrite_package'] ) { + /** + * Fires when the upgrader has successfully overwritten a currently installed + * plugin or theme with an uploaded zip package. + * + * @since 5.5.0 + * + * @param string $package The package file. + * @param array $new_plugin_data The new plugin data. + * @param string $package_type The package type (plugin or theme). + */ + do_action( 'upgrader_overwrote_package', $package, $this->new_plugin_data, 'plugin' ); + } + return true; } @@ -145,7 +188,6 @@ class Plugin_Upgrader extends WP_Upgrader { * @return bool|WP_Error True if the upgrade was successful, false or a WP_Error object otherwise. */ public function upgrade( $plugin, $args = array() ) { - $defaults = array( 'clear_update_cache' => true, ); @@ -223,7 +265,6 @@ class Plugin_Upgrader extends WP_Upgrader { * @return array|false An array of results indexed by plugin file, or false if unable to connect to the filesystem. */ public function bulk_upgrade( $plugins, $args = array() ) { - $defaults = array( 'clear_update_cache' => true, ); @@ -349,6 +390,8 @@ class Plugin_Upgrader extends WP_Upgrader { public function check_package( $source ) { global $wp_filesystem; + $this->new_plugin_data = array(); + if ( is_wp_error( $source ) ) { return $source; } @@ -359,19 +402,18 @@ class Plugin_Upgrader extends WP_Upgrader { } // Check that the folder contains at least 1 valid plugin. - $plugins_found = false; - $files = glob( $working_directory . '*.php' ); + $files = glob( $working_directory . '*.php' ); if ( $files ) { foreach ( $files as $file ) { $info = get_plugin_data( $file, false, false ); if ( ! empty( $info['Name'] ) ) { - $plugins_found = true; + $this->new_plugin_data = $info; break; } } } - if ( ! $plugins_found ) { + if ( empty( $this->new_plugin_data ) ) { return new WP_Error( 'incompatible_archive_no_plugins', $this->strings['incompatible_archive'], __( 'No valid plugins were found.' ) ); } diff --git a/src/wp-admin/includes/class-theme-installer-skin.php b/src/wp-admin/includes/class-theme-installer-skin.php index f568766086..89835f287f 100644 --- a/src/wp-admin/includes/class-theme-installer-skin.php +++ b/src/wp-admin/includes/class-theme-installer-skin.php @@ -18,22 +18,29 @@ class Theme_Installer_Skin extends WP_Upgrader_Skin { public $api; public $type; + public $url; + public $overwrite; + + private $is_downgrading = false; /** * @param array $args */ public function __construct( $args = array() ) { $defaults = array( - 'type' => 'web', - 'url' => '', - 'theme' => '', - 'nonce' => '', - 'title' => '', + 'type' => 'web', + 'url' => '', + 'theme' => '', + 'nonce' => '', + 'title' => '', + 'overwrite' => '', ); $args = wp_parse_args( $args, $defaults ); - $this->type = $args['type']; - $this->api = isset( $args['api'] ) ? $args['api'] : array(); + $this->type = $args['type']; + $this->url = $args['url']; + $this->api = isset( $args['api'] ) ? $args['api'] : array(); + $this->overwrite = $args['overwrite']; parent::__construct( $args ); } @@ -50,9 +57,33 @@ class Theme_Installer_Skin extends WP_Upgrader_Skin { } } + /** + * Hides the `process_failed` error when updating a theme by uploading a zip file. + * + * @since 5.5.0 + * + * @param $wp_error WP_Error. + * @return bool + */ + public function hide_process_failed( $wp_error ) { + if ( + 'upload' === $this->type && + '' === $this->overwrite && + $wp_error->get_error_code() === 'folder_exists' + ) { + return true; + } + + return false; + } + /** */ public function after() { + if ( $this->do_overwrite() ) { + return; + } + if ( empty( $this->upgrader->result['destination_name'] ) ) { return; } @@ -130,6 +161,8 @@ class Theme_Installer_Skin extends WP_Upgrader_Skin { if ( ! $this->result || is_wp_error( $this->result ) || is_network_admin() || ! current_user_can( 'switch_themes' ) ) { unset( $install_actions['activate'], $install_actions['preview'] ); + } elseif ( get_option( 'template' ) === $stylesheet ) { + unset( $install_actions['activate'] ); } /** @@ -147,4 +180,177 @@ class Theme_Installer_Skin extends WP_Upgrader_Skin { $this->feedback( implode( ' | ', (array) $install_actions ) ); } } + + /** + * Check if the theme can be overwritten and output the HTML for overwriting a theme on upload. + * + * @since 5.5.0 + * + * @return bool Whether the theme can be overwritten and HTML was outputted. + */ + private function do_overwrite() { + if ( 'upload' !== $this->type || ! is_wp_error( $this->result ) || 'folder_exists' !== $this->result->get_error_code() ) { + return false; + } + + $folder = $this->result->get_error_data( 'folder_exists' ); + $folder = rtrim( $folder, '/' ); + + $current_theme_data = false; + $all_themes = wp_get_themes( array( 'errors' => null ) ); + + foreach ( $all_themes as $theme ) { + if ( rtrim( $theme->get_stylesheet_directory(), '/' ) !== $folder ) { + continue; + } + + $current_theme_data = $theme; + } + + if ( empty( $current_theme_data ) || empty( $this->upgrader->new_theme_data ) ) { + return false; + } + + echo '

' . esc_html( __( 'This theme is already installed.' ) ) . '

'; + + // Check errors for current theme + if ( is_wp_error( $current_theme_data->errors() ) ) { + $this->feedback( 'current_theme_has_errors', $current_theme_data->errors()->get_error_message() ); + } + + $this->is_downgrading = version_compare( $current_theme_data['Version'], $this->upgrader->new_theme_data['Version'], '>' ); + + $is_invalid_parent = false; + if ( ! empty( $this->upgrader->new_theme_data['Template'] ) ) { + $is_invalid_parent = ! in_array( $this->upgrader->new_theme_data['Template'], array_keys( $all_themes ), true ); + } + + $rows = array( + 'Name' => __( 'Theme name' ), + 'Version' => __( 'Version' ), + 'Author' => __( 'Author' ), + 'RequiresWP' => __( 'Required WordPress version' ), + 'RequiresPHP' => __( 'Required PHP version' ), + 'Template' => __( 'Parent theme' ), + ); + + $table = ''; + $table .= ''; + + $is_same_theme = true; // Let's consider only these rows + foreach ( $rows as $field => $label ) { + $old_value = $current_theme_data->display( $field, false ); + $old_value = $old_value ? $old_value : '-'; + + $new_value = ! empty( $this->upgrader->new_theme_data[ $field ] ) ? $this->upgrader->new_theme_data[ $field ] : '-'; + + if ( $old_value === $new_value && '-' === $new_value && 'Template' === $field ) { + continue; + } + + $is_same_theme = $is_same_theme && ( $old_value === $new_value ); + + $diff_field = ( 'Version' !== $field && $new_value !== $old_value ); + $diff_version = ( 'Version' === $field && $this->is_downgrading ); + $invalid_parent = false; + + if ( 'Template' === $field && $is_invalid_parent ) { + $invalid_parent = true; + $new_value .= ' ' . __( '(not found)' ); + } + + $table .= ''; + $table .= ( $diff_field || $diff_version || $invalid_parent ) ? ''; + } + + $table .= '
' . esc_html( __( 'Current' ) ) . '' . esc_html( __( 'Uploaded' ) ) . '
' . $label . '' . esc_html( $old_value ) . '' : ''; + $table .= esc_html( $new_value ) . '
'; + + /** + * Filters the compare table output for overwrite a theme package on upload. + * + * @since 5.5.0 + * + * @param string $table The output table with Name, Version, Author, RequiresWP and RequiresPHP info. + * @param array $current_theme_data Array with current theme data. + * @param array $new_theme_data Array with uploaded theme data. + */ + echo apply_filters( 'install_theme_overwrite_comparison', $table, $current_theme_data, $this->upgrader->new_theme_data ); + + $install_actions = array(); + $can_update = true; + + $blocked_message = '

' . esc_html( __( 'The theme cannot be updated due to the following:' ) ) . '

'; + $blocked_message .= ''; + + if ( $can_update ) { + if ( $this->is_downgrading ) { + $warning = __( 'You are uploading an older version of a current theme. You can continue to install the older version, but be sure to backup your database and files first.' ); + } else { + $warning = __( 'You are updating a theme. Be sure to backup your database and files first.' ); + } + + echo '

' . $warning . '

'; + + $overwrite = $this->is_downgrading ? 'downgrade-theme' : 'update-theme'; + + $install_actions['ovewrite_theme'] = sprintf( + '%s', + wp_nonce_url( add_query_arg( 'overwrite', $overwrite, $this->url ), 'theme-upload' ), + esc_html( __( 'Replace current with uploaded' ) ) + ); + } else { + echo $blocked_message; + } + + $install_actions['themes_page'] = sprintf( + '%s', + self_admin_url( 'theme-install.php' ), + __( 'Cancel and go back' ) + ); + + /** + * Filters the list of action links available following a single theme installation failed but ovewrite is allowed. + * + * @since 5.5.0 + * + * @param string[] $install_actions Array of theme action links. + * @param object $api Object containing WordPress.org API theme data. + * @param array $new_theme_data Array with uploaded theme data. + */ + $install_actions = apply_filters( 'install_theme_ovewrite_actions', $install_actions, $this->api, $this->upgrader->new_theme_data ); + + if ( ! empty( $install_actions ) ) { + echo '

' . implode( ' ', (array) $install_actions ) . '

'; + } + + return true; + } + } diff --git a/src/wp-admin/includes/class-theme-upgrader.php b/src/wp-admin/includes/class-theme-upgrader.php index d5da2344c7..b26498b3e9 100644 --- a/src/wp-admin/includes/class-theme-upgrader.php +++ b/src/wp-admin/includes/class-theme-upgrader.php @@ -37,6 +37,16 @@ class Theme_Upgrader extends WP_Upgrader { */ public $bulk = false; + /** + * New theme info. + * + * @since 5.5.0 + * @var array $new_theme_data + * + * @see check_package() + */ + public $new_theme_data = array(); + /** * Initialize the upgrade strings. * @@ -65,6 +75,8 @@ class Theme_Upgrader extends WP_Upgrader { $this->strings['downloading_package'] = sprintf( __( 'Downloading installation package from %s…' ), '%s' ); $this->strings['unpack_package'] = __( 'Unpacking the package…' ); $this->strings['installing_package'] = __( 'Installing the theme…' ); + $this->strings['remove_old'] = __( 'Removing the old version of the theme…' ); + $this->strings['remove_old_failed'] = __( 'Could not remove the old theme.' ); $this->strings['no_files'] = __( 'The theme contains no files.' ); $this->strings['process_failed'] = __( 'Theme installation failed.' ); $this->strings['process_success'] = __( 'Theme installed successfully.' ); @@ -79,6 +91,20 @@ class Theme_Upgrader extends WP_Upgrader { $this->strings['parent_theme_install_success'] = __( 'Successfully installed the parent theme, %1$s %2$s.' ); /* translators: %s: Theme name. */ $this->strings['parent_theme_not_found'] = sprintf( __( 'The parent theme could not be found. You will need to install the parent theme, %s, before you can use this child theme.' ), '%s' ); + /* translators: %s: Theme error. */ + $this->strings['current_theme_has_errors'] = __( 'The current theme has the following error: "%s".' ); + + if ( 'update-theme' === $this->skin->overwrite ) { + $this->strings['installing_package'] = __( 'Updating the theme…' ); + $this->strings['process_failed'] = __( 'Theme update failed.' ); + $this->strings['process_success'] = __( 'Theme updated successfully.' ); + } + + if ( 'downgrade-theme' === $this->skin->overwrite ) { + $this->strings['installing_package'] = __( 'Downgrading the theme…' ); + $this->strings['process_failed'] = __( 'Theme downgrade failed.' ); + $this->strings['process_success'] = __( 'Theme downgraded successfully.' ); + } } /** @@ -200,9 +226,9 @@ class Theme_Upgrader extends WP_Upgrader { * @return bool|WP_Error True if the installation was successful, false or a WP_Error object otherwise. */ public function install( $package, $args = array() ) { - $defaults = array( 'clear_update_cache' => true, + 'overwrite_package' => false, // Do not overwrite files. ); $parsed_args = wp_parse_args( $args, $defaults ); @@ -220,7 +246,7 @@ class Theme_Upgrader extends WP_Upgrader { array( 'package' => $package, 'destination' => get_theme_root(), - 'clear_destination' => false, // Do not overwrite files. + 'clear_destination' => $args['overwrite_package'], 'clear_working' => true, 'hook_extra' => array( 'type' => 'theme', @@ -240,6 +266,20 @@ class Theme_Upgrader extends WP_Upgrader { // Refresh the Theme Update information. wp_clean_themes_cache( $parsed_args['clear_update_cache'] ); + if ( $parsed_args['overwrite_package'] ) { + /** + * Fires when the upgrader has successfully overwritten a currently installed + * plugin or theme with an uploaded zip package. + * + * @since 5.5.0 + * + * @param string $package The package file. + * @param array $new_plugin_data The new theme data. + * @param string $package_type The package type (plugin or theme). + */ + do_action( 'upgrader_overwrote_package', $package, $this->new_theme_data, 'theme' ); + } + return true; } @@ -259,7 +299,6 @@ class Theme_Upgrader extends WP_Upgrader { * @return bool|WP_Error True if the upgrade was successful, false or a WP_Error object otherwise. */ public function upgrade( $theme, $args = array() ) { - $defaults = array( 'clear_update_cache' => true, ); @@ -332,7 +371,6 @@ class Theme_Upgrader extends WP_Upgrader { * @return array[]|false An array of results, or false if unable to connect to the filesystem. */ public function bulk_upgrade( $themes, $args = array() ) { - $defaults = array( 'clear_update_cache' => true, ); @@ -461,6 +499,8 @@ class Theme_Upgrader extends WP_Upgrader { public function check_package( $source ) { global $wp_filesystem; + $this->new_theme_data = array(); + if ( is_wp_error( $source ) ) { return $source; } @@ -484,11 +524,16 @@ class Theme_Upgrader extends WP_Upgrader { ); } + // All these headers are needed on Theme_Installer_Skin::do_overwrite(). $info = get_file_data( $working_directory . 'style.css', array( - 'Name' => 'Theme Name', - 'Template' => 'Template', + 'Name' => 'Theme Name', + 'Version' => 'Version', + 'Author' => 'Author', + 'Template' => 'Template', + 'RequiresWP' => 'Requires at least', + 'RequiresPHP' => 'Requires PHP', ) ); @@ -517,6 +562,7 @@ class Theme_Upgrader extends WP_Upgrader { ); } + $this->new_theme_data = $info; return $source; } @@ -640,7 +686,6 @@ class Theme_Upgrader extends WP_Upgrader { * and the last result isn't set. */ public function theme_info( $theme = null ) { - if ( empty( $theme ) ) { if ( ! empty( $this->result['destination_name'] ) ) { $theme = $this->result['destination_name']; @@ -648,7 +693,11 @@ class Theme_Upgrader extends WP_Upgrader { return false; } } - return wp_get_theme( $theme ); + + $theme = wp_get_theme( $theme ); + $theme->cache_delete(); + + return $theme; } } diff --git a/src/wp-admin/includes/class-wp-upgrader-skin.php b/src/wp-admin/includes/class-wp-upgrader-skin.php index e776076d64..26a77ec39a 100644 --- a/src/wp-admin/includes/class-wp-upgrader-skin.php +++ b/src/wp-admin/includes/class-wp-upgrader-skin.php @@ -205,4 +205,16 @@ class WP_Upgrader_Skin { /** */ public function bulk_footer() {} + + /** + * Hides the `process_failed` error message when updating by uploading a zip file. + * + * @since 5.5.0 + * + * @param $wp_error WP_Error + * @return bool + */ + public function hide_process_failed( $wp_error ) { + return false; + } } diff --git a/src/wp-admin/includes/class-wp-upgrader.php b/src/wp-admin/includes/class-wp-upgrader.php index f29eb6cfcd..1a63096892 100644 --- a/src/wp-admin/includes/class-wp-upgrader.php +++ b/src/wp-admin/includes/class-wp-upgrader.php @@ -798,7 +798,10 @@ class WP_Upgrader { $this->skin->set_result( $result ); if ( is_wp_error( $result ) ) { $this->skin->error( $result ); - $this->skin->feedback( 'process_failed' ); + + if ( ! method_exists( $this->skin, 'hide_process_failed' ) || ! $this->skin->hide_process_failed( $result ) ) { + $this->skin->feedback( 'process_failed' ); + } } else { // Installation succeeded. $this->skin->feedback( 'process_success' ); diff --git a/src/wp-admin/includes/plugin-install.php b/src/wp-admin/includes/plugin-install.php index c8f3406f7b..dac564bfa2 100644 --- a/src/wp-admin/includes/plugin-install.php +++ b/src/wp-admin/includes/plugin-install.php @@ -353,7 +353,7 @@ function install_plugins_upload() {
- +
diff --git a/src/wp-admin/includes/theme-install.php b/src/wp-admin/includes/theme-install.php index 267ae26a94..bbc132ff4c 100644 --- a/src/wp-admin/includes/theme-install.php +++ b/src/wp-admin/includes/theme-install.php @@ -183,7 +183,7 @@ function install_themes_upload() {
- +
filename ) ) ); + $title = sprintf( __( 'Installing plugin from uploaded file: %s' ), esc_html( basename( $file_upload->filename ) ) ); $nonce = 'plugin-upload'; $url = add_query_arg( array( 'package' => $file_upload->id ), 'update.php?action=upload-plugin' ); $type = 'upload'; // Install plugin type, From Web or an Upload. - $upgrader = new Plugin_Upgrader( new Plugin_Installer_Skin( compact( 'type', 'title', 'nonce', 'url' ) ) ); - $result = $upgrader->install( $file_upload->package ); + $overwrite = isset( $_GET['overwrite'] ) ? sanitize_text_field( $_GET['overwrite'] ) : ''; + $overwrite = in_array( $overwrite, array( 'update-plugin', 'downgrade-plugin' ), true ) ? $overwrite : ''; + + $upgrader = new Plugin_Upgrader( new Plugin_Installer_Skin( compact( 'type', 'title', 'nonce', 'url', 'overwrite' ) ) ); + $result = $upgrader->install( $file_upload->package, array( 'overwrite_package' => $overwrite ) ); if ( $result || is_wp_error( $result ) ) { $file_upload->cleanup(); @@ -277,13 +280,16 @@ if ( isset( $_GET['action'] ) ) { require_once ABSPATH . 'wp-admin/admin-header.php'; /* translators: %s: File name. */ - $title = sprintf( __( 'Installing Theme from uploaded file: %s' ), esc_html( basename( $file_upload->filename ) ) ); + $title = sprintf( __( 'Installing theme from uploaded file: %s' ), esc_html( basename( $file_upload->filename ) ) ); $nonce = 'theme-upload'; $url = add_query_arg( array( 'package' => $file_upload->id ), 'update.php?action=upload-theme' ); $type = 'upload'; // Install theme type, From Web or an Upload. - $upgrader = new Theme_Upgrader( new Theme_Installer_Skin( compact( 'type', 'title', 'nonce', 'url' ) ) ); - $result = $upgrader->install( $file_upload->package ); + $overwrite = isset( $_GET['overwrite'] ) ? sanitize_text_field( $_GET['overwrite'] ) : ''; + $overwrite = in_array( $overwrite, array( 'update-theme', 'downgrade-theme' ), true ) ? $overwrite : ''; + + $upgrader = new Theme_Upgrader( new Theme_Installer_Skin( compact( 'type', 'title', 'nonce', 'url', 'overwrite' ) ) ); + $result = $upgrader->install( $file_upload->package, array( 'overwrite_package' => $overwrite ) ); if ( $result || is_wp_error( $result ) ) { $file_upload->cleanup();