Core_Upgrader   F
last analyzed

Complexity

Total Complexity 64

Size/Duplication

Total Lines 345
Duplicated Lines 3.19 %

Coupling/Cohesion

Components 0
Dependencies 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 11
loc 345
rs 3.4883
wmc 64
lcom 0
cbo 1

4 Methods

Rating   Name   Duplication   Size   Complexity  
A upgrade_strings() 11 11 1
F upgrade() 0 163 38
D should_update_to_version() 0 97 18
B check_files() 0 18 7

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Core_Upgrader often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Core_Upgrader, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * Upgrade API: Core_Upgrader class
4
 *
5
 * @package WordPress
6
 * @subpackage Upgrader
7
 * @since 4.6.0
8
 */
9
10
/**
11
 * Core class used for updating core.
12
 *
13
 * It allows for WordPress to upgrade itself in combination with
14
 * the wp-admin/includes/update-core.php file.
15
 *
16
 * @since 2.8.0
17
 * @since 4.6.0 Moved to its own file from wp-admin/includes/class-wp-upgrader.php.
18
 *
19
 * @see WP_Upgrader
20
 */
21
class Core_Upgrader extends WP_Upgrader {
22
23
	/**
24
	 * Initialize the upgrade strings.
25
	 *
26
	 * @since 2.8.0
27
	 * @access public
28
	 */
29 View Code Duplication
	public function upgrade_strings() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
30
		$this->strings['up_to_date'] = __('WordPress is at the latest version.');
31
		$this->strings['locked'] = __('Another update is currently in progress.');
32
		$this->strings['no_package'] = __('Update package not available.');
33
		$this->strings['downloading_package'] = __('Downloading update from <span class="code">%s</span>&#8230;');
34
		$this->strings['unpack_package'] = __('Unpacking the update&#8230;');
35
		$this->strings['copy_failed'] = __('Could not copy files.');
36
		$this->strings['copy_failed_space'] = __('Could not copy files. You may have run out of disk space.' );
37
		$this->strings['start_rollback'] = __( 'Attempting to roll back to previous version.' );
38
		$this->strings['rollback_was_required'] = __( 'Due to an error during updating, WordPress has rolled back to your previous version.' );
39
	}
40
41
	/**
42
	 * Upgrade WordPress core.
43
	 *
44
	 * @since 2.8.0
45
	 * @access public
46
	 *
47
	 * @global WP_Filesystem_Base $wp_filesystem Subclass
48
	 * @global callable           $_wp_filesystem_direct_method
49
	 *
50
	 * @param object $current Response object for whether WordPress is current.
51
	 * @param array  $args {
52
	 *        Optional. Arguments for upgrading WordPress core. Default empty array.
53
	 *
54
	 *        @type bool $pre_check_md5    Whether to check the file checksums before
55
	 *                                     attempting the upgrade. Default true.
56
	 *        @type bool $attempt_rollback Whether to attempt to rollback the chances if
57
	 *                                     there is a problem. Default false.
58
	 *        @type bool $do_rollback      Whether to perform this "upgrade" as a rollback.
59
	 *                                     Default false.
60
	 * }
61
	 * @return null|false|WP_Error False or WP_Error on failure, null on success.
62
	 */
63
	public function upgrade( $current, $args = array() ) {
64
		global $wp_filesystem;
65
66
		include( ABSPATH . WPINC . '/version.php' ); // $wp_version;
67
68
		$start_time = time();
69
70
		$defaults = array(
71
			'pre_check_md5'    => true,
72
			'attempt_rollback' => false,
73
			'do_rollback'      => false,
74
			'allow_relaxed_file_ownership' => false,
75
		);
76
		$parsed_args = wp_parse_args( $args, $defaults );
77
78
		$this->init();
79
		$this->upgrade_strings();
80
81
		// Is an update available?
82
		if ( !isset( $current->response ) || $current->response == 'latest' )
83
			return new WP_Error('up_to_date', $this->strings['up_to_date']);
84
85
		$res = $this->fs_connect( array( ABSPATH, WP_CONTENT_DIR ), $parsed_args['allow_relaxed_file_ownership'] );
86
		if ( ! $res || is_wp_error( $res ) ) {
87
			return $res;
88
		}
89
90
		$wp_dir = trailingslashit($wp_filesystem->abspath());
91
92
		$partial = true;
93
		if ( $parsed_args['do_rollback'] )
94
			$partial = false;
95
		elseif ( $parsed_args['pre_check_md5'] && ! $this->check_files() )
96
			$partial = false;
97
98
		/*
99
		 * If partial update is returned from the API, use that, unless we're doing
100
		 * a reinstall. If we cross the new_bundled version number, then use
101
		 * the new_bundled zip. Don't though if the constant is set to skip bundled items.
102
		 * If the API returns a no_content zip, go with it. Finally, default to the full zip.
103
		 */
104
		if ( $parsed_args['do_rollback'] && $current->packages->rollback )
105
			$to_download = 'rollback';
106
		elseif ( $current->packages->partial && 'reinstall' != $current->response && $wp_version == $current->partial_version && $partial )
0 ignored issues
show
Bug introduced by
The variable $wp_version does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
107
			$to_download = 'partial';
108
		elseif ( $current->packages->new_bundled && version_compare( $wp_version, $current->new_bundled, '<' )
109
			&& ( ! defined( 'CORE_UPGRADE_SKIP_NEW_BUNDLED' ) || ! CORE_UPGRADE_SKIP_NEW_BUNDLED ) )
110
			$to_download = 'new_bundled';
111
		elseif ( $current->packages->no_content )
112
			$to_download = 'no_content';
113
		else
114
			$to_download = 'full';
115
116
		// Lock to prevent multiple Core Updates occurring
117
		$lock = WP_Upgrader::create_lock( 'core_updater', 15 * MINUTE_IN_SECONDS );
118
		if ( ! $lock ) {
119
			return new WP_Error( 'locked', $this->strings['locked'] );
120
		}
121
122
		$download = $this->download_package( $current->packages->$to_download );
123
		if ( is_wp_error( $download ) ) {
124
			WP_Upgrader::release_lock( 'core_updater' );
125
			return $download;
126
		}
127
128
		$working_dir = $this->unpack_package( $download );
129
		if ( is_wp_error( $working_dir ) ) {
130
			WP_Upgrader::release_lock( 'core_updater' );
131
			return $working_dir;
132
		}
133
134
		// Copy update-core.php from the new version into place.
135
		if ( !$wp_filesystem->copy($working_dir . '/wordpress/wp-admin/includes/update-core.php', $wp_dir . 'wp-admin/includes/update-core.php', true) ) {
136
			$wp_filesystem->delete($working_dir, true);
137
			WP_Upgrader::release_lock( 'core_updater' );
138
			return new WP_Error( 'copy_failed_for_update_core_file', __( 'The update cannot be installed because we will be unable to copy some files. This is usually due to inconsistent file permissions.' ), 'wp-admin/includes/update-core.php' );
139
		}
140
		$wp_filesystem->chmod($wp_dir . 'wp-admin/includes/update-core.php', FS_CHMOD_FILE);
141
142
		require_once( ABSPATH . 'wp-admin/includes/update-core.php' );
143
144
		if ( ! function_exists( 'update_core' ) ) {
145
			WP_Upgrader::release_lock( 'core_updater' );
146
			return new WP_Error( 'copy_failed_space', $this->strings['copy_failed_space'] );
147
		}
148
149
		$result = update_core( $working_dir, $wp_dir );
150
151
		// In the event of an issue, we may be able to roll back.
152
		if ( $parsed_args['attempt_rollback'] && $current->packages->rollback && ! $parsed_args['do_rollback'] ) {
153
			$try_rollback = false;
154
			if ( is_wp_error( $result ) ) {
155
				$error_code = $result->get_error_code();
156
				/*
157
				 * Not all errors are equal. These codes are critical: copy_failed__copy_dir,
158
				 * mkdir_failed__copy_dir, copy_failed__copy_dir_retry, and disk_full.
159
				 * do_rollback allows for update_core() to trigger a rollback if needed.
160
				 */
161
				if ( false !== strpos( $error_code, 'do_rollback' ) )
162
					$try_rollback = true;
163
				elseif ( false !== strpos( $error_code, '__copy_dir' ) )
164
					$try_rollback = true;
165
				elseif ( 'disk_full' === $error_code )
166
					$try_rollback = true;
167
			}
168
169
			if ( $try_rollback ) {
170
				/** This filter is documented in wp-admin/includes/update-core.php */
171
				apply_filters( 'update_feedback', $result );
172
173
				/** This filter is documented in wp-admin/includes/update-core.php */
174
				apply_filters( 'update_feedback', $this->strings['start_rollback'] );
175
176
				$rollback_result = $this->upgrade( $current, array_merge( $parsed_args, array( 'do_rollback' => true ) ) );
177
178
				$original_result = $result;
179
				$result = new WP_Error( 'rollback_was_required', $this->strings['rollback_was_required'], (object) array( 'update' => $original_result, 'rollback' => $rollback_result ) );
180
			}
181
		}
182
183
		/** This action is documented in wp-admin/includes/class-wp-upgrader.php */
184
		do_action( 'upgrader_process_complete', $this, array( 'action' => 'update', 'type' => 'core' ) );
185
186
		// Clear the current updates
187
		delete_site_transient( 'update_core' );
188
189
		if ( ! $parsed_args['do_rollback'] ) {
190
			$stats = array(
191
				'update_type'      => $current->response,
192
				'success'          => true,
193
				'fs_method'        => $wp_filesystem->method,
194
				'fs_method_forced' => defined( 'FS_METHOD' ) || has_filter( 'filesystem_method' ),
195
				'fs_method_direct' => !empty( $GLOBALS['_wp_filesystem_direct_method'] ) ? $GLOBALS['_wp_filesystem_direct_method'] : '',
196
				'time_taken'       => time() - $start_time,
197
				'reported'         => $wp_version,
198
				'attempted'        => $current->version,
199
			);
200
201
			if ( is_wp_error( $result ) ) {
202
				$stats['success'] = false;
203
				// Did a rollback occur?
204
				if ( ! empty( $try_rollback ) ) {
205
					$stats['error_code'] = $original_result->get_error_code();
0 ignored issues
show
Bug introduced by
The variable $original_result 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...
206
					$stats['error_data'] = $original_result->get_error_data();
207
					// Was the rollback successful? If not, collect its error too.
208
					$stats['rollback'] = ! is_wp_error( $rollback_result );
0 ignored issues
show
Bug introduced by
The variable $rollback_result 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...
209
					if ( is_wp_error( $rollback_result ) ) {
210
						$stats['rollback_code'] = $rollback_result->get_error_code();
211
						$stats['rollback_data'] = $rollback_result->get_error_data();
212
					}
213
				} else {
214
					$stats['error_code'] = $result->get_error_code();
215
					$stats['error_data'] = $result->get_error_data();
216
				}
217
			}
218
219
			wp_version_check( $stats );
220
		}
221
222
		WP_Upgrader::release_lock( 'core_updater' );
223
224
		return $result;
225
	}
226
227
	/**
228
	 * Determines if this WordPress Core version should update to an offered version or not.
229
	 *
230
	 * @since 3.7.0
231
	 * @access public
232
	 *
233
	 * @static
234
	 *
235
	 * @param string $offered_ver The offered version, of the format x.y.z.
236
	 * @return bool True if we should update to the offered version, otherwise false.
237
	 */
238
	public static function should_update_to_version( $offered_ver ) {
239
		include( ABSPATH . WPINC . '/version.php' ); // $wp_version; // x.y.z
240
241
		$current_branch = implode( '.', array_slice( preg_split( '/[.-]/', $wp_version  ), 0, 2 ) ); // x.y
0 ignored issues
show
Bug introduced by
The variable $wp_version does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
242
		$new_branch     = implode( '.', array_slice( preg_split( '/[.-]/', $offered_ver ), 0, 2 ) ); // x.y
243
		$current_is_development_version = (bool) strpos( $wp_version, '-' );
244
245
		// Defaults:
246
		$upgrade_dev   = true;
247
		$upgrade_minor = true;
248
		$upgrade_major = false;
249
250
		// WP_AUTO_UPDATE_CORE = true (all), 'minor', false.
251
		if ( defined( 'WP_AUTO_UPDATE_CORE' ) ) {
252
			if ( false === WP_AUTO_UPDATE_CORE ) {
253
				// Defaults to turned off, unless a filter allows it
254
				$upgrade_dev = $upgrade_minor = $upgrade_major = false;
255
			} elseif ( true === WP_AUTO_UPDATE_CORE ) {
256
				// ALL updates for core
257
				$upgrade_dev = $upgrade_minor = $upgrade_major = true;
258
			} elseif ( 'minor' === WP_AUTO_UPDATE_CORE ) {
259
				// Only minor updates for core
260
				$upgrade_dev = $upgrade_major = false;
261
				$upgrade_minor = true;
262
			}
263
		}
264
265
		// 1: If we're already on that version, not much point in updating?
266
		if ( $offered_ver == $wp_version )
267
			return false;
268
269
		// 2: If we're running a newer version, that's a nope
270
		if ( version_compare( $wp_version, $offered_ver, '>' ) )
271
			return false;
272
273
		$failure_data = get_site_option( 'auto_core_update_failed' );
274
		if ( $failure_data ) {
275
			// If this was a critical update failure, cannot update.
276
			if ( ! empty( $failure_data['critical'] ) )
277
				return false;
278
279
			// Don't claim we can update on update-core.php if we have a non-critical failure logged.
280
			if ( $wp_version == $failure_data['current'] && false !== strpos( $offered_ver, '.1.next.minor' ) )
281
				return false;
282
283
			// Cannot update if we're retrying the same A to B update that caused a non-critical failure.
284
			// Some non-critical failures do allow retries, like download_failed.
285
			// 3.7.1 => 3.7.2 resulted in files_not_writable, if we are still on 3.7.1 and still trying to update to 3.7.2.
286
			if ( empty( $failure_data['retry'] ) && $wp_version == $failure_data['current'] && $offered_ver == $failure_data['attempted'] )
287
				return false;
288
		}
289
290
		// 3: 3.7-alpha-25000 -> 3.7-alpha-25678 -> 3.7-beta1 -> 3.7-beta2
291
		if ( $current_is_development_version ) {
292
293
			/**
294
			 * Filters whether to enable automatic core updates for development versions.
295
			 *
296
			 * @since 3.7.0
297
			 *
298
			 * @param bool $upgrade_dev Whether to enable automatic updates for
299
			 *                          development versions.
300
			 */
301
			if ( ! apply_filters( 'allow_dev_auto_core_updates', $upgrade_dev ) )
302
				return false;
303
			// Else fall through to minor + major branches below.
304
		}
305
306
		// 4: Minor In-branch updates (3.7.0 -> 3.7.1 -> 3.7.2 -> 3.7.4)
307
		if ( $current_branch == $new_branch ) {
308
309
			/**
310
			 * Filters whether to enable minor automatic core updates.
311
			 *
312
			 * @since 3.7.0
313
			 *
314
			 * @param bool $upgrade_minor Whether to enable minor automatic core updates.
315
			 */
316
			return apply_filters( 'allow_minor_auto_core_updates', $upgrade_minor );
317
		}
318
319
		// 5: Major version updates (3.7.0 -> 3.8.0 -> 3.9.1)
320
		if ( version_compare( $new_branch, $current_branch, '>' ) ) {
321
322
			/**
323
			 * Filters whether to enable major automatic core updates.
324
			 *
325
			 * @since 3.7.0
326
			 *
327
			 * @param bool $upgrade_major Whether to enable major automatic core updates.
328
			 */
329
			return apply_filters( 'allow_major_auto_core_updates', $upgrade_major );
330
		}
331
332
		// If we're not sure, we don't want it
333
		return false;
334
	}
335
336
	/**
337
	 * Compare the disk file checksums against the expected checksums.
338
	 *
339
	 * @since 3.7.0
340
	 * @access public
341
	 *
342
	 * @global string $wp_version
343
	 * @global string $wp_local_package
344
	 *
345
	 * @return bool True if the checksums match, otherwise false.
346
	 */
347
	public function check_files() {
348
		global $wp_version, $wp_local_package;
349
350
		$checksums = get_core_checksums( $wp_version, isset( $wp_local_package ) ? $wp_local_package : 'en_US' );
351
352
		if ( ! is_array( $checksums ) )
353
			return false;
354
355
		foreach ( $checksums as $file => $checksum ) {
356
			// Skip files which get updated
357
			if ( 'wp-content' == substr( $file, 0, 10 ) )
358
				continue;
359
			if ( ! file_exists( ABSPATH . $file ) || md5_file( ABSPATH . $file ) !== $checksum )
360
				return false;
361
		}
362
363
		return true;
364
	}
365
}
366