国内流行的内容管理系统(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.

619 line
16KB

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