Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 29 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,35 @@ Zip::create("package.zip")
->addFromDisk("s3", "object.pdf", "Something.pdf");
```

In this case the S3 client from the storage disk will be used.
In this case the S3 client from the storage disk will be used.

## Support for FTP

You can stream files from an FTP server into your zip. There's two ways to achieve this:

### Add files from disk

For this you will need to [configure an FTP disk.](https://laravel.com/docs/12.x/filesystem#ftp-driver-configuration)

```php
Zip::create('package.zip')
->addFromDisk('ftp', 'object.pdf', 'Something.pdf');

\\ OR

$disk = Storage::disk('custom_ftp_disk');
Zip::create('package.zip')
->addFromDisk($disk, 'object.pdf', 'Something.pdf');
```

### Add files from a FTP URL

Pass a valid [FTP URL](https://www.php.net/manual/en/wrappers.ftp.php) to add files from a custom URL:

```php
Zip::create('package.zip')
->add('ftp://example.com/pub/file.txt', 'object.pdf', 'Something.pdf');
```

## Specify your own filesizes

Expand Down
4 changes: 4 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,13 @@
},
"require-dev": {
"league/flysystem-aws-s3-v3": "^3.28",
"league/flysystem-ftp": "^3.29",
"orchestra/testbench": "^8.0|^9.0|^10.0",
"phpunit/phpunit": "^9.0|^10.0|^11.5.3"
},
"suggest": {
"league/flysystem-ftp": "Allows FTP support."
},
"autoload": {
"psr-4": {
"STS\\ZipStream\\": "src"
Expand Down
27 changes: 22 additions & 5 deletions src/Models/File.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@
use Illuminate\Filesystem\FilesystemAdapter;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use League\Flysystem\Ftp\FtpAdapter;
use League\Flysystem\Local\LocalFilesystemAdapter;
use Psr\Http\Message\StreamInterface;
use Illuminate\Support\Str;
use STS\ZipStream\Contracts\FileContract;
use STS\ZipStream\Exceptions\UnsupportedSourceDiskException;
use STS\ZipStream\OutputStream;
Expand Down Expand Up @@ -48,6 +49,10 @@ public static function make(string $source, ?string $zipPath = null): static
return new HttpFile($source, $zipPath);
}

if (Str::startsWith($source, "ftp") && filter_var($source, FILTER_VALIDATE_URL)) {
return new FtpFile($source, $zipPath);
}

if (Str::startsWith($source, "/") || preg_match('/^\w:[\/\\\\]/', $source) || file_exists($source)) {
return new LocalFile($source, $zipPath);
}
Expand Down Expand Up @@ -78,30 +83,42 @@ public static function makeFromDisk($disk, string $source, ?string $zipPath = nu
);
}

if($disk->getAdapter() instanceof FtpAdapter) {
return FtpFile::makeFromDisk($disk, $source, $zipPath);
}

throw new UnsupportedSourceDiskException("Unsupported disk type");
}

public static function makeWriteable(string $source): S3File|LocalFile
public static function makeWriteable(string $source): static
{
if (Str::startsWith($source, "s3://")) {
return new S3File($source);
}

if (Str::startsWith($source, "ftp") && filter_var($source, FILTER_VALIDATE_URL)) {
return new FtpFile($source);
}

return new LocalFile($source);
}

public static function makeWriteableFromDisk($disk, string $source): S3File|LocalFile
public static function makeWriteableFromDisk($disk, string $source): static
{
if(!$disk instanceof FilesystemAdapter) {
if (!$disk instanceof FilesystemAdapter) {
$disk = Storage::disk($disk);
}

if($disk instanceof AwsS3V3Adapter) {
if ($disk instanceof AwsS3V3Adapter) {
return S3File::make(
"s3://" . Arr::get($disk->getConfig(), "bucket") . "/" . $disk->path($source)
)->setS3Client($disk->getClient());
}

if ($disk->getAdapter() instanceof FtpAdapter) {
return FtpFile::makeFromDisk($disk, $source);
}

return new LocalFile(
$disk->path($source)
);
Expand Down
63 changes: 63 additions & 0 deletions src/Models/FtpFile.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<?php

namespace STS\ZipStream\Models;

use GuzzleHttp\Psr7\Utils;
use Illuminate\Filesystem\FilesystemAdapter;
use Illuminate\Support\Str;
use Psr\Http\Message\StreamInterface;
use STS\ZipStream\OutputStream;

class FtpFile extends File
{
protected FilesystemAdapter $disk;

public static function makeFromDisk($disk, string $source, ?string $zipPath = null): static
{
$config = $disk->getConfig();

$protocol = !empty($config['ssl']) ? 'ftps://' : 'ftp://';
$host = $config['host'] ?? '';

$user = $config['username'] ?? null;
$pass = $config['password'] ?? '';
$userInfo = $user ? "$user:$pass@" : '';

$root = $config['root'] ?? '';
$root = $root ? Str::start($root, '/') : '';

return new static($protocol . $userInfo . $host . $root .'/'. $source, $zipPath);
}

public function canPredictZipDataSize(): bool
{
return true;
}

public function calculateFilesize(): int
{
return stat($this->source)['size'];
}

protected function buildReadableStream(): StreamInterface
{
return Utils::streamFor($this->getStream('rb'));
}

protected function buildWritableStream(): OutputStream
{
return new OutputStream($this->getStream('wb'));
}

/**
* @return resource
*/
protected function getStream(string $mode)
{
return fopen($this->source, $mode, context: stream_context_create([
'ftp' => [
'overwrite' => true
]
]));
}
}
21 changes: 21 additions & 0 deletions tests/FileTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

use Orchestra\Testbench\TestCase;
use STS\ZipStream\Models\File;
use STS\ZipStream\Models\FtpFile;
use STS\ZipStream\Models\HttpFile;
use STS\ZipStream\Models\LocalFile;
use STS\ZipStream\Models\S3File;
Expand All @@ -20,6 +21,7 @@ protected function getPackageProviders($app)
public function testMake()
{
$this->assertInstanceOf(S3File::class, File::make('s3://bucket/key'));
$this->assertInstanceOf(FtpFile::class, File::make('ftp://foo.com/bar.txt'));
$this->assertInstanceOf(LocalFile::class, File::make('/dev/null'));
$this->assertInstanceOf(LocalFile::class, File::make('/tmp/foobar'));
$this->assertInstanceOf(LocalFile::class, File::make('C:/foo/bar'));
Expand All @@ -29,6 +31,7 @@ public function testMake()

public function testMakeWriteable()
{
$this->assertInstanceOf(FtpFile::class, File::makeWriteable('ftp://foo.com/bar.zip'));
$this->assertInstanceOf(S3File::class, File::makeWriteable('s3://bucket/key'));
$this->assertInstanceOf(LocalFile::class, File::makeWriteable('/tmp/foobar'));
$this->assertInstanceOf(LocalFile::class, File::makeWriteable("C:/"));
Expand Down Expand Up @@ -115,4 +118,22 @@ public function testFromS3Disk()
$this->assertInstanceOf(S3File::class, $file);
$this->assertEquals('s3://my-test-bucket/my-prefix/test.txt', $file->getSource());
}

public function testFromFtpDisk()
{
$options = [
'driver' => 'ftp',
'host' => 'ftp_server',
];

config(['filesystems.disks.ftp' => $options]);
config(['filesystems.disks.ftps' => $options + ['ssl' => true]]);

$file1 = File::makeFromDisk('ftp', 'file1.txt');
$file2 = File::makeFromDisk('ftps', 'file2.txt');

$this->assertContainsOnlyInstancesOf(FtpFile::class, [$file1, $file2]);
$this->assertEquals('ftp://ftp_server/file1.txt', $file1->getSource());
$this->assertEquals('ftps://ftp_server/file2.txt', $file2->getSource());
}
}