PageRenderTime 34ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/src/Gaufrette/Adapter/Ftp.php

https://bitbucket.org/ajalovec/gaufrette
PHP | 474 lines | 275 code | 77 blank | 122 comment | 31 complexity | 1022a0c4a1008ea00cb218fb560cf7cb MD5 | raw file
  1. <?php
  2. namespace Gaufrette\Adapter;
  3. use Gaufrette\Adapter;
  4. use Gaufrette\File;
  5. use Gaufrette\Filesystem;
  6. use Gaufrette\Exception;
  7. /**
  8. * Ftp adapter
  9. *
  10. * @package Gaufrette
  11. * @author Antoine Hérault <antoine.herault@gmail.com>
  12. */
  13. class Ftp implements Adapter,
  14. FileFactory
  15. {
  16. protected $connection = null;
  17. protected $directory;
  18. protected $host;
  19. protected $port;
  20. protected $username;
  21. protected $password;
  22. protected $passive;
  23. protected $create;
  24. protected $mode;
  25. protected $fileData = array();
  26. /**
  27. * Constructor
  28. *
  29. * @param string $directory The directory to use in the ftp server
  30. * @param string $host The host of the ftp server
  31. * @param array $options The options like port, username, password, passive, create, mode
  32. */
  33. public function __construct($directory, $host, $options = array())
  34. {
  35. $this->directory = (string) $directory;
  36. $this->host = $host;
  37. $this->port = isset($options['port']) ? $options['port'] : 21;
  38. $this->username = isset($options['username']) ? $options['username'] : null;
  39. $this->password = isset($options['password']) ? $options['password'] : null;
  40. $this->passive = isset($options['passive']) ? $options['passive'] : false;
  41. $this->create = isset($options['create']) ? $options['create'] : false;
  42. $this->mode = isset($options['mode']) ? $options['mode'] : FTP_BINARY;
  43. }
  44. /**
  45. * {@inheritDoc}
  46. */
  47. public function read($key)
  48. {
  49. $this->ensureDirectoryExists($this->directory, $this->create);
  50. $temp = fopen('php://temp', 'r+');
  51. if (!ftp_fget($this->getConnection(), $temp, $this->computePath($key), $this->mode)) {
  52. return false;
  53. }
  54. rewind($temp);
  55. $contents = stream_get_contents($temp);
  56. fclose($temp);
  57. return $contents;
  58. }
  59. /**
  60. * {@inheritDoc}
  61. */
  62. public function write($key, $content)
  63. {
  64. $this->ensureDirectoryExists($this->directory, $this->create);
  65. $path = $this->computePath($key);
  66. $directory = dirname($path);
  67. $this->ensureDirectoryExists($directory, true);
  68. $temp = fopen('php://temp', 'r+');
  69. $size = fwrite($temp, $content);
  70. rewind($temp);
  71. if (!ftp_fput($this->getConnection(), $path, $temp, $this->mode)) {
  72. fclose($temp);
  73. return false;
  74. }
  75. fclose($temp);
  76. return $size;
  77. }
  78. /**
  79. * {@inheritDoc}
  80. */
  81. public function rename($sourceKey, $targetKey)
  82. {
  83. $this->ensureDirectoryExists($this->directory, $this->create);
  84. $sourcePath = $this->computePath($sourceKey);
  85. $targetPath = $this->computePath($targetKey);
  86. $this->ensureDirectoryExists(dirname($targetPath), true);
  87. return ftp_rename($this->getConnection(), $sourcePath, $targetPath);
  88. }
  89. /**
  90. * {@inheritDoc}
  91. */
  92. public function exists($key)
  93. {
  94. $this->ensureDirectoryExists($this->directory, $this->create);
  95. $file = $this->computePath($key);
  96. $items = ftp_nlist($this->getConnection(), dirname($file));
  97. return $items && (in_array($file, $items) || in_array(basename($file), $items));
  98. }
  99. /**
  100. * {@inheritDoc}
  101. */
  102. public function keys()
  103. {
  104. $this->ensureDirectoryExists($this->directory, $this->create);
  105. return $this->fetchKeys();
  106. }
  107. /**
  108. * {@inheritDoc}
  109. */
  110. public function mtime($key)
  111. {
  112. $this->ensureDirectoryExists($this->directory, $this->create);
  113. $mtime = ftp_mdtm($this->getConnection(), $this->computePath($key));
  114. // the server does not support this function
  115. if (-1 === $mtime) {
  116. throw new \RuntimeException('Server does not support ftp_mdtm function.');
  117. }
  118. return $mtime;
  119. }
  120. /**
  121. * {@inheritDoc}
  122. */
  123. public function delete($key)
  124. {
  125. $this->ensureDirectoryExists($this->directory, $this->create);
  126. if ($this->isDirectory($key)) {
  127. return ftp_rmdir($this->getConnection(), $this->computePath($key));
  128. }
  129. return ftp_delete($this->getConnection(), $this->computePath($key));
  130. }
  131. /**
  132. * {@inheritDoc}
  133. */
  134. public function isDirectory($key)
  135. {
  136. $this->ensureDirectoryExists($this->directory, $this->create);
  137. return $this->isDir($this->computePath($key));
  138. }
  139. /**
  140. * Lists files from the specified directory. If a pattern is
  141. * specified, it only returns files matching it.
  142. *
  143. * @param string $directory The path of the directory to list from
  144. *
  145. * @return array An array of keys and dirs
  146. */
  147. public function listDirectory($directory = '')
  148. {
  149. $this->ensureDirectoryExists($this->directory, $this->create);
  150. $directory = preg_replace('/^[\/]*([^\/].*)$/', '/$1', $directory);
  151. $items = $this->parseRawlist(
  152. ftp_rawlist($this->getConnection(), '-al ' . $this->directory . $directory ) ? : array()
  153. );
  154. $fileData = $dirs = array();
  155. foreach ($items as $itemData) {
  156. if ('..' === $itemData['name'] || '.' === $itemData['name']) {
  157. continue;
  158. }
  159. $item = array(
  160. 'name' => $itemData['name'],
  161. 'path' => trim(($directory ? $directory . '/' : '') . $itemData['name'], '/'),
  162. 'time' => $itemData['time'],
  163. 'size' => $itemData['size'],
  164. );
  165. if ('-' === substr($itemData['perms'], 0, 1)) {
  166. $fileData[$item['path']] = $item;
  167. } elseif ('d' === substr($itemData['perms'], 0, 1)) {
  168. $dirs[] = $item['path'];
  169. }
  170. }
  171. $this->fileData = array_merge($fileData, $this->fileData);
  172. return array(
  173. 'keys' => array_keys($fileData),
  174. 'dirs' => $dirs
  175. );
  176. }
  177. /**
  178. * {@inheritDoc}
  179. */
  180. public function createFile($key, Filesystem $filesystem)
  181. {
  182. $this->ensureDirectoryExists($this->directory, $this->create);
  183. $file = new File($key, $filesystem);
  184. if (!array_key_exists($key, $this->fileData)) {
  185. $directory = dirname($key) == '.' ? '' : dirname($key);
  186. $this->listDirectory($directory);
  187. }
  188. if (isset($this->fileData[$key])) {
  189. $fileData = $this->fileData[$key];
  190. $file->setName($fileData['name']);
  191. $file->setSize($fileData['size']);
  192. }
  193. return $file;
  194. }
  195. /**
  196. * Ensures the specified directory exists. If it does not, and the create
  197. * parameter is set to TRUE, it tries to create it
  198. *
  199. * @param string $directory
  200. * @param boolean $create Whether to create the directory if it does not
  201. * exist
  202. *
  203. * @throws RuntimeException if the directory does not exist and could not
  204. * be created
  205. */
  206. protected function ensureDirectoryExists($directory, $create = false)
  207. {
  208. if (!$this->isDir($directory)) {
  209. if (!$create) {
  210. throw new \RuntimeException(sprintf('The directory \'%s\' does not exist.', $directory));
  211. }
  212. $this->createDirectory($directory);
  213. }
  214. }
  215. /**
  216. * Creates the specified directory and its parent directories
  217. *
  218. * @param string $directory Directory to create
  219. *
  220. * @throws RuntimeException if the directory could not be created
  221. */
  222. protected function createDirectory($directory)
  223. {
  224. // create parent directory if needed
  225. $parent = dirname($directory);
  226. if (!$this->isDir($parent)) {
  227. $this->createDirectory($parent);
  228. }
  229. // create the specified directory
  230. $created = ftp_mkdir($this->getConnection(), $directory);
  231. if (false === $created) {
  232. throw new \RuntimeException(sprintf('Could not create the \'%s\' directory.', $directory));
  233. }
  234. }
  235. /**
  236. * @param string $directory - full directory path
  237. * @return boolean
  238. */
  239. private function isDir($directory)
  240. {
  241. if ('/' === $directory) {
  242. return true;
  243. }
  244. if (!@ftp_chdir($this->getConnection(), $directory)) {
  245. return false;
  246. }
  247. // change directory again to return in the base directory
  248. ftp_chdir($this->getConnection(), $this->directory);
  249. return true;
  250. }
  251. /**
  252. * Fetch all Keys recursive
  253. *
  254. * @param string $directory
  255. */
  256. private function fetchKeys($directory = '')
  257. {
  258. $items = $this->listDirectory($directory);
  259. $keys = $items['dirs'];
  260. foreach ($items['dirs'] as $dir) {
  261. $keys = array_merge($keys, $this->fetchKeys($dir));
  262. }
  263. return array_merge($items['keys'], $keys);
  264. }
  265. /**
  266. * Parses the given raw list
  267. *
  268. * @param array $rawlist
  269. *
  270. * @return array
  271. */
  272. private function parseRawlist(array $rawlist)
  273. {
  274. $parsed = array();
  275. foreach ($rawlist as $line) {
  276. $infos = preg_split("/[\s]+/", $line, 9);
  277. if ($this->isLinuxListing($infos)) {
  278. $infos[7] = (strrpos($infos[7], ':') != 2 ) ? ($infos[7] . ' 00:00') : (date('Y') . ' ' . $infos[7]);
  279. if ('total' !== $infos[0]) {
  280. $parsed[] = array(
  281. 'perms' => $infos[0],
  282. 'num' => $infos[1],
  283. 'size' => $infos[4],
  284. 'time' => strtotime($infos[5] . ' ' . $infos[6] . '. ' . $infos[7]),
  285. 'name' => $infos[8]
  286. );
  287. }
  288. } else {
  289. $isDir = (boolean) ('<dir>' === $infos[2]);
  290. $parsed[] = array(
  291. 'perms' => $isDir ? 'd' : '-',
  292. 'num' => '',
  293. 'size' => $isDir ? '' : $infos[2],
  294. 'time' => strtotime($infos[0] . ' ' . $infos[1]),
  295. 'name' => $infos[3]
  296. );
  297. }
  298. }
  299. return $parsed;
  300. }
  301. /**
  302. * Computes the path for the given key
  303. *
  304. * @param string $key
  305. */
  306. private function computePath($key)
  307. {
  308. return rtrim($this->directory, '/') . '/' . $key;
  309. }
  310. /**
  311. * Indicates whether the adapter has an open ftp connection
  312. *
  313. * @return boolean
  314. */
  315. private function isConnected()
  316. {
  317. return is_resource($this->connection);
  318. }
  319. /**
  320. * Returns an opened ftp connection resource. If the connection is not
  321. * already opened, it open it before
  322. *
  323. * @return resource The ftp connection
  324. */
  325. private function getConnection()
  326. {
  327. if (!$this->isConnected()) {
  328. $this->connect();
  329. }
  330. return $this->connection;
  331. }
  332. /**
  333. * Opens the adapter's ftp connection
  334. *
  335. * @throws RuntimeException if could not connect
  336. */
  337. private function connect()
  338. {
  339. // open ftp connection
  340. $this->connection = ftp_connect($this->host, $this->port);
  341. if (!$this->connection) {
  342. throw new \RuntimeException(sprintf('Could not connect to \'%s\' (port: %s).', $this->host, $this->port));
  343. }
  344. $username = $this->username ? : 'anonymous';
  345. $password = $this->password ? : '';
  346. // login ftp user
  347. if (!ftp_login($this->connection, $username, $password)) {
  348. $this->close();
  349. throw new \RuntimeException(sprintf('Could not login as %s.', $username));
  350. }
  351. // switch to passive mode if needed
  352. if ($this->passive && !ftp_pasv($this->connection, true)) {
  353. $this->close();
  354. throw new \RuntimeException('Could not turn passive mode on.');
  355. }
  356. // ensure the adapter's directory exists
  357. if ('/' !== $this->directory) {
  358. try {
  359. $this->ensureDirectoryExists($this->directory, $this->create);
  360. } catch (\RuntimeException $e) {
  361. $this->close();
  362. throw $e;
  363. }
  364. // change the current directory for the adapter's directory
  365. if (!ftp_chdir($this->connection, $this->directory)) {
  366. $this->close();
  367. throw new \RuntimeException(sprintf('Could not change current directory for the \'%s\' directory.', $this->directory));
  368. }
  369. }
  370. }
  371. /**
  372. * Closes the adapter's ftp connection
  373. */
  374. private function close()
  375. {
  376. if ($this->isConnected()) {
  377. ftp_close($this->connection);
  378. }
  379. }
  380. /**
  381. * Asserts the specified file exists
  382. *
  383. * @param string $key
  384. *
  385. * @throws Exception\FileNotFound if the file does not exist
  386. */
  387. private function assertExists($key)
  388. {
  389. if (!$this->exists($key)) {
  390. throw new Exception\FileNotFound($key);
  391. }
  392. }
  393. private function isLinuxListing($info)
  394. {
  395. return count($info) >= 9;
  396. }
  397. }