diff --git a/src/Filesystem.php b/src/Filesystem.php index 0865ccc5e..b3915b904 100644 --- a/src/Filesystem.php +++ b/src/Filesystem.php @@ -6,12 +6,13 @@ use DateTimeInterface; use Generator; -use League\Flysystem\UrlGeneration\ShardedPrefixPublicUrlGenerator; use League\Flysystem\UrlGeneration\PrefixPublicUrlGenerator; use League\Flysystem\UrlGeneration\PublicUrlGenerator; +use League\Flysystem\UrlGeneration\ShardedPrefixPublicUrlGenerator; use League\Flysystem\UrlGeneration\TemporaryUrlGenerator; use Throwable; +use function array_key_exists; use function is_array; class Filesystem implements FilesystemOperator @@ -119,7 +120,7 @@ private function pipeListing(string $location, bool $deep, iterable $listing): G public function move(string $source, string $destination, array $config = []): void { - $config = $this->config->withoutSettings(Config::OPTION_VISIBILITY)->extend($config); + $config = $this->resolveConfigForMoveAndCopy($config); $from = $this->pathNormalizer->normalizePath($source); $to = $this->pathNormalizer->normalizePath($destination); @@ -138,7 +139,7 @@ public function move(string $source, string $destination, array $config = []): v public function copy(string $source, string $destination, array $config = []): void { - $config = $this->config->withoutSettings(Config::OPTION_VISIBILITY)->extend($config); + $config = $this->resolveConfigForMoveAndCopy($config); $from = $this->pathNormalizer->normalizePath($source); $to = $this->pathNormalizer->normalizePath($destination); @@ -256,4 +257,21 @@ private function rewindStream($resource): void rewind($resource); } } + + private function resolveConfigForMoveAndCopy(array $config): Config + { + $retainVisibility = $this->config->get('retain_visibility', $config['retain_visibility'] ?? true); + $fullConfig = $this->config->extend($config); + + /* + * By default, we retain visibility. When we do not retain visibility, the visibility setting + * from the default configuration is ignored. Only when it is set explicitly, we propagate the + * setting. + */ + if ($retainVisibility && ! array_key_exists(Config::OPTION_VISIBILITY, $config)) { + $fullConfig = $fullConfig->withoutSettings(Config::OPTION_VISIBILITY)->extend($config); + } + + return $fullConfig; + } } diff --git a/src/FilesystemTest.php b/src/FilesystemTest.php index b00705b14..a4b0e183b 100644 --- a/src/FilesystemTest.php +++ b/src/FilesystemTest.php @@ -674,4 +674,157 @@ public function unable_to_get_checksum_directory(): void $filesystem->checksum('foo'); } + + /** + * @test + * @dataProvider fileMoveOrCopyScenarios + */ + public function moving_a_file_with_visibility_scenario( + array $mainConfig, + array $moveConfig, + ?string $writeVisibility, + string $expectedVisibility + ): void { + // arrange + $filesystem = new Filesystem( + new InMemoryFilesystemAdapter(), + $mainConfig + ); + $writeConfig = $writeVisibility ? ['visibility' => $writeVisibility] : []; + $filesystem->write('from.txt', 'contents', $writeConfig); + + // act + $filesystem->move('from.txt', 'to.txt', $moveConfig); + + // assert + $this->assertEquals($expectedVisibility, $filesystem->visibility('to.txt')); + } + + /** + * @test + * @dataProvider fileMoveOrCopyScenarios + */ + public function copying_a_file_with_visibility_scenario( + array $mainConfig, + array $copyConfig, + ?string $writeVisibility, + string $expectedVisibility + ): void { + // arrange + $filesystem = new Filesystem( + new InMemoryFilesystemAdapter(), + $mainConfig + ); + $writeConfig = $writeVisibility ? ['visibility' => $writeVisibility] : []; + $filesystem->write('from.txt', 'contents', $writeConfig); + + // act + $filesystem->copy('from.txt', 'to.txt', $copyConfig); + + // assert + $this->assertEquals($expectedVisibility, $filesystem->visibility('to.txt')); + } + + public static function fileMoveOrCopyScenarios(): iterable + { + yield 'retain visibility, write default, default private' => [ + ['retain_visibility' => true, 'visibility' => 'private'], + [], + null, + 'private' + ]; + yield 'retain visibility, write default, default public' => [ + ['retain_visibility' => true, 'visibility' => 'public'], + [], + null, + 'public' + ]; + yield 'retain visibility, write public, default private' => [ + ['retain_visibility' => true, 'visibility' => 'private'], + [], + 'public', + 'public' + ]; + yield 'retain visibility, write private, default public' => [ + ['retain_visibility' => true, 'visibility' => 'public'], + [], + 'private', + 'private' + ]; + + yield 'retain visibility, write default, default private, execute public' => [ + ['retain_visibility' => true, 'visibility' => 'private'], + ['visibility' => 'public'], + null, + 'public' + ]; + yield 'retain visibility, write default, default public, execute private' => [ + ['retain_visibility' => true, 'visibility' => 'public'], + ['visibility' => 'private'], + null, + 'private' + ]; + yield 'retain visibility, write public, default private, execute private' => [ + ['retain_visibility' => true, 'visibility' => 'private'], + ['visibility' => 'private'], + 'public', + 'private' + ]; + yield 'retain visibility, write private, default public, execute public' => [ + ['retain_visibility' => true, 'visibility' => 'public'], + ['visibility' => 'public'], + 'private', + 'public' + ]; + + yield 'do not retain visibility, write default, default private' => [ + ['retain_visibility' => false, 'visibility' => 'private'], + [], + null, + 'private' + ]; + yield 'do not retain visibility, write default, default public' => [ + ['retain_visibility' => false, 'visibility' => 'public'], + [], + null, + 'public' + ]; + yield 'do not retain visibility, write public, default private' => [ + ['retain_visibility' => false, 'visibility' => 'private'], + [], + 'public', + 'private' + ]; + yield 'do not retain visibility, write private, default public' => [ + ['retain_visibility' => false, 'visibility' => 'public'], + [], + 'private', + 'public' + ]; + + yield 'do not retain visibility, write default, default private, execute public' => [ + ['retain_visibility' => false, 'visibility' => 'private'], + ['visibility' => 'public'], + null, + 'public' + ]; + yield 'do not retain visibility, write default, default public, execute private' => [ + ['retain_visibility' => false, 'visibility' => 'public'], + ['visibility' => 'private'], + null, + 'private' + ]; + yield 'do not retain visibility, write public, default private, execute public' => [ + ['retain_visibility' => false, 'visibility' => 'private'], + ['visibility' => 'public'], + 'public', + 'public' + ]; + yield 'do not retain visibility, write private, default public, execute private' => [ + ['retain_visibility' => false, 'visibility' => 'public'], + ['visibility' => 'private'], + 'private', + 'private' + ]; + } } diff --git a/src/InMemory/InMemoryFilesystemAdapter.php b/src/InMemory/InMemoryFilesystemAdapter.php index 0bccacc5e..76de6ad9f 100644 --- a/src/InMemory/InMemoryFilesystemAdapter.php +++ b/src/InMemory/InMemoryFilesystemAdapter.php @@ -230,6 +230,10 @@ public function move(string $source, string $destination, Config $config): void $this->files[$destinationPath] = $this->files[$sourcePath]; unset($this->files[$sourcePath]); + + if ($visibility = $config->get(Config::OPTION_VISIBILITY)) { + $this->setVisibility($destination, $visibility); + } } public function copy(string $source, string $destination, Config $config): void @@ -242,8 +246,11 @@ public function copy(string $source, string $destination, Config $config): void } $lastModified = $config->get('timestamp', time()); - $this->files[$destination] = $this->files[$source]->withLastModified($lastModified); + + if ($visibility = $config->get(Config::OPTION_VISIBILITY)) { + $this->setVisibility($destination, $visibility); + } } private function preparePath(string $path): string