Skip to content

Commit 1dc23c7

Browse files
committed
Merge remote-tracking branch 'origin/5.x'
2 parents 9e520f3 + e33e18a commit 1dc23c7

16 files changed

Lines changed: 4128 additions & 114 deletions

.github/workflows/pr-auto-assign.yml

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,19 +17,20 @@ jobs:
1717

1818
- name: Determine label based on branch prefix or author
1919
id: label
20+
env:
21+
PR_AUTHOR: ${{ github.event.pull_request.user.login }}
22+
HEAD_REF: ${{ github.head_ref }}
2023
run: |
21-
# Check if it's a Dependabot PR (using the PR author, not the workflow actor)
22-
if [[ "${{ github.event.pull_request.user.login }}" == "dependabot[bot]" ]]; then
23-
echo "label=dependencies" >> $GITHUB_OUTPUT
24-
# Check branch prefixes
25-
elif [[ "${{ github.head_ref }}" == feature/* ]] || [[ "${{ github.head_ref }}" == feat/* ]]; then
26-
echo "label=enhancement" >> $GITHUB_OUTPUT
27-
elif [[ "${{ github.head_ref }}" == fix/* ]]; then
28-
echo "label=fix" >> $GITHUB_OUTPUT
29-
elif [[ "${{ github.head_ref }}" == docs/* ]]; then
30-
echo "label=documentation" >> $GITHUB_OUTPUT
24+
if [[ "$PR_AUTHOR" == "dependabot[bot]" ]]; then
25+
echo "label=dependencies" >> "$GITHUB_OUTPUT"
26+
elif [[ "$HEAD_REF" == feature/* ]] || [[ "$HEAD_REF" == feat/* ]]; then
27+
echo "label=enhancement" >> "$GITHUB_OUTPUT"
28+
elif [[ "$HEAD_REF" == fix/* ]]; then
29+
echo "label=fix" >> "$GITHUB_OUTPUT"
30+
elif [[ "$HEAD_REF" == docs/* ]]; then
31+
echo "label=documentation" >> "$GITHUB_OUTPUT"
3132
else
32-
echo "label=" >> $GITHUB_OUTPUT
33+
echo "label=" >> "$GITHUB_OUTPUT"
3334
fi
3435
3536
- name: Add label to PR

.github/workflows/run-tests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ jobs:
1616
strategy:
1717
fail-fast: true
1818
matrix:
19-
os: [ubuntu-latest, windows-latest]
19+
os: [ubuntu-latest]
2020
php: [8.3]
2121
laravel: [12.*]
2222
include:

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.

composer.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
"require": {
1919
"php": "^8.3",
2020
"spatie/laravel-package-tools": "^1.16",
21-
"illuminate/contracts": "^10.0||^11.0||^12.0",
21+
"illuminate/contracts": "^10.0||^11.0||^12.0||^13.0",
2222
"backstage/filament-uploadcare-field": "self.version",
2323
"backstage/fields": "self.version"
2424
},
@@ -86,4 +86,4 @@
8686
"url": "git@github.com:backstagephp/fields.git"
8787
}
8888
}
89-
}
89+
}
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)