PageRenderTime 47ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/wp-admin/includes/class-wp-filesystem-ftpext.php

http://github.com/wordpress/wordpress
PHP | 738 lines | 371 code | 74 blank | 293 comment | 90 complexity | 7f7e68b2b535b5a45058ad0d90e321ca MD5 | raw file
Possible License(s): 0BSD
  1. <?php
  2. /**
  3. * WordPress FTP Filesystem.
  4. *
  5. * @package WordPress
  6. * @subpackage Filesystem
  7. */
  8. /**
  9. * WordPress Filesystem Class for implementing FTP.
  10. *
  11. * @since 2.5.0
  12. *
  13. * @see WP_Filesystem_Base
  14. */
  15. class WP_Filesystem_FTPext extends WP_Filesystem_Base {
  16. /**
  17. * @since 2.5.0
  18. * @var resource
  19. */
  20. public $link;
  21. /**
  22. * Constructor.
  23. *
  24. * @since 2.5.0
  25. *
  26. * @param array $opt
  27. */
  28. public function __construct( $opt = '' ) {
  29. $this->method = 'ftpext';
  30. $this->errors = new WP_Error();
  31. // Check if possible to use ftp functions.
  32. if ( ! extension_loaded( 'ftp' ) ) {
  33. $this->errors->add( 'no_ftp_ext', __( 'The ftp PHP extension is not available' ) );
  34. return;
  35. }
  36. // This class uses the timeout on a per-connection basis, others use it on a per-action basis.
  37. if ( ! defined( 'FS_TIMEOUT' ) ) {
  38. define( 'FS_TIMEOUT', 240 );
  39. }
  40. if ( empty( $opt['port'] ) ) {
  41. $this->options['port'] = 21;
  42. } else {
  43. $this->options['port'] = $opt['port'];
  44. }
  45. if ( empty( $opt['hostname'] ) ) {
  46. $this->errors->add( 'empty_hostname', __( 'FTP hostname is required' ) );
  47. } else {
  48. $this->options['hostname'] = $opt['hostname'];
  49. }
  50. // Check if the options provided are OK.
  51. if ( empty( $opt['username'] ) ) {
  52. $this->errors->add( 'empty_username', __( 'FTP username is required' ) );
  53. } else {
  54. $this->options['username'] = $opt['username'];
  55. }
  56. if ( empty( $opt['password'] ) ) {
  57. $this->errors->add( 'empty_password', __( 'FTP password is required' ) );
  58. } else {
  59. $this->options['password'] = $opt['password'];
  60. }
  61. $this->options['ssl'] = false;
  62. if ( isset( $opt['connection_type'] ) && 'ftps' == $opt['connection_type'] ) {
  63. $this->options['ssl'] = true;
  64. }
  65. }
  66. /**
  67. * Connects filesystem.
  68. *
  69. * @since 2.5.0
  70. *
  71. * @return bool True on success, false on failure.
  72. */
  73. public function connect() {
  74. if ( isset( $this->options['ssl'] ) && $this->options['ssl'] && function_exists( 'ftp_ssl_connect' ) ) {
  75. $this->link = @ftp_ssl_connect( $this->options['hostname'], $this->options['port'], FS_CONNECT_TIMEOUT );
  76. } else {
  77. $this->link = @ftp_connect( $this->options['hostname'], $this->options['port'], FS_CONNECT_TIMEOUT );
  78. }
  79. if ( ! $this->link ) {
  80. $this->errors->add(
  81. 'connect',
  82. sprintf(
  83. /* translators: %s: hostname:port */
  84. __( 'Failed to connect to FTP Server %s' ),
  85. $this->options['hostname'] . ':' . $this->options['port']
  86. )
  87. );
  88. return false;
  89. }
  90. if ( ! @ftp_login( $this->link, $this->options['username'], $this->options['password'] ) ) {
  91. $this->errors->add(
  92. 'auth',
  93. sprintf(
  94. /* translators: %s: Username. */
  95. __( 'Username/Password incorrect for %s' ),
  96. $this->options['username']
  97. )
  98. );
  99. return false;
  100. }
  101. // Set the connection to use Passive FTP.
  102. ftp_pasv( $this->link, true );
  103. if ( @ftp_get_option( $this->link, FTP_TIMEOUT_SEC ) < FS_TIMEOUT ) {
  104. @ftp_set_option( $this->link, FTP_TIMEOUT_SEC, FS_TIMEOUT );
  105. }
  106. return true;
  107. }
  108. /**
  109. * Reads entire file into a string.
  110. *
  111. * @since 2.5.0
  112. *
  113. * @param string $file Name of the file to read.
  114. * @return string|false Read data on success, false if no temporary file could be opened,
  115. * or if the file couldn't be retrieved.
  116. */
  117. public function get_contents( $file ) {
  118. $tempfile = wp_tempnam( $file );
  119. $temp = fopen( $tempfile, 'w+' );
  120. if ( ! $temp ) {
  121. unlink( $tempfile );
  122. return false;
  123. }
  124. if ( ! ftp_fget( $this->link, $temp, $file, FTP_BINARY ) ) {
  125. fclose( $temp );
  126. unlink( $tempfile );
  127. return false;
  128. }
  129. fseek( $temp, 0 ); // Skip back to the start of the file being written to.
  130. $contents = '';
  131. while ( ! feof( $temp ) ) {
  132. $contents .= fread( $temp, 8 * KB_IN_BYTES );
  133. }
  134. fclose( $temp );
  135. unlink( $tempfile );
  136. return $contents;
  137. }
  138. /**
  139. * Reads entire file into an array.
  140. *
  141. * @since 2.5.0
  142. *
  143. * @param string $file Path to the file.
  144. * @return array|false File contents in an array on success, false on failure.
  145. */
  146. public function get_contents_array( $file ) {
  147. return explode( "\n", $this->get_contents( $file ) );
  148. }
  149. /**
  150. * Writes a string to a file.
  151. *
  152. * @since 2.5.0
  153. *
  154. * @param string $file Remote path to the file where to write the data.
  155. * @param string $contents The data to write.
  156. * @param int|false $mode Optional. The file permissions as octal number, usually 0644.
  157. * Default false.
  158. * @return bool True on success, false on failure.
  159. */
  160. public function put_contents( $file, $contents, $mode = false ) {
  161. $tempfile = wp_tempnam( $file );
  162. $temp = fopen( $tempfile, 'wb+' );
  163. if ( ! $temp ) {
  164. unlink( $tempfile );
  165. return false;
  166. }
  167. mbstring_binary_safe_encoding();
  168. $data_length = strlen( $contents );
  169. $bytes_written = fwrite( $temp, $contents );
  170. reset_mbstring_encoding();
  171. if ( $data_length !== $bytes_written ) {
  172. fclose( $temp );
  173. unlink( $tempfile );
  174. return false;
  175. }
  176. fseek( $temp, 0 ); // Skip back to the start of the file being written to.
  177. $ret = ftp_fput( $this->link, $file, $temp, FTP_BINARY );
  178. fclose( $temp );
  179. unlink( $tempfile );
  180. $this->chmod( $file, $mode );
  181. return $ret;
  182. }
  183. /**
  184. * Gets the current working directory.
  185. *
  186. * @since 2.5.0
  187. *
  188. * @return string|false The current working directory on success, false on failure.
  189. */
  190. public function cwd() {
  191. $cwd = ftp_pwd( $this->link );
  192. if ( $cwd ) {
  193. $cwd = trailingslashit( $cwd );
  194. }
  195. return $cwd;
  196. }
  197. /**
  198. * Changes current directory.
  199. *
  200. * @since 2.5.0
  201. *
  202. * @param string $dir The new current directory.
  203. * @return bool True on success, false on failure.
  204. */
  205. public function chdir( $dir ) {
  206. return @ftp_chdir( $this->link, $dir );
  207. }
  208. /**
  209. * Changes filesystem permissions.
  210. *
  211. * @since 2.5.0
  212. *
  213. * @param string $file Path to the file.
  214. * @param int|false $mode Optional. The permissions as octal number, usually 0644 for files,
  215. * 0755 for directories. Default false.
  216. * @param bool $recursive Optional. If set to true, changes file permissions recursively.
  217. * Default false.
  218. * @return bool True on success, false on failure.
  219. */
  220. public function chmod( $file, $mode = false, $recursive = false ) {
  221. if ( ! $mode ) {
  222. if ( $this->is_file( $file ) ) {
  223. $mode = FS_CHMOD_FILE;
  224. } elseif ( $this->is_dir( $file ) ) {
  225. $mode = FS_CHMOD_DIR;
  226. } else {
  227. return false;
  228. }
  229. }
  230. // chmod any sub-objects if recursive.
  231. if ( $recursive && $this->is_dir( $file ) ) {
  232. $filelist = $this->dirlist( $file );
  233. foreach ( (array) $filelist as $filename => $filemeta ) {
  234. $this->chmod( $file . '/' . $filename, $mode, $recursive );
  235. }
  236. }
  237. // chmod the file or directory.
  238. if ( ! function_exists( 'ftp_chmod' ) ) {
  239. return (bool) ftp_site( $this->link, sprintf( 'CHMOD %o %s', $mode, $file ) );
  240. }
  241. return (bool) ftp_chmod( $this->link, $mode, $file );
  242. }
  243. /**
  244. * Gets the file owner.
  245. *
  246. * @since 2.5.0
  247. *
  248. * @param string $file Path to the file.
  249. * @return string|false Username of the owner on success, false on failure.
  250. */
  251. public function owner( $file ) {
  252. $dir = $this->dirlist( $file );
  253. return $dir[ $file ]['owner'];
  254. }
  255. /**
  256. * Gets the permissions of the specified file or filepath in their octal format.
  257. *
  258. * @since 2.5.0
  259. *
  260. * @param string $file Path to the file.
  261. * @return string Mode of the file (the last 3 digits).
  262. */
  263. public function getchmod( $file ) {
  264. $dir = $this->dirlist( $file );
  265. return $dir[ $file ]['permsn'];
  266. }
  267. /**
  268. * Gets the file's group.
  269. *
  270. * @since 2.5.0
  271. *
  272. * @param string $file Path to the file.
  273. * @return string|false The group on success, false on failure.
  274. */
  275. public function group( $file ) {
  276. $dir = $this->dirlist( $file );
  277. return $dir[ $file ]['group'];
  278. }
  279. /**
  280. * Copies a file.
  281. *
  282. * @since 2.5.0
  283. *
  284. * @param string $source Path to the source file.
  285. * @param string $destination Path to the destination file.
  286. * @param bool $overwrite Optional. Whether to overwrite the destination file if it exists.
  287. * Default false.
  288. * @param int|false $mode Optional. The permissions as octal number, usually 0644 for files,
  289. * 0755 for dirs. Default false.
  290. * @return bool True on success, false on failure.
  291. */
  292. public function copy( $source, $destination, $overwrite = false, $mode = false ) {
  293. if ( ! $overwrite && $this->exists( $destination ) ) {
  294. return false;
  295. }
  296. $content = $this->get_contents( $source );
  297. if ( false === $content ) {
  298. return false;
  299. }
  300. return $this->put_contents( $destination, $content, $mode );
  301. }
  302. /**
  303. * Moves a file.
  304. *
  305. * @since 2.5.0
  306. *
  307. * @param string $source Path to the source file.
  308. * @param string $destination Path to the destination file.
  309. * @param bool $overwrite Optional. Whether to overwrite the destination file if it exists.
  310. * Default false.
  311. * @return bool True on success, false on failure.
  312. */
  313. public function move( $source, $destination, $overwrite = false ) {
  314. return ftp_rename( $this->link, $source, $destination );
  315. }
  316. /**
  317. * Deletes a file or directory.
  318. *
  319. * @since 2.5.0
  320. *
  321. * @param string $file Path to the file or directory.
  322. * @param bool $recursive Optional. If set to true, deletes files and folders recursively.
  323. * Default false.
  324. * @param string|false $type Type of resource. 'f' for file, 'd' for directory.
  325. * Default false.
  326. * @return bool True on success, false on failure.
  327. */
  328. public function delete( $file, $recursive = false, $type = false ) {
  329. if ( empty( $file ) ) {
  330. return false;
  331. }
  332. if ( 'f' == $type || $this->is_file( $file ) ) {
  333. return ftp_delete( $this->link, $file );
  334. }
  335. if ( ! $recursive ) {
  336. return ftp_rmdir( $this->link, $file );
  337. }
  338. $filelist = $this->dirlist( trailingslashit( $file ) );
  339. if ( ! empty( $filelist ) ) {
  340. foreach ( $filelist as $delete_file ) {
  341. $this->delete( trailingslashit( $file ) . $delete_file['name'], $recursive, $delete_file['type'] );
  342. }
  343. }
  344. return ftp_rmdir( $this->link, $file );
  345. }
  346. /**
  347. * Checks if a file or directory exists.
  348. *
  349. * @since 2.5.0
  350. *
  351. * @param string $file Path to file or directory.
  352. * @return bool Whether $file exists or not.
  353. */
  354. public function exists( $file ) {
  355. $list = ftp_nlist( $this->link, $file );
  356. if ( empty( $list ) && $this->is_dir( $file ) ) {
  357. return true; // File is an empty directory.
  358. }
  359. return ! empty( $list ); // Empty list = no file, so invert.
  360. }
  361. /**
  362. * Checks if resource is a file.
  363. *
  364. * @since 2.5.0
  365. *
  366. * @param string $file File path.
  367. * @return bool Whether $file is a file.
  368. */
  369. public function is_file( $file ) {
  370. return $this->exists( $file ) && ! $this->is_dir( $file );
  371. }
  372. /**
  373. * Checks if resource is a directory.
  374. *
  375. * @since 2.5.0
  376. *
  377. * @param string $path Directory path.
  378. * @return bool Whether $path is a directory.
  379. */
  380. public function is_dir( $path ) {
  381. $cwd = $this->cwd();
  382. $result = @ftp_chdir( $this->link, trailingslashit( $path ) );
  383. if ( $result && $path == $this->cwd() || $this->cwd() != $cwd ) {
  384. @ftp_chdir( $this->link, $cwd );
  385. return true;
  386. }
  387. return false;
  388. }
  389. /**
  390. * Checks if a file is readable.
  391. *
  392. * @since 2.5.0
  393. *
  394. * @param string $file Path to file.
  395. * @return bool Whether $file is readable.
  396. */
  397. public function is_readable( $file ) {
  398. return true;
  399. }
  400. /**
  401. * Checks if a file or directory is writable.
  402. *
  403. * @since 2.5.0
  404. *
  405. * @param string $file Path to file or directory.
  406. * @return bool Whether $file is writable.
  407. */
  408. public function is_writable( $file ) {
  409. return true;
  410. }
  411. /**
  412. * Gets the file's last access time.
  413. *
  414. * @since 2.5.0
  415. *
  416. * @param string $file Path to file.
  417. * @return int|false Unix timestamp representing last access time, false on failure.
  418. */
  419. public function atime( $file ) {
  420. return false;
  421. }
  422. /**
  423. * Gets the file modification time.
  424. *
  425. * @since 2.5.0
  426. *
  427. * @param string $file Path to file.
  428. * @return int|false Unix timestamp representing modification time, false on failure.
  429. */
  430. public function mtime( $file ) {
  431. return ftp_mdtm( $this->link, $file );
  432. }
  433. /**
  434. * Gets the file size (in bytes).
  435. *
  436. * @since 2.5.0
  437. *
  438. * @param string $file Path to file.
  439. * @return int|false Size of the file in bytes on success, false on failure.
  440. */
  441. public function size( $file ) {
  442. return ftp_size( $this->link, $file );
  443. }
  444. /**
  445. * Sets the access and modification times of a file.
  446. *
  447. * Note: If $file doesn't exist, it will be created.
  448. *
  449. * @since 2.5.0
  450. *
  451. * @param string $file Path to file.
  452. * @param int $time Optional. Modified time to set for file.
  453. * Default 0.
  454. * @param int $atime Optional. Access time to set for file.
  455. * Default 0.
  456. * @return bool True on success, false on failure.
  457. */
  458. public function touch( $file, $time = 0, $atime = 0 ) {
  459. return false;
  460. }
  461. /**
  462. * Creates a directory.
  463. *
  464. * @since 2.5.0
  465. *
  466. * @param string $path Path for new directory.
  467. * @param int|false $chmod Optional. The permissions as octal number (or false to skip chmod).
  468. * Default false.
  469. * @param string|int $chown Optional. A user name or number (or false to skip chown).
  470. * Default false.
  471. * @param string|int $chgrp Optional. A group name or number (or false to skip chgrp).
  472. * Default false.
  473. * @return bool True on success, false on failure.
  474. */
  475. public function mkdir( $path, $chmod = false, $chown = false, $chgrp = false ) {
  476. $path = untrailingslashit( $path );
  477. if ( empty( $path ) ) {
  478. return false;
  479. }
  480. if ( ! ftp_mkdir( $this->link, $path ) ) {
  481. return false;
  482. }
  483. $this->chmod( $path, $chmod );
  484. return true;
  485. }
  486. /**
  487. * Deletes a directory.
  488. *
  489. * @since 2.5.0
  490. *
  491. * @param string $path Path to directory.
  492. * @param bool $recursive Optional. Whether to recursively remove files/directories.
  493. * Default false.
  494. * @return bool True on success, false on failure.
  495. */
  496. public function rmdir( $path, $recursive = false ) {
  497. return $this->delete( $path, $recursive );
  498. }
  499. /**
  500. * @staticvar bool $is_windows
  501. * @param string $line
  502. * @return array
  503. */
  504. public function parselisting( $line ) {
  505. static $is_windows = null;
  506. if ( is_null( $is_windows ) ) {
  507. $is_windows = stripos( ftp_systype( $this->link ), 'win' ) !== false;
  508. }
  509. if ( $is_windows && preg_match( '/([0-9]{2})-([0-9]{2})-([0-9]{2}) +([0-9]{2}):([0-9]{2})(AM|PM) +([0-9]+|<DIR>) +(.+)/', $line, $lucifer ) ) {
  510. $b = array();
  511. if ( $lucifer[3] < 70 ) {
  512. $lucifer[3] += 2000;
  513. } else {
  514. $lucifer[3] += 1900; // 4-digit year fix.
  515. }
  516. $b['isdir'] = ( '<DIR>' === $lucifer[7] );
  517. if ( $b['isdir'] ) {
  518. $b['type'] = 'd';
  519. } else {
  520. $b['type'] = 'f';
  521. }
  522. $b['size'] = $lucifer[7];
  523. $b['month'] = $lucifer[1];
  524. $b['day'] = $lucifer[2];
  525. $b['year'] = $lucifer[3];
  526. $b['hour'] = $lucifer[4];
  527. $b['minute'] = $lucifer[5];
  528. $b['time'] = mktime( $lucifer[4] + ( strcasecmp( $lucifer[6], 'PM' ) == 0 ? 12 : 0 ), $lucifer[5], 0, $lucifer[1], $lucifer[2], $lucifer[3] );
  529. $b['am/pm'] = $lucifer[6];
  530. $b['name'] = $lucifer[8];
  531. } elseif ( ! $is_windows ) {
  532. $lucifer = preg_split( '/[ ]/', $line, 9, PREG_SPLIT_NO_EMPTY );
  533. if ( $lucifer ) {
  534. // echo $line."\n";
  535. $lcount = count( $lucifer );
  536. if ( $lcount < 8 ) {
  537. return '';
  538. }
  539. $b = array();
  540. $b['isdir'] = 'd' === $lucifer[0][0];
  541. $b['islink'] = 'l' === $lucifer[0][0];
  542. if ( $b['isdir'] ) {
  543. $b['type'] = 'd';
  544. } elseif ( $b['islink'] ) {
  545. $b['type'] = 'l';
  546. } else {
  547. $b['type'] = 'f';
  548. }
  549. $b['perms'] = $lucifer[0];
  550. $b['permsn'] = $this->getnumchmodfromh( $b['perms'] );
  551. $b['number'] = $lucifer[1];
  552. $b['owner'] = $lucifer[2];
  553. $b['group'] = $lucifer[3];
  554. $b['size'] = $lucifer[4];
  555. if ( 8 == $lcount ) {
  556. sscanf( $lucifer[5], '%d-%d-%d', $b['year'], $b['month'], $b['day'] );
  557. sscanf( $lucifer[6], '%d:%d', $b['hour'], $b['minute'] );
  558. $b['time'] = mktime( $b['hour'], $b['minute'], 0, $b['month'], $b['day'], $b['year'] );
  559. $b['name'] = $lucifer[7];
  560. } else {
  561. $b['month'] = $lucifer[5];
  562. $b['day'] = $lucifer[6];
  563. if ( preg_match( '/([0-9]{2}):([0-9]{2})/', $lucifer[7], $l2 ) ) {
  564. $b['year'] = gmdate( 'Y' );
  565. $b['hour'] = $l2[1];
  566. $b['minute'] = $l2[2];
  567. } else {
  568. $b['year'] = $lucifer[7];
  569. $b['hour'] = 0;
  570. $b['minute'] = 0;
  571. }
  572. $b['time'] = strtotime( sprintf( '%d %s %d %02d:%02d', $b['day'], $b['month'], $b['year'], $b['hour'], $b['minute'] ) );
  573. $b['name'] = $lucifer[8];
  574. }
  575. }
  576. }
  577. // Replace symlinks formatted as "source -> target" with just the source name.
  578. if ( isset( $b['islink'] ) && $b['islink'] ) {
  579. $b['name'] = preg_replace( '/(\s*->\s*.*)$/', '', $b['name'] );
  580. }
  581. return $b;
  582. }
  583. /**
  584. * Gets details for files in a directory or a specific file.
  585. *
  586. * @since 2.5.0
  587. *
  588. * @param string $path Path to directory or file.
  589. * @param bool $include_hidden Optional. Whether to include details of hidden ("." prefixed) files.
  590. * Default true.
  591. * @param bool $recursive Optional. Whether to recursively include file details in nested directories.
  592. * Default false.
  593. * @return array|false {
  594. * Array of files. False if unable to list directory contents.
  595. *
  596. * @type string $name Name of the file or directory.
  597. * @type string $perms *nix representation of permissions.
  598. * @type int $permsn Octal representation of permissions.
  599. * @type string $owner Owner name or ID.
  600. * @type int $size Size of file in bytes.
  601. * @type int $lastmodunix Last modified unix timestamp.
  602. * @type mixed $lastmod Last modified month (3 letter) and day (without leading 0).
  603. * @type int $time Last modified time.
  604. * @type string $type Type of resource. 'f' for file, 'd' for directory.
  605. * @type mixed $files If a directory and $recursive is true, contains another array of files.
  606. * }
  607. */
  608. public function dirlist( $path = '.', $include_hidden = true, $recursive = false ) {
  609. if ( $this->is_file( $path ) ) {
  610. $limit_file = basename( $path );
  611. $path = dirname( $path ) . '/';
  612. } else {
  613. $limit_file = false;
  614. }
  615. $pwd = ftp_pwd( $this->link );
  616. if ( ! @ftp_chdir( $this->link, $path ) ) { // Can't change to folder = folder doesn't exist.
  617. return false;
  618. }
  619. $list = ftp_rawlist( $this->link, '-a', false );
  620. @ftp_chdir( $this->link, $pwd );
  621. if ( empty( $list ) ) { // Empty array = non-existent folder (real folder will show . at least).
  622. return false;
  623. }
  624. $dirlist = array();
  625. foreach ( $list as $k => $v ) {
  626. $entry = $this->parselisting( $v );
  627. if ( empty( $entry ) ) {
  628. continue;
  629. }
  630. if ( '.' == $entry['name'] || '..' == $entry['name'] ) {
  631. continue;
  632. }
  633. if ( ! $include_hidden && '.' == $entry['name'][0] ) {
  634. continue;
  635. }
  636. if ( $limit_file && $entry['name'] != $limit_file ) {
  637. continue;
  638. }
  639. $dirlist[ $entry['name'] ] = $entry;
  640. }
  641. $ret = array();
  642. foreach ( (array) $dirlist as $struc ) {
  643. if ( 'd' == $struc['type'] ) {
  644. if ( $recursive ) {
  645. $struc['files'] = $this->dirlist( $path . '/' . $struc['name'], $include_hidden, $recursive );
  646. } else {
  647. $struc['files'] = array();
  648. }
  649. }
  650. $ret[ $struc['name'] ] = $struc;
  651. }
  652. return $ret;
  653. }
  654. /**
  655. * Destructor.
  656. *
  657. * @since 2.5.0
  658. */
  659. public function __destruct() {
  660. if ( $this->link ) {
  661. ftp_close( $this->link );
  662. }
  663. }
  664. }