home *** CD-ROM | disk | FTP | other *** search
/ HTML Examples / WP.iso / wordpress2 / wp-admin / includes / class-theme-upgrader.php < prev    next >
Encoding:
PHP Script  |  2017-10-18  |  19.0 KB  |  594 lines

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