Make WordPress Core

Ticket #9757: 9757.4.patch

File 9757.4.patch, 32.4 KB (added by azaozz, 4 years ago)
  • src/wp-admin/css/themes.css

     
    10301030        overflow: hidden;
    10311031        position: relative;
    10321032        top: 10px;
     1033        text-align: center;
    10331034}
    10341035
    1035 .upload-plugin-wrap {
    1036         display: none;
    1037 }
    1038 
    10391036.show-upload-view .upload-theme,
    10401037.show-upload-view .upload-plugin,
    10411038.show-upload-view .upload-plugin-wrap,
     
    10491046        border: 1px solid #ccd0d4;
    10501047        padding: 30px;
    10511048        margin: 30px auto;
    1052         max-width: 380px;
    1053         display: flex;
     1049        display: inline-flex;
    10541050        justify-content: space-between;
    10551051        align-items: center;
    10561052}
    10571053
     1054.upload-theme .wp-upload-form input[type="file"],
     1055.upload-plugin .wp-upload-form input[type="file"] {
     1056        margin-right: 10px;
     1057}
     1058
    10581059.upload-theme .install-help,
    10591060.upload-plugin .install-help {
    10601061        color: #555d66; /* #f1f1f1 background */
     
    10931094        .upload-theme .install-help {
    10941095                font-size: 15px;
    10951096                padding: 20px 0 0;
    1096                 text-align: left;
    10971097        }
    10981098}
    10991099
     
    11161116        line-height: 1.9;
    11171117}
    11181118
     1119.compare-themes-table,
     1120.compare-plugins-table {
     1121        text-align: left;
     1122        margin: 1em 0 2em;
     1123        border-collapse: collapse;
     1124}
     1125
     1126.compare-themes-table th,
     1127.compare-plugins-table th {
     1128        font-weight: bold;
     1129        padding: 3px 10px;
     1130}
     1131
     1132.compare-themes-table td,
     1133.compare-plugins-table td {
     1134        padding: 3px 10px;
     1135}
     1136
     1137.compare-themes-table tr.warning,
     1138.compare-plugins-table tr.warning,
     1139a.ovewrite-uploaded-theme {
     1140        color: #a00;
     1141}
     1142
     1143a.ovewrite-uploaded-plugin {
     1144        color: #a00;
     1145        margin-left: 10px;
     1146}
     1147
     1148
     1149a.ovewrite-uploaded-theme:hover,
     1150a.ovewrite-uploaded-plugin:hover {
     1151        color: #d80707;
     1152}
     1153
    11191154/*------------------------------------------------------------------------------
    11201155  16.3 - Custom Header Screen
    11211156------------------------------------------------------------------------------*/
  • src/wp-admin/includes/class-plugin-installer-skin.php

     
    1818class Plugin_Installer_Skin extends WP_Upgrader_Skin {
    1919        public $api;
    2020        public $type;
     21        public $url;
     22        public $overwrite;
    2123
     24        private $is_downgrading = false;
     25
    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        }
     
    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                        );
     
    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();
     
    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
     
    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

     
    3939        public $bulk = false;
    4040
    4141        /**
     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
     51        /**
    4252         * Initialize the upgrade strings.
    4353         *
    4454         * @since 2.8.0
     
    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        /**
     
    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        /**
     
    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
     
    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',
     
    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
     
    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                );
     
    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                );
     
    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                }
     
    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

     
    1818class Theme_Installer_Skin extends WP_Upgrader_Skin {
    1919        public $api;
    2020        public $type;
     21        public $url;
     22        public $overwrite;
    2123
     24        private $is_downgrading = false;
     25
    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        }
     
    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                }
     
    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                /**
     
    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

     
    3838        public $bulk = false;
    3939
    4040        /**
     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
     50        /**
    4151         * Initialize the upgrade strings.
    4252         *
    4353         * @since 2.8.0
     
    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.' );
     
    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        /**
     
    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
     
    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',
     
    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
     
    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                );
     
    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                );
     
    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                }
     
    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
     
    517563                        );
    518564                }
    519565
     566                $this->new_theme_data = $info;
    520567                return $source;
    521568        }
    522569
     
    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'];
     
    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

     
    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

     
    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

     
    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 : '';
    167167
     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 ] );
     170
    168171                if ( $result || is_wp_error( $result ) ) {
    169172                        $file_upload->cleanup();
    170173                }
     
    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 : '';
    287290
     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 ] );
     293
    288294                if ( $result || is_wp_error( $result ) ) {
    289295                        $file_upload->cleanup();
    290296                }