WP_Upgrader::install_package()   F
last analyzed

Complexity

Conditions 23
Paths 1045

Size

Total Lines 177
Code Lines 71

Duplication

Lines 5
Ratio 2.82 %

Importance

Changes 0
Metric Value
cc 23
eloc 71
nc 1045
nop 1
dl 5
loc 177
rs 2
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * Upgrade API: WP_Upgrader class
4
 *
5
 * Requires skin classes and WP_Upgrader subclasses for backward compatibility.
6
 *
7
 * @package WordPress
8
 * @subpackage Upgrader
9
 * @since 2.8.0
10
 */
11
12
/** WP_Upgrader_Skin class */
13
require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader-skin.php';
14
15
/** Plugin_Upgrader_Skin class */
16
require_once ABSPATH . 'wp-admin/includes/class-plugin-upgrader-skin.php';
17
18
/** Theme_Upgrader_Skin class */
19
require_once ABSPATH . 'wp-admin/includes/class-theme-upgrader-skin.php';
20
21
/** Bulk_Upgrader_Skin class */
22
require_once ABSPATH . 'wp-admin/includes/class-bulk-upgrader-skin.php';
23
24
/** Bulk_Plugin_Upgrader_Skin class */
25
require_once ABSPATH . 'wp-admin/includes/class-bulk-plugin-upgrader-skin.php';
26
27
/** Bulk_Theme_Upgrader_Skin class */
28
require_once ABSPATH . 'wp-admin/includes/class-bulk-theme-upgrader-skin.php';
29
30
/** Plugin_Installer_Skin class */
31
require_once ABSPATH . 'wp-admin/includes/class-plugin-installer-skin.php';
32
33
/** Theme_Installer_Skin class */
34
require_once ABSPATH . 'wp-admin/includes/class-theme-installer-skin.php';
35
36
/** Language_Pack_Upgrader_Skin class */
37
require_once ABSPATH . 'wp-admin/includes/class-language-pack-upgrader-skin.php';
38
39
/** Automatic_Upgrader_Skin class */
40
require_once ABSPATH . 'wp-admin/includes/class-automatic-upgrader-skin.php';
41
42
/** WP_Ajax_Upgrader_Skin class */
43
require_once ABSPATH . 'wp-admin/includes/class-wp-ajax-upgrader-skin.php';
44
45
/**
46
 * Core class used for upgrading/installing a local set of files via
47
 * the Filesystem Abstraction classes from a Zip file.
48
 *
49
 * @since 2.8.0
50
 */
51
class WP_Upgrader {
52
53
	/**
54
	 * The error/notification strings used to update the user on the progress.
55
	 *
56
	 * @since 2.8.0
57
	 * @access public
58
	 * @var array $strings
59
	 */
60
	public $strings = array();
61
62
	/**
63
	 * The upgrader skin being used.
64
	 *
65
	 * @since 2.8.0
66
	 * @access public
67
	 * @var Automatic_Upgrader_Skin|WP_Upgrader_Skin $skin
68
	 */
69
	public $skin = null;
70
71
	/**
72
	 * The result of the installation.
73
	 *
74
	 * This is set by WP_Upgrader::install_package(), only when the package is installed
75
	 * successfully. It will then be an array, unless a WP_Error is returned by the
76
	 * {@see 'upgrader_post_install'} filter. In that case, the WP_Error will be assigned to
77
	 * it.
78
	 *
79
	 * @since 2.8.0
80
	 * @access public
81
	 *
82
	 * @var WP_Error|array $result {
83
	 *      @type string $source             The full path to the source the files were installed from.
84
	 *      @type string $source_files       List of all the files in the source directory.
85
	 *      @type string $destination        The full path to the install destination folder.
86
	 *      @type string $destination_name   The name of the destination folder, or empty if `$destination`
87
	 *                                       and `$local_destination` are the same.
88
	 *      @type string $local_destination  The full local path to the destination folder. This is usually
89
	 *                                       the same as `$destination`.
90
	 *      @type string $remote_destination The full remote path to the destination folder
91
	 *                                       (i.e., from `$wp_filesystem`).
92
	 *      @type bool   $clear_destination  Whether the destination folder was cleared.
93
	 * }
94
	 */
95
	public $result = array();
96
97
	/**
98
	 * The total number of updates being performed.
99
	 *
100
	 * Set by the bulk update methods.
101
	 *
102
	 * @since 3.0.0
103
	 * @access public
104
	 * @var int $update_count
105
	 */
106
	public $update_count = 0;
107
108
	/**
109
	 * The current update if multiple updates are being performed.
110
	 *
111
	 * Used by the bulk update methods, and incremented for each update.
112
	 *
113
	 * @since 3.0.0
114
	 * @access public
115
	 * @var int
116
	 */
117
	public $update_current = 0;
118
119
	/**
120
	 * Construct the upgrader with a skin.
121
	 *
122
	 * @since 2.8.0
123
	 * @access public
124
	 *
125
	 * @param WP_Upgrader_Skin $skin The upgrader skin to use. Default is a WP_Upgrader_Skin.
126
	 *                               instance.
127
	 */
128
	public function __construct( $skin = null ) {
129
		if ( null == $skin )
130
			$this->skin = new WP_Upgrader_Skin();
131
		else
132
			$this->skin = $skin;
133
	}
134
135
	/**
136
	 * Initialize the upgrader.
137
	 *
138
	 * This will set the relationship between the skin being used and this upgrader,
139
	 * and also add the generic strings to `WP_Upgrader::$strings`.
140
	 *
141
	 * @since 2.8.0
142
	 * @access public
143
	 */
144
	public function init() {
145
		$this->skin->set_upgrader($this);
146
		$this->generic_strings();
147
	}
148
149
	/**
150
	 * Add the generic strings to WP_Upgrader::$strings.
151
	 *
152
	 * @since 2.8.0
153
	 * @access public
154
	 */
155
	public function generic_strings() {
156
		$this->strings['bad_request'] = __('Invalid data provided.');
157
		$this->strings['fs_unavailable'] = __('Could not access filesystem.');
158
		$this->strings['fs_error'] = __('Filesystem error.');
159
		$this->strings['fs_no_root_dir'] = __('Unable to locate WordPress root directory.');
160
		$this->strings['fs_no_content_dir'] = __('Unable to locate WordPress content directory (wp-content).');
161
		$this->strings['fs_no_plugins_dir'] = __('Unable to locate WordPress plugin directory.');
162
		$this->strings['fs_no_themes_dir'] = __('Unable to locate WordPress theme directory.');
163
		/* translators: %s: directory name */
164
		$this->strings['fs_no_folder'] = __('Unable to locate needed folder (%s).');
165
166
		$this->strings['download_failed'] = __('Download failed.');
167
		$this->strings['installing_package'] = __('Installing the latest version&#8230;');
168
		$this->strings['no_files'] = __('The package contains no files.');
169
		$this->strings['folder_exists'] = __('Destination folder already exists.');
170
		$this->strings['mkdir_failed'] = __('Could not create directory.');
171
		$this->strings['incompatible_archive'] = __('The package could not be installed.');
172
		$this->strings['files_not_writable'] = __( 'The update cannot be installed because we will be unable to copy some files. This is usually due to inconsistent file permissions.' );
173
174
		$this->strings['maintenance_start'] = __('Enabling Maintenance mode&#8230;');
175
		$this->strings['maintenance_end'] = __('Disabling Maintenance mode&#8230;');
176
	}
177
178
	/**
179
	 * Connect to the filesystem.
180
	 *
181
	 * @since 2.8.0
182
	 * @access public
183
	 *
184
	 * @global WP_Filesystem_Base $wp_filesystem Subclass
185
	 *
186
	 * @param array $directories                  Optional. A list of directories. If any of these do
187
	 *                                            not exist, a WP_Error object will be returned.
188
	 *                                            Default empty array.
189
	 * @param bool  $allow_relaxed_file_ownership Whether to allow relaxed file ownership.
190
	 *                                            Default false.
191
	 * @return bool|WP_Error True if able to connect, false or a WP_Error otherwise.
192
	 */
193
	public function fs_connect( $directories = array(), $allow_relaxed_file_ownership = false ) {
194
		global $wp_filesystem;
195
196
		if ( false === ( $credentials = $this->skin->request_filesystem_credentials( false, $directories[0], $allow_relaxed_file_ownership ) ) ) {
197
			return false;
198
		}
199
200
		if ( ! WP_Filesystem( $credentials, $directories[0], $allow_relaxed_file_ownership ) ) {
201
			$error = true;
202
			if ( is_object($wp_filesystem) && $wp_filesystem->errors->get_error_code() )
203
				$error = $wp_filesystem->errors;
204
			// Failed to connect, Error and request again
205
			$this->skin->request_filesystem_credentials( $error, $directories[0], $allow_relaxed_file_ownership );
206
			return false;
207
		}
208
209
		if ( ! is_object($wp_filesystem) )
210
			return new WP_Error('fs_unavailable', $this->strings['fs_unavailable'] );
211
212 View Code Duplication
		if ( is_wp_error($wp_filesystem->errors) && $wp_filesystem->errors->get_error_code() )
213
			return new WP_Error('fs_error', $this->strings['fs_error'], $wp_filesystem->errors);
214
215
		foreach ( (array)$directories as $dir ) {
216
			switch ( $dir ) {
217
				case ABSPATH:
218
					if ( ! $wp_filesystem->abspath() )
219
						return new WP_Error('fs_no_root_dir', $this->strings['fs_no_root_dir']);
220
					break;
221
				case WP_CONTENT_DIR:
222
					if ( ! $wp_filesystem->wp_content_dir() )
223
						return new WP_Error('fs_no_content_dir', $this->strings['fs_no_content_dir']);
224
					break;
225
				case WP_PLUGIN_DIR:
226
					if ( ! $wp_filesystem->wp_plugins_dir() )
227
						return new WP_Error('fs_no_plugins_dir', $this->strings['fs_no_plugins_dir']);
228
					break;
229
				case get_theme_root():
230
					if ( ! $wp_filesystem->wp_themes_dir() )
231
						return new WP_Error('fs_no_themes_dir', $this->strings['fs_no_themes_dir']);
232
					break;
233
				default:
234
					if ( ! $wp_filesystem->find_folder($dir) )
235
						return new WP_Error( 'fs_no_folder', sprintf( $this->strings['fs_no_folder'], esc_html( basename( $dir ) ) ) );
236
					break;
237
			}
238
		}
239
		return true;
240
	} //end fs_connect();
241
242
	/**
243
	 * Download a package.
244
	 *
245
	 * @since 2.8.0
246
	 * @access public
247
	 *
248
	 * @param string $package The URI of the package. If this is the full path to an
249
	 *                        existing local file, it will be returned untouched.
250
	 * @return string|WP_Error The full path to the downloaded package file, or a WP_Error object.
251
	 */
252
	public function download_package( $package ) {
253
254
		/**
255
		 * Filters whether to return the package.
256
		 *
257
		 * @since 3.7.0
258
		 * @access public
259
		 *
260
		 * @param bool        $reply   Whether to bail without returning the package.
261
		 *                             Default false.
262
		 * @param string      $package The package file name.
263
		 * @param WP_Upgrader $this    The WP_Upgrader instance.
264
		 */
265
		$reply = apply_filters( 'upgrader_pre_download', false, $package, $this );
266
		if ( false !== $reply )
267
			return $reply;
268
269
		if ( ! preg_match('!^(http|https|ftp)://!i', $package) && file_exists($package) ) //Local file or remote?
270
			return $package; //must be a local file..
271
272
		if ( empty($package) )
273
			return new WP_Error('no_package', $this->strings['no_package']);
274
275
		$this->skin->feedback('downloading_package', $package);
276
277
		$download_file = download_url($package);
278
279
		if ( is_wp_error($download_file) )
280
			return new WP_Error('download_failed', $this->strings['download_failed'], $download_file->get_error_message());
281
282
		return $download_file;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $download_file; (WP_Error|array|boolean|string) is incompatible with the return type documented by WP_Upgrader::download_package of type string|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...
283
	}
284
285
	/**
286
	 * Unpack a compressed package file.
287
	 *
288
	 * @since 2.8.0
289
	 * @access public
290
	 *
291
	 * @global WP_Filesystem_Base $wp_filesystem Subclass
292
	 *
293
	 * @param string $package        Full path to the package file.
294
	 * @param bool   $delete_package Optional. Whether to delete the package file after attempting
295
	 *                               to unpack it. Default true.
296
	 * @return string|WP_Error The path to the unpacked contents, or a WP_Error on failure.
0 ignored issues
show
Documentation introduced by
Should the return type not be WP_Error|boolean|string?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
297
	 */
298
	public function unpack_package( $package, $delete_package = true ) {
299
		global $wp_filesystem;
300
301
		$this->skin->feedback('unpack_package');
302
303
		$upgrade_folder = $wp_filesystem->wp_content_dir() . 'upgrade/';
304
305
		//Clean up contents of upgrade directory beforehand.
306
		$upgrade_files = $wp_filesystem->dirlist($upgrade_folder);
307
		if ( !empty($upgrade_files) ) {
308
			foreach ( $upgrade_files as $file )
309
				$wp_filesystem->delete($upgrade_folder . $file['name'], true);
310
		}
311
312
		// We need a working directory - Strip off any .tmp or .zip suffixes
313
		$working_dir = $upgrade_folder . basename( basename( $package, '.tmp' ), '.zip' );
314
315
		// Clean up working directory
316
		if ( $wp_filesystem->is_dir($working_dir) )
317
			$wp_filesystem->delete($working_dir, true);
318
319
		// Unzip package to working directory
320
		$result = unzip_file( $package, $working_dir );
321
322
		// Once extracted, delete the package if required.
323
		if ( $delete_package )
324
			unlink($package);
325
326
		if ( is_wp_error($result) ) {
327
			$wp_filesystem->delete($working_dir, true);
328
			if ( 'incompatible_archive' == $result->get_error_code() ) {
329
				return new WP_Error( 'incompatible_archive', $this->strings['incompatible_archive'], $result->get_error_data() );
330
			}
331
			return $result;
332
		}
333
334
		return $working_dir;
335
	}
336
337
	/**
338
	 * Clears the directory where this item is going to be installed into.
339
	 *
340
	 * @since 4.3.0
341
	 * @access public
342
	 *
343
	 * @global WP_Filesystem_Base $wp_filesystem Subclass
344
	 *
345
	 * @param string $remote_destination The location on the remote filesystem to be cleared
346
	 * @return bool|WP_Error True upon success, WP_Error on failure.
347
	 */
348
	public function clear_destination( $remote_destination ) {
349
		global $wp_filesystem;
350
351
		if ( ! $wp_filesystem->exists( $remote_destination ) ) {
352
			return true;
353
		}
354
355
		// Check all files are writable before attempting to clear the destination.
356
		$unwritable_files = array();
357
358
		$_files = $wp_filesystem->dirlist( $remote_destination, true, true );
359
360
		// Flatten the resulting array, iterate using each as we append to the array during iteration.
361
		while ( $f = each( $_files ) ) {
362
			$file = $f['value'];
363
			$name = $f['key'];
364
365
			if ( ! isset( $file['files'] ) ) {
366
				continue;
367
			}
368
369
			foreach ( $file['files'] as $filename => $details ) {
370
				$_files[ $name . '/' . $filename ] = $details;
371
			}
372
		}
373
374
		// Check writability.
375
		foreach ( $_files as $filename => $file_details ) {
376
			if ( ! $wp_filesystem->is_writable( $remote_destination . $filename ) ) {
377
378
				// Attempt to alter permissions to allow writes and try again.
379
				$wp_filesystem->chmod( $remote_destination . $filename, ( 'd' == $file_details['type'] ? FS_CHMOD_DIR : FS_CHMOD_FILE ) );
380
				if ( ! $wp_filesystem->is_writable( $remote_destination . $filename ) ) {
381
					$unwritable_files[] = $filename;
382
				}
383
			}
384
		}
385
386
		if ( ! empty( $unwritable_files ) ) {
387
			return new WP_Error( 'files_not_writable', $this->strings['files_not_writable'], implode( ', ', $unwritable_files ) );
388
		}
389
390
		if ( ! $wp_filesystem->delete( $remote_destination, true ) ) {
391
			return new WP_Error( 'remove_old_failed', $this->strings['remove_old_failed'] );
392
		}
393
394
		return true;
395
	}
396
397
	/**
398
	 * Install a package.
399
	 *
400
	 * Copies the contents of a package form a source directory, and installs them in
401
	 * a destination directory. Optionally removes the source. It can also optionally
402
	 * clear out the destination folder if it already exists.
403
	 *
404
	 * @since 2.8.0
405
	 * @access public
406
	 *
407
	 * @global WP_Filesystem_Base $wp_filesystem Subclass
408
	 * @global array              $wp_theme_directories
409
	 *
410
	 * @param array|string $args {
411
	 *     Optional. Array or string of arguments for installing a package. Default empty array.
412
	 *
413
	 *     @type string $source                      Required path to the package source. Default empty.
414
	 *     @type string $destination                 Required path to a folder to install the package in.
415
	 *                                               Default empty.
416
	 *     @type bool   $clear_destination           Whether to delete any files already in the destination
417
	 *                                               folder. Default false.
418
	 *     @type bool   $clear_working               Whether to delete the files form the working directory
419
	 *                                               after copying to the destination. Default false.
420
	 *     @type bool   $abort_if_destination_exists Whether to abort the installation if
421
	 *                                               the destination folder already exists. Default true.
422
	 *     @type array  $hook_extra                  Extra arguments to pass to the filter hooks called by
423
	 *                                               WP_Upgrader::install_package(). Default empty array.
424
	 * }
425
	 *
426
	 * @return array|WP_Error The result (also stored in `WP_Upgrader::$result`), or a WP_Error on failure.
427
	 */
428
	public function install_package( $args = array() ) {
429
		global $wp_filesystem, $wp_theme_directories;
430
431
		$defaults = array(
432
			'source' => '', // Please always pass this
433
			'destination' => '', // and this
434
			'clear_destination' => false,
435
			'clear_working' => false,
436
			'abort_if_destination_exists' => true,
437
			'hook_extra' => array()
438
		);
439
440
		$args = wp_parse_args($args, $defaults);
441
442
		// These were previously extract()'d.
443
		$source = $args['source'];
444
		$destination = $args['destination'];
445
		$clear_destination = $args['clear_destination'];
446
447
		@set_time_limit( 300 );
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
448
449
		if ( empty( $source ) || empty( $destination ) ) {
450
			return new WP_Error( 'bad_request', $this->strings['bad_request'] );
451
		}
452
		$this->skin->feedback( 'installing_package' );
453
454
		/**
455
		 * Filters the install response before the installation has started.
456
		 *
457
		 * Returning a truthy value, or one that could be evaluated as a WP_Error
458
		 * will effectively short-circuit the installation, returning that value
459
		 * instead.
460
		 *
461
		 * @since 2.8.0
462
		 *
463
		 * @param bool|WP_Error $response   Response.
464
		 * @param array         $hook_extra Extra arguments passed to hooked filters.
465
		 */
466
		$res = apply_filters( 'upgrader_pre_install', true, $args['hook_extra'] );
467
468
		if ( is_wp_error( $res ) ) {
469
			return $res;
470
		}
471
472
		//Retain the Original source and destinations
473
		$remote_source = $args['source'];
474
		$local_destination = $destination;
475
476
		$source_files = array_keys( $wp_filesystem->dirlist( $remote_source ) );
477
		$remote_destination = $wp_filesystem->find_folder( $local_destination );
478
479
		//Locate which directory to copy to the new folder, This is based on the actual folder holding the files.
480
		if ( 1 == count( $source_files ) && $wp_filesystem->is_dir( trailingslashit( $args['source'] ) . $source_files[0] . '/' ) ) { //Only one folder? Then we want its contents.
481
			$source = trailingslashit( $args['source'] ) . trailingslashit( $source_files[0] );
482
		} elseif ( count( $source_files ) == 0 ) {
483
			return new WP_Error( 'incompatible_archive_empty', $this->strings['incompatible_archive'], $this->strings['no_files'] ); // There are no files?
484
		} else { // It's only a single file, the upgrader will use the folder name of this file as the destination folder. Folder name is based on zip filename.
485
			$source = trailingslashit( $args['source'] );
486
		}
487
488
		/**
489
		 * Filters the source file location for the upgrade package.
490
		 *
491
		 * @since 2.8.0
492
		 * @since 4.4.0 The $hook_extra parameter became available.
493
		 *
494
		 * @param string      $source        File source location.
495
		 * @param string      $remote_source Remote file source location.
496
		 * @param WP_Upgrader $this          WP_Upgrader instance.
497
		 * @param array       $hook_extra    Extra arguments passed to hooked filters.
498
		 */
499
		$source = apply_filters( 'upgrader_source_selection', $source, $remote_source, $this, $args['hook_extra'] );
500
501
		if ( is_wp_error( $source ) ) {
502
			return $source;
503
		}
504
505
		// Has the source location changed? If so, we need a new source_files list.
506
		if ( $source !== $remote_source ) {
507
			$source_files = array_keys( $wp_filesystem->dirlist( $source ) );
508
		}
509
510
		/*
511
		 * Protection against deleting files in any important base directories.
512
		 * Theme_Upgrader & Plugin_Upgrader also trigger this, as they pass the
513
		 * destination directory (WP_PLUGIN_DIR / wp-content/themes) intending
514
		 * to copy the directory into the directory, whilst they pass the source
515
		 * as the actual files to copy.
516
		 */
517
		$protected_directories = array( ABSPATH, WP_CONTENT_DIR, WP_PLUGIN_DIR, WP_CONTENT_DIR . '/themes' );
518
519
		if ( is_array( $wp_theme_directories ) ) {
520
			$protected_directories = array_merge( $protected_directories, $wp_theme_directories );
521
		}
522
523
		if ( in_array( $destination, $protected_directories ) ) {
524
			$remote_destination = trailingslashit( $remote_destination ) . trailingslashit( basename( $source ) );
525
			$destination = trailingslashit( $destination ) . trailingslashit( basename( $source ) );
526
		}
527
528
		if ( $clear_destination ) {
529
			// We're going to clear the destination if there's something there.
530
			$this->skin->feedback('remove_old');
531
532
			$removed = $this->clear_destination( $remote_destination );
533
534
			/**
535
			 * Filters whether the upgrader cleared the destination.
536
			 *
537
			 * @since 2.8.0
538
			 *
539
			 * @param mixed  $removed            Whether the destination was cleared. true on success, WP_Error on failure
540
			 * @param string $local_destination  The local package destination.
541
			 * @param string $remote_destination The remote package destination.
542
			 * @param array  $hook_extra         Extra arguments passed to hooked filters.
543
			 */
544
			$removed = apply_filters( 'upgrader_clear_destination', $removed, $local_destination, $remote_destination, $args['hook_extra'] );
545
546
			if ( is_wp_error( $removed ) ) {
547
				return $removed;
548
			}
549
		} elseif ( $args['abort_if_destination_exists'] && $wp_filesystem->exists($remote_destination) ) {
550
			//If we're not clearing the destination folder and something exists there already, Bail.
551
			//But first check to see if there are actually any files in the folder.
552
			$_files = $wp_filesystem->dirlist($remote_destination);
553
			if ( ! empty($_files) ) {
554
				$wp_filesystem->delete($remote_source, true); //Clear out the source files.
555
				return new WP_Error('folder_exists', $this->strings['folder_exists'], $remote_destination );
556
			}
557
		}
558
559
		//Create destination if needed
560 View Code Duplication
		if ( ! $wp_filesystem->exists( $remote_destination ) ) {
561
			if ( ! $wp_filesystem->mkdir( $remote_destination, FS_CHMOD_DIR ) ) {
562
				return new WP_Error( 'mkdir_failed_destination', $this->strings['mkdir_failed'], $remote_destination );
563
			}
564
		}
565
		// Copy new version of item into place.
566
		$result = copy_dir($source, $remote_destination);
567
		if ( is_wp_error($result) ) {
568
			if ( $args['clear_working'] ) {
569
				$wp_filesystem->delete( $remote_source, true );
570
			}
571
			return $result;
572
		}
573
574
		//Clear the Working folder?
575
		if ( $args['clear_working'] ) {
576
			$wp_filesystem->delete( $remote_source, true );
577
		}
578
579
		$destination_name = basename( str_replace($local_destination, '', $destination) );
580
		if ( '.' == $destination_name ) {
581
			$destination_name = '';
582
		}
583
584
		$this->result = compact( 'source', 'source_files', 'destination', 'destination_name', 'local_destination', 'remote_destination', 'clear_destination' );
585
586
		/**
587
		 * Filters the install response after the installation has finished.
588
		 *
589
		 * @since 2.8.0
590
		 *
591
		 * @param bool  $response   Install response.
592
		 * @param array $hook_extra Extra arguments passed to hooked filters.
593
		 * @param array $result     Installation result data.
594
		 */
595
		$res = apply_filters( 'upgrader_post_install', true, $args['hook_extra'], $this->result );
596
597
		if ( is_wp_error($res) ) {
598
			$this->result = $res;
599
			return $res;
600
		}
601
602
		//Bombard the calling function will all the info which we've just used.
603
		return $this->result;
604
	}
605
606
	/**
607
	 * Run an upgrade/install.
608
	 *
609
	 * Attempts to download the package (if it is not a local file), unpack it, and
610
	 * install it in the destination folder.
611
	 *
612
	 * @since 2.8.0
613
	 * @access public
614
	 *
615
	 * @param array $options {
616
	 *     Array or string of arguments for upgrading/installing a package.
617
	 *
618
	 *     @type string $package                     The full path or URI of the package to install.
619
	 *                                               Default empty.
620
	 *     @type string $destination                 The full path to the destination folder.
621
	 *                                               Default empty.
622
	 *     @type bool   $clear_destination           Whether to delete any files already in the
623
	 *                                               destination folder. Default false.
624
	 *     @type bool   $clear_working               Whether to delete the files form the working
625
	 *                                               directory after copying to the destination.
626
	 *                                               Default false.
627
	 *     @type bool   $abort_if_destination_exists Whether to abort the installation if the destination
628
	 *                                               folder already exists. When true, `$clear_destination`
629
	 *                                               should be false. Default true.
630
	 *     @type bool   $is_multi                    Whether this run is one of multiple upgrade/install
631
	 *                                               actions being performed in bulk. When true, the skin
632
	 *                                               WP_Upgrader::header() and WP_Upgrader::footer()
633
	 *                                               aren't called. Default false.
634
	 *     @type array  $hook_extra                  Extra arguments to pass to the filter hooks called by
635
	 *                                               WP_Upgrader::run().
636
	 * }
637
	 * @return array|false|WP_error The result from self::install_package() on success, otherwise a WP_Error,
0 ignored issues
show
Documentation introduced by
Should the return type not be WP_Error|boolean|string|array? Also, consider making the array more specific, something like array<String>, or String[].

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

If the return type contains the type array, this check recommends the use of a more specific type like String[] or array<String>.

Loading history...
638
	 *                              or false if unable to connect to the filesystem.
639
	 */
640
	public function run( $options ) {
641
642
		$defaults = array(
643
			'package' => '', // Please always pass this.
644
			'destination' => '', // And this
645
			'clear_destination' => false,
646
			'abort_if_destination_exists' => true, // Abort if the Destination directory exists, Pass clear_destination as false please
647
			'clear_working' => true,
648
			'is_multi' => false,
649
			'hook_extra' => array() // Pass any extra $hook_extra args here, this will be passed to any hooked filters.
650
		);
651
652
		$options = wp_parse_args( $options, $defaults );
653
654
		/**
655
		 * Filters the package options before running an update.
656
		 *
657
		 * See also {@see 'upgrader_process_complete'}.
658
		 *
659
		 * @since 4.3.0
660
		 *
661
		 * @param array $options {
662
		 *     Options used by the upgrader.
663
		 *
664
		 *     @type string $package                     Package for update.
665
		 *     @type string $destination                 Update location.
666
		 *     @type bool   $clear_destination           Clear the destination resource.
667
		 *     @type bool   $clear_working               Clear the working resource.
668
		 *     @type bool   $abort_if_destination_exists Abort if the Destination directory exists.
669
		 *     @type bool   $is_multi                    Whether the upgrader is running multiple times.
670
		 *     @type array  $hook_extra {
671
		 *         Extra hook arguments.
672
		 *
673
		 *         @type string $action               Type of action. Default 'update'.
674
		 *         @type string $type                 Type of update process. Accepts 'plugin', 'theme', or 'core'.
675
		 *         @type bool   $bulk                 Whether the update process is a bulk update. Default true.
676
		 *         @type string $plugin               The base plugin path from the plugins directory.
677
		 *         @type string $theme                The stylesheet or template name of the theme.
678
		 *         @type string $language_update_type The language pack update type. Accepts 'plugin', 'theme',
679
		 *                                            or 'core'.
680
		 *         @type object $language_update      The language pack update offer.
681
		 *     }
682
		 * }
683
		 */
684
		$options = apply_filters( 'upgrader_package_options', $options );
685
686
		if ( ! $options['is_multi'] ) { // call $this->header separately if running multiple times
687
			$this->skin->header();
688
		}
689
690
		// Connect to the Filesystem first.
691
		$res = $this->fs_connect( array( WP_CONTENT_DIR, $options['destination'] ) );
692
		// Mainly for non-connected filesystem.
693
		if ( ! $res ) {
694
			if ( ! $options['is_multi'] ) {
695
				$this->skin->footer();
696
			}
697
			return false;
698
		}
699
700
		$this->skin->before();
701
702 View Code Duplication
		if ( is_wp_error($res) ) {
703
			$this->skin->error($res);
704
			$this->skin->after();
705
			if ( ! $options['is_multi'] ) {
706
				$this->skin->footer();
707
			}
708
			return $res;
709
		}
710
711
		/*
712
		 * Download the package (Note, This just returns the filename
713
		 * of the file if the package is a local file)
714
		 */
715
		$download = $this->download_package( $options['package'] );
716 View Code Duplication
		if ( is_wp_error($download) ) {
717
			$this->skin->error($download);
718
			$this->skin->after();
719
			if ( ! $options['is_multi'] ) {
720
				$this->skin->footer();
721
			}
722
			return $download;
723
		}
724
725
		$delete_package = ( $download != $options['package'] ); // Do not delete a "local" file
726
727
		// Unzips the file into a temporary directory.
728
		$working_dir = $this->unpack_package( $download, $delete_package );
729 View Code Duplication
		if ( is_wp_error($working_dir) ) {
730
			$this->skin->error($working_dir);
731
			$this->skin->after();
732
			if ( ! $options['is_multi'] ) {
733
				$this->skin->footer();
734
			}
735
			return $working_dir;
736
		}
737
738
		// With the given options, this installs it to the destination directory.
739
		$result = $this->install_package( array(
740
			'source' => $working_dir,
741
			'destination' => $options['destination'],
742
			'clear_destination' => $options['clear_destination'],
743
			'abort_if_destination_exists' => $options['abort_if_destination_exists'],
744
			'clear_working' => $options['clear_working'],
745
			'hook_extra' => $options['hook_extra']
746
		) );
747
748
		$this->skin->set_result($result);
749
		if ( is_wp_error($result) ) {
750
			$this->skin->error($result);
751
			$this->skin->feedback('process_failed');
752
		} else {
753
			// Install succeeded.
754
			$this->skin->feedback('process_success');
755
		}
756
757
		$this->skin->after();
758
759
		if ( ! $options['is_multi'] ) {
760
761
			/**
762
			 * Fires when the upgrader process is complete.
763
			 *
764
			 * See also {@see 'upgrader_package_options'}.
765
			 *
766
			 * @since 3.6.0
767
			 * @since 3.7.0 Added to WP_Upgrader::run().
768
			 * @since 4.6.0 `$translations` was added as a possible argument to `$hook_extra`.
769
			 *
770
			 * @param WP_Upgrader $this WP_Upgrader instance. In other contexts, $this, might be a
771
			 *                          Theme_Upgrader, Plugin_Upgrader, Core_Upgrade, or Language_Pack_Upgrader instance.
772
			 * @param array       $hook_extra {
773
			 *     Array of bulk item update data.
774
			 *
775
			 *     @type string $action       Type of action. Default 'update'.
776
			 *     @type string $type         Type of update process. Accepts 'plugin', 'theme', 'translation', or 'core'.
777
			 *     @type bool   $bulk         Whether the update process is a bulk update. Default true.
778
			 *     @type array  $plugins      Array of the basename paths of the plugins' main files.
779
			 *     @type array  $themes       The theme slugs.
780
			 *     @type array  $translations {
781
			 *         Array of translations update data.
782
			 *
783
			 *         @type string $language The locale the translation is for.
784
			 *         @type string $type     Type of translation. Accepts 'plugin', 'theme', or 'core'.
785
			 *         @type string $slug     Text domain the translation is for. The slug of a theme/plugin or
786
			 *                                'default' for core translations.
787
			 *         @type string $version  The version of a theme, plugin, or core.
788
			 *     }
789
			 * }
790
			 */
791
			do_action( 'upgrader_process_complete', $this, $options['hook_extra'] );
792
793
			$this->skin->footer();
794
		}
795
796
		return $result;
797
	}
798
799
	/**
800
	 * Toggle maintenance mode for the site.
801
	 *
802
	 * Creates/deletes the maintenance file to enable/disable maintenance mode.
803
	 *
804
	 * @since 2.8.0
805
	 * @access public
806
	 *
807
	 * @global WP_Filesystem_Base $wp_filesystem Subclass
808
	 *
809
	 * @param bool $enable True to enable maintenance mode, false to disable.
810
	 */
811
	public function maintenance_mode( $enable = false ) {
812
		global $wp_filesystem;
813
		$file = $wp_filesystem->abspath() . '.maintenance';
814
		if ( $enable ) {
815
			$this->skin->feedback('maintenance_start');
816
			// Create maintenance file to signal that we are upgrading
817
			$maintenance_string = '<?php $upgrading = ' . time() . '; ?>';
818
			$wp_filesystem->delete($file);
819
			$wp_filesystem->put_contents($file, $maintenance_string, FS_CHMOD_FILE);
820
		} elseif ( ! $enable && $wp_filesystem->exists( $file ) ) {
821
			$this->skin->feedback('maintenance_end');
822
			$wp_filesystem->delete($file);
823
		}
824
	}
825
826
	/**
827
 	 * Creates a lock using WordPress options.
828
 	 *
829
 	 * @since 4.5.0
830
 	 * @access public
831
 	 * @static
832
 	 *
833
 	 * @param string $lock_name       The name of this unique lock.
834
 	 * @param int    $release_timeout Optional. The duration in seconds to respect an existing lock.
835
	 *                                Default: 1 hour.
836
 	 * @return bool False if a lock couldn't be created or if the lock is still valid. True otherwise.
837
 	 */
838
	public static function create_lock( $lock_name, $release_timeout = null ) {
839
		global $wpdb;
840
		if ( ! $release_timeout ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $release_timeout of type integer|null is loosely compared to false; this is ambiguous if the integer can be zero. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
841
			$release_timeout = HOUR_IN_SECONDS;
842
		}
843
		$lock_option = $lock_name . '.lock';
844
845
		// Try to lock.
846
		$lock_result = $wpdb->query( $wpdb->prepare( "INSERT IGNORE INTO `$wpdb->options` ( `option_name`, `option_value`, `autoload` ) VALUES (%s, %s, 'no') /* LOCK */", $lock_option, time() ) );
847
848
		if ( ! $lock_result ) {
849
			$lock_result = get_option( $lock_option );
850
851
			// If a lock couldn't be created, and there isn't a lock, bail.
852
			if ( ! $lock_result ) {
853
				return false;
854
			}
855
856
			// Check to see if the lock is still valid. If it is, bail.
857
			if ( $lock_result > ( time() - $release_timeout ) ) {
858
				return false;
859
			}
860
861
			// There must exist an expired lock, clear it and re-gain it.
862
			WP_Upgrader::release_lock( $lock_name );
863
864
			return WP_Upgrader::create_lock( $lock_name, $release_timeout );
865
		}
866
867
		// Update the lock, as by this point we've definitely got a lock, just need to fire the actions.
868
		update_option( $lock_option, time() );
869
870
		return true;
871
	}
872
873
	/**
874
 	 * Releases an upgrader lock.
875
 	 *
876
 	 * @since 4.5.0
877
 	 * @access public
878
 	 * @static
879
	 *
880
	 * @see WP_Upgrader::create_lock()
881
 	 *
882
 	 * @param string $lock_name The name of this unique lock.
883
	 * @return bool True if the lock was successfully released. False on failure.
884
 	 */
885
	public static function release_lock( $lock_name ) {
886
		return delete_option( $lock_name . '.lock' );
887
	}
888
889
}
890
891
/** Plugin_Upgrader class */
892
require_once ABSPATH . 'wp-admin/includes/class-plugin-upgrader.php';
893
894
/** Theme_Upgrader class */
895
require_once ABSPATH . 'wp-admin/includes/class-theme-upgrader.php';
896
897
/** Language_Pack_Upgrader class */
898
require_once ABSPATH . 'wp-admin/includes/class-language-pack-upgrader.php';
899
900
/** Core_Upgrader class */
901
require_once ABSPATH . 'wp-admin/includes/class-core-upgrader.php';
902
903
/** File_Upload_Upgrader class */
904
require_once ABSPATH . 'wp-admin/includes/class-file-upload-upgrader.php';
905
906
/** WP_Automatic_Updater class */
907
require_once ABSPATH . 'wp-admin/includes/class-wp-automatic-updater.php';
908