国内流行的内容管理系统(CMS)多端全媒体解决方案 https://www.dedebiz.com
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

618 lines
16KB

  1. <?php if (!defined('DEDEINC')) exit('dedebiz');
  2. /**
  3. * FTP 操作类
  4. * 不支持 SFTP 和 SSL FTP 协议, 仅支持标准 FTP 协议.
  5. * 需要传递一个数组配置
  6. * 示例:
  7. * $config['hostname'] = 'ftp.example.com';
  8. * $config['username'] = 'your-username';
  9. * $config['password'] = 'your-password';
  10. * $config['debug'] = TRUE;
  11. *
  12. * @version $Id: ftp.class.php 1 2010-07-05 11:43:09Z tianya $
  13. * @package DedeBIZ.Libraries
  14. * @copyright Copyright (c) 2020, DedeBIZ.COM
  15. * @license https://www.dedebiz.com/license
  16. * @link https://www.dedebiz.com
  17. */
  18. @set_time_limit(1000);
  19. class FTP
  20. {
  21. var $hostname = '';
  22. var $username = '';
  23. var $password = '';
  24. var $port = 21;
  25. var $passive = TRUE;
  26. var $debug = FALSE;
  27. var $conn_id = FALSE;
  28. function __construct($config = array())
  29. {
  30. $this->FTP($config);
  31. }
  32. /**
  33. * 析构函数 - 设置参数
  34. *
  35. * 构造函数则传递一个配置数组
  36. */
  37. function FTP($config = array())
  38. {
  39. if (count($config) > 0) {
  40. $this->initialize($config);
  41. }
  42. }
  43. /**
  44. * 初始化设置
  45. *
  46. * @access public
  47. * @param array
  48. * @return void
  49. */
  50. function initialize($config = array())
  51. {
  52. foreach ($config as $key => $val) {
  53. if (isset($this->$key)) {
  54. $this->$key = $val;
  55. }
  56. }
  57. // 准备主机名
  58. $this->hostname = preg_replace('|.+?://|', '', $this->hostname);
  59. }
  60. /**
  61. * FTP 链接
  62. *
  63. * @access public
  64. * @param array 链接值
  65. * @return bool
  66. */
  67. function connect($config = array())
  68. {
  69. if (count($config) > 0) {
  70. $this->initialize($config);
  71. }
  72. if (FALSE === ($this->conn_id = @ftp_connect($this->hostname, $this->port))) {
  73. if ($this->debug == TRUE) {
  74. $this->_error('无法链接');
  75. }
  76. return FALSE;
  77. }
  78. if (!$this->_login()) {
  79. if ($this->debug == TRUE) {
  80. $this->_error('无法登录');
  81. }
  82. return FALSE;
  83. }
  84. // 如果需要则设置传输模式
  85. if ($this->passive == TRUE) {
  86. ftp_pasv($this->conn_id, TRUE);
  87. }
  88. return TRUE;
  89. }
  90. /**
  91. * FTP 登录
  92. *
  93. * @access private
  94. * @return bool
  95. */
  96. function _login()
  97. {
  98. return @ftp_login($this->conn_id, $this->username, $this->password);
  99. }
  100. /**
  101. * 验证连接ID
  102. *
  103. * @access private
  104. * @return bool
  105. */
  106. function _is_conn()
  107. {
  108. if (!is_resource($this->conn_id)) {
  109. if ($this->debug == TRUE) {
  110. $this->_error('无法链接');
  111. }
  112. return FALSE;
  113. }
  114. return TRUE;
  115. }
  116. /**
  117. * 更改目录
  118. * 第二个参数可以让我们暂时关闭,以便调试
  119. * 此功能可用于检测是否存在一个文件夹
  120. * 抛出一个错误。没有什么的FTP相当于is_dir()
  121. * 因此,我们试图改变某一特定目录。
  122. *
  123. * @access public
  124. * @param string
  125. * @param bool
  126. * @return bool
  127. */
  128. function changedir($path = '', $supress_debug = FALSE)
  129. {
  130. if ($path == '' or !$this->_is_conn()) {
  131. return FALSE;
  132. }
  133. $result = @ftp_chdir($this->conn_id, $path);
  134. if ($result === FALSE) {
  135. if ($this->debug == TRUE and $supress_debug == FALSE) {
  136. $this->_error('无法更改目录');
  137. }
  138. return FALSE;
  139. }
  140. return TRUE;
  141. }
  142. /**
  143. * 创建一个目录
  144. *
  145. * @access public
  146. * @param string
  147. * @return bool
  148. */
  149. function mkdir($path = '', $permissions = NULL)
  150. {
  151. if ($path == '' or !$this->_is_conn()) {
  152. return FALSE;
  153. }
  154. $result = @ftp_mkdir($this->conn_id, $path);
  155. if ($result === FALSE) {
  156. if ($this->debug == TRUE) {
  157. $this->_error('无法创建文件夹');
  158. }
  159. return FALSE;
  160. }
  161. // 如果需要设置权限
  162. if (!is_null($permissions)) {
  163. $this->chmod($path, (int)$permissions);
  164. }
  165. return TRUE;
  166. }
  167. /**
  168. * 创建深级目录
  169. *
  170. * @access public
  171. * @param string
  172. * @return bool
  173. */
  174. function rmkdir($path = '', $pathsymbol = '/')
  175. {
  176. $pathArray = explode($pathsymbol, $path);
  177. $pathstr = $pathsymbol;
  178. foreach ($pathArray as $val) {
  179. if (!empty($val)) {
  180. //构建文件夹路径
  181. $pathstr = $pathstr . $val . $pathsymbol;
  182. if (!$this->_is_conn()) {
  183. return FALSE;
  184. }
  185. $result = @ftp_chdir($this->conn_id, $pathstr);
  186. if ($result === FALSE) {
  187. //如果不存在这个目录则创建
  188. if (!$this->mkdir($pathstr)) {
  189. return FALSE;
  190. }
  191. }
  192. }
  193. }
  194. return TRUE;
  195. }
  196. /**
  197. * 上传一个文件到服务器
  198. *
  199. * @access public
  200. * @param string
  201. * @param string
  202. * @param string
  203. * @return bool
  204. */
  205. function upload($locpath, $rempath, $mode = 'auto', $permissions = NULL)
  206. {
  207. if (!$this->_is_conn()) {
  208. return FALSE;
  209. }
  210. if (!file_exists($locpath)) {
  211. $this->_error('不存在源文件');
  212. return FALSE;
  213. }
  214. // 未指定则设置模式
  215. if ($mode == 'auto') {
  216. // 获取文件扩展名,以便本类上传类型
  217. $ext = $this->_getext($locpath);
  218. $mode = $this->_settype($ext);
  219. }
  220. $mode = ($mode == 'ascii') ? FTP_ASCII : FTP_BINARY;
  221. $result = @ftp_put($this->conn_id, $rempath, $locpath, $mode);
  222. if ($result === FALSE) {
  223. if ($this->debug == TRUE) {
  224. $this->_error('无法上传');
  225. }
  226. return FALSE;
  227. }
  228. // 如果需要设置文件权限
  229. if (!is_null($permissions)) {
  230. $this->chmod($rempath, (int)$permissions);
  231. }
  232. return TRUE;
  233. }
  234. /**
  235. * 重命名(或者移动)一个文件
  236. *
  237. * @access public
  238. * @param string
  239. * @param string
  240. * @param bool
  241. * @return bool
  242. */
  243. function rename($old_file, $new_file, $move = FALSE)
  244. {
  245. if (!$this->_is_conn()) {
  246. return FALSE;
  247. }
  248. $result = @ftp_rename($this->conn_id, $old_file, $new_file);
  249. if ($result === FALSE) {
  250. if ($this->debug == TRUE) {
  251. $msg = ($move == FALSE) ? '无法重命名' : '无法移动';
  252. $this->_error($msg);
  253. }
  254. return FALSE;
  255. }
  256. return TRUE;
  257. }
  258. /**
  259. * 移动一个文件
  260. *
  261. * @access public
  262. * @param string
  263. * @param string
  264. * @return bool
  265. */
  266. function move($old_file, $new_file)
  267. {
  268. return $this->rename($old_file, $new_file, TRUE);
  269. }
  270. /**
  271. * 重命名或者移动一个文件
  272. *
  273. * @access public
  274. * @param string
  275. * @return bool
  276. */
  277. function delete_file($filepath)
  278. {
  279. if (!$this->_is_conn()) {
  280. return FALSE;
  281. }
  282. $result = @ftp_delete($this->conn_id, $filepath);
  283. if ($result === FALSE) {
  284. if ($this->debug == TRUE) {
  285. $this->_error('无法删除');
  286. }
  287. return FALSE;
  288. }
  289. return TRUE;
  290. }
  291. /**
  292. * 删除一个文件夹,递归删除一切(包括子文件夹)中内容
  293. *
  294. * @access public
  295. * @param string
  296. * @return bool
  297. */
  298. function delete_dir($filepath)
  299. {
  300. if (!$this->_is_conn()) {
  301. return FALSE;
  302. }
  303. // 如果需要在尾部加上尾随"/"
  304. $filepath = preg_replace("/(.+?)\/*$/", "\\1/", $filepath);
  305. $list = $this->list_files($filepath);
  306. if ($list !== FALSE and count($list) > 0) {
  307. foreach ($list as $item) {
  308. // 如果我们不能删除该项目,它则可能是一个文件夹
  309. // 将调用 delete_dir()
  310. if (!@ftp_delete($this->conn_id, $item)) {
  311. $this->delete_dir($item);
  312. }
  313. }
  314. }
  315. $result = @ftp_rmdir($this->conn_id, $filepath);
  316. if ($result === FALSE) {
  317. if ($this->debug == TRUE) {
  318. $this->_error('无法删除');
  319. }
  320. return FALSE;
  321. }
  322. return TRUE;
  323. }
  324. /**
  325. * 设置文件权限
  326. *
  327. * @access public
  328. * @param string 文件地址
  329. * @param string 权限
  330. * @return bool
  331. */
  332. function chmod($path, $perm)
  333. {
  334. if (!$this->_is_conn()) {
  335. return FALSE;
  336. }
  337. // 仅PHP5才能运行
  338. if (!function_exists('ftp_chmod')) {
  339. if ($this->debug == TRUE) {
  340. $this->_error('无法更改权限');
  341. }
  342. return FALSE;
  343. }
  344. $result = @ftp_chmod($this->conn_id, $perm, $path);
  345. if ($result === FALSE) {
  346. if ($this->debug == TRUE) {
  347. $this->_error('无法更改权限');
  348. }
  349. return FALSE;
  350. }
  351. return TRUE;
  352. }
  353. /**
  354. * 在指定的目录的FTP文件列表
  355. *
  356. * @access public
  357. * @return array
  358. */
  359. function list_files($path = '.')
  360. {
  361. if (!$this->_is_conn()) {
  362. return FALSE;
  363. }
  364. return ftp_nlist($this->conn_id, $path);
  365. }
  366. /**
  367. * 返回指定目录下文件的详细列表
  368. *
  369. * @access public
  370. * @return array
  371. */
  372. function list_rawfiles($path = '.', $type = 'dir')
  373. {
  374. if (!$this->_is_conn()) {
  375. return FALSE;
  376. }
  377. $ftp_rawlist = ftp_rawlist($this->conn_id, $path, TRUE);
  378. foreach ($ftp_rawlist as $v) {
  379. $info = array();
  380. $vinfo = preg_split("/[\s]+/", $v, 9);
  381. if ($vinfo[0] !== "total") {
  382. $info['chmod'] = $vinfo[0];
  383. $info['num'] = $vinfo[1];
  384. $info['owner'] = $vinfo[2];
  385. $info['group'] = $vinfo[3];
  386. $info['size'] = $vinfo[4];
  387. $info['month'] = $vinfo[5];
  388. $info['day'] = $vinfo[6];
  389. $info['time'] = $vinfo[7];
  390. $info['name'] = $vinfo[8];
  391. $rawlist[$info['name']] = $info;
  392. }
  393. }
  394. $dir = array();
  395. $file = array();
  396. foreach ($rawlist as $k => $v) {
  397. if ($v['chmod'][0] == "d") {
  398. $dir[$k] = $v;
  399. } elseif ($v['chmod'][0] == "-") {
  400. $file[$k] = $v;
  401. }
  402. }
  403. return ($type == 'dir') ? $dir : $file;
  404. }
  405. /**
  406. * 检索一个本地目录下的所有内容(包括子目录和所有文件),并通过FTP为这个目录创建一份镜像。
  407. * 源路径下的任何结构都会被创建到服务器上。你必须给出源路径和目标路径
  408. *
  409. * @access public
  410. * @param string 含有尾随"/"的源路径
  411. * @param string 目标路径 - 含有尾随"/"的文件夹
  412. * @return bool
  413. */
  414. function mirror($locpath, $rempath)
  415. {
  416. if (!$this->_is_conn()) {
  417. return FALSE;
  418. }
  419. // 打开本地文件路径
  420. if ($fp = @opendir($locpath)) {
  421. // 尝试打开远程文件的路径.
  422. if (!$this->changedir($rempath, TRUE)) {
  423. // 如果不能打开则创建
  424. if (!$this->rmkdir($rempath) or !$this->changedir($rempath)) {
  425. return FALSE;
  426. }
  427. }
  428. // 递归读取本地目录
  429. while (FALSE !== ($file = readdir($fp))) {
  430. if (@is_dir($locpath . $file) && substr($file, 0, 1) != '.') {
  431. $this->mirror($locpath . $file . "/", $rempath . $file . "/");
  432. } elseif (substr($file, 0, 1) != ".") {
  433. // 获取文件扩展名,以便本类上传类型
  434. $ext = $this->_getext($file);
  435. $mode = $this->_settype($ext);
  436. $this->upload($locpath . $file, $rempath . $file, $mode);
  437. }
  438. }
  439. return TRUE;
  440. }
  441. return FALSE;
  442. }
  443. /**
  444. * 取出文件扩展名
  445. *
  446. * @access private
  447. * @param string
  448. * @return string
  449. */
  450. function _getext($filename)
  451. {
  452. if (FALSE === strpos($filename, '.')) {
  453. return 'txt';
  454. }
  455. $x = explode('.', $filename);
  456. return end($x);
  457. }
  458. /**
  459. * 设置上传类型
  460. *
  461. * @access private
  462. * @param string
  463. * @return string
  464. */
  465. function _settype($ext)
  466. {
  467. $text_types = array(
  468. 'txt',
  469. 'text',
  470. 'php',
  471. 'phps',
  472. 'php4',
  473. 'js',
  474. 'css',
  475. 'htm',
  476. 'html',
  477. 'phtml',
  478. 'shtml',
  479. 'log',
  480. 'xml'
  481. );
  482. return (in_array($ext, $text_types)) ? 'ascii' : 'binary';
  483. }
  484. /**
  485. * 关闭连接
  486. *
  487. * @access public
  488. * @param string 源路径
  489. * @param string 目的地路径
  490. * @return bool
  491. */
  492. function close()
  493. {
  494. if (!$this->_is_conn()) {
  495. return FALSE;
  496. }
  497. @ftp_close($this->conn_id);
  498. }
  499. /**
  500. * 显示错误信息
  501. *
  502. * @access private
  503. * @param string
  504. * @return bool
  505. */
  506. function _error($msg)
  507. {
  508. $errorTrackFile = dirname(__FILE__) . '/../data/ftp_error_trace.inc';
  509. $emsg = '';
  510. $emsg .= "<div><h3>DedeBIZ Error Warning!</h3>\r\n";
  511. $emsg .= "<div><a href='https://www.dedebiz.com' target='_blank' style='color:red'>Technical Support: https://www.dedebiz.com</a></div>";
  512. $emsg .= "<div style='line-helght:160%;font-size:14px;color:green'>\r\n";
  513. $emsg .= "<div style='color:blue'><br />Error page: <font color='red'>" . $this->GetCurUrl() . "</font></div>\r\n";
  514. $emsg .= "<div>Error infos: {$msg}</div>\r\n";
  515. $emsg .= "<br /></div></div>\r\n";
  516. echo $emsg;
  517. $savemsg = 'Page: ' . $this->GetCurUrl() . "\r\nError: " . $msg;
  518. //保存错误日志
  519. $fp = @fopen($errorTrackFile, 'a');
  520. @fwrite($fp, '<' . '?php exit();' . "\r\n/*\r\n{$savemsg}\r\n*/\r\n?" . ">\r\n");
  521. @fclose($fp);
  522. }
  523. /**
  524. * 获得当前的脚本网址
  525. *
  526. * @access public
  527. * @return string
  528. */
  529. function GetCurUrl()
  530. {
  531. if (!empty($_SERVER["REQUEST_URI"])) {
  532. $scriptName = $_SERVER["REQUEST_URI"];
  533. $nowurl = $scriptName;
  534. } else {
  535. $scriptName = $_SERVER["PHP_SELF"];
  536. if (empty($_SERVER["QUERY_STRING"])) {
  537. $nowurl = $scriptName;
  538. } else {
  539. $nowurl = $scriptName . "?" . $_SERVER["QUERY_STRING"];
  540. }
  541. }
  542. return $nowurl;
  543. }
  544. }//End Class