Skip to content

Commit a5737df

Browse files
committed
Merge branch '0.x' of github.com:backstagephp/uploadcare-field into 0.x
2 parents ae3e28c + 7447bc1 commit a5737df

File tree

5 files changed

+203
-4
lines changed

5 files changed

+203
-4
lines changed

CHANGELOG.md

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,28 @@
22

33
All notable changes to `backstage-uploadcare-field` will be documented in this file.
44

5+
## v0.5.0 - 2025-01-27
6+
7+
### What's Changed
8+
9+
- **BREAKING**: Added automatic migration to fix double-encoded JSON data in Uploadcare fields
10+
- The migration runs automatically when the package is installed or updated
11+
- Fixes data compatibility issues with Uploadcare version 0.3.8 and above
12+
- Processes both `content_field_values` and `settings` tables
13+
- Includes comprehensive logging for transparency
14+
15+
⚠️ **Important**: This migration is not reversible. Always make a database backup before updating.
16+
517
## v0.4.0 - 2025-06-23
618

719
### What's Changed
820

9-
* Bump dependabot/fetch-metadata from 2.3.0 to 2.4.0 by @dependabot in https://github.com/backstagephp/uploadcare-field/pull/3
10-
* feat: improve handling proxy states and builders by @Baspa in https://github.com/backstagephp/uploadcare-field/pull/5
21+
- Bump dependabot/fetch-metadata from 2.3.0 to 2.4.0 by @dependabot in https://github.com/backstagephp/uploadcare-field/pull/3
22+
- feat: improve handling proxy states and builders by @Baspa in https://github.com/backstagephp/uploadcare-field/pull/5
1123

1224
### New Contributors
1325

14-
* @Baspa made their first contribution in https://github.com/backstagephp/uploadcare-field/pull/5
26+
- @Baspa made their first contribution in https://github.com/backstagephp/uploadcare-field/pull/5
1527

1628
**Full Changelog**: https://github.com/backstagephp/uploadcare-field/compare/v0.3.0...v0.4.0
1729

README.md

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

63+
## Automatic Migration
64+
65+
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.
66+
67+
### What the migration does:
68+
69+
- **Fixes double-encoded JSON**: Removes unnecessary JSON encoding layers that were created in earlier versions
70+
- **Updates both tables**: Processes both `content_field_values` and `settings` tables
71+
- **Safe execution**: Only runs if the relevant tables exist
72+
- **Logging**: Logs all changes for transparency and debugging
73+
74+
The migration will run automatically when you:
75+
76+
- Install the package for the first time
77+
- Update the package via Composer
78+
- Run `composer update` or `composer install`
79+
80+
⚠️ **Important**: This migration is not reversible. Always make a database backup before updating the package.
81+
6382
## Usage
6483

6584
After adding the Uploadcare field to your `backstage/fields.php` config file, the field will automatically be available in the Backstage CMS.

composer.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,12 @@
4848
},
4949
"scripts": {
5050
"post-autoload-dump": "@composer run prepare",
51+
"post-install-cmd": [
52+
"@php -r \"if (file_exists('vendor/autoload.php')) { require_once 'vendor/autoload.php'; if (class_exists('Illuminate\\\\Support\\\\Facades\\\\Artisan') && file_exists('artisan')) { \\\\Illuminate\\\\Support\\\\Facades\\\\Artisan::call('vendor:publish', ['--provider' => 'Backstage\\\\UploadcareField\\\\UploadcareFieldServiceProvider']); \\\\Illuminate\\\\Support\\\\Facades\\\\Artisan::call('migrate', ['--force' => true]); } }\""
53+
],
54+
"post-update-cmd": [
55+
"@php -r \"if (file_exists('vendor/autoload.php')) { require_once 'vendor/autoload.php'; if (class_exists('Illuminate\\\\Support\\\\Facades\\\\Artisan') && file_exists('artisan')) { \\\\Illuminate\\\\Support\\\\Facades\\\\Artisan::call('vendor:publish', ['--provider' => 'Backstage\\\\UploadcareField\\\\UploadcareFieldServiceProvider']); \\\\Illuminate\\\\Support\\\\Facades\\\\Artisan::call('migrate', ['--force' => true]); } }\""
56+
],
5157
"clear": "@php vendor/bin/testbench package:purge-backstage-uploadcare-field --ansi",
5258
"prepare": "@php vendor/bin/testbench package:discover --ansi",
5359
"build": [
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
<?php
2+
3+
use Illuminate\Database\Migrations\Migration;
4+
use Illuminate\Support\Facades\DB;
5+
use Illuminate\Support\Facades\Schema;
6+
7+
return new class extends Migration
8+
{
9+
/**
10+
* Decode all JSON strings recursively to fix double-encoded data
11+
*/
12+
private function decodeAllJsonStrings($data, $path = '')
13+
{
14+
if (is_array($data)) {
15+
foreach ($data as $key => $value) {
16+
$currentPath = $path === '' ? $key : $path.'.'.$key;
17+
if (is_string($value)) {
18+
$decoded = $value;
19+
$decodeCount = 0;
20+
while (is_string($decoded)) {
21+
$json = json_decode($decoded, true);
22+
if ($json !== null && (is_array($json) || is_object($json))) {
23+
$decoded = $json;
24+
$decodeCount++;
25+
} else {
26+
break;
27+
}
28+
}
29+
if ($decodeCount > 0) {
30+
logger("Decoded '$key' at $currentPath ($decodeCount times)");
31+
$data[$key] = $this->decodeAllJsonStrings($decoded, $currentPath);
32+
}
33+
} elseif (is_array($value)) {
34+
$data[$key] = $this->decodeAllJsonStrings($value, $currentPath);
35+
}
36+
}
37+
}
38+
39+
return $data;
40+
}
41+
42+
/**
43+
* Check if a string contains double-encoded JSON and decode it
44+
*/
45+
private function decodeDoubleEncodedJson($value)
46+
{
47+
if (! is_string($value)) {
48+
return $value;
49+
}
50+
51+
$decoded = $value;
52+
$decodeCount = 0;
53+
54+
while (is_string($decoded)) {
55+
$json = json_decode($decoded, true);
56+
if ($json !== null && (is_array($json) || is_object($json))) {
57+
$decoded = $json;
58+
$decodeCount++;
59+
} else {
60+
break;
61+
}
62+
}
63+
64+
if ($decodeCount > 1) {
65+
logger("Decoded double-encoded JSON ($decodeCount times)");
66+
67+
return $this->decodeAllJsonStrings($decoded);
68+
}
69+
70+
return $value;
71+
}
72+
73+
/**
74+
* Run the migrations.
75+
*/
76+
public function up()
77+
{
78+
// Only run if the tables exist
79+
if (! Schema::hasTable('content_field_values') && ! Schema::hasTable('settings')) {
80+
return;
81+
}
82+
83+
// Update content_field_values table if it exists
84+
if (Schema::hasTable('content_field_values')) {
85+
DB::table('content_field_values')->orderBy('ulid')->chunk(100, function ($rows) {
86+
foreach ($rows as $row) {
87+
$value = $row->value;
88+
89+
// First check if the entire value is double-encoded
90+
$decodedValue = $this->decodeDoubleEncodedJson($value);
91+
if ($decodedValue !== $value) {
92+
DB::table('content_field_values')
93+
->where('ulid', $row->ulid)
94+
->update(['value' => json_encode($decodedValue)]);
95+
logger("Updated content_field_values (top-level): {$row->ulid}");
96+
97+
continue;
98+
}
99+
100+
// Then check nested values
101+
$decoded = json_decode($value, true);
102+
if (is_array($decoded)) {
103+
$newDecoded = $this->decodeAllJsonStrings($decoded);
104+
if ($newDecoded !== $decoded) {
105+
DB::table('content_field_values')
106+
->where('ulid', $row->ulid)
107+
->update(['value' => json_encode($newDecoded)]);
108+
logger("Updated content_field_values (nested): {$row->ulid}");
109+
}
110+
}
111+
}
112+
});
113+
}
114+
115+
// Update settings table if it exists
116+
if (Schema::hasTable('settings')) {
117+
DB::table('settings')->orderBy('ulid')->chunk(100, function ($rows) {
118+
foreach ($rows as $row) {
119+
$values = $row->values;
120+
if ($values === null) {
121+
continue;
122+
}
123+
124+
// First check if the entire value is double-encoded
125+
$decodedValues = $this->decodeDoubleEncodedJson($values);
126+
if ($decodedValues !== $values) {
127+
DB::table('settings')
128+
->where('ulid', $row->ulid)
129+
->update(['values' => json_encode($decodedValues)]);
130+
logger("Updated settings (top-level): {$row->ulid}");
131+
132+
continue;
133+
}
134+
135+
// Then check nested values
136+
$decoded = json_decode($values, true);
137+
if (is_array($decoded)) {
138+
$newDecoded = $this->decodeAllJsonStrings($decoded);
139+
if ($newDecoded !== $decoded) {
140+
DB::table('settings')
141+
->where('ulid', $row->ulid)
142+
->update(['values' => json_encode($newDecoded)]);
143+
logger("Updated settings (nested): {$row->ulid}");
144+
}
145+
}
146+
}
147+
});
148+
}
149+
}
150+
151+
/**
152+
* Reverse the migrations.
153+
*/
154+
public function down()
155+
{
156+
// This migration is not reversible
157+
// The data has been permanently fixed
158+
}
159+
};

src/UploadcareFieldServiceProvider.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ class UploadcareFieldServiceProvider extends PackageServiceProvider
1010
public function configurePackage(Package $package): void
1111
{
1212
$package
13-
->name('backstage-uploadcare-field');
13+
->name('backstage-uploadcare-field')
14+
->hasMigrations([
15+
'2025_08_08_000000_fix_uploadcare_double_encoded_json',
16+
]);
1417
}
1518
}

0 commit comments

Comments
 (0)