The Theme Engine
A website theme owns the whole public surface: the markup, styling and wording of every page a visitor sees.
Overview
The admin panel and the website are built by different systems. The panel is fixed: plain PHP templates. The website is themed: a directory of views the platform hands its data to.
A theme is not a skin: no markup underneath to fall back on. No cart view means no cart, so every theme implements the same list of surfaces.
Structure
Template Engines
Three engines are supported; a theme commits to one in its manifest. The same data reaches the view either way.
.tpl.
.twig.
.php templates, no tag layer. Full language access, full responsibility for escaping.
What a Theme Holds
A theme directory sits under templates/website. The directory is the theme; the manifest is the first file read.
templates/website/{Theme}/
├── theme.php # the manifest: engine, meta, status, settings schema. Written by the author
├── config.php # the saved setting VALUES. Written at runtime, never by hand
├── hooks.php # the theme's listeners: variables, output filters, routing additions
├── cover.png # the catalogue thumbnail meta['image'] points at
├── layouts/ # page shells a view extends: default, auth, checkout, invoice
├── partials/ # the pieces a layout assembles: header, footer, topbar, drawer, popup
├── components/ # markup two or more views share: plan grid, payment methods, panels
├── views/ # the surfaces, grouped by area: account/ auth/ checkout/ content/ page/ products/
├── tables/ # column presets for the client area list tables
├── assets/ # css, js, images, favicon the theme ships
├── locale/ # the theme's own wording, per language and per view scope
└── content/ # operator edits of that wording, written from the panel
account/dashboard; the engine adds directory and extension.
css/, js/, images/, favicon, libraries. One function addresses them.
locale/ holds the author's defaults, per language and view scope; content/ holds the operator's edits.
How Data Arrives
Controllers do the work and hand the result to the view as named variables. A theme asks through a hook rather than querying the database itself.
Most variables belong to one surface, catalogued in Template Variables. A smaller set is injected on every themed view.
brand as hex, brand_rgb as "0, 149, 149".
en) and ltr/rtl. They belong on the <html> element; writing either by hand breaks the right to left packs.
resources/, this theme's directory. Use $tadress only outside assets/.
$company_name and $current_year are on every client page. Check anything else with |default: before printing.
Reference
The Manifest: theme.php
A plain array, no class. Five top-level keys; only engine changes how the theme is built. Every key is in Theme Anatomy.
'smarty', 'twig' or 'php'. The only place the engine is declared; it decides the view extension.
'ready' or 'development'. A development theme can be previewed but not activated. Omitting the key means ready.
name, version, author, website, image, description. Read from here, never from saved values.
config.php.
The Settings Schema
settings holds two maps: groups is key => ['label', 'icon'], fields is key => descriptor:
switch · checkbox · color · number · select · textarea. Anything else appears as a text field.
value => label key. Its presence makes the field a dropdown.
other field => required value. The row collapses until every condition matches.
multilang gives one tab per active language and stores a lang => value map. rows sizes a textarea, default 4.
The Theme Object
public static function active(): self; // the installation's theme, falling back to Basic
public static function installed(): array;
public static function manifest(string $themeName): array;
public function getName(): string;
public function engine(): string;
public function exists(): bool;
public function dir(): string; // filesystem path, trailing separator
public function assetUrl(string $path = ''): string; // address of a file under assets/
public function viewExists(string $view): bool; // 'account/dashboard', no extension
public function render(string $view, array $data = []): string;
public function lang(string $key, array $vars = []): string;
public function settingsSchema(): array; // theme.php → settings
public function savedConfig(): array; // config.php → the saved values
public function setting(string $key): mixed; // saved value, else the schema default
public function allSettings(): array; // what the views receive as $setting
public function boot(): void; // includes hooks.php, once, before the first render
Example
The two halves of a setting: the schema declares it, a view reads it back under $setting.
return [
'engine' => 'smarty',
'meta' => [
'name' => 'Acme',
'version' => '1.0.0',
'author' => 'Acme Ltd',
'image' => 'cover.png',
],
'settings' => [
'groups' => [
'topbar' => ['label' => 'grp_topbar', 'icon' => 'bi-megaphone'],
],
'fields' => [
'topbar_enabled' => [
'type' => 'switch',
'group' => 'topbar',
'label' => 'set_topbar_enabled', // a key in locale/{lang}.php
'default' => false,
],
'topbar_text' => [
'type' => 'textarea',
'group' => 'topbar',
'label' => 'set_topbar_text',
'default' => '',
'rows' => 3,
'multilang' => true,
// The row stays collapsed until the switch above is on.
'depends' => ['topbar_enabled' => true],
],
],
],
];
{if $setting.topbar_enabled}
<div class="topbar">{$setting.topbar_text nofilter}</div>
{/if}
Data a theme needs on every page goes through its own hooks file. The listener gets the template path and the data, and it must return the data.
// ONE handler per theme: only the last registration's return value survives.
Hook::add("filter:template.variables", 1, function ($template, $data) {
// Runs on every website page, so anything with a query goes through the cache.
$data["footer_groups"] = Cache::remember('website', 'acme_footer_' . Language::selected(), 3600,
fn (): array => Products::groups());
return $data; // returning nothing drops every key the platform collected
});
// Markup, not data: the layout's hook points take a string and print it as-is.
Hook::add("ui:client.head.css", 1, fn () => '<link rel="stylesheet" href="' . Theme::active()->assetUrl('css/extra.css') . '">');
Pitfalls
Themes are siblings, not forks: a defect in one is almost always in the others. Apply the correction across the set.
The protections exist in the platform, but a form only gets them if the theme includes them.
Both tag engines compile views to plain PHP on disk, literals and conditions included. Keep values and decisions in PHP.
Only the last registration's return value survives, so a second one silently discards the first one's data.
Related Articles
Merci pour votre retour !
Notre équipe d'assistance est disponible 24h/24 pour tout ce que vous ne trouvez pas ci-dessus.