PageRenderTime 46ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 1ms

/extensions/Deployment/includes/filesystems/FtpFilesystem.php

https://github.com/ChuguluGames/mediawiki-svn
PHP | 614 lines | 382 code | 105 blank | 127 comment | 78 complexity | 3c37b8e72ef5b1e7e2bf74e129055f93 MD5 | raw file
  1. <?php
  2. /**
  3. * File holding the FtpFilesystem class.
  4. *
  5. * @file FtpFilesystem.php
  6. * @ingroup Deployment
  7. * @ingroup Filesystem
  8. *
  9. * @author Jeroen De Dauw
  10. */
  11. if ( !defined( 'MEDIAWIKI' ) ) {
  12. die( 'Not an entry point.' );
  13. }
  14. /**
  15. * Filesystem class for file and folder manipulation over FTP.
  16. *
  17. * @author Jeroen De Dauw
  18. */
  19. class FtpFilesystem extends Filesystem {
  20. /**
  21. * A list of options.
  22. *
  23. * @var array
  24. */
  25. protected $options = array();
  26. /**
  27. * The FTP connection link.
  28. *
  29. * @var FTP resource or false
  30. */
  31. protected $connection = false;
  32. /**
  33. * Constructor.
  34. *
  35. * @param array $options
  36. */
  37. public function __construct( array $options ) {
  38. parent::__construct();
  39. // Check if possible to use ftp functions.
  40. if ( !extension_loaded( 'ftp' ) ) {
  41. $this->addError( 'deploy-ftp-not-loaded' );
  42. return false;
  43. }
  44. // Check for missing required options.
  45. if ( !array_key_exists( 'username', $options ) ) {
  46. $this->addError( 'deploy-ftp-username-required' );
  47. }
  48. if ( !array_key_exists( 'password', $options ) ) {
  49. $this->addError( 'deploy-ftp-password-required' );
  50. }
  51. if ( !array_key_exists( 'hostname', $options ) ) {
  52. $this->addError( 'deploy-ftp-hostname-required' );
  53. }
  54. // Set default option values for those not provided.
  55. if ( !array_key_exists( 'port', $options ) ) {
  56. $options['port'] = 21;
  57. }
  58. if ( !array_key_exists( 'timeout', $options ) ) {
  59. $options['timeout'] = 240;
  60. }
  61. // Other option handling.
  62. $options['ssl'] = array_key_exists( 'connection_type', $options ) && $options['connection_type'] == 'ftps';
  63. // Store the options.
  64. $this->options = $options;
  65. }
  66. /**
  67. * @see Filesystem::connect
  68. */
  69. public function connect() {
  70. // Attempt to create a connection, either with ssl or without.
  71. if ( $this->options['ssl'] && function_exists( 'ftp_ssl_connect' ) ) {
  72. wfSuppressWarnings();
  73. $this->connection = ftp_ssl_connect( $this->options['hostname'], $this->options['port'], $this->options['timeout'] );
  74. wfRestoreWarnings();
  75. }
  76. else {
  77. // If this is true, ftp_ssl_connect was not defined, so add an error.
  78. if ( $this->options['ssl'] ) {
  79. $this->addError( 'deploy-ftp-ssl-not-loaded' );
  80. }
  81. wfSuppressWarnings();
  82. $this->connection = ftp_connect( $this->options['hostname'], $this->options['port'], $this->options['timeout'] );
  83. wfRestoreWarnings();
  84. }
  85. // Check if a connection has been established.
  86. if ( !$this->connection ) {
  87. $this->addErrorMessage( wfMsgExt( 'deploy-ftp-connect-failed', 'parsemag', $this->options['hostname'], $this->options['port'] ) );
  88. return false;
  89. }
  90. // Attempt to set the connection to use passive FTP.
  91. wfSuppressWarnings();
  92. ftp_pasv( $this->connection, true );
  93. wfRestoreWarnings();
  94. // Make sure the timeout is at least as much as the option.
  95. wfSuppressWarnings();
  96. if ( ftp_get_option( $this->connection, FTP_TIMEOUT_SEC ) < $this->options['timeout'] ) {
  97. ftp_set_option( $this->connection, FTP_TIMEOUT_SEC, $this->options['timeout'] );
  98. }
  99. wfRestoreWarnings();
  100. return true;
  101. }
  102. /**
  103. * @see Filesystem::changeDir
  104. */
  105. public function changeDir( $dir ) {
  106. wfSuppressWarnings();
  107. $result = (bool)ftp_chdir( $this->connection, $dir );
  108. wfRestoreWarnings();
  109. return $result;
  110. }
  111. /**
  112. * @see Filesystem::changeFileGroup
  113. */
  114. public function changeFileGroup( $file, $group, $recursive = false ) {
  115. return false;
  116. }
  117. /**
  118. * @see Filesystem::chmod
  119. */
  120. public function chmod( $file, $mode = false, $recursive = false ) {
  121. // TODO: refactor up?
  122. if ( !$mode ) {
  123. if ( $this->isFile( $file ) ) {
  124. $mode = FS_CHMOD_FILE;
  125. }
  126. elseif ( $this->isDir( $file ) ) {
  127. $mode = FS_CHMOD_DIR;
  128. }
  129. else {
  130. return false;
  131. }
  132. }
  133. // Not recursive, so just use chmod.
  134. if ( !$recursive || !$this->isDir( $file ) ) {
  135. if ( !function_exists( 'ftp_chmod' ) ) {
  136. wfSuppressWarnings();
  137. $result = (bool)ftp_site( $this->connection, sprintf( 'CHMOD %o %s', $mode, $file ) );
  138. wfRestoreWarnings();
  139. return $result;
  140. }
  141. else {
  142. wfSuppressWarnings();
  143. $result = (bool)ftp_chmod( $this->connection, $mode, $file );
  144. wfRestoreWarnings();
  145. return $result;
  146. }
  147. }
  148. // Recursive approach required.
  149. $file = rtrim( $file, '/' ) . '/';
  150. $files = $this->listDir( $file );
  151. foreach ( $files as $fileName ) {
  152. $this->chmod( $file . $fileName, $mode, $recursive );
  153. }
  154. return true;
  155. }
  156. /**
  157. * @see Filesystem::chown
  158. */
  159. public function chown( $file, $owner, $recursive = false ) {
  160. return false;
  161. }
  162. /**
  163. * @see Filesystem::delete
  164. */
  165. public function delete( $path, $recursive = false ) {
  166. if ( empty( $path ) ) {
  167. return false;
  168. }
  169. if ( $this->isFile( $path ) ) {
  170. wfSuppressWarnings();
  171. $result = (bool)ftp_delete( $this->connection, $path );
  172. wfRestoreWarnings();
  173. return $result;
  174. }
  175. if ( !$recursive ) {
  176. wfSuppressWarnings();
  177. $result = (bool)ftp_rmdir( $this->connection, $path );
  178. wfRestoreWarnings();
  179. return $result;
  180. }
  181. // Recursive approach required.
  182. $path = rtrim( $path, '/' ) . '/';
  183. $files = $this->listDir( $path );
  184. $success = true;
  185. foreach ( $files as $fileName ) {
  186. if ( !$this->delete( $path . $fileName, $recursive ) ) {
  187. $success = false;
  188. }
  189. }
  190. if ( $success && $this->exists( $path ) ) {
  191. wfSuppressWarnings();
  192. $ftp_rmdir = ftp_rmdir( $this->connection, $path );
  193. wfRestoreWarnings();
  194. if ( !$ftp_rmdir ) {
  195. $success = false;
  196. }
  197. }
  198. return $success;
  199. }
  200. /**
  201. * @see Filesystem::doCopy
  202. */
  203. protected function doCopy( $from, $to ) {
  204. $content = $this->getContents( $from );
  205. if ( $content === false ) {
  206. return false;
  207. }
  208. return $this->writeToFile( $to, $content );
  209. }
  210. /**
  211. * @see Filesystem::doMove
  212. */
  213. protected function doMove( $from, $to, $overwrite ) {
  214. wfSuppressWarnings();
  215. $result = (bool)ftp_rename( $this->connection, $from, $to );
  216. wfRestoreWarnings();
  217. return $result;
  218. }
  219. /**
  220. * @see Filesystem::exists
  221. */
  222. public function exists( $file ) {
  223. wfSuppressWarnings();
  224. $list = ftp_nlist( $this->connection, $file );
  225. wfRestoreWarnings();
  226. return !empty( $list );
  227. }
  228. /**
  229. * @see Filesystem::getChmod
  230. */
  231. public function getChmod( $file ) {
  232. $dir = $this->listDir( $file );
  233. return $dir[$file]['permsn'];
  234. }
  235. /**
  236. * @see Filesystem::getContents
  237. */
  238. public function getContents( $file ) {
  239. $type = FTP_BINARY;
  240. // TODO: port wp_tempnam
  241. die( __METHOD__ . ' TODO: port wp_tempnam' );
  242. $tempFileName = wp_tempnam( $file );
  243. $temp = fopen( $tempFileName , 'w+' );
  244. if ( !$temp ) {
  245. return false;
  246. }
  247. wfSuppressWarnings();
  248. $ftp_fget = ftp_fget( $this->connection, $temp, $file, $type );
  249. wfRestoreWarnings();
  250. if ( !$ftp_fget ) {
  251. return false;
  252. }
  253. // Skip back to the start of the file being written to.
  254. fseek( $temp, 0 );
  255. $contents = array();
  256. while ( !feof( $temp ) ) {
  257. $contents[] = fread( $temp, 8192 );
  258. }
  259. fclose( $temp );
  260. unlink( $tempFileName );
  261. return implode( '', $contents );
  262. }
  263. /**
  264. * @see Filesystem::getCurrentWorkingDir
  265. */
  266. public function getCurrentWorkingDir() {
  267. wfSuppressWarnings();
  268. $result = ftp_pwd( $this->connection );
  269. wfRestoreWarnings();
  270. return $result;
  271. }
  272. /**
  273. * @see Filesystem::getGroup
  274. */
  275. public function getGroup( $file ) {
  276. $dir = $this->listDir( $file );
  277. return $dir[$file]['group'];
  278. }
  279. /**
  280. * @see Filesystem::getModificationTime
  281. */
  282. public function getModificationTime( $file ) {
  283. return ftp_mdtm( $this->connection, $file );
  284. }
  285. /**
  286. * @see Filesystem::getOwner
  287. */
  288. public function getOwner( $file ) {
  289. $dir = $this->listDir( $file );
  290. return $dir[$file]['owner'];
  291. }
  292. /**
  293. * @see Filesystem::getSize
  294. */
  295. public function getSize( $file ) {
  296. return ftp_size( $this->connection, $file );
  297. }
  298. /**
  299. * @see Filesystem::isDir
  300. */
  301. public function isDir( $path ) {
  302. $cwd = $this->getCurrentWorkingDir();
  303. wfSuppressWarnings();
  304. $result = ftp_chdir( $this->connection, rtrim( $path, '/' ) . '/' );
  305. wfRestoreWarnings();
  306. if ( $result && $path == $this->getCurrentWorkingDir() || $this->getCurrentWorkingDir() != $cwd ) {
  307. wfSuppressWarnings();
  308. ftp_chdir( $this->connection, $cwd );
  309. wfRestoreWarnings();
  310. return true;
  311. }
  312. return false;
  313. }
  314. /**
  315. * @see Filesystem::isFile
  316. */
  317. public function isFile( $path ) {
  318. return $this->exists( $path ) && !$this->isDir( $path );
  319. }
  320. /**
  321. * @see Filesystem::isReadable
  322. *
  323. * TODO
  324. */
  325. public function isReadable( $file ) {
  326. return true;
  327. }
  328. /**
  329. * @see Filesystem::isWritable
  330. *
  331. * TODO
  332. */
  333. public function isWritable( $file ) {
  334. return true;
  335. }
  336. /**
  337. * @see Filesystem::listDir
  338. */
  339. public function listDir( $path, $includeHidden = true, $recursive = false ) {
  340. if ( $this->isFile( $path ) ) {
  341. $limit_file = basename( $path );
  342. $path = dirname( $path );
  343. }
  344. else {
  345. $limit_file = false;
  346. }
  347. wfSuppressWarnings();
  348. $ftp_chdir = ftp_chdir( $this->connection, $path );
  349. wfRestoreWarnings();
  350. if ( !$ftp_chdir ) {
  351. return false;
  352. }
  353. wfSuppressWarnings();
  354. $pwd = ftp_pwd( $this->connection );
  355. $list = ftp_rawlist( $this->connection, '-a', false );
  356. ftp_chdir( $this->connection, $pwd );
  357. wfRestoreWarnings();
  358. if ( empty( $list ) ) {
  359. return false;
  360. }
  361. $dirlist = array();
  362. foreach ( $list as $k => $v ) {
  363. $entry = $this->parseListing( $v );
  364. if ( empty( $entry )
  365. || ( '.' == $entry['name'] || '..' == $entry['name'] )
  366. || ( !$includeHidden && '.' == $entry['name'][0] )
  367. || ( $limit_file && $entry['name'] != $limit_file ) ) {
  368. continue;
  369. }
  370. $dirlist[ $entry['name'] ] = $entry;
  371. }
  372. $ret = array();
  373. foreach ( (array)$dirlist as $struc ) {
  374. if ( 'd' == $struc['type'] ) {
  375. if ( $recursive ) {
  376. $struc['files'] = $this->listDir( $path . '/' . $struc['name'], $includeHidden, $recursive );
  377. }
  378. else {
  379. $struc['files'] = array();
  380. }
  381. }
  382. $ret[$struc['name']] = $struc;
  383. }
  384. return $ret;
  385. }
  386. /**
  387. * @see Filesystem::makeDir
  388. */
  389. public function makeDir( $path, $chmod = false, $chown = false, $chgrp = false ) {
  390. wfSuppressWarnings();
  391. $ftp_mkdir = ftp_mkdir( $this->connection, $path );
  392. wfRestoreWarnings();
  393. if ( !$ftp_mkdir ) {
  394. return false;
  395. }
  396. $this->chmod( $path, $chmod );
  397. if ( $chown ) {
  398. $this->chown( $path, $chown );
  399. }
  400. if ( $chgrp ) {
  401. $this->changeFileGroup( $path, $chgrp );
  402. }
  403. return true;
  404. }
  405. /**
  406. * @see Filesystem::touch
  407. */
  408. public function touch( $file, $time = 0, $atime = 0 ) {
  409. return false;
  410. }
  411. /**
  412. * @see Filesystem::writeToFile
  413. */
  414. public function writeToFile( $file, $contents, $mode = false ) {
  415. $tempfile = wp_tempnam( $file );
  416. $temp = fopen( $tempfile, 'w+' );
  417. if ( !$temp ) {
  418. return false;
  419. }
  420. fwrite( $temp, $contents );
  421. // Skip back to the start of the file being written to
  422. fseek( $temp, 0 );
  423. wfSuppressWarnings();
  424. $ret = ftp_fput( $this->connection, $file, $temp, $this->isBinary( $contents ) ? FTP_BINARY : FTP_ASCII );
  425. wfRestoreWarnings();
  426. fclose( $temp );
  427. unlink( $tempfile );
  428. $this->chmod( $file, $mode );
  429. return $ret;
  430. }
  431. /**
  432. * Function copied from wp-admin/includes/class-wp-filesystem-ftpext.php in WP 3.0.
  433. * Only made it conform to the general MW guidelines, might still be messy at places though.
  434. */
  435. protected function parseListing( $line ) {
  436. static $is_windows;
  437. if ( is_null( $is_windows ) ) {
  438. $is_windows = stripos( ftp_systype( $this->connection ), 'win' ) !== false;
  439. }
  440. 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 ) ) {
  441. $b = array();
  442. if ( $lucifer[3] < 70 ) {
  443. $lucifer[3] += 2000;
  444. }
  445. else {
  446. $lucifer[3] += 1900; // 4 digit year fix
  447. }
  448. $b['isdir'] = ( $lucifer[7] == '<DIR>' );
  449. $b['type'] = $b['isdir'] ? 'd' : 'f';
  450. $b['size'] = $lucifer[7];
  451. $b['month'] = $lucifer[1];
  452. $b['day'] = $lucifer[2];
  453. $b['year'] = $lucifer[3];
  454. $b['hour'] = $lucifer[4];
  455. $b['minute'] = $lucifer[5];
  456. wfSuppressWarnings();
  457. $b['time'] = mktime( $lucifer[4] + (strcasecmp($lucifer[6], 'PM' ) == 0 ? 12 : 0), $lucifer[5], 0, $lucifer[1], $lucifer[2], $lucifer[3] );
  458. wfRestoreWarnings();
  459. $b['am/pm'] = $lucifer[6];
  460. $b['name'] = $lucifer[8];
  461. } elseif ( !$is_windows && $lucifer = preg_split( '/[ ]/', $line, 9, PREG_SPLIT_NO_EMPTY ) ) {
  462. //echo $line."\n";
  463. $lcount = count($lucifer);
  464. if ( $lcount < 8 ) {
  465. return '';
  466. }
  467. $b = array();
  468. $b['isdir'] = $lucifer[0]{0} === 'd';
  469. $b['islink'] = $lucifer[0]{0} === 'l';
  470. if ( $b['isdir'] ) {
  471. $b['type'] = 'd';
  472. }
  473. elseif ( $b['islink'] ) {
  474. $b['type'] = 'l';
  475. }
  476. else {
  477. $b['type'] = 'f';
  478. }
  479. $b['perms'] = $lucifer[0];
  480. $b['number'] = $lucifer[1];
  481. $b['owner'] = $lucifer[2];
  482. $b['group'] = $lucifer[3];
  483. $b['size'] = $lucifer[4];
  484. if ( $lcount == 8 ) {
  485. sscanf( $lucifer[5], '%d-%d-%d', $b['year'], $b['month'], $b['day'] );
  486. sscanf( $lucifer[6], '%d:%d', $b['hour'], $b['minute'] );
  487. wfSuppressWarnings();
  488. $b['time'] = mktime( $b['hour'], $b['minute'], 0, $b['month'], $b['day'], $b['year'] );
  489. wfRestoreWarnings();
  490. $b['name'] = $lucifer[7];
  491. } else {
  492. $b['month'] = $lucifer[5];
  493. $b['day'] = $lucifer[6];
  494. if ( preg_match( '/([0-9]{2}):([0-9]{2})/', $lucifer[7], $l2 ) ) {
  495. $b['year'] = date( 'Y' );
  496. $b['hour'] = $l2[1];
  497. $b['minute'] = $l2[2];
  498. } else {
  499. $b['year'] = $lucifer[7];
  500. $b['hour'] = 0;
  501. $b['minute'] = 0;
  502. }
  503. $b['time'] = strtotime( sprintf( '%d %s %d %02d:%02d', $b['day'], $b['month'], $b['year'], $b['hour'], $b['minute'] ) );
  504. $b['name'] = $lucifer[8];
  505. }
  506. }
  507. return $b;
  508. }
  509. }