@@ -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