Make WordPress Core

Ticket #9757: 9757.2.diff

File 9757.2.diff, 36.6 KB (added by desrosj, 4 years ago)
  • src/wp-admin/css/themes.css

    diff --git a/src/wp-admin/css/themes.css b/src/wp-admin/css/themes.css
    index 127ce2ebca..6136155021 100644
    a b body.folded .theme-browser ~ .theme-overlay .theme-wrap { 
    984984  16.2 - Install Themes
    985985------------------------------------------------------------------------------*/
    986986
     987.update-php .wrap {
     988        max-width: 40rem;
     989}
     990
    987991/* Already installed theme */
    988992.theme-browser .theme .theme-installed {
    989993        background: #0073aa;
    990994}
     995
    991996.theme-browser .theme .notice-success p:before {
    992997        color: #79ba49;
    993998        content: "\f147";
    body.folded .theme-browser ~ .theme-overlay .theme-wrap { 
    10301035        overflow: hidden;
    10311036        position: relative;
    10321037        top: 10px;
    1033 }
    1034 
    1035 .upload-plugin-wrap {
    1036         display: none;
     1038        text-align: center;
    10371039}
    10381040
    10391041.show-upload-view .upload-theme,
    body.folded .theme-browser ~ .theme-overlay .theme-wrap { 
    10491051        border: 1px solid #ccd0d4;
    10501052        padding: 30px;
    10511053        margin: 30px auto;
    1052         max-width: 380px;
    1053         display: flex;
     1054        display: inline-flex;
    10541055        justify-content: space-between;
    10551056        align-items: center;
    10561057}
    10571058
     1059.upload-theme .wp-upload-form input[type="file"],
     1060.upload-plugin .wp-upload-form input[type="file"] {
     1061        margin-right: 10px;
     1062}
     1063
    10581064.upload-theme .install-help,
    10591065.upload-plugin .install-help {
    10601066        color: #555d66; /* #f1f1f1 background */
    p.no-themes-local { 
    10931099        .upload-theme .install-help {
    10941100                font-size: 15px;
    10951101                padding: 20px 0 0;
    1096                 text-align: left;
    10971102        }
    10981103}
    10991104
    p.no-themes-local { 
    11161121        line-height: 1.9;
    11171122}
    11181123
     1124.update-from-upload-comparison {
     1125        border-top: 1px solid #ddd;
     1126        border-bottom: 1px solid #ddd;
     1127        text-align: left;
     1128        margin: 1rem 0 1.4rem;
     1129        border-collapse: collapse;
     1130        width: 100%;
     1131}
     1132
     1133.update-from-upload-comparison tr:last-child td {
     1134        height: 1.4rem;
     1135    vertical-align: top;
     1136}
     1137
     1138.update-from-upload-comparison tr:first-child th {
     1139        font-weight: bold;
     1140        height: 1.4rem;
     1141    vertical-align: bottom;
     1142}
     1143
     1144.update-from-upload-comparison td.name-label {
     1145        text-align: right;
     1146}
     1147
     1148.update-from-upload-comparison td,
     1149.update-from-upload-comparison th {
     1150        padding: 0.4rem 1.4rem;
     1151}
     1152
     1153.update-from-upload-comparison td.warning {
     1154        color: #a00;
     1155}
     1156
     1157.update-from-upload-actions {
     1158        margin-top: 1.4rem;
     1159}
     1160
    11191161/*------------------------------------------------------------------------------
    11201162  16.3 - Custom Header Screen
    11211163------------------------------------------------------------------------------*/
  • src/wp-admin/includes/class-plugin-installer-skin.php

    diff --git a/src/wp-admin/includes/class-plugin-installer-skin.php b/src/wp-admin/includes/class-plugin-installer-skin.php
    index cb44919c66..c5d34ba28f 100644
    a b  
    1818class Plugin_Installer_Skin extends WP_Upgrader_Skin {
    1919        public $api;
    2020        public $type;
     21        public $url;
     22        public $overwrite;
     23
     24        private $is_downgrading = false;
    2125
    2226        /**
    2327         * @param array $args
    2428         */
    2529        public function __construct( $args = array() ) {
    2630                $defaults = array(
    27                         'type'   => 'web',
    28                         'url'    => '',
    29                         'plugin' => '',
    30                         'nonce'  => '',
    31                         'title'  => '',
     31                        'type'      => 'web',
     32                        'url'       => '',
     33                        'plugin'    => '',
     34                        'nonce'     => '',
     35                        'title'     => '',
     36                        'overwrite' => '',
    3237                );
    3338                $args     = wp_parse_args( $args, $defaults );
    3439
    35                 $this->type = $args['type'];
    36                 $this->api  = isset( $args['api'] ) ? $args['api'] : array();
     40                $this->type      = $args['type'];
     41                $this->url       = $args['url'];
     42                $this->api       = isset( $args['api'] ) ? $args['api'] : array();
     43                $this->overwrite = $args['overwrite'];
    3744
    3845                parent::__construct( $args );
    3946        }
    public function __construct( $args = array() ) { 
    4350        public function before() {
    4451                if ( ! empty( $this->api ) ) {
    4552                        $this->upgrader->strings['process_success'] = sprintf(
    46                                 /* translators: 1: Plugin name, 2: Plugin version. */
    47                                 __( 'Successfully installed the plugin <strong>%1$s %2$s</strong>.' ),
     53                                $this->upgrader->strings['process_success_specific'],
    4854                                $this->api->name,
    4955                                $this->api->version
    5056                        );
    5157                }
    5258        }
    5359
     60        /**
     61         * Hides the `process_failed` error when updating a plugin by uploading a zip file.
     62         *
     63         * @since 5.5.0
     64         *
     65         * @param $wp_error WP_Error.
     66         * @return bool
     67         */
     68        public function hide_process_failed( $wp_error ) {
     69                if (
     70                        'upload' === $this->type &&
     71                        '' === $this->overwrite &&
     72                        $wp_error->get_error_code() === 'folder_exists'
     73                ) {
     74                        return true;
     75                }
     76
     77                return false;
     78        }
     79
    5480        /**
    5581         */
    5682        public function after() {
     83                // Check if the plugin can be overwritten and output the HTML.
     84                if ( $this->do_overwrite() ) {
     85                        return;
     86                }
     87
    5788                $plugin_file = $this->upgrader->plugin_info();
    5889
    5990                $install_actions = array();
    public function after() { 
    117148
    118149                if ( ! $this->result || is_wp_error( $this->result ) ) {
    119150                        unset( $install_actions['activate_plugin'], $install_actions['network_activate'] );
    120                 } elseif ( ! current_user_can( 'activate_plugin', $plugin_file ) ) {
     151                } elseif ( ! current_user_can( 'activate_plugin', $plugin_file ) || is_plugin_active( $plugin_file ) ) {
    121152                        unset( $install_actions['activate_plugin'] );
    122153                }
    123154
    public function after() { 
    138169                        $this->feedback( implode( ' ', (array) $install_actions ) );
    139170                }
    140171        }
     172
     173        /**
     174         * Check if the plugin can be overwritten and output the HTML for overwriting a plugin on upload.
     175         *
     176         * @since 5.5.0
     177         *
     178         * @return bool Whether the plugin can be overwritten and HTML was outputted.
     179         */
     180        private function do_overwrite() {
     181                if ( 'upload' !== $this->type || ! is_wp_error( $this->result ) || 'folder_exists' !== $this->result->get_error_code() ) {
     182                        return false;
     183                }
     184
     185                $folder = $this->result->get_error_data( 'folder_exists' );
     186                $folder = ltrim( substr( $folder, strlen( WP_PLUGIN_DIR ) ), '/' );
     187
     188                $current_plugin_data = false;
     189                foreach ( get_plugins() as $plugin => $plugin_data ) {
     190                        if ( strrpos( $plugin, $folder ) !== 0 ) {
     191                                continue;
     192                        }
     193
     194                        $current_plugin_data = $plugin_data;
     195                }
     196
     197                if ( empty( $current_plugin_data ) || empty( $this->upgrader->new_plugin_data ) ) {
     198                        return false;
     199                }
     200
     201                echo '<h2 class="update-from-upload-heading">' . esc_html( __( 'This plugin is already installed.' ) ) . '</h2>';
     202
     203                $this->is_downgrading = version_compare( $current_plugin_data['Version'], $this->upgrader->new_plugin_data['Version'], '>' );
     204
     205                $rows = array(
     206                        'Name'        => __( 'Plugin Name' ),
     207                        'Version'     => __( 'Version' ),
     208                        'Author'      => __( 'Author' ),
     209                        'RequiresWP'  => __( 'Required WordPress version' ),
     210                        'RequiresPHP' => __( 'Required PHP version' ),
     211                );
     212
     213                $table  = '<table class="update-from-upload-comparison"><tbody>';
     214                $table .= '<tr><th></th><th>' . esc_html( __( 'Current' ) ) . '</th>';
     215                $table .= '<th>' . esc_html( __( 'Uploaded' ) ) . '</th></tr>';
     216
     217                $is_same_plugin = true; // Let's consider only these rows
     218                foreach ( $rows as $field => $label ) {
     219                        $old_value = ! empty( $current_plugin_data[ $field ] ) ? $current_plugin_data[ $field ] : '-';
     220                        $new_value = ! empty( $this->upgrader->new_plugin_data[ $field ] ) ? $this->upgrader->new_plugin_data[ $field ] : '-';
     221
     222                        $is_same_plugin = $is_same_plugin && ( $old_value === $new_value );
     223
     224                        $diff_field   = ( 'Version' !== $field && $new_value !== $old_value );
     225                        $diff_version = ( 'Version' === $field && $this->is_downgrading );
     226
     227                        $table .= '<tr><td class="name-label">' . $label . '</td><td>' . esc_html( $old_value ) . '</td>';
     228                        $table .= ( $diff_field || $diff_version ) ? '<td class="warning">' : '<td>';
     229                        $table .= esc_html( $new_value ) . '</td></tr>';
     230                }
     231
     232                $table .= '</tbody></table>';
     233
     234                /**
     235                 * Filters the compare table output for overwrite a plugin package on upload.
     236                 *
     237                 * @since 5.5.0
     238                 *
     239                 * @param string   $table                The output table with Name, Version, Author, RequiresWP and RequiresPHP info.
     240                 * @param array    $current_plugin_data  Array with current plugin data.
     241                 * @param array    $new_plugin_data      Array with uploaded plugin data.
     242                 */
     243                echo apply_filters( 'install_plugin_ovewrite_comparison', $table, $current_plugin_data, $this->upgrader->new_plugin_data );
     244
     245                $install_actions = array();
     246                $can_update      = true;
     247
     248                $blocked_message  = '<p>' . esc_html( __( 'The plugin cannot be updated due to the following:' ) ) . '</p>';
     249                $blocked_message .= '<ul class="ul-disc">';
     250
     251                if (
     252                        ! empty( $this->upgrader->new_plugin_data['RequiresPHP'] ) &&
     253                        version_compare( phpversion(), $this->upgrader->new_plugin_data['RequiresPHP'], '<' )
     254                ) {
     255                        $error = sprintf(
     256                                /* translators: 1: Current PHP version, 2: Version required by the uploaded plugin. */
     257                                __( 'The PHP version on your server is %1$s, however the uploaded plugin requires %2$s.' ),
     258                                phpversion(),
     259                                $this->upgrader->new_plugin_data['RequiresPHP']
     260                        );
     261
     262                        $blocked_message .= '<li>' . esc_html( $error ) . '</li>';
     263                        $can_update       = false;
     264                }
     265
     266                if (
     267                        ! empty( $this->upgrader->new_plugin_data['RequiresWP'] ) &&
     268                        version_compare( $GLOBALS['wp_version'], $this->upgrader->new_plugin_data['RequiresWP'], '<' )
     269                ) {
     270                        $error = sprintf(
     271                                /* translators: 1: Current WordPress version, 2: Version required by the uploaded plugin. */
     272                                __( 'Your WordPress version is %1$s, however the uploaded plugin requires %2$s.' ),
     273                                $GLOBALS['wp_version'],
     274                                $this->upgrader->new_plugin_data['RequiresWP']
     275                        );
     276
     277                        $blocked_message .= '<li>' . esc_html( $error ) . '</li>';
     278                        $can_update       = false;
     279                }
     280
     281                $blocked_message .= '</ul>';
     282
     283                if ( $can_update ) {
     284                        if ( $this->is_downgrading ) {
     285                                $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.' );
     286                        } else {
     287                                $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.' );
     288                        }
     289
     290                        echo '<p class="update-from-upload-notice">' . $warning . '</p>';
     291
     292                        $overwrite = $this->is_downgrading ? 'downgrade-plugin' : 'update-plugin';
     293
     294                        $install_actions['ovewrite_plugin'] = sprintf(
     295                                '<a class="button button-primary" href="%s" target="_parent">%s</a>',
     296                                wp_nonce_url( add_query_arg( 'overwrite', $overwrite, $this->url ), 'plugin-upload' ),
     297                                esc_html( __( 'Replace current with uploaded' ) )
     298                        );
     299                } else {
     300                        echo $blocked_message;
     301                }
     302
     303                $install_actions['plugins_page'] = sprintf(
     304                        '<a class="button" href="%s">%s</a>',
     305                        self_admin_url( 'plugin-install.php' ),
     306                        __( 'Cancel and go back' )
     307                );
     308
     309                /**
     310                 * Filters the list of action links available following a single plugin installation failed but ovewrite is allowed.
     311                 *
     312                 * @since 5.5.0
     313                 *
     314                 * @param string[] $install_actions Array of plugin action links.
     315                 * @param object   $api             Object containing WordPress.org API plugin data.
     316                 * @param array    $new_plugin_data Array with uploaded plugin data.
     317                 */
     318                $install_actions = apply_filters( 'install_plugin_ovewrite_actions', $install_actions, $this->api, $this->upgrader->new_plugin_data );
     319
     320                if ( ! empty( $install_actions ) ) {
     321                        echo '<p class="update-from-upload-actions">' . implode( ' ', (array) $install_actions ) . '</p>';
     322                }
     323
     324                return true;
     325        }
    141326}
  • src/wp-admin/includes/class-plugin-upgrader.php

    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 b class Plugin_Upgrader extends WP_Upgrader { 
    3838         */
    3939        public $bulk = false;
    4040
     41        /**
     42         * New plugin info.
     43         *
     44         * @since 5.5.0
     45         * @var array $new_plugin_data
     46         *
     47         * @see check_package()
     48         */
     49        public $new_plugin_data = array();
     50
    4151        /**
    4252         * Initialize the upgrade strings.
    4353         *
    public function upgrade_strings() { 
    5464                $this->strings['process_failed']       = __( 'Plugin update failed.' );
    5565                $this->strings['process_success']      = __( 'Plugin updated successfully.' );
    5666                $this->strings['process_bulk_success'] = __( 'Plugins updated successfully.' );
     67
     68                /* translators: 1: Plugin name, 2: Plugin version. */
     69                $this->strings['process_success_specific'] = __( 'Successfully installed the plugin <strong>%1$s %2$s</strong>.' );
    5770        }
    5871
    5972        /**
    public function install_strings() { 
    6780                $this->strings['downloading_package'] = sprintf( __( 'Downloading installation package from %s&#8230;' ), '<span class="code">%s</span>' );
    6881                $this->strings['unpack_package']      = __( 'Unpacking the package&#8230;' );
    6982                $this->strings['installing_package']  = __( 'Installing the plugin&#8230;' );
     83                $this->strings['remove_old']          = __( 'Removing the current plugin&#8230;' );
     84                $this->strings['remove_old_failed']   = __( 'Could not remove the current plugin.' );
    7085                $this->strings['no_files']            = __( 'The plugin contains no files.' );
    7186                $this->strings['process_failed']      = __( 'Plugin installation failed.' );
    7287                $this->strings['process_success']     = __( 'Plugin installed successfully.' );
     88
     89                if ( ! empty( $this->skin->overwrite ) ) {
     90                        if ( 'update-plugin' === $this->skin->overwrite ) {
     91                                $this->strings['installing_package'] = __( 'Updating the plugin&#8230;' );
     92                                $this->strings['process_failed']     = __( 'Plugin update failed.' );
     93                                $this->strings['process_success']    = __( 'Plugin updated successfully.' );
     94                        }
     95
     96                        if ( 'downgrade-plugin' === $this->skin->overwrite ) {
     97                                $this->strings['installing_package'] = __( 'Downgrading the plugin&#8230;' );
     98                                $this->strings['process_failed']     = __( 'Plugin downgrade failed.' );
     99                                $this->strings['process_success']    = __( 'Plugin downgraded successfully.' );
     100                        }
     101                }
    73102        }
    74103
    75104        /**
    public function install_strings() { 
    88117         * @return bool|WP_Error True if the installation was successful, false or a WP_Error otherwise.
    89118         */
    90119        public function install( $package, $args = array() ) {
    91 
    92120                $defaults    = array(
    93121                        'clear_update_cache' => true,
     122                        'overwrite_package'  => false, // Do not overwrite files.
    94123                );
    95124                $parsed_args = wp_parse_args( $args, $defaults );
    96125
    public function install( $package, $args = array() ) { 
    107136                        array(
    108137                                'package'           => $package,
    109138                                'destination'       => WP_PLUGIN_DIR,
    110                                 'clear_destination' => false, // Do not overwrite files.
     139                                'clear_destination' => $parsed_args['overwrite_package'],
    111140                                'clear_working'     => true,
    112141                                'hook_extra'        => array(
    113142                                        'type'   => 'plugin',
    public function install( $package, $args = array() ) { 
    126155                // Force refresh of plugin update information.
    127156                wp_clean_plugins_cache( $parsed_args['clear_update_cache'] );
    128157
     158                if ( $parsed_args['overwrite_package'] ) {
     159                        /**
     160                         * Fires when the upgrader has successfully overwritten a currently installed
     161                         * plugin or theme with an uploaded zip package.
     162                         *
     163                         * @since 5.5.0
     164                         *
     165                         * @param string  $package          The package file.
     166                         * @param array   $new_plugin_data  The new plugin data.
     167                         * @param string  $package_type     The package type (plugin or theme).
     168                         */
     169                        do_action( 'upgrader_overwrote_package', $package, $this->new_plugin_data, 'plugin' );
     170                }
     171
    129172                return true;
    130173        }
    131174
    public function install( $package, $args = array() ) { 
    145188         * @return bool|WP_Error True if the upgrade was successful, false or a WP_Error object otherwise.
    146189         */
    147190        public function upgrade( $plugin, $args = array() ) {
    148 
    149191                $defaults    = array(
    150192                        'clear_update_cache' => true,
    151193                );
    public function upgrade( $plugin, $args = array() ) { 
    223265         * @return array|false An array of results indexed by plugin file, or false if unable to connect to the filesystem.
    224266         */
    225267        public function bulk_upgrade( $plugins, $args = array() ) {
    226 
    227268                $defaults    = array(
    228269                        'clear_update_cache' => true,
    229270                );
    public function bulk_upgrade( $plugins, $args = array() ) { 
    349390        public function check_package( $source ) {
    350391                global $wp_filesystem;
    351392
     393                $this->new_plugin_data = array();
     394
    352395                if ( is_wp_error( $source ) ) {
    353396                        return $source;
    354397                }
    public function check_package( $source ) { 
    359402                }
    360403
    361404                // Check that the folder contains at least 1 valid plugin.
    362                 $plugins_found = false;
    363                 $files         = glob( $working_directory . '*.php' );
     405                $files = glob( $working_directory . '*.php' );
    364406                if ( $files ) {
    365407                        foreach ( $files as $file ) {
    366408                                $info = get_plugin_data( $file, false, false );
    367409                                if ( ! empty( $info['Name'] ) ) {
    368                                         $plugins_found = true;
     410                                        $this->new_plugin_data = $info;
    369411                                        break;
    370412                                }
    371413                        }
    372414                }
    373415
    374                 if ( ! $plugins_found ) {
     416                if ( empty( $this->new_plugin_data ) ) {
    375417                        return new WP_Error( 'incompatible_archive_no_plugins', $this->strings['incompatible_archive'], __( 'No valid plugins were found.' ) );
    376418                }
    377419
  • src/wp-admin/includes/class-theme-installer-skin.php

    diff --git a/src/wp-admin/includes/class-theme-installer-skin.php b/src/wp-admin/includes/class-theme-installer-skin.php
    index f568766086..5ca39b226f 100644
    a b  
    1818class Theme_Installer_Skin extends WP_Upgrader_Skin {
    1919        public $api;
    2020        public $type;
     21        public $url;
     22        public $overwrite;
     23
     24        private $is_downgrading = false;
    2125
    2226        /**
    2327         * @param array $args
    2428         */
    2529        public function __construct( $args = array() ) {
    2630                $defaults = array(
    27                         'type'  => 'web',
    28                         'url'   => '',
    29                         'theme' => '',
    30                         'nonce' => '',
    31                         'title' => '',
     31                        'type'      => 'web',
     32                        'url'       => '',
     33                        'theme'     => '',
     34                        'nonce'     => '',
     35                        'title'     => '',
     36                        'overwrite' => '',
    3237                );
    3338                $args     = wp_parse_args( $args, $defaults );
    3439
    35                 $this->type = $args['type'];
    36                 $this->api  = isset( $args['api'] ) ? $args['api'] : array();
     40                $this->type      = $args['type'];
     41                $this->url       = $args['url'];
     42                $this->api       = isset( $args['api'] ) ? $args['api'] : array();
     43                $this->overwrite = $args['overwrite'];
    3744
    3845                parent::__construct( $args );
    3946        }
    public function before() { 
    5057                }
    5158        }
    5259
     60        /**
     61         * Hides the `process_failed` error when updating a theme by uploading a zip file.
     62         *
     63         * @since 5.5.0
     64         *
     65         * @param $wp_error WP_Error.
     66         * @return bool
     67         */
     68        public function hide_process_failed( $wp_error ) {
     69                if (
     70                        'upload' === $this->type &&
     71                        '' === $this->overwrite &&
     72                        $wp_error->get_error_code() === 'folder_exists'
     73                ) {
     74                        return true;
     75                }
     76
     77                return false;
     78        }
     79
    5380        /**
    5481         */
    5582        public function after() {
     83                if ( $this->do_overwrite() ) {
     84                        return;
     85                }
     86
    5687                if ( empty( $this->upgrader->result['destination_name'] ) ) {
    5788                        return;
    5889                }
    public function after() { 
    130161
    131162                if ( ! $this->result || is_wp_error( $this->result ) || is_network_admin() || ! current_user_can( 'switch_themes' ) ) {
    132163                        unset( $install_actions['activate'], $install_actions['preview'] );
     164                } elseif ( get_option( 'template' ) === $stylesheet ) {
     165                        unset( $install_actions['activate'] );
    133166                }
    134167
    135168                /**
    public function after() { 
    147180                        $this->feedback( implode( ' | ', (array) $install_actions ) );
    148181                }
    149182        }
     183
     184        /**
     185         * Check if the theme can be overwritten and output the HTML for overwriting a theme on upload.
     186         *
     187         * @since 5.5.0
     188         *
     189         * @return bool Whether the theme can be overwritten and HTML was outputted.
     190         */
     191        private function do_overwrite() {
     192                if ( 'upload' !== $this->type || ! is_wp_error( $this->result ) || 'folder_exists' !== $this->result->get_error_code() ) {
     193                        return false;
     194                }
     195
     196                $folder = $this->result->get_error_data( 'folder_exists' );
     197                $folder = rtrim( $folder, '/' );
     198
     199                $current_theme_data = false;
     200                $all_themes         = wp_get_themes( array( 'errors' => null ) );
     201
     202                foreach ( $all_themes as $theme ) {
     203                        if ( rtrim( $theme->get_stylesheet_directory(), '/' ) !== $folder ) {
     204                                continue;
     205                        }
     206
     207                        $current_theme_data = $theme;
     208                }
     209
     210                if ( empty( $current_theme_data ) || empty( $this->upgrader->new_theme_data ) ) {
     211                        return false;
     212                }
     213
     214                echo '<h2 class="update-from-upload-heading">' . esc_html( __( 'This theme is already installed.' ) ) . '</h2>';
     215
     216                // Check errors for current theme
     217                if ( is_wp_error( $current_theme_data->errors() ) ) {
     218                        $this->feedback( 'current_theme_has_errors', $current_theme_data->errors()->get_error_message() );
     219                }
     220
     221                $this->is_downgrading = version_compare( $current_theme_data['Version'], $this->upgrader->new_theme_data['Version'], '>' );
     222
     223                $is_invalid_parent = false;
     224                if ( ! empty( $this->upgrader->new_theme_data['Template'] ) ) {
     225                        $is_invalid_parent = ! in_array( $this->upgrader->new_theme_data['Template'], array_keys( $all_themes ), true );
     226                }
     227
     228                $rows = array(
     229                        'Name'        => __( 'Theme Name' ),
     230                        'Version'     => __( 'Version' ),
     231                        'Author'      => __( 'Author' ),
     232                        'RequiresWP'  => __( 'Required WordPress version' ),
     233                        'RequiresPHP' => __( 'Required PHP version' ),
     234                        'Template'    => __( 'Parent Theme' ),
     235                );
     236
     237                $table  = '<table class="update-from-upload-comparison"><tbody>';
     238                $table .= '<tr><th></th><th>' . esc_html( __( 'Current' ) ) . '</th><th>' . esc_html( __( 'Uploaded' ) ) . '</th></tr>';
     239
     240                $is_same_theme = true; // Let's consider only these rows
     241                foreach ( $rows as $field => $label ) {
     242                        $old_value = $current_theme_data->display( $field, false );
     243                        $old_value = $old_value ? $old_value : '-';
     244
     245                        $new_value = ! empty( $this->upgrader->new_theme_data[ $field ] ) ? $this->upgrader->new_theme_data[ $field ] : '-';
     246
     247                        if ( $old_value === $new_value && '-' === $new_value && 'Template' === $field ) {
     248                                continue;
     249                        }
     250
     251                        $is_same_theme = $is_same_theme && ( $old_value === $new_value );
     252
     253                        $diff_field     = ( 'Version' !== $field && $new_value !== $old_value );
     254                        $diff_version   = ( 'Version' === $field && $this->is_downgrading );
     255                        $invalid_parent = false;
     256
     257                        if ( 'Template' === $field && $is_invalid_parent ) {
     258                                $invalid_parent = true;
     259                                $new_value     .= ' ' . __( '(not found)' );
     260                        }
     261
     262                        $table .= '<tr><td class="name-label">' . $label . '</td><td>' . esc_html( $old_value ) . '</td>';
     263                        $table .= ( $diff_field || $diff_version || $invalid_parent ) ? '<td class="warning">' : '<td>';
     264                        $table .= esc_html( $new_value ) . '</td></tr>';
     265                }
     266
     267                $table .= '</tbody></table>';
     268
     269                /**
     270                 * Filters the compare table output for overwrite a theme package on upload.
     271                 *
     272                 * @since 5.5.0
     273                 *
     274                 * @param string   $table               The output table with Name, Version, Author, RequiresWP and RequiresPHP info.
     275                 * @param array    $current_theme_data  Array with current theme data.
     276                 * @param array    $new_theme_data      Array with uploaded theme data.
     277                 */
     278                echo apply_filters( 'install_theme_overwrite_comparison', $table, $current_theme_data, $this->upgrader->new_theme_data );
     279
     280                $install_actions = array();
     281                $can_update      = true;
     282
     283                $blocked_message  = '<p>' . esc_html( __( 'The theme cannot be updated due to the following:' ) ) . '</p>';
     284                $blocked_message .= '<ul class="ul-disc">';
     285
     286                if ( ! empty( $this->upgrader->new_theme_data['RequiresPHP'] ) && version_compare( phpversion(), $this->upgrader->new_theme_data['RequiresPHP'], '<' ) ) {
     287                        $error = sprintf(
     288                                /* translators: 1: Current PHP version, 2: Version required by the uploaded theme. */
     289                                __( 'The PHP version on your server is %1$s, however the uploaded theme requires %2$s.' ),
     290                                phpversion(),
     291                                $this->upgrader->new_theme_data['RequiresPHP']
     292                        );
     293
     294                        $blocked_message .= '<li>' . esc_html( $error ) . '</li>';
     295                        $can_update       = false;
     296                }
     297
     298                if ( ! empty( $this->upgrader->new_theme_data['RequiresWP'] ) && version_compare( $GLOBALS['wp_version'], $this->upgrader->new_theme_data['RequiresWP'], '<' ) ) {
     299                        $error = sprintf(
     300                                /* translators: 1: Current WordPress version, 2: Version required by the uploaded theme. */
     301                                __( 'Your WordPress version is %1$s, however the uploaded theme requires %2$s.' ),
     302                                $GLOBALS['wp_version'],
     303                                $this->upgrader->new_theme_data['RequiresWP']
     304                        );
     305
     306                        $blocked_message .= '<li>' . esc_html( $error ) . '</li>';
     307                        $can_update       = false;
     308                }
     309
     310                $blocked_message .= '</ul>';
     311
     312                if ( $can_update ) {
     313                        if ( $this->is_downgrading ) {
     314                                $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.' );
     315                        } else {
     316                                $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.' );
     317                        }
     318
     319                        echo '<p class="update-from-upload-notice">' . $warning . '</p>';
     320
     321                        $overwrite = $this->is_downgrading ? 'downgrade-theme' : 'update-theme';
     322
     323                        $install_actions['ovewrite_theme'] = sprintf(
     324                                '<a class="button button-primary" href="%s" target="_parent">%s</a>',
     325                                wp_nonce_url( add_query_arg( 'overwrite', $overwrite, $this->url ), 'theme-upload' ),
     326                                esc_html( __( 'Replace current with uploaded' ) )
     327                        );
     328                } else {
     329                        echo $blocked_message;
     330                }
     331
     332                $install_actions['themes_page'] = sprintf(
     333                        '<a class="button" href="%s" target="_parent">%s</a>',
     334                        self_admin_url( 'theme-install.php' ),
     335                        __( 'Cancel and go back' )
     336                );
     337
     338                /**
     339                 * Filters the list of action links available following a single theme installation failed but ovewrite is allowed.
     340                 *
     341                 * @since 5.5.0
     342                 *
     343                 * @param string[] $install_actions Array of theme action links.
     344                 * @param object   $api             Object containing WordPress.org API theme data.
     345                 * @param array    $new_theme_data  Array with uploaded theme data.
     346                 */
     347                $install_actions = apply_filters( 'install_theme_ovewrite_actions', $install_actions, $this->api, $this->upgrader->new_theme_data );
     348
     349                if ( ! empty( $install_actions ) ) {
     350                        echo '<p class="update-from-upload-actions">' . implode( ' ', (array) $install_actions ) . '</p>';
     351                }
     352
     353                return true;
     354        }
     355
    150356}
  • src/wp-admin/includes/class-theme-upgrader.php

    diff --git a/src/wp-admin/includes/class-theme-upgrader.php b/src/wp-admin/includes/class-theme-upgrader.php
    index d5da2344c7..332cd077d2 100644
    a b class Theme_Upgrader extends WP_Upgrader { 
    3737         */
    3838        public $bulk = false;
    3939
     40        /**
     41         * New theme info.
     42         *
     43         * @since 5.5.0
     44         * @var array $new_theme_data
     45         *
     46         * @see check_package()
     47         */
     48        public $new_theme_data = array();
     49
    4050        /**
    4151         * Initialize the upgrade strings.
    4252         *
    public function install_strings() { 
    6575                $this->strings['downloading_package'] = sprintf( __( 'Downloading installation package from %s&#8230;' ), '<span class="code">%s</span>' );
    6676                $this->strings['unpack_package']      = __( 'Unpacking the package&#8230;' );
    6777                $this->strings['installing_package']  = __( 'Installing the theme&#8230;' );
     78                $this->strings['remove_old']          = __( 'Removing the old version of the theme&#8230;' );
     79                $this->strings['remove_old_failed']   = __( 'Could not remove the old theme.' );
    6880                $this->strings['no_files']            = __( 'The theme contains no files.' );
    6981                $this->strings['process_failed']      = __( 'Theme installation failed.' );
    7082                $this->strings['process_success']     = __( 'Theme installed successfully.' );
    public function install_strings() { 
    7991                $this->strings['parent_theme_install_success'] = __( 'Successfully installed the parent theme, <strong>%1$s %2$s</strong>.' );
    8092                /* translators: %s: Theme name. */
    8193                $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>' );
     94                /* translators: %s: Theme error. */
     95                $this->strings['current_theme_has_errors'] = __( 'The current theme has the follow error: "%s".' );
     96
     97                if ( 'update-theme' === $this->skin->overwrite ) {
     98                        $this->strings['installing_package'] = __( 'Updating the theme&#8230;' );
     99                        $this->strings['process_failed']     = __( 'Theme update failed.' );
     100                        $this->strings['process_success']    = __( 'Theme updated successfully.' );
     101                }
     102
     103                if ( 'downgrade-theme' === $this->skin->overwrite ) {
     104                        $this->strings['installing_package'] = __( 'Downgrading the theme&#8230;' );
     105                        $this->strings['process_failed']     = __( 'Theme downgrade failed.' );
     106                        $this->strings['process_success']    = __( 'Theme downgraded successfully.' );
     107                }
    82108        }
    83109
    84110        /**
    public function hide_activate_preview_actions( $actions ) { 
    200226         * @return bool|WP_Error True if the installation was successful, false or a WP_Error object otherwise.
    201227         */
    202228        public function install( $package, $args = array() ) {
    203 
    204229                $defaults    = array(
    205230                        'clear_update_cache' => true,
     231                        'overwrite_package'  => false, // Do not overwrite files.
    206232                );
    207233                $parsed_args = wp_parse_args( $args, $defaults );
    208234
    public function install( $package, $args = array() ) { 
    220246                        array(
    221247                                'package'           => $package,
    222248                                'destination'       => get_theme_root(),
    223                                 'clear_destination' => false, // Do not overwrite files.
     249                                'clear_destination' => $args['overwrite_package'],
    224250                                'clear_working'     => true,
    225251                                'hook_extra'        => array(
    226252                                        'type'   => 'theme',
    public function install( $package, $args = array() ) { 
    240266                // Refresh the Theme Update information.
    241267                wp_clean_themes_cache( $parsed_args['clear_update_cache'] );
    242268
     269                if ( $parsed_args['overwrite_package'] ) {
     270                        /**
     271                         * Fires when the upgrader has successfully overwritten a currently installed
     272                         * plugin or theme with an uploaded zip package.
     273                         *
     274                         * @since 5.5.0
     275                         *
     276                         * @param string  $package          The package file.
     277                         * @param array   $new_plugin_data  The new theme data.
     278                         * @param string  $package_type     The package type (theme or theme).
     279                         */
     280                        do_action( 'upgrader_overwrote_package', $package, $this->new_theme_data, 'theme' );
     281                }
     282
    243283                return true;
    244284        }
    245285
    public function install( $package, $args = array() ) { 
    259299         * @return bool|WP_Error True if the upgrade was successful, false or a WP_Error object otherwise.
    260300         */
    261301        public function upgrade( $theme, $args = array() ) {
    262 
    263302                $defaults    = array(
    264303                        'clear_update_cache' => true,
    265304                );
    public function upgrade( $theme, $args = array() ) { 
    332371         * @return array[]|false An array of results, or false if unable to connect to the filesystem.
    333372         */
    334373        public function bulk_upgrade( $themes, $args = array() ) {
    335 
    336374                $defaults    = array(
    337375                        'clear_update_cache' => true,
    338376                );
    public function bulk_upgrade( $themes, $args = array() ) { 
    461499        public function check_package( $source ) {
    462500                global $wp_filesystem;
    463501
     502                $this->new_theme_data = array();
     503
    464504                if ( is_wp_error( $source ) ) {
    465505                        return $source;
    466506                }
    public function check_package( $source ) { 
    484524                        );
    485525                }
    486526
     527                // All these headers are needed on Theme_Installer_Skin::do_overwrite().
    487528                $info = get_file_data(
    488529                        $working_directory . 'style.css',
    489530                        array(
    490                                 'Name'     => 'Theme Name',
    491                                 'Template' => 'Template',
     531                                'Name'        => 'Theme Name',
     532                                'Version'     => 'Version',
     533                                'Author'      => 'Author',
     534                                'Template'    => 'Template',
     535                                'RequiresWP'  => 'Requires at least',
     536                                'RequiresPHP' => 'Requires PHP',
    492537                        )
    493538                );
    494539
    public function check_package( $source ) { 
    517562                        );
    518563                }
    519564
     565                $this->new_theme_data = $info;
    520566                return $source;
    521567        }
    522568
    public function delete_old_theme( $removed, $local_destination, $remote_destinat 
    640686         *                        and the last result isn't set.
    641687         */
    642688        public function theme_info( $theme = null ) {
    643 
    644689                if ( empty( $theme ) ) {
    645690                        if ( ! empty( $this->result['destination_name'] ) ) {
    646691                                $theme = $this->result['destination_name'];
    public function theme_info( $theme = null ) { 
    648693                                return false;
    649694                        }
    650695                }
    651                 return wp_get_theme( $theme );
     696
     697                $theme = wp_get_theme( $theme );
     698                $theme->cache_delete();
     699
     700                return $theme;
    652701        }
    653702
    654703}
  • src/wp-admin/includes/class-wp-upgrader-skin.php

    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 b public function bulk_header() {} 
    205205        /**
    206206         */
    207207        public function bulk_footer() {}
     208
     209        /**
     210         * Hides the `process_failed` error message when updating by uploading a zip file.
     211         *
     212         * @since 5.5.0
     213         *
     214         * @param $wp_error WP_Error
     215         * @return bool
     216         */
     217        public function hide_process_failed( $wp_error ) {
     218                return false;
     219        }
    208220}
  • src/wp-admin/includes/class-wp-upgrader.php

    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 b public function run( $options ) { 
    798798                $this->skin->set_result( $result );
    799799                if ( is_wp_error( $result ) ) {
    800800                        $this->skin->error( $result );
    801                         $this->skin->feedback( 'process_failed' );
     801
     802                        if ( ! method_exists( $this->skin, 'hide_process_failed' ) || ! $this->skin->hide_process_failed( $result ) ) {
     803                                $this->skin->feedback( 'process_failed' );
     804                        }
    802805                } else {
    803806                        // Installation succeeded.
    804807                        $this->skin->feedback( 'process_success' );
  • src/wp-admin/includes/plugin-install.php

    diff --git a/src/wp-admin/includes/plugin-install.php b/src/wp-admin/includes/plugin-install.php
    index c8f3406f7b..dac564bfa2 100644
    a b function install_plugins_upload() { 
    353353        <form method="post" enctype="multipart/form-data" class="wp-upload-form" action="<?php echo self_admin_url( 'update.php?action=upload-plugin' ); ?>">
    354354                <?php wp_nonce_field( 'plugin-upload' ); ?>
    355355                <label class="screen-reader-text" for="pluginzip"><?php _e( 'Plugin zip file' ); ?></label>
    356                 <input type="file" id="pluginzip" name="pluginzip" />
     356                <input type="file" id="pluginzip" name="pluginzip" accept=".zip" />
    357357                <?php submit_button( __( 'Install Now' ), '', 'install-plugin-submit', false ); ?>
    358358        </form>
    359359</div>
  • src/wp-admin/includes/theme-install.php

    diff --git a/src/wp-admin/includes/theme-install.php b/src/wp-admin/includes/theme-install.php
    index 267ae26a94..bbc132ff4c 100644
    a b function install_themes_upload() { 
    183183<form method="post" enctype="multipart/form-data" class="wp-upload-form" action="<?php echo self_admin_url( 'update.php?action=upload-theme' ); ?>">
    184184        <?php wp_nonce_field( 'theme-upload' ); ?>
    185185        <label class="screen-reader-text" for="themezip"><?php _e( 'Theme zip file' ); ?></label>
    186         <input type="file" id="themezip" name="themezip" />
     186        <input type="file" id="themezip" name="themezip" accept=".zip"/>
    187187        <?php submit_button( __( 'Install Now' ), '', 'install-theme-submit', false ); ?>
    188188</form>
    189189        <?php
  • src/wp-admin/update.php

    diff --git a/src/wp-admin/update.php b/src/wp-admin/update.php
    index 8cb0cf44b8..efd96f26ca 100644
    a b  
    162162                $url   = add_query_arg( array( 'package' => $file_upload->id ), 'update.php?action=upload-plugin' );
    163163                $type  = 'upload'; // Install plugin type, From Web or an Upload.
    164164
    165                 $upgrader = new Plugin_Upgrader( new Plugin_Installer_Skin( compact( 'type', 'title', 'nonce', 'url' ) ) );
    166                 $result   = $upgrader->install( $file_upload->package );
     165                $overwrite = isset( $_GET['overwrite'] ) ? sanitize_text_field( $_GET['overwrite'] ) : '';
     166                $overwrite = in_array( $overwrite, array( 'update-plugin', 'downgrade-plugin' ), true ) ? $overwrite : '';
     167
     168                $upgrader = new Plugin_Upgrader( new Plugin_Installer_Skin( compact( 'type', 'title', 'nonce', 'url', 'overwrite' ) ) );
     169                $result   = $upgrader->install( $file_upload->package, array( 'overwrite_package' => $overwrite ) );
    167170
    168171                if ( $result || is_wp_error( $result ) ) {
    169172                        $file_upload->cleanup();
     
    282285                $url   = add_query_arg( array( 'package' => $file_upload->id ), 'update.php?action=upload-theme' );
    283286                $type  = 'upload'; // Install theme type, From Web or an Upload.
    284287
    285                 $upgrader = new Theme_Upgrader( new Theme_Installer_Skin( compact( 'type', 'title', 'nonce', 'url' ) ) );
    286                 $result   = $upgrader->install( $file_upload->package );
     288                $overwrite = isset( $_GET['overwrite'] ) ? sanitize_text_field( $_GET['overwrite'] ) : '';
     289                $overwrite = in_array( $overwrite, array( 'update-theme', 'downgrade-theme' ), true ) ? $overwrite : '';
     290
     291                $upgrader = new Theme_Upgrader( new Theme_Installer_Skin( compact( 'type', 'title', 'nonce', 'url', 'overwrite' ) ) );
     292                $result   = $upgrader->install( $file_upload->package, array( 'overwrite_package' => $overwrite ) );
    287293
    288294                if ( $result || is_wp_error( $result ) ) {
    289295                        $file_upload->cleanup();