diff -r 3d4e9c994f10 -r a86126ab1dd4 wp/wp-admin/includes/class-theme-upgrader.php --- a/wp/wp-admin/includes/class-theme-upgrader.php Tue Oct 22 16:11:46 2019 +0200 +++ b/wp/wp-admin/includes/class-theme-upgrader.php Tue Dec 15 13:49:49 2020 +0100 @@ -38,6 +38,16 @@ public $bulk = false; /** + * New theme info. + * + * @since 5.5.0 + * @var array $new_theme_data + * + * @see check_package() + */ + public $new_theme_data = array(); + + /** * Initialize the upgrade strings. * * @since 2.8.0 @@ -45,7 +55,7 @@ public function upgrade_strings() { $this->strings['up_to_date'] = __( 'The theme is at the latest version.' ); $this->strings['no_package'] = __( 'Update package not available.' ); - /* translators: %s: package URL */ + /* translators: %s: Package URL. */ $this->strings['downloading_package'] = sprintf( __( 'Downloading update from %s…' ), '%s' ); $this->strings['unpack_package'] = __( 'Unpacking the update…' ); $this->strings['remove_old'] = __( 'Removing the old version of the theme…' ); @@ -61,24 +71,42 @@ */ public function install_strings() { $this->strings['no_package'] = __( 'Installation package not available.' ); - /* translators: %s: package URL */ + /* translators: %s: Package URL. */ $this->strings['downloading_package'] = sprintf( __( 'Downloading installation package from %s…' ), '%s' ); $this->strings['unpack_package'] = __( 'Unpacking the package…' ); $this->strings['installing_package'] = __( 'Installing the theme…' ); + $this->strings['remove_old'] = __( 'Removing the old version of the theme…' ); + $this->strings['remove_old_failed'] = __( 'Could not remove the old theme.' ); $this->strings['no_files'] = __( 'The theme contains no files.' ); $this->strings['process_failed'] = __( 'Theme installation failed.' ); $this->strings['process_success'] = __( 'Theme installed successfully.' ); - /* translators: 1: theme name, 2: version */ + /* translators: 1: Theme name, 2: Theme version. */ $this->strings['process_success_specific'] = __( 'Successfully installed the theme %1$s %2$s.' ); $this->strings['parent_theme_search'] = __( 'This theme requires a parent theme. Checking if it is installed…' ); - /* translators: 1: theme name, 2: version */ + /* translators: 1: Theme name, 2: Theme version. */ $this->strings['parent_theme_prepare_install'] = __( 'Preparing to install %1$s %2$s…' ); - /* translators: 1: theme name, 2: version */ + /* translators: 1: Theme name, 2: Theme version. */ $this->strings['parent_theme_currently_installed'] = __( 'The parent theme, %1$s %2$s, is currently installed.' ); - /* translators: 1: theme name, 2: version */ + /* translators: 1: Theme name, 2: Theme version. */ $this->strings['parent_theme_install_success'] = __( 'Successfully installed the parent theme, %1$s %2$s.' ); - /* translators: %s: theme name */ + /* translators: %s: Theme name. */ $this->strings['parent_theme_not_found'] = sprintf( __( 'The parent theme could not be found. You will need to install the parent theme, %s, before you can use this child theme.' ), '%s' ); + /* translators: %s: Theme error. */ + $this->strings['current_theme_has_errors'] = __( 'The current theme has the following error: "%s".' ); + + if ( ! empty( $this->skin->overwrite ) ) { + if ( 'update-theme' === $this->skin->overwrite ) { + $this->strings['installing_package'] = __( 'Updating the theme…' ); + $this->strings['process_failed'] = __( 'Theme update failed.' ); + $this->strings['process_success'] = __( 'Theme updated successfully.' ); + } + + if ( 'downgrade-theme' === $this->skin->overwrite ) { + $this->strings['installing_package'] = __( 'Downgrading the theme…' ); + $this->strings['process_failed'] = __( 'Theme downgrade failed.' ); + $this->strings['process_success'] = __( 'Theme downgraded successfully.' ); + } + } } /** @@ -91,10 +119,10 @@ * @param bool $install_result * @param array $hook_extra * @param array $child_result - * @return type + * @return bool */ public function check_parent_theme_filter( $install_result, $hook_extra, $child_result ) { - // Check to see if we need to install a parent theme + // Check to see if we need to install a parent theme. $theme_info = $this->theme_info(); if ( ! $theme_info->parent() ) { @@ -119,11 +147,11 @@ 'tags' => false, ), ) - ); //Save on a bit of bandwidth. + ); // Save on a bit of bandwidth. if ( ! $api || is_wp_error( $api ) ) { $this->skin->feedback( 'parent_theme_not_found', $theme_info->get( 'Template' ) ); - // Don't show activate or preview actions after installation + // Don't show activate or preview actions after installation. add_filter( 'install_theme_complete_actions', array( $this, 'hide_activate_preview_actions' ) ); return $install_result; } @@ -132,20 +160,21 @@ $child_api = $this->skin->api; $child_success_message = $this->strings['process_success']; - // Override them - $this->skin->api = $api; - $this->strings['process_success_specific'] = $this->strings['parent_theme_install_success'];//, $api->name, $api->version); + // Override them. + $this->skin->api = $api; + + $this->strings['process_success_specific'] = $this->strings['parent_theme_install_success']; $this->skin->feedback( 'parent_theme_prepare_install', $api->name, $api->version ); add_filter( 'install_theme_complete_actions', '__return_false', 999 ); // Don't show any actions after installing the theme. - // Install the parent theme + // Install the parent theme. $parent_result = $this->run( array( 'package' => $api->download_link, 'destination' => get_theme_root(), - 'clear_destination' => false, //Do not overwrite files. + 'clear_destination' => false, // Do not overwrite files. 'clear_working' => true, ) ); @@ -154,10 +183,10 @@ add_filter( 'install_theme_complete_actions', array( $this, 'hide_activate_preview_actions' ) ); } - // Start cleaning up after the parents installation + // Start cleaning up after the parent's installation. remove_filter( 'install_theme_complete_actions', '__return_false', 999 ); - // Reset child's result and data + // Reset child's result and data. $this->result = $child_result; $this->skin->api = $child_api; $this->strings['process_success'] = $child_success_message; @@ -199,9 +228,9 @@ * @return bool|WP_Error True if the installation was successful, false or a WP_Error object otherwise. */ public function install( $package, $args = array() ) { - $defaults = array( 'clear_update_cache' => true, + 'overwrite_package' => false, // Do not overwrite files. ); $parsed_args = wp_parse_args( $args, $defaults ); @@ -210,6 +239,7 @@ add_filter( 'upgrader_source_selection', array( $this, 'check_package' ) ); add_filter( 'upgrader_post_install', array( $this, 'check_parent_theme_filter' ), 10, 3 ); + if ( $parsed_args['clear_update_cache'] ) { // Clear cache so wp_update_themes() knows about the new theme. add_action( 'upgrader_process_complete', 'wp_clean_themes_cache', 9, 0 ); @@ -219,7 +249,7 @@ array( 'package' => $package, 'destination' => get_theme_root(), - 'clear_destination' => false, //Do not overwrite files. + 'clear_destination' => $parsed_args['overwrite_package'], 'clear_working' => true, 'hook_extra' => array( 'type' => 'theme', @@ -236,9 +266,23 @@ return $this->result; } - // Refresh the Theme Update information + // Refresh the Theme Update information. wp_clean_themes_cache( $parsed_args['clear_update_cache'] ); + if ( $parsed_args['overwrite_package'] ) { + /** + * Fires when the upgrader has successfully overwritten a currently installed + * plugin or theme with an uploaded zip package. + * + * @since 5.5.0 + * + * @param string $package The package file. + * @param array $new_theme_data The new theme data. + * @param string $package_type The package type (plugin or theme). + */ + do_action( 'upgrader_overwrote_package', $package, $this->new_theme_data, 'theme' ); + } + return true; } @@ -258,7 +302,6 @@ * @return bool|WP_Error True if the upgrade was successful, false or a WP_Error object otherwise. */ public function upgrade( $theme, $args = array() ) { - $defaults = array( 'clear_update_cache' => true, ); @@ -312,6 +355,15 @@ wp_clean_themes_cache( $parsed_args['clear_update_cache'] ); + // Ensure any future auto-update failures trigger a failure email by removing + // the last failure notification from the list when themes update successfully. + $past_failure_emails = get_option( 'auto_plugin_theme_update_emails', array() ); + + if ( isset( $past_failure_emails[ $theme ] ) ) { + unset( $past_failure_emails[ $theme ] ); + update_option( 'auto_plugin_theme_update_emails', $past_failure_emails ); + } + return true; } @@ -331,7 +383,6 @@ * @return array[]|false An array of results, or false if unable to connect to the filesystem. */ public function bulk_upgrade( $themes, $args = array() ) { - $defaults = array( 'clear_update_cache' => true, ); @@ -349,7 +400,7 @@ $this->skin->header(); - // Connect to the Filesystem first. + // Connect to the filesystem first. $res = $this->fs_connect( array( WP_CONTENT_DIR ) ); if ( ! $res ) { $this->skin->footer(); @@ -358,13 +409,15 @@ $this->skin->bulk_header(); - // Only start maintenance mode if: - // - running Multisite and there are one or more themes specified, OR - // - a theme with an update available is currently in use. - // @TODO: For multisite, maintenance mode should only kick in for individual sites if at all possible. + /* + * Only start maintenance mode if: + * - running Multisite and there are one or more themes specified, OR + * - a theme with an update available is currently in use. + * @todo For multisite, maintenance mode should only kick in for individual sites if at all possible. + */ $maintenance = ( is_multisite() && ! empty( $themes ) ); foreach ( $themes as $theme ) { - $maintenance = $maintenance || $theme == get_stylesheet() || $theme == get_template(); + $maintenance = $maintenance || get_stylesheet() === $theme || get_template() === $theme; } if ( $maintenance ) { $this->maintenance_mode( true ); @@ -388,7 +441,7 @@ continue; } - // Get the URL to the zip file + // Get the URL to the zip file. $r = $current->response[ $theme ]; $result = $this->run( @@ -406,15 +459,15 @@ $results[ $theme ] = $this->result; - // Prevent credentials auth screen from displaying multiple times + // Prevent credentials auth screen from displaying multiple times. if ( false === $result ) { break; } - } //end foreach $plugins + } // End foreach $themes. $this->maintenance_mode( false ); - // Refresh the Theme Update information + // Refresh the Theme Update information. wp_clean_themes_cache( $parsed_args['clear_update_cache'] ); /** This action is documented in wp-admin/includes/class-wp-upgrader.php */ @@ -438,6 +491,21 @@ remove_filter( 'upgrader_post_install', array( $this, 'current_after' ) ); remove_filter( 'upgrader_clear_destination', array( $this, 'delete_old_theme' ) ); + // Ensure any future auto-update failures trigger a failure email by removing + // the last failure notification from the list when themes update successfully. + $past_failure_emails = get_option( 'auto_plugin_theme_update_emails', array() ); + + foreach ( $results as $theme => $result ) { + // Maintain last failure notification when themes failed to update manually. + if ( ! $result || is_wp_error( $result ) || ! isset( $past_failure_emails[ $theme ] ) ) { + continue; + } + + unset( $past_failure_emails[ $theme ] ); + } + + update_option( 'auto_plugin_theme_update_emails', $past_failure_emails ); + return $results; } @@ -458,17 +526,19 @@ public function check_package( $source ) { global $wp_filesystem; + $this->new_theme_data = array(); + if ( is_wp_error( $source ) ) { return $source; } - // Check the folder contains a valid theme + // Check that the folder contains a valid theme. $working_directory = str_replace( $wp_filesystem->wp_content_dir(), trailingslashit( WP_CONTENT_DIR ), $source ); if ( ! is_dir( $working_directory ) ) { // Sanity check, if the above fails, let's not prevent installation. return $source; } - // A proper archive should have a style.css file in the single subdirectory + // A proper archive should have a style.css file in the single subdirectory. if ( ! file_exists( $working_directory . 'style.css' ) ) { return new WP_Error( 'incompatible_archive_theme_no_style', @@ -481,11 +551,16 @@ ); } + // All these headers are needed on Theme_Installer_Skin::do_overwrite(). $info = get_file_data( $working_directory . 'style.css', array( - 'Name' => 'Theme Name', - 'Template' => 'Template', + 'Name' => 'Theme Name', + 'Version' => 'Version', + 'Author' => 'Author', + 'Template' => 'Template', + 'RequiresWP' => 'Requires at least', + 'RequiresPHP' => 'Requires PHP', ) ); @@ -514,6 +589,32 @@ ); } + $requires_php = isset( $info['RequiresPHP'] ) ? $info['RequiresPHP'] : null; + $requires_wp = isset( $info['RequiresWP'] ) ? $info['RequiresWP'] : null; + + if ( ! is_php_version_compatible( $requires_php ) ) { + $error = sprintf( + /* translators: 1: Current PHP version, 2: Version required by the uploaded theme. */ + __( 'The PHP version on your server is %1$s, however the uploaded theme requires %2$s.' ), + phpversion(), + $requires_php + ); + + return new WP_Error( 'incompatible_php_required_version', $this->strings['incompatible_archive'], $error ); + } + if ( ! is_wp_version_compatible( $requires_wp ) ) { + $error = sprintf( + /* translators: 1: Current WordPress version, 2: Version required by the uploaded theme. */ + __( 'Your WordPress version is %1$s, however the uploaded theme requires %2$s.' ), + $GLOBALS['wp_version'], + $requires_wp + ); + + return new WP_Error( 'incompatible_wp_required_version', $this->strings['incompatible_archive'], $error ); + } + + $this->new_theme_data = $info; + return $source; } @@ -525,9 +626,9 @@ * * @since 2.8.0 * - * @param bool|WP_Error $return - * @param array $theme - * @return bool|WP_Error + * @param bool|WP_Error $return Upgrade offer return. + * @param array $theme Theme arguments. + * @return bool|WP_Error The passed in $return param or WP_Error. */ public function current_before( $return, $theme ) { if ( is_wp_error( $return ) ) { @@ -536,10 +637,12 @@ $theme = isset( $theme['theme'] ) ? $theme['theme'] : ''; - if ( $theme != get_stylesheet() ) { //If not current + // Only run if current theme + if ( get_stylesheet() !== $theme ) { return $return; } - //Change to maintenance mode now. + + // Change to maintenance mode. Bulk edit handles this separately. if ( ! $this->bulk ) { $this->maintenance_mode( true ); } @@ -555,9 +658,9 @@ * * @since 2.8.0 * - * @param bool|WP_Error $return - * @param array $theme - * @return bool|WP_Error + * @param bool|WP_Error $return Upgrade offer return. + * @param array $theme Theme arguments. + * @return bool|WP_Error The passed in $return param or WP_Error. */ public function current_after( $return, $theme ) { if ( is_wp_error( $return ) ) { @@ -566,18 +669,19 @@ $theme = isset( $theme['theme'] ) ? $theme['theme'] : ''; - if ( $theme != get_stylesheet() ) { // If not current + // Only run if current theme. + if ( get_stylesheet() !== $theme ) { return $return; } // Ensure stylesheet name hasn't changed after the upgrade: - if ( $theme == get_stylesheet() && $theme != $this->result['destination_name'] ) { + if ( get_stylesheet() === $theme && $theme !== $this->result['destination_name'] ) { wp_clean_themes_cache(); $stylesheet = $this->result['destination_name']; switch_theme( $stylesheet ); } - //Time to remove maintenance mode + // Time to remove maintenance mode. Bulk edit handles this separately. if ( ! $this->bulk ) { $this->maintenance_mode( false ); } @@ -634,7 +738,6 @@ * and the last result isn't set. */ public function theme_info( $theme = null ) { - if ( empty( $theme ) ) { if ( ! empty( $this->result['destination_name'] ) ) { $theme = $this->result['destination_name']; @@ -642,7 +745,11 @@ return false; } } - return wp_get_theme( $theme ); + + $theme = wp_get_theme( $theme ); + $theme->cache_delete(); + + return $theme; } }