Make WordPress Core

Changeset 48390


Ignore:
Timestamp:
07/07/2020 05:47:37 PM (4 years ago)
Author:
azaozz
Message:

Upgrade/install: Allow plugin and theme updates from a uploaded .zip file.

Props mariovalney, cyberhobo, imath, shaunandrews, mariovalney, earnjam, desrosj, dd32, folletto, swissspidy, melchoyce, pento, joshuawold, psykro, clorith, ahortin, galbaras, pingram3541, joyously, doobeedoo, karmatosed, poena, whyisjake, earnjam, sergeybiryukov, audrasjb, azaozz.

Fixes #9757.

Location:
trunk/src/wp-admin
Files:
10 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-admin/css/themes.css

    r48143 r48390  
    985985------------------------------------------------------------------------------*/
    986986
     987.update-php .wrap {
     988    max-width: 40rem;
     989}
     990
    987991/* Already installed theme */
    988992.theme-browser .theme .theme-installed {
    989993    background: #0073aa;
    990994}
     995
    991996.theme-browser .theme .notice-success p:before {
    992997    color: #79ba49;
     
    10311036    position: relative;
    10321037    top: 10px;
    1033 }
    1034 
    1035 .upload-plugin-wrap {
    1036     display: none;
     1038    text-align: center;
    10371039}
    10381040
     
    10501052    padding: 30px;
    10511053    margin: 30px auto;
    1052     max-width: 380px;
    1053     display: flex;
     1054    display: inline-flex;
    10541055    justify-content: space-between;
    10551056    align-items: center;
     1057}
     1058
     1059.upload-theme .wp-upload-form input[type="file"],
     1060.upload-plugin .wp-upload-form input[type="file"] {
     1061    margin-right: 10px;
    10561062}
    10571063
     
    10941100        font-size: 15px;
    10951101        padding: 20px 0 0;
    1096         text-align: left;
    10971102    }
    10981103}
     
    11151120    display: block;
    11161121    line-height: 1.9;
     1122}
     1123
     1124.update-from-upload-comparison {
     1125    border-top: 1px solid #ddd;
     1126    border-bottom: 1px solid #ddd;
     1127    text-align: left;
     1128    margin: 1rem 0 1.4rem;
     1129    border-collapse: collapse;
     1130    width: 100%;
     1131}
     1132
     1133.update-from-upload-comparison tr:last-child td {
     1134    height: 1.4rem;
     1135    vertical-align: top;
     1136}
     1137
     1138.update-from-upload-comparison tr:first-child th {
     1139    font-weight: bold;
     1140    height: 1.4rem;
     1141    vertical-align: bottom;
     1142}
     1143
     1144.update-from-upload-comparison td.name-label {
     1145    text-align: right;
     1146}
     1147
     1148.update-from-upload-comparison td,
     1149.update-from-upload-comparison th {
     1150    padding: 0.4rem 1.4rem;
     1151}
     1152
     1153.update-from-upload-comparison td.warning {
     1154    color: #a00;
     1155}
     1156
     1157.update-from-upload-actions {
     1158    margin-top: 1.4rem;
    11171159}
    11181160
  • trunk/src/wp-admin/includes/class-plugin-installer-skin.php

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

    r47814 r48390  
    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 = array();
     50
     51    /**
    4252     * Initialize the upgrade strings.
    4353     *
     
    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
     
    6881        $this->strings['unpack_package']      = __( 'Unpacking the package&#8230;' );
    6982        $this->strings['installing_package']  = __( 'Installing the plugin&#8230;' );
     83        $this->strings['remove_old']          = __( 'Removing the current plugin&#8230;' );
     84        $this->strings['remove_old_failed']   = __( 'Could not remove the current plugin.' );
    7085        $this->strings['no_files']            = __( 'The plugin contains no files.' );
    7186        $this->strings['process_failed']      = __( 'Plugin installation failed.' );
    7287        $this->strings['process_success']     = __( 'Plugin installed successfully.' );
     88
     89        if ( ! empty( $this->skin->overwrite ) ) {
     90            if ( 'update-plugin' === $this->skin->overwrite ) {
     91                $this->strings['installing_package'] = __( 'Updating the plugin&#8230;' );
     92                $this->strings['process_failed']     = __( 'Plugin update failed.' );
     93                $this->strings['process_success']    = __( 'Plugin updated successfully.' );
     94            }
     95
     96            if ( 'downgrade-plugin' === $this->skin->overwrite ) {
     97                $this->strings['installing_package'] = __( 'Downgrading the plugin&#8230;' );
     98                $this->strings['process_failed']     = __( 'Plugin downgrade failed.' );
     99                $this->strings['process_success']    = __( 'Plugin downgraded successfully.' );
     100            }
     101        }
    73102    }
    74103
     
    89118     */
    90119    public function install( $package, $args = array() ) {
    91 
    92120        $defaults    = array(
    93121            'clear_update_cache' => true,
     122            'overwrite_package'  => false, // Do not overwrite files.
    94123        );
    95124        $parsed_args = wp_parse_args( $args, $defaults );
     
    108137                'package'           => $package,
    109138                'destination'       => WP_PLUGIN_DIR,
    110                 'clear_destination' => false, // Do not overwrite files.
     139                'clear_destination' => $parsed_args['overwrite_package'],
    111140                'clear_working'     => true,
    112141                'hook_extra'        => array(
     
    127156        wp_clean_plugins_cache( $parsed_args['clear_update_cache'] );
    128157
     158        if ( $parsed_args['overwrite_package'] ) {
     159            /**
     160             * Fires when the upgrader has successfully overwritten a currently installed
     161             * plugin or theme with an uploaded zip package.
     162             *
     163             * @since 5.5.0
     164             *
     165             * @param string  $package          The package file.
     166             * @param array   $new_plugin_data  The new plugin data.
     167             * @param string  $package_type     The package type (plugin or theme).
     168             */
     169            do_action( 'upgrader_overwrote_package', $package, $this->new_plugin_data, 'plugin' );
     170        }
     171
    129172        return true;
    130173    }
     
    146189     */
    147190    public function upgrade( $plugin, $args = array() ) {
    148 
    149191        $defaults    = array(
    150192            'clear_update_cache' => true,
     
    224266     */
    225267    public function bulk_upgrade( $plugins, $args = array() ) {
    226 
    227268        $defaults    = array(
    228269            'clear_update_cache' => true,
     
    350391        global $wp_filesystem;
    351392
     393        $this->new_plugin_data = array();
     394
    352395        if ( is_wp_error( $source ) ) {
    353396            return $source;
     
    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                }
     
    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        }
  • trunk/src/wp-admin/includes/class-theme-installer-skin.php

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

    r47814 r48390  
    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     *
     
    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.' );
     
    8092        /* translators: %s: Theme name. */
    8193        $this->strings['parent_theme_not_found'] = sprintf( __( '<strong>The parent theme could not be found.</strong> You will need to install the parent theme, %s, before you can use this child theme.' ), '<strong>%s</strong>' );
     94        /* translators: %s: Theme error. */
     95        $this->strings['current_theme_has_errors'] = __( 'The current theme has the following error: "%s".' );
     96
     97        if ( 'update-theme' === $this->skin->overwrite ) {
     98            $this->strings['installing_package'] = __( 'Updating the theme&#8230;' );
     99            $this->strings['process_failed']     = __( 'Theme update failed.' );
     100            $this->strings['process_success']    = __( 'Theme updated successfully.' );
     101        }
     102
     103        if ( 'downgrade-theme' === $this->skin->overwrite ) {
     104            $this->strings['installing_package'] = __( 'Downgrading the theme&#8230;' );
     105            $this->strings['process_failed']     = __( 'Theme downgrade failed.' );
     106            $this->strings['process_success']    = __( 'Theme downgraded successfully.' );
     107        }
    82108    }
    83109
     
    201227     */
    202228    public function install( $package, $args = array() ) {
    203 
    204229        $defaults    = array(
    205230            'clear_update_cache' => true,
     231            'overwrite_package'  => false, // Do not overwrite files.
    206232        );
    207233        $parsed_args = wp_parse_args( $args, $defaults );
     
    221247                'package'           => $package,
    222248                'destination'       => get_theme_root(),
    223                 'clear_destination' => false, // Do not overwrite files.
     249                'clear_destination' => $args['overwrite_package'],
    224250                'clear_working'     => true,
    225251                'hook_extra'        => array(
     
    241267        wp_clean_themes_cache( $parsed_args['clear_update_cache'] );
    242268
     269        if ( $parsed_args['overwrite_package'] ) {
     270            /**
     271             * Fires when the upgrader has successfully overwritten a currently installed
     272             * plugin or theme with an uploaded zip package.
     273             *
     274             * @since 5.5.0
     275             *
     276             * @param string  $package          The package file.
     277             * @param array   $new_plugin_data  The new theme data.
     278             * @param string  $package_type     The package type (plugin or theme).
     279             */
     280            do_action( 'upgrader_overwrote_package', $package, $this->new_theme_data, 'theme' );
     281        }
     282
    243283        return true;
    244284    }
     
    260300     */
    261301    public function upgrade( $theme, $args = array() ) {
    262 
    263302        $defaults    = array(
    264303            'clear_update_cache' => true,
     
    333372     */
    334373    public function bulk_upgrade( $themes, $args = array() ) {
    335 
    336374        $defaults    = array(
    337375            'clear_update_cache' => true,
     
    462500        global $wp_filesystem;
    463501
     502        $this->new_theme_data = array();
     503
    464504        if ( is_wp_error( $source ) ) {
    465505            return $source;
     
    485525        }
    486526
     527        // All these headers are needed on Theme_Installer_Skin::do_overwrite().
    487528        $info = get_file_data(
    488529            $working_directory . 'style.css',
    489530            array(
    490                 'Name'     => 'Theme Name',
    491                 'Template' => 'Template',
     531                'Name'        => 'Theme Name',
     532                'Version'     => 'Version',
     533                'Author'      => 'Author',
     534                'Template'    => 'Template',
     535                'RequiresWP'  => 'Requires at least',
     536                'RequiresPHP' => 'Requires PHP',
    492537            )
    493538        );
     
    518563        }
    519564
     565        $this->new_theme_data = $info;
    520566        return $source;
    521567    }
     
    641687     */
    642688    public function theme_info( $theme = null ) {
    643 
    644689        if ( empty( $theme ) ) {
    645690            if ( ! empty( $this->result['destination_name'] ) ) {
     
    649694            }
    650695        }
    651         return wp_get_theme( $theme );
     696
     697        $theme = wp_get_theme( $theme );
     698        $theme->cache_delete();
     699
     700        return $theme;
    652701    }
    653702
  • trunk/src/wp-admin/includes/class-wp-upgrader-skin.php

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

    r48110 r48390  
    799799        if ( is_wp_error( $result ) ) {
    800800            $this->skin->error( $result );
    801             $this->skin->feedback( 'process_failed' );
     801
     802            if ( ! method_exists( $this->skin, 'hide_process_failed' ) || ! $this->skin->hide_process_failed( $result ) ) {
     803                $this->skin->feedback( 'process_failed' );
     804            }
    802805        } else {
    803806            // Installation succeeded.
  • trunk/src/wp-admin/includes/plugin-install.php

    r48281 r48390  
    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>
  • trunk/src/wp-admin/includes/theme-install.php

    r45583 r48390  
    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>
  • trunk/src/wp-admin/update.php

    r47808 r48390  
    158158
    159159        /* translators: %s: File name. */
    160         $title = sprintf( __( 'Installing Plugin from uploaded file: %s' ), esc_html( basename( $file_upload->filename ) ) );
     160        $title = sprintf( __( 'Installing plugin from uploaded file: %s' ), esc_html( basename( $file_upload->filename ) ) );
    161161        $nonce = 'plugin-upload';
    162162        $url   = add_query_arg( array( 'package' => $file_upload->id ), 'update.php?action=upload-plugin' );
    163163        $type  = 'upload'; // Install plugin type, From Web or an Upload.
    164164
    165         $upgrader = new Plugin_Upgrader( new Plugin_Installer_Skin( compact( 'type', 'title', 'nonce', 'url' ) ) );
    166         $result   = $upgrader->install( $file_upload->package );
     165        $overwrite = isset( $_GET['overwrite'] ) ? sanitize_text_field( $_GET['overwrite'] ) : '';
     166        $overwrite = in_array( $overwrite, array( 'update-plugin', 'downgrade-plugin' ), true ) ? $overwrite : '';
     167
     168        $upgrader = new Plugin_Upgrader( new Plugin_Installer_Skin( compact( 'type', 'title', 'nonce', 'url', 'overwrite' ) ) );
     169        $result   = $upgrader->install( $file_upload->package, array( 'overwrite_package' => $overwrite ) );
    167170
    168171        if ( $result || is_wp_error( $result ) ) {
     
    278281
    279282        /* translators: %s: File name. */
    280         $title = sprintf( __( 'Installing Theme from uploaded file: %s' ), esc_html( basename( $file_upload->filename ) ) );
     283        $title = sprintf( __( 'Installing theme from uploaded file: %s' ), esc_html( basename( $file_upload->filename ) ) );
    281284        $nonce = 'theme-upload';
    282285        $url   = add_query_arg( array( 'package' => $file_upload->id ), 'update.php?action=upload-theme' );
    283286        $type  = 'upload'; // Install theme type, From Web or an Upload.
    284287
    285         $upgrader = new Theme_Upgrader( new Theme_Installer_Skin( compact( 'type', 'title', 'nonce', 'url' ) ) );
    286         $result   = $upgrader->install( $file_upload->package );
     288        $overwrite = isset( $_GET['overwrite'] ) ? sanitize_text_field( $_GET['overwrite'] ) : '';
     289        $overwrite = in_array( $overwrite, array( 'update-theme', 'downgrade-theme' ), true ) ? $overwrite : '';
     290
     291        $upgrader = new Theme_Upgrader( new Theme_Installer_Skin( compact( 'type', 'title', 'nonce', 'url', 'overwrite' ) ) );
     292        $result   = $upgrader->install( $file_upload->package, array( 'overwrite_package' => $overwrite ) );
    287293
    288294        if ( $result || is_wp_error( $result ) ) {
Note: See TracChangeset for help on using the changeset viewer.