Ftp.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497
  1. <?php
  2. /**
  3. * Copyright © Magento, Inc. All rights reserved.
  4. * See COPYING.txt for license details.
  5. */
  6. namespace Magento\Framework\System;
  7. /**
  8. * Class to work with remote FTP server
  9. */
  10. class Ftp
  11. {
  12. /**
  13. * Connection object
  14. *
  15. * @var resource
  16. */
  17. protected $_conn = false;
  18. /**
  19. * Check connected, throw exception if not
  20. *
  21. * @throws \Exception
  22. * @return void
  23. */
  24. protected function checkConnected()
  25. {
  26. if (!$this->_conn) {
  27. throw new \Exception(__CLASS__ . " - no connection established with server");
  28. }
  29. }
  30. /**
  31. * Wrapper for ftp_mkdir
  32. *
  33. * @param string $name
  34. * @return string the newly created directory name on success or <b>FALSE</b> on error.
  35. */
  36. public function mdkir($name)
  37. {
  38. $this->checkConnected();
  39. return @ftp_mkdir($this->_conn, $name);
  40. }
  41. /**
  42. * Make dir recursive
  43. *
  44. * @param string $path
  45. * @param int $mode
  46. * @return bool
  47. */
  48. public function mkdirRecursive($path, $mode = 0777)
  49. {
  50. $this->checkConnected();
  51. $dir = explode("/", $path);
  52. $path = "";
  53. $ret = true;
  54. for ($i = 0, $count = count($dir); $i < $count; $i++) {
  55. $path .= "/" . $dir[$i];
  56. if (!@ftp_chdir($this->_conn, $path)) {
  57. @ftp_chdir($this->_conn, "/");
  58. if (!@ftp_mkdir($this->_conn, $path)) {
  59. $ret = false;
  60. break;
  61. } else {
  62. @ftp_chmod($this->_conn, $mode, $path);
  63. }
  64. }
  65. }
  66. return $ret;
  67. }
  68. /**
  69. * Try to login to server
  70. *
  71. * @param string $login
  72. * @param string $password
  73. * @return bool
  74. * @throws \Exception on invalid login credentials
  75. */
  76. public function login($login = "anonymous", $password = "test@gmail.com")
  77. {
  78. $this->checkConnected();
  79. $res = @ftp_login($this->_conn, $login, $password);
  80. if (!$res) {
  81. throw new \Exception("Invalid login credentials");
  82. }
  83. return $res;
  84. }
  85. /**
  86. * Validate connection string
  87. *
  88. * @param string $string
  89. * @throws \Exception
  90. * @return string
  91. */
  92. public function validateConnectionString($string)
  93. {
  94. $data = parse_url($string);
  95. if (false === $data) {
  96. throw new \Exception("Connection string invalid: '{$string}'");
  97. }
  98. if ($data['scheme'] != 'ftp') {
  99. throw new \Exception("Support for scheme unsupported: '{$data['scheme']}'");
  100. }
  101. // Decode user & password strings from URL
  102. foreach (array_intersect(array_keys($data), ['user','pass']) as $key) {
  103. $data[$key] = urldecode($data[$key]);
  104. }
  105. return $data;
  106. }
  107. /**
  108. * Connect to server using connect string
  109. * Connection string: ftp://user:pass@server:port/path
  110. * user,pass,port,path are optional parts
  111. *
  112. * @param string $string
  113. * @param int $timeout
  114. * @return void
  115. * @throws \Exception
  116. */
  117. public function connect($string, $timeout = 900)
  118. {
  119. $params = $this->validateConnectionString($string);
  120. $port = isset($params['port']) ? (int)$params['port'] : 21;
  121. $this->_conn = ftp_connect($params['host'], $port, $timeout);
  122. if (!$this->_conn) {
  123. throw new \Exception("Cannot connect to host: {$params['host']}");
  124. }
  125. if (isset($params['user']) && isset($params['pass'])) {
  126. $this->login($params['user'], $params['pass']);
  127. } else {
  128. $this->login();
  129. }
  130. if (isset($params['path'])) {
  131. if (!$this->chdir($params['path'])) {
  132. throw new \Exception("Cannot chdir after login to: {$params['path']}");
  133. }
  134. }
  135. }
  136. /**
  137. * Wrapper for ftp_fput
  138. *
  139. * @param string $remoteFile
  140. * @param resource $handle
  141. * @param int $mode FTP_BINARY | FTP_ASCII
  142. * @param int $startPos
  143. * @return bool
  144. */
  145. public function fput($remoteFile, $handle, $mode = FTP_BINARY, $startPos = 0)
  146. {
  147. $this->checkConnected();
  148. return @ftp_fput($this->_conn, $remoteFile, $handle, $mode, $startPos);
  149. }
  150. /**
  151. * Wrapper for ftp_put
  152. *
  153. * @param string $remoteFile
  154. * @param string $localFile
  155. * @param int $mode FTP_BINARY | FTP_ASCII
  156. * @param int $startPos
  157. * @return bool
  158. */
  159. public function put($remoteFile, $localFile, $mode = FTP_BINARY, $startPos = 0)
  160. {
  161. $this->checkConnected();
  162. return ftp_put($this->_conn, $remoteFile, $localFile, $mode, $startPos);
  163. }
  164. /**
  165. * Get current working directory
  166. *
  167. * @return false|string
  168. */
  169. public function getcwd()
  170. {
  171. $d = $this->raw("pwd");
  172. $data = explode(" ", $d[0], 3);
  173. if (empty($data[1])) {
  174. return false;
  175. }
  176. if ((int)$data[0] != 257) {
  177. return false;
  178. }
  179. $out = trim($data[1], '"');
  180. if ($out !== "/") {
  181. $out = rtrim($out, "/");
  182. }
  183. return $out;
  184. }
  185. /**
  186. * Wrapper for ftp_raw
  187. *
  188. * @param string $cmd
  189. * @return array The server's response as an array of strings.
  190. */
  191. public function raw($cmd)
  192. {
  193. $this->checkConnected();
  194. return @ftp_raw($this->_conn, $cmd);
  195. }
  196. /**
  197. * Upload local file to remote
  198. *
  199. * Can be used for relative and absoulte remote paths
  200. * Relative: use chdir before calling this
  201. *
  202. * @param string $remote
  203. * @param string $local
  204. * @param int $dirMode
  205. * @param int $ftpMode
  206. * @return bool
  207. * @throws \Exception
  208. */
  209. public function upload($remote, $local, $dirMode = 0777, $ftpMode = FTP_BINARY)
  210. {
  211. $this->checkConnected();
  212. if (!file_exists($local)) {
  213. throw new \Exception("Local file doesn't exist: {$local}");
  214. }
  215. if (!is_readable($local)) {
  216. throw new \Exception("Local file is not readable: {$local}");
  217. }
  218. if (is_dir($local)) {
  219. throw new \Exception("Directory given instead of file: {$local}");
  220. }
  221. $globalPathMode = substr($remote, 0, 1) == "/";
  222. $dirname = dirname($remote);
  223. $cwd = $this->getcwd();
  224. if (false === $cwd) {
  225. throw new \Exception("Server returns something awful on PWD command");
  226. }
  227. if (!$globalPathMode) {
  228. $dirname = $cwd . "/" . $dirname;
  229. $remote = $cwd . "/" . $remote;
  230. }
  231. $res = $this->mkdirRecursive($dirname, $dirMode);
  232. $this->chdir($cwd);
  233. if (!$res) {
  234. return false;
  235. }
  236. return $this->put($remote, $local, $ftpMode);
  237. }
  238. /**
  239. * Download remote file to local machine
  240. *
  241. * @param string $remote
  242. * @param string $local
  243. * @param int $ftpMode FTP_BINARY|FTP_ASCII
  244. * @return bool
  245. */
  246. public function download($remote, $local, $ftpMode = FTP_BINARY)
  247. {
  248. $this->checkConnected();
  249. return $this->get($local, $remote, $ftpMode);
  250. }
  251. /**
  252. * Wrapper for ftp_pasv
  253. *
  254. * @param bool $pasv
  255. * @return bool
  256. */
  257. public function pasv($pasv)
  258. {
  259. $this->checkConnected();
  260. return @ftp_pasv($this->_conn, (bool)$pasv);
  261. }
  262. /**
  263. * Close FTP connection
  264. *
  265. * @return void
  266. */
  267. public function close()
  268. {
  269. if ($this->_conn) {
  270. @ftp_close($this->_conn);
  271. }
  272. }
  273. /**
  274. * Wrapper for ftp_chmod
  275. *
  276. * @param int $mode
  277. * @param string $remoteFile
  278. * @return int The new file permissions on success or <b>FALSE</b> on error.
  279. */
  280. public function chmod($mode, $remoteFile)
  281. {
  282. $this->checkConnected();
  283. return @ftp_chmod($this->_conn, $mode, $remoteFile);
  284. }
  285. /**
  286. * Wrapper for ftp_chdir
  287. *
  288. * @param string $dir
  289. * @return bool
  290. */
  291. public function chdir($dir)
  292. {
  293. $this->checkConnected();
  294. return @ftp_chdir($this->_conn, $dir);
  295. }
  296. /**
  297. * Wrapper for ftp_cdup
  298. *
  299. * @return bool
  300. */
  301. public function cdup()
  302. {
  303. $this->checkConnected();
  304. return @ftp_cdup($this->_conn);
  305. }
  306. /**
  307. * Wrapper for ftp_get
  308. *
  309. * @param string $localFile
  310. * @param string $remoteFile
  311. * @param int $fileMode FTP_BINARY | FTP_ASCII
  312. * @param int $resumeOffset
  313. * @return bool
  314. * @SuppressWarnings(PHPMD.BooleanGetMethodName)
  315. */
  316. public function get($localFile, $remoteFile, $fileMode = FTP_BINARY, $resumeOffset = 0)
  317. {
  318. $remoteFile = $this->correctFilePath($remoteFile);
  319. $this->checkConnected();
  320. return @ftp_get($this->_conn, $localFile, $remoteFile, $fileMode, $resumeOffset);
  321. }
  322. /**
  323. * Wrapper for ftp_nlist
  324. *
  325. * @param string $dir
  326. * @return bool
  327. */
  328. public function nlist($dir = "/")
  329. {
  330. $this->checkConnected();
  331. $dir = $this->correctFilePath($dir);
  332. return @ftp_nlist($this->_conn, $dir);
  333. }
  334. /**
  335. * Wrapper for ftp_rawlist
  336. *
  337. * @param string $dir
  338. * @param bool $recursive
  339. * @return array an array where each element corresponds to one line of text.
  340. */
  341. public function rawlist($dir = "/", $recursive = false)
  342. {
  343. $this->checkConnected();
  344. $dir = $this->correctFilePath($dir);
  345. return @ftp_rawlist($this->_conn, $dir, $recursive);
  346. }
  347. /**
  348. * Convert byte count to float KB/MB format
  349. *
  350. * @param int $bytes
  351. * @return string
  352. */
  353. public static function byteconvert($bytes)
  354. {
  355. $symbol = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
  356. $exp = floor(log($bytes) / log(1024));
  357. return sprintf('%.2f ' . $symbol[$exp], $bytes / pow(1024, floor($exp)));
  358. }
  359. /**
  360. * Chmod string "-rwxrwxrwx" to "777" converter
  361. *
  362. * @param string $chmod
  363. * @return string
  364. */
  365. public static function chmodnum($chmod)
  366. {
  367. $trans = ['-' => '0', 'r' => '4', 'w' => '2', 'x' => '1'];
  368. $chmod = substr(strtr($chmod, $trans), 1);
  369. $array = str_split($chmod, 3);
  370. return array_sum(str_split($array[0])) . array_sum(str_split($array[1])) . array_sum(str_split($array[2]));
  371. }
  372. /**
  373. * Check whether file exists
  374. *
  375. * @param string $path
  376. * @param bool $excludeIfIsDir
  377. * @return bool
  378. */
  379. public function fileExists($path, $excludeIfIsDir = true)
  380. {
  381. $path = $this->correctFilePath($path);
  382. $globalPathMode = substr($path, 0, 1) == "/";
  383. $file = basename($path);
  384. $dir = $globalPathMode ? dirname($path) : $this->getcwd() . "/" . $path;
  385. $data = $this->ls($dir);
  386. foreach ($data as $row) {
  387. if ($file == $row['name']) {
  388. if ($excludeIfIsDir && $row['dir']) {
  389. continue;
  390. }
  391. return true;
  392. }
  393. }
  394. return false;
  395. }
  396. /**
  397. * Get directory contents in PHP array
  398. *
  399. * @param string $dir
  400. * @param bool $recursive
  401. * @return array
  402. * @SuppressWarnings(PHPMD.ShortMethodName)
  403. */
  404. public function ls($dir = "/", $recursive = false)
  405. {
  406. $dir = $this->correctFilePath($dir);
  407. $rawfiles = (array)$this->rawlist($dir, $recursive);
  408. $structure = [];
  409. $arraypointer = & $structure;
  410. foreach ($rawfiles as $rawfile) {
  411. if ($rawfile[0] == '/') {
  412. $paths = array_slice(explode('/', str_replace(':', '', $rawfile)), 1);
  413. $arraypointer = & $structure;
  414. foreach ($paths as $path) {
  415. foreach ($arraypointer as $i => $file) {
  416. if ($file['name'] == $path) {
  417. $arraypointer = & $arraypointer[$i]['children'];
  418. break;
  419. }
  420. }
  421. }
  422. } elseif (!empty($rawfile)) {
  423. $info = preg_split("/[\s]+/", $rawfile, 9);
  424. $arraypointer[] = [
  425. 'name' => $info[8],
  426. 'dir' => $info[0][0] == 'd',
  427. 'size' => (int)$info[4],
  428. 'chmod' => self::chmodnum($info[0]),
  429. 'rawdata' => $info,
  430. 'raw' => $rawfile,
  431. ];
  432. }
  433. }
  434. return $structure;
  435. }
  436. /**
  437. * Correct file path
  438. *
  439. * @param string $str
  440. * @return string
  441. */
  442. public function correctFilePath($str)
  443. {
  444. $str = str_replace("\\", "/", $str);
  445. $str = preg_replace("/^.\//", "", $str);
  446. return $str;
  447. }
  448. /**
  449. * Delete file
  450. *
  451. * @param string $file
  452. * @return bool
  453. */
  454. public function delete($file)
  455. {
  456. $this->checkConnected();
  457. $file = $this->correctFilePath($file);
  458. return @ftp_delete($this->_conn, $file);
  459. }
  460. }