Index: src/wp-admin/css/themes.css
===================================================================
--- src/wp-admin/css/themes.css	(revision 48358)
+++ src/wp-admin/css/themes.css	(working copy)
@@ -984,10 +984,15 @@
   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,12 +1035,9 @@
 	overflow: hidden;
 	position: relative;
 	top: 10px;
+	text-align: center;
 }
 
-.upload-plugin-wrap {
-	display: none;
-}
-
 .show-upload-view .upload-theme,
 .show-upload-view .upload-plugin,
 .show-upload-view .upload-plugin-wrap,
@@ -1049,12 +1051,16 @@
 	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 @@
 	.upload-theme .install-help {
 		font-size: 15px;
 		padding: 20px 0 0;
-		text-align: left;
 	}
 }
 
@@ -1116,6 +1121,43 @@
 	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
 ------------------------------------------------------------------------------*/
Index: src/wp-admin/includes/class-plugin-installer-skin.php
===================================================================
--- src/wp-admin/includes/class-plugin-installer-skin.php	(revision 48358)
+++ src/wp-admin/includes/class-plugin-installer-skin.php	(working copy)
@@ -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,8 +50,7 @@
 	public function before() {
 		if ( ! empty( $this->api ) ) {
 			$this->upgrader->strings['process_success'] = sprintf(
-				/* translators: 1: Plugin name, 2: Plugin version. */
-				__( 'Successfully installed the plugin <strong>%1$s %2$s</strong>.' ),
+				$this->upgrader->strings['process_success_specific'],
 				$this->api->name,
 				$this->api->version
 			);
@@ -52,8 +58,33 @@
 	}
 
 	/**
+	 * 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 @@
 
 		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 @@
 			$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 '<h2 class="update-from-upload-heading">' . esc_html( __( 'This plugin is already installed.' ) ) . '</h2>';
+
+		$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 class="update-from-upload-comparison"><tbody>';
+		$table .= '<tr><th></th><th>' . esc_html( __( 'Current' ) ) . '</th>';
+		$table .= '<th>' . esc_html( __( 'Uploaded' ) ) . '</th></tr>';
+
+		$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 .= '<tr><td class="name-label">'. $label . '</td><td>' . esc_html( $old_value ) . '</td>';
+			$table .= ( $diff_field || $diff_version ) ? '<td class="warning">' : '<td>';
+			$table .= esc_html( $new_value ) . '</td></tr>';
+		}
+
+		$table .= '</tbody></table>';
+
+		/**
+		 * 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  = '<p>' . esc_html( __( 'The plugin cannot be updated due to the following:' ) ) . '</p>';
+		$blocked_message .= '<ul class="ul-disc">';
+
+		if (
+			! empty( $this->upgrader->new_plugin_data['RequiresPHP'] ) &&
+			version_compare( phpversion(), $this->upgrader->new_plugin_data['RequiresPHP'], '<' )
+		) {
+			$error = sprintf(
+				/* translators: 1: Current PHP version, 2: Version required by the uploaded plugin. */
+				__( 'The PHP version on your server is %1$s, however the uploaded plugin requires %2$s.' ),
+				phpversion(),
+				$this->upgrader->new_plugin_data['RequiresPHP']
+			);
+
+			$blocked_message .= '<li>' . esc_html( $error ) . '</li>';
+			$can_update = false;
+		}
+
+		if (
+			! empty( $this->upgrader->new_plugin_data['RequiresWP'] ) &&
+			version_compare( $GLOBALS['wp_version'], $this->upgrader->new_plugin_data['RequiresWP'], '<' )
+		) {
+			$error = sprintf(
+				/* translators: 1: Current WordPress version, 2: Version required by the uploaded plugin. */
+				__( 'Your WordPress version is %1$s, however the uploaded plugin requires %2$s.' ),
+				$GLOBALS['wp_version'],
+				$this->upgrader->new_plugin_data['RequiresWP']
+			);
+
+			$blocked_message .= '<li>' . esc_html( $error ) . '</li>';
+			$can_update = false;
+		}
+
+		$blocked_message .= '</ul>';
+
+		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 <a href="https://wordpress.org/support/article/wordpress-backups/">backup your database and files</a> first.' );
+			} else {
+				$warning = __( 'You are updating a plugin. Be sure to <a href="https://wordpress.org/support/article/wordpress-backups/">backup your database and files</a> first.' );
+			}
+
+			echo '<p class="update-from-upload-notice">' . $warning . '</p>';
+
+			$overwrite = $this->is_downgrading ? 'downgrade-plugin' : 'update-plugin';
+
+			$install_actions['ovewrite_plugin'] = sprintf(
+				'<a class="button button-primary" href="%s" target="_parent">%s</a>',
+				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(
+			'<a class="button" href="%s">%s</a>',
+			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 '<p class="update-from-upload-actions">' . implode( ' ', (array) $install_actions ) . '</p>';
+		}
+
+		return true;
+	}
 }
Index: src/wp-admin/includes/class-plugin-upgrader.php
===================================================================
--- src/wp-admin/includes/class-plugin-upgrader.php	(revision 48358)
+++ src/wp-admin/includes/class-plugin-upgrader.php	(working copy)
@@ -39,6 +39,16 @@
 	public $bulk = false;
 
 	/**
+	 * New plugin info.
+	 *
+	 * @since 5.5.0
+	 * @var array $new_plugin_data
+	 *
+	 * @see check_package()
+	 */
+	public $new_plugin_data = [];
+
+	/**
 	 * Initialize the upgrade strings.
 	 *
 	 * @since 2.8.0
@@ -54,6 +64,9 @@
 		$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 <strong>%1$s %2$s</strong>.' );
 	}
 
 	/**
@@ -67,9 +80,23 @@
 		$this->strings['downloading_package'] = sprintf( __( 'Downloading installation package from %s&#8230;' ), '<span class="code">%s</span>' );
 		$this->strings['unpack_package']      = __( 'Unpacking the package&#8230;' );
 		$this->strings['installing_package']  = __( 'Installing the plugin&#8230;' );
+		$this->strings['remove_old']          = __( 'Removing the current plugin&#8230;' );
+		$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 ( 'update-plugin' === $this->skin->overwrite ) {
+			$this->strings['installing_package'] = __( 'Updating the plugin&#8230;' );
+			$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&#8230;' );
+			$this->strings['process_failed']     = __( 'Plugin downgrade failed.' );
+			$this->strings['process_success']    = __( 'Plugin downgraded successfully.' );
+		}
 	}
 
 	/**
@@ -88,9 +115,9 @@
 	 * @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 +134,7 @@
 			array(
 				'package'           => $package,
 				'destination'       => WP_PLUGIN_DIR,
-				'clear_destination' => false, // Do not overwrite files.
+				'clear_destination' => $args['overwrite_package'],
 				'clear_working'     => true,
 				'hook_extra'        => array(
 					'type'   => 'plugin',
@@ -126,6 +153,19 @@
 		// Force refresh of plugin update information.
 		wp_clean_plugins_cache( $parsed_args['clear_update_cache'] );
 
+		if ( $parsed_args['overwrite_package'] ) {
+			/**
+			 * Fires when the upgrader overwrites 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_overwrited_package', $package, $this->new_plugin_data, 'plugin' );
+		}
+
 		return true;
 	}
 
@@ -145,7 +185,6 @@
 	 * @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 +262,6 @@
 	 * @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 +387,8 @@
 	public function check_package( $source ) {
 		global $wp_filesystem;
 
+		$this->new_plugin_data = [];
+
 		if ( is_wp_error( $source ) ) {
 			return $source;
 		}
@@ -359,19 +399,18 @@
 		}
 
 		// 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.' ) );
 		}
 
Index: src/wp-admin/includes/class-theme-installer-skin.php
===================================================================
--- src/wp-admin/includes/class-theme-installer-skin.php	(revision 48358)
+++ src/wp-admin/includes/class-theme-installer-skin.php	(working copy)
@@ -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 );
 	}
@@ -51,8 +58,32 @@
 	}
 
 	/**
+	 * 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 @@
 
 		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 @@
 			$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 '<h2 class="update-from-upload-heading">' . esc_html( __( 'This theme is already installed.' ) ) . '</h2>';
+
+		// 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 class="update-from-upload-comparison"><tbody>';
+		$table .= '<tr><th></th><th>' . esc_html( __( 'Current' ) ) . '</th><th>' . esc_html( __( 'Uploaded' ) ) . '</th></tr>';
+
+		$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 .= '<tr><td class="name-label">'. $label . '</td><td>' . esc_html( $old_value ) . '</td>';
+			$table .= ( $diff_field || $diff_version || $invalid_parent ) ? '<td class="warning">' : '<td>';
+			$table .= esc_html( $new_value ) . '</td></tr>';
+		}
+
+		$table .= '</tbody></table>';
+
+		/**
+		 * 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  = '<p>' . esc_html( __( 'The theme cannot be updated due to the following:' ) ) . '</p>';
+		$blocked_message .= '<ul class="ul-disc">';
+
+		if ( ! empty( $this->upgrader->new_theme_data['RequiresPHP'] ) && version_compare( phpversion(), $this->upgrader->new_theme_data['RequiresPHP'], '<' ) ) {
+			$error = sprintf(
+				/* translators: 1: Current PHP version, 2: Version required by the uploaded theme. */
+				__( 'The PHP version on your server is %1$s, however the uploaded theme requires %2$s.' ),
+				phpversion(),
+				$this->upgrader->new_theme_data['RequiresPHP']
+			);
+
+			$blocked_message .= '<li>' . esc_html( $error ) . '</li>';
+			$can_update = false;
+		}
+
+		if ( ! empty( $this->upgrader->new_theme_data['RequiresWP'] ) && version_compare( $GLOBALS['wp_version'], $this->upgrader->new_theme_data['RequiresWP'], '<' ) ) {
+			$error = sprintf(
+				/* translators: 1: Current WordPress version, 2: Version required by the uploaded theme. */
+				__( 'Your WordPress version is %1$s, however the uploaded theme requires %2$s.' ),
+				$GLOBALS['wp_version'],
+				$this->upgrader->new_theme_data['RequiresWP']
+			);
+
+			$blocked_message .= '<li>' . esc_html( $error ) . '</li>';
+			$can_update = false;
+		}
+
+		$blocked_message .= '</ul>';
+
+		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 <a href="https://wordpress.org/support/article/wordpress-backups/">backup your database and files</a> first.' );
+			} else {
+				$warning = __( 'You are updating a theme. Be sure to <a href="https://wordpress.org/support/article/wordpress-backups/">backup your database and files</a> first.' );
+			}
+
+			echo '<p class="update-from-upload-notice">' . $warning . '</p>';
+
+			$overwrite = $this->is_downgrading ? 'downgrade-theme' : 'update-theme';
+
+			$install_actions['ovewrite_theme'] = sprintf(
+				'<a class="button button-primary" href="%s" target="_parent">%s</a>',
+				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(
+			'<a class="button" href="%s" target="_parent">%s</a>',
+			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 '<p class="update-from-upload-actions">' . implode( ' ', (array) $install_actions ) . '</p>';
+		}
+
+		return true;
+	}
+
 }
Index: src/wp-admin/includes/class-theme-upgrader.php
===================================================================
--- src/wp-admin/includes/class-theme-upgrader.php	(revision 48358)
+++ src/wp-admin/includes/class-theme-upgrader.php	(working copy)
@@ -38,6 +38,16 @@
 	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.
 	 *
 	 * @since 2.8.0
@@ -65,6 +75,8 @@
 		$this->strings['downloading_package'] = sprintf( __( 'Downloading installation package from %s&#8230;' ), '<span class="code">%s</span>' );
 		$this->strings['unpack_package']      = __( 'Unpacking the package&#8230;' );
 		$this->strings['installing_package']  = __( 'Installing the theme&#8230;' );
+		$this->strings['remove_old']          = __( 'Removing the old version of the theme&#8230;' );
+		$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 @@
 		$this->strings['parent_theme_install_success'] = __( 'Successfully installed the parent theme, <strong>%1$s %2$s</strong>.' );
 		/* translators: %s: Theme name. */
 		$this->strings['parent_theme_not_found'] = sprintf( __( '<strong>The parent theme could not be found.</strong> You will need to install the parent theme, %s, before you can use this child theme.' ), '<strong>%s</strong>' );
+		/* translators: %s: Theme error. */
+		$this->strings['current_theme_has_errors'] = __( 'The current theme has the follow error: "%s".' );
+
+		if ( 'update-theme' === $this->skin->overwrite ) {
+			$this->strings['installing_package'] = __( 'Updating the theme&#8230;' );
+			$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&#8230;' );
+			$this->strings['process_failed']     = __( 'Theme downgrade failed.' );
+			$this->strings['process_success']    = __( 'Theme downgraded successfully.' );
+		}
 	}
 
 	/**
@@ -200,9 +226,9 @@
 	 * @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 @@
 			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,19 @@
 		// Refresh the Theme Update information.
 		wp_clean_themes_cache( $parsed_args['clear_update_cache'] );
 
+		if ( $parsed_args['overwrite_package'] ) {
+			/**
+			 * Fires when the upgrader overwrites 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 (theme or theme).
+			 */
+			do_action( 'upgrader_overwrited_package', $package, $this->new_theme_data, 'theme' );
+		}
+
 		return true;
 	}
 
@@ -259,7 +298,6 @@
 	 * @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 +370,6 @@
 	 * @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 +498,8 @@
 	public function check_package( $source ) {
 		global $wp_filesystem;
 
+		$this->new_theme_data = array();
+
 		if ( is_wp_error( $source ) ) {
 			return $source;
 		}
@@ -484,11 +523,16 @@
 			);
 		}
 
+		// 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 +561,7 @@
 			);
 		}
 
+		$this->new_theme_data = $info;
 		return $source;
 	}
 
@@ -640,7 +685,6 @@
 	 *                        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 +692,11 @@
 				return false;
 			}
 		}
-		return wp_get_theme( $theme );
+
+		$theme = wp_get_theme( $theme );
+		$theme->cache_delete();
+
+		return $theme;
 	}
 
 }
Index: src/wp-admin/includes/class-wp-upgrader-skin.php
===================================================================
--- src/wp-admin/includes/class-wp-upgrader-skin.php	(revision 48358)
+++ src/wp-admin/includes/class-wp-upgrader-skin.php	(working copy)
@@ -205,4 +205,16 @@
 	/**
 	 */
 	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;
+	}
 }
Index: src/wp-admin/includes/class-wp-upgrader.php
===================================================================
--- src/wp-admin/includes/class-wp-upgrader.php	(revision 48358)
+++ src/wp-admin/includes/class-wp-upgrader.php	(working copy)
@@ -798,7 +798,10 @@
 		$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' );
Index: src/wp-admin/includes/plugin-install.php
===================================================================
--- src/wp-admin/includes/plugin-install.php	(revision 48358)
+++ src/wp-admin/includes/plugin-install.php	(working copy)
@@ -353,7 +353,7 @@
 	<form method="post" enctype="multipart/form-data" class="wp-upload-form" action="<?php echo self_admin_url( 'update.php?action=upload-plugin' ); ?>">
 		<?php wp_nonce_field( 'plugin-upload' ); ?>
 		<label class="screen-reader-text" for="pluginzip"><?php _e( 'Plugin zip file' ); ?></label>
-		<input type="file" id="pluginzip" name="pluginzip" />
+		<input type="file" id="pluginzip" name="pluginzip" accept=".zip" />
 		<?php submit_button( __( 'Install Now' ), '', 'install-plugin-submit', false ); ?>
 	</form>
 </div>
Index: src/wp-admin/includes/theme-install.php
===================================================================
--- src/wp-admin/includes/theme-install.php	(revision 48358)
+++ src/wp-admin/includes/theme-install.php	(working copy)
@@ -183,7 +183,7 @@
 <form method="post" enctype="multipart/form-data" class="wp-upload-form" action="<?php echo self_admin_url( 'update.php?action=upload-theme' ); ?>">
 	<?php wp_nonce_field( 'theme-upload' ); ?>
 	<label class="screen-reader-text" for="themezip"><?php _e( 'Theme zip file' ); ?></label>
-	<input type="file" id="themezip" name="themezip" />
+	<input type="file" id="themezip" name="themezip" accept=".zip"/>
 	<?php submit_button( __( 'Install Now' ), '', 'install-theme-submit', false ); ?>
 </form>
 	<?php
Index: src/wp-admin/update.php
===================================================================
--- src/wp-admin/update.php	(revision 48358)
+++ src/wp-admin/update.php	(working copy)
@@ -162,9 +162,12 @@
 		$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, [ 'overwrite_package' => $overwrite ] );
+
 		if ( $result || is_wp_error( $result ) ) {
 			$file_upload->cleanup();
 		}
@@ -282,9 +285,12 @@
 		$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, [ 'overwrite_package' => $overwrite ] );
+
 		if ( $result || is_wp_error( $result ) ) {
 			$file_upload->cleanup();
 		}
