diff --git a/bundle/Resources/config/services/core.yaml b/bundle/Resources/config/services/core.yaml index c111bd6c..c2afe6fb 100644 --- a/bundle/Resources/config/services/core.yaml +++ b/bundle/Resources/config/services/core.yaml @@ -27,6 +27,7 @@ services: arguments: - "@netgen_remote_media.provider.cloudinary.gateway.inner" - "@netgen_remote_media.cache.pool" + - "%netgen_remote_media.cloudinary.folder_mode%" - "%netgen_remote_media.cache.ttl%" netgen_remote_media.provider.cloudinary.gateway.logged: @@ -48,6 +49,7 @@ services: - "@netgen_remote_media.provider.cloudinary.resolver.upload_options" - "%netgen_remote_media.named_remote_resources%" - "%netgen_remote_media.named_remote_resource_locations%" + - '%netgen_remote_media.cloudinary.folder_mode%' - "@?logger" netgen_remote_media.provider.cloudinary.converter.resource_type: @@ -79,6 +81,7 @@ services: - '@netgen_remote_media.provider.cloudinary.converter.resource_type' - '@netgen_remote_media.provider.cloudinary.converter.visibility_type' - '@netgen_remote_media.factory.md5_file_hash' + - '%netgen_remote_media.cloudinary.folder_mode%' netgen_remote_media.provider.cloudinary.factory.search_result: class: Netgen\RemoteMedia\Core\Provider\Cloudinary\Factory\SearchResult @@ -97,6 +100,7 @@ services: public: false arguments: - '@netgen_remote_media.provider.cloudinary.converter.visibility_type' + - '%netgen_remote_media.cloudinary.folder_mode%' netgen_remote_media.provider.cloudinary.resolver.search_expression: class: Netgen\RemoteMedia\Core\Provider\Cloudinary\Resolver\SearchExpression @@ -104,6 +108,7 @@ services: arguments: - '@netgen_remote_media.provider.cloudinary.converter.resource_type' - '@netgen_remote_media.provider.cloudinary.converter.visibility_type' + - '%netgen_remote_media.cloudinary.folder_mode%' netgen_remote_media.factory.date_time: class: Netgen\RemoteMedia\Core\Factory\DateTime diff --git a/lib/Core/Provider/Cloudinary/CloudinaryProvider.php b/lib/Core/Provider/Cloudinary/CloudinaryProvider.php index 09dd39be..c9f61f3e 100644 --- a/lib/Core/Provider/Cloudinary/CloudinaryProvider.php +++ b/lib/Core/Provider/Cloudinary/CloudinaryProvider.php @@ -50,8 +50,9 @@ public function __construct( private UploadOptionsResolver $uploadOptionsResolver, array $namedRemoteResources, array $namedRemoteResourceLocations, + private string $folderMode, ?LoggerInterface $logger = null, - bool $shouldDeleteFromRemote = false + bool $shouldDeleteFromRemote = false, ) { parent::__construct( $registry, @@ -121,7 +122,7 @@ public function loadFromRemote(string $remoteId): RemoteResource { try { return $this->gateway->get( - CloudinaryRemoteId::fromRemoteId($remoteId), + CloudinaryRemoteId::fromRemoteId($remoteId, $this->folderMode), ); } catch (InvalidRemoteIdException $exception) { $this->logger->notice('[NGRM][Cloudinary] ' . $exception->getMessage()); @@ -133,7 +134,7 @@ public function loadFromRemote(string $remoteId): RemoteResource public function deleteFromRemote(RemoteResource $resource): void { $this->gateway->delete( - CloudinaryRemoteId::fromRemoteId($resource->getRemoteId()), + CloudinaryRemoteId::fromRemoteId($resource->getRemoteId(), $this->folderMode), ); } @@ -163,12 +164,12 @@ public function updateOnRemote(RemoteResource $resource): void if (count($resource->getTags()) === 0) { $this->gateway->removeAllTagsFromResource( - CloudinaryRemoteId::fromRemoteId($resource->getRemoteId()), + CloudinaryRemoteId::fromRemoteId($resource->getRemoteId(), $this->folderMode), ); } $this->gateway->update( - CloudinaryRemoteId::fromRemoteId($resource->getRemoteId()), + CloudinaryRemoteId::fromRemoteId($resource->getRemoteId(), $this->folderMode), $options, ); } @@ -181,7 +182,7 @@ public function generateDownloadLink(RemoteResource $resource, array $transforma } return $this->gateway->getDownloadLink( - CloudinaryRemoteId::fromRemoteId($resource->getRemoteId()), + CloudinaryRemoteId::fromRemoteId($resource->getRemoteId(), $this->folderMode), $options, $resource instanceof AuthenticatedRemoteResource ? $resource->getToken() : null, ); @@ -189,7 +190,7 @@ public function generateDownloadLink(RemoteResource $resource, array $transforma public function authenticateRemoteResource(RemoteResource $resource, AuthToken $token): AuthenticatedRemoteResource { - $url = $this->gateway->getAuthenticatedUrl(CloudinaryRemoteId::fromRemoteId($resource->getRemoteId()), $token); + $url = $this->gateway->getAuthenticatedUrl(CloudinaryRemoteId::fromRemoteId($resource->getRemoteId(), $this->folderMode), $token); return new AuthenticatedRemoteResource($resource, $url, $token); } @@ -197,7 +198,7 @@ public function authenticateRemoteResource(RemoteResource $resource, AuthToken $ public function authenticateRemoteResourceLocation(RemoteResourceLocation $location, AuthToken $token): RemoteResourceLocation { $url = $this->gateway->getAuthenticatedUrl( - CloudinaryRemoteId::fromRemoteId($location->getRemoteResource()->getRemoteId()), + CloudinaryRemoteId::fromRemoteId($location->getRemoteResource()->getRemoteId(), $this->folderMode), $token, ); @@ -257,7 +258,7 @@ protected function internalUpload(ResourceStruct $resourceStruct): RemoteResourc protected function internalBuildVariation(RemoteResource $resource, array $transformations = []): RemoteResourceVariation { $variationUrl = $this->gateway->getVariationUrl( - CloudinaryRemoteId::fromRemoteId($resource->getRemoteId()), + CloudinaryRemoteId::fromRemoteId($resource->getRemoteId(), $this->folderMode), $transformations, $resource instanceof AuthenticatedRemoteResource ? $resource->getToken() : null, ); @@ -279,7 +280,7 @@ protected function internalBuildVideoThumbnail(RemoteResource $resource, array $ $options['start_offset'] = $startOffset !== null ? $startOffset : 'auto'; $thumbnailUrl = $this->gateway->getVideoThumbnail( - CloudinaryRemoteId::fromRemoteId($resource->getRemoteId()), + CloudinaryRemoteId::fromRemoteId($resource->getRemoteId(), $this->folderMode), $options, $resource instanceof AuthenticatedRemoteResource ? $resource->getToken() : null, ); @@ -312,7 +313,7 @@ protected function generatePictureTag(RemoteResource $resource, array $transform } return $this->gateway->getImageTag( - CloudinaryRemoteId::fromRemoteId($resource->getRemoteId()), + CloudinaryRemoteId::fromRemoteId($resource->getRemoteId(), $this->folderMode), $options, $resource instanceof AuthenticatedRemoteResource ? $resource->getToken() : null, ); @@ -340,7 +341,7 @@ protected function generateVideoTag(RemoteResource $resource, array $transformat } return $this->gateway->getVideoTag( - CloudinaryRemoteId::fromRemoteId($resource->getRemoteId()), + CloudinaryRemoteId::fromRemoteId($resource->getRemoteId(), $this->folderMode), $options, $resource instanceof AuthenticatedRemoteResource ? $resource->getToken() : null, ); @@ -368,7 +369,7 @@ protected function generateVideoThumbnailTag(RemoteResource $resource, array $tr } $thumbnailTag = $this->gateway->getImageTag( - CloudinaryRemoteId::fromRemoteId($resource->getRemoteId()), + CloudinaryRemoteId::fromRemoteId($resource->getRemoteId(), $this->folderMode), $options, $resource instanceof AuthenticatedRemoteResource ? $resource->getToken() : null, ); @@ -392,7 +393,7 @@ protected function generateAudioTag(RemoteResource $resource, array $transformat ]; $tag = $this->gateway->getVideoTag( - CloudinaryRemoteId::fromRemoteId($resource->getRemoteId()), + CloudinaryRemoteId::fromRemoteId($resource->getRemoteId(), $this->folderMode), $options, $resource instanceof AuthenticatedRemoteResource ? $resource->getToken() : null, ); diff --git a/lib/Core/Provider/Cloudinary/CloudinaryRemoteId.php b/lib/Core/Provider/Cloudinary/CloudinaryRemoteId.php index 25d37037..7be927ef 100644 --- a/lib/Core/Provider/Cloudinary/CloudinaryRemoteId.php +++ b/lib/Core/Provider/Cloudinary/CloudinaryRemoteId.php @@ -6,6 +6,7 @@ use Netgen\RemoteMedia\API\Values\Folder; use Netgen\RemoteMedia\Exception\Cloudinary\InvalidRemoteIdException; +use Netgen\RemoteMedia\Exception\NotSupportedException; use function array_pop; use function count; @@ -17,22 +18,24 @@ final class CloudinaryRemoteId public function __construct( private string $type, private string $resourceType, - private string $resourceId + private string $resourceId, + private string $folderMode = CloudinaryProvider::FOLDER_MODE_FIXED, ) {} - public static function fromCloudinaryData(array $data): self + public static function fromCloudinaryData(array $data, string $folderMode = CloudinaryProvider::FOLDER_MODE_FIXED): self { return new self( $data['type'] ?? 'upload', $data['resource_type'] ?? 'image', $data['public_id'], + $folderMode, ); } /** * @throws InvalidRemoteIdException */ - public static function fromRemoteId(string $remoteId): self + public static function fromRemoteId(string $remoteId, string $folderMode = CloudinaryProvider::FOLDER_MODE_FIXED): self { $parts = explode('|', $remoteId); @@ -44,6 +47,7 @@ public static function fromRemoteId(string $remoteId): self $parts[0], $parts[1], $parts[2], + $folderMode, ); } @@ -75,6 +79,13 @@ public function getResourceId(): string public function getFolder(): ?Folder { + if ($this->folderMode !== CloudinaryProvider::FOLDER_MODE_FIXED) { + throw new NotSupportedException( + 'Cloudinary', + sprintf('fetching folder from path in "%s" folder mode', $this->folderMode), + ); + } + $resourceIdParts = explode('/', $this->resourceId); array_pop($resourceIdParts); diff --git a/lib/Core/Provider/Cloudinary/Factory/RemoteResource.php b/lib/Core/Provider/Cloudinary/Factory/RemoteResource.php index ca27209d..c4a94bdc 100644 --- a/lib/Core/Provider/Cloudinary/Factory/RemoteResource.php +++ b/lib/Core/Provider/Cloudinary/Factory/RemoteResource.php @@ -7,7 +7,9 @@ use Cloudinary\Asset\Media; use Netgen\RemoteMedia\API\Factory\FileHash as FileHashFactoryInterface; use Netgen\RemoteMedia\API\Factory\RemoteResource as RemoteResourceFactoryInterface; +use Netgen\RemoteMedia\API\Values\Folder; use Netgen\RemoteMedia\API\Values\RemoteResource as RemoteResourceValue; +use Netgen\RemoteMedia\Core\Provider\Cloudinary\CloudinaryProvider; use Netgen\RemoteMedia\Core\Provider\Cloudinary\CloudinaryRemoteId; use Netgen\RemoteMedia\Core\Provider\Cloudinary\Converter\ResourceType as ResourceTypeConverter; use Netgen\RemoteMedia\Core\Provider\Cloudinary\Converter\VisibilityType as VisibilityTypeConverter; @@ -25,14 +27,15 @@ final class RemoteResource implements RemoteResourceFactoryInterface public function __construct( private ResourceTypeConverter $resourceTypeConverter, private VisibilityTypeConverter $visibilityTypeConverter, - private FileHashFactoryInterface $fileHashFactory + private FileHashFactoryInterface $fileHashFactory, + private string $folderMode, ) {} public function create($data): RemoteResourceValue { $this->validateData($data); - $cloudinaryRemoteId = CloudinaryRemoteId::fromCloudinaryData($data); + $cloudinaryRemoteId = CloudinaryRemoteId::fromCloudinaryData($data, $this->folderMode); return new RemoteResourceValue( remoteId: $cloudinaryRemoteId->getRemoteId(), @@ -43,7 +46,7 @@ public function create($data): RemoteResourceValue originalFilename: $this->resolveOriginalFilename($data), version: ($data['version'] ?? null) !== null ? (string) $data['version'] : null, visibility: $this->resolveVisibility($data), - folder: $cloudinaryRemoteId->getFolder(), + folder: $this->resolveFolder($data), size: $data['bytes'] ?? 0, altText: $this->resolveAltText($data), caption: $this->resolveCaption($data), @@ -97,6 +100,19 @@ private function resolveVisibility(array $data): string return $this->visibilityTypeConverter->fromCloudinaryType($type); } + private function resolveFolder(array $data): ?Folder + { + if ($this->folderMode === CloudinaryProvider::FOLDER_MODE_FIXED) { + return CloudinaryRemoteId::fromCloudinaryData($data, $this->folderMode)->getFolder(); + } + + if (($data['asset_folder'] ?? '') === '') { + return null; + } + + return Folder::fromPath($data['asset_folder']); + } + private function resolveAltText(array $data): ?string { if (($data['context']['custom']['alt_text'] ?? null) !== null) { diff --git a/lib/Core/Provider/Cloudinary/Gateway/Cache/Psr6CachedGateway.php b/lib/Core/Provider/Cloudinary/Gateway/Cache/Psr6CachedGateway.php index 5876af2e..60024a79 100644 --- a/lib/Core/Provider/Cloudinary/Gateway/Cache/Psr6CachedGateway.php +++ b/lib/Core/Provider/Cloudinary/Gateway/Cache/Psr6CachedGateway.php @@ -39,7 +39,8 @@ final class Psr6CachedGateway implements CacheableGatewayInterface public function __construct( private GatewayInterface $gateway, private CacheItemPoolInterface $cache, - private int $ttl = 7200 + private string $folderMode, + private int $ttl = 7200, ) {} public function usage(): StatusData @@ -198,7 +199,7 @@ public function upload(string $fileUri, array $options): RemoteResource { $uploadResult = $this->gateway->upload($fileUri, $options); - $this->invalidateResourceCache(CloudinaryRemoteId::fromRemoteId($uploadResult->getRemoteId())); + $this->invalidateResourceCache(CloudinaryRemoteId::fromRemoteId($uploadResult->getRemoteId(), $this->folderMode)); $this->invalidateResourceListCache(); $this->invalidateFoldersCache(); $this->invalidateTagsCache(); diff --git a/lib/Core/Provider/Cloudinary/Resolver/SearchExpression.php b/lib/Core/Provider/Cloudinary/Resolver/SearchExpression.php index 5e3feff8..e35ee17d 100644 --- a/lib/Core/Provider/Cloudinary/Resolver/SearchExpression.php +++ b/lib/Core/Provider/Cloudinary/Resolver/SearchExpression.php @@ -6,6 +6,7 @@ use Netgen\RemoteMedia\API\Search\Query; use Netgen\RemoteMedia\API\Values\RemoteResource; +use Netgen\RemoteMedia\Core\Provider\Cloudinary\CloudinaryProvider; use Netgen\RemoteMedia\Core\Provider\Cloudinary\CloudinaryRemoteId; use Netgen\RemoteMedia\Core\Provider\Cloudinary\Converter\ResourceType as ResourceTypeConverter; use Netgen\RemoteMedia\Core\Provider\Cloudinary\Converter\VisibilityType as VisibilityTypeConverter; @@ -30,6 +31,7 @@ final class SearchExpression public function __construct( private ResourceTypeConverter $resourceTypeConverter, private VisibilityTypeConverter $visibilityTypeConverter, + private string $folderMode, ) {} public function resolve(Query $query): string @@ -230,7 +232,9 @@ private function resolveFolders(Query $query): ?string return null; } - $folders = array_map(static fn ($value) => sprintf('folder:"%s"', $value), $query->getFolders()); + $key = $this->folderMode === CloudinaryProvider::FOLDER_MODE_DYNAMIC ? 'asset_folder' : 'folder'; + + $folders = array_map(static fn ($value) => sprintf('%s:"%s"', $key, $value), $query->getFolders()); return '(' . implode(' OR ', $folders) . ')'; } @@ -254,7 +258,7 @@ private function resolveResourceIds(Query $query): ?string $resourceIds = array_unique( array_map( - static fn ($remoteId) => CloudinaryRemoteId::fromRemoteId($remoteId)->getResourceId(), + static fn ($remoteId) => CloudinaryRemoteId::fromRemoteId($remoteId, $this->folderMode)->getResourceId(), $query->getRemoteIds(), ), ); diff --git a/lib/Core/Provider/Cloudinary/Resolver/UploadOptions.php b/lib/Core/Provider/Cloudinary/Resolver/UploadOptions.php index 79c43e1b..23833911 100644 --- a/lib/Core/Provider/Cloudinary/Resolver/UploadOptions.php +++ b/lib/Core/Provider/Cloudinary/Resolver/UploadOptions.php @@ -6,6 +6,7 @@ use Netgen\RemoteMedia\API\Upload\FileStruct; use Netgen\RemoteMedia\API\Upload\ResourceStruct; +use Netgen\RemoteMedia\Core\Provider\Cloudinary\CloudinaryProvider; use Netgen\RemoteMedia\Core\Provider\Cloudinary\Converter\VisibilityType as VisibilityTypeConverter; use Netgen\RemoteMedia\Exception\MimeCategoryParseException; use Netgen\RemoteMedia\Exception\MimeTypeNotFoundException; @@ -25,6 +26,7 @@ final class UploadOptions { public function __construct( private VisibilityTypeConverter $visibilityTypeConverter, + private string $folderMode, private array $noExtensionMimeTypes = ['image', 'video'], private ?MimeTypesInterface $mimeTypes = null ) { @@ -48,11 +50,11 @@ public function resolve(ResourceStruct $resourceStruct): array $publicId = md5_file($resourceStruct->getFileStruct()->getUri()); } - if ($resourceStruct->getFolder()) { + if ($resourceStruct->getFolder() && $this->folderMode === CloudinaryProvider::FOLDER_MODE_FIXED) { $publicId = $resourceStruct->getFolder()->getPath() . '/' . $publicId; } - return [ + $options = [ 'public_id' => $publicId, 'overwrite' => $resourceStruct->doOverwrite(), 'invalidate' => $resourceStruct->doInvalidate() || $resourceStruct->doOverwrite(), @@ -64,6 +66,12 @@ public function resolve(ResourceStruct $resourceStruct): array 'access_control' => $this->visibilityTypeConverter->toCloudinaryAccessControl($resourceStruct->getVisibility()), 'tags' => $resourceStruct->getTags(), ]; + + if ($resourceStruct->getFolder() && $this->folderMode === CloudinaryProvider::FOLDER_MODE_DYNAMIC) { + $options['folder'] = $resourceStruct->getFolder()->getPath(); + } + + return $options; } private function appendExtension(string $publicId, FileStruct $fileStruct): string