Programmable blueprints
Intro
Blueprints for Panel forms are usually static YAML files. While this is totally fine for most use cases, there are some situations where you might wish there was a more dynamic way of creating blueprints.
In a Kirby plugin, we can register blueprints inside the blueprints array as key/value pairs, where the key is the name of the blueprint. As values we can either provide the path to a file or we can assign an array or a callback. While an array is static, the callback approach allows us to create blueprint plugins dynamically without sacrificing performance.
This approach works for complete page/user/file blueprints as well as for partial blueprints like tabs, fieldgroups etc. and opens up many new possibilities.
Dynamic blueprints can be risky if they handle unvalidated or untrusted user data. This recipe assumes that all handled content is trusted.
We don't have access to the current page object in the PHP blueprint files, and therefore have to hard-code the pages we want to fetch via PHP. So the possibilities we have with this type of setup are not endless but useful in certain situations.
Prerequisites
- A running Kirby Starterkit
- The Pages Display section plugin installed, for the page sections example
Let's start with a new plugin folder called programmable-blueprints
in /site/plugins/
. Inside that folder, let's create the obligatory index.php
file, where we will register all blueprints for this recipe.
Load different blueprints per user
With programmable blueprints it is now much easier to load different blueprints per user or user role, which is for example useful if you want to hide fields/sections/tabs from certain users/roles.
Let's assume we had multiple user roles, e.g. the default admin
role and other roles like editor
etc., and we wanted to provide a different site.yml
blueprints for each role.
To achieve this, we create a subfolder /blueprints
with two files site.admin.yml
and site.editor.yml
.
Then we register a site
blueprint and assign the two files to use conditionally:
We use Data::read()
to read the yaml
files into an array.
Next we fill the blueprints with some content. For this example, let's keep them simple:
The site.admin.yml
get's two tabs, one to access all pages of the site, the second for general meta settings which should not be editable by non-admin users.
In the site.editor.yml
file, we don't need tabs and only include the pages overview without the meta data settings:
To check if everything works, delete the site.yml
file from /site/blueprints
. Then log in as admin
. Create a new editor
user role as outlined in the docs, create a new user with this role and log in with this user. You should now see the simplified site view.
Page blueprint with filtered pages sections
A typical situation that has come up multiple times in support are dynamic numbers of pages sections that filter pages by category. Since the number of categories is usually not set in stone from the outset but new categories might be added any time, we cannot possibly set up all sections in advance. In such a use case, being able to create those sections dynamically is a big plus.
For this example we register a pages blueprint called notes
in our index.php
and include the notes.php
file which we have yet to create.
Now create a new subfolder pages
in the /blueprints
folder, and inside the pages
folder the notes.php
file with the following code:
As mentioned in the prerequistes, this example requires the Pages Display plugin.
To use this blueprint, remove the original /site/blueprints/pages/notes.yml
file.
As a result, we will end up with as many sections as we have tags (and in this example, there will be only one page per section, because all tags are only used once). For the screenshot I've reassigned the tags, so that the result looks less silly:
Note that we have set changeSlug
to false
, because the blueprint would stop working if the page was renamed. If you want to keep the option to change the slug, you can work with a page ID that doesn't change instead.
Field group with dynamic fields
The same kind of logic will work for dynamic fields, for example if you wanted to create a number of pages fields where users can select one (or more) pages from each parent.
Again, we first register the new blueprint:
And then create a fieldgroup called multiselects
in the given path:
In a page/user/file blueprint, we can now use this field group like this:
Multilang field options
When you use language keys in your blueprints to translate field labels etc., these translations are shown based on the selected user language, not based on the currently selected content language.
This example from the docs…
will therefore not switch to the German translation when we switch the content language to German, but when a user selects German as their interface language.
But often, users expect to see the translated option labels when they switch the content language. So, how can we achieve this?
Let's register a new field blueprint in our index.php
:
Then we create a new file category.php
in the corresponding folder with the following code:
In our page blueprint, we can now replace the category field definition:
Assigning filtered blueprints to sections
When you want to assign allowed templates to a pages section, you have to list them out one by one. Not with our programmatic approach.
For this example, we register a new section blueprint:
And create /blueprints/sections/notes.php
with the following code:
First we fetch all registered page blueprints into $blueprints
and then remove all the unwanted ones from the array. The remaining blueprints we assign to the section's templates
prop.
We can now reuse this section in our templates like normal.
If we need the same set of blueprints for multiple sections, we can return the filtered set from its own blueprint and include it in our PHP pages/sections blueprints:
From blueprints.php
we now only return an array of blueprints:
And load this array for example in the notes.php
section:
We don't have to register options/blueprints.php
, because we cannot use it like a normal extension but have to include it explicitly.
Tabs
In this last example, we register a tab blueprint.
Let's assume we already had a basic yml
blueprint that we wanted to extend programmatically.
This is our basic blueprint, which we put into the blueprints/tabs
folder for simplicity.
How can we extend this via PHP? In the same folder, let's create the meta.php
file we already registered above, and add the following code:
First, we read the file we want to extend into an array with Data::read()
.
Then we change the tab label and add a new field in the basicMeta
section by merging the original fields array with the new twitterHandle
field.
We can now add this tab in our page blueprints:
If you want to add the new field at another position in the array, or remove another item from the original field list, you can achieve this using PHP's array functions.
Recap
In this recipe we looked into creating different types of blueprints dynamically, which can be helpful in several use cases. And if you don't like YAML
, it might even become your favorite way of creating blueprints. Despite some limitations, you can still get pretty creative with this approach.
If you have other great ideas how to use this feature, let us know.