file.php ➔ _unzip_file_pclzip()   F
last analyzed

Complexity

Conditions 23
Paths 269

Size

Total Lines 80
Code Lines 44

Duplication

Lines 25
Ratio 31.25 %

Importance

Changes 0
Metric Value
cc 23
eloc 44
nc 269
nop 3
dl 25
loc 80
rs 3.8309
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
 * Filesystem API: Top-level functionality
4
 *
5
 * Functions for reading, writing, modifying, and deleting files on the file system.
6
 * Includes functionality for theme-specific files as well as operations for uploading,
7
 * archiving, and rendering output when necessary.
8
 *
9
 * @package WordPress
10
 * @subpackage Filesystem
11
 * @since 2.3.0
12
 */
13
14
/** The descriptions for theme files. */
15
$wp_file_descriptions = array(
16
	'functions.php'         => __( 'Theme Functions' ),
17
	'header.php'            => __( 'Theme Header' ),
18
	'footer.php'            => __( 'Theme Footer' ),
19
	'sidebar.php'           => __( 'Sidebar' ),
20
	'comments.php'          => __( 'Comments' ),
21
	'searchform.php'        => __( 'Search Form' ),
22
	'404.php'               => __( '404 Template' ),
23
	'link.php'              => __( 'Links Template' ),
24
	// Archives
25
	'index.php'             => __( 'Main Index Template' ),
26
	'archive.php'           => __( 'Archives' ),
27
	'author.php'            => __( 'Author Template' ),
28
	'taxonomy.php'          => __( 'Taxonomy Template' ),
29
	'category.php'          => __( 'Category Template' ),
30
	'tag.php'               => __( 'Tag Template' ),
31
	'home.php'              => __( 'Posts Page' ),
32
	'search.php'            => __( 'Search Results' ),
33
	'date.php'              => __( 'Date Template' ),
34
	// Content
35
	'singular.php'          => __( 'Singular Template' ),
36
	'single.php'            => __( 'Single Post' ),
37
	'page.php'              => __( 'Single Page' ),
38
	'front-page.php'        => __( 'Static Front Page' ),
39
	// Attachments
40
	'attachment.php'        => __( 'Attachment Template' ),
41
	'image.php'             => __( 'Image Attachment Template' ),
42
	'video.php'             => __( 'Video Attachment Template' ),
43
	'audio.php'             => __( 'Audio Attachment Template' ),
44
	'application.php'       => __( 'Application Attachment Template' ),
45
	// Embeds
46
	'embed.php'             => __( 'Embed Template' ),
47
	'embed-404.php'         => __( 'Embed 404 Template' ),
48
	'embed-content.php'     => __( 'Embed Content Template' ),
49
	'header-embed.php'      => __( 'Embed Header Template' ),
50
	'footer-embed.php'      => __( 'Embed Footer Template' ),
51
	// Stylesheets
52
	'style.css'             => __( 'Stylesheet' ),
53
	'editor-style.css'      => __( 'Visual Editor Stylesheet' ),
54
	'editor-style-rtl.css'  => __( 'Visual Editor RTL Stylesheet' ),
55
	'rtl.css'               => __( 'RTL Stylesheet' ),
56
	// Other
57
	'my-hacks.php'          => __( 'my-hacks.php (legacy hacks support)' ),
58
	'.htaccess'             => __( '.htaccess (for rewrite rules )' ),
59
	// Deprecated files
60
	'wp-layout.css'         => __( 'Stylesheet' ),
61
	'wp-comments.php'       => __( 'Comments Template' ),
62
	'wp-comments-popup.php' => __( 'Popup Comments Template' ),
63
	'comments-popup.php'    => __( 'Popup Comments' ),
64
);
65
66
/**
67
 * Get the description for standard WordPress theme files and other various standard
68
 * WordPress files
69
 *
70
 * @since 1.5.0
71
 *
72
 * @global array $wp_file_descriptions
73
 * @param string $file Filesystem path or filename
74
 * @return string Description of file from $wp_file_descriptions or basename of $file if description doesn't exist.
75
 *                Appends 'Page Template' to basename of $file if the file is a page template
76
 */
77
function get_file_description( $file ) {
78
	global $wp_file_descriptions, $allowed_files;
79
80
	$dirname = pathinfo( $file, PATHINFO_DIRNAME );
81
82
	$file_path = $allowed_files[ $file ];
83
	if ( isset( $wp_file_descriptions[ basename( $file ) ] ) && '.' === $dirname ) {
84
		return $wp_file_descriptions[ basename( $file ) ];
85
	} elseif ( file_exists( $file_path ) && is_file( $file_path ) ) {
86
		$template_data = implode( '', file( $file_path ) );
87
		if ( preg_match( '|Template Name:(.*)$|mi', $template_data, $name ) ) {
88
			return sprintf( __( '%s Page Template' ), _cleanup_header_comment( $name[1] ) );
89
		}
90
	}
91
92
	return trim( basename( $file ) );
93
}
94
95
/**
96
 * Get the absolute filesystem path to the root of the WordPress installation
97
 *
98
 * @since 1.5.0
99
 *
100
 * @return string Full filesystem path to the root of the WordPress installation
101
 */
102
function get_home_path() {
103
	$home    = set_url_scheme( get_option( 'home' ), 'http' );
104
	$siteurl = set_url_scheme( get_option( 'siteurl' ), 'http' );
105
	if ( ! empty( $home ) && 0 !== strcasecmp( $home, $siteurl ) ) {
106
		$wp_path_rel_to_home = str_ireplace( $home, '', $siteurl ); /* $siteurl - $home */
107
		$pos = strripos( str_replace( '\\', '/', $_SERVER['SCRIPT_FILENAME'] ), trailingslashit( $wp_path_rel_to_home ) );
108
		$home_path = substr( $_SERVER['SCRIPT_FILENAME'], 0, $pos );
109
		$home_path = trailingslashit( $home_path );
110
	} else {
111
		$home_path = ABSPATH;
112
	}
113
114
	return str_replace( '\\', '/', $home_path );
115
}
116
117
/**
118
 * Returns a listing of all files in the specified folder and all subdirectories up to 100 levels deep.
119
 * The depth of the recursiveness can be controlled by the $levels param.
120
 *
121
 * @since 2.6.0
122
 *
123
 * @param string $folder Optional. Full path to folder. Default empty.
124
 * @param int    $levels Optional. Levels of folders to follow, Default 100 (PHP Loop limit).
125
 * @return bool|array False on failure, Else array of files
126
 */
127
function list_files( $folder = '', $levels = 100 ) {
128
	if ( empty($folder) )
129
		return false;
130
131
	if ( ! $levels )
132
		return false;
133
134
	$files = array();
135
	if ( $dir = @opendir( $folder ) ) {
136
		while (($file = readdir( $dir ) ) !== false ) {
137
			if ( in_array($file, array('.', '..') ) )
138
				continue;
139
			if ( is_dir( $folder . '/' . $file ) ) {
140
				$files2 = list_files( $folder . '/' . $file, $levels - 1);
141
				if ( $files2 )
142
					$files = array_merge($files, $files2 );
143
				else
144
					$files[] = $folder . '/' . $file . '/';
145
			} else {
146
				$files[] = $folder . '/' . $file;
147
			}
148
		}
149
	}
150
	@closedir( $dir );
151
	return $files;
152
}
153
154
/**
155
 * Returns a filename of a Temporary unique file.
156
 * Please note that the calling function must unlink() this itself.
157
 *
158
 * The filename is based off the passed parameter or defaults to the current unix timestamp,
159
 * while the directory can either be passed as well, or by leaving it blank, default to a writable temporary directory.
160
 *
161
 * @since 2.6.0
162
 *
163
 * @param string $filename Optional. Filename to base the Unique file off. Default empty.
164
 * @param string $dir      Optional. Directory to store the file in. Default empty.
165
 * @return string a writable filename
166
 */
167
function wp_tempnam( $filename = '', $dir = '' ) {
168
	if ( empty( $dir ) ) {
169
		$dir = get_temp_dir();
170
	}
171
172
	if ( empty( $filename ) || '.' == $filename || '/' == $filename || '\\' == $filename ) {
173
		$filename = time();
174
	}
175
176
	// Use the basename of the given file without the extension as the name for the temporary directory
177
	$temp_filename = basename( $filename );
178
	$temp_filename = preg_replace( '|\.[^.]*$|', '', $temp_filename );
179
180
	// If the folder is falsey, use its parent directory name instead.
181
	if ( ! $temp_filename ) {
182
		return wp_tempnam( dirname( $filename ), $dir );
183
	}
184
185
	// Suffix some random data to avoid filename conflicts
186
	$temp_filename .= '-' . wp_generate_password( 6, false );
187
	$temp_filename .= '.tmp';
188
	$temp_filename = $dir . wp_unique_filename( $dir, $temp_filename );
189
190
	$fp = @fopen( $temp_filename, 'x' );
191
	if ( ! $fp && is_writable( $dir ) && file_exists( $temp_filename ) ) {
192
		return wp_tempnam( $filename, $dir );
193
	}
194
	if ( $fp ) {
195
		fclose( $fp );
196
	}
197
198
	return $temp_filename;
199
}
200
201
/**
202
 * Make sure that the file that was requested to edit, is allowed to be edited
203
 *
204
 * Function will die if you are not allowed to edit the file
205
 *
206
 * @since 1.5.0
207
 *
208
 * @param string $file file the users is attempting to edit
209
 * @param array $allowed_files Array of allowed files to edit, $file must match an entry exactly
210
 * @return string|null
211
 */
212
function validate_file_to_edit( $file, $allowed_files = '' ) {
213
	$code = validate_file( $file, $allowed_files );
214
215
	if (!$code )
216
		return $file;
217
218
	switch ( $code ) {
219
		case 1 :
220
			wp_die( __( 'Sorry, that file cannot be edited.' ) );
221
222
		// case 2 :
223
		// wp_die( __('Sorry, can&#8217;t call files with their real path.' ));
224
225
		case 3 :
226
			wp_die( __( 'Sorry, that file cannot be edited.' ) );
227
	}
228
}
229
230
/**
231
 * Handle PHP uploads in WordPress, sanitizing file names, checking extensions for mime type,
232
 * and moving the file to the appropriate directory within the uploads directory.
233
 *
234
 * @access private
235
 * @since 4.0.0
236
 *
237
 * @see wp_handle_upload_error
238
 *
239
 * @param array       $file      Reference to a single element of $_FILES. Call the function once for each uploaded file.
240
 * @param array|false $overrides An associative array of names => values to override default variables. Default false.
241
 * @param string      $time      Time formatted in 'yyyy/mm'.
242
 * @param string      $action    Expected value for $_POST['action'].
243
 * @return array On success, returns an associative array of file attributes. On failure, returns
244
 *               $overrides['upload_error_handler'](&$file, $message ) or array( 'error'=>$message ).
245
 */
246
function _wp_handle_upload( &$file, $overrides, $time, $action ) {
247
	// The default error handler.
248
	if ( ! function_exists( 'wp_handle_upload_error' ) ) {
249
		function wp_handle_upload_error( &$file, $message ) {
250
			return array( 'error' => $message );
251
		}
252
	}
253
254
	/**
255
	 * Filters the data for a file before it is uploaded to WordPress.
256
	 *
257
	 * The dynamic portion of the hook name, `$action`, refers to the post action.
258
	 *
259
	 * @since 2.9.0 as 'wp_handle_upload_prefilter'.
260
	 * @since 4.0.0 Converted to a dynamic hook with `$action`.
261
	 *
262
	 * @param array $file An array of data for a single file.
263
	 */
264
	$file = apply_filters( "{$action}_prefilter", $file );
265
266
	// You may define your own function and pass the name in $overrides['upload_error_handler']
267
	$upload_error_handler = 'wp_handle_upload_error';
268
	if ( isset( $overrides['upload_error_handler'] ) ) {
269
		$upload_error_handler = $overrides['upload_error_handler'];
270
	}
271
272
	// You may have had one or more 'wp_handle_upload_prefilter' functions error out the file. Handle that gracefully.
273 View Code Duplication
	if ( isset( $file['error'] ) && ! is_numeric( $file['error'] ) && $file['error'] ) {
274
		return call_user_func_array( $upload_error_handler, array( &$file, $file['error'] ) );
275
	}
276
277
	// Install user overrides. Did we mention that this voids your warranty?
278
279
	// You may define your own function and pass the name in $overrides['unique_filename_callback']
280
	$unique_filename_callback = null;
281
	if ( isset( $overrides['unique_filename_callback'] ) ) {
282
		$unique_filename_callback = $overrides['unique_filename_callback'];
283
	}
284
285
	/*
286
	 * This may not have orignially been intended to be overrideable,
287
	 * but historically has been.
288
	 */
289
	if ( isset( $overrides['upload_error_strings'] ) ) {
290
		$upload_error_strings = $overrides['upload_error_strings'];
291
	} else {
292
		// Courtesy of php.net, the strings that describe the error indicated in $_FILES[{form field}]['error'].
293
		$upload_error_strings = array(
294
			false,
295
			__( 'The uploaded file exceeds the upload_max_filesize directive in php.ini.' ),
296
			__( 'The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form.' ),
297
			__( 'The uploaded file was only partially uploaded.' ),
298
			__( 'No file was uploaded.' ),
299
			'',
300
			__( 'Missing a temporary folder.' ),
301
			__( 'Failed to write file to disk.' ),
302
			__( 'File upload stopped by extension.' )
303
		);
304
	}
305
306
	// All tests are on by default. Most can be turned off by $overrides[{test_name}] = false;
307
	$test_form = isset( $overrides['test_form'] ) ? $overrides['test_form'] : true;
308
	$test_size = isset( $overrides['test_size'] ) ? $overrides['test_size'] : true;
309
310
	// If you override this, you must provide $ext and $type!!
311
	$test_type = isset( $overrides['test_type'] ) ? $overrides['test_type'] : true;
312
	$mimes = isset( $overrides['mimes'] ) ? $overrides['mimes'] : false;
313
314
	// A correct form post will pass this test.
315 View Code Duplication
	if ( $test_form && ( ! isset( $_POST['action'] ) || ( $_POST['action'] != $action ) ) ) {
316
		return call_user_func_array( $upload_error_handler, array( &$file, __( 'Invalid form submission.' ) ) );
317
	}
318
	// A successful upload will pass this test. It makes no sense to override this one.
319 View Code Duplication
	if ( isset( $file['error'] ) && $file['error'] > 0 ) {
320
		return call_user_func_array( $upload_error_handler, array( &$file, $upload_error_strings[ $file['error'] ] ) );
321
	}
322
323
	$test_file_size = 'wp_handle_upload' === $action ? $file['size'] : filesize( $file['tmp_name'] );
324
	// A non-empty file will pass this test.
325
	if ( $test_size && ! ( $test_file_size > 0 ) ) {
326
		if ( is_multisite() ) {
327
			$error_msg = __( 'File is empty. Please upload something more substantial.' );
328
		} else {
329
			$error_msg = __( 'File is empty. Please upload something more substantial. This error could also be caused by uploads being disabled in your php.ini or by post_max_size being defined as smaller than upload_max_filesize in php.ini.' );
330
		}
331
		return call_user_func_array( $upload_error_handler, array( &$file, $error_msg ) );
332
	}
333
334
	// A properly uploaded file will pass this test. There should be no reason to override this one.
335
	$test_uploaded_file = 'wp_handle_upload' === $action ? @ is_uploaded_file( $file['tmp_name'] ) : @ is_file( $file['tmp_name'] );
336
	if ( ! $test_uploaded_file ) {
337
		return call_user_func_array( $upload_error_handler, array( &$file, __( 'Specified file failed upload test.' ) ) );
338
	}
339
340
	// A correct MIME type will pass this test. Override $mimes or use the upload_mimes filter.
341
	if ( $test_type ) {
342
		$wp_filetype = wp_check_filetype_and_ext( $file['tmp_name'], $file['name'], $mimes );
343
		$ext = empty( $wp_filetype['ext'] ) ? '' : $wp_filetype['ext'];
344
		$type = empty( $wp_filetype['type'] ) ? '' : $wp_filetype['type'];
345
		$proper_filename = empty( $wp_filetype['proper_filename'] ) ? '' : $wp_filetype['proper_filename'];
346
347
		// Check to see if wp_check_filetype_and_ext() determined the filename was incorrect
348
		if ( $proper_filename ) {
349
			$file['name'] = $proper_filename;
350
		}
351
		if ( ( ! $type || !$ext ) && ! current_user_can( 'unfiltered_upload' ) ) {
352
			return call_user_func_array( $upload_error_handler, array( &$file, __( 'Sorry, this file type is not permitted for security reasons.' ) ) );
353
		}
354
		if ( ! $type ) {
355
			$type = $file['type'];
356
		}
357
	} else {
358
		$type = '';
359
	}
360
361
	/*
362
	 * A writable uploads dir will pass this test. Again, there's no point
363
	 * overriding this one.
364
	 */
365
	if ( ! ( ( $uploads = wp_upload_dir( $time ) ) && false === $uploads['error'] ) ) {
366
		return call_user_func_array( $upload_error_handler, array( &$file, $uploads['error'] ) );
367
	}
368
369
	$filename = wp_unique_filename( $uploads['path'], $file['name'], $unique_filename_callback );
370
371
	// Move the file to the uploads dir.
372
	$new_file = $uploads['path'] . "/$filename";
373
	if ( 'wp_handle_upload' === $action ) {
374
		$move_new_file = @ move_uploaded_file( $file['tmp_name'], $new_file );
375
	} else {
376
		// use copy and unlink because rename breaks streams.
377
		$move_new_file = @ copy( $file['tmp_name'], $new_file );
378
		unlink( $file['tmp_name'] );
379
	}
380
381
	if ( false === $move_new_file ) {
382
		if ( 0 === strpos( $uploads['basedir'], ABSPATH ) ) {
383
			$error_path = str_replace( ABSPATH, '', $uploads['basedir'] ) . $uploads['subdir'];
384
		} else {
385
			$error_path = basename( $uploads['basedir'] ) . $uploads['subdir'];
386
		}
387
		return $upload_error_handler( $file, sprintf( __('The uploaded file could not be moved to %s.' ), $error_path ) );
388
	}
389
390
	// Set correct file permissions.
391
	$stat = stat( dirname( $new_file ));
392
	$perms = $stat['mode'] & 0000666;
393
	@ chmod( $new_file, $perms );
394
395
	// Compute the URL.
396
	$url = $uploads['url'] . "/$filename";
397
398
	if ( is_multisite() ) {
399
		delete_transient( 'dirsize_cache' );
400
	}
401
402
	/**
403
	 * Filters the data array for the uploaded file.
404
	 *
405
	 * @since 2.1.0
406
	 *
407
	 * @param array  $upload {
408
	 *     Array of upload data.
409
	 *
410
	 *     @type string $file Filename of the newly-uploaded file.
411
	 *     @type string $url  URL of the uploaded file.
412
	 *     @type string $type File type.
413
	 * }
414
	 * @param string $context The type of upload action. Values include 'upload' or 'sideload'.
415
	 */
416
	return apply_filters( 'wp_handle_upload', array(
417
		'file' => $new_file,
418
		'url'  => $url,
419
		'type' => $type
420
	), 'wp_handle_sideload' === $action ? 'sideload' : 'upload' );
421
}
422
423
/**
424
 * Wrapper for _wp_handle_upload().
425
 *
426
 * Passes the {@see 'wp_handle_upload'} action.
427
 *
428
 * @since 2.0.0
429
 *
430
 * @see _wp_handle_upload()
431
 *
432
 * @param array      $file      Reference to a single element of `$_FILES`. Call the function once for
433
 *                              each uploaded file.
434
 * @param array|bool $overrides Optional. An associative array of names=>values to override default
435
 *                              variables. Default false.
436
 * @param string     $time      Optional. Time formatted in 'yyyy/mm'. Default null.
437
 * @return array On success, returns an associative array of file attributes. On failure, returns
438
 *               $overrides['upload_error_handler'](&$file, $message ) or array( 'error'=>$message ).
439
 */
440 View Code Duplication
function wp_handle_upload( &$file, $overrides = false, $time = null ) {
0 ignored issues
show
Duplication introduced by
This function 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...
441
	/*
442
	 *  $_POST['action'] must be set and its value must equal $overrides['action']
443
	 *  or this:
444
	 */
445
	$action = 'wp_handle_upload';
446
	if ( isset( $overrides['action'] ) ) {
447
		$action = $overrides['action'];
448
	}
449
450
	return _wp_handle_upload( $file, $overrides, $time, $action );
0 ignored issues
show
Bug introduced by
It seems like $overrides defined by parameter $overrides on line 440 can also be of type boolean; however, _wp_handle_upload() does only seem to accept array|false, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
451
}
452
453
/**
454
 * Wrapper for _wp_handle_upload().
455
 *
456
 * Passes the {@see 'wp_handle_sideload'} action.
457
 *
458
 * @since 2.6.0
459
 *
460
 * @see _wp_handle_upload()
461
 *
462
 * @param array      $file      An array similar to that of a PHP `$_FILES` POST array
463
 * @param array|bool $overrides Optional. An associative array of names=>values to override default
464
 *                              variables. Default false.
465
 * @param string     $time      Optional. Time formatted in 'yyyy/mm'. Default null.
466
 * @return array On success, returns an associative array of file attributes. On failure, returns
467
 *               $overrides['upload_error_handler'](&$file, $message ) or array( 'error'=>$message ).
468
 */
469 View Code Duplication
function wp_handle_sideload( &$file, $overrides = false, $time = null ) {
0 ignored issues
show
Duplication introduced by
This function 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...
470
	/*
471
	 *  $_POST['action'] must be set and its value must equal $overrides['action']
472
	 *  or this:
473
	 */
474
	$action = 'wp_handle_sideload';
475
	if ( isset( $overrides['action'] ) ) {
476
		$action = $overrides['action'];
477
	}
478
	return _wp_handle_upload( $file, $overrides, $time, $action );
0 ignored issues
show
Bug introduced by
It seems like $overrides defined by parameter $overrides on line 469 can also be of type boolean; however, _wp_handle_upload() does only seem to accept array|false, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
479
}
480
481
482
/**
483
 * Downloads a URL to a local temporary file using the WordPress HTTP Class.
484
 * Please note, That the calling function must unlink() the file.
485
 *
486
 * @since 2.5.0
487
 *
488
 * @param string $url the URL of the file to download
489
 * @param int $timeout The timeout for the request to download the file default 300 seconds
490
 * @return mixed WP_Error on failure, string Filename on success.
491
 */
492
function download_url( $url, $timeout = 300 ) {
493
	//WARNING: The file is not automatically deleted, The script must unlink() the file.
494
	if ( ! $url )
495
		return new WP_Error('http_no_url', __('Invalid URL Provided.'));
496
497
	$url_filename = basename( parse_url( $url, PHP_URL_PATH ) );
498
499
	$tmpfname = wp_tempnam( $url_filename );
500
	if ( ! $tmpfname )
501
		return new WP_Error('http_no_file', __('Could not create Temporary file.'));
502
503
	$response = wp_safe_remote_get( $url, array( 'timeout' => $timeout, 'stream' => true, 'filename' => $tmpfname ) );
504
505
	if ( is_wp_error( $response ) ) {
506
		unlink( $tmpfname );
507
		return $response;
508
	}
509
510
	if ( 200 != wp_remote_retrieve_response_code( $response ) ){
511
		unlink( $tmpfname );
512
		return new WP_Error( 'http_404', trim( wp_remote_retrieve_response_message( $response ) ) );
513
	}
514
515
	$content_md5 = wp_remote_retrieve_header( $response, 'content-md5' );
516
	if ( $content_md5 ) {
517
		$md5_check = verify_file_md5( $tmpfname, $content_md5 );
518
		if ( is_wp_error( $md5_check ) ) {
519
			unlink( $tmpfname );
520
			return $md5_check;
521
		}
522
	}
523
524
	return $tmpfname;
525
}
526
527
/**
528
 * Calculates and compares the MD5 of a file to its expected value.
529
 *
530
 * @since 3.7.0
531
 *
532
 * @param string $filename The filename to check the MD5 of.
533
 * @param string $expected_md5 The expected MD5 of the file, either a base64 encoded raw md5, or a hex-encoded md5
534
 * @return bool|object WP_Error on failure, true on success, false when the MD5 format is unknown/unexpected
535
 */
536
function verify_file_md5( $filename, $expected_md5 ) {
537
	if ( 32 == strlen( $expected_md5 ) )
538
		$expected_raw_md5 = pack( 'H*', $expected_md5 );
539
	elseif ( 24 == strlen( $expected_md5 ) )
540
		$expected_raw_md5 = base64_decode( $expected_md5 );
541
	else
542
		return false; // unknown format
543
544
	$file_md5 = md5_file( $filename, true );
545
546
	if ( $file_md5 === $expected_raw_md5 )
547
		return true;
548
549
	return new WP_Error( 'md5_mismatch', sprintf( __( 'The checksum of the file (%1$s) does not match the expected checksum value (%2$s).' ), bin2hex( $file_md5 ), bin2hex( $expected_raw_md5 ) ) );
550
}
551
552
/**
553
 * Unzips a specified ZIP file to a location on the Filesystem via the WordPress Filesystem Abstraction.
554
 * Assumes that WP_Filesystem() has already been called and set up. Does not extract a root-level __MACOSX directory, if present.
555
 *
556
 * Attempts to increase the PHP Memory limit to 256M before uncompressing,
557
 * However, The most memory required shouldn't be much larger than the Archive itself.
558
 *
559
 * @since 2.5.0
560
 *
561
 * @global WP_Filesystem_Base $wp_filesystem Subclass
562
 *
563
 * @param string $file Full path and filename of zip archive
564
 * @param string $to Full path on the filesystem to extract archive to
565
 * @return mixed WP_Error on failure, True on success
566
 */
567
function unzip_file($file, $to) {
568
	global $wp_filesystem;
569
570
	if ( ! $wp_filesystem || !is_object($wp_filesystem) )
571
		return new WP_Error('fs_unavailable', __('Could not access filesystem.'));
572
573
	// Unzip can use a lot of memory, but not this much hopefully.
574
	wp_raise_memory_limit( 'admin' );
575
576
	$needed_dirs = array();
577
	$to = trailingslashit($to);
578
579
	// Determine any parent dir's needed (of the upgrade directory)
580
	if ( ! $wp_filesystem->is_dir($to) ) { //Only do parents if no children exist
581
		$path = preg_split('![/\\\]!', untrailingslashit($to));
582
		for ( $i = count($path); $i >= 0; $i-- ) {
583
			if ( empty($path[$i]) )
584
				continue;
585
586
			$dir = implode('/', array_slice($path, 0, $i+1) );
587
			if ( preg_match('!^[a-z]:$!i', $dir) ) // Skip it if it looks like a Windows Drive letter.
588
				continue;
589
590
			if ( ! $wp_filesystem->is_dir($dir) )
591
				$needed_dirs[] = $dir;
592
			else
593
				break; // A folder exists, therefor, we dont need the check the levels below this
594
		}
595
	}
596
597
	/**
598
	 * Filters whether to use ZipArchive to unzip archives.
599
	 *
600
	 * @since 3.0.0
601
	 *
602
	 * @param bool $ziparchive Whether to use ZipArchive. Default true.
603
	 */
604
	if ( class_exists( 'ZipArchive', false ) && apply_filters( 'unzip_file_use_ziparchive', true ) ) {
605
		$result = _unzip_file_ziparchive($file, $to, $needed_dirs);
606
		if ( true === $result ) {
607
			return $result;
608
		} elseif ( is_wp_error($result) ) {
609
			if ( 'incompatible_archive' != $result->get_error_code() )
610
				return $result;
611
		}
612
	}
613
	// Fall through to PclZip if ZipArchive is not available, or encountered an error opening the file.
614
	return _unzip_file_pclzip($file, $to, $needed_dirs);
615
}
616
617
/**
618
 * This function should not be called directly, use unzip_file instead. Attempts to unzip an archive using the ZipArchive class.
619
 * Assumes that WP_Filesystem() has already been called and set up.
620
 *
621
 * @since 3.0.0
622
 * @see unzip_file
623
 * @access private
624
 *
625
 * @global WP_Filesystem_Base $wp_filesystem Subclass
626
 *
627
 * @param string $file Full path and filename of zip archive
628
 * @param string $to Full path on the filesystem to extract archive to
629
 * @param array $needed_dirs A partial list of required folders needed to be created.
630
 * @return mixed WP_Error on failure, True on success
631
 */
632
function _unzip_file_ziparchive($file, $to, $needed_dirs = array() ) {
633
	global $wp_filesystem;
634
635
	$z = new ZipArchive();
636
637
	$zopen = $z->open( $file, ZIPARCHIVE::CHECKCONS );
638
	if ( true !== $zopen )
639
		return new WP_Error( 'incompatible_archive', __( 'Incompatible Archive.' ), array( 'ziparchive_error' => $zopen ) );
640
641
	$uncompressed_size = 0;
642
643
	for ( $i = 0; $i < $z->numFiles; $i++ ) {
644
		if ( ! $info = $z->statIndex($i) )
645
			return new WP_Error( 'stat_failed_ziparchive', __( 'Could not retrieve file from archive.' ) );
646
647
		if ( '__MACOSX/' === substr($info['name'], 0, 9) ) // Skip the OS X-created __MACOSX directory
648
			continue;
649
650
		$uncompressed_size += $info['size'];
651
652
		if ( '/' === substr( $info['name'], -1 ) ) {
653
			// Directory.
654
			$needed_dirs[] = $to . untrailingslashit( $info['name'] );
655
		} elseif ( '.' !== $dirname = dirname( $info['name'] ) ) {
656
			// Path to a file.
657
			$needed_dirs[] = $to . untrailingslashit( $dirname );
658
		}
659
	}
660
661
	/*
662
	 * disk_free_space() could return false. Assume that any falsey value is an error.
663
	 * A disk that has zero free bytes has bigger problems.
664
	 * Require we have enough space to unzip the file and copy its contents, with a 10% buffer.
665
	 */
666 View Code Duplication
	if ( defined( 'DOING_CRON' ) && DOING_CRON ) {
667
		$available_space = @disk_free_space( WP_CONTENT_DIR );
668
		if ( $available_space && ( $uncompressed_size * 2.1 ) > $available_space )
669
			return new WP_Error( 'disk_full_unzip_file', __( 'Could not copy files. You may have run out of disk space.' ), compact( 'uncompressed_size', 'available_space' ) );
670
	}
671
672
	$needed_dirs = array_unique($needed_dirs);
673 View Code Duplication
	foreach ( $needed_dirs as $dir ) {
674
		// Check the parent folders of the folders all exist within the creation array.
675
		if ( untrailingslashit($to) == $dir ) // Skip over the working directory, We know this exists (or will exist)
676
			continue;
677
		if ( strpos($dir, $to) === false ) // If the directory is not within the working directory, Skip it
678
			continue;
679
680
		$parent_folder = dirname($dir);
681
		while ( !empty($parent_folder) && untrailingslashit($to) != $parent_folder && !in_array($parent_folder, $needed_dirs) ) {
682
			$needed_dirs[] = $parent_folder;
683
			$parent_folder = dirname($parent_folder);
684
		}
685
	}
686
	asort($needed_dirs);
687
688
	// Create those directories if need be:
689 View Code Duplication
	foreach ( $needed_dirs as $_dir ) {
690
		// Only check to see if the Dir exists upon creation failure. Less I/O this way.
691
		if ( ! $wp_filesystem->mkdir( $_dir, FS_CHMOD_DIR ) && ! $wp_filesystem->is_dir( $_dir ) ) {
692
			return new WP_Error( 'mkdir_failed_ziparchive', __( 'Could not create directory.' ), substr( $_dir, strlen( $to ) ) );
693
		}
694
	}
695
	unset($needed_dirs);
696
697
	for ( $i = 0; $i < $z->numFiles; $i++ ) {
698
		if ( ! $info = $z->statIndex($i) )
699
			return new WP_Error( 'stat_failed_ziparchive', __( 'Could not retrieve file from archive.' ) );
700
701
		if ( '/' == substr($info['name'], -1) ) // directory
702
			continue;
703
704
		if ( '__MACOSX/' === substr($info['name'], 0, 9) ) // Don't extract the OS X-created __MACOSX directory files
705
			continue;
706
707
		$contents = $z->getFromIndex($i);
708
		if ( false === $contents )
709
			return new WP_Error( 'extract_failed_ziparchive', __( 'Could not extract file from archive.' ), $info['name'] );
710
711 View Code Duplication
		if ( ! $wp_filesystem->put_contents( $to . $info['name'], $contents, FS_CHMOD_FILE) )
712
			return new WP_Error( 'copy_failed_ziparchive', __( 'Could not copy file.' ), $info['name'] );
713
	}
714
715
	$z->close();
716
717
	return true;
718
}
719
720
/**
721
 * This function should not be called directly, use unzip_file instead. Attempts to unzip an archive using the PclZip library.
722
 * Assumes that WP_Filesystem() has already been called and set up.
723
 *
724
 * @since 3.0.0
725
 * @see unzip_file
726
 * @access private
727
 *
728
 * @global WP_Filesystem_Base $wp_filesystem Subclass
729
 *
730
 * @param string $file Full path and filename of zip archive
731
 * @param string $to Full path on the filesystem to extract archive to
732
 * @param array $needed_dirs A partial list of required folders needed to be created.
733
 * @return mixed WP_Error on failure, True on success
734
 */
735
function _unzip_file_pclzip($file, $to, $needed_dirs = array()) {
736
	global $wp_filesystem;
737
738
	mbstring_binary_safe_encoding();
739
740
	require_once(ABSPATH . 'wp-admin/includes/class-pclzip.php');
741
742
	$archive = new PclZip($file);
743
744
	$archive_files = $archive->extract(PCLZIP_OPT_EXTRACT_AS_STRING);
745
746
	reset_mbstring_encoding();
747
748
	// Is the archive valid?
749
	if ( !is_array($archive_files) )
750
		return new WP_Error('incompatible_archive', __('Incompatible Archive.'), $archive->errorInfo(true));
751
752
	if ( 0 == count($archive_files) )
753
		return new WP_Error( 'empty_archive_pclzip', __( 'Empty archive.' ) );
754
755
	$uncompressed_size = 0;
756
757
	// Determine any children directories needed (From within the archive)
758
	foreach ( $archive_files as $file ) {
759
		if ( '__MACOSX/' === substr($file['filename'], 0, 9) ) // Skip the OS X-created __MACOSX directory
760
			continue;
761
762
		$uncompressed_size += $file['size'];
763
764
		$needed_dirs[] = $to . untrailingslashit( $file['folder'] ? $file['filename'] : dirname($file['filename']) );
765
	}
766
767
	/*
768
	 * disk_free_space() could return false. Assume that any falsey value is an error.
769
	 * A disk that has zero free bytes has bigger problems.
770
	 * Require we have enough space to unzip the file and copy its contents, with a 10% buffer.
771
	 */
772 View Code Duplication
	if ( defined( 'DOING_CRON' ) && DOING_CRON ) {
773
		$available_space = @disk_free_space( WP_CONTENT_DIR );
774
		if ( $available_space && ( $uncompressed_size * 2.1 ) > $available_space )
775
			return new WP_Error( 'disk_full_unzip_file', __( 'Could not copy files. You may have run out of disk space.' ), compact( 'uncompressed_size', 'available_space' ) );
776
	}
777
778
	$needed_dirs = array_unique($needed_dirs);
779 View Code Duplication
	foreach ( $needed_dirs as $dir ) {
780
		// Check the parent folders of the folders all exist within the creation array.
781
		if ( untrailingslashit($to) == $dir ) // Skip over the working directory, We know this exists (or will exist)
782
			continue;
783
		if ( strpos($dir, $to) === false ) // If the directory is not within the working directory, Skip it
784
			continue;
785
786
		$parent_folder = dirname($dir);
787
		while ( !empty($parent_folder) && untrailingslashit($to) != $parent_folder && !in_array($parent_folder, $needed_dirs) ) {
788
			$needed_dirs[] = $parent_folder;
789
			$parent_folder = dirname($parent_folder);
790
		}
791
	}
792
	asort($needed_dirs);
793
794
	// Create those directories if need be:
795 View Code Duplication
	foreach ( $needed_dirs as $_dir ) {
796
		// Only check to see if the dir exists upon creation failure. Less I/O this way.
797
		if ( ! $wp_filesystem->mkdir( $_dir, FS_CHMOD_DIR ) && ! $wp_filesystem->is_dir( $_dir ) )
798
			return new WP_Error( 'mkdir_failed_pclzip', __( 'Could not create directory.' ), substr( $_dir, strlen( $to ) ) );
799
	}
800
	unset($needed_dirs);
801
802
	// Extract the files from the zip
803
	foreach ( $archive_files as $file ) {
804
		if ( $file['folder'] )
805
			continue;
806
807
		if ( '__MACOSX/' === substr($file['filename'], 0, 9) ) // Don't extract the OS X-created __MACOSX directory files
808
			continue;
809
810 View Code Duplication
		if ( ! $wp_filesystem->put_contents( $to . $file['filename'], $file['content'], FS_CHMOD_FILE) )
811
			return new WP_Error( 'copy_failed_pclzip', __( 'Could not copy file.' ), $file['filename'] );
812
	}
813
	return true;
814
}
815
816
/**
817
 * Copies a directory from one location to another via the WordPress Filesystem Abstraction.
818
 * Assumes that WP_Filesystem() has already been called and setup.
819
 *
820
 * @since 2.5.0
821
 *
822
 * @global WP_Filesystem_Base $wp_filesystem Subclass
823
 *
824
 * @param string $from source directory
825
 * @param string $to destination directory
826
 * @param array $skip_list a list of files/folders to skip copying
827
 * @return mixed WP_Error on failure, True on success.
828
 */
829 View Code Duplication
function copy_dir($from, $to, $skip_list = array() ) {
0 ignored issues
show
Duplication introduced by
This function 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...
830
	global $wp_filesystem;
831
832
	$dirlist = $wp_filesystem->dirlist($from);
833
834
	$from = trailingslashit($from);
835
	$to = trailingslashit($to);
836
837
	foreach ( (array) $dirlist as $filename => $fileinfo ) {
838
		if ( in_array( $filename, $skip_list ) )
839
			continue;
840
841
		if ( 'f' == $fileinfo['type'] ) {
842
			if ( ! $wp_filesystem->copy($from . $filename, $to . $filename, true, FS_CHMOD_FILE) ) {
843
				// If copy failed, chmod file to 0644 and try again.
844
				$wp_filesystem->chmod( $to . $filename, FS_CHMOD_FILE );
845
				if ( ! $wp_filesystem->copy($from . $filename, $to . $filename, true, FS_CHMOD_FILE) )
846
					return new WP_Error( 'copy_failed_copy_dir', __( 'Could not copy file.' ), $to . $filename );
847
			}
848
		} elseif ( 'd' == $fileinfo['type'] ) {
849
			if ( !$wp_filesystem->is_dir($to . $filename) ) {
850
				if ( !$wp_filesystem->mkdir($to . $filename, FS_CHMOD_DIR) )
851
					return new WP_Error( 'mkdir_failed_copy_dir', __( 'Could not create directory.' ), $to . $filename );
852
			}
853
854
			// generate the $sub_skip_list for the subdirectory as a sub-set of the existing $skip_list
855
			$sub_skip_list = array();
856
			foreach ( $skip_list as $skip_item ) {
857
				if ( 0 === strpos( $skip_item, $filename . '/' ) )
858
					$sub_skip_list[] = preg_replace( '!^' . preg_quote( $filename, '!' ) . '/!i', '', $skip_item );
859
			}
860
861
			$result = copy_dir($from . $filename, $to . $filename, $sub_skip_list);
862
			if ( is_wp_error($result) )
863
				return $result;
864
		}
865
	}
866
	return true;
867
}
868
869
/**
870
 * Initialises and connects the WordPress Filesystem Abstraction classes.
871
 * This function will include the chosen transport and attempt connecting.
872
 *
873
 * Plugins may add extra transports, And force WordPress to use them by returning
874
 * the filename via the {@see 'filesystem_method_file'} filter.
875
 *
876
 * @since 2.5.0
877
 *
878
 * @global WP_Filesystem_Base $wp_filesystem Subclass
879
 *
880
 * @param array|false  $args                         Optional. Connection args, These are passed directly to
881
 *                                                   the `WP_Filesystem_*()` classes. Default false.
882
 * @param string|false $context                      Optional. Context for get_filesystem_method(). Default false.
883
 * @param bool         $allow_relaxed_file_ownership Optional. Whether to allow Group/World writable. Default false.
884
 * @return null|bool false on failure, true on success.
885
 */
886
function WP_Filesystem( $args = false, $context = false, $allow_relaxed_file_ownership = false ) {
887
	global $wp_filesystem;
888
889
	require_once(ABSPATH . 'wp-admin/includes/class-wp-filesystem-base.php');
890
891
	$method = get_filesystem_method( $args, $context, $allow_relaxed_file_ownership );
0 ignored issues
show
Bug introduced by
It seems like $args defined by parameter $args on line 886 can also be of type false; however, get_filesystem_method() does only seem to accept array, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
Bug introduced by
It seems like $context defined by parameter $context on line 886 can also be of type false; however, get_filesystem_method() does only seem to accept string, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
892
893
	if ( ! $method )
894
		return false;
895
896
	if ( ! class_exists( "WP_Filesystem_$method" ) ) {
897
898
		/**
899
		 * Filters the path for a specific filesystem method class file.
900
		 *
901
		 * @since 2.6.0
902
		 *
903
		 * @see get_filesystem_method()
904
		 *
905
		 * @param string $path   Path to the specific filesystem method class file.
906
		 * @param string $method The filesystem method to use.
907
		 */
908
		$abstraction_file = apply_filters( 'filesystem_method_file', ABSPATH . 'wp-admin/includes/class-wp-filesystem-' . $method . '.php', $method );
909
910
		if ( ! file_exists($abstraction_file) )
911
			return;
912
913
		require_once($abstraction_file);
914
	}
915
	$method = "WP_Filesystem_$method";
916
917
	$wp_filesystem = new $method($args);
918
919
	//Define the timeouts for the connections. Only available after the construct is called to allow for per-transport overriding of the default.
920
	if ( ! defined('FS_CONNECT_TIMEOUT') )
921
		define('FS_CONNECT_TIMEOUT', 30);
922
	if ( ! defined('FS_TIMEOUT') )
923
		define('FS_TIMEOUT', 30);
924
925
	if ( is_wp_error($wp_filesystem->errors) && $wp_filesystem->errors->get_error_code() )
926
		return false;
927
928
	if ( !$wp_filesystem->connect() )
929
		return false; //There was an error connecting to the server.
930
931
	// Set the permission constants if not already set.
932
	if ( ! defined('FS_CHMOD_DIR') )
933
		define('FS_CHMOD_DIR', ( fileperms( ABSPATH ) & 0777 | 0755 ) );
934
	if ( ! defined('FS_CHMOD_FILE') )
935
		define('FS_CHMOD_FILE', ( fileperms( ABSPATH . 'index.php' ) & 0777 | 0644 ) );
936
937
	return true;
938
}
939
940
/**
941
 * Determines which method to use for reading, writing, modifying, or deleting
942
 * files on the filesystem.
943
 *
944
 * The priority of the transports are: Direct, SSH2, FTP PHP Extension, FTP Sockets
945
 * (Via Sockets class, or `fsockopen()`). Valid values for these are: 'direct', 'ssh2',
946
 * 'ftpext' or 'ftpsockets'.
947
 *
948
 * The return value can be overridden by defining the `FS_METHOD` constant in `wp-config.php`,
949
 * or filtering via {@see 'filesystem_method'}.
950
 *
951
 * @link https://codex.wordpress.org/Editing_wp-config.php#WordPress_Upgrade_Constants
952
 *
953
 * Plugins may define a custom transport handler, See WP_Filesystem().
954
 *
955
 * @since 2.5.0
956
 *
957
 * @global callable $_wp_filesystem_direct_method
958
 *
959
 * @param array  $args                         Optional. Connection details. Default empty array.
960
 * @param string $context                      Optional. Full path to the directory that is tested
961
 *                                             for being writable. Default empty.
962
 * @param bool   $allow_relaxed_file_ownership Optional. Whether to allow Group/World writable.
963
 *                                             Default false.
964
 * @return string The transport to use, see description for valid return values.
965
 */
966
function get_filesystem_method( $args = array(), $context = '', $allow_relaxed_file_ownership = false ) {
967
	$method = defined('FS_METHOD') ? FS_METHOD : false; // Please ensure that this is either 'direct', 'ssh2', 'ftpext' or 'ftpsockets'
968
969
	if ( ! $context ) {
970
		$context = WP_CONTENT_DIR;
971
	}
972
973
	// If the directory doesn't exist (wp-content/languages) then use the parent directory as we'll create it.
974
	if ( WP_LANG_DIR == $context && ! is_dir( $context ) ) {
975
		$context = dirname( $context );
976
	}
977
978
	$context = trailingslashit( $context );
979
980
	if ( ! $method ) {
981
982
		$temp_file_name = $context . 'temp-write-test-' . time();
983
		$temp_handle = @fopen($temp_file_name, 'w');
984
		if ( $temp_handle ) {
985
986
			// Attempt to determine the file owner of the WordPress files, and that of newly created files
987
			$wp_file_owner = $temp_file_owner = false;
988
			if ( function_exists('fileowner') ) {
989
				$wp_file_owner = @fileowner( __FILE__ );
990
				$temp_file_owner = @fileowner( $temp_file_name );
991
			}
992
993
			if ( $wp_file_owner !== false && $wp_file_owner === $temp_file_owner ) {
994
				// WordPress is creating files as the same owner as the WordPress files,
995
				// this means it's safe to modify & create new files via PHP.
996
				$method = 'direct';
997
				$GLOBALS['_wp_filesystem_direct_method'] = 'file_owner';
998
			} elseif ( $allow_relaxed_file_ownership ) {
999
				// The $context directory is writable, and $allow_relaxed_file_ownership is set, this means we can modify files
1000
				// safely in this directory. This mode doesn't create new files, only alter existing ones.
1001
				$method = 'direct';
1002
				$GLOBALS['_wp_filesystem_direct_method'] = 'relaxed_ownership';
1003
			}
1004
1005
			@fclose($temp_handle);
1006
			@unlink($temp_file_name);
1007
		}
1008
 	}
1009
1010
	if ( ! $method && isset($args['connection_type']) && 'ssh' == $args['connection_type'] && extension_loaded('ssh2') && function_exists('stream_get_contents') ) $method = 'ssh2';
1011
	if ( ! $method && extension_loaded('ftp') ) $method = 'ftpext';
1012
	if ( ! $method && ( extension_loaded('sockets') || function_exists('fsockopen') ) ) $method = 'ftpsockets'; //Sockets: Socket extension; PHP Mode: FSockopen / fwrite / fread
1013
1014
	/**
1015
	 * Filters the filesystem method to use.
1016
	 *
1017
	 * @since 2.6.0
1018
	 *
1019
	 * @param string $method  Filesystem method to return.
1020
	 * @param array  $args    An array of connection details for the method.
1021
	 * @param string $context Full path to the directory that is tested for being writable.
1022
	 * @param bool   $allow_relaxed_file_ownership Whether to allow Group/World writable.
1023
	 */
1024
	return apply_filters( 'filesystem_method', $method, $args, $context, $allow_relaxed_file_ownership );
1025
}
1026
1027
/**
1028
 * Displays a form to the user to request for their FTP/SSH details in order
1029
 * to connect to the filesystem.
1030
 *
1031
 * All chosen/entered details are saved, excluding the password.
1032
 *
1033
 * Hostnames may be in the form of hostname:portnumber (eg: wordpress.org:2467)
1034
 * to specify an alternate FTP/SSH port.
1035
 *
1036
 * Plugins may override this form by returning true|false via the {@see 'request_filesystem_credentials'} filter.
1037
 *
1038
 * @since 2.5.0
1039
 * @since 4.6.0 The `$context` parameter default changed from `false` to an empty string.
1040
 *
1041
 * @global string $pagenow
1042
 *
1043
 * @param string $form_post                    The URL to post the form to.
1044
 * @param string $type                         Optional. Chosen type of filesystem. Default empty.
1045
 * @param bool   $error                        Optional. Whether the current request has failed to connect.
1046
 *                                             Default false.
1047
 * @param string $context                      Optional. Full path to the directory that is tested for being
1048
 *                                             writable. Default empty.
1049
 * @param array  $extra_fields                 Optional. Extra `POST` fields to be checked for inclusion in
1050
 *                                             the post. Default null.
1051
 * @param bool   $allow_relaxed_file_ownership Optional. Whether to allow Group/World writable. Default false.
1052
 *
1053
 * @return bool False on failure, true on success.
1054
 */
1055
function request_filesystem_credentials( $form_post, $type = '', $error = false, $context = '', $extra_fields = null, $allow_relaxed_file_ownership = false ) {
1056
	global $pagenow;
1057
1058
	/**
1059
	 * Filters the filesystem credentials form output.
1060
	 *
1061
	 * Returning anything other than an empty string will effectively short-circuit
1062
	 * output of the filesystem credentials form, returning that value instead.
1063
	 *
1064
	 * @since 2.5.0
1065
	 * @since 4.6.0 The `$context` parameter default changed from `false` to an empty string.
1066
	 *
1067
	 * @param mixed  $output                       Form output to return instead. Default empty.
1068
	 * @param string $form_post                    The URL to post the form to.
1069
	 * @param string $type                         Chosen type of filesystem.
1070
	 * @param bool   $error                        Whether the current request has failed to connect.
1071
	 *                                             Default false.
1072
	 * @param string $context                      Full path to the directory that is tested for
1073
	 *                                             being writable.
1074
	 * @param bool   $allow_relaxed_file_ownership Whether to allow Group/World writable.
1075
	 *                                             Default false.
1076
	 * @param array  $extra_fields                 Extra POST fields.
1077
	 */
1078
	$req_cred = apply_filters( 'request_filesystem_credentials', '', $form_post, $type, $error, $context, $extra_fields, $allow_relaxed_file_ownership );
1079
	if ( '' !== $req_cred )
1080
		return $req_cred;
1081
1082
	if ( empty($type) ) {
1083
		$type = get_filesystem_method( array(), $context, $allow_relaxed_file_ownership );
1084
	}
1085
1086
	if ( 'direct' == $type )
1087
		return true;
1088
1089
	if ( is_null( $extra_fields ) )
1090
		$extra_fields = array( 'version', 'locale' );
1091
1092
	$credentials = get_option('ftp_credentials', array( 'hostname' => '', 'username' => ''));
1093
1094
	// If defined, set it to that, Else, If POST'd, set it to that, If not, Set it to whatever it previously was(saved details in option)
1095
	$credentials['hostname'] = defined('FTP_HOST') ? FTP_HOST : (!empty($_POST['hostname']) ? wp_unslash( $_POST['hostname'] ) : $credentials['hostname']);
1096
	$credentials['username'] = defined('FTP_USER') ? FTP_USER : (!empty($_POST['username']) ? wp_unslash( $_POST['username'] ) : $credentials['username']);
1097
	$credentials['password'] = defined('FTP_PASS') ? FTP_PASS : (!empty($_POST['password']) ? wp_unslash( $_POST['password'] ) : '');
1098
1099
	// Check to see if we are setting the public/private keys for ssh
1100
	$credentials['public_key'] = defined('FTP_PUBKEY') ? FTP_PUBKEY : (!empty($_POST['public_key']) ? wp_unslash( $_POST['public_key'] ) : '');
1101
	$credentials['private_key'] = defined('FTP_PRIKEY') ? FTP_PRIKEY : (!empty($_POST['private_key']) ? wp_unslash( $_POST['private_key'] ) : '');
1102
1103
	// Sanitize the hostname, Some people might pass in odd-data:
1104
	$credentials['hostname'] = preg_replace('|\w+://|', '', $credentials['hostname']); //Strip any schemes off
1105
1106
	if ( strpos($credentials['hostname'], ':') ) {
1107
		list( $credentials['hostname'], $credentials['port'] ) = explode(':', $credentials['hostname'], 2);
1108
		if ( ! is_numeric($credentials['port']) )
1109
			unset($credentials['port']);
1110
	} else {
1111
		unset($credentials['port']);
1112
	}
1113
1114
	if ( ( defined( 'FTP_SSH' ) && FTP_SSH ) || ( defined( 'FS_METHOD' ) && 'ssh2' == FS_METHOD ) ) {
1115
		$credentials['connection_type'] = 'ssh';
1116
	} elseif ( ( defined( 'FTP_SSL' ) && FTP_SSL ) && 'ftpext' == $type ) { //Only the FTP Extension understands SSL
1117
		$credentials['connection_type'] = 'ftps';
1118
	} elseif ( ! empty( $_POST['connection_type'] ) ) {
1119
		$credentials['connection_type'] = wp_unslash( $_POST['connection_type'] );
1120
	} elseif ( ! isset( $credentials['connection_type'] ) ) { //All else fails (And it's not defaulted to something else saved), Default to FTP
1121
		$credentials['connection_type'] = 'ftp';
1122
	}
1123
	if ( ! $error &&
1124
			(
1125
				( !empty($credentials['password']) && !empty($credentials['username']) && !empty($credentials['hostname']) ) ||
1126
				( 'ssh' == $credentials['connection_type'] && !empty($credentials['public_key']) && !empty($credentials['private_key']) )
1127
			) ) {
1128
		$stored_credentials = $credentials;
1129
		if ( !empty($stored_credentials['port']) ) //save port as part of hostname to simplify above code.
1130
			$stored_credentials['hostname'] .= ':' . $stored_credentials['port'];
1131
1132
		unset($stored_credentials['password'], $stored_credentials['port'], $stored_credentials['private_key'], $stored_credentials['public_key']);
1133
		if ( ! wp_installing() ) {
1134
			update_option( 'ftp_credentials', $stored_credentials );
1135
		}
1136
		return $credentials;
1137
	}
1138
	$hostname = isset( $credentials['hostname'] ) ? $credentials['hostname'] : '';
1139
	$username = isset( $credentials['username'] ) ? $credentials['username'] : '';
1140
	$public_key = isset( $credentials['public_key'] ) ? $credentials['public_key'] : '';
1141
	$private_key = isset( $credentials['private_key'] ) ? $credentials['private_key'] : '';
1142
	$port = isset( $credentials['port'] ) ? $credentials['port'] : '';
1143
	$connection_type = isset( $credentials['connection_type'] ) ? $credentials['connection_type'] : '';
1144
1145
	if ( $error ) {
1146
		$error_string = __('<strong>ERROR:</strong> There was an error connecting to the server, Please verify the settings are correct.');
1147
		if ( is_wp_error($error) )
1148
			$error_string = esc_html( $error->get_error_message() );
0 ignored issues
show
Bug introduced by
The method get_error_message cannot be called on $error (of type boolean).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
1149
		echo '<div id="message" class="error"><p>' . $error_string . '</p></div>';
1150
	}
1151
1152
	$types = array();
1153
	if ( extension_loaded('ftp') || extension_loaded('sockets') || function_exists('fsockopen') )
1154
		$types[ 'ftp' ] = __('FTP');
1155
	if ( extension_loaded('ftp') ) //Only this supports FTPS
1156
		$types[ 'ftps' ] = __('FTPS (SSL)');
1157
	if ( extension_loaded('ssh2') && function_exists('stream_get_contents') )
1158
		$types[ 'ssh' ] = __('SSH2');
1159
1160
	/**
1161
	 * Filters the connection types to output to the filesystem credentials form.
1162
	 *
1163
	 * @since 2.9.0
1164
	 * @since 4.6.0 The `$context` parameter default changed from `false` to an empty string.
1165
	 *
1166
	 * @param array  $types       Types of connections.
1167
	 * @param array  $credentials Credentials to connect with.
1168
	 * @param string $type        Chosen filesystem method.
1169
	 * @param object $error       Error object.
1170
	 * @param string $context     Full path to the directory that is tested
1171
	 *                            for being writable.
1172
	 */
1173
	$types = apply_filters( 'fs_ftp_connection_types', $types, $credentials, $type, $error, $context );
1174
1175
?>
1176
<form action="<?php echo esc_url( $form_post ) ?>" method="post">
1177
<div id="request-filesystem-credentials-form" class="request-filesystem-credentials-form">
1178
<?php
1179
// Print a H1 heading in the FTP credentials modal dialog, default is a H2.
1180
$heading_tag = 'h2';
1181
if ( 'plugins.php' === $pagenow || 'plugin-install.php' === $pagenow ) {
1182
	$heading_tag = 'h1';
1183
}
1184
echo "<$heading_tag id='request-filesystem-credentials-title'>" . __( 'Connection Information' ) . "</$heading_tag>";
1185
?>
1186
<p id="request-filesystem-credentials-desc"><?php
1187
	$label_user = __('Username');
1188
	$label_pass = __('Password');
1189
	_e('To perform the requested action, WordPress needs to access your web server.');
1190
	echo ' ';
1191
	if ( ( isset( $types['ftp'] ) || isset( $types['ftps'] ) ) ) {
1192
		if ( isset( $types['ssh'] ) ) {
1193
			_e('Please enter your FTP or SSH credentials to proceed.');
1194
			$label_user = __('FTP/SSH Username');
1195
			$label_pass = __('FTP/SSH Password');
1196
		} else {
1197
			_e('Please enter your FTP credentials to proceed.');
1198
			$label_user = __('FTP Username');
1199
			$label_pass = __('FTP Password');
1200
		}
1201
		echo ' ';
1202
	}
1203
	_e('If you do not remember your credentials, you should contact your web host.');
1204
?></p>
1205
<label for="hostname">
1206
	<span class="field-title"><?php _e( 'Hostname' ) ?></span>
1207
	<input name="hostname" type="text" id="hostname" aria-describedby="request-filesystem-credentials-desc" class="code" placeholder="<?php esc_attr_e( 'example: www.wordpress.org' ) ?>" value="<?php echo esc_attr($hostname); if ( !empty($port) ) echo ":$port"; ?>"<?php disabled( defined('FTP_HOST') ); ?> />
1208
</label>
1209
<div class="ftp-username">
1210
	<label for="username">
1211
		<span class="field-title"><?php echo $label_user; ?></span>
1212
		<input name="username" type="text" id="username" value="<?php echo esc_attr($username) ?>"<?php disabled( defined('FTP_USER') ); ?> />
1213
	</label>
1214
</div>
1215
<div class="ftp-password">
1216
	<label for="password">
1217
		<span class="field-title"><?php echo $label_pass; ?></span>
1218
		<input name="password" type="password" id="password" value="<?php if ( defined('FTP_PASS') ) echo '*****'; ?>"<?php disabled( defined('FTP_PASS') ); ?> />
1219
		<em><?php if ( ! defined('FTP_PASS') ) _e( 'This password will not be stored on the server.' ); ?></em>
1220
	</label>
1221
</div>
1222
<fieldset>
1223
<legend><?php _e( 'Connection Type' ); ?></legend>
1224
<?php
1225
	$disabled = disabled( ( defined( 'FTP_SSL' ) && FTP_SSL ) || ( defined( 'FTP_SSH' ) && FTP_SSH ), true, false );
1226
	foreach ( $types as $name => $text ) : ?>
1227
	<label for="<?php echo esc_attr( $name ) ?>">
1228
		<input type="radio" name="connection_type" id="<?php echo esc_attr( $name ) ?>" value="<?php echo esc_attr( $name ) ?>"<?php checked( $name, $connection_type ); echo $disabled; ?> />
1229
		<?php echo $text; ?>
1230
	</label>
1231
<?php
1232
	endforeach;
1233
?>
1234
</fieldset>
1235
<?php
1236
if ( isset( $types['ssh'] ) ) {
1237
	$hidden_class = '';
1238
	if ( 'ssh' != $connection_type || empty( $connection_type ) ) {
1239
		$hidden_class = ' class="hidden"';
1240
	}
1241
?>
1242
<fieldset id="ssh-keys"<?php echo $hidden_class; ?>">
1243
<legend><?php _e( 'Authentication Keys' ); ?></legend>
1244
<label for="public_key">
1245
	<span class="field-title"><?php _e('Public Key:') ?></span>
1246
	<input name="public_key" type="text" id="public_key" aria-describedby="auth-keys-desc" value="<?php echo esc_attr($public_key) ?>"<?php disabled( defined('FTP_PUBKEY') ); ?> />
1247
</label>
1248
<label for="private_key">
1249
	<span class="field-title"><?php _e('Private Key:') ?></span>
1250
	<input name="private_key" type="text" id="private_key" value="<?php echo esc_attr($private_key) ?>"<?php disabled( defined('FTP_PRIKEY') ); ?> />
1251
</label>
1252
<p id="auth-keys-desc"><?php _e( 'Enter the location on the server where the public and private keys are located. If a passphrase is needed, enter that in the password field above.' ) ?></p>
1253
</fieldset>
1254
<?php
1255
}
1256
1257
foreach ( (array) $extra_fields as $field ) {
1258
	if ( isset( $_POST[ $field ] ) )
1259
		echo '<input type="hidden" name="' . esc_attr( $field ) . '" value="' . esc_attr( wp_unslash( $_POST[ $field ] ) ) . '" />';
0 ignored issues
show
Bug introduced by
It seems like wp_unslash($_POST[$field]) targeting wp_unslash() can also be of type array; however, esc_attr() does only seem to accept string, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
1260
}
1261
?>
1262
	<p class="request-filesystem-credentials-action-buttons">
1263
		<button class="button cancel-button" data-js-action="close" type="button"><?php _e( 'Cancel' ); ?></button>
1264
		<?php submit_button( __( 'Proceed' ), 'button', 'upgrade', false ); ?>
1265
	</p>
1266
</div>
1267
</form>
1268
<?php
1269
	return false;
1270
}
1271
1272
/**
1273
 * Print the filesystem credentials modal when needed.
1274
 *
1275
 * @since 4.2.0
1276
 */
1277
function wp_print_request_filesystem_credentials_modal() {
1278
	$filesystem_method = get_filesystem_method();
1279
	ob_start();
1280
	$filesystem_credentials_are_stored = request_filesystem_credentials( self_admin_url() );
1281
	ob_end_clean();
1282
	$request_filesystem_credentials = ( $filesystem_method != 'direct' && ! $filesystem_credentials_are_stored );
1283
	if ( ! $request_filesystem_credentials ) {
1284
		return;
1285
	}
1286
	?>
1287
	<div id="request-filesystem-credentials-dialog" class="notification-dialog-wrap request-filesystem-credentials-dialog">
1288
		<div class="notification-dialog-background"></div>
1289
		<div class="notification-dialog" role="dialog" aria-labelledby="request-filesystem-credentials-title" tabindex="0">
1290
			<div class="request-filesystem-credentials-dialog-content">
1291
				<?php request_filesystem_credentials( site_url() ); ?>
1292
			</div>
1293
		</div>
1294
	</div>
1295
	<?php
1296
}
1297