Make WordPress Core

Ticket #9757: 9757.5.patch

File 9757.5.patch, 34.2 KB (added by mariovalney, 4 years ago)

Avoid install plugin with wrong PHP requirement.

  • src/wp-admin/css/themes.css

    diff --git a/src/wp-admin/css/themes.css b/src/wp-admin/css/themes.css
    index a5ef219f77..fc5e528de5 100644
    a b body.folded .theme-browser ~ .theme-overlay .theme-wrap { 
    10291029        overflow: hidden;
    10301030        position: relative;
    10311031        top: 10px;
    1032 }
    1033 
    1034 .upload-plugin-wrap {
    1035         display: none;
     1032        text-align: center;
    10361033}
    10371034
    10381035.show-upload-view .upload-theme,
    body.folded .theme-browser ~ .theme-overlay .theme-wrap { 
    10481045        border: 1px solid #ccd0d4;
    10491046        padding: 30px;
    10501047        margin: 30px auto;
    1051         max-width: 380px;
    1052         display: flex;
     1048        display: inline-flex;
    10531049        justify-content: space-between;
    10541050        align-items: center;
    10551051}
    10561052
     1053.upload-theme .wp-upload-form input[type="file"],
     1054.upload-plugin .wp-upload-form input[type="file"] {
     1055        margin-right: 10px;
     1056}
     1057
    10571058.upload-theme .install-help,
    10581059.upload-plugin .install-help {
    10591060        color: #555d66; /* #f1f1f1 background */
    p.no-themes-local { 
    10921093        .upload-theme .install-help {
    10931094                font-size: 15px;
    10941095                padding: 20px 0 0;
    1095                 text-align: left;
    10961096        }
    10971097}
    10981098
    p.no-themes-local { 
    11151115        line-height: 1.9;
    11161116}
    11171117
     1118.compare-themes-table,
     1119.compare-plugins-table {
     1120        text-align: left;
     1121        margin: 1em 0 2em;
     1122        border-collapse: collapse;
     1123}
     1124
     1125.compare-themes-table th,
     1126.compare-plugins-table th {
     1127        font-weight: bold;
     1128        padding: 3px 10px;
     1129}
     1130
     1131.compare-themes-table td,
     1132.compare-plugins-table td {
     1133        padding: 3px 10px;
     1134}
     1135
     1136.compare-themes-table tr.warning,
     1137.compare-plugins-table tr.warning,
     1138a.ovewrite-uploaded-theme {
     1139        color: #a00;
     1140}
     1141
     1142a.ovewrite-uploaded-plugin {
     1143        color: #a00;
     1144        margin-left: 10px;
     1145}
     1146
     1147
     1148a.ovewrite-uploaded-theme:hover,
     1149a.ovewrite-uploaded-plugin:hover {
     1150        color: #d80707;
     1151}
     1152
    11181153/*------------------------------------------------------------------------------
    11191154  16.3 - Custom Header Screen
    11201155------------------------------------------------------------------------------*/
  • 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 9ee23be90b..46a50a0f25 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        }
    class Plugin_Installer_Skin extends WP_Upgrader_Skin { 
    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                        );
    class Plugin_Installer_Skin extends WP_Upgrader_Skin { 
    5460        /**
    5561         */
    5662        public function after() {
     63                // Check if the plugin can be overwritten and output the HTML.
     64                if ( $this->do_overwrite() ) {
     65                        return;
     66                }
     67
    5768                $plugin_file = $this->upgrader->plugin_info();
    5869
    5970                $install_actions = array();
    class Plugin_Installer_Skin extends WP_Upgrader_Skin { 
    117128
    118129                if ( ! $this->result || is_wp_error( $this->result ) ) {
    119130                        unset( $install_actions['activate_plugin'], $install_actions['network_activate'] );
    120                 } elseif ( ! current_user_can( 'activate_plugin', $plugin_file ) ) {
     131                } elseif ( ! current_user_can( 'activate_plugin', $plugin_file ) || is_plugin_active( $plugin_file ) ) {
    121132                        unset( $install_actions['activate_plugin'] );
    122133                }
    123134
    class Plugin_Installer_Skin extends WP_Upgrader_Skin { 
    138149                        $this->feedback( implode( ' ', (array) $install_actions ) );
    139150                }
    140151        }
     152
     153        /**
     154         * Check if the plugin can be overwritten and output the HTML for overwriting a plugin on upload.
     155         *
     156         * @since 5.5.0
     157         *
     158         * @return bool Whether the plugin can be overwritten and HTML was outputted.
     159         */
     160        private function do_overwrite() {
     161                if ( 'upload' !== $this->type || ! is_wp_error( $this->result ) || 'folder_exists' !== $this->result->get_error_code() ) {
     162                        return false;
     163                }
     164
     165                $folder = $this->result->get_error_data( 'folder_exists' );
     166                $folder = ltrim( substr( $folder, strlen( WP_PLUGIN_DIR ) ), '/' );
     167
     168                $current_plugin_data = false;
     169                foreach ( get_plugins() as $plugin => $plugin_data ) {
     170                        if ( strrpos( $plugin, $folder ) !== 0 ) {
     171                                continue;
     172                        }
     173
     174                        $current_plugin_data = $plugin_data;
     175                }
     176
     177                if ( empty( $current_plugin_data ) || empty( $this->upgrader->new_plugin_data ) ) {
     178                        return false;
     179                }
     180
     181                $this->feedback( 'compare_before_overwrite' );
     182
     183                $this->is_downgrading = version_compare( $current_plugin_data['Version'], $this->upgrader->new_plugin_data['Version'], '>' );
     184
     185                $rows = array(
     186                        'Name'        => __( 'Plugin Name' ),
     187                        'Version'     => __( 'Version' ),
     188                        'Author'      => __( 'Author' ),
     189                        'RequiresWP'  => __( 'Required WordPress version' ),
     190                        'RequiresPHP' => __( 'Required PHP version' ),
     191                );
     192
     193                $table  = '<table class="compare-plugins-table"><tbody>';
     194                $table .= '<tr><th></th><th>' . esc_html( __( 'Current' ) ) . '</th><th>' . esc_html( __( 'Uploaded' ) ) . '</th></tr>';
     195
     196                $is_same_plugin = true; // Let's consider only these rows
     197                foreach ( $rows as $field => $label ) {
     198                        $old_value = ! empty( $current_plugin_data[ $field ] ) ? $current_plugin_data[ $field ] : '-';
     199                        $new_value = ! empty( $this->upgrader->new_plugin_data[ $field ] ) ? $this->upgrader->new_plugin_data[ $field ] : '-';
     200
     201                        $is_same_plugin = $is_same_plugin && ( $old_value === $new_value );
     202
     203                        $diff_field   = ( 'Version' !== $field && $new_value !== $old_value );
     204                        $diff_version = ( 'Version' === $field && $this->is_downgrading );
     205
     206                        $table .= ( $diff_field || $diff_version ) ? '<tr class="warning">' : '<tr>';
     207                        $table .= '<td>' . $label . '</td><td>' . esc_html( $old_value ) . '</td><td>' . esc_html( $new_value ) . '</td></tr>';
     208                }
     209
     210                $table .= '</tbody></table>';
     211
     212                if ( $is_same_plugin ) {
     213                        $this->feedback( 'reuploading_plugin' );
     214                }
     215
     216                /**
     217                 * Filters the compare table output for overwrite a plugin package on upload.
     218                 *
     219                 * @since 5.5.0
     220                 *
     221                 * @param string   $table                The output table with Name, Version, Author, RequiresWP and RequiresPHP info.
     222                 * @param array    $current_plugin_data  Array with current plugin data.
     223                 * @param array    $new_plugin_data      Array with uploaded plugin data.
     224                 */
     225                echo apply_filters( 'install_plugin_ovewrite_comparison', $table, $current_plugin_data, $this->upgrader->new_plugin_data );
     226
     227                // Action links.
     228                $install_actions = array(
     229                        'plugins_page' => sprintf(
     230                                '<a href="%s">%s</a>',
     231                                self_admin_url( 'plugin-install.php' ),
     232                                __( 'Cancel and go back' )
     233                        ),
     234                );
     235
     236                $can_update = true;
     237
     238                if ( ! empty( $this->upgrader->new_plugin_data['RequiresPHP'] ) && version_compare( phpversion(), $this->upgrader->new_plugin_data['RequiresPHP'], '<' ) ) {
     239                        $error = sprintf(
     240                                /* translators: 1: Current PHP version, 2: Version required by the uploaded plugin. */
     241                                __( 'The plugin cannot be updated. The PHP version on your server is %1$s, however the uploaded plugin requires %2$s.' ),
     242                                phpversion(),
     243                                $this->upgrader->new_plugin_data['RequiresPHP']
     244                        );
     245
     246                        $this->feedback( '<div class="notice notice-error inline"><p>' . esc_html( $error ) . '</p></div>' );
     247                        $can_update = false;
     248                }
     249
     250                if ( ! empty( $this->upgrader->new_plugin_data['RequiresWP'] ) && version_compare( $GLOBALS['wp_version'], $this->upgrader->new_plugin_data['RequiresWP'], '<' ) ) {
     251                        $error = sprintf(
     252                                /* translators: 1: Current WordPress version, 2: Version required by the uploaded plugin. */
     253                                __( 'The plugin cannot be updated. Your WordPress version is %1$s, however the uploaded plugin requires %2$s.' ),
     254                                $GLOBALS['wp_version'],
     255                                $this->upgrader->new_plugin_data['RequiresWP']
     256                        );
     257
     258                        $this->feedback( '<div class="notice notice-error inline"><p>' . esc_html( $error ) . '</p></div>' );
     259                        $can_update = false;
     260                }
     261
     262                if ( $can_update ) {
     263                        $warning = __( '<strong>Important:</strong> Before updating, please <a href="https://wordpress.org/support/article/wordpress-backups/">back up your database and files</a>.' );
     264                        $this->feedback( '<div class="notice notice-warning inline"><p>' . $warning . '</p></div>' );
     265
     266                        $overwrite = 'update-plugin';
     267                        $label     = __( 'Remove current and install the uploaded version' );
     268
     269                        if ( $this->is_downgrading ) {
     270                                $overwrite = 'downgrade-plugin';
     271
     272                                if ( isset( $this->upgrader->new_plugin_data['Version'] ) ) {
     273                                        $label = sprintf( __( 'Remove current and install version %s' ), $this->upgrader->new_plugin_data['Version'] );
     274                                }
     275                        }
     276
     277                        $install_actions['ovewrite_plugin'] = sprintf(
     278                                '<a class="ovewrite-uploaded-plugin" href="%s" target="_parent">%s</a>',
     279                                wp_nonce_url( add_query_arg( 'overwrite', $overwrite, $this->url ), 'plugin-upload' ),
     280                                esc_html( $label )
     281                        );
     282                }
     283
     284                /**
     285                 * Filters the list of action links available following a single plugin installation failed but ovewrite is allowed.
     286                 *
     287                 * @since 5.5.0
     288                 *
     289                 * @param string[] $install_actions Array of plugin action links.
     290                 * @param object   $api             Object containing WordPress.org API plugin data.
     291                 * @param array    $new_plugin_data Array with uploaded plugin data.
     292                 */
     293                $install_actions = apply_filters( 'install_plugin_ovewrite_actions', $install_actions, $this->api, $this->upgrader->new_plugin_data );
     294
     295                if ( ! empty( $install_actions ) ) {
     296                        $this->feedback( implode( ' ', (array) $install_actions ) );
     297                }
     298
     299                return true;
     300        }
    141301}
  • 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 214f371866..3c2c5f1740 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 = [];
     50
    4151        /**
    4252         * Initialize the upgrade strings.
    4353         *
    class Plugin_Upgrader extends WP_Upgrader { 
    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        /**
    class Plugin_Upgrader extends WP_Upgrader { 
    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                $this->strings['reuploading_plugin']       = __( 'Seems you have uploaded a plugin that is already installed.' );
     90                $this->strings['compare_before_overwrite'] = __( 'Plugin information:' );
     91
     92                if ( 'update-plugin' === $this->skin->overwrite ) {
     93                        $this->strings['installing_package'] = __( 'Updating the plugin&#8230;' );
     94                        $this->strings['process_failed']     = __( 'Plugin update failed.' );
     95                        $this->strings['process_success']    = __( 'Plugin updated successfully.' );
     96                }
     97
     98                if ( 'downgrade-plugin' === $this->skin->overwrite ) {
     99                        $this->strings['installing_package'] = __( 'Downgrading the plugin&#8230;' );
     100                        $this->strings['process_failed']     = __( 'Plugin downgrade failed.' );
     101                        $this->strings['process_success']    = __( 'Plugin downgraded successfully.' );
     102                }
    73103        }
    74104
    75105        /**
    class Plugin_Upgrader extends WP_Upgrader { 
    88118         * @return bool|WP_Error True if the installation was successful, false or a WP_Error otherwise.
    89119         */
    90120        public function install( $package, $args = array() ) {
    91 
    92121                $defaults    = array(
    93122                        'clear_update_cache' => true,
     123                        'overwrite_package'  => false, // Do not overwrite files.
    94124                );
    95125                $parsed_args = wp_parse_args( $args, $defaults );
    96126
    class Plugin_Upgrader extends WP_Upgrader { 
    107137                        array(
    108138                                'package'           => $package,
    109139                                'destination'       => WP_PLUGIN_DIR,
    110                                 'clear_destination' => false, // Do not overwrite files.
     140                                'clear_destination' => $args['overwrite_package'],
    111141                                'clear_working'     => true,
    112142                                'hook_extra'        => array(
    113143                                        'type'   => 'plugin',
    class Plugin_Upgrader extends WP_Upgrader { 
    126156                // Force refresh of plugin update information.
    127157                wp_clean_plugins_cache( $parsed_args['clear_update_cache'] );
    128158
     159                if ( $parsed_args['overwrite_package'] ) {
     160                        /**
     161                         * Fires when the upgrader process a install forcing a package overwrite.
     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_overwrited_package', $package, $this->new_plugin_data, 'plugin' );
     170                }
     171
    129172                return true;
    130173        }
    131174
    class Plugin_Upgrader extends WP_Upgrader { 
    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                );
    class Plugin_Upgrader extends WP_Upgrader { 
    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                );
    class Plugin_Upgrader extends WP_Upgrader { 
    349390        public function check_package( $source ) {
    350391                global $wp_filesystem;
    351392
     393                $this->new_plugin_data = [];
     394
    352395                if ( is_wp_error( $source ) ) {
    353396                        return $source;
    354397                }
    class Plugin_Upgrader extends WP_Upgrader { 
    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
     420                if ( ! empty( $this->new_plugin_data['RequiresPHP'] ) && version_compare( phpversion(), $this->new_plugin_data['RequiresPHP'], '<' ) ) {
     421            return new WP_Error( 'incompatible_php_required_version', $this->strings['incompatible_archive'], __( 'Current PHP version does not meet minimum requirements for this plugin.' ) );
     422        }
     423
    378424                return $source;
    379425        }
    380426
  • 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..1458042d2a 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        }
    class Theme_Installer_Skin extends WP_Upgrader_Skin { 
    5360        /**
    5461         */
    5562        public function after() {
     63                if ( $this->do_overwrite() ) {
     64                        return;
     65                }
     66
    5667                if ( empty( $this->upgrader->result['destination_name'] ) ) {
    5768                        return;
    5869                }
    class Theme_Installer_Skin extends WP_Upgrader_Skin { 
    130141
    131142                if ( ! $this->result || is_wp_error( $this->result ) || is_network_admin() || ! current_user_can( 'switch_themes' ) ) {
    132143                        unset( $install_actions['activate'], $install_actions['preview'] );
     144                } elseif ( get_option( 'template' ) === $stylesheet ) {
     145                        unset( $install_actions['activate'] );
    133146                }
    134147
    135148                /**
    class Theme_Installer_Skin extends WP_Upgrader_Skin { 
    147160                        $this->feedback( implode( ' | ', (array) $install_actions ) );
    148161                }
    149162        }
     163
     164        /**
     165         * Check if the theme can be overwritten and output the HTML for overwriting a theme on upload.
     166         *
     167         * @since 5.5.0
     168         *
     169         * @return bool Whether the theme can be overwritten and HTML was outputted.
     170         */
     171        private function do_overwrite() {
     172                if ( 'upload' !== $this->type || ! is_wp_error( $this->result ) || 'folder_exists' !== $this->result->get_error_code() ) {
     173                        return false;
     174                }
     175
     176                $folder = $this->result->get_error_data( 'folder_exists' );
     177                $folder = rtrim( $folder, '/' );
     178
     179                $current_theme_data = false;
     180                $all_themes         = wp_get_themes( array( 'errors' => null ) );
     181                foreach ( $all_themes as $theme ) {
     182                        if ( rtrim( $theme->get_stylesheet_directory(), '/' ) !== $folder ) {
     183                                continue;
     184                        }
     185
     186                        $current_theme_data = $theme;
     187                }
     188
     189                if ( empty( $current_theme_data ) || empty( $this->upgrader->new_theme_data ) ) {
     190                        return false;
     191                }
     192
     193                $this->is_downgrading = version_compare( $current_theme_data['Version'], $this->upgrader->new_theme_data['Version'], '>' );
     194
     195                $is_invalid_parent = false;
     196                if ( ! empty( $this->upgrader->new_theme_data['Template'] ) ) {
     197                        $is_invalid_parent = ! in_array( $this->upgrader->new_theme_data['Template'], array_keys( $all_themes ), true );
     198                }
     199
     200                $rows = array(
     201                        'Name'        => __( 'Theme Name' ),
     202                        'Version'     => __( 'Version' ),
     203                        'Author'      => __( 'Author' ),
     204                        'RequiresWP'  => __( 'Required WordPress version' ),
     205                        'RequiresPHP' => __( 'Required PHP version' ),
     206                        'Template'    => __( 'Parent Theme' ),
     207                );
     208
     209                $table  = '<table class="compare-themes-table"><tbody>';
     210                $table .= '<tr><th></th><th>' . esc_html( __( 'Current' ) ) . '</th><th>' . esc_html( __( 'Uploaded' ) ) . '</th></tr>';
     211
     212                $is_same_theme = true; // Let's consider only these rows
     213                foreach ( $rows as $field => $label ) {
     214                        $old_value = $current_theme_data->display( $field, false );
     215                        $old_value = $old_value ? $old_value : '-';
     216
     217                        $new_value = ! empty( $this->upgrader->new_theme_data[ $field ] ) ? $this->upgrader->new_theme_data[ $field ] : '-';
     218
     219                        if ( $old_value === $new_value && '-' === $new_value && 'Template' === $field ) {
     220                                continue;
     221                        }
     222
     223                        $is_same_theme = $is_same_theme && ( $old_value === $new_value );
     224
     225                        $diff_field     = ( 'Version' !== $field && $new_value !== $old_value );
     226                        $diff_version   = ( 'Version' === $field && $this->is_downgrading );
     227                        $invalid_parent = false;
     228
     229                        if ( 'Template' === $field && $is_invalid_parent ) {
     230                                $invalid_parent = true;
     231                                $new_value     .= ' ' . __( '(not found)' );
     232                        }
     233
     234                        $table .= ( $diff_field || $diff_version || $invalid_parent ) ? '<tr class="warning">' : '<tr>';
     235                        $table .= '<td>' . $label . '</td><td>' . esc_html( $old_value ) . '</td><td>' . esc_html( $new_value ) . '</td></tr>';
     236                }
     237
     238                $table .= '</tbody></table>';
     239
     240                $this->feedback( 'compare_before_overwrite' );
     241
     242                if ( $is_same_theme ) {
     243                        $this->feedback( 'reuploading_theme' );
     244                }
     245
     246                // Check errors for current theme
     247                if ( is_wp_error( $current_theme_data->errors() ) ) {
     248                        $this->feedback( 'current_theme_has_errors', $current_theme_data->errors()->get_error_message() );
     249                }
     250
     251                /**
     252                 * Filters the compare table output for overwrite a theme package on upload.
     253                 *
     254                 * @since 5.5.0
     255                 *
     256                 * @param string   $table               The output table with Name, Version, Author, RequiresWP and RequiresPHP info.
     257                 * @param array    $current_theme_data  Array with current theme data.
     258                 * @param array    $new_theme_data      Array with uploaded theme data.
     259                 */
     260                echo apply_filters( 'install_theme_overwrite_comparison', $table, $current_theme_data, $this->upgrader->new_theme_data );
     261
     262                $install_actions = array(
     263                        'themes_page' => sprintf(
     264                                '<a href="%s" target="_parent">%s</a>',
     265                                self_admin_url( 'theme-install.php' ),
     266                                __( 'Cancel and go back' )
     267                        ),
     268                );
     269
     270                $can_update = true;
     271
     272                if ( ! empty( $this->upgrader->new_theme_data['RequiresPHP'] ) && version_compare( phpversion(), $this->upgrader->new_theme_data['RequiresPHP'], '<' ) ) {
     273                        $error = sprintf(
     274                                /* translators: 1: Current PHP version, 2: Version required by the uploaded theme. */
     275                                __( 'The theme cannot be updated. The PHP version on your server is %1$s, however the uploaded theme requires %2$s.' ),
     276                                phpversion(),
     277                                $this->upgrader->new_theme_data['RequiresPHP']
     278                        );
     279
     280                        $this->feedback( '<div class="notice notice-error inline"><p>' . esc_html( $error ) . '</p></div>' );
     281                        $can_update = false;
     282                }
     283
     284                if ( ! empty( $this->upgrader->new_theme_data['RequiresWP'] ) && version_compare( $GLOBALS['wp_version'], $this->upgrader->new_theme_data['RequiresWP'], '<' ) ) {
     285                        $error = sprintf(
     286                                /* translators: 1: Current WordPress version, 2: Version required by the uploaded theme. */
     287                                __( 'The theme cannot be updated. Your WordPress version is %1$s, however the uploaded theme requires %2$s.' ),
     288                                $GLOBALS['wp_version'],
     289                                $this->upgrader->new_theme_data['RequiresWP']
     290                        );
     291
     292                        $this->feedback( '<div class="notice notice-error inline"><p>' . esc_html( $error ) . '</p></div>' );
     293                        $can_update = false;
     294                }
     295
     296                if ( $can_update ) {
     297                        $overwrite = 'update-theme';
     298                        $label     = __( 'Remove current and install the uploaded version' );
     299
     300                        if ( $this->is_downgrading ) {
     301                                $overwrite = 'downgrade-theme';
     302
     303                                if ( isset( $this->upgrader->new_theme_data['Version'] ) ) {
     304                                        $label = sprintf( __( 'Remove current and install version %s' ), $this->upgrader->new_theme_data['Version'] );
     305                                }
     306                        }
     307
     308                        $install_actions['ovewrite_theme'] = sprintf(
     309                                '<a class="ovewrite-uploaded-theme" href="%s" target="_parent">%s</a>',
     310                                wp_nonce_url( add_query_arg( 'overwrite', $overwrite, $this->url ), 'theme-upload' ),
     311                                esc_html( $label )
     312                        );
     313
     314                        $warning = __( '<strong>Important:</strong> Before updating, please <a href="https://wordpress.org/support/article/wordpress-backups/">back up your database and files</a>.' );
     315                        $this->feedback( '<div class="notice notice-warning inline"><p>' . $warning . '</p></div>' );
     316                }
     317
     318                /**
     319                 * Filters the list of action links available following a single theme installation failed but ovewrite is allowed.
     320                 *
     321                 * @since 5.5.0
     322                 *
     323                 * @param string[] $install_actions Array of theme action links.
     324                 * @param object   $api             Object containing WordPress.org API theme data.
     325                 * @param array    $new_theme_data  Array with uploaded theme data.
     326                 */
     327                $install_actions = apply_filters( 'install_theme_ovewrite_actions', $install_actions, $this->api, $this->upgrader->new_theme_data );
     328                if ( ! empty( $install_actions ) ) {
     329                        $this->feedback( implode( ' | ', (array) $install_actions ) );
     330                }
     331
     332                return true;
     333        }
     334
    150335}
  • 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 9d561c0893..8035d5e727 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         *
    class Theme_Upgrader extends WP_Upgrader { 
    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.' );
    class Theme_Upgrader extends WP_Upgrader { 
    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
     95                $this->strings['compare_before_overwrite'] = __( 'Theme information:' );
     96                $this->strings['reuploading_theme']        = __( 'Seems you have uploaded a theme that is already installed.' );
     97                $this->strings['current_theme_has_errors'] = __( 'The current theme has the follow error: "%s".' );
     98
     99                if ( 'update-theme' === $this->skin->overwrite ) {
     100                        $this->strings['installing_package'] = __( 'Updating the theme&#8230;' );
     101                        $this->strings['process_failed']     = __( 'Theme update failed.' );
     102                        $this->strings['process_success']    = __( 'Theme updated successfully.' );
     103                }
     104
     105                if ( 'downgrade-theme' === $this->skin->overwrite ) {
     106                        $this->strings['installing_package'] = __( 'Downgrading the theme&#8230;' );
     107                        $this->strings['process_failed']     = __( 'Theme downgrade failed.' );
     108                        $this->strings['process_success']    = __( 'Theme downgraded successfully.' );
     109                }
    82110        }
    83111
    84112        /**
    class Theme_Upgrader extends WP_Upgrader { 
    200228         * @return bool|WP_Error True if the installation was successful, false or a WP_Error object otherwise.
    201229         */
    202230        public function install( $package, $args = array() ) {
    203 
    204231                $defaults    = array(
    205232                        'clear_update_cache' => true,
     233                        'overwrite_package'  => false, // Do not overwrite files.
    206234                );
    207235                $parsed_args = wp_parse_args( $args, $defaults );
    208236
    class Theme_Upgrader extends WP_Upgrader { 
    220248                        array(
    221249                                'package'           => $package,
    222250                                'destination'       => get_theme_root(),
    223                                 'clear_destination' => false, // Do not overwrite files.
     251                                'clear_destination' => $args['overwrite_package'],
    224252                                'clear_working'     => true,
    225253                                'hook_extra'        => array(
    226254                                        'type'   => 'theme',
    class Theme_Upgrader extends WP_Upgrader { 
    240268                // Refresh the Theme Update information.
    241269                wp_clean_themes_cache( $parsed_args['clear_update_cache'] );
    242270
     271                if ( $parsed_args['overwrite_package'] ) {
     272                        /**
     273                         * Fires when the upgrader process a install forcing a package overwrite.
     274                         *
     275                         * @since 5.5.0
     276                         *
     277                         * @param string  $package          The package file.
     278                         * @param array   $new_plugin_data  The new theme data.
     279                         * @param string  $package_type     The package type (theme or theme).
     280                         */
     281                        do_action( 'upgrader_overwrited_package', $package, $this->new_theme_data, 'theme' );
     282                }
     283
    243284                return true;
    244285        }
    245286
    class Theme_Upgrader extends WP_Upgrader { 
    259300         * @return bool|WP_Error True if the upgrade was successful, false or a WP_Error object otherwise.
    260301         */
    261302        public function upgrade( $theme, $args = array() ) {
    262 
    263303                $defaults    = array(
    264304                        'clear_update_cache' => true,
    265305                );
    class Theme_Upgrader extends WP_Upgrader { 
    332372         * @return array[]|false An array of results, or false if unable to connect to the filesystem.
    333373         */
    334374        public function bulk_upgrade( $themes, $args = array() ) {
    335 
    336375                $defaults    = array(
    337376                        'clear_update_cache' => true,
    338377                );
    class Theme_Upgrader extends WP_Upgrader { 
    461500        public function check_package( $source ) {
    462501                global $wp_filesystem;
    463502
     503                $this->new_theme_data = array();
     504
    464505                if ( is_wp_error( $source ) ) {
    465506                        return $source;
    466507                }
    class Theme_Upgrader extends WP_Upgrader { 
    484525                        );
    485526                }
    486527
     528                // All these headers are needed on Theme_Installer_Skin::do_overwrite().
    487529                $info = get_file_data(
    488530                        $working_directory . 'style.css',
    489531                        array(
    490                                 'Name'     => 'Theme Name',
    491                                 'Template' => 'Template',
     532                                'Name'        => 'Theme Name',
     533                                'Version'     => 'Version',
     534                                'Author'      => 'Author',
     535                                'Template'    => 'Template',
     536                                'RequiresWP'  => 'Requires at least',
     537                                'RequiresPHP' => 'Requires PHP',
    492538                        )
    493539                );
    494540
    class Theme_Upgrader extends WP_Upgrader { 
    517563                        );
    518564                }
    519565
     566                $this->new_theme_data = $info;
    520567                return $source;
    521568        }
    522569
    class Theme_Upgrader extends WP_Upgrader { 
    640687         *                        and the last result isn't set.
    641688         */
    642689        public function theme_info( $theme = null ) {
    643 
    644690                if ( empty( $theme ) ) {
    645691                        if ( ! empty( $this->result['destination_name'] ) ) {
    646692                                $theme = $this->result['destination_name'];
    class Theme_Upgrader extends WP_Upgrader { 
    648694                                return false;
    649695                        }
    650696                }
    651                 return wp_get_theme( $theme );
     697
     698                $theme = wp_get_theme( $theme );
     699                $theme->cache_delete();
     700
     701                return $theme;
    652702        }
    653703
    654704}
  • 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 9f90f84cb5..c124ae9379 100644
    a b function install_plugins_upload() { 
    351351        <form method="post" enctype="multipart/form-data" class="wp-upload-form" action="<?php echo self_admin_url( 'update.php?action=upload-plugin' ); ?>">
    352352                <?php wp_nonce_field( 'plugin-upload' ); ?>
    353353                <label class="screen-reader-text" for="pluginzip"><?php _e( 'Plugin zip file' ); ?></label>
    354                 <input type="file" id="pluginzip" name="pluginzip" />
     354                <input type="file" id="pluginzip" name="pluginzip" accept=".zip" />
    355355                <?php submit_button( __( 'Install Now' ), '', 'install-plugin-submit', false ); ?>
    356356        </form>
    357357</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 33d14e232e..0eff84e99f 100644
    a b if ( isset( $_GET['action'] ) ) { 
    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, [ 'overwrite_package' => $overwrite ] );
    167170
    168171                if ( $result || is_wp_error( $result ) ) {
    169172                        $file_upload->cleanup();
    if ( isset( $_GET['action'] ) ) { 
    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, [ 'overwrite_package' => $overwrite ] );
    287293
    288294                if ( $result || is_wp_error( $result ) ) {
    289295                        $file_upload->cleanup();