Make WordPress Core

Ticket #22704: background-upgrader.php

File background-upgrader.php, 13.4 KB (added by dd32, 13 years ago)
Line 
1<?php
2
3class WP_Background_Upgrader {
4
5 static $skin;
6
7 private function __construct() {}
8 static function init() {
9
10 // Detect updates for Core, Plugins, and, Themes
11 add_action( 'set_site_transient_' . 'update_core', array( 'WP_Background_Upgrader', 'queue_updates_check_core' ) );
12 add_action( 'set_site_transient_' . 'update_plugins', array( 'WP_Background_Upgrader', 'queue_updates_check_plugins_themes' ) );
13 add_action( 'set_site_transient_' . 'update_themes', array( 'WP_Background_Upgrader', 'queue_updates_check_plugins_themes' ) );
14 // Uh.. Yeah, work around for #25213
15 add_action( 'set_site_transient_' . '_site_transient_' . 'update_core', array( 'WP_Background_Upgrader', 'queue_updates_check' ) );
16 add_action( 'set_site_transient_' . '_site_transient_' . 'update_plugins', array( 'WP_Background_Upgrader', 'queue_updates_check' ) );
17 add_action( 'set_site_transient_' . '_site_transient_' . 'update_themes', array( 'WP_Background_Upgrader', 'queue_updates_check' ) );
18
19 // Cron Updates
20 add_action( 'wp_background_upgrader_process', array( 'WP_Background_Upgrader', 'perform_queued_updates' ) );
21
22 // Temporary way of testing to see if the install is fataling
23 // Used with WP_Background_Upgrader::test_if_site_is_ok()
24 add_action( 'template_redirect', array( 'WP_Background_Upgrader','test_if_site_is_ok_callback' ) , 1 );
25
26 }
27
28 /**
29 * Loads the WordPress Admin environment for performing the updates.
30 * Needed because cron runs as a front-end process
31 */
32 static function load_required_admin_includes() {
33 // include ABSPATH . 'wp-admin/includes/admin.php'; // Load the whole lot of 'em
34 include ABSPATH . 'wp-admin/includes/update.php';
35 include ABSPATH . 'wp-admin/includes/plugin.php';
36 include ABSPATH . 'wp-admin/includes/plugin-install.php';
37 include ABSPATH . 'wp-admin/includes/theme.php';
38 include ABSPATH . 'wp-admin/includes/theme-install.php';
39 include ABSPATH . 'wp-admin/includes/file.php';
40 include ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
41 }
42
43 /**
44 * Finds the best auto-update available for this site.
45 * If there's 1.2.3 and 1.3 on offer, it'll choose 1.3 if the install allows it, else, 1.2.3
46 */
47 static function find_core_auto_update() {
48 $updates = get_site_transient( 'update_core' );
49 $auto_update = false;
50 foreach ( $updates->updates as $update ) {
51 if ( 'autoupdate' != $update->response )
52 continue;
53
54 if ( ! self::should_upgrade( 'core', $update, ABSPATH ) )
55 continue;
56
57 if ( ! $auto_update || version_compare( $update->current, $auto_update->current, '>' ) )
58 $auto_update = $update;
59 }
60 return $auto_update;
61 }
62
63 static function upgrader_disabled() {
64 // That's a no if you don't want files changes
65 if ( defined( 'DISABLE_FILE_MODS' ) && DISABLE_FILE_MODS )
66 return true;
67
68 if ( defined( 'AUTOMATIC_UPDATER_DISABLED' ) && AUTOMATIC_UPDATER_DISABLED )
69 return true;
70
71 return apply_filters( 'auto_upgrader_disabled', false );
72 }
73
74 /**
75 * Tests to see if we should upgrade a specific item, does not test to see if we CAN update the item.
76 */
77 static function should_upgrade( $type, $item, $context ) {
78
79 if ( self::upgrader_disabled() )
80 return false;
81
82 // ..and also check for GIT/SVN checkouts
83 if ( ! apply_filters( 'auto_upgrade_ignore_checkout_status', false ) ) {
84 $stop_dirs = array(
85 ABSPATH,
86 untrailingslashit( $context ),
87 );
88 if ( ! file_exists( ABSPATH . '/wp-config.php' ) ) // wp-config.php up one folder in a deployment situation
89 $stop_dirs[] = dirname( ABSPATH );
90 foreach ( array_unique( $stop_dirs ) as $dir ) {
91 if ( file_exists( $dir . '/.svn' ) || file_exists( $dir . '/.git' ) )
92 return false;
93 }
94 }
95
96 // Next up, do we actually have it enabled for this type of update?
97 switch ( $type ) {
98 default: $upgrade = false; break;
99 case 'core':
100 $upgrade = self::should_upgrade_to_core_version( $item->current );
101 break;
102 case 'plugin': $upgrade = false; break;
103 case 'theme': $upgrade = false; break;
104 case 'lang': $upgrade = false; break;
105 }
106
107 // And does the user / plugins want it?
108 // Plugins may filter on 'auto_upgrade_plugin', and check the 2nd param, $item, to only enable it for certain Plugins/Themes
109 if ( ! apply_filters( 'auto_upgrade_' . $type, $upgrade, $item ) )
110 return false;
111
112 // If it's a core update, are we actually compatible with it's requirements?
113 if ( 'core' == $type ) {
114 global $wpdb;
115
116 $php_compat = version_compare( phpversion(), $item->php_version, '>=' );
117 if ( file_exists( WP_CONTENT_DIR . '/db.php' ) && empty( $wpdb->is_mysql ) )
118 $mysql_compat = true;
119 else
120 $mysql_compat = version_compare( $wpdb->db_version(), $item->mysql_version, '>=' );
121
122 if ( ! $php_compat || ! $mysql_compat )
123 return false;
124 }
125
126 // Make sure loop-back HTTP requests work, we need to be able to verify everything worked OK and to be able to upgrade the database
127 // If this doesn't pass, chances are they don't want auto-updates anyway
128 if ( ! self::test_if_site_is_ok() )
129 return false;
130
131 return true;
132 }
133
134 // Checks to see if WP_Filesystem is setup to allow unattended upgrades
135 static function can_upgrade( $context ) {
136 if ( ! self::$skin )
137 self::$skin = new WP_Background_Upgrader_Skin();
138 return (bool) self::$skin->request_filesystem_credentials();
139 }
140
141 // Determines if this WordPress Core version should update to $offered_ver or not
142 static function should_upgrade_to_core_version( $offered_ver /* x.y.z */ ) {
143 include ABSPATH . WPINC . '/version.php'; // $wp_version; // x.y.z
144
145 $current_branch = implode( '.', array_slice( preg_split( '/[.-]/', $wp_version ), 0, 2 ) ); // x.y
146 $new_branch = implode( '.', array_slice( preg_split( '/[.-]/', $offered_ver ), 0, 2 ) ); // x.y
147 $current_is_development_version = (bool) strpos( $wp_version, '-' );
148
149 // Defaults:
150 $upgrade_dev = false;
151 $upgrade_minor = true;
152 $upgrade_major = false;
153
154 // WP_AUTO_UPDATE_CORE = true (all), 'minor', false.
155 if ( defined( 'WP_AUTO_UPDATE_CORE' ) ) {
156 if ( false === WP_AUTO_UPDATE_CORE ) {
157 // Defaults to turned off, unless a filter allows it
158 $upgrade_dev = $upgrade_minor = $upgrade_major = false;
159 } elseif ( true === WP_AUTO_UPDATE_CORE ) {
160 // ALL updates for core
161 $upgrade_dev = $upgrade_minor = $upgrade_major = true;
162 } elseif ( 'minor' === WP_AUTO_UPDATE_CORE ) {
163 // Only minor updates for core
164 $upgrade_dev = $upgrade_major = false;
165 $upgrade_minor = true;
166 }
167 }
168
169 // 1: If we're already on that version, not much point in updating?
170 if ( $offered_ver == $wp_version )
171 return false;
172
173 // 2: If we're running a newer version, that's a nope
174 if ( version_compare( $wp_version, $offered_ver, '>=' ) )
175 return false;
176
177 // 3: 3.7-alpha-25000 -> 3.7-alpha-25678 -> 3.7-beta1 -> 3.7-beta2
178 if ( $current_is_development_version ) {
179 if ( ! apply_filters( 'allow_dev_background_core_updates', $upgrade_dev ) )
180 return false;
181 // else fall through to minor + major branches below
182 }
183
184 // 4: Minor In-branch updates (3.7.0 -> 3.7.1 -> 3.7.2 -> 3.7.4)
185 if ( $current_branch == $new_branch )
186 return apply_filters( 'allow_minor_background_core_updates', $upgrade_minor );
187
188 // 5: Major version updates (3.7.0 -> 3.8.0 -> 3.9.1)
189 if ( version_compare( $new_branch, $current_branch, '>' ) )
190 return apply_filters( 'allow_major_background_core_updates', $upgrade_major );
191
192 // If we're not sure, we don't want it
193 return false;
194 }
195
196 static function upgrade( $type, $item ) {
197
198 wp_mail(
199 get_site_option( 'admin_email' ),
200 __METHOD__,
201 "Starting an upgrade for:\n\n" . var_export( compact( 'type', 'item' ), true ) . "\n\n" . wp_debug_backtrace_summary()
202 );
203
204 self::$skin = new Background_Upgrader_Skin();
205
206 switch( $type ) {
207 case 'core':
208 // Okay, Why does the Core upgrader not use the Upgrader's skin during the actual main part of the upgrade???
209 add_filter( 'update_feedback', function( $message ) {
210 WP_Background_Upgrader::$skin->feedback( $message );
211 } );
212 $upgrader = new Core_Upgrader( self::$skin );
213 $context = ABSPATH;
214 break;
215 case 'plugin':
216 $upgrader = new Plugin_Upgrader( self::$skin );
217 $context = WP_PLUGIN_DIR; // We don't support custom Plugin directories, or updates for WPMU_PLUGIN_DIR
218 break;
219 case 'theme':
220 $upgrader = new Theme_Upgrader( self::$skin );
221 $context = get_theme_root( $item );
222 break;
223 case 'lang':
224 return false; // Not quite yet!
225 // $upgrader = new Language_Upgrader( self::$skin );
226 $context = WP_LANG_DIR;
227 break;
228 }
229
230 // Determine if we can perform this upgrade or not
231 if ( ! self::should_upgrade( $type, $item, $context ) || ! self::can_upgrade( $context ) )
232 return false;
233
234 // Boom, This sites about to get a whole new splash of paint!
235 $upgrade_result = $upgrader->upgrade( $item, array(
236 'clear_update_cache' => false,
237 ) );
238
239 // Core doesn't output this, so lets append it so we don't get confused
240 if ( 'core' == $type ) {
241 if ( is_wp_error( $upgrade_result ) ) {
242 self::$skin->error( __( 'Installation Failed' ), $upgrade_result );
243 } else {
244 self::$skin->feedback( __( 'WordPress updated successfully' ) );
245 }
246 }
247
248 // Clear cache's and update results
249 switch ( $type ) {
250 case 'core':
251 delete_site_transient( 'update_core' );
252 break;
253 case 'theme':
254 wp_clean_themes_cache();
255 break;
256 case 'plugin':
257 wp_clean_plugins_cache();
258 break;
259 }
260
261 var_dump( compact( 'type', 'item', 'upgrader', 'upgrade_result' ) );
262
263 wp_mail(
264 get_site_option( 'admin_email' ),
265 __METHOD__,
266 var_export( array(
267 $upgrade_result,
268 $upgrader,
269 self::$skin,
270 ), true )
271 );
272
273 return $upgrade_result;
274 }
275
276 /**
277 * Queues a cron entry in the event that the automatic upgrader is enabled, and there's at least one potentially upgradable item
278 */
279 static function queue_updates_check( $updates ) {
280
281 if ( self::upgrader_disabled() )
282 return;
283
284 $plugin_updates = get_site_transient( 'update_plugins' );
285 $theme_updates = get_site_transient( 'update_themes' );
286 if ( ! self::find_core_auto_update() && empty( $plugin_updates->response ) && empty( $theme_updates->response ) )
287 return;
288
289 if ( ! wp_next_scheduled( 'wp_background_upgrader_process' ) ) {
290 // If the transient update was triggered by a user pageview, update in an hours time, else, now.
291 $when_to_update = get_current_user_id() ? time() + HOUR_IN_SECONDS : time();
292 $when_to_update = apply_filters( 'auto_upgrade_when_to_upgrade', $when_to_update );
293
294 wp_schedule_single_event( $when_to_update, 'wp_background_upgrader_process' );
295 }
296
297 }
298
299 /**
300 * Kicks off a upgrade request for each item in the upgrade "queue"
301 */
302 static function perform_queued_updates() {
303
304 $lock_name = 'auto_upgrader.lock';
305 if ( get_site_transient( $lock_name ) ) {
306 // Test to see if it was set more than an hour ago, if so, cleanup.
307 if ( true || get_site_transient( $lock_name ) < ( time() - HOUR_IN_SECONDS ) )
308 delete_site_transient( $lock_name );
309 else // Recent lock
310 return;
311 }
312 // Lock upgrades for us for half an hour
313 if ( ! set_site_transient( $lock_name, microtime( true ), HOUR_IN_SECONDS / 2 ) )
314 return;
315
316 // Admin-ize ourselves
317 self::load_required_admin_includes();
318
319 // Next, Plugins
320 wp_update_plugins(); // Check for Plugin updates
321 $plugin_updates = get_site_transient( 'update_plugins' );
322 if ( $plugin_updates && !empty( $plugin_updates->response ) ) {
323 foreach ( array_keys( $plugin_updates->response ) as $plugin ) {
324 self::upgrade( 'plugin', $plugin );
325 }
326 // Force refresh of plugin update information
327 wp_clean_plugins_cache();
328 }
329
330 // Next, those themes we all love
331 wp_update_themes(); // Check for Theme updates
332 $theme_updates = get_site_transient( 'update_themes' );
333 if ( $theme_updates && !empty( $theme_updates->response ) ) {
334 foreach ( array_keys( $theme_updates->response ) as $theme ) {
335 self::upgrade( 'theme', $theme );
336 }
337 // Force refresh of theme update information
338 wp_clean_themes_cache();
339 }
340
341 // Up first, Core.
342 wp_version_check(); // Check for Core updates
343 $core_update = self::find_core_auto_update();
344 if ( $core_update )
345 self::upgrade( 'core', $core_update );
346
347 // Cleanup, These won't trigger any updates this time due to the locking transient
348 wp_version_check(); // check for Core updates
349 wp_update_themes(); // Check for Theme updates
350 wp_update_plugins(); // Check for Plugin updates
351
352 delete_site_transient( $lock_name );
353
354 }
355
356 /**
357 * Make a loopback HTTP request to admin-ajax.php and the blogs front-end to ensure
358 * that the blog isn't fataling, or encountering issues after the update
359 */
360 static function test_if_site_is_ok() {
361 // Is the Admin ajax handler returning what we expect of it?
362 $http_result = wp_remote_post( admin_url( 'admin-ajax.php' ), array( 'timeout' => 10 ) );
363 $admin_ok = ! is_wp_error( $http_result ) && ( '0' === wp_remote_retrieve_body( $http_result ) );
364
365 if ( ! $admin_ok )
366 return false;
367
368 // Is the front-end returning what we'd expect of it? (Note, this is a filter on template_redirect + wp_footer below)
369 // This WILL break with caching plugins, a POST should make it past most caching plugins, but I suspect not all of them
370 $http_result = wp_remote_post( site_url( '?test-upgrade-OK=1' ), array( 'timeout' => 10 ) );
371 $front_ok = ! is_wp_error( $http_result ) && ( 'OK' === wp_remote_retrieve_body( $http_result ) );
372
373 if ( ! $front_ok )
374 return false;
375
376 return true;
377 }
378
379 static function test_if_site_is_ok_callback() {
380 if ( ! isset( $_GET['test-upgrade-OK'] ) )
381 return;
382
383 ob_start();
384 add_action( 'wp_footer', function() {
385 ob_end_clean();
386 die( 'OK' );
387 }, 999 );
388 }
389}
390WP_Background_Upgrader::init();