v4.1+Settings Manager
Intro
XalokNext offers a way to edit installation-dependent settings. Some examples of things commonly stored as settings are metatags (home/category/article), analytics code (at least the property ID changes between installations), robots.txt (PRE would block search engines while PRO must allow them).
Defining settings
In order to define settings, overwrite the wf_cms_base.settings_manager
service. In this class, add a public property $settings
:
<?php
namespace App\Bundle\CmsBundle\Settings;
class SettingsManager extends \Wf\Bundle\CmsBaseBundle\Settings\SettingsManager
{
public $settings = [
'ads.txt' => [
'editor' => 'code',
'editorOptions' => [
'language' => 'text',
],
'group' => 'ads',
'response_type' => 'text/plain',
],
];
}
The key of the setting (ads.txt
in the example above) serves as the slug of the setting. The settings' definition has the following keys:
editor
(defaulttext
): the type of form field used to edit this setting in the admin. Possible values:text
(text input),checkbox
(checkbox),code
(a visual multiline editor that supports syntax highlighting).editorOptions
(defaultnull
): options that will be passed to the choseneditor
. So far only thecode
editor handles options. The most common option is thelanguage
option (defaults tohtml
). You can check more available options in the Monaco editor's documentation (this is the library that thecode
editor uses internally)group
(defaultgeneric
): related settings can be groupped in the admin UI using this option. The value can be any arbitrary string.response_type
(defaultnull
): Check Use settings/As a response section below- v7.3+
helpText
(defaultnull
) Shows a small text explaining how to use the setting.It has its own translation domain
WfCmsSettings
:
Use settings
In PHP code
// $settingsManager is the `wf_cms_base.settings_manager` service
$adsTxtSetting = $settingsManager->getSetting('ads.txt');
$value = $adsTxtSetting->getParameters();
In Twig
<!-- as an ESI include that will be invalidated on every change to the setting -->
{{ wf_cms_render_setting('ads.txt') }}
<!-- inline ->
{{ wf_cms_render_setting_inline('ads.txt') }}
Twig functions options
Both wf_cms_render_setting
and wf_cms_render_setting_inline
twig functions described above accept onlyPublic
and onlyAdmin
(new in v7.2) options:
<!--
Renders the `ads_script` setting only on the public part of the project,
useful for keeping the admin part ads-free.
-->
{{ wf_cms_render_setting('ads_script', {onlyPublic: true}) }}
<!--
Renders the `admin_welcome_message` only in the admin part
-->
{{ wf_cms_render_setting('admin_welcome_message', {onlyAdmin: true}) }}
As a response
You can add a route that will serve the settings' contents as its response:
ads_txt:
path: /ads.txt
defaults:
_controller: WfCmsBaseBundle:Settings:show
slug: ads.txt
For the response, the response_type
added in the setting's definition will be used.
Settings fixtures
While changing the value of a setting (article metatags, for example) without a deploy (using the setting's admin UI) is nice, having to add the same base values on each installation is tedious. For this, XalokNext's settings can have a fixture file with a base value.
Add a .txt
file in the app/settings-fixtures
directory:
// e.g.: app/settings-fixtures/ads.txt.txt
// yes, there's double `.txt` in this case, the first comes from the setting's slug (`ads.txt`)
// the second is the convention - all settings fixtures are stored as `.txt` files
base value of the ads.txt setting
On a fresh installation, XalokNext will use the contents of this file for the setting. If the value is changed through the admin UI (the values are stored in the DB), the overwritten value will be used. If, after the manual edit, the fixture is updated, a warning sign will be displayed next to the setting in the admin UI informing the user of this change. The user then has the chance to see the differences between their version and the fixture, apply the necessary changes and 'Mark fixture as reviewed' - this will hide the warning, so that future updates to the fixture file will get a new warning sign.
Entity settings
The entity
option with value category
or tag
may be passed to the setting's definition:
'category-tags' => [
'editor' => 'code',
'entity' => 'category',
'group' => 'seo',
]
This allows editing a 'default' value for this setting but also allows overwriting the setting for each category/tag.
You must also pass the entity
option with the actual entity for which the setting is being rendered:
{{ wf_cms_render_setting_inline('category-tags', {
entity: category,
}) }}
or, in PHP:
// the wf_cms.entity_routing_util service has been added in XalokNext:6.0
$settingsManager; // wf_cms_base.settings_manager service
$settingsProcessor; // wf_cms_base.settings_processor service
$entityRoutingUtil; // wf_cms.entity_routing_util service
$category; // the category/tag entity
$setting = $settingsManager->getSetting('category-tags');
$settingsProcessor->getParameters(
$setting,
[
'%title%' => $category->getTitle(),
]
$entityRoutingUtil->getEntityUri($category)
);
Replacement variables
In some cases a setting might render a different value, depending on where it's used. For example, category-tags
will most likely include a <title>%title%</title>
tag, and the value of %title%
depends on the category being rendered. Add a variables
key to the setting's definition:
'category-tags' => [
'editor' => 'code',
'entity' => 'category',
'group' => 'seo',
'variables' => [
'%title%',
]
]
and pass them when using the setting:
In twig:
{{ wf_cms_render_setting('category-tags', {
entity: category,
replace: {
'%title%': category.title
}
) }}
In PHP:
$settingsManager; // wf_cms_base.settings_manager service
$settingsProcessor; // wf_cms_base.settings_processor service
$category; // the category/tag entity
$setting = $settingsManager->getSetting('category-tags');
$settingsProcessor->getParameters(
$setting,
[
'%title%' => $category->getTitle(),
]
);
Conditions in settings' values
Some cases may require showing a certain line only in certain conditions.
{% if %hasAmp% %}<link rel="amphtml" href="%ampUrl%" />{% endif %}
Of course, %hasAmp%
should be present in the setting's definition and passed when rendering the setting.
Setting apps
Besides groupping related settings in groups, these groups can be further groupped into apps.
<?php
namespace App\Bundle\CmsBundle\Settings;
class SettingsManager extends \Wf\Bundle\CmsBaseBundle\Settings\SettingsManager
{
public $apps = [
'advertisement' => [
'ads', 'app-ads', 'ads-bidders',
],
];
}
The key (advertisement
) serves as the slug of the app, the value is an array of group names that will be shown in this app. All groups that are not part of any app will be left in the default settings' app.
Then render this app somewhere in your admin twigs:
{{ render(controller('WfCmsBaseAdminBundle:Settings:app', {
app: 'advertisement'
})) }}
Custom modules
Intro
A special case of a settings' app is an interface for administering embed codes. Each embed code can be used inside the editor by using the wfed/free/generic
(or wf-role="embed"
in v7+) module:
<div
wf-module="wfed/free/generic"
wf-role="content_generic"
wf-toolbar-position="bottom"
wf-allow="+-"
{# wf-facet="amp" #} <!-- uncomment this line to use the `amp.html` template when rendering
wf-url="/custom_modules_json"
></div>
To define an embed, add a directory in the app/settings-fixtures
directory:
app/settings-fixtures/facebook:
amp.html # template for the amp platform
defaultScript.html # read the "Default script" section below
desktop.html # template for the desktop platform
index.json: # the JSON definition of the editor form
{ "fields": { "url": { "type": "textarea", label: "URL of the Facebook embed" } } }
instant.html # template for the instant (articles) platform
For each of these directories XalokNext creates a custom module definition, no need to define them in the SettingsManager::$settings
. Head over to Tools->Custom modules in the admin's main menu (URL: /settings/custom-modules
in the admin) to make changes to these custom modules' definitions in the current environment.
Custom module editor fields
The fields
editor for the custom module in the UI shows the fields
key in the index.json
fixture (if available).
Example: fields
that the editor can fill when selecting this custom module type (setting-fixtures/custom-modules/__CUSTOM_MODULE_TYPE__/index.json
). This example shows the various field types available and their options
{
"fields": {
"url": {
"type": "text",
"label": "URL of the embed"
},
"description": {
"type": "textarea",
"label": "Description"
},
"color": {
"type": "select",
"label": "Color",
"select_options": [
{
"name": "Red",
"value": "red"
},
{
"name": "Green",
"value": "green"
}
]
}
]
}
Required fields
By default, all the fields are required - that is, if the editor doesn't fill up a value for any of the fields, the module will not show on the public side.
If any of the fields has the required
property, the rest will become optional. To avoid confusion, if you want to change the default behaviour and add the required
field to one of the fields, it's best to add it the all the fields.
Example: In this example the description
field takes an implicit required: false
. Without the required: true
for the url
field, both fields would be required
{
"fields": {
"url": {
"type": "text",
"label": "URL of the embed",
"required": true
},
"description": {
"type": "textarea",
"label": "Description"
}
}
}
Note: A field being required doesn't mean that the editor will get an error if they don't fill up that field - only that the module will be discarded on the public side.
Templates
The HTML files mentioned in the Intro contain the code for the templates that will be shown on the public side.
By default, XalokNext uses default.html
, but this can be controlled by using wf-facet="__TEMPLATE_NAME__"
(e.g.: wf-facet="amp"
) when adding the module in the template.
In the template files, you can use the values of the various fields by using the %field_name%
syntax.
Example: A template for a custom module, using the fields defined in the fields section
<div class="my-custom-module">
<span
data-url="%url%"
style="backbround-color: %color%"
>
%description%
</span>
</div>
Derived fields (matches
)
In the custom modules' UI there is an editor titled "matches" - it's showing the value of the matches
key inside index.json
. This can be used to derive some fields from existing ones.
Example: Extracting an relativeUrl
field derived from the url
field
{
"fields": {
"url": {
"label": "URL"
}
},
"matches": {
"relativeUrl": {
"regex": "https?:\\/\\/[^\\/]+\\/(.*)",
"from": "url"
}
}
}
Note: the parenthesis in the regex
is what contains the value for the derived field.
This can then be used just in the template files like any other field, as %relativeUrl%
.
Derived fields transformations
The value of the derived field can additionally be transformed by some JavaScript code, eval
-ed at runtime. Use the match
variable in this code for the matched value.
Example: Replace "photo" with "post" in the relativeUrl
field
{
"fields": {
"url": {
"label": "URL"
}
},
"matches": {
"relativeUrl": {
"regex": "https?:\\/\\/[^\\/]+\\/(.*)",
"from": "url",
"transform": "match.replace('photo', 'post')"
}
}
}
v7.2 Default script
Each custom module can have a setup script - one instance of this script should be rendered, no matter how many modules of that type are included.
For articles, this is already included in @WfCmsBase/Article/base_show.html.twig
in the bodyscripts
block (https://git.xalok.com/HML/cms-standard/-/blob/962966ab3121a7c476b67d3426c4cbd5bfc02b27/Wf/Bundle/CmsBaseBundle/Resources/views/Article/base_show.html.twig#L6) - make sure it's not overwritten in the project.
The action rendering these default scripts accepts an array of Page
entities - useful if one wants to render the default scripts across multiple boards, for example:
{{ render(controller('wf_cms.controller.assets:embedModulesDefaultScriptsAction', {
page: [board1, board2]
})) }}
Here is an example of Facebook module that should be in parts.
<aside class="c-detail__embed">
<div class="code">
<script async defer crossorigin="anonymous" src="https://connect.facebook.net/es_ES/sdk.js#xfbml=1&version=v15.0&appId=391758137597734&autoLogAppEvents=1" nonce="w1xclsYD"></script>
<div class="fb-post" data-href="%url%" data-width="500" data-show-text="true">
</div>
</div>
</aside>
The script should only be included once on the page and not be used as part of HTML structure. It would be used by every occurrence but not repeated each time the module is included.
Default script:
<script async defer crossorigin="anonymous" src="https://connect.facebook.net/es_ES/sdk.js#xfbml=1&version=v15.0&appId=391758137597734&autoLogAppEvents=1" nonce="w1xclsYD"></script>
Template:
<aside class="c-detail__embed">
<div class="code">
<div class="fb-post" data-href="%url%" data-width="500" data-show-text="true">
</div>
</div>
</aside>