PageRenderTime 66ms CodeModel.GetById 27ms RepoModel.GetById 0ms app.codeStats 0ms

/src/elFinder/elFinderVolumeFTP.class.php

https://github.com/ahmadazimi/laravel-elfinder
PHP | 1430 lines | 808 code | 185 blank | 437 comment | 216 complexity | 3ec5879cba0462ad0aca03cef88b5c3e MD5 | raw file
  1. <?php
  2. function chmodnum($chmod) {
  3. $trans = array('-' => '0', 'r' => '4', 'w' => '2', 'x' => '1');
  4. $chmod = substr(strtr($chmod, $trans), 1);
  5. $array = str_split($chmod, 3);
  6. return array_sum(str_split($array[0])) . array_sum(str_split($array[1])) . array_sum(str_split($array[2]));
  7. }
  8. elFinder::$netDrivers['ftp'] = 'FTP';
  9. /**
  10. * Simple elFinder driver for FTP
  11. *
  12. * @author Dmitry (dio) Levashov
  13. * @author Cem (discofever)
  14. **/
  15. class elFinderVolumeFTP extends elFinderVolumeDriver {
  16. /**
  17. * Driver id
  18. * Must be started from letter and contains [a-z0-9]
  19. * Used as part of volume id
  20. *
  21. * @var string
  22. **/
  23. protected $driverId = 'f';
  24. /**
  25. * FTP Connection Instance
  26. *
  27. * @var ftp
  28. **/
  29. protected $connect = null;
  30. /**
  31. * Directory for tmp files
  32. * If not set driver will try to use tmbDir as tmpDir
  33. *
  34. * @var string
  35. **/
  36. protected $tmpPath = '';
  37. /**
  38. * Last FTP error message
  39. *
  40. * @var string
  41. **/
  42. protected $ftpError = '';
  43. /**
  44. * FTP server output list as ftp on linux
  45. *
  46. * @var bool
  47. **/
  48. protected $ftpOsUnix;
  49. /**
  50. * Tmp folder path
  51. *
  52. * @var string
  53. **/
  54. protected $tmp = '';
  55. /**
  56. * Net mount key
  57. *
  58. * @var string
  59. **/
  60. public $netMountKey = '';
  61. /**
  62. * Constructor
  63. * Extend options with required fields
  64. *
  65. * @return void
  66. * @author Dmitry (dio) Levashov
  67. * @author Cem (DiscoFever)
  68. **/
  69. public function __construct() {
  70. $opts = array(
  71. 'host' => 'localhost',
  72. 'user' => '',
  73. 'pass' => '',
  74. 'port' => 21,
  75. 'mode' => 'passive',
  76. 'path' => '/',
  77. 'timeout' => 20,
  78. 'owner' => true,
  79. 'tmbPath' => '',
  80. 'tmpPath' => '',
  81. 'dirMode' => 0755,
  82. 'fileMode' => 0644,
  83. 'icon' => (defined('ELFINDER_IMG_PARENT_URL')? (rtrim(ELFINDER_IMG_PARENT_URL, '/').'/') : '').'img/volume_icon_ftp.png'
  84. );
  85. $this->options = array_merge($this->options, $opts);
  86. $this->options['mimeDetect'] = 'internal';
  87. }
  88. /*********************************************************************/
  89. /* INIT AND CONFIGURE */
  90. /*********************************************************************/
  91. /**
  92. * Prepare FTP connection
  93. * Connect to remote server and check if credentials are correct, if so, store the connection id in $ftp_conn
  94. *
  95. * @return bool
  96. * @author Dmitry (dio) Levashov
  97. * @author Cem (DiscoFever)
  98. **/
  99. protected function init() {
  100. if (!$this->options['host']
  101. || !$this->options['user']
  102. || !$this->options['pass']
  103. || !$this->options['port']) {
  104. return $this->setError('Required options undefined.');
  105. }
  106. // make ney mount key
  107. $this->netMountKey = md5(join('-', array('ftp', $this->options['host'], $this->options['port'], $this->options['path'], $this->options['user'])));
  108. if (!function_exists('ftp_connect')) {
  109. return $this->setError('FTP extension not loaded.');
  110. }
  111. // remove protocol from host
  112. $scheme = parse_url($this->options['host'], PHP_URL_SCHEME);
  113. if ($scheme) {
  114. $this->options['host'] = substr($this->options['host'], strlen($scheme)+3);
  115. }
  116. // normalize root path
  117. $this->root = $this->options['path'] = $this->_normpath($this->options['path']);
  118. if (empty($this->options['alias'])) {
  119. $this->options['alias'] = $this->options['user'].'@'.$this->options['host'];
  120. // $num = elFinder::$volumesCnt-1;
  121. // $this->options['alias'] = $this->root == '/' || $this->root == '.' ? 'FTP folder '.$num : basename($this->root);
  122. }
  123. $this->rootName = $this->options['alias'];
  124. $this->options['separator'] = '/';
  125. return $this->connect();
  126. }
  127. /**
  128. * Configure after successfull mount.
  129. *
  130. * @return void
  131. * @author Dmitry (dio) Levashov
  132. **/
  133. protected function configure() {
  134. parent::configure();
  135. if (!empty($this->options['tmpPath'])) {
  136. if ((is_dir($this->options['tmpPath']) || @mkdir($this->options['tmpPath'], 0755, true)) && is_writable($this->options['tmpPath'])) {
  137. $this->tmp = $this->options['tmpPath'];
  138. }
  139. }
  140. if (!$this->tmp && $this->tmbPath) {
  141. $this->tmp = $this->tmbPath;
  142. }
  143. if (!$this->tmp) {
  144. $this->disabled[] = 'mkfile';
  145. $this->disabled[] = 'paste';
  146. $this->disabled[] = 'duplicate';
  147. $this->disabled[] = 'upload';
  148. $this->disabled[] = 'edit';
  149. $this->disabled[] = 'archive';
  150. $this->disabled[] = 'extract';
  151. }
  152. // echo $this->tmp;
  153. }
  154. /**
  155. * Connect to ftp server
  156. *
  157. * @return bool
  158. * @author Dmitry (dio) Levashov
  159. **/
  160. protected function connect() {
  161. if (!($this->connect = ftp_connect($this->options['host'], $this->options['port'], $this->options['timeout']))) {
  162. return $this->setError('Unable to connect to FTP server '.$this->options['host']);
  163. }
  164. if (!ftp_login($this->connect, $this->options['user'], $this->options['pass'])) {
  165. $this->umount();
  166. return $this->setError('Unable to login into '.$this->options['host']);
  167. }
  168. // switch off extended passive mode - may be usefull for some servers
  169. @ftp_exec($this->connect, 'epsv4 off' );
  170. // enter passive mode if required
  171. ftp_pasv($this->connect, $this->options['mode'] == 'passive');
  172. // enter root folder
  173. if (!ftp_chdir($this->connect, $this->root)
  174. || $this->root != ftp_pwd($this->connect)) {
  175. $this->umount();
  176. return $this->setError('Unable to open root folder.');
  177. }
  178. // check for MLST support
  179. $features = ftp_raw($this->connect, 'FEAT');
  180. if (!is_array($features)) {
  181. $this->umount();
  182. return $this->setError('Server does not support command FEAT.');
  183. }
  184. foreach ($features as $feat) {
  185. if (strpos(trim($feat), 'MLST') === 0) {
  186. return true;
  187. }
  188. }
  189. return $this->setError('Server does not support command MLST.');
  190. }
  191. /*********************************************************************/
  192. /* FS API */
  193. /*********************************************************************/
  194. /**
  195. * Close opened connection
  196. *
  197. * @return void
  198. * @author Dmitry (dio) Levashov
  199. **/
  200. public function umount() {
  201. $this->connect && @ftp_close($this->connect);
  202. }
  203. /**
  204. * Parse line from ftp_rawlist() output and return file stat (array)
  205. *
  206. * @param string $raw line from ftp_rawlist() output
  207. * @return array
  208. * @author Dmitry Levashov
  209. **/
  210. protected function parseRaw($raw) {
  211. $info = preg_split("/\s+/", $raw, 9);
  212. $stat = array();
  213. if (count($info) < 9 || $info[8] == '.' || $info[8] == '..') {
  214. return false;
  215. }
  216. if (!isset($this->ftpOsUnix)) {
  217. $this->ftpOsUnix = !preg_match('/\d/', substr($info[0], 0, 1));
  218. }
  219. if ($this->ftpOsUnix) {
  220. $stat['ts'] = strtotime($info[5].' '.$info[6].' '.$info[7]);
  221. if (empty($stat['ts'])) {
  222. $stat['ts'] = strtotime($info[6].' '.$info[5].' '.$info[7]);
  223. }
  224. $name = $info[8];
  225. if (preg_match('|(.+)\-\>(.+)|', $name, $m)) {
  226. $name = trim($m[1]);
  227. $target = trim($m[2]);
  228. if (substr($target, 0, 1) != '/') {
  229. $target = $this->root.'/'.$target;
  230. }
  231. $target = $this->_normpath($target);
  232. $stat['name'] = $name;
  233. if ($this->_inpath($target, $this->root)
  234. && ($tstat = $this->stat($target))) {
  235. $stat['size'] = $tstat['mime'] == 'directory' ? 0 : $info[4];
  236. $stat['alias'] = $this->_relpath($target);
  237. $stat['thash'] = $tstat['hash'];
  238. $stat['mime'] = $tstat['mime'];
  239. $stat['read'] = $tstat['read'];
  240. $stat['write'] = $tstat['write'];
  241. } else {
  242. $stat['mime'] = 'symlink-broken';
  243. $stat['read'] = false;
  244. $stat['write'] = false;
  245. $stat['size'] = 0;
  246. }
  247. return $stat;
  248. }
  249. $perm = $this->parsePermissions($info[0]);
  250. $stat['name'] = $name;
  251. $stat['mime'] = substr(strtolower($info[0]), 0, 1) == 'd' ? 'directory' : $this->mimetype($stat['name']);
  252. $stat['size'] = $stat['mime'] == 'directory' ? 0 : $info[4];
  253. $stat['read'] = $perm['read'];
  254. $stat['write'] = $perm['write'];
  255. $stat['perm'] = substr($info[0], 1);
  256. } else {
  257. die('Windows ftp servers not supported yet');
  258. }
  259. return $stat;
  260. }
  261. /**
  262. * Parse permissions string. Return array(read => true/false, write => true/false)
  263. *
  264. * @param string $perm permissions string
  265. * @return string
  266. * @author Dmitry (dio) Levashov
  267. **/
  268. protected function parsePermissions($perm) {
  269. $res = array();
  270. $parts = array();
  271. $owner = $this->options['owner'];
  272. for ($i = 0, $l = strlen($perm); $i < $l; $i++) {
  273. $parts[] = substr($perm, $i, 1);
  274. }
  275. $read = ($owner && $parts[0] == 'r') || $parts[4] == 'r' || $parts[7] == 'r';
  276. return array(
  277. 'read' => $parts[0] == 'd' ? $read && (($owner && $parts[3] == 'x') || $parts[6] == 'x' || $parts[9] == 'x') : $read,
  278. 'write' => ($owner && $parts[2] == 'w') || $parts[5] == 'w' || $parts[8] == 'w'
  279. );
  280. }
  281. /**
  282. * Cache dir contents
  283. *
  284. * @param string $path dir path
  285. * @return void
  286. * @author Dmitry Levashov
  287. **/
  288. protected function cacheDir($path) {
  289. $this->dirsCache[$path] = array();
  290. if (preg_match('/\'|\"/', $path)) {
  291. foreach (ftp_nlist($this->connect, $path) as $p) {
  292. if (($stat = $this->_stat($p)) &&empty($stat['hidden'])) {
  293. // $files[] = $stat;
  294. $this->dirsCache[$path][] = $p;
  295. }
  296. }
  297. return;
  298. }
  299. foreach (ftp_rawlist($this->connect, $path) as $raw) {
  300. if (($stat = $this->parseRaw($raw))) {
  301. $p = $path.'/'.$stat['name'];
  302. $stat = $this->updateCache($p, $stat);
  303. if (empty($stat['hidden'])) {
  304. // $files[] = $stat;
  305. $this->dirsCache[$path][] = $p;
  306. }
  307. }
  308. }
  309. }
  310. /**
  311. * Return ftp transfer mode for file
  312. *
  313. * @param string $path file path
  314. * @return string
  315. * @author Dmitry (dio) Levashov
  316. **/
  317. protected function ftpMode($path) {
  318. return strpos($this->mimetype($path), 'text/') === 0 ? FTP_ASCII : FTP_BINARY;
  319. }
  320. /*********************** paths/urls *************************/
  321. /**
  322. * Return parent directory path
  323. *
  324. * @param string $path file path
  325. * @return string
  326. * @author Dmitry (dio) Levashov
  327. **/
  328. protected function _dirname($path) {
  329. return dirname($path);
  330. }
  331. /**
  332. * Return file name
  333. *
  334. * @param string $path file path
  335. * @return string
  336. * @author Dmitry (dio) Levashov
  337. **/
  338. protected function _basename($path) {
  339. return basename($path);
  340. }
  341. /**
  342. * Join dir name and file name and retur full path
  343. *
  344. * @param string $dir
  345. * @param string $name
  346. * @return string
  347. * @author Dmitry (dio) Levashov
  348. **/
  349. protected function _joinPath($dir, $name) {
  350. return $dir.DIRECTORY_SEPARATOR.$name;
  351. }
  352. /**
  353. * Return normalized path, this works the same as os.path.normpath() in Python
  354. *
  355. * @param string $path path
  356. * @return string
  357. * @author Troex Nevelin
  358. **/
  359. protected function _normpath($path) {
  360. if (empty($path)) {
  361. $path = '.';
  362. }
  363. // path must be start with /
  364. $path = preg_replace('|^\.\/?|', '/', $path);
  365. $path = preg_replace('/^([^\/])/', "/$1", $path);
  366. if (strpos($path, '/') === 0) {
  367. $initial_slashes = true;
  368. } else {
  369. $initial_slashes = false;
  370. }
  371. if (($initial_slashes)
  372. && (strpos($path, '//') === 0)
  373. && (strpos($path, '///') === false)) {
  374. $initial_slashes = 2;
  375. }
  376. $initial_slashes = (int) $initial_slashes;
  377. $comps = explode('/', $path);
  378. $new_comps = array();
  379. foreach ($comps as $comp) {
  380. if (in_array($comp, array('', '.'))) {
  381. continue;
  382. }
  383. if (($comp != '..')
  384. || (!$initial_slashes && !$new_comps)
  385. || ($new_comps && (end($new_comps) == '..'))) {
  386. array_push($new_comps, $comp);
  387. } elseif ($new_comps) {
  388. array_pop($new_comps);
  389. }
  390. }
  391. $comps = $new_comps;
  392. $path = implode('/', $comps);
  393. if ($initial_slashes) {
  394. $path = str_repeat('/', $initial_slashes) . $path;
  395. }
  396. return $path ? $path : '.';
  397. }
  398. /**
  399. * Return file path related to root dir
  400. *
  401. * @param string $path file path
  402. * @return string
  403. * @author Dmitry (dio) Levashov
  404. **/
  405. protected function _relpath($path) {
  406. return $path == $this->root ? '' : substr($path, strlen($this->root)+1);
  407. }
  408. /**
  409. * Convert path related to root dir into real path
  410. *
  411. * @param string $path file path
  412. * @return string
  413. * @author Dmitry (dio) Levashov
  414. **/
  415. protected function _abspath($path) {
  416. return $path == $this->separator ? $this->root : $this->root.$this->separator.$path;
  417. }
  418. /**
  419. * Return fake path started from root dir
  420. *
  421. * @param string $path file path
  422. * @return string
  423. * @author Dmitry (dio) Levashov
  424. **/
  425. protected function _path($path) {
  426. return $this->rootName.($path == $this->root ? '' : $this->separator.$this->_relpath($path));
  427. }
  428. /**
  429. * Return true if $path is children of $parent
  430. *
  431. * @param string $path path to check
  432. * @param string $parent parent path
  433. * @return bool
  434. * @author Dmitry (dio) Levashov
  435. **/
  436. protected function _inpath($path, $parent) {
  437. return $path == $parent || strpos($path, $parent.'/') === 0;
  438. }
  439. /***************** file stat ********************/
  440. /**
  441. * Return stat for given path.
  442. * Stat contains following fields:
  443. * - (int) size file size in b. required
  444. * - (int) ts file modification time in unix time. required
  445. * - (string) mime mimetype. required for folders, others - optionally
  446. * - (bool) read read permissions. required
  447. * - (bool) write write permissions. required
  448. * - (bool) locked is object locked. optionally
  449. * - (bool) hidden is object hidden. optionally
  450. * - (string) alias for symlinks - link target path relative to root path. optionally
  451. * - (string) target for symlinks - link target path. optionally
  452. *
  453. * If file does not exists - returns empty array or false.
  454. *
  455. * @param string $path file path
  456. * @return array|false
  457. * @author Dmitry (dio) Levashov
  458. **/
  459. protected function _stat($path) {
  460. $raw = ftp_raw($this->connect, 'MLST '.$path);
  461. if (is_array($raw) && count($raw) > 1 && substr(trim($raw[0]), 0, 1) == 2) {
  462. $parts = explode(';', trim($raw[1]));
  463. array_pop($parts);
  464. $parts = array_map('strtolower', $parts);
  465. $stat = array();
  466. // debug($parts);
  467. foreach ($parts as $part) {
  468. list($key, $val) = explode('=', $part);
  469. switch ($key) {
  470. case 'type':
  471. $stat['mime'] = strpos($val, 'dir') !== false ? 'directory' : $this->mimetype($path);
  472. break;
  473. case 'size':
  474. $stat['size'] = $val;
  475. break;
  476. case 'modify':
  477. $ts = mktime(intval(substr($val, 8, 2)), intval(substr($val, 10, 2)), intval(substr($val, 12, 2)), intval(substr($val, 4, 2)), intval(substr($val, 6, 2)), substr($val, 0, 4));
  478. $stat['ts'] = $ts;
  479. // $stat['date'] = $this->formatDate($ts);
  480. break;
  481. case 'unix.mode':
  482. $stat['chmod'] = $val;
  483. break;
  484. case 'perm':
  485. $val = strtolower($val);
  486. $stat['read'] = (int)preg_match('/e|l|r/', $val);
  487. $stat['write'] = (int)preg_match('/w|m|c/', $val);
  488. if (!preg_match('/f|d/', $val)) {
  489. $stat['locked'] = 1;
  490. }
  491. break;
  492. }
  493. }
  494. if (empty($stat['mime'])) {
  495. return array();
  496. }
  497. if ($stat['mime'] == 'directory') {
  498. $stat['size'] = 0;
  499. }
  500. if (isset($stat['chmod'])) {
  501. $stat['perm'] = '';
  502. if ($stat['chmod'][0] == 0) {
  503. $stat['chmod'] = substr($stat['chmod'], 1);
  504. }
  505. for ($i = 0; $i <= 2; $i++) {
  506. $perm[$i] = array(false, false, false);
  507. $n = isset($stat['chmod'][$i]) ? $stat['chmod'][$i] : 0;
  508. if ($n - 4 >= 0) {
  509. $perm[$i][0] = true;
  510. $n = $n - 4;
  511. $stat['perm'] .= 'r';
  512. } else {
  513. $stat['perm'] .= '-';
  514. }
  515. if ($n - 2 >= 0) {
  516. $perm[$i][1] = true;
  517. $n = $n - 2;
  518. $stat['perm'] .= 'w';
  519. } else {
  520. $stat['perm'] .= '-';
  521. }
  522. if ($n - 1 == 0) {
  523. $perm[$i][2] = true;
  524. $stat['perm'] .= 'x';
  525. } else {
  526. $stat['perm'] .= '-';
  527. }
  528. $stat['perm'] .= ' ';
  529. }
  530. $stat['perm'] = trim($stat['perm']);
  531. $owner = $this->options['owner'];
  532. $read = ($owner && $perm[0][0]) || $perm[1][0] || $perm[2][0];
  533. $stat['read'] = $stat['mime'] == 'directory' ? $read && (($owner && $perm[0][2]) || $perm[1][2] || $perm[2][2]) : $read;
  534. $stat['write'] = ($owner && $perm[0][1]) || $perm[1][1] || $perm[2][1];
  535. unset($stat['chmod']);
  536. }
  537. return $stat;
  538. }
  539. return array();
  540. }
  541. /**
  542. * Return true if path is dir and has at least one childs directory
  543. *
  544. * @param string $path dir path
  545. * @return bool
  546. * @author Dmitry (dio) Levashov
  547. **/
  548. protected function _subdirs($path) {
  549. if (preg_match('/\s|\'|\"/', $path)) {
  550. foreach (ftp_nlist($this->connect, $path) as $p) {
  551. if (($stat = $this->stat($path.'/'.$p)) && $stat['mime'] == 'directory') {
  552. return true;
  553. }
  554. }
  555. return false;
  556. }
  557. foreach (ftp_rawlist($this->connect, $path) as $str) {
  558. if (($stat = $this->parseRaw($str)) && $stat['mime'] == 'directory') {
  559. return true;
  560. }
  561. }
  562. return false;
  563. }
  564. /**
  565. * Return object width and height
  566. * Ususaly used for images, but can be realize for video etc...
  567. *
  568. * @param string $path file path
  569. * @param string $mime file mime type
  570. * @return string
  571. * @author Dmitry (dio) Levashov
  572. **/
  573. protected function _dimensions($path, $mime) {
  574. return false;
  575. }
  576. /******************** file/dir content *********************/
  577. /**
  578. * Return files list in directory.
  579. *
  580. * @param string $path dir path
  581. * @return array
  582. * @author Dmitry (dio) Levashov
  583. * @author Cem (DiscoFever)
  584. **/
  585. protected function _scandir($path) {
  586. $files = array();
  587. foreach (ftp_rawlist($this->connect, $path) as $str) {
  588. if (($stat = $this->parseRaw($str))) {
  589. $files[] = $path.DIRECTORY_SEPARATOR.$stat['name'];
  590. }
  591. }
  592. return $files;
  593. }
  594. /**
  595. * Open file and return file pointer
  596. *
  597. * @param string $path file path
  598. * @param bool $write open file for writing
  599. * @return resource|false
  600. * @author Dmitry (dio) Levashov
  601. **/
  602. protected function _fopen($path, $mode='rb') {
  603. if ($this->tmp) {
  604. $local = $this->tmp.DIRECTORY_SEPARATOR.md5($path);
  605. if (ftp_get($this->connect, $local, $path, FTP_BINARY)) {
  606. return @fopen($local, $mode);
  607. }
  608. }
  609. return false;
  610. }
  611. /**
  612. * Close opened file
  613. *
  614. * @param resource $fp file pointer
  615. * @return bool
  616. * @author Dmitry (dio) Levashov
  617. **/
  618. protected function _fclose($fp, $path='') {
  619. @fclose($fp);
  620. if ($path) {
  621. @unlink($this->tmp.DIRECTORY_SEPARATOR.md5($path));
  622. }
  623. }
  624. /******************** file/dir manipulations *************************/
  625. /**
  626. * Create dir and return created dir path or false on failed
  627. *
  628. * @param string $path parent dir path
  629. * @param string $name new directory name
  630. * @return string|bool
  631. * @author Dmitry (dio) Levashov
  632. **/
  633. protected function _mkdir($path, $name) {
  634. $path = $path.'/'.$name;
  635. if (ftp_mkdir($this->connect, $path) === false) {
  636. return false;
  637. }
  638. $this->options['dirMode'] && @ftp_chmod($this->connect, $this->options['dirMode'], $path);
  639. return $path;
  640. }
  641. /**
  642. * Create file and return it's path or false on failed
  643. *
  644. * @param string $path parent dir path
  645. * @param string $name new file name
  646. * @return string|bool
  647. * @author Dmitry (dio) Levashov
  648. **/
  649. protected function _mkfile($path, $name) {
  650. if ($this->tmp) {
  651. $path = $path.'/'.$name;
  652. $local = $this->tmp.DIRECTORY_SEPARATOR.md5($path);
  653. $res = touch($local) && ftp_put($this->connect, $path, $local, FTP_ASCII);
  654. @unlink($local);
  655. return $res ? $path : false;
  656. }
  657. return false;
  658. }
  659. /**
  660. * Create symlink. FTP driver does not support symlinks.
  661. *
  662. * @param string $target link target
  663. * @param string $path symlink path
  664. * @return bool
  665. * @author Dmitry (dio) Levashov
  666. **/
  667. protected function _symlink($target, $path, $name) {
  668. return false;
  669. }
  670. /**
  671. * Copy file into another file
  672. *
  673. * @param string $source source file path
  674. * @param string $targetDir target directory path
  675. * @param string $name new file name
  676. * @return bool
  677. * @author Dmitry (dio) Levashov
  678. **/
  679. protected function _copy($source, $targetDir, $name) {
  680. $res = false;
  681. if ($this->tmp) {
  682. $local = $this->tmp.DIRECTORY_SEPARATOR.md5($source);
  683. $target = $targetDir.DIRECTORY_SEPARATOR.$name;
  684. if (ftp_get($this->connect, $local, $source, FTP_BINARY)
  685. && ftp_put($this->connect, $target, $local, $this->ftpMode($target))) {
  686. $res = $target;
  687. }
  688. @unlink($local);
  689. }
  690. return $res;
  691. }
  692. /**
  693. * Move file into another parent dir.
  694. * Return new file path or false.
  695. *
  696. * @param string $source source file path
  697. * @param string $target target dir path
  698. * @param string $name file name
  699. * @return string|bool
  700. * @author Dmitry (dio) Levashov
  701. **/
  702. protected function _move($source, $targetDir, $name) {
  703. $target = $targetDir.DIRECTORY_SEPARATOR.$name;
  704. return ftp_rename($this->connect, $source, $target) ? $target : false;
  705. }
  706. /**
  707. * Remove file
  708. *
  709. * @param string $path file path
  710. * @return bool
  711. * @author Dmitry (dio) Levashov
  712. **/
  713. protected function _unlink($path) {
  714. return ftp_delete($this->connect, $path);
  715. }
  716. /**
  717. * Remove dir
  718. *
  719. * @param string $path dir path
  720. * @return bool
  721. * @author Dmitry (dio) Levashov
  722. **/
  723. protected function _rmdir($path) {
  724. return ftp_rmdir($this->connect, $path);
  725. }
  726. /**
  727. * Create new file and write into it from file pointer.
  728. * Return new file path or false on error.
  729. *
  730. * @param resource $fp file pointer
  731. * @param string $dir target dir path
  732. * @param string $name file name
  733. * @param array $stat file stat (required by some virtual fs)
  734. * @return bool|string
  735. * @author Dmitry (dio) Levashov
  736. **/
  737. protected function _save($fp, $dir, $name, $stat) {
  738. $path = $dir.'/'.$name;
  739. return ftp_fput($this->connect, $path, $fp, $this->ftpMode($path))
  740. ? $path
  741. : false;
  742. }
  743. /**
  744. * Get file contents
  745. *
  746. * @param string $path file path
  747. * @return string|false
  748. * @author Dmitry (dio) Levashov
  749. **/
  750. protected function _getContents($path) {
  751. $contents = '';
  752. if (($fp = $this->_fopen($path))) {
  753. while (!feof($fp)) {
  754. $contents .= fread($fp, 8192);
  755. }
  756. $this->_fclose($fp, $path);
  757. return $contents;
  758. }
  759. return false;
  760. }
  761. /**
  762. * Write a string to a file
  763. *
  764. * @param string $path file path
  765. * @param string $content new file content
  766. * @return bool
  767. * @author Dmitry (dio) Levashov
  768. **/
  769. protected function _filePutContents($path, $content) {
  770. $res = false;
  771. if ($this->tmp) {
  772. $local = $this->tmp.DIRECTORY_SEPARATOR.md5($path).'.txt';
  773. if (@file_put_contents($local, $content, LOCK_EX) !== false
  774. && ($fp = @fopen($local, 'rb'))) {
  775. clearstatcache();
  776. $res = ftp_fput($this->connect, $path, $fp, $this->ftpMode($path));
  777. @fclose($fp);
  778. }
  779. file_exists($local) && @unlink($local);
  780. }
  781. return $res;
  782. }
  783. /**
  784. * Detect available archivers
  785. *
  786. * @return void
  787. **/
  788. protected function _checkArchivers() {
  789. // die('Not yet implemented. (_checkArchivers)');
  790. return array();
  791. }
  792. /**
  793. * Unpack archive
  794. *
  795. * @param string $path archive path
  796. * @param array $arc archiver command and arguments (same as in $this->archivers)
  797. * @return true
  798. * @return void
  799. * @author Dmitry (dio) Levashov
  800. * @author Alexey Sukhotin
  801. **/
  802. protected function _unpack($path, $arc) {
  803. die('Not yet implemented. (_unpack)');
  804. return false;
  805. }
  806. /**
  807. * Recursive symlinks search
  808. *
  809. * @param string $path file/dir path
  810. * @return bool
  811. * @author Dmitry (dio) Levashov
  812. **/
  813. protected function _findSymlinks($path) {
  814. die('Not yet implemented. (_findSymlinks)');
  815. if (is_link($path)) {
  816. return true;
  817. }
  818. if (is_dir($path)) {
  819. foreach (scandir($path) as $name) {
  820. if ($name != '.' && $name != '..') {
  821. $p = $path.DIRECTORY_SEPARATOR.$name;
  822. if (is_link($p)) {
  823. return true;
  824. }
  825. if (is_dir($p) && $this->_findSymlinks($p)) {
  826. return true;
  827. } elseif (is_file($p)) {
  828. $this->archiveSize += filesize($p);
  829. }
  830. }
  831. }
  832. } else {
  833. $this->archiveSize += filesize($path);
  834. }
  835. return false;
  836. }
  837. /**
  838. * Extract files from archive
  839. *
  840. * @param string $path archive path
  841. * @param array $arc archiver command and arguments (same as in $this->archivers)
  842. * @return true
  843. * @author Dmitry (dio) Levashov,
  844. * @author Alexey Sukhotin
  845. **/
  846. protected function _extract($path, $arc)
  847. {
  848. // get current directory
  849. $cwd = getcwd();
  850. $tmpDir = $this->tempDir();
  851. if (!$tmpDir) {
  852. return false;
  853. }
  854. $basename = $this->_basename($path);
  855. $localPath = $tmpDir . DIRECTORY_SEPARATOR . $basename;
  856. if (!ftp_get($this->connect, $localPath, $path, FTP_BINARY)) {
  857. //cleanup
  858. $this->deleteDir($tmpDir);
  859. return false;
  860. }
  861. $remoteDirectory = dirname($path);
  862. chdir($tmpDir);
  863. $command = escapeshellcmd($arc['cmd'] . ' ' . $arc['argc'] . ' "' . $basename . '"');
  864. $descriptorspec = array(
  865. 0 => array("pipe", "r"), // stdin is a pipe that the child will read from
  866. 1 => array("pipe", "w"), // stdout is a pipe that the child will write to
  867. 2 => array("pipe", "w") // stderr is a file to write to
  868. );
  869. $process = proc_open($command, $descriptorspec, $pipes, $cwd);
  870. if (is_resource($process)) {
  871. fclose($pipes[0]);
  872. fclose($pipes[1]);
  873. $return_value = proc_close($process);
  874. }
  875. unlink($basename);
  876. $filesToProcess = elFinderVolumeFTP::listFilesInDirectory($tmpDir, true);
  877. if(!$filesToProcess) {
  878. $this->setError(elFinder::ERROR_EXTRACT_EXEC, $tmpDir." is not a directory");
  879. $this->deleteDir($tmpDir); //cleanup
  880. return false;
  881. }
  882. if (count($filesToProcess) > 1) {
  883. // for several files - create new directory
  884. // create unique name for directory
  885. $name = basename($path);
  886. if (preg_match('/\.((tar\.(gz|bz|bz2|z|lzo))|cpio\.gz|ps\.gz|xcf\.(gz|bz2)|[a-z0-9]{1,4})$/i', $name, $m)) {
  887. $name = substr($name, 0, strlen($name) - strlen($m[0]));
  888. }
  889. $test = dirname($path) . DIRECTORY_SEPARATOR . $name;
  890. if ($this->stat($test)) {
  891. $name = $this->uniqueName(dirname($path), $name, '-', false);
  892. }
  893. $newPath = dirname($path) . DIRECTORY_SEPARATOR . $name;
  894. $success = $this->_mkdir(dirname($path), $name);
  895. foreach ($filesToProcess as $filename) {
  896. if (!$success) {
  897. break;
  898. }
  899. $targetPath = $newPath . DIRECTORY_SEPARATOR . $filename;
  900. if (is_dir($filename)) {
  901. $success = $this->_mkdir($newPath, $filename);
  902. } else {
  903. $success = ftp_put($this->connect, $targetPath, $filename, FTP_BINARY);
  904. }
  905. }
  906. unset($filename);
  907. } else {
  908. $filename = $filesToProcess[0];
  909. $newPath = $remoteDirectory . DIRECTORY_SEPARATOR . $filename;
  910. $success = ftp_put($this->connect, $newPath, $filename, FTP_BINARY);
  911. }
  912. // return to initial directory
  913. chdir($cwd);
  914. //cleanup
  915. if(!$this->deleteDir($tmpDir)) {
  916. return false;
  917. }
  918. if (!$success) {
  919. $this->setError(elFinder::ERROR_FTP_UPLOAD_FILE, $newPath);
  920. return false;
  921. }
  922. $this->clearcache();
  923. return $newPath;
  924. }
  925. /**
  926. * Create archive and return its path
  927. *
  928. * @param string $dir target dir
  929. * @param array $files files names list
  930. * @param string $name archive name
  931. * @param array $arc archiver options
  932. * @return string|bool
  933. * @author Dmitry (dio) Levashov,
  934. * @author Alexey Sukhotin
  935. **/
  936. protected function _archive($dir, $files, $name, $arc)
  937. {
  938. // get current directory
  939. $cwd = getcwd();
  940. $tmpDir = $this->tempDir();
  941. if (!$tmpDir) {
  942. return false;
  943. }
  944. //download data
  945. if (!$this->ftp_download_files($dir, $files, $tmpDir)) {
  946. //cleanup
  947. $this->deleteDir($tmpDir);
  948. return false;
  949. }
  950. // go to the temporary directory
  951. chdir($tmpDir);
  952. // path to local copy of archive
  953. $path = $tmpDir . DIRECTORY_SEPARATOR . $name;
  954. $file_names_string = "";
  955. foreach (scandir($tmpDir) as $filename) {
  956. if ('.' == $filename) {
  957. continue;
  958. }
  959. if ('..' == $filename) {
  960. continue;
  961. }
  962. $file_names_string = $file_names_string . '"' . $filename . '" ';
  963. }
  964. $command = escapeshellcmd($arc['cmd'] . ' ' . $arc['argc'] . ' "' . $name . '" ' . $file_names_string);
  965. $descriptorspec = array(
  966. 0 => array("pipe", "r"), // stdin is a pipe that the child will read from
  967. 1 => array("pipe", "w"), // stdout is a pipe that the child will write to
  968. 2 => array("pipe", "w") // stderr is a file to write to
  969. );
  970. $process = proc_open($command, $descriptorspec, $pipes, $cwd);
  971. if (is_resource($process)) {
  972. fclose($pipes[0]);
  973. fclose($pipes[1]);
  974. $return_value = proc_close($process);
  975. }
  976. $remoteArchiveFile = $dir . DIRECTORY_SEPARATOR . $name;
  977. // upload archive
  978. if (!ftp_put($this->connect, $remoteArchiveFile, $path, FTP_BINARY)) {
  979. $this->setError(elFinder::ERROR_FTP_UPLOAD_FILE, $remoteArchiveFile);
  980. $this->deleteDir($tmpDir); //cleanup
  981. return false;
  982. }
  983. // return to initial work directory
  984. chdir($cwd);
  985. //cleanup
  986. if(!$this->deleteDir($tmpDir)) {
  987. return false;
  988. }
  989. return $remoteArchiveFile;
  990. }
  991. /**
  992. * Create writable temporary directory and return path to it.
  993. * @return string path to the new temporary directory or false in case of error.
  994. */
  995. private function tempDir()
  996. {
  997. $tempPath = tempnam($this->tmp, 'elFinder');
  998. if (!$tempPath) {
  999. $this->setError(elFinder::ERROR_CREATING_TEMP_DIR, $this->tmp);
  1000. return false;
  1001. }
  1002. $success = unlink($tempPath);
  1003. if (!$success) {
  1004. $this->setError(elFinder::ERROR_CREATING_TEMP_DIR, $this->tmp);
  1005. return false;
  1006. }
  1007. $success = mkdir($tempPath, 0700, true);
  1008. if (!$success) {
  1009. $this->setError(elFinder::ERROR_CREATING_TEMP_DIR, $this->tmp);
  1010. return false;
  1011. }
  1012. return $tempPath;
  1013. }
  1014. /**
  1015. * Gets in a single FTP request an array of absolute remote FTP paths of files and
  1016. * folders in $remote_directory omitting symbolic links.
  1017. * @param $remote_directory string remote FTP path to scan for file and folders recursively
  1018. * @return array of elements each of which is an array of two elements:
  1019. * <ul>
  1020. * <li>$item['path'] - absolute remote FTP path</li>
  1021. * <li>$item['type'] - either 'f' for file or 'd' for directory</li>
  1022. * </ul>
  1023. */
  1024. protected function ftp_scan_dir($remote_directory)
  1025. {
  1026. $buff = ftp_rawlist($this->connect, $remote_directory, true);
  1027. $next_folder = false;
  1028. $items = array();
  1029. foreach ($buff as $str) {
  1030. if ('' == $str) {
  1031. $next_folder = true;
  1032. continue;
  1033. }
  1034. if ($next_folder) {
  1035. $remote_directory = preg_replace('/\:/', '', $str);
  1036. $next_folder = false;
  1037. $item = array();
  1038. $item['path'] = $remote_directory;
  1039. $item['type'] = 'd'; // directory
  1040. $items[] = $item;
  1041. continue;
  1042. }
  1043. $info = preg_split("/\s+/", $str, 9);
  1044. $type = substr($info[0], 0, 1);
  1045. switch ($type) {
  1046. case 'l' : //omit symbolic links
  1047. case 'd' :
  1048. break;
  1049. default:
  1050. $remote_file_path = $remote_directory . DIRECTORY_SEPARATOR . $info[8];
  1051. $item = array();
  1052. $item['path'] = $remote_file_path;
  1053. $item['type'] = 'f'; // normal file
  1054. $items[] = $item;
  1055. }
  1056. }
  1057. return $items;
  1058. }
  1059. /**
  1060. * Downloads specified files from remote directory
  1061. * if there is a directory among files it is downloaded recursively (omitting symbolic links).
  1062. * @param $remote_directory string remote FTP path to a source directory to download from.
  1063. * @param array $files list of files to download from remote directory.
  1064. * @param $dest_local_directory string destination folder to store downloaded files.
  1065. * @return bool true on success and false on failure.
  1066. */
  1067. private function ftp_download_files($remote_directory, array $files, $dest_local_directory)
  1068. {
  1069. $contents = $this->ftp_scan_dir($remote_directory);
  1070. if (!isset($contents)) {
  1071. $this->setError(elFinder::ERROR_FTP_DOWNLOAD_FILE, $remote_directory);
  1072. return false;
  1073. }
  1074. foreach ($contents as $item) {
  1075. $drop = true;
  1076. foreach ($files as $file) {
  1077. if ($remote_directory . DIRECTORY_SEPARATOR . $file == $item['path'] || strstr($item['path'], $remote_directory . DIRECTORY_SEPARATOR . $file . DIRECTORY_SEPARATOR)) {
  1078. $drop = false;
  1079. break;
  1080. }
  1081. }
  1082. if ($drop) continue;
  1083. $relative_path = str_replace($remote_directory, '', $item['path']);
  1084. $local_path = $dest_local_directory . DIRECTORY_SEPARATOR . $relative_path;
  1085. switch ($item['type']) {
  1086. case 'd':
  1087. $success = mkdir($local_path);
  1088. break;
  1089. case 'f':
  1090. $success = ftp_get($this->connect, $local_path, $item['path'], FTP_BINARY);
  1091. break;
  1092. default:
  1093. $success = true;
  1094. }
  1095. if (!$success) {
  1096. $this->setError(elFinder::ERROR_FTP_DOWNLOAD_FILE, $remote_directory);
  1097. return false;
  1098. }
  1099. }
  1100. return true;
  1101. }
  1102. /**
  1103. * Delete local directory recursively.
  1104. * @param $dirPath string to directory to be erased.
  1105. * @return bool true on success and false on failure.
  1106. */
  1107. private function deleteDir($dirPath)
  1108. {
  1109. if (!is_dir($dirPath)) {
  1110. $success = unlink($dirPath);
  1111. } else {
  1112. $success = true;
  1113. foreach (array_reverse(elFinderVolumeFTP::listFilesInDirectory($dirPath, false)) as $path) {
  1114. $path = $dirPath . DIRECTORY_SEPARATOR . $path;
  1115. if(is_link($path)) {
  1116. unlink($path);
  1117. } else if (is_dir($path)) {
  1118. $success = rmdir($path);
  1119. } else {
  1120. $success = unlink($path);
  1121. }
  1122. if (!$success) {
  1123. break;
  1124. }
  1125. }
  1126. if($success) {
  1127. $success = rmdir($dirPath);
  1128. }
  1129. }
  1130. if(!$success) {
  1131. $this->setError(elFinder::ERROR_RM, $dirPath);
  1132. return false;
  1133. }
  1134. return $success;
  1135. }
  1136. /**
  1137. * Returns array of strings containing all files and folders in the specified local directory.
  1138. * @param $dir
  1139. * @param string $prefix
  1140. * @internal param string $path path to directory to scan.
  1141. * @return array array of files and folders names relative to the $path
  1142. * or an empty array if the directory $path is empty,
  1143. * <br />
  1144. * false if $path is not a directory or does not exist.
  1145. */
  1146. private static function listFilesInDirectory($dir, $omitSymlinks, $prefix = '')
  1147. {
  1148. if (!is_dir($dir)) {
  1149. return false;
  1150. }
  1151. $excludes = array(".","..");
  1152. $result = array();
  1153. $files = scandir($dir);
  1154. if(!$files) {
  1155. return array();
  1156. }
  1157. foreach($files as $file) {
  1158. if(!in_array($file, $excludes)) {
  1159. $path = $dir.DIRECTORY_SEPARATOR.$file;
  1160. if(is_link($path)) {
  1161. if($omitSymlinks) {
  1162. continue;
  1163. } else {
  1164. $result[] = $prefix.$file;
  1165. }
  1166. } else if(is_dir($path)) {
  1167. $result[] = $prefix.$file.DIRECTORY_SEPARATOR;
  1168. $subs = elFinderVolumeFTP::listFilesInDirectory($path, $omitSymlinks, $prefix.$file.DIRECTORY_SEPARATOR);
  1169. if($subs) {
  1170. $result = array_merge($result, $subs);
  1171. }
  1172. } else {
  1173. $result[] = $prefix.$file;
  1174. }
  1175. }
  1176. }
  1177. return $result;
  1178. }
  1179. /**
  1180. * Resize image
  1181. * @param string $hash
  1182. * @param int $width
  1183. * @param int $height
  1184. * @param int $x
  1185. * @param int $y
  1186. * @param string $mode
  1187. * @param string $bg
  1188. * @param int $degree
  1189. * @return array|bool|false
  1190. */
  1191. public function resize($hash, $width, $height, $x, $y, $mode = 'resize', $bg = '', $degree = 0) {
  1192. if ($this->commandDisabled('resize')) {
  1193. return $this->setError(elFinder::ERROR_PERM_DENIED);
  1194. }
  1195. if (($file = $this->file($hash)) == false) {
  1196. return $this->setError(elFinder::ERROR_FILE_NOT_FOUND);
  1197. }
  1198. if (!$file['write'] || !$file['read']) {
  1199. return $this->setError(elFinder::ERROR_PERM_DENIED);
  1200. }
  1201. $path = $this->decode($hash);
  1202. $tmpDir = $this->tempDir();
  1203. if (!$tmpDir) {
  1204. return false;
  1205. }
  1206. $local_path = $tmpDir . DIRECTORY_SEPARATOR . basename($path);
  1207. $remote_directory = ftp_pwd($this->connect);
  1208. $success = ftp_get($this->connect, $local_path, $path, FTP_BINARY);
  1209. if (!$success) {
  1210. $this->setError(elFinder::ERROR_FTP_DOWNLOAD_FILE, $remote_directory);
  1211. return false;
  1212. }
  1213. if (!$this->canResize($path, $file)) {
  1214. return $this->setError(elFinder::ERROR_UNSUPPORT_TYPE);
  1215. }
  1216. switch($mode) {
  1217. case 'propresize':
  1218. $result = $this->imgResize($local_path, $width, $height, true, true);
  1219. break;
  1220. case 'crop':
  1221. $result = $this->imgCrop($local_path, $width, $height, $x, $y);
  1222. break;
  1223. case 'fitsquare':
  1224. $result = $this->imgSquareFit($local_path, $width, $height, 'center', 'middle', ($bg ? $bg : $this->options['tmbBgColor']));
  1225. break;
  1226. case 'rotate':
  1227. $result = $this->imgRotate($local_path, $degree, ($bg ? $bg : $this->options['tmbBgColor']));
  1228. break;
  1229. default:
  1230. $result = $this->imgResize($local_path, $width, $height, false, true);
  1231. break;
  1232. }
  1233. if ($result) {
  1234. // upload to FTP and clear temp local file
  1235. if (!ftp_put($this->connect, $path, $local_path, FTP_BINARY)) {
  1236. $this->setError(elFinder::ERROR_FTP_UPLOAD_FILE, $path);
  1237. $this->deleteDir($tmpDir); //cleanup
  1238. }
  1239. $this->clearcache();
  1240. return $this->stat($path);
  1241. }
  1242. $this->setError(elFinder::ERROR_UNKNOWN);
  1243. return false;
  1244. }
  1245. } // END class