Hi @Moucky and super thanks for this thread and sharing your WIP code! It helped me immensely, and in fact I managed to do something similar for my own use case.
What I needed to do was to replace a bunch of HTML inside a builder field with different type of blocks, some of which are custom blocks of type column (GitHub - youngcut/kirby-column-blocks: Use columns in block fields based on the layout field.). I had to:
- loop over all blocks and update type text blocks, as well as, loop over sub-blocks inside columns block and update possible text block inside there
- reconstruct the whole builder block data structure, including the columns type block
- update the builder field of the page
I got really stuck trying to understand how to put back the custom columns block, which uses the layout feature. At the end, it was thanks to your code that I discovered the Kirby\Cms\LayoutColum
method. After that I realised I had to wrap the layout object inside a new block and all worked together. Following my code, in the hope it would be useful to you and anybody else here in the forum.
$updatedBlocks = [];
foreach($blocks as $block) {
$blockType = $block->type();
if ($blockType === 'columns') {
// we have one layout per block, no need to loop over
$layout = $block->layout()->toLayouts()->first();
$columnsNew = [];
foreach($layout->columns() as $column) {
// we need to:
// - parse the layout blocks
// - reconstruct the layout with updated blocks
// - convert it back to a layout object
$subblocks = $column->blocks();
$updatedSubblocks = parseBlocks($subblocks, $client, 'layout');
$subblocksNew = new Kirby\Cms\Blocks($updatedSubblocks);
$columnNew = new Kirby\Cms\LayoutColumn(
[
'blocks' => $subblocksNew->toArray(),
'width' => $column->width(),
]
);
array_push($columnsNew, $columnNew);
};
$layoutColumnsNew = new Kirby\Cms\LayoutColumns($columnsNew);
$layoutNew = Kirby\Cms\Layout::factory([
'columns' => $layoutColumnsNew->toArray(),
]);
$layoutsNew = new Kirby\Cms\Layouts([$layoutNew]);
// -- update block
$blockLayoutUpdated = [
'content' => [
'layout' => $layoutsNew->toArray(),
],
'type' => 'columns',
];
$blockLayoutNew = new Kirby\Cms\Block($blockLayoutUpdated);
array_push($updatedBlocks, $blockLayoutNew);
} else if ($blockType === 'text') {
// custom code to update block-text
// [...]
// -- update block
$blockUpdated = [
'content' => [
'text' => $text_new,
'footnotes' => $footnotes_new,
],
'type' => $block->type(),
];
$blockUpdated = new Kirby\Cms\Block($blockUpdated);
array_push($updatedBlocks, $blockUpdated);
} else {
array_push($updatedBlocks, $block);
}
}; // -- end blocks foreach
-
parseBlocks()
is the function you see above, which is used in a recursive manner to map over the blocks inside the columns block (each columns block has x-number of columns, each with a block) - the important steps to reconstruct a block with a layout / columns inside, are:
new Kirby\Cms\LayoutColumn()
new Kirby\Cms\LayoutColumns()
Kirby\Cms\Layout::factory()
new Kirby\Cms\Layouts()
- check the code snippet above for more details, arguments, etc
One you get the data in the $updatedBlocks
array, here’s how I updated the builder field:
$blocksNew = new Kirby\Cms\Blocks($updatedBlocks);
// -- write to file
kirby()->impersonate('kirby');
$newPage->update([
'builder' => json_encode($blocksNew->toArray()),
]);
Tested with Kirby 7.4
and 8.1
.