<?php   if(!defined('DEDEINC')) exit('dedecms');
/**
 * FTP 操作類
 * 不支持 SFTP 和 SSL FTP 協議, 僅支持標準 FTP 協議.
 * 需要傳遞一個數組配置
 * 示例:
 * $config['hostname'] = 'ftp.example.com';
 * $config['username'] = 'your-username';
 * $config['password'] = 'your-password';
 * $config['debug'] = TRUE;
 *
 * @version        $Id: ftp.class.php 1 2010-07-05 11:43:09Z tianya $
 * @package        DedeCMS.Libraries
 * @copyright      Copyright (c) 2007 - 2010, DesDev, Inc.
 * @license        http://help.dedecms.com/usersguide/license.html
 * @link           http://www.dedecms.com
 */
@set_time_limit(1000);
class FTP {
    var $hostname    = '';
    var $username    = '';
    var $password    = '';
    var $port        = 21;
    var $passive    = TRUE;
    var $debug        = FALSE;
    var $conn_id    = FALSE;

    /**
     * 析構函數 - 設置參數
     *
     * 構造函數則傳遞一個配置數組
     */
    function FTP($config = array())
    {
        if (count($config) > 0)
        {
            $this->initialize($config);
        }
    }

    /**
     * 初始化設置
     *
     * @access    public
     * @param    array
     * @return    void
     */
    function initialize($config = array())
    {
        foreach ($config as $key => $val)
        {
            if (isset($this->$key))
            {
                $this->$key = $val;
            }
        }

        // 準備主機名
        $this->hostname = preg_replace('|.+?://|', '', $this->hostname);
    }

    /**
     * FTP 鏈接
     *
     * @access    public
     * @param    array     鏈接值
     * @return    bool
     */
    function connect($config = array())
    {
        if (count($config) > 0)
        {
            $this->initialize($config);
        }

        if (FALSE === ($this->conn_id = @ftp_connect($this->hostname, $this->port)))
        {
            if ($this->debug == TRUE)
            {
                $this->_error('無法鏈接');
            }
            return FALSE;
        }

        if ( ! $this->_login())
        {
            if ($this->debug == TRUE)
            {
                $this->_error('無法登錄');
            }
            return FALSE;
        }

        // 如果需要則設置傳輸模式
        if ($this->passive == TRUE)
        {
            ftp_pasv($this->conn_id, TRUE);
        }

        return TRUE;
    }

    /**
     * FTP 登錄
     *
     * @access    private
     * @return    bool
     */
    function _login()
    {
        return @ftp_login($this->conn_id, $this->username, $this->password);
    }

    /**
     * 驗證連接ID
     *
     * @access    private
     * @return    bool
     */
    function _is_conn()
    {
        if ( ! is_resource($this->conn_id))
        {
            if ($this->debug == TRUE)
            {
                $this->_error('無法鏈接');
            }
            return FALSE;
        }
        return TRUE;
    }

    /**
     * 更改目錄
     * 第二個參數可以讓我們暫時關閉，以便調試
     * 此功能可用於檢測是否存在一個文件夾
     * 拋出一個錯誤。沒有什麼的FTP相當於is_dir()
     * 因此，我們試圖改變某一特定目錄。
     *
     * @access    public
     * @param    string
     * @param    bool
     * @return    bool
     */
    function changedir($path = '', $supress_debug = FALSE)
    {
        if ($path == '' OR ! $this->_is_conn())
        {
            return FALSE;
        }

        $result = @ftp_chdir($this->conn_id, $path);

        if ($result === FALSE)
        {
            if ($this->debug == TRUE AND $supress_debug == FALSE)
            {
                $this->_error('無法更改目錄');
            }
            return FALSE;
        }

        return TRUE;
    }

    /**
     * 創建一個目錄
     *
     * @access    public
     * @param    string
     * @return    bool
     */
    function mkdir($path = '', $permissions = NULL)
    {
        if ($path == '' OR ! $this->_is_conn())
        {
            return FALSE;
        }

        $result = @ftp_mkdir($this->conn_id, $path);

        if ($result === FALSE)
        {
            if ($this->debug == TRUE)
            {
                $this->_error('無法創建文件夾');
            }
            return FALSE;
        }

        // 如果需要設置權限
        if ( ! is_null($permissions))
        {
            $this->chmod($path, (int)$permissions);
        }

        return TRUE;
    }

    /**
     * 創建深級目錄
     *
     * @access    public
     * @param    string
     * @return    bool
     */
    function rmkdir($path = '', $pathsymbol = '/')
    {
        $pathArray = explode($pathsymbol,$path);
        $pathstr = $pathsymbol;
        foreach($pathArray as $val)
        {
            if(!empty($val))
            {
                //構建文件夾路徑
                $pathstr = $pathstr.$val.$pathsymbol;
                if (! $this->_is_conn())
                {
                    return FALSE;
                }
                $result = @ftp_chdir($this->conn_id, $pathstr);
                if($result === FALSE)
                {
                    //如果不存在這個目錄則創建
                    if(!$this->mkdir($pathstr))
                    {
                        return FALSE;
                    }
                }
            }
        }
        return TRUE;
    }

    /**
     * 上傳一個文件到服務器
     *
     * @access    public
     * @param    string
     * @param    string
     * @param    string
     * @return    bool
     */
    function upload($locpath, $rempath, $mode = 'auto', $permissions = NULL)
    {
        if (!$this->_is_conn())
        {
            return FALSE;
        }

        if (!file_exists($locpath))
        {
            $this->_error('不存在源文件');
            return FALSE;
        }

        // 未指定則設置模式
        if ($mode == 'auto')
        {
            // 獲取文件擴展名，以便本類上傳類型
            $ext = $this->_getext($locpath);
            $mode = $this->_settype($ext);
        }

        $mode = ($mode == 'ascii') ? FTP_ASCII : FTP_BINARY;

        $result = @ftp_put($this->conn_id, $rempath, $locpath, $mode);

        if ($result === FALSE)
        {
            if ($this->debug == TRUE)
            {
                $this->_error('無法上傳');
            }
            return FALSE;
        }

        // 如果需要設置文件權限
        if ( ! is_null($permissions))
        {
            $this->chmod($rempath, (int)$permissions);
        }

        return TRUE;
    }

    /**
     * 重命名(或者移動)一個文件
     *
     * @access    public
     * @param    string
     * @param    string
     * @param    bool
     * @return    bool
     */
    function rename($old_file, $new_file, $move = FALSE)
    {
        if ( ! $this->_is_conn())
        {
            return FALSE;
        }

        $result = @ftp_rename($this->conn_id, $old_file, $new_file);

        if ($result === FALSE)
        {
            if ($this->debug == TRUE)
            {
                $msg = ($move == FALSE) ? '無法重命名' : '無法移動';

                $this->_error($msg);
            }
            return FALSE;
        }

        return TRUE;
    }

    /**
     * 移動一個文件
     *
     * @access    public
     * @param    string
     * @param    string
     * @return    bool
     */
    function move($old_file, $new_file)
    {
        return $this->rename($old_file, $new_file, TRUE);
    }

    /**
     * 重命名或者移動一個文件
     *
     * @access    public
     * @param    string
     * @return    bool
     */
    function delete_file($filepath)
    {
        if ( ! $this->_is_conn())
        {
            return FALSE;
        }

        $result = @ftp_delete($this->conn_id, $filepath);

        if ($result === FALSE)
        {
            if ($this->debug == TRUE)
            {
                $this->_error('無法刪除');
            }
            return FALSE;
        }

        return TRUE;
    }
    
    /**
     * 刪除一個文件夾，遞歸刪除一切（包括子文件夾）中內容
     *
     * @access    public
     * @param    string
     * @return    bool
     */
    function delete_dir($filepath)
    {
        if ( ! $this->_is_conn())
        {
            return FALSE;
        }

        // 如果需要在尾部加上尾隨"/"
        $filepath = preg_replace("/(.+?)\/*$/", "\\1/",  $filepath);

        $list = $this->list_files($filepath);

        if ($list !== FALSE AND count($list) > 0)
        {
            foreach ($list as $item)
            {
                // 如果我們不能刪除該項目,它則可能是一個文件夾
                // 將調用 delete_dir()
                if ( ! @ftp_delete($this->conn_id, $item))
                {
                    $this->delete_dir($item);
                }
            }
        }

        $result = @ftp_rmdir($this->conn_id, $filepath);

        if ($result === FALSE)
        {
            if ($this->debug == TRUE)
            {
                $this->_error('無法刪除');
            }
            return FALSE;
        }

        return TRUE;
    }

    /**
     * 設置文件權限
     *
     * @access    public
     * @param    string     文件地址
     * @param    string    權限
     * @return    bool
     */
    function chmod($path, $perm)
    {
        if ( ! $this->_is_conn())
        {
            return FALSE;
        }

        // 僅PHP5才能運行
        if ( ! function_exists('ftp_chmod'))
        {
            if ($this->debug == TRUE)
            {
                $this->_error('無法更改權限');
            }
            return FALSE;
        }

        $result = @ftp_chmod($this->conn_id, $perm, $path);

        if ($result === FALSE)
        {
            if ($this->debug == TRUE)
            {
                $this->_error('無法更改權限');
            }
            return FALSE;
        }

        return TRUE;
    }

    /**
     * 在指定的目錄的FTP文件列表
     *
     * @access    public
     * @return    array
     */
    function list_files($path = '.')
    {
        if ( ! $this->_is_conn())
        {
            return FALSE;
        }

        return ftp_nlist($this->conn_id, $path);
    }
    
    /**
     * 返回指定目錄下文件的詳細列表
     *
     * @access    public
     * @return    array
     */
    function list_rawfiles($path = '.', $type='dir')
    {
        if ( ! $this->_is_conn())
        {
            return FALSE;
        }
        
        $ftp_rawlist = ftp_rawlist($this->conn_id, $path, TRUE);
      foreach ($ftp_rawlist as $v) {
        $info = array();
        $vinfo = preg_split("/[\s]+/", $v, 9);
        if ($vinfo[0] !== "total") {
          $info['chmod'] = $vinfo[0];
          $info['num'] = $vinfo[1];
          $info['owner'] = $vinfo[2];
          $info['group'] = $vinfo[3];
          $info['size'] = $vinfo[4];
          $info['month'] = $vinfo[5];
          $info['day'] = $vinfo[6];
          $info['time'] = $vinfo[7];
          $info['name'] = $vinfo[8];
          $rawlist[$info['name']] = $info;
        }
      }
      
      $dir = array();
      $file = array();
      foreach ($rawlist as $k => $v) {
        if ($v['chmod']{0} == "d") {
          $dir[$k] = $v;
        } elseif ($v['chmod']{0} == "-") {
          $file[$k] = $v;
        }
      }

      return ($type == 'dir')? $dir : $file;
    }

    /**
     * 檢索一個本地目錄下的所有內容(包括子目錄和所有文件)，並通過FTP為這個目錄創建一份鏡像。
     * 源路徑下的任何結構都會被創建到服務器上。你必須給出源路徑和目標路徑
     *
     * @access    public
     * @param    string    含有尾隨"/"的源路徑
     * @param    string    目標路徑 - 含有尾隨"/"的文件夾
     * @return    bool
     */
    function mirror($locpath, $rempath)
    {
        if ( ! $this->_is_conn())
        {
            return FALSE;
        }

        // 打開本地文件路徑
        if ($fp = @opendir($locpath))
        {
            // 嘗試打開遠程文件的路徑.
            if ( ! $this->changedir($rempath, TRUE))
            {
                // 如果不能打開則創建
                if ( ! $this->rmkdir($rempath) OR ! $this->changedir($rempath))
                {
                    return FALSE;
                }
            }

            // 遞歸讀取本地目錄
            while (FALSE !== ($file = readdir($fp)))
            {
                if (@is_dir($locpath.$file) && substr($file, 0, 1) != '.')
                {
                    $this->mirror($locpath.$file."/", $rempath.$file."/");
                }
                elseif (substr($file, 0, 1) != ".")
                {
                    // 獲取文件擴展名，以便本類上傳類型
                    $ext = $this->_getext($file);
                    $mode = $this->_settype($ext);

                    $this->upload($locpath.$file, $rempath.$file, $mode);
                }
            }
            return TRUE;
        }

        return FALSE;
    }

    /**
     * 取出文件擴展名
     *
     * @access    private
     * @param    string
     * @return    string
     */
    function _getext($filename)
    {
        if (FALSE === strpos($filename, '.'))
        {
            return 'txt';
        }

        $x = explode('.', $filename);
        return end($x);
    }

    /**
     * 設置上傳類型
     *
     * @access    private
     * @param    string
     * @return    string
     */
    function _settype($ext)
    {
        $text_types = array(
                            'txt',
                            'text',
                            'php',
                            'phps',
                            'php4',
                            'js',
                            'css',
                            'htm',
                            'html',
                            'phtml',
                            'shtml',
                            'log',
                            'xml'
                            );


        return (in_array($ext, $text_types)) ? 'ascii' : 'binary';
    }

    /**
     * 關閉連接
     *
     * @access    public
     * @param    string    源路徑
     * @param    string    目的地路徑
     * @return    bool
     */
    function close()
    {
        if ( ! $this->_is_conn())
        {
            return FALSE;
        }

        @ftp_close($this->conn_id);
    }
    
    /**
     * 顯示錯誤信息
     *
     * @access    private
     * @param    string
     * @return    bool
     */
    function _error($msg)
    {
        $errorTrackFile = dirname(__FILE__).'/../data/ftp_error_trace.inc';
        $emsg = '';
        $emsg .= "<div><h3>DedeCMS Error Warning!</h3>\r\n";
        $emsg .= "<div><a href='http://bbs.dedecms.com' target='_blank' style='color:red'>Technical Support: http://bbs.dedecms.com</a></div>";
        $emsg .= "<div style='line-helght:160%;font-size:14px;color:green'>\r\n";
        $emsg .= "<div style='color:blue'><br />Error page: <font color='red'>".$this->GetCurUrl()."</font></div>\r\n";
        $emsg .= "<div>Error infos: {$msg}</div>\r\n";
        $emsg .= "<br /></div></div>\r\n";
        
        echo $emsg;
        
        $savemsg = 'Page: '.$this->GetCurUrl()."\r\nError: ".$msg;
        //保存錯誤日誌
        $fp = @fopen($errorTrackFile, 'a');
        @fwrite($fp, '<'.'?php  exit();'."\r\n/*\r\n{$savemsg}\r\n*/\r\n?".">\r\n");
        @fclose($fp);
    }

    /**
     * 獲得當前的腳本網址
     *
     * @access    public
     * @return    string
     */
    function GetCurUrl()
    {
        if(!empty($_SERVER["REQUEST_URI"]))
        {
            $scriptName = $_SERVER["REQUEST_URI"];
            $nowurl = $scriptName;
        }
        else
        {
            $scriptName = $_SERVER["PHP_SELF"];
            if(empty($_SERVER["QUERY_STRING"])) {
                $nowurl = $scriptName;
            }
            else {
                $nowurl = $scriptName."?".$_SERVER["QUERY_STRING"];
            }
        }
        return $nowurl;
    }

}//End Class