~canonical-sysadmins/wordpress/4.7.2

« back to all changes in this revision

Viewing changes to wp-admin/includes/file.php

  • Committer: Jacek Nykis
  • Date: 2015-01-05 16:17:05 UTC
  • Revision ID: jacek.nykis@canonical.com-20150105161705-w544l1h5mcg7u4w9
Initial commit

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
<?php
 
2
/**
 
3
 * Functions for reading, writing, modifying, and deleting files on the file system.
 
4
 * Includes functionality for theme-specific files as well as operations for uploading,
 
5
 * archiving, and rendering output when necessary.
 
6
 *
 
7
 * @package WordPress
 
8
 * @subpackage Administration
 
9
 */
 
10
 
 
11
/** The descriptions for theme files. */
 
12
$wp_file_descriptions = array(
 
13
        'index.php' => __( 'Main Index Template' ),
 
14
        'style.css' => __( 'Stylesheet' ),
 
15
        'editor-style.css' => __( 'Visual Editor Stylesheet' ),
 
16
        'editor-style-rtl.css' => __( 'Visual Editor RTL Stylesheet' ),
 
17
        'rtl.css' => __( 'RTL Stylesheet' ),
 
18
        'comments.php' => __( 'Comments' ),
 
19
        'comments-popup.php' => __( 'Popup Comments' ),
 
20
        'footer.php' => __( 'Footer' ),
 
21
        'header.php' => __( 'Header' ),
 
22
        'sidebar.php' => __( 'Sidebar' ),
 
23
        'archive.php' => __( 'Archives' ),
 
24
        'author.php' => __( 'Author Template' ),
 
25
        'tag.php' => __( 'Tag Template' ),
 
26
        'category.php' => __( 'Category Template' ),
 
27
        'page.php' => __( 'Page Template' ),
 
28
        'search.php' => __( 'Search Results' ),
 
29
        'searchform.php' => __( 'Search Form' ),
 
30
        'single.php' => __( 'Single Post' ),
 
31
        '404.php' => __( '404 Template' ),
 
32
        'link.php' => __( 'Links Template' ),
 
33
        'functions.php' => __( 'Theme Functions' ),
 
34
        'attachment.php' => __( 'Attachment Template' ),
 
35
        'image.php' => __('Image Attachment Template'),
 
36
        'video.php' => __('Video Attachment Template'),
 
37
        'audio.php' => __('Audio Attachment Template'),
 
38
        'application.php' => __('Application Attachment Template'),
 
39
        'my-hacks.php' => __( 'my-hacks.php (legacy hacks support)' ),
 
40
        '.htaccess' => __( '.htaccess (for rewrite rules )' ),
 
41
        // Deprecated files
 
42
        'wp-layout.css' => __( 'Stylesheet' ),
 
43
        'wp-comments.php' => __( 'Comments Template' ),
 
44
        'wp-comments-popup.php' => __( 'Popup Comments Template' ),
 
45
);
 
46
 
 
47
/**
 
48
 * Get the description for standard WordPress theme files and other various standard
 
49
 * WordPress files
 
50
 *
 
51
 * @since 1.5.0
 
52
 *
 
53
 * @uses _cleanup_header_comment
 
54
 * @uses $wp_file_descriptions
 
55
 * @param string $file Filesystem path or filename
 
56
 * @return string Description of file from $wp_file_descriptions or basename of $file if description doesn't exist
 
57
 */
 
58
function get_file_description( $file ) {
 
59
        global $wp_file_descriptions;
 
60
 
 
61
        if ( isset( $wp_file_descriptions[basename( $file )] ) ) {
 
62
                return $wp_file_descriptions[basename( $file )];
 
63
        }
 
64
        elseif ( file_exists( $file ) && is_file( $file ) ) {
 
65
                $template_data = implode( '', file( $file ) );
 
66
                if ( preg_match( '|Template Name:(.*)$|mi', $template_data, $name ))
 
67
                        return sprintf( __( '%s Page Template' ), _cleanup_header_comment($name[1]) );
 
68
        }
 
69
 
 
70
        return trim( basename( $file ) );
 
71
}
 
72
 
 
73
/**
 
74
 * Get the absolute filesystem path to the root of the WordPress installation
 
75
 *
 
76
 * @since 1.5.0
 
77
 *
 
78
 * @uses get_option
 
79
 * @return string Full filesystem path to the root of the WordPress installation
 
80
 */
 
81
function get_home_path() {
 
82
        $home    = set_url_scheme( get_option( 'home' ), 'http' );
 
83
        $siteurl = set_url_scheme( get_option( 'siteurl' ), 'http' );
 
84
        if ( ! empty( $home ) && 0 !== strcasecmp( $home, $siteurl ) ) {
 
85
                $wp_path_rel_to_home = str_ireplace( $home, '', $siteurl ); /* $siteurl - $home */
 
86
                $pos = strripos( str_replace( '\\', '/', $_SERVER['SCRIPT_FILENAME'] ), trailingslashit( $wp_path_rel_to_home ) );
 
87
                $home_path = substr( $_SERVER['SCRIPT_FILENAME'], 0, $pos );
 
88
                $home_path = trailingslashit( $home_path );
 
89
        } else {
 
90
                $home_path = ABSPATH;
 
91
        }
 
92
 
 
93
        return str_replace( '\\', '/', $home_path );
 
94
}
 
95
 
 
96
/**
 
97
 * Returns a listing of all files in the specified folder and all subdirectories up to 100 levels deep.
 
98
 * The depth of the recursiveness can be controlled by the $levels param.
 
99
 *
 
100
 * @since 2.6.0
 
101
 *
 
102
 * @param string $folder Full path to folder
 
103
 * @param int $levels (optional) Levels of folders to follow, Default: 100 (PHP Loop limit).
 
104
 * @return bool|array False on failure, Else array of files
 
105
 */
 
106
function list_files( $folder = '', $levels = 100 ) {
 
107
        if ( empty($folder) )
 
108
                return false;
 
109
 
 
110
        if ( ! $levels )
 
111
                return false;
 
112
 
 
113
        $files = array();
 
114
        if ( $dir = @opendir( $folder ) ) {
 
115
                while (($file = readdir( $dir ) ) !== false ) {
 
116
                        if ( in_array($file, array('.', '..') ) )
 
117
                                continue;
 
118
                        if ( is_dir( $folder . '/' . $file ) ) {
 
119
                                $files2 = list_files( $folder . '/' . $file, $levels - 1);
 
120
                                if ( $files2 )
 
121
                                        $files = array_merge($files, $files2 );
 
122
                                else
 
123
                                        $files[] = $folder . '/' . $file . '/';
 
124
                        } else {
 
125
                                $files[] = $folder . '/' . $file;
 
126
                        }
 
127
                }
 
128
        }
 
129
        @closedir( $dir );
 
130
        return $files;
 
131
}
 
132
 
 
133
/**
 
134
 * Returns a filename of a Temporary unique file.
 
135
 * Please note that the calling function must unlink() this itself.
 
136
 *
 
137
 * The filename is based off the passed parameter or defaults to the current unix timestamp,
 
138
 * while the directory can either be passed as well, or by leaving it blank, default to a writable temporary directory.
 
139
 *
 
140
 * @since 2.6.0
 
141
 *
 
142
 * @param string $filename (optional) Filename to base the Unique file off
 
143
 * @param string $dir (optional) Directory to store the file in
 
144
 * @return string a writable filename
 
145
 */
 
146
function wp_tempnam($filename = '', $dir = '') {
 
147
        if ( empty($dir) )
 
148
                $dir = get_temp_dir();
 
149
        $filename = basename($filename);
 
150
        if ( empty($filename) )
 
151
                $filename = time();
 
152
 
 
153
        $filename = preg_replace('|\..*$|', '.tmp', $filename);
 
154
        $filename = $dir . wp_unique_filename($dir, $filename);
 
155
        touch($filename);
 
156
        return $filename;
 
157
}
 
158
 
 
159
/**
 
160
 * Make sure that the file that was requested to edit, is allowed to be edited
 
161
 *
 
162
 * Function will die if if you are not allowed to edit the file
 
163
 *
 
164
 * @since 1.5.0
 
165
 *
 
166
 * @uses wp_die
 
167
 * @uses validate_file
 
168
 * @param string $file file the users is attempting to edit
 
169
 * @param array $allowed_files Array of allowed files to edit, $file must match an entry exactly
 
170
 * @return null
 
171
 */
 
172
function validate_file_to_edit( $file, $allowed_files = '' ) {
 
173
        $code = validate_file( $file, $allowed_files );
 
174
 
 
175
        if (!$code )
 
176
                return $file;
 
177
 
 
178
        switch ( $code ) {
 
179
                case 1 :
 
180
                        wp_die( __( 'Sorry, that file cannot be edited.' ) );
 
181
 
 
182
                // case 2 :
 
183
                // wp_die( __('Sorry, can&#8217;t call files with their real path.' ));
 
184
 
 
185
                case 3 :
 
186
                        wp_die( __( 'Sorry, that file cannot be edited.' ) );
 
187
        }
 
188
}
 
189
 
 
190
/**
 
191
 * Handle PHP uploads in WordPress, sanitizing file names, checking extensions for mime type,
 
192
 * and moving the file to the appropriate directory within the uploads directory.
 
193
 *
 
194
 * @since 4.0.0
 
195
 *
 
196
 * @see wp_handle_upload_error
 
197
 *
 
198
 * @param array  $file      Reference to a single element of $_FILES. Call the function once for
 
199
 *                          each uploaded file.
 
200
 * @param array  $overrides An associative array of names => values to override default variables.
 
201
 * @param string $time      Time formatted in 'yyyy/mm'.
 
202
 * @param string $action    Expected value for $_POST['action'].
 
203
 * @return array On success, returns an associative array of file attributes. On failure, returns
 
204
 *               $overrides['upload_error_handler'](&$file, $message ) or array( 'error'=>$message ).
 
205
*/
 
206
function _wp_handle_upload( &$file, $overrides, $time, $action ) {
 
207
        // The default error handler.
 
208
        if ( ! function_exists( 'wp_handle_upload_error' ) ) {
 
209
                function wp_handle_upload_error( &$file, $message ) {
 
210
                        return array( 'error' => $message );
 
211
                }
 
212
        }
 
213
 
 
214
        /**
 
215
         * The dynamic portion of the hook name, $action, refers to the post action.
 
216
         *
 
217
         * @since 2.9.0 as 'wp_handle_upload_prefilter'
 
218
         * @since 4.0.0 Converted to a dynamic hook with $action
 
219
         *
 
220
         * @param array $file An array of data for a single file.
 
221
         */
 
222
        $file = apply_filters( "{$action}_prefilter", $file );
 
223
 
 
224
        // You may define your own function and pass the name in $overrides['upload_error_handler']
 
225
        $upload_error_handler = 'wp_handle_upload_error';
 
226
        if ( isset( $overrides['upload_error_handler'] ) ) {
 
227
                $upload_error_handler = $overrides['upload_error_handler'];
 
228
        }
 
229
 
 
230
        // You may have had one or more 'wp_handle_upload_prefilter' functions error out the file. Handle that gracefully.
 
231
        if ( isset( $file['error'] ) && ! is_numeric( $file['error'] ) && $file['error'] ) {
 
232
                return $upload_error_handler( $file, $file['error'] );
 
233
        }
 
234
 
 
235
        // Install user overrides. Did we mention that this voids your warranty?
 
236
 
 
237
        // You may define your own function and pass the name in $overrides['unique_filename_callback']
 
238
        $unique_filename_callback = null;
 
239
        if ( isset( $overrides['unique_filename_callback'] ) ) {
 
240
                $unique_filename_callback = $overrides['unique_filename_callback'];
 
241
        }
 
242
 
 
243
        /*
 
244
         * This may not have orignially been intended to be overrideable,
 
245
         * but historically has been.
 
246
         */
 
247
        if ( isset( $overrides['upload_error_strings'] ) ) {
 
248
                $upload_error_strings = $overrides['upload_error_strings'];
 
249
        } else {
 
250
                // Courtesy of php.net, the strings that describe the error indicated in $_FILES[{form field}]['error'].
 
251
                $upload_error_strings = array(
 
252
                        false,
 
253
                        __( 'The uploaded file exceeds the upload_max_filesize directive in php.ini.' ),
 
254
                        __( 'The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form.' ),
 
255
                        __( 'The uploaded file was only partially uploaded.' ),
 
256
                        __( 'No file was uploaded.' ),
 
257
                        '',
 
258
                        __( 'Missing a temporary folder.' ),
 
259
                        __( 'Failed to write file to disk.' ),
 
260
                        __( 'File upload stopped by extension.' )
 
261
                );
 
262
        }
 
263
 
 
264
        // All tests are on by default. Most can be turned off by $overrides[{test_name}] = false;
 
265
        $test_form = isset( $overrides['test_form'] ) ? $overrides['test_form'] : true;
 
266
        $test_size = isset( $overrides['test_size'] ) ? $overrides['test_size'] : true;
 
267
 
 
268
        // If you override this, you must provide $ext and $type!!
 
269
        $test_type = isset( $overrides['test_type'] ) ? $overrides['test_type'] : true;
 
270
        $mimes = isset( $overrides['mimes'] ) ? $overrides['mimes'] : false;
 
271
 
 
272
        $test_upload = isset( $overrides['test_upload'] ) ? $overrides['test_upload'] : true;
 
273
 
 
274
        // A correct form post will pass this test.
 
275
        if ( $test_form && ( ! isset( $_POST['action'] ) || ( $_POST['action'] != $action ) ) ) {
 
276
                return call_user_func( $upload_error_handler, $file, __( 'Invalid form submission.' ) );
 
277
        }
 
278
        // A successful upload will pass this test. It makes no sense to override this one.
 
279
        if ( isset( $file['error'] ) && $file['error'] > 0 ) {
 
280
                return call_user_func( $upload_error_handler, $file, $upload_error_strings[ $file['error'] ] );
 
281
        }
 
282
 
 
283
        $test_file_size = 'wp_handle_upload' === $action ? $file['size'] : filesize( $file['tmp_name'] );
 
284
        // A non-empty file will pass this test.
 
285
        if ( $test_size && ! ( $test_file_size > 0 ) ) {
 
286
                if ( is_multisite() ) {
 
287
                        $error_msg = __( 'File is empty. Please upload something more substantial.' );
 
288
                } else {
 
289
                        $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.' );
 
290
                }
 
291
                return call_user_func( $upload_error_handler, $file, $error_msg );
 
292
        }
 
293
 
 
294
        // A properly uploaded file will pass this test. There should be no reason to override this one.
 
295
        $test_uploaded_file = 'wp_handle_upload' === $action ? @ is_uploaded_file( $file['tmp_name'] ) : @ is_file( $file['tmp_name'] );
 
296
        if ( $test_upload && ! $test_uploaded_file ) {
 
297
                return call_user_func( $upload_error_handler, $file, __( 'Specified file failed upload test.' ) );
 
298
        }
 
299
 
 
300
        // A correct MIME type will pass this test. Override $mimes or use the upload_mimes filter.
 
301
        if ( $test_type ) {
 
302
                $wp_filetype = wp_check_filetype_and_ext( $file['tmp_name'], $file['name'], $mimes );
 
303
                $ext = empty( $wp_filetype['ext'] ) ? '' : $wp_filetype['ext'];
 
304
                $type = empty( $wp_filetype['type'] ) ? '' : $wp_filetype['type'];
 
305
                $proper_filename = empty( $wp_filetype['proper_filename'] ) ? '' : $wp_filetype['proper_filename'];
 
306
 
 
307
                // Check to see if wp_check_filetype_and_ext() determined the filename was incorrect
 
308
                if ( $proper_filename ) {
 
309
                        $file['name'] = $proper_filename;
 
310
                }
 
311
                if ( ( ! $type || !$ext ) && ! current_user_can( 'unfiltered_upload' ) ) {
 
312
                        return call_user_func( $upload_error_handler, $file, __( 'Sorry, this file type is not permitted for security reasons.' ) );
 
313
                }
 
314
                if ( ! $type ) {
 
315
                        $type = $file['type'];
 
316
                }
 
317
        } else {
 
318
                $type = '';
 
319
        }
 
320
 
 
321
        /*
 
322
         * A writable uploads dir will pass this test. Again, there's no point
 
323
         * overriding this one.
 
324
         */
 
325
        if ( ! ( ( $uploads = wp_upload_dir( $time ) ) && false === $uploads['error'] ) ) {
 
326
                return call_user_func( $upload_error_handler, $file, $uploads['error'] );
 
327
        }
 
328
 
 
329
        $filename = wp_unique_filename( $uploads['path'], $file['name'], $unique_filename_callback );
 
330
 
 
331
        // Move the file to the uploads dir.
 
332
        $new_file = $uploads['path'] . "/$filename";
 
333
        if ( 'wp_handle_upload' === $action ) {
 
334
                $move_new_file = @ move_uploaded_file( $file['tmp_name'], $new_file );
 
335
        } else {
 
336
                $move_new_file = @ rename( $file['tmp_name'], $new_file );
 
337
        }
 
338
 
 
339
        if ( false === $move_new_file ) {
 
340
                if ( 0 === strpos( $uploads['basedir'], ABSPATH ) ) {
 
341
                        $error_path = str_replace( ABSPATH, '', $uploads['basedir'] ) . $uploads['subdir'];
 
342
                } else {
 
343
                        $error_path = basename( $uploads['basedir'] ) . $uploads['subdir'];
 
344
                }
 
345
                return $upload_error_handler( $file, sprintf( __('The uploaded file could not be moved to %s.' ), $error_path ) );
 
346
        }
 
347
 
 
348
        // Set correct file permissions.
 
349
        $stat = stat( dirname( $new_file ));
 
350
        $perms = $stat['mode'] & 0000666;
 
351
        @ chmod( $new_file, $perms );
 
352
 
 
353
        // Compute the URL.
 
354
        $url = $uploads['url'] . "/$filename";
 
355
 
 
356
        if ( is_multisite() ) {
 
357
                delete_transient( 'dirsize_cache' );
 
358
        }
 
359
 
 
360
        /**
 
361
         * Filter the data array for the uploaded file.
 
362
         *
 
363
         * @since 2.1.0
 
364
         *
 
365
         * @param array  $upload {
 
366
         *     Array of upload data.
 
367
         *
 
368
         *     @type string $file Filename of the newly-uploaded file.
 
369
         *     @type string $url  URL of the uploaded file.
 
370
         *     @type string $type File type.
 
371
         * }
 
372
         * @param string $context The type of upload action. Values include 'upload' or 'sideload'.
 
373
         */
 
374
        return apply_filters( 'wp_handle_upload', array(
 
375
                'file' => $new_file,
 
376
                'url'  => $url,
 
377
                'type' => $type
 
378
        ), 'wp_handle_sideload' === $action ? 'sideload' : 'upload' ); }
 
379
 
 
380
/**
 
381
 * Wrapper for _wp_handle_upload(), passes 'wp_handle_upload' action.
 
382
 *
 
383
 * @since 2.0.0
 
384
 *
 
385
 * @see _wp_handle_upload()
 
386
 *
 
387
 * @param array      $file      Reference to a single element of $_FILES. Call the function once for
 
388
 *                              each uploaded file.
 
389
 * @param array|bool $overrides Optional. An associative array of names=>values to override default
 
390
 *                              variables. Default false.
 
391
 * @param string     $time      Optional. Time formatted in 'yyyy/mm'. Default null.
 
392
 * @return array On success, returns an associative array of file attributes. On failure, returns
 
393
 *               $overrides['upload_error_handler'](&$file, $message ) or array( 'error'=>$message ).
 
394
 */
 
395
function wp_handle_upload( &$file, $overrides = false, $time = null ) {
 
396
        /*
 
397
         *  $_POST['action'] must be set and its value must equal $overrides['action']
 
398
         *  or this:
 
399
         */
 
400
        $action = 'wp_handle_upload';
 
401
        if ( isset( $overrides['action'] ) ) {
 
402
                $action = $overrides['action'];
 
403
        }
 
404
 
 
405
        return _wp_handle_upload( $file, $overrides, $time, $action );
 
406
}
 
407
 
 
408
/**
 
409
 * Wrapper for _wp_handle_upload(), passes 'wp_handle_sideload' action
 
410
 *
 
411
 * @since 2.6.0
 
412
 *
 
413
 * @see _wp_handle_upload()
 
414
 *
 
415
 * @param array      $file      An array similar to that of a PHP $_FILES POST array
 
416
 * @param array|bool $overrides Optional. An associative array of names=>values to override default
 
417
 *                              variables. Default false.
 
418
 * @param string     $time      Optional. Time formatted in 'yyyy/mm'. Default null.
 
419
 * @return array On success, returns an associative array of file attributes. On failure, returns
 
420
 *               $overrides['upload_error_handler'](&$file, $message ) or array( 'error'=>$message ).
 
421
 */
 
422
function wp_handle_sideload( &$file, $overrides = false, $time = null ) {
 
423
        /*
 
424
         *  $_POST['action'] must be set and its value must equal $overrides['action']
 
425
         *  or this:
 
426
         */
 
427
        $action = 'wp_handle_sideload';
 
428
        if ( isset( $overrides['action'] ) ) {
 
429
                $action = $overrides['action'];
 
430
        }
 
431
        return _wp_handle_upload( $file, $overrides, $time, $action );
 
432
}
 
433
 
 
434
 
 
435
/**
 
436
 * Downloads a url to a local temporary file using the WordPress HTTP Class.
 
437
 * Please note, That the calling function must unlink() the file.
 
438
 *
 
439
 * @since 2.5.0
 
440
 *
 
441
 * @param string $url the URL of the file to download
 
442
 * @param int $timeout The timeout for the request to download the file default 300 seconds
 
443
 * @return mixed WP_Error on failure, string Filename on success.
 
444
 */
 
445
function download_url( $url, $timeout = 300 ) {
 
446
        //WARNING: The file is not automatically deleted, The script must unlink() the file.
 
447
        if ( ! $url )
 
448
                return new WP_Error('http_no_url', __('Invalid URL Provided.'));
 
449
 
 
450
        $tmpfname = wp_tempnam($url);
 
451
        if ( ! $tmpfname )
 
452
                return new WP_Error('http_no_file', __('Could not create Temporary file.'));
 
453
 
 
454
        $response = wp_safe_remote_get( $url, array( 'timeout' => $timeout, 'stream' => true, 'filename' => $tmpfname ) );
 
455
 
 
456
        if ( is_wp_error( $response ) ) {
 
457
                unlink( $tmpfname );
 
458
                return $response;
 
459
        }
 
460
 
 
461
        if ( 200 != wp_remote_retrieve_response_code( $response ) ){
 
462
                unlink( $tmpfname );
 
463
                return new WP_Error( 'http_404', trim( wp_remote_retrieve_response_message( $response ) ) );
 
464
        }
 
465
 
 
466
        $content_md5 = wp_remote_retrieve_header( $response, 'content-md5' );
 
467
        if ( $content_md5 ) {
 
468
                $md5_check = verify_file_md5( $tmpfname, $content_md5 );
 
469
                if ( is_wp_error( $md5_check ) ) {
 
470
                        unlink( $tmpfname );
 
471
                        return $md5_check;
 
472
                }
 
473
        }
 
474
 
 
475
        return $tmpfname;
 
476
}
 
477
 
 
478
/**
 
479
 * Calculates and compares the MD5 of a file to its expected value.
 
480
 *
 
481
 * @since 3.7.0
 
482
 *
 
483
 * @param string $filename The filename to check the MD5 of.
 
484
 * @param string $expected_md5 The expected MD5 of the file, either a base64 encoded raw md5, or a hex-encoded md5
 
485
 * @return bool|object WP_Error on failure, true on success, false when the MD5 format is unknown/unexpected
 
486
 */
 
487
function verify_file_md5( $filename, $expected_md5 ) {
 
488
        if ( 32 == strlen( $expected_md5 ) )
 
489
                $expected_raw_md5 = pack( 'H*', $expected_md5 );
 
490
        elseif ( 24 == strlen( $expected_md5 ) )
 
491
                $expected_raw_md5 = base64_decode( $expected_md5 );
 
492
        else
 
493
                return false; // unknown format
 
494
 
 
495
        $file_md5 = md5_file( $filename, true );
 
496
 
 
497
        if ( $file_md5 === $expected_raw_md5 )
 
498
                return true;
 
499
 
 
500
        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 ) ) );
 
501
}
 
502
 
 
503
/**
 
504
 * Unzips a specified ZIP file to a location on the Filesystem via the WordPress Filesystem Abstraction.
 
505
 * Assumes that WP_Filesystem() has already been called and set up. Does not extract a root-level __MACOSX directory, if present.
 
506
 *
 
507
 * Attempts to increase the PHP Memory limit to 256M before uncompressing,
 
508
 * However, The most memory required shouldn't be much larger than the Archive itself.
 
509
 *
 
510
 * @since 2.5.0
 
511
 *
 
512
 * @param string $file Full path and filename of zip archive
 
513
 * @param string $to Full path on the filesystem to extract archive to
 
514
 * @return mixed WP_Error on failure, True on success
 
515
 */
 
516
function unzip_file($file, $to) {
 
517
        global $wp_filesystem;
 
518
 
 
519
        if ( ! $wp_filesystem || !is_object($wp_filesystem) )
 
520
                return new WP_Error('fs_unavailable', __('Could not access filesystem.'));
 
521
 
 
522
        // Unzip can use a lot of memory, but not this much hopefully
 
523
        /** This filter is documented in wp-admin/admin.php */
 
524
        @ini_set( 'memory_limit', apply_filters( 'admin_memory_limit', WP_MAX_MEMORY_LIMIT ) );
 
525
 
 
526
        $needed_dirs = array();
 
527
        $to = trailingslashit($to);
 
528
 
 
529
        // Determine any parent dir's needed (of the upgrade directory)
 
530
        if ( ! $wp_filesystem->is_dir($to) ) { //Only do parents if no children exist
 
531
                $path = preg_split('![/\\\]!', untrailingslashit($to));
 
532
                for ( $i = count($path); $i >= 0; $i-- ) {
 
533
                        if ( empty($path[$i]) )
 
534
                                continue;
 
535
 
 
536
                        $dir = implode('/', array_slice($path, 0, $i+1) );
 
537
                        if ( preg_match('!^[a-z]:$!i', $dir) ) // Skip it if it looks like a Windows Drive letter.
 
538
                                continue;
 
539
 
 
540
                        if ( ! $wp_filesystem->is_dir($dir) )
 
541
                                $needed_dirs[] = $dir;
 
542
                        else
 
543
                                break; // A folder exists, therefor, we dont need the check the levels below this
 
544
                }
 
545
        }
 
546
 
 
547
        /**
 
548
         * Filter whether to use ZipArchive to unzip archives.
 
549
         *
 
550
         * @since 3.0.0
 
551
         *
 
552
         * @param bool $ziparchive Whether to use ZipArchive. Default true.
 
553
         */
 
554
        if ( class_exists( 'ZipArchive' ) && apply_filters( 'unzip_file_use_ziparchive', true ) ) {
 
555
                $result = _unzip_file_ziparchive($file, $to, $needed_dirs);
 
556
                if ( true === $result ) {
 
557
                        return $result;
 
558
                } elseif ( is_wp_error($result) ) {
 
559
                        if ( 'incompatible_archive' != $result->get_error_code() )
 
560
                                return $result;
 
561
                }
 
562
        }
 
563
        // Fall through to PclZip if ZipArchive is not available, or encountered an error opening the file.
 
564
        return _unzip_file_pclzip($file, $to, $needed_dirs);
 
565
}
 
566
 
 
567
/**
 
568
 * This function should not be called directly, use unzip_file instead. Attempts to unzip an archive using the ZipArchive class.
 
569
 * Assumes that WP_Filesystem() has already been called and set up.
 
570
 *
 
571
 * @since 3.0.0
 
572
 * @see unzip_file
 
573
 * @access private
 
574
 *
 
575
 * @param string $file Full path and filename of zip archive
 
576
 * @param string $to Full path on the filesystem to extract archive to
 
577
 * @param array $needed_dirs A partial list of required folders needed to be created.
 
578
 * @return mixed WP_Error on failure, True on success
 
579
 */
 
580
function _unzip_file_ziparchive($file, $to, $needed_dirs = array() ) {
 
581
        global $wp_filesystem;
 
582
 
 
583
        $z = new ZipArchive();
 
584
 
 
585
        $zopen = $z->open( $file, ZIPARCHIVE::CHECKCONS );
 
586
        if ( true !== $zopen )
 
587
                return new WP_Error( 'incompatible_archive', __( 'Incompatible Archive.' ), array( 'ziparchive_error' => $zopen ) );
 
588
 
 
589
        $uncompressed_size = 0;
 
590
 
 
591
        for ( $i = 0; $i < $z->numFiles; $i++ ) {
 
592
                if ( ! $info = $z->statIndex($i) )
 
593
                        return new WP_Error( 'stat_failed_ziparchive', __( 'Could not retrieve file from archive.' ) );
 
594
 
 
595
                if ( '__MACOSX/' === substr($info['name'], 0, 9) ) // Skip the OS X-created __MACOSX directory
 
596
                        continue;
 
597
 
 
598
                $uncompressed_size += $info['size'];
 
599
 
 
600
                if ( '/' == substr($info['name'], -1) ) // directory
 
601
                        $needed_dirs[] = $to . untrailingslashit($info['name']);
 
602
                else
 
603
                        $needed_dirs[] = $to . untrailingslashit(dirname($info['name']));
 
604
        }
 
605
 
 
606
        /*
 
607
         * disk_free_space() could return false. Assume that any falsey value is an error.
 
608
         * A disk that has zero free bytes has bigger problems.
 
609
         * Require we have enough space to unzip the file and copy its contents, with a 10% buffer.
 
610
         */
 
611
        if ( defined( 'DOING_CRON' ) && DOING_CRON ) {
 
612
                $available_space = @disk_free_space( WP_CONTENT_DIR );
 
613
                if ( $available_space && ( $uncompressed_size * 2.1 ) > $available_space )
 
614
                        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' ) );
 
615
        }
 
616
 
 
617
        $needed_dirs = array_unique($needed_dirs);
 
618
        foreach ( $needed_dirs as $dir ) {
 
619
                // Check the parent folders of the folders all exist within the creation array.
 
620
                if ( untrailingslashit($to) == $dir ) // Skip over the working directory, We know this exists (or will exist)
 
621
                        continue;
 
622
                if ( strpos($dir, $to) === false ) // If the directory is not within the working directory, Skip it
 
623
                        continue;
 
624
 
 
625
                $parent_folder = dirname($dir);
 
626
                while ( !empty($parent_folder) && untrailingslashit($to) != $parent_folder && !in_array($parent_folder, $needed_dirs) ) {
 
627
                        $needed_dirs[] = $parent_folder;
 
628
                        $parent_folder = dirname($parent_folder);
 
629
                }
 
630
        }
 
631
        asort($needed_dirs);
 
632
 
 
633
        // Create those directories if need be:
 
634
        foreach ( $needed_dirs as $_dir ) {
 
635
                if ( ! $wp_filesystem->mkdir($_dir, FS_CHMOD_DIR) && ! $wp_filesystem->is_dir($_dir) ) // Only check to see if the Dir exists upon creation failure. Less I/O this way.
 
636
                        return new WP_Error( 'mkdir_failed_ziparchive', __( 'Could not create directory.' ), substr( $_dir, strlen( $to ) ) );
 
637
        }
 
638
        unset($needed_dirs);
 
639
 
 
640
        for ( $i = 0; $i < $z->numFiles; $i++ ) {
 
641
                if ( ! $info = $z->statIndex($i) )
 
642
                        return new WP_Error( 'stat_failed_ziparchive', __( 'Could not retrieve file from archive.' ) );
 
643
 
 
644
                if ( '/' == substr($info['name'], -1) ) // directory
 
645
                        continue;
 
646
 
 
647
                if ( '__MACOSX/' === substr($info['name'], 0, 9) ) // Don't extract the OS X-created __MACOSX directory files
 
648
                        continue;
 
649
 
 
650
                $contents = $z->getFromIndex($i);
 
651
                if ( false === $contents )
 
652
                        return new WP_Error( 'extract_failed_ziparchive', __( 'Could not extract file from archive.' ), $info['name'] );
 
653
 
 
654
                if ( ! $wp_filesystem->put_contents( $to . $info['name'], $contents, FS_CHMOD_FILE) )
 
655
                        return new WP_Error( 'copy_failed_ziparchive', __( 'Could not copy file.' ), $info['name'] );
 
656
        }
 
657
 
 
658
        $z->close();
 
659
 
 
660
        return true;
 
661
}
 
662
 
 
663
/**
 
664
 * This function should not be called directly, use unzip_file instead. Attempts to unzip an archive using the PclZip library.
 
665
 * Assumes that WP_Filesystem() has already been called and set up.
 
666
 *
 
667
 * @since 3.0.0
 
668
 * @see unzip_file
 
669
 * @access private
 
670
 *
 
671
 * @param string $file Full path and filename of zip archive
 
672
 * @param string $to Full path on the filesystem to extract archive to
 
673
 * @param array $needed_dirs A partial list of required folders needed to be created.
 
674
 * @return mixed WP_Error on failure, True on success
 
675
 */
 
676
function _unzip_file_pclzip($file, $to, $needed_dirs = array()) {
 
677
        global $wp_filesystem;
 
678
 
 
679
        mbstring_binary_safe_encoding();
 
680
 
 
681
        require_once(ABSPATH . 'wp-admin/includes/class-pclzip.php');
 
682
 
 
683
        $archive = new PclZip($file);
 
684
 
 
685
        $archive_files = $archive->extract(PCLZIP_OPT_EXTRACT_AS_STRING);
 
686
 
 
687
        reset_mbstring_encoding();
 
688
 
 
689
        // Is the archive valid?
 
690
        if ( !is_array($archive_files) )
 
691
                return new WP_Error('incompatible_archive', __('Incompatible Archive.'), $archive->errorInfo(true));
 
692
 
 
693
        if ( 0 == count($archive_files) )
 
694
                return new WP_Error( 'empty_archive_pclzip', __( 'Empty archive.' ) );
 
695
 
 
696
        $uncompressed_size = 0;
 
697
 
 
698
        // Determine any children directories needed (From within the archive)
 
699
        foreach ( $archive_files as $file ) {
 
700
                if ( '__MACOSX/' === substr($file['filename'], 0, 9) ) // Skip the OS X-created __MACOSX directory
 
701
                        continue;
 
702
 
 
703
                $uncompressed_size += $file['size'];
 
704
 
 
705
                $needed_dirs[] = $to . untrailingslashit( $file['folder'] ? $file['filename'] : dirname($file['filename']) );
 
706
        }
 
707
 
 
708
        /*
 
709
         * disk_free_space() could return false. Assume that any falsey value is an error.
 
710
         * A disk that has zero free bytes has bigger problems.
 
711
         * Require we have enough space to unzip the file and copy its contents, with a 10% buffer.
 
712
         */
 
713
        if ( defined( 'DOING_CRON' ) && DOING_CRON ) {
 
714
                $available_space = @disk_free_space( WP_CONTENT_DIR );
 
715
                if ( $available_space && ( $uncompressed_size * 2.1 ) > $available_space )
 
716
                        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' ) );
 
717
        }
 
718
 
 
719
        $needed_dirs = array_unique($needed_dirs);
 
720
        foreach ( $needed_dirs as $dir ) {
 
721
                // Check the parent folders of the folders all exist within the creation array.
 
722
                if ( untrailingslashit($to) == $dir ) // Skip over the working directory, We know this exists (or will exist)
 
723
                        continue;
 
724
                if ( strpos($dir, $to) === false ) // If the directory is not within the working directory, Skip it
 
725
                        continue;
 
726
 
 
727
                $parent_folder = dirname($dir);
 
728
                while ( !empty($parent_folder) && untrailingslashit($to) != $parent_folder && !in_array($parent_folder, $needed_dirs) ) {
 
729
                        $needed_dirs[] = $parent_folder;
 
730
                        $parent_folder = dirname($parent_folder);
 
731
                }
 
732
        }
 
733
        asort($needed_dirs);
 
734
 
 
735
        // Create those directories if need be:
 
736
        foreach ( $needed_dirs as $_dir ) {
 
737
                // Only check to see if the dir exists upon creation failure. Less I/O this way.
 
738
                if ( ! $wp_filesystem->mkdir( $_dir, FS_CHMOD_DIR ) && ! $wp_filesystem->is_dir( $_dir ) )
 
739
                        return new WP_Error( 'mkdir_failed_pclzip', __( 'Could not create directory.' ), substr( $_dir, strlen( $to ) ) );
 
740
        }
 
741
        unset($needed_dirs);
 
742
 
 
743
        // Extract the files from the zip
 
744
        foreach ( $archive_files as $file ) {
 
745
                if ( $file['folder'] )
 
746
                        continue;
 
747
 
 
748
                if ( '__MACOSX/' === substr($file['filename'], 0, 9) ) // Don't extract the OS X-created __MACOSX directory files
 
749
                        continue;
 
750
 
 
751
                if ( ! $wp_filesystem->put_contents( $to . $file['filename'], $file['content'], FS_CHMOD_FILE) )
 
752
                        return new WP_Error( 'copy_failed_pclzip', __( 'Could not copy file.' ), $file['filename'] );
 
753
        }
 
754
        return true;
 
755
}
 
756
 
 
757
/**
 
758
 * Copies a directory from one location to another via the WordPress Filesystem Abstraction.
 
759
 * Assumes that WP_Filesystem() has already been called and setup.
 
760
 *
 
761
 * @since 2.5.0
 
762
 *
 
763
 * @param string $from source directory
 
764
 * @param string $to destination directory
 
765
 * @param array $skip_list a list of files/folders to skip copying
 
766
 * @return mixed WP_Error on failure, True on success.
 
767
 */
 
768
function copy_dir($from, $to, $skip_list = array() ) {
 
769
        global $wp_filesystem;
 
770
 
 
771
        $dirlist = $wp_filesystem->dirlist($from);
 
772
 
 
773
        $from = trailingslashit($from);
 
774
        $to = trailingslashit($to);
 
775
 
 
776
        foreach ( (array) $dirlist as $filename => $fileinfo ) {
 
777
                if ( in_array( $filename, $skip_list ) )
 
778
                        continue;
 
779
 
 
780
                if ( 'f' == $fileinfo['type'] ) {
 
781
                        if ( ! $wp_filesystem->copy($from . $filename, $to . $filename, true, FS_CHMOD_FILE) ) {
 
782
                                // If copy failed, chmod file to 0644 and try again.
 
783
                                $wp_filesystem->chmod( $to . $filename, FS_CHMOD_FILE );
 
784
                                if ( ! $wp_filesystem->copy($from . $filename, $to . $filename, true, FS_CHMOD_FILE) )
 
785
                                        return new WP_Error( 'copy_failed_copy_dir', __( 'Could not copy file.' ), $to . $filename );
 
786
                        }
 
787
                } elseif ( 'd' == $fileinfo['type'] ) {
 
788
                        if ( !$wp_filesystem->is_dir($to . $filename) ) {
 
789
                                if ( !$wp_filesystem->mkdir($to . $filename, FS_CHMOD_DIR) )
 
790
                                        return new WP_Error( 'mkdir_failed_copy_dir', __( 'Could not create directory.' ), $to . $filename );
 
791
                        }
 
792
 
 
793
                        // generate the $sub_skip_list for the subdirectory as a sub-set of the existing $skip_list
 
794
                        $sub_skip_list = array();
 
795
                        foreach ( $skip_list as $skip_item ) {
 
796
                                if ( 0 === strpos( $skip_item, $filename . '/' ) )
 
797
                                        $sub_skip_list[] = preg_replace( '!^' . preg_quote( $filename, '!' ) . '/!i', '', $skip_item );
 
798
                        }
 
799
 
 
800
                        $result = copy_dir($from . $filename, $to . $filename, $sub_skip_list);
 
801
                        if ( is_wp_error($result) )
 
802
                                return $result;
 
803
                }
 
804
        }
 
805
        return true;
 
806
}
 
807
 
 
808
/**
 
809
 * Initialises and connects the WordPress Filesystem Abstraction classes.
 
810
 * This function will include the chosen transport and attempt connecting.
 
811
 *
 
812
 * Plugins may add extra transports, And force WordPress to use them by returning the filename via the 'filesystem_method_file' filter.
 
813
 *
 
814
 * @since 2.5.0
 
815
 *
 
816
 * @param array $args (optional) Connection args, These are passed directly to the WP_Filesystem_*() classes.
 
817
 * @param string $context (optional) Context for get_filesystem_method(), See function declaration for more information.
 
818
 * @return boolean false on failure, true on success
 
819
 */
 
820
function WP_Filesystem( $args = false, $context = false ) {
 
821
        global $wp_filesystem;
 
822
 
 
823
        require_once(ABSPATH . 'wp-admin/includes/class-wp-filesystem-base.php');
 
824
 
 
825
        $method = get_filesystem_method($args, $context);
 
826
 
 
827
        if ( ! $method )
 
828
                return false;
 
829
 
 
830
        if ( ! class_exists("WP_Filesystem_$method") ) {
 
831
 
 
832
                /**
 
833
                 * Filter the path for a specific filesystem method class file.
 
834
                 *
 
835
                 * @since 2.6.0
 
836
                 *
 
837
                 * @see get_filesystem_method()
 
838
                 *
 
839
                 * @param string $path   Path to the specific filesystem method class file.
 
840
                 * @param string $method The filesystem method to use.
 
841
                 */
 
842
                $abstraction_file = apply_filters( 'filesystem_method_file', ABSPATH . 'wp-admin/includes/class-wp-filesystem-' . $method . '.php', $method );
 
843
 
 
844
                if ( ! file_exists($abstraction_file) )
 
845
                        return;
 
846
 
 
847
                require_once($abstraction_file);
 
848
        }
 
849
        $method = "WP_Filesystem_$method";
 
850
 
 
851
        $wp_filesystem = new $method($args);
 
852
 
 
853
        //Define the timeouts for the connections. Only available after the construct is called to allow for per-transport overriding of the default.
 
854
        if ( ! defined('FS_CONNECT_TIMEOUT') )
 
855
                define('FS_CONNECT_TIMEOUT', 30);
 
856
        if ( ! defined('FS_TIMEOUT') )
 
857
                define('FS_TIMEOUT', 30);
 
858
 
 
859
        if ( is_wp_error($wp_filesystem->errors) && $wp_filesystem->errors->get_error_code() )
 
860
                return false;
 
861
 
 
862
        if ( !$wp_filesystem->connect() )
 
863
                return false; //There was an error connecting to the server.
 
864
 
 
865
        // Set the permission constants if not already set.
 
866
        if ( ! defined('FS_CHMOD_DIR') )
 
867
                define('FS_CHMOD_DIR', ( fileperms( ABSPATH ) & 0777 | 0755 ) );
 
868
        if ( ! defined('FS_CHMOD_FILE') )
 
869
                define('FS_CHMOD_FILE', ( fileperms( ABSPATH . 'index.php' ) & 0777 | 0644 ) );
 
870
 
 
871
        return true;
 
872
}
 
873
 
 
874
/**
 
875
 * Determines which Filesystem Method to use.
 
876
 * The priority of the Transports are: Direct, SSH2, FTP PHP Extension, FTP Sockets (Via Sockets class, or fsockopen())
 
877
 *
 
878
 * Note that the return value of this function can be overridden in 2 ways
 
879
 *  - By defining FS_METHOD in your <code>wp-config.php</code> file
 
880
 *  - By using the filesystem_method filter
 
881
 * Valid values for these are: 'direct', 'ssh2', 'ftpext' or 'ftpsockets'
 
882
 * Plugins may also define a custom transport handler, See the WP_Filesystem function for more information.
 
883
 *
 
884
 * @since 2.5.0
 
885
 *
 
886
 * @param array $args Connection details.
 
887
 * @param string $context Full path to the directory that is tested for being writable.
 
888
 * @return string The transport to use, see description for valid return values.
 
889
 */
 
890
function get_filesystem_method($args = array(), $context = false) {
 
891
        $method = defined('FS_METHOD') ? FS_METHOD : false; // Please ensure that this is either 'direct', 'ssh2', 'ftpext' or 'ftpsockets'
 
892
 
 
893
        if ( ! $method && function_exists('getmyuid') && function_exists('fileowner') ){
 
894
                if ( !$context )
 
895
                        $context = WP_CONTENT_DIR;
 
896
 
 
897
                // If the directory doesn't exist (wp-content/languages) then use the parent directory as we'll create it.
 
898
                if ( WP_LANG_DIR == $context && ! is_dir( $context ) )
 
899
                        $context = dirname( $context );
 
900
 
 
901
                $context = trailingslashit($context);
 
902
                $temp_file_name = $context . 'temp-write-test-' . time();
 
903
                $temp_handle = @fopen($temp_file_name, 'w');
 
904
                if ( $temp_handle ) {
 
905
                        if ( getmyuid() == @fileowner($temp_file_name) )
 
906
                                $method = 'direct';
 
907
                        @fclose($temp_handle);
 
908
                        @unlink($temp_file_name);
 
909
                }
 
910
        }
 
911
 
 
912
        if ( ! $method && isset($args['connection_type']) && 'ssh' == $args['connection_type'] && extension_loaded('ssh2') && function_exists('stream_get_contents') ) $method = 'ssh2';
 
913
        if ( ! $method && extension_loaded('ftp') ) $method = 'ftpext';
 
914
        if ( ! $method && ( extension_loaded('sockets') || function_exists('fsockopen') ) ) $method = 'ftpsockets'; //Sockets: Socket extension; PHP Mode: FSockopen / fwrite / fread
 
915
 
 
916
        /**
 
917
         * Filter the filesystem method to use.
 
918
         *
 
919
         * @since 2.6.0
 
920
         *
 
921
         * @param string $method Filesystem method to return.
 
922
         * @param array  $args   An array of connection details for the method.
 
923
         */
 
924
        return apply_filters( 'filesystem_method', $method, $args );
 
925
}
 
926
 
 
927
/**
 
928
 * Displays a form to the user to request for their FTP/SSH details in order to connect to the filesystem.
 
929
 * All chosen/entered details are saved, Excluding the Password.
 
930
 *
 
931
 * Hostnames may be in the form of hostname:portnumber (eg: wordpress.org:2467) to specify an alternate FTP/SSH port.
 
932
 *
 
933
 * Plugins may override this form by returning true|false via the <code>request_filesystem_credentials</code> filter.
 
934
 *
 
935
 * @since 2.5.0
 
936
 *
 
937
 * @param string $form_post the URL to post the form to
 
938
 * @param string $type the chosen Filesystem method in use
 
939
 * @param boolean $error if the current request has failed to connect
 
940
 * @param string $context The directory which is needed access to, The write-test will be performed on this directory by get_filesystem_method()
 
941
 * @param string $extra_fields Extra POST fields which should be checked for to be included in the post.
 
942
 * @return boolean False on failure. True on success.
 
943
 */
 
944
function request_filesystem_credentials($form_post, $type = '', $error = false, $context = false, $extra_fields = null) {
 
945
 
 
946
        /**
 
947
         * Filter the filesystem credentials form output.
 
948
         *
 
949
         * Returning anything other than an empty string will effectively short-circuit
 
950
         * output of the filesystem credentials form, returning that value instead.
 
951
         *
 
952
         * @since 2.5.0
 
953
         *
 
954
         * @param mixed  $output       Form output to return instead. Default empty.
 
955
         * @param string $form_post    URL to POST the form to.
 
956
         * @param string $type         Chosen type of filesystem.
 
957
         * @param bool   $error        Whether the current request has failed to connect.
 
958
         *                             Default false.
 
959
         * @param string $context      Full path to the directory that is tested for
 
960
         *                             being writable.
 
961
         * @param array  $extra_fields Extra POST fields.
 
962
         */
 
963
        $req_cred = apply_filters( 'request_filesystem_credentials', '', $form_post, $type, $error, $context, $extra_fields );
 
964
        if ( '' !== $req_cred )
 
965
                return $req_cred;
 
966
 
 
967
        if ( empty($type) )
 
968
                $type = get_filesystem_method(array(), $context);
 
969
 
 
970
        if ( 'direct' == $type )
 
971
                return true;
 
972
 
 
973
        if ( is_null( $extra_fields ) )
 
974
                $extra_fields = array( 'version', 'locale' );
 
975
 
 
976
        $credentials = get_option('ftp_credentials', array( 'hostname' => '', 'username' => ''));
 
977
 
 
978
        // 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)
 
979
        $credentials['hostname'] = defined('FTP_HOST') ? FTP_HOST : (!empty($_POST['hostname']) ? wp_unslash( $_POST['hostname'] ) : $credentials['hostname']);
 
980
        $credentials['username'] = defined('FTP_USER') ? FTP_USER : (!empty($_POST['username']) ? wp_unslash( $_POST['username'] ) : $credentials['username']);
 
981
        $credentials['password'] = defined('FTP_PASS') ? FTP_PASS : (!empty($_POST['password']) ? wp_unslash( $_POST['password'] ) : '');
 
982
 
 
983
        // Check to see if we are setting the public/private keys for ssh
 
984
        $credentials['public_key'] = defined('FTP_PUBKEY') ? FTP_PUBKEY : (!empty($_POST['public_key']) ? wp_unslash( $_POST['public_key'] ) : '');
 
985
        $credentials['private_key'] = defined('FTP_PRIKEY') ? FTP_PRIKEY : (!empty($_POST['private_key']) ? wp_unslash( $_POST['private_key'] ) : '');
 
986
 
 
987
        // Sanitize the hostname, Some people might pass in odd-data:
 
988
        $credentials['hostname'] = preg_replace('|\w+://|', '', $credentials['hostname']); //Strip any schemes off
 
989
 
 
990
        if ( strpos($credentials['hostname'], ':') ) {
 
991
                list( $credentials['hostname'], $credentials['port'] ) = explode(':', $credentials['hostname'], 2);
 
992
                if ( ! is_numeric($credentials['port']) )
 
993
                        unset($credentials['port']);
 
994
        } else {
 
995
                unset($credentials['port']);
 
996
        }
 
997
 
 
998
        if ( ( defined('FTP_SSH') && FTP_SSH ) || ( defined('FS_METHOD') && 'ssh2' == FS_METHOD ) )
 
999
                $credentials['connection_type'] = 'ssh';
 
1000
        else if ( (defined('FTP_SSL') && FTP_SSL) && 'ftpext' == $type ) //Only the FTP Extension understands SSL
 
1001
                $credentials['connection_type'] = 'ftps';
 
1002
        else if ( !empty($_POST['connection_type']) )
 
1003
                $credentials['connection_type'] = wp_unslash( $_POST['connection_type'] );
 
1004
        else if ( !isset($credentials['connection_type']) ) //All else fails (And it's not defaulted to something else saved), Default to FTP
 
1005
                $credentials['connection_type'] = 'ftp';
 
1006
 
 
1007
        if ( ! $error &&
 
1008
                        (
 
1009
                                ( !empty($credentials['password']) && !empty($credentials['username']) && !empty($credentials['hostname']) ) ||
 
1010
                                ( 'ssh' == $credentials['connection_type'] && !empty($credentials['public_key']) && !empty($credentials['private_key']) )
 
1011
                        ) ) {
 
1012
                $stored_credentials = $credentials;
 
1013
                if ( !empty($stored_credentials['port']) ) //save port as part of hostname to simplify above code.
 
1014
                        $stored_credentials['hostname'] .= ':' . $stored_credentials['port'];
 
1015
 
 
1016
                unset($stored_credentials['password'], $stored_credentials['port'], $stored_credentials['private_key'], $stored_credentials['public_key']);
 
1017
                update_option('ftp_credentials', $stored_credentials);
 
1018
                return $credentials;
 
1019
        }
 
1020
        $hostname = isset( $credentials['hostname'] ) ? $credentials['hostname'] : '';
 
1021
        $username = isset( $credentials['username'] ) ? $credentials['username'] : '';
 
1022
        $public_key = isset( $credentials['public_key'] ) ? $credentials['public_key'] : '';
 
1023
        $private_key = isset( $credentials['private_key'] ) ? $credentials['private_key'] : '';
 
1024
        $port = isset( $credentials['port'] ) ? $credentials['port'] : '';
 
1025
        $connection_type = isset( $credentials['connection_type'] ) ? $credentials['connection_type'] : '';
 
1026
 
 
1027
        if ( $error ) {
 
1028
                $error_string = __('<strong>ERROR:</strong> There was an error connecting to the server, Please verify the settings are correct.');
 
1029
                if ( is_wp_error($error) )
 
1030
                        $error_string = esc_html( $error->get_error_message() );
 
1031
                echo '<div id="message" class="error"><p>' . $error_string . '</p></div>';
 
1032
        }
 
1033
 
 
1034
        $types = array();
 
1035
        if ( extension_loaded('ftp') || extension_loaded('sockets') || function_exists('fsockopen') )
 
1036
                $types[ 'ftp' ] = __('FTP');
 
1037
        if ( extension_loaded('ftp') ) //Only this supports FTPS
 
1038
                $types[ 'ftps' ] = __('FTPS (SSL)');
 
1039
        if ( extension_loaded('ssh2') && function_exists('stream_get_contents') )
 
1040
                $types[ 'ssh' ] = __('SSH2');
 
1041
 
 
1042
        /**
 
1043
         * Filter the connection types to output to the filesystem credentials form.
 
1044
         *
 
1045
         * @since 2.9.0
 
1046
         *
 
1047
         * @param array  $types       Types of connections.
 
1048
         * @param array  $credentials Credentials to connect with.
 
1049
         * @param string $type        Chosen filesystem method.
 
1050
         * @param object $error       Error object.
 
1051
         * @param string $context     Full path to the directory that is tested
 
1052
         *                            for being writable.
 
1053
         */
 
1054
        $types = apply_filters( 'fs_ftp_connection_types', $types, $credentials, $type, $error, $context );
 
1055
 
 
1056
?>
 
1057
<script type="text/javascript">
 
1058
<!--
 
1059
jQuery(function($){
 
1060
        jQuery("#ssh").click(function () {
 
1061
                jQuery("#ssh_keys").show();
 
1062
        });
 
1063
        jQuery("#ftp, #ftps").click(function () {
 
1064
                jQuery("#ssh_keys").hide();
 
1065
        });
 
1066
        jQuery('form input[value=""]:first').focus();
 
1067
});
 
1068
-->
 
1069
</script>
 
1070
<form action="<?php echo esc_url( $form_post ) ?>" method="post">
 
1071
<div>
 
1072
<h3><?php _e('Connection Information') ?></h3>
 
1073
<p><?php
 
1074
        $label_user = __('Username');
 
1075
        $label_pass = __('Password');
 
1076
        _e('To perform the requested action, WordPress needs to access your web server.');
 
1077
        echo ' ';
 
1078
        if ( ( isset( $types['ftp'] ) || isset( $types['ftps'] ) ) ) {
 
1079
                if ( isset( $types['ssh'] ) ) {
 
1080
                        _e('Please enter your FTP or SSH credentials to proceed.');
 
1081
                        $label_user = __('FTP/SSH Username');
 
1082
                        $label_pass = __('FTP/SSH Password');
 
1083
                } else {
 
1084
                        _e('Please enter your FTP credentials to proceed.');
 
1085
                        $label_user = __('FTP Username');
 
1086
                        $label_pass = __('FTP Password');
 
1087
                }
 
1088
                echo ' ';
 
1089
        }
 
1090
        _e('If you do not remember your credentials, you should contact your web host.');
 
1091
?></p>
 
1092
<table class="form-table">
 
1093
<tr>
 
1094
<th scope="row"><label for="hostname"><?php _e('Hostname') ?></label></th>
 
1095
<td><input name="hostname" type="text" id="hostname" value="<?php echo esc_attr($hostname); if ( !empty($port) ) echo ":$port"; ?>"<?php disabled( defined('FTP_HOST') ); ?> size="40" /></td>
 
1096
</tr>
 
1097
 
 
1098
<tr>
 
1099
<th scope="row"><label for="username"><?php echo $label_user; ?></label></th>
 
1100
<td><input name="username" type="text" id="username" value="<?php echo esc_attr($username) ?>"<?php disabled( defined('FTP_USER') ); ?> size="40" /></td>
 
1101
</tr>
 
1102
 
 
1103
<tr>
 
1104
<th scope="row"><label for="password"><?php echo $label_pass; ?></label></th>
 
1105
<td><div><input name="password" type="password" id="password" value="<?php if ( defined('FTP_PASS') ) echo '*****'; ?>"<?php disabled( defined('FTP_PASS') ); ?> size="40" /></div>
 
1106
<div><em><?php if ( ! defined('FTP_PASS') ) _e( 'This password will not be stored on the server.' ); ?></em></div></td>
 
1107
</tr>
 
1108
 
 
1109
<?php if ( isset($types['ssh']) ) : ?>
 
1110
<tr id="ssh_keys" style="<?php if ( 'ssh' != $connection_type ) echo 'display:none' ?>">
 
1111
<th scope="row"><?php _e('Authentication Keys') ?>
 
1112
<div class="key-labels textright">
 
1113
<label for="public_key"><?php _e('Public Key:') ?></label ><br />
 
1114
<label for="private_key"><?php _e('Private Key:') ?></label>
 
1115
</div></th>
 
1116
<td><br /><input name="public_key" type="text" id="public_key" value="<?php echo esc_attr($public_key) ?>"<?php disabled( defined('FTP_PUBKEY') ); ?> size="40" />
 
1117
        <br /><input name="private_key" type="text" id="private_key" value="<?php echo esc_attr($private_key) ?>"<?php disabled( defined('FTP_PRIKEY') ); ?> size="40" />
 
1118
<div><?php _e('Enter the location on the server where the keys are located. If a passphrase is needed, enter that in the password field above.') ?></div></td>
 
1119
</tr>
 
1120
<?php endif; ?>
 
1121
 
 
1122
<tr>
 
1123
<th scope="row"><?php _e('Connection Type') ?></th>
 
1124
<td>
 
1125
<fieldset><legend class="screen-reader-text"><span><?php _e('Connection Type') ?></span></legend>
 
1126
<?php
 
1127
        $disabled = disabled( (defined('FTP_SSL') && FTP_SSL) || (defined('FTP_SSH') && FTP_SSH), true, false );
 
1128
        foreach ( $types as $name => $text ) : ?>
 
1129
        <label for="<?php echo esc_attr($name) ?>">
 
1130
                <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; ?> />
 
1131
                <?php echo $text ?>
 
1132
        </label>
 
1133
        <?php endforeach; ?>
 
1134
</fieldset>
 
1135
</td>
 
1136
</tr>
 
1137
</table>
 
1138
 
 
1139
<?php
 
1140
foreach ( (array) $extra_fields as $field ) {
 
1141
        if ( isset( $_POST[ $field ] ) )
 
1142
                echo '<input type="hidden" name="' . esc_attr( $field ) . '" value="' . esc_attr( wp_unslash( $_POST[ $field ] ) ) . '" />';
 
1143
}
 
1144
submit_button( __( 'Proceed' ), 'button', 'upgrade' );
 
1145
?>
 
1146
</div>
 
1147
</form>
 
1148
<?php
 
1149
        return false;
 
1150
}