: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
// Prevent credentials auth screen from displaying multiple times.
if ( false === $result ) {
} // End foreach $themes.
$this->maintenance_mode( false );
// 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 */
'upgrader_process_complete',
$this->skin->bulk_footer();
// Cleanup our hooks, in case something else does an upgrade on this connection.
remove_filter( 'upgrader_pre_install', array( $this, 'current_before' ) );
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 ] ) ) {
unset( $past_failure_emails[ $theme ] );
update_option( 'auto_plugin_theme_update_emails', $past_failure_emails );
* Checks that the package source contains a valid theme.
* Hooked to the {@see 'upgrader_source_selection'} filter by Theme_Upgrader::install().
* @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
* @global string $wp_version The WordPress version string.
* @param string $source The path to the downloaded package source.
* @return string|WP_Error The source as passed, or a WP_Error object on failure.
public function check_package( $source ) {
global $wp_filesystem, $wp_version;
$this->new_theme_data = array();
if ( is_wp_error( $source ) ) {
// 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 ) ) { // Confidence check, if the above fails, let's not prevent installation.
// A proper archive should have a style.css file in the single subdirectory.
if ( ! file_exists( $working_directory . 'style.css' ) ) {
'incompatible_archive_theme_no_style',
$this->strings['incompatible_archive'],
/* translators: %s: style.css */
__( 'The theme is missing the %s stylesheet.' ),
// All these headers are needed on Theme_Installer_Skin::do_overwrite().
$working_directory . 'style.css',
'Template' => 'Template',
'RequiresWP' => 'Requires at least',
'RequiresPHP' => 'Requires PHP',
if ( empty( $info['Name'] ) ) {
'incompatible_archive_theme_no_name',
$this->strings['incompatible_archive'],
/* translators: %s: style.css */
__( 'The %s stylesheet does not contain a valid theme header.' ),
* Parent themes must contain an index file:
* - classic themes require /index.php
* - block themes require /templates/index.html or block-templates/index.html (deprecated 5.9.0).
empty( $info['Template'] ) &&
! file_exists( $working_directory . 'index.php' ) &&
! file_exists( $working_directory . 'templates/index.html' ) &&
! file_exists( $working_directory . 'block-templates/index.html' )
'incompatible_archive_theme_no_index',
$this->strings['incompatible_archive'],
/* translators: 1: templates/index.html, 2: index.php, 3: Documentation URL, 4: Template, 5: style.css */
__( 'Template is missing. Standalone themes need to have a %1$s or %2$s template file. <a href="%3$s">Child themes</a> need to have a %4$s header in the %5$s stylesheet.' ),
'<code>templates/index.html</code>',
'<code>index.php</code>',
__( 'https://developer.wordpress.org/themes/advanced-topics/child-themes/' ),
$requires_php = isset( $info['RequiresPHP'] ) ? $info['RequiresPHP'] : null;
$requires_wp = isset( $info['RequiresWP'] ) ? $info['RequiresWP'] : null;
if ( ! is_php_version_compatible( $requires_php ) ) {
/* 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.' ),
return new WP_Error( 'incompatible_php_required_version', $this->strings['incompatible_archive'], $error );
if ( ! is_wp_version_compatible( $requires_wp ) ) {
/* 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.' ),
return new WP_Error( 'incompatible_wp_required_version', $this->strings['incompatible_archive'], $error );
$this->new_theme_data = $info;
* Turns on maintenance mode before attempting to upgrade the active theme.
* Hooked to the {@see 'upgrader_pre_install'} filter by Theme_Upgrader::upgrade() and
* Theme_Upgrader::bulk_upgrade().
* @param bool|WP_Error $response The installation response before the installation has started.
* @param array $theme Theme arguments.
* @return bool|WP_Error The original `$response` parameter or WP_Error.
public function current_before( $response, $theme ) {
if ( is_wp_error( $response ) ) {
$theme = isset( $theme['theme'] ) ? $theme['theme'] : '';
// Only run if active theme.
if ( get_stylesheet() !== $theme ) {
// Change to maintenance mode. Bulk edit handles this separately.
$this->maintenance_mode( true );
* Turns off maintenance mode after upgrading the active theme.
* Hooked to the {@see 'upgrader_post_install'} filter by Theme_Upgrader::upgrade()
* and Theme_Upgrader::bulk_upgrade().
* @param bool|WP_Error $response The installation response after the installation has finished.
* @param array $theme Theme arguments.
* @return bool|WP_Error The original `$response` parameter or WP_Error.
public function current_after( $response, $theme ) {
if ( is_wp_error( $response ) ) {
$theme = isset( $theme['theme'] ) ? $theme['theme'] : '';
// Only run if active theme.
if ( get_stylesheet() !== $theme ) {
// Ensure stylesheet name hasn't changed after the upgrade:
if ( get_stylesheet() === $theme && $theme !== $this->result['destination_name'] ) {
$stylesheet = $this->result['destination_name'];
switch_theme( $stylesheet );
// Time to remove maintenance mode. Bulk edit handles this separately.
$this->maintenance_mode( false );
* Deletes the old theme during an upgrade.
* Hooked to the {@see 'upgrader_clear_destination'} filter by Theme_Upgrader::upgrade()
* and Theme_Upgrader::bulk_upgrade().
* @global WP_Filesystem_Base $wp_filesystem Subclass
* @param string $local_destination
* @param string $remote_destination
public function delete_old_theme( $removed, $local_destination, $remote_destination, $theme ) {
if ( is_wp_error( $removed ) ) {
return $removed; // Pass errors through.
if ( ! isset( $theme['theme'] ) ) {
$theme = $theme['theme'];
$themes_dir = trailingslashit( $wp_filesystem->wp_themes_dir( $theme ) );
if ( $wp_filesystem->exists( $themes_dir . $theme ) ) {
if ( ! $wp_filesystem->delete( $themes_dir . $theme, true ) ) {
* Gets the WP_Theme object for a theme.
* @since 3.0.0 The `$theme` argument was added.
* @param string $theme The directory name of the theme. This is optional, and if not supplied,
* the directory name from the last result will be used.
* @return WP_Theme|false The theme's info object, or false `$theme` is not supplied
* and the last result isn't set.
public function theme_info( $theme = null ) {
if ( ! empty( $this->result['destination_name'] ) ) {
$theme = $this->result['destination_name'];
$theme = wp_get_theme( $theme );