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

2 | 3 | 4 | 5 |

FTP Client for Yii 2

6 |
7 |

8 | 9 | [![Latest Stable Version](https://poser.pugx.org/yii2mod/yii2-ftp/v/stable)](https://packagist.org/packages/yii2mod/yii2-ftp) [![Total Downloads](https://poser.pugx.org/yii2mod/yii2-ftp/downloads)](https://packagist.org/packages/yii2mod/yii2-ftp) [![License](https://poser.pugx.org/yii2mod/yii2-ftp/license)](https://packagist.org/packages/yii2mod/yii2-ftp) 10 | 11 | > yii2-ftp is a fork of [Nicolab/php-ftp-client](https://github.com/Nicolab/php-ftp-client) v1.2.0 12 | 13 | Installation 14 | ------------ 15 | 16 | The preferred way to install this extension is through [composer](http://getcomposer.org/download/). 17 | 18 | Either run 19 | 20 | ``` 21 | php composer.phar require --prefer-dist yii2mod/yii2-ftp "*" 22 | ``` 23 | 24 | or add 25 | 26 | ``` 27 | "yii2mod/yii2-ftp": "*" 28 | ``` 29 | 30 | to the require section of your composer.json. 31 | 32 | 33 | ## Getting Started 34 | 35 | Connect to a server FTP : 36 | 37 | ```php 38 | $ftp = new \yii2mod\ftp\FtpClient(); 39 | $host = 'ftp.example.com'; 40 | $ftp->connect($host); 41 | $ftp->login($login, $password); 42 | ``` 43 | 44 | OR 45 | 46 | Connect to a server FTP via SSL (on port 22 or other port) : 47 | 48 | ```php 49 | $ftp = new \yii2mod\ftp\FtpClient(); 50 | $host = 'ftp.example.com'; 51 | $ftp->connect($host, true, 22); 52 | $ftp->login($login, $password); 53 | ``` 54 | 55 | Note: The connection is implicitly closed at the end of script execution (when the object is destroyed). Therefore it is unnecessary to call `$ftp->close()`, except for an explicit re-connection. 56 | 57 | 58 | ### Usage 59 | 60 | Upload all files and all directories is easy : 61 | 62 | ```php 63 | // upload with the BINARY mode 64 | $ftp->putAll($source_directory, $target_directory); 65 | 66 | // Is equal to 67 | $ftp->putAll($source_directory, $target_directory, FTP_BINARY); 68 | 69 | // or upload with the ASCII mode 70 | $ftp->putAll($source_directory, $target_directory, FTP_ASCII); 71 | ``` 72 | 73 | *Note : FTP_ASCII and FTP_BINARY are predefined PHP internal constants.* 74 | 75 | Get a directory size : 76 | 77 | ```php 78 | // size of the current directory 79 | $size = $ftp->dirSize(); 80 | 81 | // size of a given directory 82 | $size = $ftp->dirSize('/path/of/directory'); 83 | ``` 84 | 85 | Count the items in a directory : 86 | 87 | ```php 88 | // count in the current directory 89 | $total = $ftp->count(); 90 | 91 | // count in a given directory 92 | $total = $ftp->count('/path/of/directory'); 93 | 94 | // count only the "files" in the current directory 95 | $total_file = $ftp->count('.', 'file'); 96 | 97 | // count only the "files" in a given directory 98 | $total_file = $ftp->count('/path/of/directory', 'file'); 99 | 100 | // count only the "directories" in a given directory 101 | $total_dir = $ftp->count('/path/of/directory', 'directory'); 102 | 103 | // count only the "symbolic links" in a given directory 104 | $total_link = $ftp->count('/path/of/directory', 'link'); 105 | ``` 106 | 107 | Detailed list of all files and directories : 108 | 109 | ```php 110 | // scan the current directory and returns the details of each item 111 | $items = $ftp->scanDir(); 112 | 113 | // scan the current directory (recursive) and returns the details of each item 114 | var_dump($ftp->scanDir('.', true)); 115 | ``` 116 | 117 | Result: 118 | 119 | 'directory#www' => 120 | array (size=10) 121 | 'permissions' => string 'drwx---r-x' (length=10) 122 | 'number' => string '3' (length=1) 123 | 'owner' => string '32385' (length=5) 124 | 'group' => string 'users' (length=5) 125 | 'size' => string '5' (length=1) 126 | 'month' => string 'Nov' (length=3) 127 | 'day' => string '24' (length=2) 128 | 'time' => string '17:25' (length=5) 129 | 'name' => string 'www' (length=3) 130 | 'type' => string 'directory' (length=9) 131 | 132 | 'link#www/index.html' => 133 | array (size=11) 134 | 'permissions' => string 'lrwxrwxrwx' (length=10) 135 | 'number' => string '1' (length=1) 136 | 'owner' => string '0' (length=1) 137 | 'group' => string 'users' (length=5) 138 | 'size' => string '38' (length=2) 139 | 'month' => string 'Nov' (length=3) 140 | 'day' => string '16' (length=2) 141 | 'time' => string '14:57' (length=5) 142 | 'name' => string 'index.html' (length=10) 143 | 'type' => string 'link' (length=4) 144 | 'target' => string '/var/www/shared/index.html' (length=26) 145 | 146 | 'file#www/README' => 147 | array (size=10) 148 | 'permissions' => string '-rw----r--' (length=10) 149 | 'number' => string '1' (length=1) 150 | 'owner' => string '32385' (length=5) 151 | 'group' => string 'users' (length=5) 152 | 'size' => string '0' (length=1) 153 | 'month' => string 'Nov' (length=3) 154 | 'day' => string '24' (length=2) 155 | 'time' => string '17:25' (length=5) 156 | 'name' => string 'README' (length=6) 157 | 'type' => string 'file' (length=4) 158 | 159 | 160 | All FTP PHP functions are supported and some improved : 161 | 162 | ```php 163 | // Requests execution of a command on the FTP server 164 | $ftp->exec($command); 165 | 166 | // Turns passive mode on or off 167 | $ftp->pasv(true); 168 | 169 | // Set permissions on a file via FTP 170 | $ftp->chmod('0777', 'file.php'); 171 | 172 | // Removes a directory 173 | $ftp->rmdir('path/of/directory/to/remove'); 174 | 175 | // Removes a directory (recursive) 176 | $ftp->rmdir('path/of/directory/to/remove', true); 177 | 178 | // Creates a directory 179 | $ftp->mkdir('path/of/directory/to/create'); 180 | 181 | // Creates a directory (recursive), 182 | // creates automaticaly the sub directory if not exist 183 | $ftp->mkdir('path/of/directory/to/create', true); 184 | 185 | // and more ... 186 | ``` 187 | 188 | Get the help information of remote FTP server : 189 | 190 | ```php 191 | var_dump($ftp->help()); 192 | ``` 193 | 194 | Result : 195 | 196 | array (size=6) 197 | 0 => string '214-The following SITE commands are recognized' (length=46) 198 | 1 => string ' ALIAS' (length=6) 199 | 2 => string ' CHMOD' (length=6) 200 | 3 => string ' IDLE' (length=5) 201 | 4 => string ' UTIME' (length=6) 202 | 5 => string '214 Pure-FTPd - http://pureftpd.org/' (length=36) 203 | 204 | 205 | _Note : The result depend of FTP server._ 206 | 207 | 208 | ## Support us 209 | 210 | Does your business depend on our contributions? Reach out and support us on [Patreon](https://www.patreon.com/yii2mod). 211 | All pledges will be dedicated to allocating workforce on maintenance and new awesome stuff. 212 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "yii2mod/yii2-ftp", 3 | "type": "library", 4 | "description": "A flexible FTP and SSL-FTP client for PHP. This lib provides helpers easy to use to manage the remote files.", 5 | "license": "MIT", 6 | "keywords": [ 7 | "yii2", 8 | "extension", 9 | "ftp", 10 | "sftp", 11 | "ssl-ftp", 12 | "ssl", 13 | "file", 14 | "server", 15 | "lib", 16 | "helper" 17 | ], 18 | "require": { 19 | "php": ">=5.4", 20 | "ext-ftp": "*" 21 | }, 22 | "require-dev": { 23 | "friendsofphp/php-cs-fixer": "~2.0" 24 | }, 25 | "autoload": { 26 | "psr-4": { 27 | "yii2mod\\ftp\\": "" 28 | } 29 | } 30 | } 31 | --------------------------------------------------------------------------------