I have a Kirby 4 site that I’m migrating to v5, and which could make use of the new Entries field for one of its blueprints.
The story goes like this:
- There is an
artistblueprint with awebsiteURL field. - The client requested a way to fill in more website URLs for a single artist, and the quick-and-dirty solution was to create a couple new fields:
website_twoandwebsite_three. - This was during the Kirby 5 betas, so I knew that the Kirby 5
entriesfield type would be a better way to handle this eventually.
Now I’m trying to migrate the existing content from the old fields to the new one. I start by adding a new Entries field to the blueprint:
# New Entries field allowing multiple URLs
websites:
type: entries
label: Websites
field:
type: url
# Old workaround for multiple URLs (up to 3)
website:
type: url
label: Website 1
website_two:
type: url
label: Website 2
website_three:
type: url
label: Website 3
Ideally, I’d like to copy the data from the old fields into the new websites field, and then delete the old data, all in one pass, without losing my client’s content of course.
Basically, I need a script. I prototyped something locally using a route that only works for admin users:
function () {
if (!kirby()->user()->isAdmin()) {
return 'nope';
}
$pages = collection('artists-with-drafts');
$count = 0;
foreach ($pages as $page) {
/** @var ArtistPage $page */
$sites = [];
$new = $page->content()->get('websites')?->yaml() ?? [];
foreach($new as $value) {
$value = is_string($value) ? trim($value) : '';
if ($value !== '') $sites[] = $value;
}
$old = array_map(function($key) use ($page) {
$value = $page->content()->get($key)->value;
return is_string($value) ? trim($value) : '';
}, ['website', 'website_two', 'website_three']);
$unsaved = array_filter($old, function($value) use ($sites) {
return $value !== '' && !in_array($value, $sites);
});
if (count($unsaved) == 0) continue;
$page->update([
'websites' => Yaml::encode(array_merge($sites, $unsaved)),
'website' => null,
'website_two' => null,
'website_three' => null,
]);
$count++;
}
return "Updated $count pages.";
}
That seems to work well, but I have a few questions to hopefully improve on it:
-
Is there a better place or way to run admin scripts on the content, with access to Kirby APIs? I’m happy running something on the command line on the server over SSH, rather than having to go through a public route.
-
Are there shorter, nicer or more battle-tested ways to write content migrations for a page’s fields?
-
Calling
$page->update(['field_to_remove' => null])will blank the field in the page’s.txtfile, but not remove it. You need to also remove the field’s definition in the blueprint. But I’m afraid that removing it in the blueprint will lead to data loss if some page is saved by an editor before I can run my migration script. Any ways to handle this? Best workaround I have so far would be to do the migration in two steps: one to populate the new field, and then later on update the blueprint to remove the old fields.