[ Index ]

PHP Cross Reference of WordPress

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 $result
  28       * @see WP_Upgrader::$result
  29       */
  30      public $result;
  31  
  32      /**
  33       * Whether multiple themes are being upgraded/installed in bulk.
  34       *
  35       * @since 2.9.0
  36       * @var bool $bulk
  37       */
  38      public $bulk = false;
  39  
  40      /**
  41       * Initialize the upgrade strings.
  42       *
  43       * @since 2.8.0
  44       */
  45  	public function upgrade_strings() {
  46          $this->strings['up_to_date'] = __( 'The theme is at the latest version.' );
  47          $this->strings['no_package'] = __( 'Update package not available.' );
  48          /* translators: %s: Package URL. */
  49          $this->strings['downloading_package'] = sprintf( __( 'Downloading update from %s&#8230;' ), '<span class="code">%s</span>' );
  50          $this->strings['unpack_package']      = __( 'Unpacking the update&#8230;' );
  51          $this->strings['remove_old']          = __( 'Removing the old version of the theme&#8230;' );
  52          $this->strings['remove_old_failed']   = __( 'Could not remove the old theme.' );
  53          $this->strings['process_failed']      = __( 'Theme update failed.' );
  54          $this->strings['process_success']     = __( 'Theme updated successfully.' );
  55      }
  56  
  57      /**
  58       * Initialize the installation strings.
  59       *
  60       * @since 2.8.0
  61       */
  62  	public function install_strings() {
  63          $this->strings['no_package'] = __( 'Installation package not available.' );
  64          /* translators: %s: Package URL. */
  65          $this->strings['downloading_package'] = sprintf( __( 'Downloading installation package from %s&#8230;' ), '<span class="code">%s</span>' );
  66          $this->strings['unpack_package']      = __( 'Unpacking the package&#8230;' );
  67          $this->strings['installing_package']  = __( 'Installing the theme&#8230;' );
  68          $this->strings['no_files']            = __( 'The theme contains no files.' );
  69          $this->strings['process_failed']      = __( 'Theme installation failed.' );
  70          $this->strings['process_success']     = __( 'Theme installed successfully.' );
  71          /* translators: 1: Theme name, 2: Theme version. */
  72          $this->strings['process_success_specific'] = __( 'Successfully installed the theme <strong>%1$s %2$s</strong>.' );
  73          $this->strings['parent_theme_search']      = __( 'This theme requires a parent theme. Checking if it is installed&#8230;' );
  74          /* translators: 1: Theme name, 2: Theme version. */
  75          $this->strings['parent_theme_prepare_install'] = __( 'Preparing to install <strong>%1$s %2$s</strong>&#8230;' );
  76          /* translators: 1: Theme name, 2: Theme version. */
  77          $this->strings['parent_theme_currently_installed'] = __( 'The parent theme, <strong>%1$s %2$s</strong>, is currently installed.' );
  78          /* translators: 1: Theme name, 2: Theme version. */
  79          $this->strings['parent_theme_install_success'] = __( 'Successfully installed the parent theme, <strong>%1$s %2$s</strong>.' );
  80          /* translators: %s: Theme name. */
  81          $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>' );
  82      }
  83  
  84      /**
  85       * Check if a child theme is being installed and we need to install its parent.
  86       *
  87       * Hooked to the {@see 'upgrader_post_install'} filter by Theme_Upgrader::install().
  88       *
  89       * @since 3.4.0
  90       *
  91       * @param bool  $install_result
  92       * @param array $hook_extra
  93       * @param array $child_result
  94       * @return type
  95       */
  96  	public function check_parent_theme_filter( $install_result, $hook_extra, $child_result ) {
  97          // Check to see if we need to install a parent theme
  98          $theme_info = $this->theme_info();
  99  
 100          if ( ! $theme_info->parent() ) {
 101              return $install_result;
 102          }
 103  
 104          $this->skin->feedback( 'parent_theme_search' );
 105  
 106          if ( ! $theme_info->parent()->errors() ) {
 107              $this->skin->feedback( 'parent_theme_currently_installed', $theme_info->parent()->display( 'Name' ), $theme_info->parent()->display( 'Version' ) );
 108              // We already have the theme, fall through.
 109              return $install_result;
 110          }
 111  
 112          // We don't have the parent theme, let's install it.
 113          $api = themes_api(
 114              'theme_information',
 115              array(
 116                  'slug'   => $theme_info->get( 'Template' ),
 117                  'fields' => array(
 118                      'sections' => false,
 119                      'tags'     => false,
 120                  ),
 121              )
 122          ); //Save on a bit of bandwidth.
 123  
 124          if ( ! $api || is_wp_error( $api ) ) {
 125              $this->skin->feedback( 'parent_theme_not_found', $theme_info->get( 'Template' ) );
 126              // Don't show activate or preview actions after installation
 127              add_filter( 'install_theme_complete_actions', array( $this, 'hide_activate_preview_actions' ) );
 128              return $install_result;
 129          }
 130  
 131          // Backup required data we're going to override:
 132          $child_api             = $this->skin->api;
 133          $child_success_message = $this->strings['process_success'];
 134  
 135          // Override them
 136          $this->skin->api                           = $api;
 137          $this->strings['process_success_specific'] = $this->strings['parent_theme_install_success'];//, $api->name, $api->version);
 138  
 139          $this->skin->feedback( 'parent_theme_prepare_install', $api->name, $api->version );
 140  
 141          add_filter( 'install_theme_complete_actions', '__return_false', 999 ); // Don't show any actions after installing the theme.
 142  
 143          // Install the parent theme
 144          $parent_result = $this->run(
 145              array(
 146                  'package'           => $api->download_link,
 147                  'destination'       => get_theme_root(),
 148                  'clear_destination' => false, //Do not overwrite files.
 149                  'clear_working'     => true,
 150              )
 151          );
 152  
 153          if ( is_wp_error( $parent_result ) ) {
 154              add_filter( 'install_theme_complete_actions', array( $this, 'hide_activate_preview_actions' ) );
 155          }
 156  
 157          // Start cleaning up after the parents installation
 158          remove_filter( 'install_theme_complete_actions', '__return_false', 999 );
 159  
 160          // Reset child's result and data
 161          $this->result                     = $child_result;
 162          $this->skin->api                  = $child_api;
 163          $this->strings['process_success'] = $child_success_message;
 164  
 165          return $install_result;
 166      }
 167  
 168      /**
 169       * Don't display the activate and preview actions to the user.
 170       *
 171       * Hooked to the {@see 'install_theme_complete_actions'} filter by
 172       * Theme_Upgrader::check_parent_theme_filter() when installing
 173       * a child theme and installing the parent theme fails.
 174       *
 175       * @since 3.4.0
 176       *
 177       * @param array $actions Preview actions.
 178       * @return array
 179       */
 180  	public function hide_activate_preview_actions( $actions ) {
 181          unset( $actions['activate'], $actions['preview'] );
 182          return $actions;
 183      }
 184  
 185      /**
 186       * Install a theme package.
 187       *
 188       * @since 2.8.0
 189       * @since 3.7.0 The `$args` parameter was added, making clearing the update cache optional.
 190       *
 191       * @param string $package The full local path or URI of the package.
 192       * @param array  $args {
 193       *     Optional. Other arguments for installing a theme package. Default empty array.
 194       *
 195       *     @type bool $clear_update_cache Whether to clear the updates cache if successful.
 196       *                                    Default true.
 197       * }
 198       *
 199       * @return bool|WP_Error True if the installation was successful, false or a WP_Error object otherwise.
 200       */
 201  	public function install( $package, $args = array() ) {
 202  
 203          $defaults    = array(
 204              'clear_update_cache' => true,
 205          );
 206          $parsed_args = wp_parse_args( $args, $defaults );
 207  
 208          $this->init();
 209          $this->install_strings();
 210  
 211          add_filter( 'upgrader_source_selection', array( $this, 'check_package' ) );
 212          add_filter( 'upgrader_post_install', array( $this, 'check_parent_theme_filter' ), 10, 3 );
 213          if ( $parsed_args['clear_update_cache'] ) {
 214              // Clear cache so wp_update_themes() knows about the new theme.
 215              add_action( 'upgrader_process_complete', 'wp_clean_themes_cache', 9, 0 );
 216          }
 217  
 218          $this->run(
 219              array(
 220                  'package'           => $package,
 221                  'destination'       => get_theme_root(),
 222                  'clear_destination' => false, //Do not overwrite files.
 223                  'clear_working'     => true,
 224                  'hook_extra'        => array(
 225                      'type'   => 'theme',
 226                      'action' => 'install',
 227                  ),
 228              )
 229          );
 230  
 231          remove_action( 'upgrader_process_complete', 'wp_clean_themes_cache', 9 );
 232          remove_filter( 'upgrader_source_selection', array( $this, 'check_package' ) );
 233          remove_filter( 'upgrader_post_install', array( $this, 'check_parent_theme_filter' ) );
 234  
 235          if ( ! $this->result || is_wp_error( $this->result ) ) {
 236              return $this->result;
 237          }
 238  
 239          // Refresh the Theme Update information
 240          wp_clean_themes_cache( $parsed_args['clear_update_cache'] );
 241  
 242          return true;
 243      }
 244  
 245      /**
 246       * Upgrade a theme.
 247       *
 248       * @since 2.8.0
 249       * @since 3.7.0 The `$args` parameter was added, making clearing the update cache optional.
 250       *
 251       * @param string $theme The theme slug.
 252       * @param array  $args {
 253       *     Optional. Other arguments for upgrading a theme. Default empty array.
 254       *
 255       *     @type bool $clear_update_cache Whether to clear the update cache if successful.
 256       *                                    Default true.
 257       * }
 258       * @return bool|WP_Error True if the upgrade was successful, false or a WP_Error object otherwise.
 259       */
 260  	public function upgrade( $theme, $args = array() ) {
 261  
 262          $defaults    = array(
 263              'clear_update_cache' => true,
 264          );
 265          $parsed_args = wp_parse_args( $args, $defaults );
 266  
 267          $this->init();
 268          $this->upgrade_strings();
 269  
 270          // Is an update available?
 271          $current = get_site_transient( 'update_themes' );
 272          if ( ! isset( $current->response[ $theme ] ) ) {
 273              $this->skin->before();
 274              $this->skin->set_result( false );
 275              $this->skin->error( 'up_to_date' );
 276              $this->skin->after();
 277              return false;
 278          }
 279  
 280          $r = $current->response[ $theme ];
 281  
 282          add_filter( 'upgrader_pre_install', array( $this, 'current_before' ), 10, 2 );
 283          add_filter( 'upgrader_post_install', array( $this, 'current_after' ), 10, 2 );
 284          add_filter( 'upgrader_clear_destination', array( $this, 'delete_old_theme' ), 10, 4 );
 285          if ( $parsed_args['clear_update_cache'] ) {
 286              // Clear cache so wp_update_themes() knows about the new theme.
 287              add_action( 'upgrader_process_complete', 'wp_clean_themes_cache', 9, 0 );
 288          }
 289  
 290          $this->run(
 291              array(
 292                  'package'           => $r['package'],
 293                  'destination'       => get_theme_root( $theme ),
 294                  'clear_destination' => true,
 295                  'clear_working'     => true,
 296                  'hook_extra'        => array(
 297                      'theme'  => $theme,
 298                      'type'   => 'theme',
 299                      'action' => 'update',
 300                  ),
 301              )
 302          );
 303  
 304          remove_action( 'upgrader_process_complete', 'wp_clean_themes_cache', 9 );
 305          remove_filter( 'upgrader_pre_install', array( $this, 'current_before' ) );
 306          remove_filter( 'upgrader_post_install', array( $this, 'current_after' ) );
 307          remove_filter( 'upgrader_clear_destination', array( $this, 'delete_old_theme' ) );
 308  
 309          if ( ! $this->result || is_wp_error( $this->result ) ) {
 310              return $this->result;
 311          }
 312  
 313          wp_clean_themes_cache( $parsed_args['clear_update_cache'] );
 314  
 315          return true;
 316      }
 317  
 318      /**
 319       * Upgrade several themes at once.
 320       *
 321       * @since 3.0.0
 322       * @since 3.7.0 The `$args` parameter was added, making clearing the update cache optional.
 323       *
 324       * @param string[] $themes Array of the theme slugs.
 325       * @param array    $args {
 326       *     Optional. Other arguments for upgrading several themes at once. Default empty array.
 327       *
 328       *     @type bool $clear_update_cache Whether to clear the update cache if successful.
 329       *                                    Default true.
 330       * }
 331       * @return array[]|false An array of results, or false if unable to connect to the filesystem.
 332       */
 333  	public function bulk_upgrade( $themes, $args = array() ) {
 334  
 335          $defaults    = array(
 336              'clear_update_cache' => true,
 337          );
 338          $parsed_args = wp_parse_args( $args, $defaults );
 339  
 340          $this->init();
 341          $this->bulk = true;
 342          $this->upgrade_strings();
 343  
 344          $current = get_site_transient( 'update_themes' );
 345  
 346          add_filter( 'upgrader_pre_install', array( $this, 'current_before' ), 10, 2 );
 347          add_filter( 'upgrader_post_install', array( $this, 'current_after' ), 10, 2 );
 348          add_filter( 'upgrader_clear_destination', array( $this, 'delete_old_theme' ), 10, 4 );
 349  
 350          $this->skin->header();
 351  
 352          // Connect to the Filesystem first.
 353          $res = $this->fs_connect( array( WP_CONTENT_DIR ) );
 354          if ( ! $res ) {
 355              $this->skin->footer();
 356              return false;
 357          }
 358  
 359          $this->skin->bulk_header();
 360  
 361          // Only start maintenance mode if:
 362          // - running Multisite and there are one or more themes specified, OR
 363          // - a theme with an update available is currently in use.
 364          // @TODO: For multisite, maintenance mode should only kick in for individual sites if at all possible.
 365          $maintenance = ( is_multisite() && ! empty( $themes ) );
 366          foreach ( $themes as $theme ) {
 367              $maintenance = $maintenance || $theme == get_stylesheet() || $theme == get_template();
 368          }
 369          if ( $maintenance ) {
 370              $this->maintenance_mode( true );
 371          }
 372  
 373          $results = array();
 374  
 375          $this->update_count   = count( $themes );
 376          $this->update_current = 0;
 377          foreach ( $themes as $theme ) {
 378              $this->update_current++;
 379  
 380              $this->skin->theme_info = $this->theme_info( $theme );
 381  
 382              if ( ! isset( $current->response[ $theme ] ) ) {
 383                  $this->skin->set_result( true );
 384                  $this->skin->before();
 385                  $this->skin->feedback( 'up_to_date' );
 386                  $this->skin->after();
 387                  $results[ $theme ] = true;
 388                  continue;
 389              }
 390  
 391              // Get the URL to the zip file
 392              $r = $current->response[ $theme ];
 393  
 394              $result = $this->run(
 395                  array(
 396                      'package'           => $r['package'],
 397                      'destination'       => get_theme_root( $theme ),
 398                      'clear_destination' => true,
 399                      'clear_working'     => true,
 400                      'is_multi'          => true,
 401                      'hook_extra'        => array(
 402                          'theme' => $theme,
 403                      ),
 404                  )
 405              );
 406  
 407              $results[ $theme ] = $this->result;
 408  
 409              // Prevent credentials auth screen from displaying multiple times
 410              if ( false === $result ) {
 411                  break;
 412              }
 413          } //end foreach $plugins
 414  
 415          $this->maintenance_mode( false );
 416  
 417          // Refresh the Theme Update information
 418          wp_clean_themes_cache( $parsed_args['clear_update_cache'] );
 419  
 420          /** This action is documented in wp-admin/includes/class-wp-upgrader.php */
 421          do_action(
 422              'upgrader_process_complete',
 423              $this,
 424              array(
 425                  'action' => 'update',
 426                  'type'   => 'theme',
 427                  'bulk'   => true,
 428                  'themes' => $themes,
 429              )
 430          );
 431  
 432          $this->skin->bulk_footer();
 433  
 434          $this->skin->footer();
 435  
 436          // Cleanup our hooks, in case something else does a upgrade on this connection.
 437          remove_filter( 'upgrader_pre_install', array( $this, 'current_before' ) );
 438          remove_filter( 'upgrader_post_install', array( $this, 'current_after' ) );
 439          remove_filter( 'upgrader_clear_destination', array( $this, 'delete_old_theme' ) );
 440  
 441          return $results;
 442      }
 443  
 444      /**
 445       * Check that the package source contains a valid theme.
 446       *
 447       * Hooked to the {@see 'upgrader_source_selection'} filter by Theme_Upgrader::install().
 448       * It will return an error if the theme doesn't have style.css or index.php
 449       * files.
 450       *
 451       * @since 3.3.0
 452       *
 453       * @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
 454       *
 455       * @param string $source The full path to the package source.
 456       * @return string|WP_Error The source or a WP_Error.
 457       */
 458  	public function check_package( $source ) {
 459          global $wp_filesystem;
 460  
 461          if ( is_wp_error( $source ) ) {
 462              return $source;
 463          }
 464  
 465          // Check the folder contains a valid theme
 466          $working_directory = str_replace( $wp_filesystem->wp_content_dir(), trailingslashit( WP_CONTENT_DIR ), $source );
 467          if ( ! is_dir( $working_directory ) ) { // Sanity check, if the above fails, let's not prevent installation.
 468              return $source;
 469          }
 470  
 471          // A proper archive should have a style.css file in the single subdirectory
 472          if ( ! file_exists( $working_directory . 'style.css' ) ) {
 473              return new WP_Error(
 474                  'incompatible_archive_theme_no_style',
 475                  $this->strings['incompatible_archive'],
 476                  sprintf(
 477                      /* translators: %s: style.css */
 478                      __( 'The theme is missing the %s stylesheet.' ),
 479                      '<code>style.css</code>'
 480                  )
 481              );
 482          }
 483  
 484          $info = get_file_data(
 485              $working_directory . 'style.css',
 486              array(
 487                  'Name'     => 'Theme Name',
 488                  'Template' => 'Template',
 489              )
 490          );
 491  
 492          if ( empty( $info['Name'] ) ) {
 493              return new WP_Error(
 494                  'incompatible_archive_theme_no_name',
 495                  $this->strings['incompatible_archive'],
 496                  sprintf(
 497                      /* translators: %s: style.css */
 498                      __( 'The %s stylesheet doesn&#8217;t contain a valid theme header.' ),
 499                      '<code>style.css</code>'
 500                  )
 501              );
 502          }
 503  
 504          // If it's not a child theme, it must have at least an index.php to be legit.
 505          if ( empty( $info['Template'] ) && ! file_exists( $working_directory . 'index.php' ) ) {
 506              return new WP_Error(
 507                  'incompatible_archive_theme_no_index',
 508                  $this->strings['incompatible_archive'],
 509                  sprintf(
 510                      /* translators: %s: index.php */
 511                      __( 'The theme is missing the %s file.' ),
 512                      '<code>index.php</code>'
 513                  )
 514              );
 515          }
 516  
 517          return $source;
 518      }
 519  
 520      /**
 521       * Turn on maintenance mode before attempting to upgrade the current theme.
 522       *
 523       * Hooked to the {@see 'upgrader_pre_install'} filter by Theme_Upgrader::upgrade() and
 524       * Theme_Upgrader::bulk_upgrade().
 525       *
 526       * @since 2.8.0
 527       *
 528       * @param bool|WP_Error  $return
 529       * @param array          $theme
 530       * @return bool|WP_Error
 531       */
 532  	public function current_before( $return, $theme ) {
 533          if ( is_wp_error( $return ) ) {
 534              return $return;
 535          }
 536  
 537          $theme = isset( $theme['theme'] ) ? $theme['theme'] : '';
 538  
 539          if ( $theme != get_stylesheet() ) { //If not current
 540              return $return;
 541          }
 542          //Change to maintenance mode now.
 543          if ( ! $this->bulk ) {
 544              $this->maintenance_mode( true );
 545          }
 546  
 547          return $return;
 548      }
 549  
 550      /**
 551       * Turn off maintenance mode after upgrading the current theme.
 552       *
 553       * Hooked to the {@see 'upgrader_post_install'} filter by Theme_Upgrader::upgrade()
 554       * and Theme_Upgrader::bulk_upgrade().
 555       *
 556       * @since 2.8.0
 557       *
 558       * @param bool|WP_Error  $return
 559       * @param array          $theme
 560       * @return bool|WP_Error
 561       */
 562  	public function current_after( $return, $theme ) {
 563          if ( is_wp_error( $return ) ) {
 564              return $return;
 565          }
 566  
 567          $theme = isset( $theme['theme'] ) ? $theme['theme'] : '';
 568  
 569          if ( $theme != get_stylesheet() ) { // If not current
 570              return $return;
 571          }
 572  
 573          // Ensure stylesheet name hasn't changed after the upgrade:
 574          if ( $theme == get_stylesheet() && $theme != $this->result['destination_name'] ) {
 575              wp_clean_themes_cache();
 576              $stylesheet = $this->result['destination_name'];
 577              switch_theme( $stylesheet );
 578          }
 579  
 580          //Time to remove maintenance mode
 581          if ( ! $this->bulk ) {
 582              $this->maintenance_mode( false );
 583          }
 584          return $return;
 585      }
 586  
 587      /**
 588       * Delete the old theme during an upgrade.
 589       *
 590       * Hooked to the {@see 'upgrader_clear_destination'} filter by Theme_Upgrader::upgrade()
 591       * and Theme_Upgrader::bulk_upgrade().
 592       *
 593       * @since 2.8.0
 594       *
 595       * @global WP_Filesystem_Base $wp_filesystem Subclass
 596       *
 597       * @param bool   $removed
 598       * @param string $local_destination
 599       * @param string $remote_destination
 600       * @param array  $theme
 601       * @return bool
 602       */
 603  	public function delete_old_theme( $removed, $local_destination, $remote_destination, $theme ) {
 604          global $wp_filesystem;
 605  
 606          if ( is_wp_error( $removed ) ) {
 607              return $removed; // Pass errors through.
 608          }
 609  
 610          if ( ! isset( $theme['theme'] ) ) {
 611              return $removed;
 612          }
 613  
 614          $theme      = $theme['theme'];
 615          $themes_dir = trailingslashit( $wp_filesystem->wp_themes_dir( $theme ) );
 616          if ( $wp_filesystem->exists( $themes_dir . $theme ) ) {
 617              if ( ! $wp_filesystem->delete( $themes_dir . $theme, true ) ) {
 618                  return false;
 619              }
 620          }
 621  
 622          return true;
 623      }
 624  
 625      /**
 626       * Get the WP_Theme object for a theme.
 627       *
 628       * @since 2.8.0
 629       * @since 3.0.0 The `$theme` argument was added.
 630       *
 631       * @param string $theme The directory name of the theme. This is optional, and if not supplied,
 632       *                      the directory name from the last result will be used.
 633       * @return WP_Theme|false The theme's info object, or false `$theme` is not supplied
 634       *                        and the last result isn't set.
 635       */
 636  	public function theme_info( $theme = null ) {
 637  
 638          if ( empty( $theme ) ) {
 639              if ( ! empty( $this->result['destination_name'] ) ) {
 640                  $theme = $this->result['destination_name'];
 641              } else {
 642                  return false;
 643              }
 644          }
 645          return wp_get_theme( $theme );
 646      }
 647  
 648  }


Generated: Sun Sep 15 01:00:03 2019 Cross-referenced by PHPXref 0.7.1