/wp-admin/includes/class-theme-upgrader.php

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