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();