Make WordPress Core

Changeset 48390


Ignore:
Timestamp:
07/07/2020 05:47:37 PM (6 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.