PageRenderTime 65ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 1ms

/web/wordpress/wp-admin/includes/file.php

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