You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

605 lines
19 KiB

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