Skip to content

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
<?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 (default text): 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 (default null): options that will be passed to the chosen editor. So far only the code editor handles options. The most common option is the language option (defaults to html). You can check more available options in the Monaco editor's documentation (this is the library that the code editor uses internally)

  • group (default generic): related settings can be groupped in the admin UI using this option. The value can be any arbitrary string.

  • response_type (default null): Check Use settings/As a response section below

  • v7.3+ helpText (default null) Shows a small text explaining how to use the setting.

    It has its own translation domain WfCmsSettings:

    xai-generate-image

Use settings

In PHP code

php
// $settingsManager is the `wf_cms_base.settings_manager` service
$adsTxtSetting = $settingsManager->getSetting('ads.txt');
$value = $adsTxtSetting->getParameters();

In Twig

html
<!-- 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:

html
<!-- 
    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:

yaml
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:

text
// 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:

php
        '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:

html
{{ wf_cms_render_setting_inline('category-tags', {
    entity: category,
}) }}

or, in PHP:

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:

php
        'category-tags' => [
            'editor' => 'code',
            'entity' => 'category',
            'group' => 'seo',
            'variables' => [
                '%title%',
            ]
        ]

and pass them when using the setting:

In twig:

html
{{ wf_cms_render_setting('category-tags', {
    entity: category,
    replace: {
      '%title%': category.title
    } 
) }}

In PHP:

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.

html
{% 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
<?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:

html
{{ 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:

html
<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

json
{
  "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

json
{
  "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

html
<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

json
{
  "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

json
{
  "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:

html
    {{ render(controller('wf_cms.controller.assets:embedModulesDefaultScriptsAction', {
        page: [board1, board2]
    })) }}

Here is an example of Facebook module that should be in parts.

html
  <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:

html
<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:

html
  <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>