WP_Automatic_Updater::is_disabled()   B
last analyzed

Complexity

Conditions 4
Paths 4

Size

Total Lines 25
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 7
nc 4
nop 0
dl 0
loc 25
rs 8.5806
c 0
b 0
f 0
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 ( ! wp_is_file_mod_allowed( 'automatic_updater' ) )
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 );
0 ignored issues
show
Bug introduced by
The variable $checkout does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
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 View Code Duplication
		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 View Code Duplication
			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 ) )
0 ignored issues
show
Bug introduced by
The variable $context does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
285
			return false;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return false; (false) is incompatible with the return type documented by WP_Automatic_Updater::update of type null|WP_Error.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
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 );
0 ignored issues
show
Bug introduced by
The variable $upgrader does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
Bug introduced by
The method get_name_for_update does only exist in Language_Pack_Upgrader, but not in Core_Upgrader and Plugin...ader and Theme_Upgrader.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
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 View Code Duplication
		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;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return false; (false) is incompatible with the return type documented by WP_Automatic_Updater::update of type null|WP_Error.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
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,
0 ignored issues
show
Bug introduced by
The variable $item_name does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
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
	public function run() {
376
		if ( $this->is_disabled() )
377
			return;
378
379
		if ( ! is_main_network() || ! is_main_site() )
380
			return;
381
382
		if ( ! WP_Upgrader::create_lock( 'auto_updater' ) )
383
			return;
384
385
		// Don't automatically run these thins, as we'll handle it ourselves
386
		remove_action( 'upgrader_process_complete', array( 'Language_Pack_Upgrader', 'async_upgrade' ), 20 );
387
		remove_action( 'upgrader_process_complete', 'wp_version_check' );
388
		remove_action( 'upgrader_process_complete', 'wp_update_plugins' );
389
		remove_action( 'upgrader_process_complete', 'wp_update_themes' );
390
391
		// Next, Plugins
392
		wp_update_plugins(); // Check for Plugin updates
393
		$plugin_updates = get_site_transient( 'update_plugins' );
394
		if ( $plugin_updates && !empty( $plugin_updates->response ) ) {
395
			foreach ( $plugin_updates->response as $plugin ) {
396
				$this->update( 'plugin', $plugin );
397
			}
398
			// Force refresh of plugin update information
399
			wp_clean_plugins_cache();
400
		}
401
402
		// Next, those themes we all love
403
		wp_update_themes();  // Check for Theme updates
404
		$theme_updates = get_site_transient( 'update_themes' );
405
		if ( $theme_updates && !empty( $theme_updates->response ) ) {
406
			foreach ( $theme_updates->response as $theme ) {
407
				$this->update( 'theme', (object) $theme );
408
			}
409
			// Force refresh of theme update information
410
			wp_clean_themes_cache();
411
		}
412
413
		// Next, Process any core update
414
		wp_version_check(); // Check for Core updates
415
		$core_update = find_core_auto_update();
416
417
		if ( $core_update )
418
			$this->update( 'core', $core_update );
419
420
		// Clean up, and check for any pending translations
421
		// (Core_Upgrader checks for core updates)
422
		$theme_stats = array();
423 View Code Duplication
		if ( isset( $this->update_results['theme'] ) ) {
424
			foreach ( $this->update_results['theme'] as $upgrade ) {
425
				$theme_stats[ $upgrade->item->theme ] = ( true === $upgrade->result );
426
			}
427
		}
428
		wp_update_themes( $theme_stats );  // Check for Theme updates
429
430
		$plugin_stats = array();
431 View Code Duplication
		if ( isset( $this->update_results['plugin'] ) ) {
432
			foreach ( $this->update_results['plugin'] as $upgrade ) {
433
				$plugin_stats[ $upgrade->item->plugin ] = ( true === $upgrade->result );
434
			}
435
		}
436
		wp_update_plugins( $plugin_stats ); // Check for Plugin updates
437
438
		// Finally, Process any new translations
439
		$language_updates = wp_get_translation_updates();
440
		if ( $language_updates ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $language_updates of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
441
			foreach ( $language_updates as $update ) {
442
				$this->update( 'translation', $update );
443
			}
444
445
			// Clear existing caches
446
			wp_clean_update_cache();
447
448
			wp_version_check();  // check for Core updates
449
			wp_update_themes();  // Check for Theme updates
450
			wp_update_plugins(); // Check for Plugin updates
451
		}
452
453
		// Send debugging email to all development installs.
454
		if ( ! empty( $this->update_results ) ) {
455
			$development_version = false !== strpos( get_bloginfo( 'version' ), '-' );
456
457
			/**
458
			 * Filters whether to send a debugging email for each automatic background update.
459
			 *
460
			 * @since 3.7.0
461
			 *
462
			 * @param bool $development_version By default, emails are sent if the
463
			 *                                  install is a development version.
464
			 *                                  Return false to avoid the email.
465
			 */
466
			if ( apply_filters( 'automatic_updates_send_debug_email', $development_version ) )
467
				$this->send_debug_email();
468
469
			if ( ! empty( $this->update_results['core'] ) )
470
				$this->after_core_update( $this->update_results['core'][0] );
471
472
			/**
473
			 * Fires after all automatic updates have run.
474
			 *
475
			 * @since 3.8.0
476
			 *
477
			 * @param array $update_results The results of all attempted updates.
478
			 */
479
			do_action( 'automatic_updates_complete', $this->update_results );
480
		}
481
482
		WP_Upgrader::release_lock( 'auto_updater' );
483
	}
484
485
	/**
486
	 * If we tried to perform a core update, check if we should send an email,
487
	 * and if we need to avoid processing future updates.
488
	 *
489
	 * @since Unknown
490
	 * @access protected
491
	 *
492
	 * @param object $update_result The result of the core update. Includes the update offer and result.
493
	 */
494
	protected function after_core_update( $update_result ) {
495
		$wp_version = get_bloginfo( 'version' );
496
497
		$core_update = $update_result->item;
498
		$result      = $update_result->result;
499
500
		if ( ! is_wp_error( $result ) ) {
501
			$this->send_email( 'success', $core_update );
502
			return;
503
		}
504
505
		$error_code = $result->get_error_code();
506
507
		// Any of these WP_Error codes are critical failures, as in they occurred after we started to copy core files.
508
		// We should not try to perform a background update again until there is a successful one-click update performed by the user.
509
		$critical = false;
510
		if ( $error_code === 'disk_full' || false !== strpos( $error_code, '__copy_dir' ) ) {
511
			$critical = true;
512
		} elseif ( $error_code === 'rollback_was_required' && is_wp_error( $result->get_error_data()->rollback ) ) {
513
			// A rollback is only critical if it failed too.
514
			$critical = true;
515
			$rollback_result = $result->get_error_data()->rollback;
516
		} elseif ( false !== strpos( $error_code, 'do_rollback' ) ) {
517
			$critical = true;
518
		}
519
520
		if ( $critical ) {
521
			$critical_data = array(
522
				'attempted'  => $core_update->current,
523
				'current'    => $wp_version,
524
				'error_code' => $error_code,
525
				'error_data' => $result->get_error_data(),
526
				'timestamp'  => time(),
527
				'critical'   => true,
528
			);
529
			if ( isset( $rollback_result ) ) {
530
				$critical_data['rollback_code'] = $rollback_result->get_error_code();
531
				$critical_data['rollback_data'] = $rollback_result->get_error_data();
532
			}
533
			update_site_option( 'auto_core_update_failed', $critical_data );
534
			$this->send_email( 'critical', $core_update, $result );
535
			return;
536
		}
537
538
		/*
539
		 * Any other WP_Error code (like download_failed or files_not_writable) occurs before
540
		 * we tried to copy over core files. Thus, the failures are early and graceful.
541
		 *
542
		 * We should avoid trying to perform a background update again for the same version.
543
		 * But we can try again if another version is released.
544
		 *
545
		 * For certain 'transient' failures, like download_failed, we should allow retries.
546
		 * In fact, let's schedule a special update for an hour from now. (It's possible
547
		 * the issue could actually be on WordPress.org's side.) If that one fails, then email.
548
		 */
549
		$send = true;
550
  		$transient_failures = array( 'incompatible_archive', 'download_failed', 'insane_distro', 'locked' );
551
  		if ( in_array( $error_code, $transient_failures ) && ! get_site_option( 'auto_core_update_failed' ) ) {
552
  			wp_schedule_single_event( time() + HOUR_IN_SECONDS, 'wp_maybe_auto_update' );
553
  			$send = false;
554
  		}
555
556
  		$n = get_site_option( 'auto_core_update_notified' );
557
		// Don't notify if we've already notified the same email address of the same version of the same notification type.
558
		if ( $n && 'fail' == $n['type'] && $n['email'] == get_site_option( 'admin_email' ) && $n['version'] == $core_update->current )
559
			$send = false;
560
561
		update_site_option( 'auto_core_update_failed', array(
562
			'attempted'  => $core_update->current,
563
			'current'    => $wp_version,
564
			'error_code' => $error_code,
565
			'error_data' => $result->get_error_data(),
566
			'timestamp'  => time(),
567
			'retry'      => in_array( $error_code, $transient_failures ),
568
		) );
569
570
		if ( $send )
571
			$this->send_email( 'fail', $core_update, $result );
572
	}
573
574
	/**
575
	 * Sends an email upon the completion or failure of a background core update.
576
	 *
577
	 * @since 3.7.0
578
	 * @access protected
579
	 *
580
	 * @param string $type        The type of email to send. Can be one of 'success', 'fail', 'manual', 'critical'.
581
	 * @param object $core_update The update offer that was attempted.
582
	 * @param mixed  $result      Optional. The result for the core update. Can be WP_Error.
583
	 */
584
	protected function send_email( $type, $core_update, $result = null ) {
585
		update_site_option( 'auto_core_update_notified', array(
586
			'type'      => $type,
587
			'email'     => get_site_option( 'admin_email' ),
588
			'version'   => $core_update->current,
589
			'timestamp' => time(),
590
		) );
591
592
		$next_user_core_update = get_preferred_from_update_core();
593
		// If the update transient is empty, use the update we just performed
594
		if ( ! $next_user_core_update )
595
			$next_user_core_update = $core_update;
596
		$newer_version_available = ( 'upgrade' == $next_user_core_update->response && version_compare( $next_user_core_update->version, $core_update->version, '>' ) );
597
598
		/**
599
		 * Filters whether to send an email following an automatic background core update.
600
		 *
601
		 * @since 3.7.0
602
		 *
603
		 * @param bool   $send        Whether to send the email. Default true.
604
		 * @param string $type        The type of email to send. Can be one of
605
		 *                            'success', 'fail', 'critical'.
606
		 * @param object $core_update The update offer that was attempted.
607
		 * @param mixed  $result      The result for the core update. Can be WP_Error.
608
		 */
609
		if ( 'manual' !== $type && ! apply_filters( 'auto_core_update_send_email', true, $type, $core_update, $result ) )
610
			return;
611
612
		switch ( $type ) {
613
			case 'success' : // We updated.
614
				/* translators: 1: Site name, 2: WordPress version number. */
615
				$subject = __( '[%1$s] Your site has updated to WordPress %2$s' );
616
				break;
617
618
			case 'fail' :   // We tried to update but couldn't.
619
			case 'manual' : // We can't update (and made no attempt).
620
				/* translators: 1: Site name, 2: WordPress version number. */
621
				$subject = __( '[%1$s] WordPress %2$s is available. Please update!' );
622
				break;
623
624
			case 'critical' : // We tried to update, started to copy files, then things went wrong.
625
				/* translators: 1: Site name. */
626
				$subject = __( '[%1$s] URGENT: Your site may be down due to a failed update' );
627
				break;
628
629
			default :
630
				return;
631
		}
632
633
		// If the auto update is not to the latest version, say that the current version of WP is available instead.
634
		$version = 'success' === $type ? $core_update->current : $next_user_core_update->current;
635
		$subject = sprintf( $subject, wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES ), $version );
636
637
		$body = '';
638
639
		switch ( $type ) {
640
			case 'success' :
641
				$body .= sprintf( __( 'Howdy! Your site at %1$s has been updated automatically to WordPress %2$s.' ), home_url(), $core_update->current );
642
				$body .= "\n\n";
643
				if ( ! $newer_version_available )
644
					$body .= __( 'No further action is needed on your part.' ) . ' ';
645
646
				// Can only reference the About screen if their update was successful.
647
				list( $about_version ) = explode( '-', $core_update->current, 2 );
648
				$body .= sprintf( __( "For more on version %s, see the About WordPress screen:" ), $about_version );
649
				$body .= "\n" . admin_url( 'about.php' );
650
651
				if ( $newer_version_available ) {
652
					$body .= "\n\n" . sprintf( __( 'WordPress %s is also now available.' ), $next_user_core_update->current ) . ' ';
653
					$body .= __( 'Updating is easy and only takes a few moments:' );
654
					$body .= "\n" . network_admin_url( 'update-core.php' );
655
				}
656
657
				break;
658
659
			case 'fail' :
660
			case 'manual' :
661
				$body .= sprintf( __( 'Please update your site at %1$s to WordPress %2$s.' ), home_url(), $next_user_core_update->current );
662
663
				$body .= "\n\n";
664
665
				// Don't show this message if there is a newer version available.
666
				// Potential for confusion, and also not useful for them to know at this point.
667
				if ( 'fail' == $type && ! $newer_version_available )
668
					$body .= __( 'We tried but were unable to update your site automatically.' ) . ' ';
669
670
				$body .= __( 'Updating is easy and only takes a few moments:' );
671
				$body .= "\n" . network_admin_url( 'update-core.php' );
672
				break;
673
674
			case 'critical' :
675
				if ( $newer_version_available )
676
					$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 );
677
				else
678
					$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 );
679
680
				$body .= "\n\n" . __( "This means your site may be offline or broken. Don't panic; this can be fixed." );
681
682
				$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:" );
683
				$body .= "\n" . network_admin_url( 'update-core.php' );
684
				break;
685
		}
686
687
		$critical_support = 'critical' === $type && ! empty( $core_update->support_email );
688
		if ( $critical_support ) {
689
			// Support offer if available.
690
			$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 );
691
		} else {
692
			// Add a note about the support forums.
693
			$body .= "\n\n" . __( 'If you experience any issues or need support, the volunteers in the WordPress.org support forums may be able to help.' );
694
			$body .= "\n" . __( 'https://wordpress.org/support/' );
695
		}
696
697
		// Updates are important!
698
		if ( $type != 'success' || $newer_version_available ) {
699
			$body .= "\n\n" . __( 'Keeping your site updated is important for security. It also makes the internet a safer place for you and your readers.' );
700
		}
701
702
		if ( $critical_support ) {
703
			$body .= " " . __( "If you reach out to us, we'll also ensure you'll never have this problem again." );
704
		}
705
706
		// If things are successful and we're now on the latest, mention plugins and themes if any are out of date.
707
		if ( $type == 'success' && ! $newer_version_available && ( get_plugin_updates() || get_theme_updates() ) ) {
708
			$body .= "\n\n" . __( 'You also have some plugins or themes with updates available. Update them now:' );
709
			$body .= "\n" . network_admin_url();
710
		}
711
712
		$body .= "\n\n" . __( 'The WordPress Team' ) . "\n";
713
714
		if ( 'critical' == $type && is_wp_error( $result ) ) {
715
			$body .= "\n***\n\n";
716
			$body .= sprintf( __( 'Your site was running version %s.' ), get_bloginfo( 'version' ) );
717
			$body .= ' ' . __( 'We have some data that describes the error your site encountered.' );
718
			$body .= ' ' . __( 'Your hosting company, support forum volunteers, or a friendly developer may be able to use this information to help you:' );
719
720
			// If we had a rollback and we're still critical, then the rollback failed too.
721
			// Loop through all errors (the main WP_Error, the update result, the rollback result) for code, data, etc.
722
			if ( 'rollback_was_required' == $result->get_error_code() )
723
				$errors = array( $result, $result->get_error_data()->update, $result->get_error_data()->rollback );
724
			else
725
				$errors = array( $result );
726
727
			foreach ( $errors as $error ) {
728
				if ( ! is_wp_error( $error ) )
729
					continue;
730
				$error_code = $error->get_error_code();
731
				$body .= "\n\n" . sprintf( __( "Error code: %s" ), $error_code );
732
				if ( 'rollback_was_required' == $error_code )
733
					continue;
734
				if ( $error->get_error_message() )
735
					$body .= "\n" . $error->get_error_message();
736
				$error_data = $error->get_error_data();
737
				if ( $error_data )
738
					$body .= "\n" . implode( ', ', (array) $error_data );
739
			}
740
			$body .= "\n";
741
		}
742
743
		$to  = get_site_option( 'admin_email' );
744
		$headers = '';
745
746
		$email = compact( 'to', 'subject', 'body', 'headers' );
747
748
		/**
749
		 * Filters the email sent following an automatic background core update.
750
		 *
751
		 * @since 3.7.0
752
		 *
753
		 * @param array $email {
754
		 *     Array of email arguments that will be passed to wp_mail().
755
		 *
756
		 *     @type string $to      The email recipient. An array of emails
757
		 *                            can be returned, as handled by wp_mail().
758
		 *     @type string $subject The email's subject.
759
		 *     @type string $body    The email message body.
760
		 *     @type string $headers Any email headers, defaults to no headers.
761
		 * }
762
		 * @param string $type        The type of email being sent. Can be one of
763
		 *                            'success', 'fail', 'manual', 'critical'.
764
		 * @param object $core_update The update offer that was attempted.
765
		 * @param mixed  $result      The result for the core update. Can be WP_Error.
766
		 */
767
		$email = apply_filters( 'auto_core_update_email', $email, $type, $core_update, $result );
768
769
		wp_mail( $email['to'], wp_specialchars_decode( $email['subject'] ), $email['body'], $email['headers'] );
770
	}
771
772
	/**
773
	 * Prepares and sends an email of a full log of background update results, useful for debugging and geekery.
774
	 *
775
	 * @since 3.7.0
776
	 * @access protected
777
	 */
778
	protected function send_debug_email() {
779
		$update_count = 0;
780
		foreach ( $this->update_results as $type => $updates )
781
			$update_count += count( $updates );
782
783
		$body = array();
784
		$failures = 0;
785
786
		$body[] = sprintf( __( 'WordPress site: %s' ), network_home_url( '/' ) );
787
788
		// Core
789
		if ( isset( $this->update_results['core'] ) ) {
790
			$result = $this->update_results['core'][0];
791
			if ( $result->result && ! is_wp_error( $result->result ) ) {
792
				$body[] = sprintf( __( 'SUCCESS: WordPress was successfully updated to %s' ), $result->name );
793
			} else {
794
				$body[] = sprintf( __( 'FAILED: WordPress failed to update to %s' ), $result->name );
795
				$failures++;
796
			}
797
			$body[] = '';
798
		}
799
800
		// Plugins, Themes, Translations
801
		foreach ( array( 'plugin', 'theme', 'translation' ) as $type ) {
802
			if ( ! isset( $this->update_results[ $type ] ) )
803
				continue;
804
			$success_items = wp_list_filter( $this->update_results[ $type ], array( 'result' => true ) );
805
			if ( $success_items ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $success_items of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
806
				$messages = array(
807
					'plugin'      => __( 'The following plugins were successfully updated:' ),
808
					'theme'       => __( 'The following themes were successfully updated:' ),
809
					'translation' => __( 'The following translations were successfully updated:' ),
810
				);
811
812
				$body[] = $messages[ $type ];
813
				foreach ( wp_list_pluck( $success_items, 'name' ) as $name ) {
814
					$body[] = ' * ' . sprintf( __( 'SUCCESS: %s' ), $name );
815
				}
816
			}
817
			if ( $success_items != $this->update_results[ $type ] ) {
818
				// Failed updates
819
				$messages = array(
820
					'plugin'      => __( 'The following plugins failed to update:' ),
821
					'theme'       => __( 'The following themes failed to update:' ),
822
					'translation' => __( 'The following translations failed to update:' ),
823
				);
824
825
				$body[] = $messages[ $type ];
826
				foreach ( $this->update_results[ $type ] as $item ) {
827
					if ( ! $item->result || is_wp_error( $item->result ) ) {
828
						$body[] = ' * ' . sprintf( __( 'FAILED: %s' ), $item->name );
829
						$failures++;
830
					}
831
				}
832
			}
833
			$body[] = '';
834
		}
835
836
		$site_title = wp_specialchars_decode( get_bloginfo( 'name' ), ENT_QUOTES );
837
		if ( $failures ) {
838
			$body[] = trim( __(
839
"BETA TESTING?
840
=============
841
842
This debugging email is sent when you are using a development version of WordPress.
843
844
If you think these failures might be due to a bug in WordPress, could you report it?
845
 * Open a thread in the support forums: https://wordpress.org/support/forum/alphabeta
846
 * Or, if you're comfortable writing a bug report: https://core.trac.wordpress.org/
847
848
Thanks! -- The WordPress Team" ) );
849
			$body[] = '';
850
851
			$subject = sprintf( __( '[%s] There were failures during background updates' ), $site_title );
852
		} else {
853
			$subject = sprintf( __( '[%s] Background updates have finished' ), $site_title );
854
		}
855
856
		$body[] = trim( __(
857
'UPDATE LOG
858
==========' ) );
859
		$body[] = '';
860
861
		foreach ( array( 'core', 'plugin', 'theme', 'translation' ) as $type ) {
862
			if ( ! isset( $this->update_results[ $type ] ) )
863
				continue;
864
			foreach ( $this->update_results[ $type ] as $update ) {
865
				$body[] = $update->name;
866
				$body[] = str_repeat( '-', strlen( $update->name ) );
867
				foreach ( $update->messages as $message )
868
					$body[] = "  " . html_entity_decode( str_replace( '&#8230;', '...', $message ) );
869
				if ( is_wp_error( $update->result ) ) {
870
					$results = array( 'update' => $update->result );
871
					// If we rolled back, we want to know an error that occurred then too.
872
					if ( 'rollback_was_required' === $update->result->get_error_code() )
873
						$results = (array) $update->result->get_error_data();
874
					foreach ( $results as $result_type => $result ) {
875
						if ( ! is_wp_error( $result ) )
876
							continue;
877
878
						if ( 'rollback' === $result_type ) {
879
							/* translators: 1: Error code, 2: Error message. */
880
							$body[] = '  ' . sprintf( __( 'Rollback Error: [%1$s] %2$s' ), $result->get_error_code(), $result->get_error_message() );
881
						} else {
882
							/* translators: 1: Error code, 2: Error message. */
883
							$body[] = '  ' . sprintf( __( 'Error: [%1$s] %2$s' ), $result->get_error_code(), $result->get_error_message() );
884
						}
885
886
						if ( $result->get_error_data() )
887
							$body[] = '         ' . implode( ', ', (array) $result->get_error_data() );
888
					}
889
				}
890
				$body[] = '';
891
			}
892
		}
893
894
		$email = array(
895
			'to'      => get_site_option( 'admin_email' ),
896
			'subject' => $subject,
897
			'body'    => implode( "\n", $body ),
898
			'headers' => ''
899
		);
900
901
		/**
902
		 * Filters the debug email that can be sent following an automatic
903
		 * background core update.
904
		 *
905
		 * @since 3.8.0
906
		 *
907
		 * @param array $email {
908
		 *     Array of email arguments that will be passed to wp_mail().
909
		 *
910
		 *     @type string $to      The email recipient. An array of emails
911
		 *                           can be returned, as handled by wp_mail().
912
		 *     @type string $subject Email subject.
913
		 *     @type string $body    Email message body.
914
		 *     @type string $headers Any email headers. Default empty.
915
		 * }
916
		 * @param int   $failures The number of failures encountered while upgrading.
917
		 * @param mixed $results  The results of all attempted updates.
918
		 */
919
		$email = apply_filters( 'automatic_updates_debug_email', $email, $failures, $this->update_results );
920
921
		wp_mail( $email['to'], wp_specialchars_decode( $email['subject'] ), $email['body'], $email['headers'] );
922
	}
923
}
924