the transition from Builder field to Kirby Blocks was smooth until Kirby v3.6. In the latest version (3.7) the automatic migration is gone, which is understandable.
Unfortunately I have a Kirby website with a lot of pages that all use the Builder field and that need migration to Kirby Blocks. Changing the blueprints and templates/snippets is not a big deal, but having the data saved properly so that also v3.7 can read it, is.
If I understand correctly, until v3.6 the data that was saved from the Builder field was converted to the Kirby-Block-(JSON-)Syntax on saving of pages. At the moment for me this means, that I would have to go through hundreds of pages manually, change something, save it, in order to have all my data stored in the new Kirby-Block-Syntax so that v3.7 can actually work with it.
Is there a way to convert the data (the field value that are stored in the txt-files) programmatically / in one automated process? Or is there a thing or an approach that I missed?
I hope I made the issue understandable. Thanks in advance. Help is very appreciated.
Yes, this should be possible programmatically, using the old BlockConverter class from until 3.6 as a basis. Quoting what @distantnative outlined on Discord:
Thinking out loud how to approach this:
take BlockConverter class and put it in a plugin
in the plugin loop through all blueprints and find all fields with type for old blocks
for each blueprint/template loop through whole content index and run fields that have been identified as potential old blocks through converter
@texnixe Thank you very much for the reply (and thanks @distantnative for the suggestions).
Yes, I also was thinking that the BlockConverter class should be capable of this, but having a look at the builderBlock method made me hesitant since I couldn’t see any YAML to JSON conversion here - but I probably don’t understand the architecture here completely.
So I am going to try this, but I am still a bit lost in terms of how to use the BlockConverter on a field / page. So I guess I should be using BlockConverter::builderBlock() but I have no real idea from where to get the $params before or how to create/save the new converted field based on the $params I get returned.
I’ve never used the builder, only the editor, so not 100% positive my script also works for the builder migration, but you can give it a try! Add the following to your config.php, then (after you changed the field type to blocks in your blueprints) visit https://yoursite.com/blocks-migration
use Kirby\Cms\BlockConverter;
use Kirby\Cms\Response;
use Kirby\Toolkit\A;
return [
'routes' => [
'pattern' => '/blocks-migration',
'action' => function() {
if (!kirby()->user() || !kirby()->user()->isAdmin()) {
return new Response('You need to log in as admin.', 'text/plain', 401);
}
$csrf = csrf();
return "
<!doctypehtml><meta charset=UTF-8><h1>Editor → Blocks</h1>
<form method=POST>
<input type=hidden name=csrf value=$csrf>
<button>Start migration</button>
</form>
";
}
],
[
'pattern' => '/blocks-migration',
'method' => 'POST',
'action' => function() {
$kirby = kirby();
if (!$kirby->user() || !$kirby->user()->isAdmin()) {
return new Response('You need to log in as admin.', 'text/plain', 401);
}
if (!csrf(get('csrf'))) {
return new Response('Invalid token.', 'text/plain', 403);
}
$pages = site()->index(true);
$updated_contents = [];
foreach($pages as $page) {
$block_fields = A::filter($page->blueprint()->fields(), fn($f) => $f['type'] === 'blocks');
foreach($block_fields as $field) {
$name = $field['name'];
if (!$kirby->multilang()) {
if (!$page->content()->has($name)) continue;
$value = $page->content()->get($name)->toData('json');
$converted = BlockConverter::builderBlock($value);
if (!A::isAssociative($converted)) {
$converted = BlockConverter::editorBlocks($converted);
}
if ($value === $converted) continue;
$page->update([ $name => $converted ]);
$updated_contents[] = "{$page->id()} → $name";
} else {
foreach($kirby->languages() as $lang) {
$langCode = $lang->code();
if (!$page->content($langCode)->has($name)) continue;
$value = $page->content($langCode)->get($name)->toData('json');
$converted = BlockConverter::builderBlock($value);
if (!A::isAssociative($converted)) {
$converted = BlockConverter::editorBlocks($converted);
}
if ($value === $converted) continue;
$page->update([ $name => $converted ], $langCode);
$updated_contents[] = "{$page->id()} → $name ($langCode)";
}
}
}
}
$count = count($updated_contents);
return "
<!doctypehtml><meta charset=UTF-8><h1>Editor → Blocks</h1>
<p>Migrated $count field contents to the native blocks format.
<ol>". r($count, '<li>') . A::join($updated_contents, '<li>') . "</ol>
<a href={$kirby->site()->panelUrl()}>Back to the panel</a>
";
}
]
];
If you have a loooooot of pages and fields, you might need to update the script to use batched processing as described here in the cookbook: Batch updating content | Kirby CMS
Please do! I intend to polish it a little and then publish it as a Kirby plugin, so some feedback about whether it works for builder users would be super valuable
I’m trying to migrate my Kirby 3.6 site to Kirby 3.7, and also make the move from builder to blocks.
When I add the code by @jonaskuske to my config.php, I get a “Cannot use object of type Closure as array” error, though – do you have any ideas on what causes this?
You’ve probably saved the Builder values as yaml so you need to replace json with yaml in the toData() methods.
We’ve adopted the above script to be executed as a CLI script and also reimplemented the convert function so it also works with Kirby 3.7. But this version lacks Support for converting Editor fields.
To run this, place this as convert_builder.php and run it with php convert_builder.php in the root of your Kirby installation. Please note: To convert nested Builder blocks you need to run this script with Kirby 3.6.
Hi, thanks @mactux
I tried your script on 3.6.6.1 and 3.7.5 and i have this error :
TypeError: convertBuilderBlock(): Argument #1 ($params) must be of type array, string given, called in path/convert_builder.php on line 56 in file /path/public/convert_builder.php on line 28
Stack trace:
1. TypeError->() /path/convert_builder.php:28
2. convertBuilderBlock() /path/convert_builder.php:56
3. migrateField() /path/convert_builder.php:78
You should maybe print the value of $block. I think some conversion from yaml to array did not work. Maybe some old data inside this block or the blocks are saved as json? Then you need to change