Skip to content

Commit 2e5f09e

Browse files
author
Backstage
committed
fix: add missing site ulid to resources (#43)
1 parent ace4cc3 commit 2e5f09e

19 files changed

Lines changed: 4504 additions & 99 deletions

README.md

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,57 @@ return [
6060
];
6161
```
6262

63+
### CSS and Styling
64+
65+
This package includes a MediaGridPicker component that requires Tailwind CSS classes to be properly compiled. The package automatically registers its CSS assets with Filament, but you may need to ensure your main application's Tailwind build includes the package's source files.
66+
67+
If you're using Tailwind CSS v4 in your main application, add the package's source directories to your `resources/css/sources.css` file:
68+
69+
```css
70+
@source "/path/to/backstage-uploadcare-field/resources/";
71+
@source "/path/to/backstage-uploadcare-field/src/";
72+
```
73+
74+
For Tailwind CSS v3, add the package paths to your `tailwind.config.js`:
75+
76+
```javascript
77+
module.exports = {
78+
content: [
79+
// ... your existing paths
80+
'./vendor/backstage/uploadcare-field/resources/**/*.blade.php',
81+
'./vendor/backstage/uploadcare-field/src/**/*.php',
82+
],
83+
// ... rest of your config
84+
}
85+
```
86+
87+
The package's CSS is automatically loaded in Filament admin panels and includes all necessary styles for the MediaGridPicker component.
88+
89+
### MediaGridPicker Integration
90+
91+
The package includes a MediaGridPicker component that allows users to select existing media files from the media library and add them directly to Uploadcare fields. This feature is automatically available when using uploadcare fields in Filament forms.
92+
93+
**How it works:**
94+
1. When editing content with uploadcare fields, a "Select from Media" button appears next to the field
95+
2. Clicking this button opens a modal with a grid of existing media files
96+
3. Selecting a media file automatically adds it to the Uploadcare field
97+
4. The integration uses Alpine.js events and JavaScript to communicate between the MediaGridPicker and Uploadcare components
98+
99+
**Technical details:**
100+
- The MediaGridPicker dispatches an `add-uploadcare-file` event when a file is selected
101+
- The package's JavaScript listens for this event and attempts to add the file to the Uploadcare field
102+
- Multiple fallback methods are used to ensure compatibility with different Uploadcare configurations:
103+
1. **Direct Uploadcare API**: Tries to use the Uploadcare widget's API to add files
104+
2. **Livewire State Management**: Updates the Livewire component state directly
105+
3. **Hidden Input Fields**: Sets values on hidden input fields and triggers events
106+
4. **File Object Creation**: Attempts to create File objects from CDN URLs
107+
5. **Generic Input Fields**: Falls back to setting values on any matching input fields
108+
109+
**Debugging:**
110+
- The JavaScript includes comprehensive console logging to help debug integration issues
111+
- Check the browser console for detailed information about which methods are being attempted
112+
- The system will log which Uploadcare elements are found and which methods succeed or fail
113+
63114
## Automatic Migration
64115

65116
This package includes an automatic migration that fixes double-encoded JSON data in Uploadcare fields. This migration runs automatically when the package is installed or updated.

database/migrations/2025_08_08_000000_fix_uploadcare_double_encoded_json.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ private function decodeAllJsonStrings($data, $path = '')
1313
{
1414
if (is_array($data)) {
1515
foreach ($data as $key => $value) {
16-
$currentPath = $path === '' ? $key : $path.'.'.$key;
16+
$currentPath = $path === '' ? $key : $path . '.' . $key;
1717
if (is_string($value)) {
1818
$decoded = $value;
1919
$decodeCount = 0;
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
<?php
2+
3+
use Backstage\Media\Models\Media;
4+
use Illuminate\Database\Migrations\Migration;
5+
use Illuminate\Support\Facades\DB;
6+
use Illuminate\Support\Str;
7+
8+
return new class extends Migration
9+
{
10+
public function up(): void
11+
{
12+
$targetFieldIds = DB::table('fields')
13+
->whereIn('field_type', ['uploadcare', 'builder', 'repeater'])
14+
->pluck('ulid');
15+
16+
if ($targetFieldIds->isEmpty()) {
17+
return;
18+
}
19+
20+
$firstSiteUlid = DB::table('sites')->orderBy('ulid')->value('ulid');
21+
22+
$processValue = function (&$data, $siteUlid, $rowUlid) use (&$processValue) {
23+
$anyModified = false;
24+
25+
if (! is_array($data)) {
26+
return false;
27+
}
28+
29+
$isRawUploadcareList = false;
30+
if (! empty($data) && isset($data[0]) && is_array($data[0]) && isset($data[0]['uuid'])) {
31+
$isRawUploadcareList = true;
32+
}
33+
34+
$isAlreadyUlidList = false;
35+
if (! empty($data) && isset($data[0]) && is_string($data[0]) && strlen($data[0]) === 26) {
36+
$isAlreadyUlidList = true;
37+
foreach ($data as $item) {
38+
if (! is_string($item) || strlen($item) !== 26) {
39+
$isAlreadyUlidList = false;
40+
41+
break;
42+
}
43+
}
44+
}
45+
46+
if ($isRawUploadcareList) {
47+
$newUlids = [];
48+
foreach ($data as $fileData) {
49+
$uuid = $fileData['uuid'];
50+
51+
$media = Media::where('filename', $uuid)->first();
52+
53+
if (! $media) {
54+
$media = new Media;
55+
$media->ulid = (string) Str::ulid();
56+
$media->site_ulid = $siteUlid;
57+
$media->disk = 'uploadcare';
58+
$media->filename = $uuid;
59+
$info = $fileData['fileInfo'] ?? $fileData;
60+
$detailedInfo = $info['imageInfo'] ?? $info['videoInfo'] ?? $info['contentInfo'] ?? [];
61+
62+
$media->extension = $detailedInfo['format']
63+
?? pathinfo($info['originalFilename'] ?? $info['name'] ?? '', PATHINFO_EXTENSION);
64+
65+
$media->original_filename = $info['originalFilename'] ?? $info['original_filename'] ?? $info['name'] ?? 'unknown';
66+
$media->mime_type = $info['mimeType'] ?? $info['mime_type'] ?? 'application/octet-stream';
67+
$media->size = $info['size'] ?? 0;
68+
$media->public = true;
69+
$media->metadata = $info;
70+
71+
$media->checksum = md5($uuid);
72+
$media->save();
73+
}
74+
$newUlids[] = $media->ulid;
75+
76+
DB::table('media_relationships')->insertOrIgnore([
77+
'media_ulid' => $media->ulid,
78+
'model_type' => 'content_field_value',
79+
'model_id' => $rowUlid,
80+
'meta' => json_encode($fileData),
81+
'created_at' => now(),
82+
'updated_at' => now(),
83+
]);
84+
}
85+
$data = $newUlids;
86+
87+
return true;
88+
}
89+
90+
if ($isAlreadyUlidList) {
91+
foreach ($data as $mediaUlid) {
92+
$media = Media::where('ulid', $mediaUlid)->first();
93+
if ($media) {
94+
DB::table('media_relationships')->insertOrIgnore([
95+
'media_ulid' => $media->ulid,
96+
'model_type' => 'content_field_value',
97+
'model_id' => $rowUlid,
98+
'meta' => json_encode($media->metadata),
99+
'created_at' => now(),
100+
'updated_at' => now(),
101+
]);
102+
}
103+
}
104+
105+
return false;
106+
}
107+
108+
foreach ($data as $key => &$value) {
109+
if (is_array($value)) {
110+
if ($processValue($value, $siteUlid, $rowUlid)) {
111+
$anyModified = true;
112+
}
113+
} elseif (is_string($value)) {
114+
if (str_starts_with($value, '[') || str_starts_with($value, '{')) {
115+
$decoded = json_decode($value, true);
116+
if (json_last_error() === JSON_ERROR_NONE && is_array($decoded)) {
117+
if ($processValue($decoded, $siteUlid, $rowUlid)) {
118+
$value = $decoded;
119+
$anyModified = true;
120+
}
121+
}
122+
}
123+
}
124+
}
125+
126+
return $anyModified;
127+
};
128+
129+
DB::table('content_field_values')
130+
->whereIn('field_ulid', $targetFieldIds)
131+
->chunkById(50, function ($rows) use ($processValue, $firstSiteUlid) {
132+
foreach ($rows as $row) {
133+
$value = $row->value;
134+
$decoded = json_decode($value, true);
135+
136+
if (is_string($decoded)) {
137+
$decoded = json_decode($decoded, true);
138+
}
139+
140+
if (! is_array($decoded)) {
141+
continue;
142+
}
143+
144+
// Use row's site_ulid if available, otherwise fallback to first site
145+
$siteUlid = $row->site_ulid ?? $firstSiteUlid;
146+
147+
if ($processValue($decoded, $siteUlid, $row->ulid)) {
148+
DB::table('content_field_values')
149+
->where('ulid', $row->ulid)
150+
->update(['value' => json_encode($decoded)]);
151+
}
152+
}
153+
}, 'ulid');
154+
}
155+
156+
public function down(): void
157+
{
158+
//
159+
}
160+
};

0 commit comments

Comments
 (0)