Skip to content
Merged
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
2 changes: 1 addition & 1 deletion src/CloudinaryServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public function boot(): void
]);
}

$adapter = new CloudinaryStorageAdapter($cloudinary);
$adapter = new CloudinaryStorageAdapter($cloudinary, null, $config['prefix'] ?? null);

return new FilesystemAdapter(new Filesystem($adapter, $config), $adapter, $config);
});
Expand Down
38 changes: 30 additions & 8 deletions src/CloudinaryStorageAdapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,13 @@

class CloudinaryStorageAdapter implements ChecksumProvider, FilesystemAdapter
{
private MimeTypeDetector $mimeTypeDetector;

public function __construct(private Cloudinary $cloudinary, ?MimeTypeDetector $mimeTypeDetector = null)
public function __construct(
private Cloudinary $cloudinary,
private ?MimeTypeDetector $mimeTypeDetector = null,
private ?string $prefix = null)
{
$this->mimeTypeDetector = $mimeTypeDetector ?: new FinfoMimeTypeDetector;
$this->prefix = $prefix ? str_replace('\\', '/', trim($prefix, '/')) : '';
}

public function getUrl(string $path): string
Expand All @@ -38,7 +40,7 @@ public function copy(string $source, string $destination, Config $config): void

public function createDirectory(string $path, Config $config): void
{
$this->cloudinary->adminApi()->createFolder($path);
$this->cloudinary->adminApi()->createFolder($this->applyPrefixToPath($path));
}

public function delete(string $path): void
Expand All @@ -58,7 +60,7 @@ public function delete(string $path): void

public function deleteDirectory(string $path): void
{
$this->cloudinary->adminApi()->deleteAssetsByPrefix($path);
$this->cloudinary->adminApi()->deleteAssetsByPrefix($this->applyPrefixToPath($path));
}

public function directoryExists(string $path): bool
Expand Down Expand Up @@ -105,7 +107,7 @@ public function listContents(string $path, bool $deep): array|\Traversable
do {
$response = $this->cloudinary->adminApi()->assets([
'type' => 'upload',
'prefix' => $path,
'prefix' => $this->applyPrefixToPath($path),
'max_results' => 500,
'next_cursor' => isset($response) ? $response->offsetGet('next_cursor') : null,
]);
Expand Down Expand Up @@ -194,12 +196,12 @@ public function checksum(string $path, Config $config): string
public function prepareResource(string $path): array
{
$info = pathinfo($path);

// Ensure dirname uses forward slashes, regardless of OS
$dirname = str_replace('\\', '/', $info['dirname']);
// Always use forward slash for path construction
$id = $dirname.'/'.$info['filename'];

$mimeType = $this->mimeTypeDetector->detectMimeTypeFromPath($path);

if (strpos($mimeType, 'image/') === 0) {
Expand All @@ -210,6 +212,26 @@ public function prepareResource(string $path): array
return [$id, 'video'];
}

// If a prefix is configured, apply it to the id. When applying a prefix
// strip any leading './' or '/' from the generated id so we don't end up
// with paths like "prefix/./file".
if ($this->prefix !== '') {
$normalizedId = ltrim($id, './\\/');
$id = $this->prefix.
($normalizedId !== '' ? '/'.$normalizedId : '');
}

return [$id, 'raw'];
}

private function applyPrefixToPath(string $path): string
{
if ($this->prefix === '') {
return $path;
}

$trimmed = ltrim(str_replace('\\', '/', $path), '\/');

return $this->prefix.($trimmed !== '' ? '/'.$trimmed : '');
}
}
19 changes: 19 additions & 0 deletions tests/Unit/CloudinaryStorageAdapterTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ function createApiResponse(array $data, int $statusCode = 200): ApiResponse
$this->cloudinary->adminApi()->willReturn($this->adminApi->reveal());

$this->adapter = new CloudinaryStorageAdapter($this->cloudinary->reveal());

// Also create an adapter instance configured with a prefix for tests
// that assert prefix behavior.
$this->prefixedAdapter = new CloudinaryStorageAdapter($this->cloudinary->reveal(), null, 'Fixtures');
});

it('can copy a file', function () {
Expand Down Expand Up @@ -138,6 +142,21 @@ function createApiResponse(array $data, int $statusCode = 200): ApiResponse
expect(iterator_to_array($contents))->toHaveCount(1);
});

it('applies configured prefix when listing contents', function () {
$response = createApiResponse([
'resources' => [],
]);

$this->adminApi->assets(Argument::that(function ($options) {
return $options['type'] === 'upload'
&& $options['prefix'] === 'Fixtures/test-dir'
&& $options['max_results'] === 500;
}))->willReturn($response)->shouldBeCalled();

$contents = $this->prefixedAdapter->listContents('test-dir', false);
expect(iterator_to_array($contents))->toHaveCount(0);
});

it('can read a file', function () {
$this->adminApi->asset(
Argument::exact('Fixtures/test-file'),
Expand Down