Theme Settings

5 Aufrufe Markdown

Declare the settings a theme accepts in its manifest. The admin form, the validation and the saved file follow. The view reads the result as one variable.

Overview

A theme is data, not a class: it declares what it can be configured with, never how that is edited. The panel validates each field by its declared type and writes the values into the theme's own directory.

That is the boundary of the contract. A theme adds a field and gets a control, but does not decide the form's plumbing. A value outside the schema is never stored.

Structure

Two Files, Two Owners

theme.php Schema, defaults, and the theme's name, version and engine. Shipped with the theme, never written at runtime.
config.php The saved values only: a flat map written by the panel, absent until the first save and kept out of version control.
Reading The saved value if there is one, otherwise the schema default.
The form Generated from the schema. A theme wanting more ships its own admin markup, and is handed the builder and the saved values.

The Shape of the Schema

theme.php, the settings block
$manifest['settings'] = [
    // key => the group's heading and icon. The label is a LOCALE KEY of this theme.
    'groups' => [
        'topbar' => ['label' => 'grp_topbar', 'icon' => 'bi-megaphone'],
    ],
    // key => a descriptor. The key is the POST name, the config key and the name the
    // view reads, all at once: renaming it orphans whatever was already saved.
    'fields' => [
        'topbar_enabled' => ['type' => 'switch', 'group' => 'topbar', 'label' => 'set_topbar_enabled', 'default' => false],
    ],
];

Step by Step

Declare a Group

  1. Add an entry to the groups map with a heading and an icon class.
  2. Put the heading in the theme's own locale file, both languages, and reference it by key. An untranslated key prints as itself.
  3. A field whose group is not declared still appears, ungrouped.

Declare a Field

  1. Pick the type from the list below. It decides the control, the validation and what is stored.
  2. Give it a real default, not an empty placeholder. That is the value on a fresh install and after every reset.
  3. Add desc for a help sentence, and depends when the field only makes sense while another is on.
  4. Reload the configure page; the control is there, no other file touched.

Read It in a View

  1. Read it off the settings variable by key. It is a variable and not a function so that it works inside a condition.
  2. For a colour, read the property value and its _rgb twin wherever a translucent version is needed.
  3. From PHP, ask the theme object for one setting by key, without loading the whole map.

Take Over the Admin Form

  1. Add an admin-settings.php to the theme directory; it replaces the generated form entirely.
  2. Build the markup with the form builder, saved values and schema you are handed. Return the HTML as a string.
  3. Keep field names identical to the schema keys. A control named anything else is posted and discarded.

Reference

Field Types

TypeControlStored asValidation on save
switch, checkboxCheckbox; desc is the label beside itBooleanSet means true, absent means false
selectDropdown built from optionsThe chosen keyMust be a key of options, else the default
numberNumber inputIntegerCast to integer; non-numeric becomes zero
colorColour picker plus a typeable hex boxHex string with the leading hashThree to eight hex digits, else the default
textareaTextarea sized by rowsString, or a language map when multilingualTags stripped unless html is on, then allowlist-filtered
anything else, or a missing typeText inputString, or a language map when multilingualSame as a textarea

What a Descriptor Accepts

type One of the types above; absent means a text field.
group The group this field belongs to. An undeclared group leaves it ungrouped.
label A locale key of this theme, not literal text. Resolved in the operator's language, then English, then the key.
desc A locale key for the help line. On a checkbox it becomes the label beside the box instead.
placeholder A locale key for the hint inside a text control. Not a label; the field still needs one.
default The value until the operator saves one, and the fallback when a posted value fails validation. An empty colour makes the derived rgb twin three zeroes.
options Select only, as stored value to label key: ['rail' => 'opt_rail', 'card' => 'opt_card']. Also the allowlist the saved value is checked against.
depends A map of another field to the value it must hold: ['topbar_enabled' => true]. The row collapses until every condition matches, on load and as the operator types.
multilang Text and textarea only. One tab per active language, stored as a language map. Views receive a plain string.
rows Height of a textarea, defaulting to four. Ignored by every other type.
html · allowed_tags Keeps markup in a text value instead of stripping it. The allowlist is narrowable per field; the default covers basic formatting and links.
from_logo Colour only. Marks the field as a brand colour and gives its palette index. The "pick colours from the logo" button fills it from the site logo.

The Settings API

signatures
// The manifest's settings block, exactly as declared (groups + fields).
public function settingsSchema(): array;

// One value: the saved one if the key was ever saved, otherwise the schema default,
// otherwise null. This is how the platform reads a single theme capability.
public function setting(string $key): mixed;

// The saved values only (config.php). Empty until the first save.
public function savedConfig(): array;

// Every schema field merged with its value : what the view receives as $setting.
// A multilingual value is reduced to the ACTIVE language here, and a colour field
// also produces a "{key}_rgb" entry.
public function allSettings(): array;

// A hex colour to the comma-separated triplet an rgba() expression needs. Short forms
// are expanded and a short value is padded, so an empty string yields three zeroes
// rather than an error. This is what produces the "{key}_rgb" entries above.
public static function hexToRgb(string $hex): string;

Example

A group with four fields, the saved file it produces and the view that reads it back. One reason to show them together: the schema key, the file key and the view name are the same string.

templates/website/Acme/theme.php
return [
    'engine' => 'smarty',
    'status' => 'ready',
    'meta'   => [
        'name'    => 'Acme',
        'version' => '1.0.0',
        'author'  => 'Acme Ltd',
        'image'   => 'cover.png',
    ],
    'settings' => [
        'groups' => [
            'brand'    => ['label' => 'grp_brand', 'icon' => 'bi-palette'],
            'topbar'   => ['label' => 'grp_topbar', 'icon' => 'bi-megaphone'],
            'checkout' => ['label' => 'grp_checkout', 'icon' => 'bi-cart3'],
        ],
        'fields' => [
            // A brand colour. `from_logo` is its index in the palette the panel extracts
            // from the site logo, so the "pick colours from the logo" button fills it.
            // The default is a hash plus six hex digits (the picker's own format) —
            // replace the placeholder with this theme's real colour. Leave it empty and
            // the derived primary_color_rgb reads as three zeroes, which paints every
            // translucent surface built from it black.
            'primary_color' => [
                'type'      => 'color',
                'group'     => 'brand',
                'label'     => 'set_primary_color',
                'from_logo' => 0,
                'default'   => '#RRGGBB',
            ],

            // The gate. Everything below it depends on this one.
            'topbar_enabled' => [
                'type'    => 'switch',
                'group'   => 'topbar',
                'label'   => 'set_topbar_enabled',
                'desc'    => 'set_topbar_enabled_desc',
                'default' => false,
            ],

            // Per-language, and allowed to carry markup: an announcement usually has a
            // link in it. The stored value is a map; the view still gets a string.
            'topbar_text' => [
                'type'      => 'textarea',
                'group'     => 'topbar',
                'label'     => 'set_topbar_text',
                'default'   => '',
                'rows'      => 3,
                'multilang' => true,
                'html'      => true,
                'depends'   => ['topbar_enabled' => true],
            ],

            // The keys are what gets stored; the values are locale keys of this theme.
            'checkout_sidebar' => [
                'type'    => 'select',
                'group'   => 'checkout',
                'label'   => 'set_checkout_sidebar',
                'options' => [
                    'rail'  => 'opt_checkout_rail',
                    'card'  => 'opt_checkout_card',
                    'stack' => 'opt_checkout_stack',
                ],
                'default' => 'rail',
            ],

            'popup_width' => [
                'type'    => 'number',
                'group'   => 'checkout',
                'label'   => 'set_popup_width',
                'default' => 500,
            ],
        ],
    ],
];
templates/website/Acme/config.php
return [
    // Stored with the leading hash, exactly as the picker posted it. Note that EVERY
    // schema field is written on every save, whether the operator touched it or not.
    'primary_color'    => '#RRGGBB',
    'topbar_enabled'   => true,
    // Multilingual: stored per language, one entry per ACTIVE language at save time.
    'topbar_text'      => [
        'en' => 'Free migration this month.',
        'tr' => 'Bu ay ücretsiz taşıma.',
    ],
    'checkout_sidebar' => 'card',
    'popup_width'      => 520,
];
the view that reads it
{* A boolean reads directly inside a condition, which is why this is a VARIABLE and
   not a function: a registered function cannot be called from {if}. *}
{if $setting.topbar_enabled}
    {* Markup was kept on save, so it is printed unfiltered here. *}
    <div class="topbar">{$setting.topbar_text nofilter}</div>
{/if}

{* A select is just its stored key: use it as a class rather than branching four times. *}
<div class="checkout-layout-{$setting.checkout_sidebar}">

{* A colour field also produces an _rgb twin, for a translucent version of the same colour. *}
<style>
    :root {
        --acme-brand: {$setting.primary_color};
        --acme-brand-rgb: {$setting.primary_color_rgb};
    }
</style>

Pitfalls

Renaming a key orphans what was already saved

The key is the form name, the stored key and the name the view reads. A value under the old name is never read again and never cleaned up. Every installation that had configured the theme falls back to the default.

The saved file is generated, not source

It holds values and nothing else. Name and version stay in the manifest, where the theme listing and the upgrade check read them. Shipping one in a theme package hands your test configuration to everyone who installs it.

A multilingual value is a map in the file and a string in the view

PHP asking for the raw setting gets the whole map. The view gets the active language resolved. Code confusing the two works in exactly one place.

A collapsed dependent field is still saved and still read

The dependency hides the row; it does not disable the value. A view reading a dependent setting must check the field it depends on too. Otherwise it shows something the operator believes is off.

War das hilfreich?

Vielen Dank für Ihre Rückmeldung!

Brauchen Sie weitere Hilfe?

Unser Support-Team ist rund um die Uhr für Sie da, wenn Sie oben nicht fündig werden.