From 13a3fefa7fa3e753d1a3ea4e13f38a091df08171 Mon Sep 17 00:00:00 2001 From: Frank de Jonge Date: Thu, 20 Oct 2022 12:17:33 +0200 Subject: [PATCH] [FEATURE] added temporary URLs. --- .../FilesystemAdapterTestCase.php | 25 ++++++++++ src/AsyncAwsS3/AsyncAwsS3Adapter.php | 12 ++++- src/AsyncAwsS3/AsyncAwsS3AdapterTest.php | 5 +- src/AsyncAwsS3/S3ClientStub.php | 6 +++ src/AsyncAwsS3/composer.json | 2 +- src/AwsS3V3/AwsS3V3Adapter.php | 18 ++++++- src/AwsS3V3/composer.json | 2 +- .../AzureBlobStorageAdapter.php | 48 ++++++++++++++++--- .../AzureBlobStorageAdapterTest.php | 10 +++- src/AzureBlobStorage/composer.json | 2 +- src/Filesystem.php | 14 ++++++ src/FilesystemReader.php | 3 ++ .../GoogleCloudStorageAdapter.php | 11 ++++- src/GoogleCloudStorage/composer.json | 2 +- src/PathPrefixing/PathPrefixedAdapter.php | 14 +++++- src/PathPrefixing/composer.json | 2 +- src/ReadOnly/ReadOnlyFilesystemAdapter.php | 14 +++++- src/ReadOnly/composer.json | 2 +- src/UnableToGenerateTemporaryUrl.php | 26 ++++++++++ src/UrlGeneration/TemporaryUrlGenerator.php | 16 +++++++ 20 files changed, 214 insertions(+), 20 deletions(-) create mode 100644 src/UnableToGenerateTemporaryUrl.php create mode 100644 src/UrlGeneration/TemporaryUrlGenerator.php diff --git a/src/AdapterTestUtilities/FilesystemAdapterTestCase.php b/src/AdapterTestUtilities/FilesystemAdapterTestCase.php index 5d6d39c27..949f59bc8 100644 --- a/src/AdapterTestUtilities/FilesystemAdapterTestCase.php +++ b/src/AdapterTestUtilities/FilesystemAdapterTestCase.php @@ -4,6 +4,9 @@ namespace League\Flysystem\AdapterTestUtilities; +use DateInterval; +use DateTime; +use DateTimeImmutable; use Generator; use League\Flysystem\ChecksumProvider; use League\Flysystem\Config; @@ -17,12 +20,14 @@ use League\Flysystem\UnableToRetrieveMetadata; use League\Flysystem\UnableToSetVisibility; use League\Flysystem\UrlGeneration\PublicUrlGenerator; +use League\Flysystem\UrlGeneration\TemporaryUrlGenerator; use League\Flysystem\Visibility; use PHPUnit\Framework\TestCase; use Throwable; use function file_get_contents; use function is_resource; use function iterator_to_array; +use function var_dump; use const PHP_EOL; /** @@ -793,6 +798,26 @@ public function generating_a_public_url(): void self::assertEquals('public contents', $contents); } + /** + * @test + */ + public function generating_a_temporary_url(): void + { + $adapter = $this->adapter(); + + if ( ! $adapter instanceof TemporaryUrlGenerator) { + $this->markTestSkipped('Adapter does not supply temporary URls'); + } + + $adapter->write('some/private.txt', 'public contents', new Config(['visibility' => 'private'])); + + $expiresAt = (new DateTimeImmutable())->add(DateInterval::createFromDateString('1 minute')); + $url = $adapter->temporaryUrl('some/private.txt', $expiresAt, new Config()); + $contents = file_get_contents($url); + + self::assertEquals('public contents', $contents); + } + /** * @test */ diff --git a/src/AsyncAwsS3/AsyncAwsS3Adapter.php b/src/AsyncAwsS3/AsyncAwsS3Adapter.php index 260c0b586..23370d57b 100644 --- a/src/AsyncAwsS3/AsyncAwsS3Adapter.php +++ b/src/AsyncAwsS3/AsyncAwsS3Adapter.php @@ -12,6 +12,8 @@ use AsyncAws\S3\ValueObject\CommonPrefix; use AsyncAws\S3\ValueObject\ObjectIdentifier; use AsyncAws\SimpleS3\SimpleS3Client; +use DateTimeImmutable; +use DateTimeInterface; use Generator; use League\Flysystem\ChecksumAlgoIsNotSupported; use League\Flysystem\ChecksumProvider; @@ -32,13 +34,14 @@ use League\Flysystem\UnableToRetrieveMetadata; use League\Flysystem\UnableToSetVisibility; use League\Flysystem\UrlGeneration\PublicUrlGenerator; +use League\Flysystem\UrlGeneration\TemporaryUrlGenerator; use League\Flysystem\Visibility; use League\MimeTypeDetection\FinfoMimeTypeDetector; use League\MimeTypeDetection\MimeTypeDetector; use Throwable; use function trim; -class AsyncAwsS3Adapter implements FilesystemAdapter, PublicUrlGenerator, ChecksumProvider +class AsyncAwsS3Adapter implements FilesystemAdapter, PublicUrlGenerator, ChecksumProvider, TemporaryUrlGenerator { /** * @var string[] @@ -530,4 +533,11 @@ public function checksum(string $path, Config $config): string return trim($metadata['ETag'], '"'); } + + public function temporaryUrl(string $path, DateTimeInterface $expiresAt, Config $config): string + { + $location = $this->prefixer->prefixPath($path); + + return $this->client->getPresignedUrl($this->bucket, $location, DateTimeImmutable::createFromInterface($expiresAt)); + } } diff --git a/src/AsyncAwsS3/AsyncAwsS3AdapterTest.php b/src/AsyncAwsS3/AsyncAwsS3AdapterTest.php index 1a3634310..898044a53 100644 --- a/src/AsyncAwsS3/AsyncAwsS3AdapterTest.php +++ b/src/AsyncAwsS3/AsyncAwsS3AdapterTest.php @@ -23,7 +23,6 @@ use League\Flysystem\UnableToCheckFileExistence; use League\Flysystem\UnableToDeleteFile; use League\Flysystem\UnableToMoveFile; -use League\Flysystem\UnableToProvideChecksum; use League\Flysystem\UnableToRetrieveMetadata; use League\Flysystem\Visibility; @@ -98,11 +97,13 @@ private static function s3Client(): S3Client self::markTestSkipped('No AWS credentials present for testing.'); } - return static::$s3Client = new SimpleS3Client([ + static::$s3Client = new SimpleS3Client([ 'accessKeyId' => $key, 'accessKeySecret' => $secret, 'region' => $region, ]); + + return static::$s3Client; } diff --git a/src/AsyncAwsS3/S3ClientStub.php b/src/AsyncAwsS3/S3ClientStub.php index 96ea1dfad..af07df602 100644 --- a/src/AsyncAwsS3/S3ClientStub.php +++ b/src/AsyncAwsS3/S3ClientStub.php @@ -28,6 +28,7 @@ use AsyncAws\S3\Result\PutObjectOutput; use AsyncAws\S3\S3Client; use AsyncAws\SimpleS3\SimpleS3Client; +use DateTimeImmutable; use Symfony\Component\HttpClient\MockHttpClient; /** @@ -179,4 +180,9 @@ public function getUrl(string $bucket, string $key): string { return $this->actualClient->getUrl($bucket, $key); } + + public function getPresignedUrl(string $bucket, string $key, ?DateTimeImmutable $expires = null): string + { + return $this->actualClient->getPresignedUrl($bucket, $key, $expires); + } } diff --git a/src/AsyncAwsS3/composer.json b/src/AsyncAwsS3/composer.json index 9f2efc836..03597e1b1 100644 --- a/src/AsyncAwsS3/composer.json +++ b/src/AsyncAwsS3/composer.json @@ -10,7 +10,7 @@ }, "require": { "php": "^8.0.2", - "league/flysystem": "^3.8.0", + "league/flysystem": "^3.10.0", "league/mime-type-detection": "^1.0.0", "async-aws/s3": "^1.5" }, diff --git a/src/AwsS3V3/AwsS3V3Adapter.php b/src/AwsS3V3/AwsS3V3Adapter.php index a0b4d1640..77e3dc209 100644 --- a/src/AwsS3V3/AwsS3V3Adapter.php +++ b/src/AwsS3V3/AwsS3V3Adapter.php @@ -6,6 +6,7 @@ use Aws\Api\DateTimeResult; use Aws\S3\S3ClientInterface; +use DateTimeInterface; use Generator; use League\Flysystem\ChecksumAlgoIsNotSupported; use League\Flysystem\ChecksumProvider; @@ -29,6 +30,7 @@ use League\Flysystem\UnableToSetVisibility; use League\Flysystem\UnableToWriteFile; use League\Flysystem\UrlGeneration\PublicUrlGenerator; +use League\Flysystem\UrlGeneration\TemporaryUrlGenerator; use League\Flysystem\Visibility; use League\MimeTypeDetection\FinfoMimeTypeDetector; use League\MimeTypeDetection\MimeTypeDetector; @@ -36,7 +38,7 @@ use Throwable; use function trim; -class AwsS3V3Adapter implements FilesystemAdapter, PublicUrlGenerator, ChecksumProvider +class AwsS3V3Adapter implements FilesystemAdapter, PublicUrlGenerator, ChecksumProvider, TemporaryUrlGenerator { /** * @var string[] @@ -537,4 +539,18 @@ public function checksum(string $path, Config $config): string return trim($metadata['ETag'], '"'); } + + public function temporaryUrl(string $path, DateTimeInterface $expiresAt, Config $config): string + { + $options = $config->get('get_object_options', []); + $command = $this->client->getCommand('GetObject', [ + 'Bucket' => $this->bucket, + 'Key' => $this->prefixer->prefixPath($path), + ] + $options); + + $presignedRequestOptions = $config->get('presigned_request_options', []); + $request = $this->client->createPresignedRequest($command, $expiresAt, $presignedRequestOptions); + + return (string) $request->getUri(); + } } diff --git a/src/AwsS3V3/composer.json b/src/AwsS3V3/composer.json index b0558da54..9e8a9feb0 100644 --- a/src/AwsS3V3/composer.json +++ b/src/AwsS3V3/composer.json @@ -10,7 +10,7 @@ }, "require": { "php": "^8.0.2", - "league/flysystem": "^3.8.0", + "league/flysystem": "^3.10.0", "league/mime-type-detection": "^1.0.0", "aws/aws-sdk-php": "^3.132.4" }, diff --git a/src/AzureBlobStorage/AzureBlobStorageAdapter.php b/src/AzureBlobStorage/AzureBlobStorageAdapter.php index 74e0095df..c9fa9e6a5 100644 --- a/src/AzureBlobStorage/AzureBlobStorageAdapter.php +++ b/src/AzureBlobStorage/AzureBlobStorageAdapter.php @@ -4,6 +4,8 @@ namespace League\Flysystem\AzureBlobStorage; +use DateTime; +use DateTimeInterface; use League\Flysystem\ChecksumAlgoIsNotSupported; use League\Flysystem\ChecksumProvider; use League\Flysystem\Config; @@ -16,6 +18,7 @@ use League\Flysystem\UnableToCopyFile; use League\Flysystem\UnableToDeleteDirectory; use League\Flysystem\UnableToDeleteFile; +use League\Flysystem\UnableToGenerateTemporaryUrl; use League\Flysystem\UnableToMoveFile; use League\Flysystem\UnableToProvideChecksum; use League\Flysystem\UnableToReadFile; @@ -23,20 +26,24 @@ use League\Flysystem\UnableToSetVisibility; use League\Flysystem\UnableToWriteFile; use League\Flysystem\UrlGeneration\PublicUrlGenerator; +use League\Flysystem\UrlGeneration\TemporaryUrlGenerator; use League\MimeTypeDetection\FinfoMimeTypeDetector; use League\MimeTypeDetection\MimeTypeDetector; use MicrosoftAzure\Storage\Blob\BlobRestProxy; +use MicrosoftAzure\Storage\Blob\BlobSharedAccessSignatureHelper; use MicrosoftAzure\Storage\Blob\Models\BlobProperties; use MicrosoftAzure\Storage\Blob\Models\CreateBlockBlobOptions; use MicrosoftAzure\Storage\Blob\Models\ListBlobsOptions; use MicrosoftAzure\Storage\Common\Exceptions\ServiceException; +use MicrosoftAzure\Storage\Common\Internal\Resources; +use MicrosoftAzure\Storage\Common\Internal\StorageServiceSettings; use MicrosoftAzure\Storage\Common\Models\ContinuationToken; use Throwable; use function base64_decode; use function bin2hex; use function stream_get_contents; -class AzureBlobStorageAdapter implements FilesystemAdapter, PublicUrlGenerator, ChecksumProvider +class AzureBlobStorageAdapter implements FilesystemAdapter, PublicUrlGenerator, ChecksumProvider, TemporaryUrlGenerator { /** @var string[] */ private const META_OPTIONS = [ @@ -50,16 +57,12 @@ class AzureBlobStorageAdapter implements FilesystemAdapter, PublicUrlGenerator, const ON_VISIBILITY_IGNORE = 'ignore'; private BlobRestProxy $client; - private MimeTypeDetector $mimeTypeDetector; - private int $maxResultsForContentsListing; - private string $container; - private PathPrefixer $prefixer; - private string $visibilityHandling; + private ?StorageServiceSettings $serviceSettings; public function __construct( BlobRestProxy $client, @@ -68,6 +71,7 @@ public function __construct( MimeTypeDetector $mimeTypeDetector = null, int $maxResultsForContentsListing = 5000, string $visibilityHandling = self::ON_VISIBILITY_THROW_ERROR, + StorageServiceSettings $serviceSettings = null, ) { $this->client = $client; $this->container = $container; @@ -75,6 +79,7 @@ public function __construct( $this->mimeTypeDetector = $mimeTypeDetector ?? new FinfoMimeTypeDetector(); $this->maxResultsForContentsListing = $maxResultsForContentsListing; $this->visibilityHandling = $visibilityHandling; + $this->serviceSettings = $serviceSettings; } public function copy(string $source, string $destination, Config $config): void @@ -373,4 +378,35 @@ public function checksum(string $path, Config $config): string return bin2hex(base64_decode($checksum)); } + + public function temporaryUrl(string $path, DateTimeInterface $expiresAt, Config $config): string + { + if ( ! $this->serviceSettings instanceof StorageServiceSettings) { + throw UnableToGenerateTemporaryUrl::noGeneratorConfigured( + $path, + 'The $serviceSettings constructor parameter must be set to generate temporary URLs.', + ); + } + $sas = new BlobSharedAccessSignatureHelper($this->serviceSettings->getName(), $this->serviceSettings->getKey()); + $baseUrl = $this->publicUrl($path, $config); + $resourceName = $this->container . '/' . ltrim($this->prefixer->prefixPath($path), '/'); + $token = $sas->generateBlobServiceSharedAccessSignatureToken( + Resources::RESOURCE_TYPE_BLOB, + $resourceName, + 'r', // read + DateTime::createFromInterface($expiresAt), + $config->get('signed_start', ''), + $config->get('signed_ip', ''), + $config->get('signed_protocol', 'https'), + $config->get('signed_identifier', ''), + $config->get('cache_control', ''), + $config->get('content_deposition', ''), + $config->get('content_encoding', ''), + $config->get('content_language', ''), + $config->get('content_type', ''), + ); + + return "$baseUrl?$token"; + + } } diff --git a/src/AzureBlobStorage/AzureBlobStorageAdapterTest.php b/src/AzureBlobStorage/AzureBlobStorageAdapterTest.php index 41ab5387d..177067dd1 100644 --- a/src/AzureBlobStorage/AzureBlobStorageAdapterTest.php +++ b/src/AzureBlobStorage/AzureBlobStorageAdapterTest.php @@ -10,6 +10,7 @@ use League\Flysystem\UnableToSetVisibility; use League\Flysystem\Visibility; use MicrosoftAzure\Storage\Blob\BlobRestProxy; +use MicrosoftAzure\Storage\Common\Internal\StorageServiceSettings; use function getenv; /** @@ -22,14 +23,21 @@ class AzureBlobStorageAdapterTest extends TestCase protected static function createFilesystemAdapter(): FilesystemAdapter { $dsn = getenv('FLYSYSTEM_AZURE_DSN'); + $dsn = 'DefaultEndpointsProtocol=https;AccountName=notpublicflysystemtests;AccountKey=B+Dm9IKyFvzr5TztsHcJqhF0jMpm2gR1v8OEsS0kgfftq6gnqgxcVJxF8peagofUwC/mOGbAgBSQ+AStnIXXPg==;EndpointSuffix=core.windows.net'; if (empty($dsn)) { self::markTestSkipped('FLYSYSTEM_AZURE_DSN is not provided.'); } $client = BlobRestProxy::createBlobService($dsn); + $serviceSettings = StorageServiceSettings::createFromConnectionString($dsn); - return new AzureBlobStorageAdapter($client, self::CONTAINER_NAME, 'ci'); + return new AzureBlobStorageAdapter( + $client, + self::CONTAINER_NAME, + 'ci', + serviceSettings: $serviceSettings, + ); } /** diff --git a/src/AzureBlobStorage/composer.json b/src/AzureBlobStorage/composer.json index 55beede40..cff0448ad 100644 --- a/src/AzureBlobStorage/composer.json +++ b/src/AzureBlobStorage/composer.json @@ -7,7 +7,7 @@ }, "require": { "php": "^8.0.2", - "league/flysystem": "^3.8.0", + "league/flysystem": "^3.10.0", "microsoft/azure-storage-blob": "^1.1" }, "license": "MIT", diff --git a/src/Filesystem.php b/src/Filesystem.php index 3a9a35330..1a6b6107b 100644 --- a/src/Filesystem.php +++ b/src/Filesystem.php @@ -4,10 +4,12 @@ namespace League\Flysystem; +use DateTimeInterface; use Generator; use League\Flysystem\UrlGeneration\ShardedPrefixPublicUrlGenerator; use League\Flysystem\UrlGeneration\PrefixPublicUrlGenerator; use League\Flysystem\UrlGeneration\PublicUrlGenerator; +use League\Flysystem\UrlGeneration\TemporaryUrlGenerator; use Throwable; use function is_array; @@ -20,6 +22,7 @@ class Filesystem implements FilesystemOperator private Config $config; private PathNormalizer $pathNormalizer; private ?PublicUrlGenerator $publicUrlGenerator; + private ?TemporaryUrlGenerator $temporaryUrlGenerator; public function __construct( FilesystemAdapter $adapter, @@ -170,6 +173,17 @@ public function publicUrl(string $path, array $config = []): string return $this->publicUrlGenerator->publicUrl($path, $config); } + public function temporaryUrl(string $path, DateTimeInterface $expiresAt, array $config = []): string + { + $generator = $this->temporaryUrlGenerator ?: $this->adapter; + + if ($generator instanceof TemporaryUrlGenerator) { + return $this->temporaryUrlGenerator->temporaryUrl($path, $expiresAt, $this->config->extend($config)); + } + + throw UnableToGenerateTemporaryUrl::noGeneratorConfigured($path); + } + public function checksum(string $path, array $config = []): string { $config = $this->config->extend($config); diff --git a/src/FilesystemReader.php b/src/FilesystemReader.php index f9322e13f..9e4acacf9 100644 --- a/src/FilesystemReader.php +++ b/src/FilesystemReader.php @@ -4,11 +4,14 @@ namespace League\Flysystem; +use DateTimeInterface; + /** * This interface contains everything to read from and inspect * a filesystem. All methods containing are non-destructive. * * @method string publicUrl(string $path, array $config = []) Will be added in 4.0 + * @method string temporaryUrl(string $path, DateTimeInterface $expiresAt, array $config = []) Will be added in 4.0 * @method string checksum(string $path, array $config = []) Will be added in 4.0 */ interface FilesystemReader diff --git a/src/GoogleCloudStorage/GoogleCloudStorageAdapter.php b/src/GoogleCloudStorage/GoogleCloudStorageAdapter.php index 82530d434..d65061abd 100644 --- a/src/GoogleCloudStorage/GoogleCloudStorageAdapter.php +++ b/src/GoogleCloudStorage/GoogleCloudStorageAdapter.php @@ -4,6 +4,7 @@ namespace League\Flysystem\GoogleCloudStorage; +use DateTimeInterface; use Google\Cloud\Core\Exception\NotFoundException; use Google\Cloud\Storage\Bucket; use Google\Cloud\Storage\StorageObject; @@ -27,6 +28,7 @@ use League\Flysystem\UnableToSetVisibility; use League\Flysystem\UnableToWriteFile; use League\Flysystem\UrlGeneration\PublicUrlGenerator; +use League\Flysystem\UrlGeneration\TemporaryUrlGenerator; use League\Flysystem\Visibility; use League\MimeTypeDetection\FinfoMimeTypeDetector; use League\MimeTypeDetection\MimeTypeDetector; @@ -41,7 +43,7 @@ use function sprintf; use function strlen; -class GoogleCloudStorageAdapter implements FilesystemAdapter, PublicUrlGenerator, ChecksumProvider +class GoogleCloudStorageAdapter implements FilesystemAdapter, PublicUrlGenerator, ChecksumProvider, TemporaryUrlGenerator { /** * @var Bucket @@ -404,4 +406,11 @@ public function checksum(string $path, Config $config): string return bin2hex(base64_decode($checksum)); } + + public function temporaryUrl(string $path, DateTimeInterface $expiresAt, Config $config): string + { + $location = $this->prefixer->prefixPath($path); + + return $this->bucket->object($location)->signedUrl($expiresAt, $config->get('gcp_signing_options', [])); + } } diff --git a/src/GoogleCloudStorage/composer.json b/src/GoogleCloudStorage/composer.json index 647a44a71..8b1b5520c 100644 --- a/src/GoogleCloudStorage/composer.json +++ b/src/GoogleCloudStorage/composer.json @@ -12,7 +12,7 @@ "require": { "php": "^8.0.2", "google/cloud-storage": "^1.23", - "league/flysystem": "^3.8.0", + "league/flysystem": "^3.10.0", "league/mime-type-detection": "^1.0.0" }, "license": "MIT", diff --git a/src/PathPrefixing/PathPrefixedAdapter.php b/src/PathPrefixing/PathPrefixedAdapter.php index c4bd0f80a..617c6c3d7 100644 --- a/src/PathPrefixing/PathPrefixedAdapter.php +++ b/src/PathPrefixing/PathPrefixedAdapter.php @@ -2,6 +2,7 @@ namespace League\Flysystem\PathPrefixing; +use DateTimeInterface; use Generator; use League\Flysystem\CalculateChecksumFromStream; use League\Flysystem\ChecksumProvider; @@ -16,15 +17,17 @@ use League\Flysystem\UnableToDeleteDirectory; use League\Flysystem\UnableToDeleteFile; use League\Flysystem\UnableToGeneratePublicUrl; +use League\Flysystem\UnableToGenerateTemporaryUrl; use League\Flysystem\UnableToMoveFile; use League\Flysystem\UnableToReadFile; use League\Flysystem\UnableToRetrieveMetadata; use League\Flysystem\UnableToSetVisibility; use League\Flysystem\UnableToWriteFile; use League\Flysystem\UrlGeneration\PublicUrlGenerator; +use League\Flysystem\UrlGeneration\TemporaryUrlGenerator; use Throwable; -class PathPrefixedAdapter implements FilesystemAdapter, PublicUrlGenerator, ChecksumProvider +class PathPrefixedAdapter implements FilesystemAdapter, PublicUrlGenerator, ChecksumProvider, TemporaryUrlGenerator { use CalculateChecksumFromStream; @@ -209,4 +212,13 @@ public function checksum(string $path, Config $config): string return $this->calculateChecksumFromStream($path, $config); } + + public function temporaryUrl(string $path, DateTimeInterface $expiresAt, Config $config): string + { + if ( ! $this->adapter instanceof TemporaryUrlGenerator) { + throw UnableToGenerateTemporaryUrl::noGeneratorConfigured($path); + } + + return $this->adapter->temporaryUrl($path, $expiresAt, $config); + } } diff --git a/src/PathPrefixing/composer.json b/src/PathPrefixing/composer.json index ed3903503..213b1512b 100644 --- a/src/PathPrefixing/composer.json +++ b/src/PathPrefixing/composer.json @@ -11,7 +11,7 @@ }, "require": { "php": "^8.0.2", - "league/flysystem": "^3.7.0" + "league/flysystem": "^3.10.0" }, "license": "MIT", "authors": [ diff --git a/src/ReadOnly/ReadOnlyFilesystemAdapter.php b/src/ReadOnly/ReadOnlyFilesystemAdapter.php index 51a2aebd2..3eaf40af4 100644 --- a/src/ReadOnly/ReadOnlyFilesystemAdapter.php +++ b/src/ReadOnly/ReadOnlyFilesystemAdapter.php @@ -2,6 +2,7 @@ namespace League\Flysystem\ReadOnly; +use DateTimeInterface; use League\Flysystem\CalculateChecksumFromStream; use League\Flysystem\ChecksumProvider; use League\Flysystem\Config; @@ -12,12 +13,14 @@ use League\Flysystem\UnableToDeleteDirectory; use League\Flysystem\UnableToDeleteFile; use League\Flysystem\UnableToGeneratePublicUrl; +use League\Flysystem\UnableToGenerateTemporaryUrl; use League\Flysystem\UnableToMoveFile; use League\Flysystem\UnableToSetVisibility; use League\Flysystem\UnableToWriteFile; use League\Flysystem\UrlGeneration\PublicUrlGenerator; +use League\Flysystem\UrlGeneration\TemporaryUrlGenerator; -class ReadOnlyFilesystemAdapter implements FilesystemAdapter, PublicUrlGenerator, ChecksumProvider +class ReadOnlyFilesystemAdapter implements FilesystemAdapter, PublicUrlGenerator, ChecksumProvider, TemporaryUrlGenerator { use CalculateChecksumFromStream; @@ -127,4 +130,13 @@ public function checksum(string $path, Config $config): string return $this->calculateChecksumFromStream($path, $config); } + + public function temporaryUrl(string $path, DateTimeInterface $expiresAt, Config $config): string + { + if ( ! $this->adapter instanceof TemporaryUrlGenerator) { + throw UnableToGenerateTemporaryUrl::noGeneratorConfigured($path); + } + + return $this->adapter->temporaryUrl($path, $expiresAt, $config); + } } diff --git a/src/ReadOnly/composer.json b/src/ReadOnly/composer.json index 03c808162..aa7d95c2f 100644 --- a/src/ReadOnly/composer.json +++ b/src/ReadOnly/composer.json @@ -11,7 +11,7 @@ }, "require": { "php": "^8.0.2", - "league/flysystem": "^3.7.0" + "league/flysystem": "^3.10.0" }, "license": "MIT", "authors": [ diff --git a/src/UnableToGenerateTemporaryUrl.php b/src/UnableToGenerateTemporaryUrl.php new file mode 100644 index 000000000..338601c73 --- /dev/null +++ b/src/UnableToGenerateTemporaryUrl.php @@ -0,0 +1,26 @@ +getMessage(), $path, $exception); + } + + public static function noGeneratorConfigured(string $path, string $extraReason = ''): static + { + return new static('No generator was configured ' . $extraReason, $path); + } +} diff --git a/src/UrlGeneration/TemporaryUrlGenerator.php b/src/UrlGeneration/TemporaryUrlGenerator.php new file mode 100644 index 000000000..87074f753 --- /dev/null +++ b/src/UrlGeneration/TemporaryUrlGenerator.php @@ -0,0 +1,16 @@ +