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