The power of placeholders
Placeholders are a powerful way to include repeatedly used text snippets like company names, email addresses or phone numbers, but also dynamic stuff like galleries into your text fields. When you want to change this text snippet, you can do that in one place and it will automatically update in all places where the placeholder is used. It's also useful if these text snippets need a special styling or markup. Placeholders thus help you to keep information consistent and maintainable.
Adding placeholders in text fields
By default, such placeholders are wrapped in double curly braces like this:
Text:
{{ company }} was founded in 2005 with the intention of making a lot of money quickly.
Today, {{ company }} rules the world. Contact us at {{ phone }}.
If you want to use different start and end characters instead of the curly braces, you can control this using the $start
and $end
parameters of the Str::template()
method.
Replacing placeholders in templates
In your templates, you can replace such placeholders on the fly using the Str::template()
method:
<?= Str::template($page->text()->kt(), [
'company' => 'Great Company',
'phone' => '+01 123 1234567'
]) ?>
This works great if you only need a few placeholders in a few limited places.
However, if you want to use placeholders all over the place, you don't want to repeat each replacement in every template. So how can we make this more versatile?
Defining the replacements in config.php
If you know in advance what text snippets you need and what they should be replaced with, you can define them in your configuration file:
<?php
return [
'placeholders' => [
'company' => 'Great company',
'phone' => '+01 123 1234567',
],
];
With this in place, we can pass this array to the Str::template()
method:
<?= Str::template($page->text()->kt(), option('placeholders', [])) ?>
Now, if we want to add additional placeholders, we can do that in the config.
But hey, this is still quite limited.
Let the user define the placeholders
Maybe your editors would prefer to have control over the types of placeholders they need and what they should be replaced with. A structure field lends itself well to this purpose, and the site.yml
blueprint is a good place to define site-wide placeholders.
placeholders:
label: Text replacements
type: structure
fields:
key:
label: Placeholder key
type: text
help: >
The key is what you use in your textfields, e.g. {{ company }}
value:
label: Replacement text
type: textarea
help: >
The replacement text is the text that will replace your keyholders.
To get the array of options from this field, we create a toOptions()
field method in a plugin:
<?php
Kirby::plugin('cookbook/placeholders', [
'fieldMethods' => [
'toOptions' => function($field) {
$result = [];
foreach ($field->toStructure() as $option) {
if ($option->key()->isNotEmpty() && $option->value()->isNotEmpty()) {
$result[$option->key()->value()] = $option->value()->html();
}
}
return $result;
}
]
]);
We can now get the replacement array from the field above like this:
<?= Str::template($page->text()->kt(), $site->placeholders()->toOptions()) ?>
Custom field method
Wouldn't it be nice to create our own field method and write it like this?:
<?= $page->text()->kt()->replace([
'company' => 'Great company',
'phone' => '+01 123 1234567',
]) ?>
… or connected to our structure field:
<?= $page->text()->kt()->replace($site->placeholders()->toOptions()) ?>
Let's put this in our plugin:
<?php
Kirby::plugin('cookbook/placeholders', [
'fieldMethods' => [
'replace' => function ($field, array $placeholders = []) {
// replace the field value
$field->value = Str::template($field->value, $placeholders);
return $field;
}
]
]);
Wrapping the replacement code in a hook
We can automate it even more, though. A hook replaces all the placeholders automatically, so that we don't even have to call our method in templates anymore:
<?php
Kirby::plugin('cookbook/placeholders', [
'hooks' => [
'kirbytext:before' => function ($text) {
return Str::template($text, site()->placeholders()->toOptions());
}
]
]);
The final plugin
For the final plugin, we want to get rid of some of the hardcoded stuff and leave it to the developer to decide where the replacements are defined, in the config or by the editor in a structure field, or even allow overriding/extending the replacements defined in config.php
.
Config options for plugins should always follow the naming convention: yourName.pluginName.optionName
<?php
Kirby::plugin('cookbook/placeholders', [
'fieldMethods' => [
'replace' => function ($field, array $placeholders = []) {
// replace the field value
$field->value = Str::template($field->value, $placeholders);
return $field;
},
'toOptions' => function($field) {
$result = [];
foreach ($field->toStructure() as $option) {
// use only key/value pairs that are not empty
if ($option->key()->isNotEmpty() && $option->value()->isNotEmpty()) {
// check if the block field is set to true and parse KirbyTags
if ($option->block()->toBool() === true) {
$result[$option->key()->value()] = kirbytags($option->value());
} else {
$result[$option->key()->value()] = $option->value()->html();
}
}
}
return $result;
}
],
'hooks' => [
'kirbytags:before' => function ($text) {
// set the options from the config as defaults
$defaults = option('cookbook.placeholders.replacements', []);
$options = [];
// if options from fields are set to true, get options from field
if (option('cookbook.placeholders.field')) {
$field = option('cookbook.placeholders.field', 'placeholders');
$options = site()->{$field}()->toOptions();
}
// merge defaults and options
$options = array_merge($defaults, $options);
return Str::template($text, $options);
}
]
]);
Now, we can control the behavior of the plugin through additional config options:
<?php
return [
'cookbook.placeholders.replacements' => [
'company' => 'Great company',
'phone' => '+01 123 1234567',
],
'cookbook.placeholders.field' => 'placeholders'
];
We can set some defaults in the configuration file and optionally replace or extend them with options from the placeholders field.
We can now even allow block level replacements and control them via an additional block
toggle field within the structure field definition:
placeholders:
label: Text replacements
type: structure
fields:
key:
label: Placeholder key
type: text
help: >
The key is what you use in your textfields, e.g. {{ company }}
block:
type: toggle
text: Block type replacement?
value:
label: Replacement text
type: textarea
help: >
The replacement text is the text that will replace your keyholders.
If you rename the fields within the structure field or don't want to put the field into the site options, make sure to adapt the plugin code accordingly.