vendor/nicolab/php-ftp-client/src/FtpClient/FtpClient.php line 556

Open in your IDE?
  1. <?php
  2. /*
  3.  * This file is part of the `nicolab/php-ftp-client` package.
  4.  *
  5.  * (c) Nicolas Tallefourtane <[email protected]>
  6.  *
  7.  * For the full copyright and license information, please view the LICENSE
  8.  * file that was distributed with this source code.
  9.  *
  10.  * @copyright Nicolas Tallefourtane http://nicolab.net
  11.  */
  12. namespace FtpClient;
  13. use \Countable;
  14. /**
  15.  * The FTP and SSL-FTP client for PHP.
  16.  *
  17.  * @method bool alloc(int $filesize, string &$result = null) Allocates space for a file to be uploaded
  18.  * @method bool append(string $remote_file, string $local_file, int $mode = FTP_BINARY) Append the contents of a file to another file on the FTP server
  19.  * @method bool cdup() Changes to the parent directory
  20.  * @method bool chdir(string $directory) Changes the current directory on a FTP server
  21.  * @method int chmod(int $mode, string $filename) Set permissions on a file via FTP
  22.  * @method bool delete(string $path) Deletes a file on the FTP server
  23.  * @method bool exec(string $command) Requests execution of a command on the FTP server
  24.  * @method bool fget(resource $handle, string $remote_file, int $mode, int $resumepos = 0) Downloads a file from the FTP server and saves to an open file
  25.  * @method bool fput(string $remote_file, resource $handle, int $mode, int $startpos = 0) Uploads from an open file to the FTP server
  26.  * @method mixed get_option(int $option) Retrieves various runtime behaviours of the current FTP stream
  27.  * @method bool get(string $local_file, string $remote_file, int $mode, int $resumepos = 0) Downloads a file from the FTP server
  28.  * @method int mdtm(string $remote_file) Returns the last modified time of the given file
  29.  * @method array mlsd(string $remote_dir) Returns a list of files in the given directory
  30.  * @method int nb_continue() Continues retrieving/sending a file (non-blocking)
  31.  * @method int 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)
  32.  * @method int 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)
  33.  * @method int 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)
  34.  * @method int nb_put(string $remote_file, string $local_file, int $mode, int $startpos = 0) Stores a file on the FTP server (non-blocking)
  35.  * @method bool pasv(bool $pasv) Turns passive mode on or off
  36.  * @method bool put(string $remote_file, string $local_file, int $mode, int $startpos = 0) Uploads a file to the FTP server
  37.  * @method string pwd() Returns the current directory name
  38.  * @method bool quit() Closes an FTP connection
  39.  * @method array raw(string $command) Sends an arbitrary command to an FTP server
  40.  * @method bool rename(string $oldname, string $newname) Renames a file or a directory on the FTP server
  41.  * @method bool set_option(int $option, mixed $value) Set miscellaneous runtime FTP options
  42.  * @method bool site(string $command) Sends a SITE command to the server
  43.  * @method int size(string $remote_file) Returns the size of the given file
  44.  * @method string systype() Returns the system type identifier of the remote FTP server
  45.  *
  46.  * @author Nicolas Tallefourtane <[email protected]>
  47.  */
  48. class FtpClient implements Countable
  49. {
  50.     /**
  51.      * The connection with the server.
  52.      *
  53.      * @var resource
  54.      */
  55.     protected $conn;
  56.     /**
  57.      * PHP FTP functions wrapper.
  58.      *
  59.      * @var FtpWrapper
  60.      */
  61.     private $ftp;
  62.     /**
  63.      * Constructor.
  64.      *
  65.      * @param  resource|null $connection
  66.      * @throws FtpException  If FTP extension is not loaded.
  67.      */
  68.     public function __construct($connection null)
  69.     {
  70.         if (!extension_loaded('ftp')) {
  71.             throw new FtpException('FTP extension is not loaded!');
  72.         }
  73.         if ($connection) {
  74.             $this->conn $connection;
  75.         }
  76.         $this->setWrapper(new FtpWrapper($this->conn));
  77.     }
  78.     /**
  79.      * Close the connection when the object is destroyed.
  80.      */
  81.     public function __destruct()
  82.     {
  83.         if ($this->conn) {
  84.             $this->ftp->close();
  85.         }
  86.     }
  87.     /**
  88.      * Call an internal method or a FTP method handled by the wrapper.
  89.      *
  90.      * Wrap the FTP PHP functions to call as method of FtpClient object.
  91.      * The connection is automaticaly passed to the FTP PHP functions.
  92.      *
  93.      * @param  string       $method
  94.      * @param  array        $arguments
  95.      * @return mixed
  96.      * @throws FtpException When the function is not valid
  97.      */
  98.     public function __call($method, array $arguments)
  99.     {
  100.         return $this->ftp->__call($method$arguments);
  101.     }
  102.     /**
  103.      * Overwrites the PHP limit
  104.      *
  105.      * @param  string|null $memory            The memory limit, if null is not modified
  106.      * @param  int         $time_limit        The max execution time, unlimited by default
  107.      * @param  bool        $ignore_user_abort Ignore user abort, true by default
  108.      * @return FtpClient
  109.      */
  110.     public function setPhpLimit($memory null$time_limit 0$ignore_user_abort true)
  111.     {
  112.         if (null !== $memory) {
  113.             ini_set('memory_limit'$memory);
  114.         }
  115.         ignore_user_abort($ignore_user_abort);
  116.         set_time_limit($time_limit);
  117.         return $this;
  118.     }
  119.     /**
  120.      * Get the help information of the remote FTP server.
  121.      *
  122.      * @return array
  123.      */
  124.     public function help()
  125.     {
  126.         return $this->ftp->raw('help');
  127.     }
  128.     /**
  129.      * Open a FTP connection.
  130.      *
  131.      * @param string $host
  132.      * @param bool   $ssl
  133.      * @param int    $port
  134.      * @param int    $timeout
  135.      *
  136.      * @return FtpClient
  137.      * @throws FtpException If unable to connect
  138.      */
  139.     public function connect($host$ssl false$port 21$timeout 90)
  140.     {
  141.         if ($ssl) {
  142.             $this->conn $this->ftp->ssl_connect($host$port$timeout);
  143.         } else {
  144.             $this->conn $this->ftp->connect($host$port$timeout);
  145.         }
  146.         if (!$this->conn) {
  147.             throw new FtpException('Unable to connect');
  148.         }
  149.         return $this;
  150.     }
  151.     /**
  152.      * Closes the current FTP connection.
  153.      *
  154.      * @return bool
  155.      */
  156.     public function close()
  157.     {
  158.         if ($this->conn) {
  159.             $this->ftp->close();
  160.             $this->conn null;
  161.         }
  162.     }
  163.     /**
  164.      * Get the connection with the server.
  165.      *
  166.      * @return resource
  167.      */
  168.     public function getConnection()
  169.     {
  170.         return $this->conn;
  171.     }
  172.     /**
  173.      * Get the wrapper.
  174.      *
  175.      * @return FtpWrapper
  176.      */
  177.     public function getWrapper()
  178.     {
  179.         return $this->ftp;
  180.     }
  181.     /**
  182.      * Logs in to an FTP connection.
  183.      *
  184.      * @param string $username
  185.      * @param string $password
  186.      *
  187.      * @return FtpClient
  188.      * @throws FtpException If the login is incorrect
  189.      */
  190.     public function login($username 'anonymous'$password '')
  191.     {
  192.         $result $this->ftp->login($username$password);
  193.         if ($result === false) {
  194.             throw new FtpException('Login incorrect');
  195.         }
  196.         return $this;
  197.     }
  198.     /**
  199.      * Returns the last modified time of the given file.
  200.      * Return -1 on error
  201.      *
  202.      * @param string $remoteFile
  203.      * @param string|null $format
  204.      *
  205.      * @return int
  206.      */
  207.     public function modifiedTime($remoteFile$format null)
  208.     {
  209.         $time $this->ftp->mdtm($remoteFile);
  210.         if ($time !== -&& $format !== null) {
  211.             return date($format$time);
  212.         }
  213.         return $time;
  214.     }
  215.     /**
  216.      * Changes to the parent directory.
  217.      *
  218.      * @throws FtpException
  219.      * @return FtpClient
  220.      */
  221.     public function up()
  222.     {
  223.         $result $this->ftp->cdup();
  224.         if ($result === false) {
  225.             throw new FtpException('Unable to get parent folder');
  226.         }
  227.         return $this;
  228.     }
  229.     /**
  230.      * Returns a list of files in the given directory.
  231.      *
  232.      * @param string   $directory The directory, by default is "." the current directory
  233.      * @param bool     $recursive
  234.      * @param callable $filter    A callable to filter the result, by default is asort() PHP function.
  235.      *                            The result is passed in array argument,
  236.      *                            must take the argument by reference !
  237.      *                            The callable should proceed with the reference array
  238.      *                            because is the behavior of several PHP sorting
  239.      *                            functions (by reference ensure directly the compatibility
  240.      *                            with all PHP sorting functions).
  241.      *
  242.      * @return array
  243.      * @throws FtpException If unable to list the directory
  244.      */
  245.     public function nlist($directory '.'$recursive false$filter 'sort')
  246.     {
  247.         if (!$this->isDir($directory)) {
  248.             throw new FtpException('"'.$directory.'" is not a directory');
  249.         }
  250.         $files $this->ftp->nlist($directory);
  251.         if ($files === false) {
  252.             throw new FtpException('Unable to list directory');
  253.         }
  254.         $result  = array();
  255.         $dir_len strlen($directory);
  256.         // if it's the current
  257.         if (false !== ($kdot array_search('.'$files))) {
  258.             unset($files[$kdot]);
  259.         }
  260.         // if it's the parent
  261.         if(false !== ($kdot array_search('..'$files))) {
  262.             unset($files[$kdot]);
  263.         }
  264.         if (!$recursive) {
  265.             $result $files;
  266.             // working with the reference (behavior of several PHP sorting functions)
  267.             $filter($result);
  268.             return $result;
  269.         }
  270.         // utils for recursion
  271.         $flatten = function (array $arr) use (&$flatten) {
  272.             $flat = [];
  273.             foreach ($arr as $k => $v) {
  274.                 if (is_array($v)) {
  275.                     $flat array_merge($flat$flatten($v));
  276.                 } else {
  277.                     $flat[] = $v;
  278.                 }
  279.             }
  280.             return $flat;
  281.         };
  282.         foreach ($files as $file) {
  283.             $file $directory.'/'.$file;
  284.             // if contains the root path (behavior of the recursivity)
  285.             if (=== strpos($file$directory$dir_len)) {
  286.                 $file substr($file$dir_len);
  287.             }
  288.             if ($this->isDir($file)) {
  289.                 $result[] = $file;
  290.                 $items    $flatten($this->nlist($filetrue$filter));
  291.                 foreach ($items as $item) {
  292.                     $result[] = $item;
  293.                 }
  294.             } else {
  295.                 $result[] = $file;
  296.             }
  297.         }
  298.         $result array_unique($result);
  299.         $filter($result);
  300.         return $result;
  301.     }
  302.     /**
  303.      * Creates a directory.
  304.      *
  305.      * @see FtpClient::rmdir()
  306.      * @see FtpClient::remove()
  307.      * @see FtpClient::put()
  308.      * @see FtpClient::putAll()
  309.      *
  310.      * @param  string $directory The directory
  311.      * @param  bool   $recursive
  312.      * @return bool
  313.      */
  314.     public function mkdir($directory$recursive false)
  315.     {
  316.         if (!$recursive or $this->isDir($directory)) {
  317.             return $this->ftp->mkdir($directory);
  318.         }
  319.         $result false;
  320.         $pwd    $this->ftp->pwd();
  321.         $parts  explode('/'$directory);
  322.         foreach ($parts as $part) {
  323.             if ($part == '') {
  324.                 continue;
  325.             }
  326.             if (!@$this->ftp->chdir($part)) {
  327.                 $result $this->ftp->mkdir($part);
  328.                 $this->ftp->chdir($part);
  329.             }
  330.         }
  331.         $this->ftp->chdir($pwd);
  332.         return $result;
  333.     }
  334.     /**
  335.      * Remove a directory.
  336.      *
  337.      * @see FtpClient::mkdir()
  338.      * @see FtpClient::cleanDir()
  339.      * @see FtpClient::remove()
  340.      * @see FtpClient::delete()
  341.      * @param  string       $directory
  342.      * @param  bool         $recursive Forces deletion if the directory is not empty
  343.      * @return bool
  344.      * @throws FtpException If unable to list the directory to remove
  345.      */
  346.     public function rmdir($directory$recursive true)
  347.     {
  348.         if ($recursive) {
  349.             $files $this->nlist($directoryfalse'rsort');
  350.             // remove children
  351.             foreach ($files as $file) {
  352.                 $this->remove($filetrue);
  353.             }
  354.         }
  355.         // remove the directory
  356.         return $this->ftp->rmdir($directory);
  357.     }
  358.     /**
  359.      * Empty directory.
  360.      *
  361.      * @see FtpClient::remove()
  362.      * @see FtpClient::delete()
  363.      * @see FtpClient::rmdir()
  364.      *
  365.      * @param  string $directory
  366.      * @return bool
  367.      */
  368.     public function cleanDir($directory)
  369.     {
  370.         if (!$files $this->nlist($directory)) {
  371.             return $this->isEmpty($directory);
  372.         }
  373.         // remove children
  374.         foreach ($files as $file) {
  375.             $this->remove($filetrue);
  376.         }
  377.         return $this->isEmpty($directory);
  378.     }
  379.     /**
  380.      * Remove a file or a directory.
  381.      *
  382.      * @see FtpClient::rmdir()
  383.      * @see FtpClient::cleanDir()
  384.      * @see FtpClient::delete()
  385.      * @param  string $path      The path of the file or directory to remove
  386.      * @param  bool   $recursive Is effective only if $path is a directory, {@see FtpClient::rmdir()}
  387.      * @return bool
  388.      */
  389.     public function remove($path$recursive false)
  390.     {
  391.         if ($path == '.' || $path == '..') {
  392.             return false;
  393.         }
  394.         try {
  395.             if (@$this->ftp->delete($path)
  396.             or ($this->isDir($path
  397.             and $this->rmdir($path$recursive))) {
  398.                 return true;
  399.             } else {
  400.                 // in special cases the delete can fail (for example, at Symfony's "r+e.gex[c]a(r)s" directory)
  401.                 $newPath preg_replace('/[^A-Za-z0-9\/]/'''$path);
  402.                 if ($this->rename($path$newPath)) {
  403.                     if (@$this->ftp->delete($newPath
  404.                     or ($this->isDir($newPath
  405.                     and $this->rmdir($newPath$recursive))) {
  406.                         return true;
  407.                     }
  408.                 }
  409.             }
  410.             return false;
  411.         } catch (\Exception $e) {
  412.             return false;
  413.         }
  414.     }
  415.     /**
  416.      * Check if a directory exist.
  417.      *
  418.      * @param string $directory
  419.      * @return bool
  420.      * @throws FtpException
  421.      */
  422.     public function isDir($directory)
  423.     {
  424.         $pwd $this->ftp->pwd();
  425.         if ($pwd === false) {
  426.             throw new FtpException('Unable to resolve the current directory');
  427.         }
  428.         if (@$this->ftp->chdir($directory)) {
  429.             $this->ftp->chdir($pwd);
  430.             return true;
  431.         }
  432.         $this->ftp->chdir($pwd);
  433.         return false;
  434.     }
  435.     /**
  436.      * Check if a directory is empty.
  437.      *
  438.      * @param  string $directory
  439.      * @return bool
  440.      */
  441.     public function isEmpty($directory)
  442.     {
  443.         return $this->count($directorynullfalse) === true false;
  444.     }
  445.     /**
  446.      * Scan a directory and returns the details of each item.
  447.      *
  448.      * @see FtpClient::nlist()
  449.      * @see FtpClient::rawlist()
  450.      * @see FtpClient::parseRawList()
  451.      * @see FtpClient::dirSize()
  452.      * @param  string $directory
  453.      * @param  bool   $recursive
  454.      * @return array
  455.      */
  456.     public function scanDir($directory '.'$recursive false)
  457.     {
  458.         return $this->parseRawList($this->rawlist($directory$recursive));
  459.     }
  460.     /**
  461.      * Returns the total size of the given directory in bytes.
  462.      *
  463.      * @param  string $directory The directory, by default is the current directory.
  464.      * @param  bool   $recursive true by default
  465.      * @return int    The size in bytes.
  466.      */
  467.     public function dirSize($directory '.'$recursive true)
  468.     {
  469.         $items $this->scanDir($directory$recursive);
  470.         $size  0;
  471.         foreach ($items as $item) {
  472.             $size += (int) $item['size'];
  473.         }
  474.         return $size;
  475.     }
  476.     /**
  477.      * Count the items (file, directory, link, unknown).
  478.      *
  479.      * @param  string      $directory The directory, by default is the current directory.
  480.      * @param  string|null $type      The type of item to count (file, directory, link, unknown)
  481.      * @param  bool        $recursive true by default
  482.      * @return int
  483.      */
  484.     public function count($directory '.'$type null$recursive true)
  485.     {
  486.         $items  = (null === $type $this->nlist($directory$recursive)
  487.             : $this->scanDir($directory$recursive));
  488.         $count 0;
  489.         foreach ($items as $item) {
  490.             if (null === $type or $item['type'] == $type) {
  491.                 $count++;
  492.             }
  493.         }
  494.         return $count;
  495.     }
  496.     /**
  497.      * Downloads a file from the FTP server into a string
  498.      *
  499.      * @param  string $remote_file
  500.      * @param  int    $mode
  501.      * @param  int    $resumepos
  502.      * @return string|null
  503.      */
  504.     public function getContent($remote_file$mode FTP_BINARY$resumepos 0)
  505.     {
  506.         $handle fopen('php://temp''r+');
  507.         if ($this->ftp->fget($handle$remote_file$mode$resumepos)) {
  508.             rewind($handle);
  509.             return stream_get_contents($handle);
  510.         }
  511.         return null;
  512.     }
  513.     /**
  514.      * Uploads a file to the server from a string.
  515.      *
  516.      * @param  string       $remote_file
  517.      * @param  string       $content
  518.      * @return FtpClient
  519.      * @throws FtpException When the transfer fails
  520.      */
  521.     public function putFromString($remote_file$content)
  522.     {
  523.         $handle fopen('php://temp''w');
  524.         fwrite($handle$content);
  525.         rewind($handle);
  526.         if ($this->ftp->fput($remote_file$handleFTP_BINARY)) {
  527.             return $this;
  528.         }
  529.         throw new FtpException('Unable to put the file "'.$remote_file.'"');
  530.     }
  531.     /**
  532.      * Uploads a file to the server.
  533.      *
  534.      * @param  string       $local_file
  535.      * @return FtpClient
  536.      * @throws FtpException When the transfer fails
  537.      */
  538.     public function putFromPath($local_file)
  539.     {
  540.         $remote_file basename($local_file);
  541.         $handle      fopen($local_file'r');
  542.         if ($this->ftp->fput($remote_file$handleFTP_BINARY)) {
  543.             rewind($handle);
  544.             return $this;
  545.         }
  546.         throw new FtpException(
  547.             'Unable to put the remote file from the local file "'.$local_file.'"'
  548.         );
  549.     }
  550.     /**
  551.      * Upload files.
  552.      *
  553.      * @param  string    $source_directory
  554.      * @param  string    $target_directory
  555.      * @param  int       $mode
  556.      * @return FtpClient
  557.      */
  558.     public function putAll($source_directory$target_directory$mode FTP_BINARY)
  559.     {
  560.         $d dir($source_directory);
  561.         // do this for each file in the directory
  562.         while ($file $d->read()) {
  563.             // to prevent an infinite loop
  564.             if ($file != "." && $file != "..") {
  565.                 // do the following if it is a directory
  566.                 if (is_dir($source_directory.'/'.$file)) {
  567.                     if (!$this->isDir($target_directory.'/'.$file)) {
  568.                         // create directories that do not yet exist
  569.                         $this->ftp->mkdir($target_directory.'/'.$file);
  570.                     }
  571.                     // recursive part
  572.                     $this->putAll(
  573.                         $source_directory.'/'.$file$target_directory.'/'.$file,
  574.                         $mode
  575.                     );
  576.                 } else {
  577.                     // put the files
  578.                     $this->ftp->put(
  579.                         $target_directory.'/'.$file$source_directory.'/'.$file,
  580.                         $mode
  581.                     );
  582.                 }
  583.             }
  584.         }
  585.     $d->close();
  586.         return $this;
  587.     }
  588.     /**
  589.      * Downloads all files from remote FTP directory
  590.      *
  591.      * @param  string $source_directory The remote directory
  592.      * @param  string $target_directory The local directory
  593.      * @param  int    $mode
  594.      * @return FtpClient
  595.      */
  596.     public function getAll($source_directory$target_directory$mode FTP_BINARY)
  597.     {
  598.         if ($source_directory != ".") {
  599.             if ($this->ftp->chdir($source_directory) == false) {
  600.                 throw new FtpException("Unable to change directory: ".$source_directory);
  601.             }
  602.             if (!(is_dir($target_directory))) {
  603.                 mkdir($target_directory);
  604.         }
  605.             chdir($target_directory);
  606.         }
  607.         $contents $this->ftp->nlist(".");
  608.         foreach ($contents as $file) {
  609.             if ($file == '.' || $file == '..') {
  610.                 continue;
  611.         }
  612.             $this->ftp->get($target_directory."/".$file$file$mode);
  613.         }
  614.         $this->ftp->chdir("..");
  615.         chdir("..");
  616.         return $this;
  617.     }
  618.     /**
  619.      * Returns a detailed list of files in the given directory.
  620.      *
  621.      * @see FtpClient::nlist()
  622.      * @see FtpClient::scanDir()
  623.      * @see FtpClient::dirSize()
  624.      * @param  string       $directory The directory, by default is the current directory
  625.      * @param  bool         $recursive
  626.      * @return array
  627.      * @throws FtpException
  628.      */
  629.     public function rawlist($directory '.'$recursive false)
  630.     {
  631.         if (!$this->isDir($directory)) {
  632.             throw new FtpException('"'.$directory.'" is not a directory.');
  633.         }
  634.         if (strpos($directory" ") > 0) {
  635.             $ftproot $this->ftp->pwd();
  636.             $this->ftp->chdir($directory);
  637.             $list  $this->ftp->rawlist("");
  638.             $this->ftp->chdir($ftproot);
  639.         } else {
  640.             $list  $this->ftp->rawlist($directory);
  641.         }
  642.         $items = array();
  643.         if (!$list) {
  644.             return $items;
  645.         }
  646.         if (false == $recursive) {
  647.             foreach ($list as $path => $item) {
  648.                 $chunks preg_split("/\s+/"$item);
  649.                 // if not "name"
  650.                 if (!isset($chunks[8]) || strlen($chunks[8]) === || $chunks[8] == '.' || $chunks[8] == '..') {
  651.                     continue;
  652.                 }
  653.                 $path $directory.'/'.$chunks[8];
  654.                 if (isset($chunks[9])) {
  655.                     $nbChunks count($chunks);
  656.                     for ($i 9$i $nbChunks$i++) {
  657.                         $path .= ' '.$chunks[$i];
  658.                     }
  659.                 }
  660.                 if (substr($path02) == './') {
  661.                     $path substr($path2);
  662.                 }
  663.                 $items$this->rawToType($item).'#'.$path ] = $item;
  664.             }
  665.             return $items;
  666.         }
  667.         $path '';
  668.         foreach ($list as $item) {
  669.             $len strlen($item);
  670.             if (!$len
  671.             // "."
  672.             || ($item[$len-1] == '.' && $item[$len-2] == ' '
  673.             // ".."
  674.             or $item[$len-1] == '.' && $item[$len-2] == '.' && $item[$len-3] == ' ')
  675.             ) {
  676.                 continue;
  677.             }
  678.             $chunks preg_split("/\s+/"$item);
  679.             // if not "name"
  680.             if (!isset($chunks[8]) || strlen($chunks[8]) === || $chunks[8] == '.' || $chunks[8] == '..') {
  681.                 continue;
  682.             }
  683.             $path $directory.'/'.$chunks[8];
  684.             if (isset($chunks[9])) {
  685.                 $nbChunks count($chunks);
  686.                 for ($i 9$i $nbChunks$i++) {
  687.                     $path .= ' '.$chunks[$i];
  688.                 }
  689.             }
  690.             if (substr($path02) == './') {
  691.                 $path substr($path2);
  692.             }
  693.             $items[$this->rawToType($item).'#'.$path] = $item;
  694.             if ($item[0] == 'd') {
  695.                 $sublist $this->rawlist($pathtrue);
  696.                 foreach ($sublist as $subpath => $subitem) {
  697.                     $items[$subpath] = $subitem;
  698.                 }
  699.             }
  700.         }
  701.         return $items;
  702.     }
  703.     /**
  704.      * Parse raw list.
  705.      *
  706.      * @see FtpClient::rawlist()
  707.      * @see FtpClient::scanDir()
  708.      * @see FtpClient::dirSize()
  709.      * @param  array $rawlist
  710.      * @return array
  711.      */
  712.     public function parseRawList(array $rawlist)
  713.     {
  714.         $items = array();
  715.         $path  '';
  716.         foreach ($rawlist as $key => $child) {
  717.             $chunks preg_split("/\s+/"$child9);
  718.             if (isset($chunks[8]) && ($chunks[8] == '.' or $chunks[8] == '..')) {
  719.                 continue;
  720.             }
  721.             if (count($chunks) === 1) {
  722.                 $len strlen($chunks[0]);
  723.                 if ($len && $chunks[0][$len-1] == ':') {
  724.                     $path substr($chunks[0], 0, -1);
  725.                 }
  726.                 continue;
  727.             }
  728.             // Prepare for filename that has space
  729.             $nameSlices array_slice($chunks8true);
  730.             $item = [
  731.                 'permissions' => $chunks[0],
  732.                 'number'      => $chunks[1],
  733.                 'owner'       => $chunks[2],
  734.                 'group'       => $chunks[3],
  735.                 'size'        => $chunks[4],
  736.                 'month'       => $chunks[5],
  737.                 'day'         => $chunks[6],
  738.                 'time'        => $chunks[7],
  739.                 'name'        => implode(' '$nameSlices),
  740.                 'type'        => $this->rawToType($chunks[0]),
  741.             ];
  742.             if ($item['type'] == 'link' && isset($chunks[10])) {
  743.                 $item['target'] = $chunks[10]; // 9 is "->"
  744.             }
  745.             // if the key is not the path, behavior of ftp_rawlist() PHP function
  746.             if (is_int($key) || false === strpos($key$item['name'])) {
  747.                 array_splice($chunks08);
  748.                 $key $item['type'].'#'
  749.                     .($path $path.'/' '')
  750.                     .implode(' '$chunks);
  751.                 if ($item['type'] == 'link') {
  752.                     // get the first part of 'link#the-link.ext -> /path/of/the/source.ext'
  753.                     $exp explode(' ->'$key);
  754.                     $key rtrim($exp[0]);
  755.                 }
  756.                 $items[$key] = $item;
  757.             } else {
  758.                 // the key is the path, behavior of FtpClient::rawlist() method()
  759.                 $items[$key] = $item;
  760.             }
  761.         }
  762.         return $items;
  763.     }
  764.     /**
  765.      * Convert raw info (drwx---r-x ...) to type (file, directory, link, unknown).
  766.      * Only the first char is used for resolving.
  767.      *
  768.      * @param  string $permission Example : drwx---r-x
  769.      *
  770.      * @return string The file type (file, directory, link, unknown)
  771.      * @throws FtpException
  772.      */
  773.     public function rawToType($permission)
  774.     {
  775.         if (!is_string($permission)) {
  776.             throw new FtpException('The "$permission" argument must be a string, "'
  777.             .gettype($permission).'" given.');
  778.         }
  779.         if (empty($permission[0])) {
  780.             return 'unknown';
  781.         }
  782.         switch ($permission[0]) {
  783.             case '-':
  784.                 return 'file';
  785.             case 'd':
  786.                 return 'directory';
  787.             case 'l':
  788.                 return 'link';
  789.             default:
  790.                 return 'unknown';
  791.         }
  792.     }
  793.     /**
  794.      * Set the wrapper which forward the PHP FTP functions to use in FtpClient instance.
  795.      *
  796.      * @param  FtpWrapper $wrapper
  797.      * @return FtpClient
  798.      */
  799.     protected function setWrapper(FtpWrapper $wrapper)
  800.     {
  801.         $this->ftp $wrapper;
  802.         return $this;
  803.     }
  804. }