PageRenderTime 51ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 0ms

/modules/admin/assets/connectors/elFinderVolumeFTP.class.php

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