Seditio Source
Root |
./othercms/slaed_cms_6.2_pro/plugins/filemanager/include/FtpClient.php
<?php
/*
 * This file is part of the `nicolab/php-ftp-client` package.
 *
 * (c) Nicolas Tallefourtane <dev@nicolab.net>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright Nicolas Tallefourtane http://nicolab.net
 */
namespace FtpClient;

use \
Countable;

/**
 * The FTP and SSL-FTP client for PHP.
 *
 * @method bool alloc() alloc(int $filesize, string &$result = null) Allocates space for a file to be uploaded
 * @method bool cdup() cdup() Changes to the parent directory
 * @method bool chdir() chdir(string $directory) Changes the current directory on a FTP server
 * @method int chmod() chmod(int $mode, string $filename) Set permissions on a file via FTP
 * @method bool delete() delete(string $path) Deletes a file on the FTP server
 * @method bool exec() exec(string $command) Requests execution of a command on the FTP server
 * @method bool fget() fget(resource $handle, string $remote_file, int $mode, int $resumepos = 0) Downloads a file from the FTP server and saves to an open file
 * @method bool fput() fput(string $remote_file, resource $handle, int $mode, int $startpos = 0) Uploads from an open file to the FTP server
 * @method mixed get_option() get_option(int $option) Retrieves various runtime behaviours of the current FTP stream
 * @method bool get() get(string $local_file, string $remote_file, int $mode, int $resumepos = 0) Downloads a file from the FTP server
 * @method int mdtm() mdtm(string $remote_file) Returns the last modified time of the given file
 * @method int nb_continue() nb_continue() Continues retrieving/sending a file (non-blocking)
 * @method int nb_fget() nb_fget(resource $handle, string $remote_file, int $mode, int $resumepos = 0) Retrieves a file from the FTP server and writes it to an open file (non-blocking)
 * @method int nb_fput() nb_fput(string $remote_file, resource $handle, int $mode, int $startpos = 0) Stores a file from an open file to the FTP server (non-blocking)
 * @method int nb_get() nb_get(string $local_file, string $remote_file, int $mode, int $resumepos = 0) Retrieves a file from the FTP server and writes it to a local file (non-blocking)
 * @method int nb_put() nb_put(string $remote_file, string $local_file, int $mode, int $startpos = 0) Stores a file on the FTP server (non-blocking)
 * @method bool pasv() pasv(bool $pasv) Turns passive mode on or off
 * @method bool put() put(string $remote_file, string $local_file, int $mode, int $startpos = 0) Uploads a file to the FTP server
 * @method string pwd() pwd() Returns the current directory name
 * @method bool quit() quit() Closes an FTP connection
 * @method array raw() raw(string $command) Sends an arbitrary command to an FTP server
 * @method bool rename() rename(string $oldname, string $newname) Renames a file or a directory on the FTP server
 * @method bool set_option() set_option(int $option, mixed $value) Set miscellaneous runtime FTP options
 * @method bool site() site(string $command) Sends a SITE command to the server
 * @method int size() size(string $remote_file) Returns the size of the given file
 * @method string systype() systype() Returns the system type identifier of the remote FTP server
 *
 * @author Nicolas Tallefourtane <dev@nicolab.net>
 */
class FtpClient implements Countable
{
   
/**
     * The connection with the server.
     *
     * @var resource
     */
   
protected $conn;

   
/**
     * PHP FTP functions wrapper.
     *
     * @var FtpWrapper
     */
   
private $ftp;

   
/**
     * Constructor.
     *
     * @param  resource|null $connection
     * @throws FtpException  If FTP extension is not loaded.
     */
   
public function __construct($connection = null)
    {
        if (!
extension_loaded('ftp')) {
            throw new
FtpException('FTP extension is not loaded!');
        }

        if (
$connection) {
           
$this->conn = $connection;
        }

       
$this->setWrapper(new FtpWrapper($this->conn));
    }

   
/**
     * Close the connection when the object is destroyed.
     */
   
public function __destruct()
    {
        if (
$this->conn) {
           
$this->ftp->close();
        }
    }

   
/**
     * Call an internal method or a FTP method handled by the wrapper.
     *
     * Wrap the FTP PHP functions to call as method of FtpClient object.
     * The connection is automaticaly passed to the FTP PHP functions.
     *
     * @param  string       $method
     * @param  array        $arguments
     * @return mixed
     * @throws FtpException When the function is not valid
     */
   
public function __call($method, array $arguments)
    {
        return
$this->ftp->__call($method, $arguments);
    }

   
/**
     * Overwrites the PHP limit
     *
     * @param  string|null $memory            The memory limit, if null is not modified
     * @param  int         $time_limit        The max execution time, unlimited by default
     * @param  bool        $ignore_user_abort Ignore user abort, true by default
     * @return FtpClient
     */
   
public function setPhpLimit($memory = null, $time_limit = 0, $ignore_user_abort = true)
    {
        if (
null !== $memory) {
           
ini_set('memory_limit', $memory);
        }

       
ignore_user_abort(true);
       
set_time_limit($time_limit);

        return
$this;
    }

   
/**
     * Get the help information of the remote FTP server.
     *
     * @return array
     */
   
public function help()
    {
        return
$this->ftp->raw('help');
    }

   
/**
     * Open a FTP connection.
     *
     * @param string $host
     * @param bool   $ssl
     * @param int    $port
     * @param int    $timeout
     *
     * @return FTPClient
     * @throws FtpException If unable to connect
     */
   
public function connect($host, $ssl = false, $port = 21, $timeout = 90)
    {
        if (
$ssl) {
           
$this->conn = @$this->ftp->ssl_connect($host, $port, $timeout);
        } else {
           
$this->conn = @$this->ftp->connect($host, $port, $timeout);
        }

        if (!
$this->conn) {
            throw new
FtpException('Unable to connect');
        }

        return
$this;
    }

   
/**
     * Closes the current FTP connection.
     *
     * @return bool
     */
   
public function close()
    {
        if (
$this->conn) {
           
$this->ftp->close();
           
$this->conn = null;
        }
    }

   
/**
     * Get the connection with the server.
     *
     * @return resource
     */
   
public function getConnection()
    {
        return
$this->conn;
    }

   
/**
     * Get the wrapper.
     *
     * @return FtpWrapper
     */
   
public function getWrapper()
    {
        return
$this->ftp;
    }

   
/**
     * Logs in to an FTP connection.
     *
     * @param string $username
     * @param string $password
     *
     * @return FtpClient
     * @throws FtpException If the login is incorrect
     */
   
public function login($username = 'anonymous', $password = '')
    {
       
$result = $this->ftp->login($username, $password);

        if (
$result === false) {
            throw new
FtpException('Login incorrect');
        }

        return
$this;
    }

   
/**
     * Returns the last modified time of the given file.
     * Return -1 on error
     *
     * @param string $remoteFile
     * @param string|null $format
     *
     * @return int
     */
   
public function modifiedTime($remoteFile, $format = null)
    {
       
$time = $this->ftp->mdtm($remoteFile);

        if (
$time !== -1 && $format !== null) {
            return
date($format, $time);
        }

        return
$time;
    }

   
/**
     * Changes to the parent directory.
     *
     * @throws FtpException
     * @return FtpClient
     */
   
public function up()
    {
       
$result = @$this->ftp->cdup();

        if (
$result === false) {
            throw new
FtpException('Unable to get parent folder');
        }

        return
$this;
    }

   
/**
     * Returns a list of files in the given directory.
     *
     * @param string   $directory The directory, by default is "." the current directory
     * @param bool     $recursive
     * @param callable $filter    A callable to filter the result, by default is asort() PHP function.
     *                            The result is passed in array argument,
     *                            must take the argument by reference !
     *                            The callable should proceed with the reference array
     *                            because is the behavior of several PHP sorting
     *                            functions (by reference ensure directly the compatibility
     *                            with all PHP sorting functions).
     *
     * @return array
     * @throws FtpException If unable to list the directory
     */
   
public function nlist($directory = '.', $recursive = false, $filter = 'sort')
    {
        if (!
$this->isDir($directory)) {
            throw new
FtpException('"'.$directory.'" is not a directory');
        }

       
$files = $this->ftp->nlist($directory);

        if (
$files === false) {
            throw new
FtpException('Unable to list directory');
        }

       
$result  = array();
       
$dir_len = strlen($directory);

       
// if it's the current
       
if (false !== ($kdot = array_search('.', $files))) {
            unset(
$files[$kdot]);
        }

       
// if it's the parent
       
if(false !== ($kdot = array_search('..', $files))) {
            unset(
$files[$kdot]);
        }

        if (!
$recursive) {
            foreach (
$files as $file) {
               
$result[] = $directory.'/'.$file;
            }

           
// working with the reference (behavior of several PHP sorting functions)
           
$filter($result);

            return
$result;
        }

       
// utils for recursion
       
$flatten = function (array $arr) use (&$flatten) {

           
$flat = [];

            foreach (
$arr as $k => $v) {
                if (
is_array($v)) {
                   
$flat = array_merge($flat, $flatten($v));
                } else {
                   
$flat[] = $v;
                }
            }

            return
$flat;
        };

        foreach (
$files as $file) {
           
$file = $directory.'/'.$file;

           
// if contains the root path (behavior of the recursivity)
           
if (0 === strpos($file, $directory, $dir_len)) {
               
$file = substr($file, $dir_len);
            }

            if (
$this->isDir($file)) {
               
$result[] = $file;
               
$items    = $flatten($this->nlist($file, true, $filter));

                foreach (
$items as $item) {
                   
$result[] = $item;
                }

            } else {
               
$result[] = $file;
            }
        }

       
$result = array_unique($result);

       
$filter($result);

        return
$result;
    }

   
/**
     * Creates a directory.
     *
     * @see FtpClient::rmdir()
     * @see FtpClient::remove()
     * @see FtpClient::put()
     * @see FtpClient::putAll()
     *
     * @param  string $directory The directory
     * @param  bool   $recursive
     * @return array
     */
   
public function mkdir($directory, $recursive = false)
    {
        if (!
$recursive or $this->isDir($directory)) {
            return
$this->ftp->mkdir($directory);
        }

       
$result = false;
       
$pwd    = $this->ftp->pwd();
       
$parts  = explode('/', $directory);

        foreach (
$parts as $part) {

            if (!@
$this->ftp->chdir($part)) {
               
$result = $this->ftp->mkdir($part);
               
$this->ftp->chdir($part);
            }
        }

       
$this->ftp->chdir($pwd);

        return
$result;
    }

   
/**
     * Remove a directory.
     *
     * @see FtpClient::mkdir()
     * @see FtpClient::cleanDir()
     * @see FtpClient::remove()
     * @see FtpClient::delete()
     * @param  string       $directory
     * @param  bool         $recursive Forces deletion if the directory is not empty
     * @return bool
     * @throws FtpException If unable to list the directory to remove
     */
   
public function rmdir($directory, $recursive = true)
    {
        if (
$recursive) {
           
$files = $this->nlist($directory, false, 'rsort');

           
// remove children
           
foreach ($files as $file) {
               
$this->remove($file, true);
            }
        }

       
// remove the directory
       
return $this->ftp->rmdir($directory);
    }

   
/**
     * Empty directory.
     *
     * @see FtpClient::remove()
     * @see FtpClient::delete()
     * @see FtpClient::rmdir()
     *
     * @param  string $directory
     * @return bool
     */
   
public function cleanDir($directory)
    {
        if(!
$files = $this->nlist($directory)) {
            return
$this->isEmpty($directory);
        }

       
// remove children
       
foreach ($files as $file) {
           
$this->remove($file, true);
        }

        return
$this->isEmpty($directory);
    }

   
/**
     * Remove a file or a directory.
     *
     * @see FtpClient::rmdir()
     * @see FtpClient::cleanDir()
     * @see FtpClient::delete()
     * @param  string $path      The path of the file or directory to remove
     * @param  bool   $recursive Is effective only if $path is a directory, {@see FtpClient::rmdir()}
     * @return bool
     */
   
public function remove($path, $recursive = false)
    {
        try {
            if (@
$this->ftp->delete($path)
            or (
$this->isDir($path) and @$this->rmdir($path, $recursive))) {
                return
true;
            }

            return
false;
        } catch (\
Exception $e) {
            return
false;
        }
    }

   
/**
     * Check if a directory exist.
     *
     * @param string $directory
     * @return bool
     * @throws FtpException
     */
   
public function isDir($directory)
    {
       
$pwd = $this->ftp->pwd();

        if (
$pwd === false) {
            throw new
FtpException('Unable to resolve the current directory');
        }

        if (@
$this->ftp->chdir($directory)) {
           
$this->ftp->chdir($pwd);
            return
true;
        }

       
$this->ftp->chdir($pwd);

        return
false;
    }

   
/**
     * Check if a directory is empty.
     *
     * @param  string $directory
     * @return bool
     */
   
public function isEmpty($directory)
    {
        return
$this->count($directory, null, false) === 0 ? true : false;
    }

   
/**
     * Scan a directory and returns the details of each item.
     *
     * @see FtpClient::nlist()
     * @see FtpClient::rawlist()
     * @see FtpClient::parseRawList()
     * @see FtpClient::dirSize()
     * @param  string $directory
     * @param  bool   $recursive
     * @return array
     */
   
public function scanDir($directory = '.', $recursive = false)
    {
        return
$this->parseRawList($this->rawlist($directory, $recursive));
    }

   
/**
     * Returns the total size of the given directory in bytes.
     *
     * @param  string $directory The directory, by default is the current directory.
     * @param  bool   $recursive true by default
     * @return int    The size in bytes.
     */
   
public function dirSize($directory = '.', $recursive = true)
    {
       
$items = $this->scanDir($directory, $recursive);
       
$size  = 0;

        foreach (
$items as $item) {
           
$size += (int) $item['size'];
        }

        return
$size;
    }

   
/**
     * Count the items (file, directory, link, unknown).
     *
     * @param  string      $directory The directory, by default is the current directory.
     * @param  string|null $type      The type of item to count (file, directory, link, unknown)
     * @param  bool        $recursive true by default
     * @return int
     */
   
public function count($directory = '.', $type = null, $recursive = true)
    {
       
$items  = (null === $type ? $this->nlist($directory, $recursive)
            :
$this->scanDir($directory, $recursive));

       
$count = 0;
        foreach (
$items as $item) {
            if (
null === $type or $item['type'] == $type) {
               
$count++;
            }
        }

        return
$count;
    }

   
/**
     * Uploads a file to the server from a string.
     *
     * @param  string       $remote_file
     * @param  string       $content
     * @return FtpClient
     * @throws FtpException When the transfer fails
     */
   
public function putFromString($remote_file, $content)
    {
       
$handle = fopen('php://temp', 'w');

       
fwrite($handle, $content);
       
rewind($handle);

        if (
$this->ftp->fput($remote_file, $handle, FTP_BINARY)) {
            return
$this;
        }

        throw new
FtpException('Unable to put the file "'.$remote_file.'"');
    }

   
/**
     * Uploads a file to the server.
     *
     * @param  string       $local_file
     * @return FtpClient
     * @throws FtpException When the transfer fails
     */
   
public function putFromPath($local_file)
    {
       
$remote_file = basename($local_file);
       
$handle      = fopen($local_file, 'r');

        if (
$this->ftp->fput($remote_file, $handle, FTP_BINARY)) {
           
rewind($handle);
            return
$this;
        }

        throw new
FtpException(
           
'Unable to put the remote file from the local file "'.$local_file.'"'
       
);
    }

   
/**
     * Upload files.
     *
     * @param  string    $source_directory
     * @param  string    $target_directory
     * @param  int       $mode
     * @return FtpClient
     */
   
public function putAll($source_directory, $target_directory, $mode = FTP_BINARY)
    {
       
$d = dir($source_directory);

       
// do this for each file in the directory
       
while ($file = $d->read()) {

           
// to prevent an infinite loop
           
if ($file != "." && $file != "..") {

               
// do the following if it is a directory
               
if (is_dir($source_directory.'/'.$file)) {

                    if (!
$this->isDir($target_directory.'/'.$file)) {

                       
// create directories that do not yet exist
                       
$this->ftp->mkdir($target_directory.'/'.$file);
                    }

                   
// recursive part
                   
$this->putAll(
                       
$source_directory.'/'.$file, $target_directory.'/'.$file,
                       
$mode
                   
);
                } else {

                   
// put the files
                   
$this->ftp->put(
                       
$target_directory.'/'.$file, $source_directory.'/'.$file,
                       
$mode
                   
);
                }
            }
        }

        return
$this;
    }

   
/**
     * Returns a detailed list of files in the given directory.
     *
     * @see FtpClient::nlist()
     * @see FtpClient::scanDir()
     * @see FtpClient::dirSize()
     * @param  string       $directory The directory, by default is the current directory
     * @param  bool         $recursive
     * @return array
     * @throws FtpException
     */
   
public function rawlist($directory = '.', $recursive = false)
    {
        if (!
$this->isDir($directory)) {
            throw new
FtpException('"'.$directory.'" is not a directory.');
        }

       
$list  = $this->ftp->rawlist($directory);
       
$items = array();

        if (!
$list) {
            return
$items;
        }

        if (
false == $recursive) {

            foreach (
$list as $path => $item) {
               
$chunks = preg_split("/\s+/", $item);

               
// if not "name"
               
if (empty($chunks[8]) || $chunks[8] == '.' || $chunks[8] == '..') {
                    continue;
                }

               
$path = $directory.'/'.$chunks[8];

                if (isset(
$chunks[9])) {
                   
$nbChunks = count($chunks);

                    for (
$i = 9; $i < $nbChunks; $i++) {
                       
$path .= ' '.$chunks[$i];
                    }
                }


                if (
substr($path, 0, 2) == './') {
                   
$path = substr($path, 2);
                }

               
$items[ $this->rawToType($item).'#'.$path ] = $item;
            }

            return
$items;
        }

       
$path = '';

        foreach (
$list as $item) {
           
$len = strlen($item);

            if (!
$len

           
// "."
           
|| ($item[$len-1] == '.' && $item[$len-2] == ' '

           
// ".."
           
or $item[$len-1] == '.' && $item[$len-2] == '.' && $item[$len-3] == ' ')
            ){

                continue;
            }

           
$chunks = preg_split("/\s+/", $item);

           
// if not "name"
           
if (empty($chunks[8]) || $chunks[8] == '.' || $chunks[8] == '..') {
                continue;
            }

           
$path = $directory.'/'.$chunks[8];

            if (isset(
$chunks[9])) {
               
$nbChunks = count($chunks);

                for (
$i = 9; $i < $nbChunks; $i++) {
                   
$path .= ' '.$chunks[$i];
                }
            }

            if (
substr($path, 0, 2) == './') {
               
$path = substr($path, 2);
            }

           
$items[$this->rawToType($item).'#'.$path] = $item;

            if (
$item[0] == 'd') {
               
$sublist = $this->rawlist($path, true);

                foreach (
$sublist as $subpath => $subitem) {
                   
$items[$subpath] = $subitem;
                }
            }
        }

        return
$items;
    }

   
/**
     * Parse raw list.
     *
     * @see FtpClient::rawlist()
     * @see FtpClient::scanDir()
     * @see FtpClient::dirSize()
     * @param  array $rawlist
     * @return array
     */
   
public function parseRawList(array $rawlist)
    {
       
$items = array();
       
$path  = '';

        foreach (
$rawlist as $key => $child) {
           
$chunks = preg_split("/\s+/", $child);

            if (isset(
$chunks[8]) && ($chunks[8] == '.' or $chunks[8] == '..')) {
                continue;
            }

            if (
count($chunks) === 1) {
               
$len = strlen($chunks[0]);

                if (
$len && $chunks[0][$len-1] == ':') {
                   
$path = substr($chunks[0], 0, -1);
                }

                continue;
            }

           
$item = [
               
'permissions' => $chunks[0],
               
'number'      => $chunks[1],
               
'owner'       => $chunks[2],
               
'group'       => $chunks[3],
               
'size'        => $chunks[4],
               
'month'       => $chunks[5],
               
'day'         => $chunks[6],
               
'time'        => $chunks[7],
               
'name'        => $chunks[8],
               
'type'        => $this->rawToType($chunks[0]),
            ];

            unset(
$chunks[0]);
            unset(
$chunks[1]);
            unset(
$chunks[2]);
            unset(
$chunks[3]);
            unset(
$chunks[4]);
            unset(
$chunks[5]);
            unset(
$chunks[6]);
            unset(
$chunks[7]);
           
$item['name'] = implode(' ', $chunks);

            if (
$item['type'] == 'link') {
               
$item['target'] = $chunks[10]; // 9 is "->"
           
}

           
// if the key is not the path, behavior of ftp_rawlist() PHP function
           
if (is_int($key) || false === strpos($key, $item['name'])) {
               
array_splice($chunks, 0, 8);

               
$key = $item['type'].'#'
                   
.($path ? $path.'/' : '')
                    .
implode(" ", $chunks);

                if (
$item['type'] == 'link') {

                   
// get the first part of 'link#the-link.ext -> /path/of/the/source.ext'
                   
$exp = explode(' ->', $key);
                   
$key = rtrim($exp[0]);
                }

               
$items[$key] = $item;

            } else {

               
// the key is the path, behavior of FtpClient::rawlist() method()
               
$items[$key] = $item;
            }
        }

        return
$items;
    }

   
/**
     * Convert raw info (drwx---r-x ...) to type (file, directory, link, unknown).
     * Only the first char is used for resolving.
     *
     * @param  string $permission Example : drwx---r-x
     *
     * @return string The file type (file, directory, link, unknown)
     * @throws FtpException
     */
   
public function rawToType($permission)
    {
        if (!
is_string($permission)) {
            throw new
FtpException('The "$permission" argument must be a string, "'
           
.gettype($permission).'" given.');
        }

        if (empty(
$permission[0])) {
            return
'unknown';
        }

        switch (
$permission[0]) {
            case
'-':
                return
'file';

            case
'd':
                return
'directory';

            case
'l':
                return
'link';

            default:
                return
'unknown';
        }
    }

   
/**
     * Set the wrapper which forward the PHP FTP functions to use in FtpClient instance.
     *
     * @param  FtpWrapper $wrapper
     * @return FtpClient
     */
   
protected function setWrapper(FtpWrapper $wrapper)
    {
       
$this->ftp = $wrapper;

        return
$this;
    }
}