~canonical-sysadmins/wordpress/4.8.1

« back to all changes in this revision

Viewing changes to wp-admin/includes/class-wp-automatic-updater.php

  • Committer: Barry Price
  • Date: 2016-08-17 04:50:12 UTC
  • mfrom: (1.1.18 upstream)
  • Revision ID: barry.price@canonical.com-20160817045012-qfui81zhqnqv2ba9
Merge WP4.6 from upstream

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
<?php
 
2
/**
 
3
 * Upgrade API: WP_Automatic_Updater class
 
4
 *
 
5
 * @package WordPress
 
6
 * @subpackage Upgrader
 
7
 * @since 4.6.0
 
8
 */
 
9
 
 
10
/**
 
11
 * Core class used for handling automatic background updates.
 
12
 *
 
13
 * @since 3.7.0
 
14
 * @since 4.6.0 Moved to its own file from wp-admin/includes/class-wp-upgrader.php.
 
15
 */
 
16
class WP_Automatic_Updater {
 
17
 
 
18
        /**
 
19
         * Tracks update results during processing.
 
20
         *
 
21
         * @var array
 
22
         * @access protected
 
23
         */
 
24
        protected $update_results = array();
 
25
 
 
26
        /**
 
27
         * Whether the entire automatic updater is disabled.
 
28
         *
 
29
         * @since 3.7.0
 
30
         * @access public
 
31
         */
 
32
        public function is_disabled() {
 
33
                // Background updates are disabled if you don't want file changes.
 
34
                if ( defined( 'DISALLOW_FILE_MODS' ) && DISALLOW_FILE_MODS )
 
35
                        return true;
 
36
 
 
37
                if ( wp_installing() )
 
38
                        return true;
 
39
 
 
40
                // More fine grained control can be done through the WP_AUTO_UPDATE_CORE constant and filters.
 
41
                $disabled = defined( 'AUTOMATIC_UPDATER_DISABLED' ) && AUTOMATIC_UPDATER_DISABLED;
 
42
 
 
43
                /**
 
44
                 * Filters whether to entirely disable background updates.
 
45
                 *
 
46
                 * There are more fine-grained filters and controls for selective disabling.
 
47
                 * This filter parallels the AUTOMATIC_UPDATER_DISABLED constant in name.
 
48
                 *
 
49
                 * This also disables update notification emails. That may change in the future.
 
50
                 *
 
51
                 * @since 3.7.0
 
52
                 *
 
53
                 * @param bool $disabled Whether the updater should be disabled.
 
54
                 */
 
55
                return apply_filters( 'automatic_updater_disabled', $disabled );
 
56
        }
 
57
 
 
58
        /**
 
59
         * Check for version control checkouts.
 
60
         *
 
61
         * Checks for Subversion, Git, Mercurial, and Bazaar. It recursively looks up the
 
62
         * filesystem to the top of the drive, erring on the side of detecting a VCS
 
63
         * checkout somewhere.
 
64
         *
 
65
         * ABSPATH is always checked in addition to whatever $context is (which may be the
 
66
         * wp-content directory, for example). The underlying assumption is that if you are
 
67
         * using version control *anywhere*, then you should be making decisions for
 
68
         * how things get updated.
 
69
         *
 
70
         * @since 3.7.0
 
71
         * @access public
 
72
         *
 
73
         * @param string $context The filesystem path to check, in addition to ABSPATH.
 
74
         */
 
75
        public function is_vcs_checkout( $context ) {
 
76
                $context_dirs = array( untrailingslashit( $context ) );
 
77
                if ( $context !== ABSPATH )
 
78
                        $context_dirs[] = untrailingslashit( ABSPATH );
 
79
 
 
80
                $vcs_dirs = array( '.svn', '.git', '.hg', '.bzr' );
 
81
                $check_dirs = array();
 
82
 
 
83
                foreach ( $context_dirs as $context_dir ) {
 
84
                        // Walk up from $context_dir to the root.
 
85
                        do {
 
86
                                $check_dirs[] = $context_dir;
 
87
 
 
88
                                // Once we've hit '/' or 'C:\', we need to stop. dirname will keep returning the input here.
 
89
                                if ( $context_dir == dirname( $context_dir ) )
 
90
                                        break;
 
91
 
 
92
                        // Continue one level at a time.
 
93
                        } while ( $context_dir = dirname( $context_dir ) );
 
94
                }
 
95
 
 
96
                $check_dirs = array_unique( $check_dirs );
 
97
 
 
98
                // Search all directories we've found for evidence of version control.
 
99
                foreach ( $vcs_dirs as $vcs_dir ) {
 
100
                        foreach ( $check_dirs as $check_dir ) {
 
101
                                if ( $checkout = @is_dir( rtrim( $check_dir, '\\/' ) . "/$vcs_dir" ) )
 
102
                                        break 2;
 
103
                        }
 
104
                }
 
105
 
 
106
                /**
 
107
                 * Filters whether the automatic updater should consider a filesystem
 
108
                 * location to be potentially managed by a version control system.
 
109
                 *
 
110
                 * @since 3.7.0
 
111
                 *
 
112
                 * @param bool $checkout  Whether a VCS checkout was discovered at $context
 
113
                 *                        or ABSPATH, or anywhere higher.
 
114
                 * @param string $context The filesystem context (a path) against which
 
115
                 *                        filesystem status should be checked.
 
116
                 */
 
117
                return apply_filters( 'automatic_updates_is_vcs_checkout', $checkout, $context );
 
118
        }
 
119
 
 
120
        /**
 
121
         * Tests to see if we can and should update a specific item.
 
122
         *
 
123
         * @since 3.7.0
 
124
         * @access public
 
125
         *
 
126
         * @global wpdb $wpdb WordPress database abstraction object.
 
127
         *
 
128
         * @param string $type    The type of update being checked: 'core', 'theme',
 
129
         *                        'plugin', 'translation'.
 
130
         * @param object $item    The update offer.
 
131
         * @param string $context The filesystem context (a path) against which filesystem
 
132
         *                        access and status should be checked.
 
133
         */
 
134
        public function should_update( $type, $item, $context ) {
 
135
                // Used to see if WP_Filesystem is set up to allow unattended updates.
 
136
                $skin = new Automatic_Upgrader_Skin;
 
137
 
 
138
                if ( $this->is_disabled() )
 
139
                        return false;
 
140
 
 
141
                // Only relax the filesystem checks when the update doesn't include new files
 
142
                $allow_relaxed_file_ownership = false;
 
143
                if ( 'core' == $type && isset( $item->new_files ) && ! $item->new_files ) {
 
144
                        $allow_relaxed_file_ownership = true;
 
145
                }
 
146
 
 
147
                // If we can't do an auto core update, we may still be able to email the user.
 
148
                if ( ! $skin->request_filesystem_credentials( false, $context, $allow_relaxed_file_ownership ) || $this->is_vcs_checkout( $context ) ) {
 
149
                        if ( 'core' == $type )
 
150
                                $this->send_core_update_notification_email( $item );
 
151
                        return false;
 
152
                }
 
153
 
 
154
                // Next up, is this an item we can update?
 
155
                if ( 'core' == $type )
 
156
                        $update = Core_Upgrader::should_update_to_version( $item->current );
 
157
                else
 
158
                        $update = ! empty( $item->autoupdate );
 
159
 
 
160
                /**
 
161
                 * Filters whether to automatically update core, a plugin, a theme, or a language.
 
162
                 *
 
163
                 * The dynamic portion of the hook name, `$type`, refers to the type of update
 
164
                 * being checked. Can be 'core', 'theme', 'plugin', or 'translation'.
 
165
                 *
 
166
                 * Generally speaking, plugins, themes, and major core versions are not updated
 
167
                 * by default, while translations and minor and development versions for core
 
168
                 * are updated by default.
 
169
                 *
 
170
                 * See the {@see 'allow_dev_auto_core_updates', {@see 'allow_minor_auto_core_updates'},
 
171
                 * and {@see 'allow_major_auto_core_updates'} filters for a more straightforward way to
 
172
                 * adjust core updates.
 
173
                 *
 
174
                 * @since 3.7.0
 
175
                 *
 
176
                 * @param bool   $update Whether to update.
 
177
                 * @param object $item   The update offer.
 
178
                 */
 
179
                $update = apply_filters( 'auto_update_' . $type, $update, $item );
 
180
 
 
181
                if ( ! $update ) {
 
182
                        if ( 'core' == $type )
 
183
                                $this->send_core_update_notification_email( $item );
 
184
                        return false;
 
185
                }
 
186
 
 
187
                // If it's a core update, are we actually compatible with its requirements?
 
188
                if ( 'core' == $type ) {
 
189
                        global $wpdb;
 
190
 
 
191
                        $php_compat = version_compare( phpversion(), $item->php_version, '>=' );
 
192
                        if ( file_exists( WP_CONTENT_DIR . '/db.php' ) && empty( $wpdb->is_mysql ) )
 
193
                                $mysql_compat = true;
 
194
                        else
 
195
                                $mysql_compat = version_compare( $wpdb->db_version(), $item->mysql_version, '>=' );
 
196
 
 
197
                        if ( ! $php_compat || ! $mysql_compat )
 
198
                                return false;
 
199
                }
 
200
 
 
201
                return true;
 
202
        }
 
203
 
 
204
        /**
 
205
         * Notifies an administrator of a core update.
 
206
         *
 
207
         * @since 3.7.0
 
208
         * @access protected
 
209
         *
 
210
         * @param object $item The update offer.
 
211
         */
 
212
        protected function send_core_update_notification_email( $item ) {
 
213
                $notified = get_site_option( 'auto_core_update_notified' );
 
214
 
 
215
                // Don't notify if we've already notified the same email address of the same version.
 
216
                if ( $notified && $notified['email'] == get_site_option( 'admin_email' ) && $notified['version'] == $item->current )
 
217
                        return false;
 
218
 
 
219
                // See if we need to notify users of a core update.
 
220
                $notify = ! empty( $item->notify_email );
 
221
 
 
222
                /**
 
223
                 * Filters whether to notify the site administrator of a new core update.
 
224
                 *
 
225
                 * By default, administrators are notified when the update offer received
 
226
                 * from WordPress.org sets a particular flag. This allows some discretion
 
227
                 * in if and when to notify.
 
228
                 *
 
229
                 * This filter is only evaluated once per release. If the same email address
 
230
                 * was already notified of the same new version, WordPress won't repeatedly
 
231
                 * email the administrator.
 
232
                 *
 
233
                 * This filter is also used on about.php to check if a plugin has disabled
 
234
                 * these notifications.
 
235
                 *
 
236
                 * @since 3.7.0
 
237
                 *
 
238
                 * @param bool   $notify Whether the site administrator is notified.
 
239
                 * @param object $item   The update offer.
 
240
                 */
 
241
                if ( ! apply_filters( 'send_core_update_notification_email', $notify, $item ) )
 
242
                        return false;
 
243
 
 
244
                $this->send_email( 'manual', $item );
 
245
                return true;
 
246
        }
 
247
 
 
248
        /**
 
249
         * Update an item, if appropriate.
 
250
         *
 
251
         * @since 3.7.0
 
252
         * @access public
 
253
         *
 
254
         * @param string $type The type of update being checked: 'core', 'theme', 'plugin', 'translation'.
 
255
         * @param object $item The update offer.
 
256
         *
 
257
         * @return null|WP_Error
 
258
         */
 
259
        public function update( $type, $item ) {
 
260
                $skin = new Automatic_Upgrader_Skin;
 
261
 
 
262
                switch ( $type ) {
 
263
                        case 'core':
 
264
                                // The Core upgrader doesn't use the Upgrader's skin during the actual main part of the upgrade, instead, firing a filter.
 
265
                                add_filter( 'update_feedback', array( $skin, 'feedback' ) );
 
266
                                $upgrader = new Core_Upgrader( $skin );
 
267
                                $context  = ABSPATH;
 
268
                                break;
 
269
                        case 'plugin':
 
270
                                $upgrader = new Plugin_Upgrader( $skin );
 
271
                                $context  = WP_PLUGIN_DIR; // We don't support custom Plugin directories, or updates for WPMU_PLUGIN_DIR
 
272
                                break;
 
273
                        case 'theme':
 
274
                                $upgrader = new Theme_Upgrader( $skin );
 
275
                                $context  = get_theme_root( $item->theme );
 
276
                                break;
 
277
                        case 'translation':
 
278
                                $upgrader = new Language_Pack_Upgrader( $skin );
 
279
                                $context  = WP_CONTENT_DIR; // WP_LANG_DIR;
 
280
                                break;
 
281
                }
 
282
 
 
283
                // Determine whether we can and should perform this update.
 
284
                if ( ! $this->should_update( $type, $item, $context ) )
 
285
                        return false;
 
286
 
 
287
                /**
 
288
                 * Fires immediately prior to an auto-update.
 
289
                 *
 
290
                 * @since 4.4.0
 
291
                 *
 
292
                 * @param string $type    The type of update being checked: 'core', 'theme', 'plugin', or 'translation'.
 
293
                 * @param object $item    The update offer.
 
294
                 * @param string $context The filesystem context (a path) against which filesystem access and status
 
295
                 *                        should be checked.
 
296
                 */
 
297
                do_action( 'pre_auto_update', $type, $item, $context );
 
298
 
 
299
                $upgrader_item = $item;
 
300
                switch ( $type ) {
 
301
                        case 'core':
 
302
                                $skin->feedback( __( 'Updating to WordPress %s' ), $item->version );
 
303
                                $item_name = sprintf( __( 'WordPress %s' ), $item->version );
 
304
                                break;
 
305
                        case 'theme':
 
306
                                $upgrader_item = $item->theme;
 
307
                                $theme = wp_get_theme( $upgrader_item );
 
308
                                $item_name = $theme->Get( 'Name' );
 
309
                                $skin->feedback( __( 'Updating theme: %s' ), $item_name );
 
310
                                break;
 
311
                        case 'plugin':
 
312
                                $upgrader_item = $item->plugin;
 
313
                                $plugin_data = get_plugin_data( $context . '/' . $upgrader_item );
 
314
                                $item_name = $plugin_data['Name'];
 
315
                                $skin->feedback( __( 'Updating plugin: %s' ), $item_name );
 
316
                                break;
 
317
                        case 'translation':
 
318
                                $language_item_name = $upgrader->get_name_for_update( $item );
 
319
                                $item_name = sprintf( __( 'Translations for %s' ), $language_item_name );
 
320
                                $skin->feedback( sprintf( __( 'Updating translations for %1$s (%2$s)&#8230;' ), $language_item_name, $item->language ) );
 
321
                                break;
 
322
                }
 
323
 
 
324
                $allow_relaxed_file_ownership = false;
 
325
                if ( 'core' == $type && isset( $item->new_files ) && ! $item->new_files ) {
 
326
                        $allow_relaxed_file_ownership = true;
 
327
                }
 
328
 
 
329
                // Boom, This sites about to get a whole new splash of paint!
 
330
                $upgrade_result = $upgrader->upgrade( $upgrader_item, array(
 
331
                        'clear_update_cache' => false,
 
332
                        // Always use partial builds if possible for core updates.
 
333
                        'pre_check_md5'      => false,
 
334
                        // Only available for core updates.
 
335
                        'attempt_rollback'   => true,
 
336
                        // Allow relaxed file ownership in some scenarios
 
337
                        'allow_relaxed_file_ownership' => $allow_relaxed_file_ownership,
 
338
                ) );
 
339
 
 
340
                // If the filesystem is unavailable, false is returned.
 
341
                if ( false === $upgrade_result ) {
 
342
                        $upgrade_result = new WP_Error( 'fs_unavailable', __( 'Could not access filesystem.' ) );
 
343
                }
 
344
 
 
345
                if ( 'core' == $type ) {
 
346
                        if ( is_wp_error( $upgrade_result ) && ( 'up_to_date' == $upgrade_result->get_error_code() || 'locked' == $upgrade_result->get_error_code() ) ) {
 
347
                                // These aren't actual errors, treat it as a skipped-update instead to avoid triggering the post-core update failure routines.
 
348
                                return false;
 
349
                        }
 
350
 
 
351
                        // Core doesn't output this, so let's append it so we don't get confused.
 
352
                        if ( is_wp_error( $upgrade_result ) ) {
 
353
                                $skin->error( __( 'Installation Failed' ), $upgrade_result );
 
354
                        } else {
 
355
                                $skin->feedback( __( 'WordPress updated successfully' ) );
 
356
                        }
 
357
                }
 
358
 
 
359
                $this->update_results[ $type ][] = (object) array(
 
360
                        'item'     => $item,
 
361
                        'result'   => $upgrade_result,
 
362
                        'name'     => $item_name,
 
363
                        'messages' => $skin->get_upgrade_messages()
 
364
                );
 
365
 
 
366
                return $upgrade_result;
 
367
        }
 
368
 
 
369
        /**
 
370
         * Kicks off the background update process, looping through all pending updates.
 
371
         *
 
372
         * @since 3.7.0
 
373
         * @access public
 
374
         *
 
375
         * @global wpdb   $wpdb
 
376
         * @global string $wp_version
 
377
         */
 
378
        public function run() {
 
379
                global $wpdb, $wp_version;
 
380
 
 
381
                if ( $this->is_disabled() )
 
382
                        return;
 
383
 
 
384
                if ( ! is_main_network() || ! is_main_site() )
 
385
                        return;
 
386
 
 
387
                if ( ! WP_Upgrader::create_lock( 'auto_updater' ) )
 
388
                        return;
 
389
 
 
390
                // Don't automatically run these thins, as we'll handle it ourselves
 
391
                remove_action( 'upgrader_process_complete', array( 'Language_Pack_Upgrader', 'async_upgrade' ), 20 );
 
392
                remove_action( 'upgrader_process_complete', 'wp_version_check' );
 
393
                remove_action( 'upgrader_process_complete', 'wp_update_plugins' );
 
394
                remove_action( 'upgrader_process_complete', 'wp_update_themes' );
 
395
 
 
396
                // Next, Plugins
 
397
                wp_update_plugins(); // Check for Plugin updates
 
398
                $plugin_updates = get_site_transient( 'update_plugins' );
 
399
                if ( $plugin_updates && !empty( $plugin_updates->response ) ) {
 
400
                        foreach ( $plugin_updates->response as $plugin ) {
 
401
                                $this->update( 'plugin', $plugin );
 
402
                        }
 
403
                        // Force refresh of plugin update information
 
404
                        wp_clean_plugins_cache();
 
405
                }
 
406
 
 
407
                // Next, those themes we all love
 
408
                wp_update_themes();  // Check for Theme updates
 
409
                $theme_updates = get_site_transient( 'update_themes' );
 
410
                if ( $theme_updates && !empty( $theme_updates->response ) ) {
 
411
                        foreach ( $theme_updates->response as $theme ) {
 
412
                                $this->update( 'theme', (object) $theme );
 
413
                        }
 
414
                        // Force refresh of theme update information
 
415
                        wp_clean_themes_cache();
 
416
                }
 
417
 
 
418
                // Next, Process any core update
 
419
                wp_version_check(); // Check for Core updates
 
420
                $core_update = find_core_auto_update();
 
421
 
 
422
                if ( $core_update )
 
423
                        $this->update( 'core', $core_update );
 
424
 
 
425
                // Clean up, and check for any pending translations
 
426
                // (Core_Upgrader checks for core updates)
 
427
                $theme_stats = array();
 
428
                if ( isset( $this->update_results['theme'] ) ) {
 
429
                        foreach ( $this->update_results['theme'] as $upgrade ) {
 
430
                                $theme_stats[ $upgrade->item->theme ] = ( true === $upgrade->result );
 
431
                        }
 
432
                }
 
433
                wp_update_themes( $theme_stats );  // Check for Theme updates
 
434
 
 
435
                $plugin_stats = array();
 
436
                if ( isset( $this->update_results['plugin'] ) ) {
 
437
                        foreach ( $this->update_results['plugin'] as $upgrade ) {
 
438
                                $plugin_stats[ $upgrade->item->plugin ] = ( true === $upgrade->result );
 
439
                        }
 
440
                }
 
441
                wp_update_plugins( $plugin_stats ); // Check for Plugin updates
 
442
 
 
443
                // Finally, Process any new translations
 
444
                $language_updates = wp_get_translation_updates();
 
445
                if ( $language_updates ) {
 
446
                        foreach ( $language_updates as $update ) {
 
447
                                $this->update( 'translation', $update );
 
448
                        }
 
449
 
 
450
                        // Clear existing caches
 
451
                        wp_clean_update_cache();
 
452
 
 
453
                        wp_version_check();  // check for Core updates
 
454
                        wp_update_themes();  // Check for Theme updates
 
455
                        wp_update_plugins(); // Check for Plugin updates
 
456
                }
 
457
 
 
458
                // Send debugging email to all development installs.
 
459
                if ( ! empty( $this->update_results ) ) {
 
460
                        $development_version = false !== strpos( $wp_version, '-' );
 
461
 
 
462
                        /**
 
463
                         * Filters whether to send a debugging email for each automatic background update.
 
464
                         *
 
465
                         * @since 3.7.0
 
466
                         *
 
467
                         * @param bool $development_version By default, emails are sent if the
 
468
                         *                                  install is a development version.
 
469
                         *                                  Return false to avoid the email.
 
470
                         */
 
471
                        if ( apply_filters( 'automatic_updates_send_debug_email', $development_version ) )
 
472
                                $this->send_debug_email();
 
473
 
 
474
                        if ( ! empty( $this->update_results['core'] ) )
 
475
                                $this->after_core_update( $this->update_results['core'][0] );
 
476
 
 
477
                        /**
 
478
                         * Fires after all automatic updates have run.
 
479
                         *
 
480
                         * @since 3.8.0
 
481
                         *
 
482
                         * @param array $update_results The results of all attempted updates.
 
483
                         */
 
484
                        do_action( 'automatic_updates_complete', $this->update_results );
 
485
                }
 
486
 
 
487
                WP_Upgrader::release_lock( 'auto_updater' );
 
488
        }
 
489
 
 
490
        /**
 
491
         * If we tried to perform a core update, check if we should send an email,
 
492
         * and if we need to avoid processing future updates.
 
493
         *
 
494
         * @since Unknown
 
495
         * @access protected
 
496
         *
 
497
         * @global string $wp_version
 
498
         *
 
499
         * @param object $update_result The result of the core update. Includes the update offer and result.
 
500
         */
 
501
        protected function after_core_update( $update_result ) {
 
502
                global $wp_version;
 
503
 
 
504
                $core_update = $update_result->item;
 
505
                $result      = $update_result->result;
 
506
 
 
507
                if ( ! is_wp_error( $result ) ) {
 
508
                        $this->send_email( 'success', $core_update );
 
509
                        return;
 
510
                }
 
511
 
 
512
                $error_code = $result->get_error_code();
 
513
 
 
514
                // Any of these WP_Error codes are critical failures, as in they occurred after we started to copy core files.
 
515
                // We should not try to perform a background update again until there is a successful one-click update performed by the user.
 
516
                $critical = false;
 
517
                if ( $error_code === 'disk_full' || false !== strpos( $error_code, '__copy_dir' ) ) {
 
518
                        $critical = true;
 
519
                } elseif ( $error_code === 'rollback_was_required' && is_wp_error( $result->get_error_data()->rollback ) ) {
 
520
                        // A rollback is only critical if it failed too.
 
521
                        $critical = true;
 
522
                        $rollback_result = $result->get_error_data()->rollback;
 
523
                } elseif ( false !== strpos( $error_code, 'do_rollback' ) ) {
 
524
                        $critical = true;
 
525
                }
 
526
 
 
527
                if ( $critical ) {
 
528
                        $critical_data = array(
 
529
                                'attempted'  => $core_update->current,
 
530
                                'current'    => $wp_version,
 
531
                                'error_code' => $error_code,
 
532
                                'error_data' => $result->get_error_data(),
 
533
                                'timestamp'  => time(),
 
534
                                'critical'   => true,
 
535
                        );
 
536
                        if ( isset( $rollback_result ) ) {
 
537
                                $critical_data['rollback_code'] = $rollback_result->get_error_code();
 
538
                                $critical_data['rollback_data'] = $rollback_result->get_error_data();
 
539
                        }
 
540
                        update_site_option( 'auto_core_update_failed', $critical_data );
 
541
                        $this->send_email( 'critical', $core_update, $result );
 
542
                        return;
 
543
                }
 
544
 
 
545
                /*
 
546
                 * Any other WP_Error code (like download_failed or files_not_writable) occurs before
 
547
                 * we tried to copy over core files. Thus, the failures are early and graceful.
 
548
                 *
 
549
                 * We should avoid trying to perform a background update again for the same version.
 
550
                 * But we can try again if another version is released.
 
551
                 *
 
552
                 * For certain 'transient' failures, like download_failed, we should allow retries.
 
553
                 * In fact, let's schedule a special update for an hour from now. (It's possible
 
554
                 * the issue could actually be on WordPress.org's side.) If that one fails, then email.
 
555
                 */
 
556
                $send = true;
 
557
                $transient_failures = array( 'incompatible_archive', 'download_failed', 'insane_distro', 'locked' );
 
558
                if ( in_array( $error_code, $transient_failures ) && ! get_site_option( 'auto_core_update_failed' ) ) {
 
559
                        wp_schedule_single_event( time() + HOUR_IN_SECONDS, 'wp_maybe_auto_update' );
 
560
                        $send = false;
 
561
                }
 
562
 
 
563
                $n = get_site_option( 'auto_core_update_notified' );
 
564
                // Don't notify if we've already notified the same email address of the same version of the same notification type.
 
565
                if ( $n && 'fail' == $n['type'] && $n['email'] == get_site_option( 'admin_email' ) && $n['version'] == $core_update->current )
 
566
                        $send = false;
 
567
 
 
568
                update_site_option( 'auto_core_update_failed', array(
 
569
                        'attempted'  => $core_update->current,
 
570
                        'current'    => $wp_version,
 
571
                        'error_code' => $error_code,
 
572
                        'error_data' => $result->get_error_data(),
 
573
                        'timestamp'  => time(),
 
574
                        'retry'      => in_array( $error_code, $transient_failures ),
 
575
                ) );
 
576
 
 
577
                if ( $send )
 
578
                        $this->send_email( 'fail', $core_update, $result );
 
579
        }
 
580
 
 
581
        /**
 
582
         * Sends an email upon the completion or failure of a background core update.
 
583
         *
 
584
         * @since 3.7.0
 
585
         * @access protected
 
586
         *
 
587
         * @global string $wp_version
 
588
         *
 
589
         * @param string $type        The type of email to send. Can be one of 'success', 'fail', 'manual', 'critical'.
 
590
         * @param object $core_update The update offer that was attempted.
 
591
         * @param mixed  $result      Optional. The result for the core update. Can be WP_Error.
 
592
         */
 
593
        protected function send_email( $type, $core_update, $result = null ) {
 
594
                update_site_option( 'auto_core_update_notified', array(
 
595
                        'type'      => $type,
 
596
                        'email'     => get_site_option( 'admin_email' ),
 
597
                        'version'   => $core_update->current,
 
598
                        'timestamp' => time(),
 
599
                ) );
 
600
 
 
601
                $next_user_core_update = get_preferred_from_update_core();
 
602
                // If the update transient is empty, use the update we just performed
 
603
                if ( ! $next_user_core_update )
 
604
                        $next_user_core_update = $core_update;
 
605
                $newer_version_available = ( 'upgrade' == $next_user_core_update->response && version_compare( $next_user_core_update->version, $core_update->version, '>' ) );
 
606
 
 
607
                /**
 
608
                 * Filters whether to send an email following an automatic background core update.
 
609
                 *
 
610
                 * @since 3.7.0
 
611
                 *
 
612
                 * @param bool   $send        Whether to send the email. Default true.
 
613
                 * @param string $type        The type of email to send. Can be one of
 
614
                 *                            'success', 'fail', 'critical'.
 
615
                 * @param object $core_update The update offer that was attempted.
 
616
                 * @param mixed  $result      The result for the core update. Can be WP_Error.
 
617
                 */
 
618
                if ( 'manual' !== $type && ! apply_filters( 'auto_core_update_send_email', true, $type, $core_update, $result ) )
 
619
                        return;
 
620
 
 
621
                switch ( $type ) {
 
622
                        case 'success' : // We updated.
 
623
                                /* translators: 1: Site name, 2: WordPress version number. */
 
624
                                $subject = __( '[%1$s] Your site has updated to WordPress %2$s' );
 
625
                                break;
 
626
 
 
627
                        case 'fail' :   // We tried to update but couldn't.
 
628
                        case 'manual' : // We can't update (and made no attempt).
 
629
                                /* translators: 1: Site name, 2: WordPress version number. */
 
630
                                $subject = __( '[%1$s] WordPress %2$s is available. Please update!' );
 
631
                                break;
 
632
 
 
633
                        case 'critical' : // We tried to update, started to copy files, then things went wrong.
 
634
                                /* translators: 1: Site name. */
 
635
                                $subject = __( '[%1$s] URGENT: Your site may be down due to a failed update' );
 
636
                                break;
 
637
 
 
638
                        default :
 
639
                                return;
 
640
                }
 
641
 
 
642
                // If the auto update is not to the latest version, say that the current version of WP is available instead.
 
643
                $version = 'success' === $type ? $core_update->current : $next_user_core_update->current;
 
644
                $subject = sprintf( $subject, wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES ), $version );
 
645
 
 
646
                $body = '';
 
647
 
 
648
                switch ( $type ) {
 
649
                        case 'success' :
 
650
                                $body .= sprintf( __( 'Howdy! Your site at %1$s has been updated automatically to WordPress %2$s.' ), home_url(), $core_update->current );
 
651
                                $body .= "\n\n";
 
652
                                if ( ! $newer_version_available )
 
653
                                        $body .= __( 'No further action is needed on your part.' ) . ' ';
 
654
 
 
655
                                // Can only reference the About screen if their update was successful.
 
656
                                list( $about_version ) = explode( '-', $core_update->current, 2 );
 
657
                                $body .= sprintf( __( "For more on version %s, see the About WordPress screen:" ), $about_version );
 
658
                                $body .= "\n" . admin_url( 'about.php' );
 
659
 
 
660
                                if ( $newer_version_available ) {
 
661
                                        $body .= "\n\n" . sprintf( __( 'WordPress %s is also now available.' ), $next_user_core_update->current ) . ' ';
 
662
                                        $body .= __( 'Updating is easy and only takes a few moments:' );
 
663
                                        $body .= "\n" . network_admin_url( 'update-core.php' );
 
664
                                }
 
665
 
 
666
                                break;
 
667
 
 
668
                        case 'fail' :
 
669
                        case 'manual' :
 
670
                                $body .= sprintf( __( 'Please update your site at %1$s to WordPress %2$s.' ), home_url(), $next_user_core_update->current );
 
671
 
 
672
                                $body .= "\n\n";
 
673
 
 
674
                                // Don't show this message if there is a newer version available.
 
675
                                // Potential for confusion, and also not useful for them to know at this point.
 
676
                                if ( 'fail' == $type && ! $newer_version_available )
 
677
                                        $body .= __( 'We tried but were unable to update your site automatically.' ) . ' ';
 
678
 
 
679
                                $body .= __( 'Updating is easy and only takes a few moments:' );
 
680
                                $body .= "\n" . network_admin_url( 'update-core.php' );
 
681
                                break;
 
682
 
 
683
                        case 'critical' :
 
684
                                if ( $newer_version_available )
 
685
                                        $body .= sprintf( __( 'Your site at %1$s experienced a critical failure while trying to update WordPress to version %2$s.' ), home_url(), $core_update->current );
 
686
                                else
 
687
                                        $body .= sprintf( __( 'Your site at %1$s experienced a critical failure while trying to update to the latest version of WordPress, %2$s.' ), home_url(), $core_update->current );
 
688
 
 
689
                                $body .= "\n\n" . __( "This means your site may be offline or broken. Don't panic; this can be fixed." );
 
690
 
 
691
                                $body .= "\n\n" . __( "Please check out your site now. It's possible that everything is working. If it says you need to update, you should do so:" );
 
692
                                $body .= "\n" . network_admin_url( 'update-core.php' );
 
693
                                break;
 
694
                }
 
695
 
 
696
                $critical_support = 'critical' === $type && ! empty( $core_update->support_email );
 
697
                if ( $critical_support ) {
 
698
                        // Support offer if available.
 
699
                        $body .= "\n\n" . sprintf( __( "The WordPress team is willing to help you. Forward this email to %s and the team will work with you to make sure your site is working." ), $core_update->support_email );
 
700
                } else {
 
701
                        // Add a note about the support forums.
 
702
                        $body .= "\n\n" . __( 'If you experience any issues or need support, the volunteers in the WordPress.org support forums may be able to help.' );
 
703
                        $body .= "\n" . __( 'https://wordpress.org/support/' );
 
704
                }
 
705
 
 
706
                // Updates are important!
 
707
                if ( $type != 'success' || $newer_version_available ) {
 
708
                        $body .= "\n\n" . __( 'Keeping your site updated is important for security. It also makes the internet a safer place for you and your readers.' );
 
709
                }
 
710
 
 
711
                if ( $critical_support ) {
 
712
                        $body .= " " . __( "If you reach out to us, we'll also ensure you'll never have this problem again." );
 
713
                }
 
714
 
 
715
                // If things are successful and we're now on the latest, mention plugins and themes if any are out of date.
 
716
                if ( $type == 'success' && ! $newer_version_available && ( get_plugin_updates() || get_theme_updates() ) ) {
 
717
                        $body .= "\n\n" . __( 'You also have some plugins or themes with updates available. Update them now:' );
 
718
                        $body .= "\n" . network_admin_url();
 
719
                }
 
720
 
 
721
                $body .= "\n\n" . __( 'The WordPress Team' ) . "\n";
 
722
 
 
723
                if ( 'critical' == $type && is_wp_error( $result ) ) {
 
724
                        $body .= "\n***\n\n";
 
725
                        $body .= sprintf( __( 'Your site was running version %s.' ), $GLOBALS['wp_version'] );
 
726
                        $body .= ' ' . __( 'We have some data that describes the error your site encountered.' );
 
727
                        $body .= ' ' . __( 'Your hosting company, support forum volunteers, or a friendly developer may be able to use this information to help you:' );
 
728
 
 
729
                        // If we had a rollback and we're still critical, then the rollback failed too.
 
730
                        // Loop through all errors (the main WP_Error, the update result, the rollback result) for code, data, etc.
 
731
                        if ( 'rollback_was_required' == $result->get_error_code() )
 
732
                                $errors = array( $result, $result->get_error_data()->update, $result->get_error_data()->rollback );
 
733
                        else
 
734
                                $errors = array( $result );
 
735
 
 
736
                        foreach ( $errors as $error ) {
 
737
                                if ( ! is_wp_error( $error ) )
 
738
                                        continue;
 
739
                                $error_code = $error->get_error_code();
 
740
                                $body .= "\n\n" . sprintf( __( "Error code: %s" ), $error_code );
 
741
                                if ( 'rollback_was_required' == $error_code )
 
742
                                        continue;
 
743
                                if ( $error->get_error_message() )
 
744
                                        $body .= "\n" . $error->get_error_message();
 
745
                                $error_data = $error->get_error_data();
 
746
                                if ( $error_data )
 
747
                                        $body .= "\n" . implode( ', ', (array) $error_data );
 
748
                        }
 
749
                        $body .= "\n";
 
750
                }
 
751
 
 
752
                $to  = get_site_option( 'admin_email' );
 
753
                $headers = '';
 
754
 
 
755
                $email = compact( 'to', 'subject', 'body', 'headers' );
 
756
 
 
757
                /**
 
758
                 * Filters the email sent following an automatic background core update.
 
759
                 *
 
760
                 * @since 3.7.0
 
761
                 *
 
762
                 * @param array $email {
 
763
                 *     Array of email arguments that will be passed to wp_mail().
 
764
                 *
 
765
                 *     @type string $to      The email recipient. An array of emails
 
766
                 *                            can be returned, as handled by wp_mail().
 
767
                 *     @type string $subject The email's subject.
 
768
                 *     @type string $body    The email message body.
 
769
                 *     @type string $headers Any email headers, defaults to no headers.
 
770
                 * }
 
771
                 * @param string $type        The type of email being sent. Can be one of
 
772
                 *                            'success', 'fail', 'manual', 'critical'.
 
773
                 * @param object $core_update The update offer that was attempted.
 
774
                 * @param mixed  $result      The result for the core update. Can be WP_Error.
 
775
                 */
 
776
                $email = apply_filters( 'auto_core_update_email', $email, $type, $core_update, $result );
 
777
 
 
778
                wp_mail( $email['to'], wp_specialchars_decode( $email['subject'] ), $email['body'], $email['headers'] );
 
779
        }
 
780
 
 
781
        /**
 
782
         * Prepares and sends an email of a full log of background update results, useful for debugging and geekery.
 
783
         *
 
784
         * @since 3.7.0
 
785
         * @access protected
 
786
         */
 
787
        protected function send_debug_email() {
 
788
                $update_count = 0;
 
789
                foreach ( $this->update_results as $type => $updates )
 
790
                        $update_count += count( $updates );
 
791
 
 
792
                $body = array();
 
793
                $failures = 0;
 
794
 
 
795
                $body[] = sprintf( __( 'WordPress site: %s' ), network_home_url( '/' ) );
 
796
 
 
797
                // Core
 
798
                if ( isset( $this->update_results['core'] ) ) {
 
799
                        $result = $this->update_results['core'][0];
 
800
                        if ( $result->result && ! is_wp_error( $result->result ) ) {
 
801
                                $body[] = sprintf( __( 'SUCCESS: WordPress was successfully updated to %s' ), $result->name );
 
802
                        } else {
 
803
                                $body[] = sprintf( __( 'FAILED: WordPress failed to update to %s' ), $result->name );
 
804
                                $failures++;
 
805
                        }
 
806
                        $body[] = '';
 
807
                }
 
808
 
 
809
                // Plugins, Themes, Translations
 
810
                foreach ( array( 'plugin', 'theme', 'translation' ) as $type ) {
 
811
                        if ( ! isset( $this->update_results[ $type ] ) )
 
812
                                continue;
 
813
                        $success_items = wp_list_filter( $this->update_results[ $type ], array( 'result' => true ) );
 
814
                        if ( $success_items ) {
 
815
                                $messages = array(
 
816
                                        'plugin'      => __( 'The following plugins were successfully updated:' ),
 
817
                                        'theme'       => __( 'The following themes were successfully updated:' ),
 
818
                                        'translation' => __( 'The following translations were successfully updated:' ),
 
819
                                );
 
820
 
 
821
                                $body[] = $messages[ $type ];
 
822
                                foreach ( wp_list_pluck( $success_items, 'name' ) as $name ) {
 
823
                                        $body[] = ' * ' . sprintf( __( 'SUCCESS: %s' ), $name );
 
824
                                }
 
825
                        }
 
826
                        if ( $success_items != $this->update_results[ $type ] ) {
 
827
                                // Failed updates
 
828
                                $messages = array(
 
829
                                        'plugin'      => __( 'The following plugins failed to update:' ),
 
830
                                        'theme'       => __( 'The following themes failed to update:' ),
 
831
                                        'translation' => __( 'The following translations failed to update:' ),
 
832
                                );
 
833
 
 
834
                                $body[] = $messages[ $type ];
 
835
                                foreach ( $this->update_results[ $type ] as $item ) {
 
836
                                        if ( ! $item->result || is_wp_error( $item->result ) ) {
 
837
                                                $body[] = ' * ' . sprintf( __( 'FAILED: %s' ), $item->name );
 
838
                                                $failures++;
 
839
                                        }
 
840
                                }
 
841
                        }
 
842
                        $body[] = '';
 
843
                }
 
844
 
 
845
                $site_title = wp_specialchars_decode( get_bloginfo( 'name' ), ENT_QUOTES );
 
846
                if ( $failures ) {
 
847
                        $body[] = trim( __(
 
848
"BETA TESTING?
 
849
=============
 
850
 
 
851
This debugging email is sent when you are using a development version of WordPress.
 
852
 
 
853
If you think these failures might be due to a bug in WordPress, could you report it?
 
854
 * Open a thread in the support forums: https://wordpress.org/support/forum/alphabeta
 
855
 * Or, if you're comfortable writing a bug report: https://core.trac.wordpress.org/
 
856
 
 
857
Thanks! -- The WordPress Team" ) );
 
858
                        $body[] = '';
 
859
 
 
860
                        $subject = sprintf( __( '[%s] There were failures during background updates' ), $site_title );
 
861
                } else {
 
862
                        $subject = sprintf( __( '[%s] Background updates have finished' ), $site_title );
 
863
                }
 
864
 
 
865
                $body[] = trim( __(
 
866
'UPDATE LOG
 
867
==========' ) );
 
868
                $body[] = '';
 
869
 
 
870
                foreach ( array( 'core', 'plugin', 'theme', 'translation' ) as $type ) {
 
871
                        if ( ! isset( $this->update_results[ $type ] ) )
 
872
                                continue;
 
873
                        foreach ( $this->update_results[ $type ] as $update ) {
 
874
                                $body[] = $update->name;
 
875
                                $body[] = str_repeat( '-', strlen( $update->name ) );
 
876
                                foreach ( $update->messages as $message )
 
877
                                        $body[] = "  " . html_entity_decode( str_replace( '&#8230;', '...', $message ) );
 
878
                                if ( is_wp_error( $update->result ) ) {
 
879
                                        $results = array( 'update' => $update->result );
 
880
                                        // If we rolled back, we want to know an error that occurred then too.
 
881
                                        if ( 'rollback_was_required' === $update->result->get_error_code() )
 
882
                                                $results = (array) $update->result->get_error_data();
 
883
                                        foreach ( $results as $result_type => $result ) {
 
884
                                                if ( ! is_wp_error( $result ) )
 
885
                                                        continue;
 
886
 
 
887
                                                if ( 'rollback' === $result_type ) {
 
888
                                                        /* translators: 1: Error code, 2: Error message. */
 
889
                                                        $body[] = '  ' . sprintf( __( 'Rollback Error: [%1$s] %2$s' ), $result->get_error_code(), $result->get_error_message() );
 
890
                                                } else {
 
891
                                                        /* translators: 1: Error code, 2: Error message. */
 
892
                                                        $body[] = '  ' . sprintf( __( 'Error: [%1$s] %2$s' ), $result->get_error_code(), $result->get_error_message() );
 
893
                                                }
 
894
 
 
895
                                                if ( $result->get_error_data() )
 
896
                                                        $body[] = '         ' . implode( ', ', (array) $result->get_error_data() );
 
897
                                        }
 
898
                                }
 
899
                                $body[] = '';
 
900
                        }
 
901
                }
 
902
 
 
903
                $email = array(
 
904
                        'to'      => get_site_option( 'admin_email' ),
 
905
                        'subject' => $subject,
 
906
                        'body'    => implode( "\n", $body ),
 
907
                        'headers' => ''
 
908
                );
 
909
 
 
910
                /**
 
911
                 * Filters the debug email that can be sent following an automatic
 
912
                 * background core update.
 
913
                 *
 
914
                 * @since 3.8.0
 
915
                 *
 
916
                 * @param array $email {
 
917
                 *     Array of email arguments that will be passed to wp_mail().
 
918
                 *
 
919
                 *     @type string $to      The email recipient. An array of emails
 
920
                 *                           can be returned, as handled by wp_mail().
 
921
                 *     @type string $subject Email subject.
 
922
                 *     @type string $body    Email message body.
 
923
                 *     @type string $headers Any email headers. Default empty.
 
924
                 * }
 
925
                 * @param int   $failures The number of failures encountered while upgrading.
 
926
                 * @param mixed $results  The results of all attempted updates.
 
927
                 */
 
928
                $email = apply_filters( 'automatic_updates_debug_email', $email, $failures, $this->update_results );
 
929
 
 
930
                wp_mail( $email['to'], wp_specialchars_decode( $email['subject'] ), $email['body'], $email['headers'] );
 
931
        }
 
932
}