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