Skip to content

Commit bc3c250

Browse files
committed
fix: saving and reading Uploadccare metadata
1 parent e96cfc7 commit bc3c250

File tree

2 files changed

+135
-23
lines changed

2 files changed

+135
-23
lines changed

packages/uploadcare-field/src/Observers/ContentFieldValueObserver.php

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,9 @@ private function parseItem(mixed $item): array
150150
$uuidOffset = $matches[1][1] ?? null;
151151
$uuidLen = strlen($uuid);
152152
$modifiers = ($uuidOffset !== null) ? substr($item, $uuidOffset + $uuidLen) : '';
153+
if (! empty($modifiers) && $modifiers[0] === '/') {
154+
$modifiers = substr($modifiers, 1);
155+
}
153156
$meta = [
154157
'cdnUrl' => $item,
155158
'cdnUrlModifiers' => $modifiers,
@@ -173,8 +176,12 @@ private function parseItem(mixed $item): array
173176
$uuidOffset = $matches[1][1] ?? null;
174177
$uuidLen = strlen($foundUuid);
175178
$modifiers = ($uuidOffset !== null) ? substr($item['cdnUrl'], $uuidOffset + $uuidLen) : '';
179+
180+
if (! empty($modifiers) && $modifiers[0] === '/') {
181+
$modifiers = substr($modifiers, 1);
182+
}
176183
if (! empty($modifiers)) {
177-
$meta['cdnUrlModifiers'] = $modifiers;
184+
$meta['cdnUrlModifiers'] = $meta['cdnUrlModifiers'] ?? $modifiers;
178185
$meta['cdnUrl'] = $item['cdnUrl']; // Ensure url matches
179186
}
180187
}

packages/uploadcare-field/src/Uploadcare.php

Lines changed: 127 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -737,29 +737,68 @@ private static function normalizeCurrentState(mixed $state): array
737737

738738
public function hydrate(mixed $value, ?Model $model = null): mixed
739739
{
740-
// Try to load from model relationship if available
741-
$hydratedFromModel = self::hydrateFromModel($model);
742-
743-
if ($hydratedFromModel !== null) {
744-
return $hydratedFromModel;
745-
}
746-
740+
// If value is null or empty, return early (don't load all media from relationship)
747741
if (empty($value)) {
748742
return $value;
749743
}
750744

751-
$mediaModel = self::getMediaModel();
752-
745+
// Normalize value first
753746
if (is_string($value) && json_validate($value)) {
754747
$decoded = json_decode($value, true);
755-
756748
if (is_array($decoded)) {
757749
$value = $decoded;
758750
}
759751
}
760752

753+
// Try to load from model relationship if available
754+
// Pass the value so hydrateFromModel can filter by ULIDs if needed
755+
$hydratedFromModel = self::hydrateFromModel($model, $value);
756+
757+
if ($hydratedFromModel !== null && $hydratedFromModel->isNotEmpty()) {
758+
// Always return an array of Media instances for consistency
759+
return $hydratedFromModel->all();
760+
}
761+
762+
$mediaModel = self::getMediaModel();
763+
761764
if (is_string($value) && ! json_validate($value)) {
762-
return $mediaModel::where('ulid', $value)->first() ?? $value;
765+
// Check if it's a ULID
766+
if (preg_match('/^[0-9A-HJKMNP-TV-Z]{26}$/i', $value)) {
767+
$media = $mediaModel::where('ulid', $value)->first();
768+
return $media ? [$media] : $value;
769+
}
770+
771+
// Check if it's a CDN URL - try to extract UUID and load Media
772+
if (filter_var($value, FILTER_VALIDATE_URL) && (str_contains($value, 'ucarecdn.com') || str_contains($value, 'ucarecd.net'))) {
773+
$uuid = self::extractUuidFromString($value);
774+
if ($uuid) {
775+
$media = $mediaModel::where('filename', $uuid)->first();
776+
if ($media) {
777+
// Extract modifiers from URL if present
778+
$cdnUrlModifiers = null;
779+
$uuidPos = strpos($value, $uuid);
780+
if ($uuidPos !== false) {
781+
$modifiers = substr($value, $uuidPos + strlen($uuid));
782+
if (! empty($modifiers) && $modifiers[0] === '/') {
783+
$cdnUrlModifiers = substr($modifiers, 1);
784+
} elseif (! empty($modifiers)) {
785+
$cdnUrlModifiers = $modifiers;
786+
}
787+
}
788+
789+
// Attach the CDN URL as edit metadata
790+
$media->setAttribute('edit', [
791+
'uuid' => $uuid,
792+
'cdnUrl' => $value,
793+
'cdnUrlModifiers' => $cdnUrlModifiers,
794+
]);
795+
796+
return [$media];
797+
}
798+
}
799+
}
800+
801+
return $value;
763802
}
764803

765804
$hydratedUlids = self::hydrateBackstageUlids($value);
@@ -770,28 +809,94 @@ public function hydrate(mixed $value, ?Model $model = null): mixed
770809
return $value;
771810
}
772811

773-
private static function hydrateFromModel(?Model $model): ?array
812+
private static function hydrateFromModel(?Model $model, mixed $value = null): ?\Illuminate\Support\Collection
774813
{
775814
if (! $model || ! method_exists($model, 'media')) {
776815
return null;
777816
}
778817

779-
if (! $model->relationLoaded('media')) {
780-
$model->load('media');
818+
// Extract ULIDs from value if it's an array
819+
$ulids = null;
820+
if (is_array($value) && ! empty($value)) {
821+
$ulids = array_filter(Arr::flatten($value), function ($item) {
822+
return is_string($item) && preg_match('/^[0-9A-HJKMNP-TV-Z]{26}$/i', $item);
823+
});
824+
$ulids = array_values($ulids); // Re-index
781825
}
782826

783-
if ($model->media->isEmpty()) {
827+
$mediaQuery = $model->media()->withPivot(['meta', 'position'])->distinct();
828+
829+
if (! empty($ulids)) {
830+
$mediaQuery->whereIn('media_ulid', $ulids)
831+
->orderByRaw('FIELD(media_ulid, ' . implode(',', array_fill(0, count($ulids), '?')) . ')', $ulids);
832+
}
833+
834+
$media = $mediaQuery->get()->unique('ulid');
835+
836+
if ($media->isEmpty()) {
784837
return null;
785838
}
786839

787-
return $model->media->map(function ($media) {
788-
$meta = $media->pivot->meta ? json_decode($media->pivot->meta, true) : [];
840+
try {
841+
return $media->map(function ($mediaItem) {
842+
$meta = null;
843+
if (isset($mediaItem->pivot) && isset($mediaItem->pivot->meta)) {
844+
$meta = is_string($mediaItem->pivot->meta)
845+
? json_decode($mediaItem->pivot->meta, true)
846+
: $mediaItem->pivot->meta;
847+
}
848+
$meta = is_array($meta) ? $meta : [];
849+
850+
// Get base metadata
851+
$metadata = is_string($mediaItem->metadata)
852+
? json_decode($mediaItem->metadata, true)
853+
: $mediaItem->metadata;
854+
$metadata = is_array($metadata) ? $metadata : [];
789855

790-
return array_merge($media->toArray(), $meta, [
791-
'uuid' => $media->filename,
792-
'cdnUrl' => $meta['cdnUrl'] ?? $media->metadata['cdnUrl'] ?? null,
793-
]);
794-
})->toArray();
856+
// Merge pivot meta (cropped data) with base metadata, pivot takes precedence
857+
$mergedMeta = array_merge($metadata, $meta);
858+
859+
// Ensure cdnUrlModifiers is included from pivot meta
860+
$cdnUrl = $mergedMeta['cdnUrl'] ?? $metadata['cdnUrl'] ?? null;
861+
$cdnUrlModifiers = $mergedMeta['cdnUrlModifiers'] ?? null;
862+
863+
// If we have a cdnUrl with modifiers but no explicit cdnUrlModifiers, extract from URL
864+
if (! $cdnUrlModifiers && $cdnUrl && is_string($cdnUrl)) {
865+
$uuid = self::extractUuidFromString($cdnUrl);
866+
if ($uuid) {
867+
$uuidPos = strpos($cdnUrl, $uuid);
868+
if ($uuidPos !== false) {
869+
$modifiers = substr($cdnUrl, $uuidPos + strlen($uuid));
870+
if (! empty($modifiers) && $modifiers[0] === '/') {
871+
$cdnUrlModifiers = substr($modifiers, 1);
872+
} elseif (! empty($modifiers)) {
873+
$cdnUrlModifiers = $modifiers;
874+
}
875+
}
876+
}
877+
}
878+
879+
// Add cdnUrlModifiers to merged meta if extracted
880+
if ($cdnUrlModifiers) {
881+
$mergedMeta['cdnUrlModifiers'] = $cdnUrlModifiers;
882+
}
883+
if ($cdnUrl) {
884+
$mergedMeta['cdnUrl'] = $cdnUrl;
885+
}
886+
if (! isset($mergedMeta['uuid'])) {
887+
$mergedMeta['uuid'] = $mediaItem->filename;
888+
}
889+
890+
// Attach merged metadata to Media object's edit property (used by UploadcareService)
891+
$mediaItem->setAttribute('edit', $mergedMeta);
892+
893+
return $mediaItem;
894+
})
895+
->filter() // Remove any null items
896+
->values();
897+
} catch (\Throwable $e) {
898+
return null;
899+
}
795900
}
796901

797902
private static function resolveMediaFromMixedValue(mixed $item): ?Model

0 commit comments

Comments
 (0)