[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

Search

title

Body

[close]

/wp-admin/includes/ -> class-theme-upgrader.php (source)

   1  <?php
   2  /**
   3   * Upgrade API: Theme_Upgrader class
   4   *
   5   * @package WordPress
   6   * @subpackage Upgrader
   7   * @since 4.6.0
   8   */
   9  
  10  /**
  11   * Core class used for upgrading/installing themes.
  12   *
  13   * It is designed to upgrade/install themes from a local zip, remote zip URL,
  14   * or uploaded zip file.
  15   *
  16   * @since 2.8.0
  17   * @since 4.6.0 Moved to its own file from wp-admin/includes/class-wp-upgrader.php.
  18   *
  19   * @see WP_Upgrader
  20   */
  21  class Theme_Upgrader extends WP_Upgrader {
  22  
  23      /**
  24       * Result of the theme upgrade offer.
  25       *
  26       * @since 2.8.0
  27       * @var array|WP_Error|null $result
  28       * @phpstan-var non-empty-array<string, mixed>|WP_Error|null
  29       * @see WP_Upgrader::$result
  30       */
  31      public $result;
  32  
  33      /**
  34       * Whether multiple themes are being upgraded/installed in bulk.
  35       *
  36       * @since 2.9.0
  37       * @var bool $bulk
  38       */
  39      public $bulk = false;
  40  
  41      /**
  42       * New theme info.
  43       *
  44       * @since 5.5.0
  45       * @var array $new_theme_data
  46       *
  47       * @see check_package()
  48       */
  49      public $new_theme_data = array();
  50  
  51      /**
  52       * Initializes the upgrade strings.
  53       *
  54       * @since 2.8.0
  55       */
  56  	public function upgrade_strings() {
  57          $this->strings['up_to_date'] = __( 'The theme is at the latest version.' );
  58          $this->strings['no_package'] = __( 'Update package not available.' );
  59          /* translators: %s: Package URL. */
  60          $this->strings['downloading_package'] = sprintf( __( 'Downloading update from %s&#8230;' ), '<span class="code pre">%s</span>' );
  61          $this->strings['unpack_package']      = __( 'Unpacking the update&#8230;' );
  62          $this->strings['remove_old']          = __( 'Removing the old version of the theme&#8230;' );
  63          $this->strings['remove_old_failed']   = __( 'Could not remove the old theme.' );
  64          $this->strings['process_failed']      = __( 'Theme update failed.' );
  65          $this->strings['process_success']     = __( 'Theme updated successfully.' );
  66      }
  67  
  68      /**
  69       * Initializes the installation strings.
  70       *
  71       * @since 2.8.0
  72       */
  73  	public function install_strings() {
  74          $this->strings['no_package'] = __( 'Installation package not available.' );
  75          /* translators: %s: Package URL. */
  76          $this->strings['downloading_package'] = sprintf( __( 'Downloading installation package from %s&#8230;' ), '<span class="code pre">%s</span>' );
  77          $this->strings['unpack_package']      = __( 'Unpacking the package&#8230;' );
  78          $this->strings['installing_package']  = __( 'Installing the theme&#8230;' );
  79          $this->strings['remove_old']          = __( 'Removing the old version of the theme&#8230;' );
  80          $this->strings['remove_old_failed']   = __( 'Could not remove the old theme.' );
  81          $this->strings['no_files']            = __( 'The theme contains no files.' );
  82          $this->strings['process_failed']      = __( 'Theme installation failed.' );
  83          $this->strings['process_success']     = __( 'Theme installed successfully.' );
  84          /* translators: 1: Theme name, 2: Theme version. */
  85          $this->strings['process_success_specific'] = __( 'Successfully installed the theme <strong>%1$s %2$s</strong>.' );
  86          $this->strings['parent_theme_search']      = __( 'This theme requires a parent theme. Checking if it is installed&#8230;' );
  87          /* translators: 1: Theme name, 2: Theme version. */
  88          $this->strings['parent_theme_prepare_install'] = __( 'Preparing to install <strong>%1$s %2$s</strong>&#8230;' );
  89          /* translators: 1: Theme name, 2: Theme version. */
  90          $this->strings['parent_theme_currently_installed'] = __( 'The parent theme, <strong>%1$s %2$s</strong>, is currently installed.' );
  91          /* translators: 1: Theme name, 2: Theme version. */
  92          $this->strings['parent_theme_install_success'] = __( 'Successfully installed the parent theme, <strong>%1$s %2$s</strong>.' );
  93          /* translators: %s: Theme name. */
  94          $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>' );
  95          /* translators: %s: Theme error. */
  96          $this->strings['current_theme_has_errors'] = __( 'The active theme has the following error: "%s".' );
  97  
  98          if ( ! empty( $this->skin->overwrite ) ) {
  99              if ( 'update-theme' === $this->skin->overwrite ) {
 100                  $this->strings['installing_package'] = __( 'Updating the theme&#8230;' );
 101                  $this->strings['process_failed']     = __( 'Theme update failed.' );
 102                  $this->strings['process_success']    = __( 'Theme updated successfully.' );
 103              }
 104  
 105              if ( 'downgrade-theme' === $this->skin->overwrite ) {
 106                  $this->strings['installing_package'] = __( 'Downgrading the theme&#8230;' );
 107                  $this->strings['process_failed']     = __( 'Theme downgrade failed.' );
 108                  $this->strings['process_success']    = __( 'Theme downgraded successfully.' );
 109              }
 110          }
 111      }
 112  
 113      /**
 114       * Checks if a child theme is being installed and its parent also needs to be installed.
 115       *
 116       * Hooked to the {@see 'upgrader_post_install'} filter by Theme_Upgrader::install().
 117       *
 118       * @since 3.4.0
 119       *
 120       * @param bool  $install_result
 121       * @param array $hook_extra
 122       * @param array $child_result
 123       * @return bool
 124       *
 125       * @phpstan-param non-empty-array<string, mixed> $child_result
 126       */
 127  	public function check_parent_theme_filter( $install_result, $hook_extra, $child_result ) {
 128          // Check to see if we need to install a parent theme.
 129          $theme_info = $this->theme_info();
 130  
 131          if ( ! $theme_info->parent() ) {
 132              return $install_result;
 133          }
 134  
 135          $this->skin->feedback( 'parent_theme_search' );
 136  
 137          if ( ! $theme_info->parent()->errors() ) {
 138              $this->skin->feedback( 'parent_theme_currently_installed', $theme_info->parent()->display( 'Name' ), $theme_info->parent()->display( 'Version' ) );
 139              // We already have the theme, fall through.
 140              return $install_result;
 141          }
 142  
 143          // We don't have the parent theme, let's install it.
 144          $api = themes_api(
 145              'theme_information',
 146              array(
 147                  'slug'   => $theme_info->get( 'Template' ),
 148                  'fields' => array(
 149                      'sections' => false,
 150                      'tags'     => false,
 151                  ),
 152              )
 153          ); // Save on a bit of bandwidth.
 154  
 155          if ( ! $api || is_wp_error( $api ) ) {
 156              $this->skin->feedback( 'parent_theme_not_found', $theme_info->get( 'Template' ) );
 157              // Don't show activate or preview actions after installation.
 158              add_filter( 'install_theme_complete_actions', array( $this, 'hide_activate_preview_actions' ) );
 159              return $install_result;
 160          }
 161  
 162          // Backup required data we're going to override:
 163          $child_api             = $this->skin->api;
 164          $child_success_message = $this->strings['process_success'];
 165  
 166          // Override them.
 167          $this->skin->api = $api;
 168  
 169          $this->strings['process_success_specific'] = $this->strings['parent_theme_install_success'];
 170  
 171          $this->skin->feedback( 'parent_theme_prepare_install', $api->name, $api->version );
 172  
 173          add_filter( 'install_theme_complete_actions', '__return_false', 999 ); // Don't show any actions after installing the theme.
 174  
 175          // Install the parent theme.
 176          $parent_result = $this->run(
 177              array(
 178                  'package'           => $api->download_link,
 179                  'destination'       => get_theme_root(),
 180                  'clear_destination' => false, // Do not overwrite files.
 181                  'clear_working'     => true,
 182              )
 183          );
 184  
 185          if ( is_wp_error( $parent_result ) ) {
 186              add_filter( 'install_theme_complete_actions', array( $this, 'hide_activate_preview_actions' ) );
 187          }
 188  
 189          // Start cleaning up after the parent's installation.
 190          remove_filter( 'install_theme_complete_actions', '__return_false', 999 );
 191  
 192          // Reset child's result and data.
 193          $this->result                     = $child_result;
 194          $this->skin->api                  = $child_api;
 195          $this->strings['process_success'] = $child_success_message;
 196  
 197          return $install_result;
 198      }
 199  
 200      /**
 201       * Don't display the activate and preview actions to the user.
 202       *
 203       * Hooked to the {@see 'install_theme_complete_actions'} filter by
 204       * Theme_Upgrader::check_parent_theme_filter() when installing
 205       * a child theme and installing the parent theme fails.
 206       *
 207       * @since 3.4.0
 208       *
 209       * @param array $actions Preview actions.
 210       * @return array
 211       */
 212  	public function hide_activate_preview_actions( $actions ) {
 213          unset( $actions['activate'], $actions['preview'] );
 214          return $actions;
 215      }
 216  
 217      /**
 218       * Install a theme package.
 219       *
 220       * @since 2.8.0
 221       * @since 3.7.0 The `$args` parameter was added, making clearing the update cache optional.
 222       *
 223       * @param string $package The full local path or URI of the package.
 224       * @param array  $args {
 225       *     Optional. Other arguments for installing a theme package. Default empty array.
 226       *
 227       *     @type bool $clear_update_cache Whether to clear the updates cache if successful.
 228       *                                    Default true.
 229       * }
 230       *
 231       * @return bool|WP_Error|null True if the installation was successful, false or a WP_Error object otherwise.
 232       *                            Null if the installation failed before the package was installed, for
 233       *                            example when the package could not be downloaded.
 234       */
 235  	public function install( $package, $args = array() ) {
 236          $defaults    = array(
 237              'clear_update_cache' => true,
 238              'overwrite_package'  => false, // Do not overwrite files.
 239          );
 240          $parsed_args = wp_parse_args( $args, $defaults );
 241  
 242          $this->init();
 243          $this->install_strings();
 244  
 245          add_filter( 'upgrader_source_selection', array( $this, 'check_package' ) );
 246          add_filter( 'upgrader_post_install', array( $this, 'check_parent_theme_filter' ), 10, 3 );
 247  
 248          if ( $parsed_args['clear_update_cache'] ) {
 249              // Clear cache so wp_update_themes() knows about the new theme.
 250              add_action( 'upgrader_process_complete', 'wp_clean_themes_cache', 9, 0 );
 251          }
 252  
 253          $this->run(
 254              array(
 255                  'package'           => $package,
 256                  'destination'       => get_theme_root(),
 257                  'clear_destination' => $parsed_args['overwrite_package'],
 258                  'clear_working'     => true,
 259                  'hook_extra'        => array(
 260                      'type'   => 'theme',
 261                      'action' => 'install',
 262                  ),
 263              )
 264          );
 265  
 266          remove_action( 'upgrader_process_complete', 'wp_clean_themes_cache', 9 );
 267          remove_filter( 'upgrader_source_selection', array( $this, 'check_package' ) );
 268          remove_filter( 'upgrader_post_install', array( $this, 'check_parent_theme_filter' ) );
 269  
 270          if ( ! $this->result || is_wp_error( $this->result ) ) {
 271              return $this->result;
 272          }
 273  
 274          // Refresh the Theme Update information.
 275          wp_clean_themes_cache( $parsed_args['clear_update_cache'] );
 276  
 277          if ( $parsed_args['overwrite_package'] ) {
 278              /** This action is documented in wp-admin/includes/class-plugin-upgrader.php */
 279              do_action( 'upgrader_overwrote_package', $package, $this->new_theme_data, 'theme' );
 280          }
 281  
 282          return true;
 283      }
 284  
 285      /**
 286       * Upgrades a theme.
 287       *
 288       * @since 2.8.0
 289       * @since 3.7.0 The `$args` parameter was added, making clearing the update cache optional.
 290       *
 291       * @param string $theme The theme slug.
 292       * @param array  $args {
 293       *     Optional. Other arguments for upgrading a theme. Default empty array.
 294       *
 295       *     @type bool $clear_update_cache Whether to clear the update cache if successful.
 296       *                                    Default true.
 297       * }
 298       * @return bool|WP_Error|null True if the upgrade was successful, false or a WP_Error object otherwise.
 299       *                            Null if the upgrade failed before the package was installed, for example
 300       *                            when the package could not be downloaded.
 301       */
 302  	public function upgrade( $theme, $args = array() ) {
 303          $defaults    = array(
 304              'clear_update_cache' => true,
 305          );
 306          $parsed_args = wp_parse_args( $args, $defaults );
 307  
 308          $this->init();
 309          $this->upgrade_strings();
 310  
 311          // Is an update available?
 312          $current = get_site_transient( 'update_themes' );
 313          if ( ! isset( $current->response[ $theme ] ) ) {
 314              $this->skin->before();
 315              $this->skin->set_result( false );
 316              $this->skin->error( 'up_to_date' );
 317              $this->skin->after();
 318              return false;
 319          }
 320  
 321          $upgrade_data = $current->response[ $theme ];
 322  
 323          add_filter( 'upgrader_pre_install', array( $this, 'current_before' ), 10, 2 );
 324          add_filter( 'upgrader_post_install', array( $this, 'current_after' ), 10, 2 );
 325          add_filter( 'upgrader_clear_destination', array( $this, 'delete_old_theme' ), 10, 4 );
 326          if ( $parsed_args['clear_update_cache'] ) {
 327              // Clear cache so wp_update_themes() knows about the new theme.
 328              add_action( 'upgrader_process_complete', 'wp_clean_themes_cache', 9, 0 );
 329          }
 330  
 331          $this->run(
 332              array(
 333                  'package'           => $upgrade_data['package'],
 334                  'destination'       => get_theme_root( $theme ),
 335                  'clear_destination' => true,
 336                  'clear_working'     => true,
 337                  'hook_extra'        => array(
 338                      'theme'       => $theme,
 339                      'type'        => 'theme',
 340                      'action'      => 'update',
 341                      'temp_backup' => array(
 342                          'slug' => $theme,
 343                          'src'  => get_theme_root( $theme ),
 344                          'dir'  => 'themes',
 345                      ),
 346                  ),
 347              )
 348          );
 349  
 350          remove_action( 'upgrader_process_complete', 'wp_clean_themes_cache', 9 );
 351          remove_filter( 'upgrader_pre_install', array( $this, 'current_before' ) );
 352          remove_filter( 'upgrader_post_install', array( $this, 'current_after' ) );
 353          remove_filter( 'upgrader_clear_destination', array( $this, 'delete_old_theme' ) );
 354  
 355          if ( ! $this->result || is_wp_error( $this->result ) ) {
 356              return $this->result;
 357          }
 358  
 359          wp_clean_themes_cache( $parsed_args['clear_update_cache'] );
 360  
 361          /*
 362           * Ensure any future auto-update failures trigger a failure email by removing
 363           * the last failure notification from the list when themes update successfully.
 364           */
 365          $past_failure_emails = get_option( 'auto_plugin_theme_update_emails', array() );
 366  
 367          if ( isset( $past_failure_emails[ $theme ] ) ) {
 368              unset( $past_failure_emails[ $theme ] );
 369              update_option( 'auto_plugin_theme_update_emails', $past_failure_emails );
 370          }
 371  
 372          return true;
 373      }
 374  
 375      /**
 376       * Upgrades several themes at once.
 377       *
 378       * @since 3.0.0
 379       * @since 3.7.0 The `$args` parameter was added, making clearing the update cache optional.
 380       *
 381       * @param string[] $themes Array of the theme slugs.
 382       * @param array    $args {
 383       *     Optional. Other arguments for upgrading several themes at once. Default empty array.
 384       *
 385       *     @type bool $clear_update_cache Whether to clear the update cache if successful.
 386       *                                    Default true.
 387       * }
 388       * @return array[]|false An array of results, or false if unable to connect to the filesystem.
 389       */
 390  	public function bulk_upgrade( $themes, $args = array() ) {
 391          $wp_version  = wp_get_wp_version();
 392          $defaults    = array(
 393              'clear_update_cache' => true,
 394          );
 395          $parsed_args = wp_parse_args( $args, $defaults );
 396  
 397          $this->init();
 398          $this->bulk = true;
 399          $this->upgrade_strings();
 400  
 401          $current = get_site_transient( 'update_themes' );
 402  
 403          add_filter( 'upgrader_pre_install', array( $this, 'current_before' ), 10, 2 );
 404          add_filter( 'upgrader_post_install', array( $this, 'current_after' ), 10, 2 );
 405          add_filter( 'upgrader_clear_destination', array( $this, 'delete_old_theme' ), 10, 4 );
 406  
 407          $this->skin->header();
 408  
 409          // Connect to the filesystem first.
 410          $connected = $this->fs_connect( array( WP_CONTENT_DIR ) );
 411          if ( ! $connected ) {
 412              $this->skin->footer();
 413              return false;
 414          }
 415  
 416          $this->skin->bulk_header();
 417  
 418          /*
 419           * Only start maintenance mode if:
 420           * - running Multisite and there are one or more themes specified, OR
 421           * - a theme with an update available is currently in use.
 422           * @todo For multisite, maintenance mode should only kick in for individual sites if at all possible.
 423           */
 424          $maintenance = ( is_multisite() && ! empty( $themes ) );
 425          foreach ( $themes as $theme ) {
 426              $maintenance = $maintenance || get_stylesheet() === $theme || get_template() === $theme;
 427          }
 428          if ( $maintenance ) {
 429              $this->maintenance_mode( true );
 430          }
 431  
 432          $results = array();
 433  
 434          $this->update_count   = count( $themes );
 435          $this->update_current = 0;
 436          foreach ( $themes as $theme ) {
 437              ++$this->update_current;
 438  
 439              $this->skin->theme_info = $this->theme_info( $theme );
 440  
 441              if ( ! isset( $current->response[ $theme ] ) ) {
 442                  $this->skin->set_result( true );
 443                  $this->skin->before();
 444                  $this->skin->feedback( 'up_to_date' );
 445                  $this->skin->after();
 446                  $results[ $theme ] = true;
 447                  continue;
 448              }
 449  
 450              // Get the URL to the zip file.
 451              $upgrade_data = $current->response[ $theme ];
 452  
 453              if ( isset( $upgrade_data['requires'] ) && ! is_wp_version_compatible( $upgrade_data['requires'] ) ) {
 454                  $result = new WP_Error(
 455                      'incompatible_wp_required_version',
 456                      sprintf(
 457                          /* translators: 1: Current WordPress version, 2: WordPress version required by the new theme version. */
 458                          __( 'Your WordPress version is %1$s, however the new theme version requires %2$s.' ),
 459                          $wp_version,
 460                          $upgrade_data['requires']
 461                      )
 462                  );
 463  
 464                  $this->skin->before();
 465                  $this->skin->error( $result );
 466                  $this->skin->after();
 467              } elseif ( isset( $upgrade_data['requires_php'] ) && ! is_php_version_compatible( $upgrade_data['requires_php'] ) ) {
 468                  $result = new WP_Error(
 469                      'incompatible_php_required_version',
 470                      sprintf(
 471                          /* translators: 1: Current PHP version, 2: PHP version required by the new theme version. */
 472                          __( 'The PHP version on your server is %1$s, however the new theme version requires %2$s.' ),
 473                          PHP_VERSION,
 474                          $upgrade_data['requires_php']
 475                      )
 476                  );
 477  
 478                  $this->skin->before();
 479                  $this->skin->error( $result );
 480                  $this->skin->after();
 481              } else {
 482                  add_filter( 'upgrader_source_selection', array( $this, 'check_package' ) );
 483                  $result = $this->run(
 484                      array(
 485                          'package'           => $upgrade_data['package'],
 486                          'destination'       => get_theme_root( $theme ),
 487                          'clear_destination' => true,
 488                          'clear_working'     => true,
 489                          'is_multi'          => true,
 490                          'hook_extra'        => array(
 491                              'theme'       => $theme,
 492                              'temp_backup' => array(
 493                                  'slug' => $theme,
 494                                  'src'  => get_theme_root( $theme ),
 495                                  'dir'  => 'themes',
 496                              ),
 497                          ),
 498                      )
 499                  );
 500                  remove_filter( 'upgrader_source_selection', array( $this, 'check_package' ) );
 501              }
 502  
 503              $results[ $theme ] = $result;
 504  
 505              // Prevent credentials auth screen from displaying multiple times.
 506              if ( false === $result ) {
 507                  break;
 508              }
 509          } // End foreach $themes.
 510  
 511          $this->maintenance_mode( false );
 512  
 513          // Refresh the Theme Update information.
 514          wp_clean_themes_cache( $parsed_args['clear_update_cache'] );
 515  
 516          /** This action is documented in wp-admin/includes/class-wp-upgrader.php */
 517          do_action(
 518              'upgrader_process_complete',
 519              $this,
 520              array(
 521                  'action' => 'update',
 522                  'type'   => 'theme',
 523                  'bulk'   => true,
 524                  'themes' => $themes,
 525              )
 526          );
 527  
 528          $this->skin->bulk_footer();
 529  
 530          $this->skin->footer();
 531  
 532          // Cleanup our hooks, in case something else does an upgrade on this connection.
 533          remove_filter( 'upgrader_pre_install', array( $this, 'current_before' ) );
 534          remove_filter( 'upgrader_post_install', array( $this, 'current_after' ) );
 535          remove_filter( 'upgrader_clear_destination', array( $this, 'delete_old_theme' ) );
 536  
 537          /*
 538           * Ensure any future auto-update failures trigger a failure email by removing
 539           * the last failure notification from the list when themes update successfully.
 540           */
 541          $past_failure_emails = get_option( 'auto_plugin_theme_update_emails', array() );
 542  
 543          foreach ( $results as $theme => $result ) {
 544              // Maintain last failure notification when themes failed to update manually.
 545              if ( ! $result || is_wp_error( $result ) || ! isset( $past_failure_emails[ $theme ] ) ) {
 546                  continue;
 547              }
 548  
 549              unset( $past_failure_emails[ $theme ] );
 550          }
 551  
 552          update_option( 'auto_plugin_theme_update_emails', $past_failure_emails );
 553  
 554          return $results;
 555      }
 556  
 557      /**
 558       * Checks that the package source contains a valid theme.
 559       *
 560       * Hooked to the {@see 'upgrader_source_selection'} filter by Theme_Upgrader::install().
 561       *
 562       * @since 3.3.0
 563       *
 564       * @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
 565       *
 566       * @param string $source The path to the downloaded package source.
 567       * @return string|WP_Error The source as passed, or a WP_Error object on failure.
 568       */
 569  	public function check_package( $source ) {
 570          global $wp_filesystem;
 571  
 572          $wp_version           = wp_get_wp_version();
 573          $this->new_theme_data = array();
 574  
 575          if ( is_wp_error( $source ) ) {
 576              return $source;
 577          }
 578  
 579          // Check that the folder contains a valid theme.
 580          $working_directory = str_replace( $wp_filesystem->wp_content_dir(), trailingslashit( WP_CONTENT_DIR ), $source );
 581          if ( ! is_dir( $working_directory ) ) { // Confidence check, if the above fails, let's not prevent installation.
 582              return $source;
 583          }
 584  
 585          // A proper archive should have a style.css file in the single subdirectory.
 586          if ( ! file_exists( $working_directory . 'style.css' ) ) {
 587              return new WP_Error(
 588                  'incompatible_archive_theme_no_style',
 589                  $this->strings['incompatible_archive'],
 590                  sprintf(
 591                      /* translators: %s: style.css */
 592                      __( 'The theme is missing the %s stylesheet.' ),
 593                      '<code>style.css</code>'
 594                  )
 595              );
 596          }
 597  
 598          // All these headers are needed on Theme_Installer_Skin::do_overwrite().
 599          $new_theme_data = get_file_data(
 600              $working_directory . 'style.css',
 601              array(
 602                  'Name'        => 'Theme Name',
 603                  'Version'     => 'Version',
 604                  'Author'      => 'Author',
 605                  'Template'    => 'Template',
 606                  'RequiresWP'  => 'Requires at least',
 607                  'RequiresPHP' => 'Requires PHP',
 608              )
 609          );
 610  
 611          if ( empty( $new_theme_data['Name'] ) ) {
 612              return new WP_Error(
 613                  'incompatible_archive_theme_no_name',
 614                  $this->strings['incompatible_archive'],
 615                  sprintf(
 616                      /* translators: %s: style.css */
 617                      __( 'The %s stylesheet does not contain a valid theme header.' ),
 618                      '<code>style.css</code>'
 619                  )
 620              );
 621          }
 622  
 623          /*
 624           * Parent themes must contain an index file:
 625           * - classic themes require /index.php
 626           * - block themes require /templates/index.html or block-templates/index.html (deprecated 5.9.0).
 627           */
 628          if (
 629              empty( $new_theme_data['Template'] ) &&
 630              ! file_exists( $working_directory . 'index.php' ) &&
 631              ! file_exists( $working_directory . 'templates/index.html' ) &&
 632              ! file_exists( $working_directory . 'block-templates/index.html' )
 633          ) {
 634              return new WP_Error(
 635                  'incompatible_archive_theme_no_index',
 636                  $this->strings['incompatible_archive'],
 637                  sprintf(
 638                      /* translators: 1: templates/index.html, 2: index.php, 3: Documentation URL, 4: Template, 5: style.css */
 639                      __( '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.' ),
 640                      '<code>templates/index.html</code>',
 641                      '<code>index.php</code>',
 642                      __( 'https://developer.wordpress.org/themes/advanced-topics/child-themes/' ),
 643                      '<code>Template</code>',
 644                      '<code>style.css</code>'
 645                  )
 646              );
 647          }
 648  
 649          $requires_php = $new_theme_data['RequiresPHP'] ?? null;
 650          $requires_wp  = $new_theme_data['RequiresWP'] ?? null;
 651  
 652          if ( ! is_php_version_compatible( $requires_php ) ) {
 653              $error = sprintf(
 654                  /* translators: 1: Current PHP version, 2: Version required by the uploaded theme. */
 655                  __( 'The PHP version on your server is %1$s, however the uploaded theme requires %2$s.' ),
 656                  PHP_VERSION,
 657                  $requires_php
 658              );
 659  
 660              return new WP_Error( 'incompatible_php_required_version', $this->strings['incompatible_archive'], $error );
 661          }
 662          if ( ! is_wp_version_compatible( $requires_wp ) ) {
 663              $error = sprintf(
 664                  /* translators: 1: Current WordPress version, 2: Version required by the uploaded theme. */
 665                  __( 'Your WordPress version is %1$s, however the uploaded theme requires %2$s.' ),
 666                  $wp_version,
 667                  $requires_wp
 668              );
 669  
 670              return new WP_Error( 'incompatible_wp_required_version', $this->strings['incompatible_archive'], $error );
 671          }
 672  
 673          $this->new_theme_data = $new_theme_data;
 674  
 675          return $source;
 676      }
 677  
 678      /**
 679       * Turns on maintenance mode before attempting to upgrade the active theme.
 680       *
 681       * Hooked to the {@see 'upgrader_pre_install'} filter by Theme_Upgrader::upgrade() and
 682       * Theme_Upgrader::bulk_upgrade().
 683       *
 684       * @since 2.8.0
 685       *
 686       * @param bool|WP_Error $response The installation response before the installation has started.
 687       * @param array         $theme    Theme arguments.
 688       * @return bool|WP_Error The original `$response` parameter or WP_Error.
 689       */
 690  	public function current_before( $response, $theme ) {
 691          if ( is_wp_error( $response ) ) {
 692              return $response;
 693          }
 694  
 695          $theme = $theme['theme'] ?? '';
 696  
 697          // Only run if active theme.
 698          if ( get_stylesheet() !== $theme ) {
 699              return $response;
 700          }
 701  
 702          // Change to maintenance mode. Bulk edit handles this separately.
 703          if ( ! $this->bulk ) {
 704              $this->maintenance_mode( true );
 705          }
 706  
 707          return $response;
 708      }
 709  
 710      /**
 711       * Turns off maintenance mode after upgrading the active theme.
 712       *
 713       * Hooked to the {@see 'upgrader_post_install'} filter by Theme_Upgrader::upgrade()
 714       * and Theme_Upgrader::bulk_upgrade().
 715       *
 716       * @since 2.8.0
 717       *
 718       * @param bool|WP_Error $response The installation response after the installation has finished.
 719       * @param array         $theme    Theme arguments.
 720       * @return bool|WP_Error The original `$response` parameter or WP_Error.
 721       */
 722  	public function current_after( $response, $theme ) {
 723          if ( is_wp_error( $response ) ) {
 724              return $response;
 725          }
 726  
 727          $theme = $theme['theme'] ?? '';
 728  
 729          // Only run if active theme.
 730          if ( get_stylesheet() !== $theme ) {
 731              return $response;
 732          }
 733  
 734          // Ensure stylesheet name hasn't changed after the upgrade:
 735          if ( get_stylesheet() === $theme && $theme !== $this->result['destination_name'] ) {
 736              wp_clean_themes_cache();
 737              $stylesheet = $this->result['destination_name'];
 738              switch_theme( $stylesheet );
 739          }
 740  
 741          // Time to remove maintenance mode. Bulk edit handles this separately.
 742          if ( ! $this->bulk ) {
 743              $this->maintenance_mode( false );
 744          }
 745          return $response;
 746      }
 747  
 748      /**
 749       * Deletes the old theme during an upgrade.
 750       *
 751       * Hooked to the {@see 'upgrader_clear_destination'} filter by Theme_Upgrader::upgrade()
 752       * and Theme_Upgrader::bulk_upgrade().
 753       *
 754       * @since 2.8.0
 755       *
 756       * @global WP_Filesystem_Base $wp_filesystem Filesystem subclass.
 757       *
 758       * @param bool   $removed
 759       * @param string $local_destination
 760       * @param string $remote_destination
 761       * @param array  $theme
 762       * @return bool
 763       */
 764  	public function delete_old_theme( $removed, $local_destination, $remote_destination, $theme ) {
 765          global $wp_filesystem;
 766  
 767          if ( is_wp_error( $removed ) ) {
 768              return $removed; // Pass errors through.
 769          }
 770  
 771          if ( ! isset( $theme['theme'] ) ) {
 772              return $removed;
 773          }
 774  
 775          $theme      = $theme['theme'];
 776          $themes_dir = trailingslashit( $wp_filesystem->wp_themes_dir( $theme ) );
 777          if ( $wp_filesystem->exists( $themes_dir . $theme ) ) {
 778              if ( ! $wp_filesystem->delete( $themes_dir . $theme, true ) ) {
 779                  return false;
 780              }
 781          }
 782  
 783          return true;
 784      }
 785  
 786      /**
 787       * Gets the WP_Theme object for a theme.
 788       *
 789       * @since 2.8.0
 790       * @since 3.0.0 The `$theme` argument was added.
 791       *
 792       * @param string $theme The directory name of the theme. This is optional, and if not supplied,
 793       *                      the directory name from the last result will be used.
 794       * @return WP_Theme|false The theme's info object, or false `$theme` is not supplied
 795       *                        and the last result isn't set.
 796       */
 797  	public function theme_info( $theme = null ) {
 798          if ( empty( $theme ) ) {
 799              if ( ! empty( $this->result['destination_name'] ) ) {
 800                  $theme = $this->result['destination_name'];
 801              } else {
 802                  return false;
 803              }
 804          }
 805  
 806          $theme = wp_get_theme( $theme );
 807          $theme->cache_delete();
 808  
 809          return $theme;
 810      }
 811  }


Generated : Tue Oct 20 08:20:34 2026 Cross-referenced by PHPXref