vinou / site-builder
PHP library to generate template based webpages with dynamic content from the Vinou Plattform
Requires
- php: >=8.2
- bramus/router: *
- erusev/parsedown: ^1.7
- gumlet/php-image-resize: ^1.9
- phpmailer/phpmailer: ^6.0
- s1syphos/php-simple-captcha: ^2.3
- symfony/yaml: ^6.0|^7.0
- thepixeldeveloper/sitemap: ^5.1
- twig/extensions: 1.*
- twig/twig: 2.*
- vinou/api-connector: ^3.0.0
- vinou/translations: 4.*
- dev-master
- 4.1.4
- 4.1.3
- 4.1.2
- 4.1.1
- 4.1.0
- 4.0.1
- 3.1.5
- 3.1.4
- 3.1.3
- 3.1.2
- 3.1.1
- 3.1.0
- 3.0.6
- 3.0.5
- 3.0.4
- 3.0.3
- 3.0.2
- 3.0.1
- 3.0.0
- 2.9.11
- 2.9.10
- 2.9.9
- 2.9.8
- 2.9.7
- 2.9.6
- 2.9.5
- 2.9.4
- 2.9.3
- 2.9.2
- 2.9.1
- 2.9.0
- 2.8.4
- 2.8.3
- 2.8.2
- 2.8.1
- 2.8.0
- 2.7.2
- 2.7.1
- 2.7.0
- 2.6.5
- 2.6.4
- 2.6.3
- 2.6.2
- 2.6.1
- 2.6.0
- 2.5.2
- 2.5.1
- 2.5.0
- 2.4.3
- 2.4.2
- 2.4.1
- 2.4.0
- 2.3.3
- 2.3.2
- 2.3.1
- 2.3.0
- 2.2.1
- 2.2.0
- 2.1.1
- 2.1.0
- 2.0.5
- 2.0.4
- 2.0.3
- 2.0.2
- 2.0.1
- 2.0.0
- 1.3.5
- 1.3.4
- 1.3.3
- 1.3.2
- 1.3.1
- 1.3.0
- 1.2.12
- 1.2.11
- 1.2.10
- 1.2.9
- 1.2.8
- 1.2.7
- 1.2.6
- 1.2.5
- 1.2.4
- 1.2.3
- 1.2.2
- 1.2.1
- 1.2.0
- 1.1.1
- 1.1.0
- 1.0.9
- 1.0.8
- 1.0.7
- 1.0.6
- 1.0.5
- 1.0.4
- 1.0.3
- 1.0.2
- 1.0.1
- 1.0.0
- dev-release/2.X
- dev-develop
- dev-optional_mail_delivery
- dev-optimized_captcha
- dev-+548_optimize_external_order_payment_methods
- dev-development
This package is auto-updated.
Last update: 2026-05-18 12:42:38 UTC
README
The Vinou Site-Builder is a PHP library that combines PHP routing configured in YAML files with Twig template rendering. It provides a data processing pipeline to call registered processors and pipe results directly into Twig templates.
Table of contents
- Typical project structure
- Installation
- Route configuration
- Settings
- Template override hierarchy
- Twig filters
- Classlist
- Provider
Typical project structure
| File | Description |
|---|---|
composer.json |
Composer configuration |
config/settings.yml |
SiteBuilder and ApiConnector settings |
config/routes.yml |
Project-specific routes |
config/mail.yml |
SMTP credentials and form definitions |
public/index.php |
Application entry point |
public/.htaccess |
Routes all requests to index.php |
public/Resources/Layouts/ |
Twig layout overrides |
public/Resources/Partials/ |
Twig partial overrides |
public/Resources/Templates/ |
Page templates |
public/Resources/Sass/ |
SCSS source files |
The complete example project is available in Examples/Project/.
Installation
composer require vinou/site-builder cp -R vendor/vinou/site-builder/Examples/Project/config ./ cp -R vendor/vinou/site-builder/Examples/Project/web ./
Required after installation:
- Set Vinou
authidandtokeninconfig/settings.yml— find them in Vinou Office - Set SMTP credentials in
config/mail.yml
Typical optional setup:
- Add project routes to
config/routes.yml - Configure Vinou constants in
public/index.php
Minimal public/index.php:
<?php require_once __DIR__ . '/../vendor/autoload.php'; define('VINOU_ROOT', realpath('./')); define('VINOU_MODE', 'Shop'); define('VINOU_CONFIG_DIR', '../config/'); $session = new \Vinou\ApiConnector\Session\Session(); $session::setValue('language', 'de'); $site = new \Vinou\SiteBuilder\Site(); $site->setRouteFile('routes.yml'); $site->loadTheme('my-theme', 'Theme/MyTheme/'); $site->run();
Route configuration
1. General route parameters
| Parameter | Default | Options | Description |
|---|---|---|---|
type |
page |
page |
Render a Twig template |
redirect |
Redirect to internal or external URL | ||
namespace |
Group sub-routes under a URL prefix | ||
sitemap |
Render a sitemap.xml | ||
method |
get |
get | post | all |
Accepted HTTP method |
template |
— | Pages/start.twig |
Twig template to render |
redirect |
— | /other/page or https://… |
Redirect target (requires type: redirect) |
pageTitle |
— | string |
Page title, available as {{ pageTitle }} in templates |
public |
true |
true | false |
false requires a Vinou client login |
sitemap |
false |
true | false | Array |
Include in sitemap; array for dynamic entry generation |
twig |
— | cache: false |
Override Twig settings for this route |
urlKeys |
— | [slug, id] |
Named aliases for URL wildcard segments |
dataProcessing |
— | Array | Data loading steps, results available in template |
postProcessing |
— | Array | Runs after dataProcessing, same syntax |
extend |
false |
true |
Deep-merge with parent route instead of replacing it |
excludeContent |
— | [key1, key2] |
Skip specific additionalContent items for this route |
2. Sitemap configuration for a route
Use sitemap as an array on routes with a URL wildcard to generate one sitemap entry per item:
weine/{path_segment}: template: 'Wines/Detail.twig' pageTitle: 'Wein-Details' public: true sitemap: function: getWinesAll params: lazy: false pageSize: 500 dataKey: 'wines' dataProcessing: wine: getWine
| Parameter | Description |
|---|---|
function |
API-Connector function to fetch the list of items |
params |
Parameters passed to the function |
params/lazy |
Load items in recurring pages (recommended for large datasets) |
params/pageSize |
Items per page when using lazy loading |
dataKey |
Key in the API response containing the items array |
3. Use dataProcessing
Each entry in dataProcessing calls a function in a processor and stores the result under the given key in the Twig context.
Shorthand — calls an API-Connector function directly:
dataProcessing: wines: getWinesAll wineries: getWineriesAll
Full syntax:
dataProcessing: wines: function: getWinesAll params: pageSize: 50 orderBy: topseller DESC cluster: - type - taste_id - vintage
Combining data with formatter/mergeData:
dataProcessing: wines: function: getWinesAll bundles: function: getBundlesAll items: processor: formatter function: mergeData useRouteData: false useData: - wines - bundles
mergeData flattens sub-arrays into one list and sets object_type per entry (e.g. wines, bundles).
Full parameter reference:
| Parameter | Example | Description |
|---|---|---|
processor |
formatter |
Registered processor identifier (default: Vinou API) |
class |
\Vendor\Ns\Processor |
Instantiate a class directly as processor |
function |
getWinesAll |
Function name to call on the processor |
params |
Array | Static parameters passed to the function |
useRouteData |
false |
Set to false to exclude URL wildcard segments from function arguments |
useData |
[wines, bundles] |
Keys already in renderArr to pass as input |
key |
wines |
Key to extract from the function's return value |
forceLoadAll |
true |
Use the complete return value instead of extracting by key |
loadOnlyFirst |
true |
Use only the first element of the result array |
postParams |
query,page |
Comma-separated POST field names to forward (whitelist) |
getParams |
query,page |
Comma-separated GET parameter names to forward (whitelist) |
stopProcessing |
true |
Abort remaining dataProcessing steps if result is empty |
4. Extend a parent route
Setting extend: true on a project route causes it to deep-merge (array_replace_recursive) with the existing route definition from the theme instead of replacing it entirely. Only the keys you define are overridden; all other settings are inherited.
# config/routes.yml weine: extend: true pageTitle: 'Unser Weinshop' # overrides theme value dataProcessing: text: # added on top of theme dataProcessing function: getText params: identifier: 'weine'
Without extend: true, the project route replaces the theme route completely.
5. Global content (additionalContent)
additionalContent is defined in settings.yml (typically in the theme) and runs on every page before route-specific dataProcessing. Results are available in all templates.
# Theme/MyTheme/Configuration/settings.yml additionalContent: categories: function: getCategoriesAll params: orderBy: sorting ASC client: getClient paymentMethods: function: getAvailablePayments key: payments
To skip specific items on a route, use excludeContent:
# config/routes.yml kontakt: template: 'Pages/kontakt.twig' excludeContent: [categories] dataProcessing: captcha: processor: mailer function: loadCaptcha
6. Registered processors
| Identifier | Class | Description |
|---|---|---|
| (default) | \Vinou\ApiConnector\Api |
All Vinou API calls (get*, search*, etc.) |
shop |
\Vinou\SiteBuilder\Processors\Shop |
Basket, billing, delivery, payments, campaigns |
mailer |
\Vinou\SiteBuilder\Processors\Mailer |
Form handling, captcha, email dispatch |
files |
\Vinou\SiteBuilder\Processors\Files |
Read local files with metadata |
external |
\Vinou\SiteBuilder\Processors\External |
Fetch external URLs / JSON |
sitemap |
\Vinou\SiteBuilder\Processors\Sitemap |
Sitemap XML generation |
formatter |
\Vinou\SiteBuilder\Processors\Formatter |
Merge and format loaded data |
7. Register your own processor
Create a processor class:
<?php namespace YourVendor\YourNamespace\Processors; class YourProcessor extends \Vinou\SiteBuilder\Processors\AbstractProcessor { public function dataMagic($data = null) { // transform data here return $data; } }
Register it in index.php before calling $site->run():
$site = new \Vinou\SiteBuilder\Site(); $site->render->loadProcessor('myprocessor', new \YourVendor\YourNamespace\Processors\YourProcessor()); $site->run();
Use it in a route:
my-route: template: 'Pages/template.twig' dataProcessing: result: processor: myprocessor function: dataMagic params: foo: bar
Settings
Settings are loaded from YAML files and merged in this order (later wins):
- Theme:
YourTheme/Configuration/settings.yml - Project:
config/settings.yml
The theme controls whether SiteBuilder built-in defaults are loaded:
system: load: defaultRoutes: false # disable SiteBuilder's bundled routes defaultSettings: false # disable SiteBuilder's bundled settings
Global settings reference (settings key):
| Key | Description |
|---|---|
defaults.payment |
Default payment method |
allowedPayments |
Comma-separated list of accepted payment methods |
minBasketSize |
Minimum number of bottles required to check out |
packageSteps |
Allowed basket sizes as array (e.g. [6,12,18]) |
maxItemQuantity |
Maximum quantity per basket item |
enableClickAndCollect |
Enable click & collect option |
useStockDistribution |
Distribute stock across positions |
deliveryCountries |
Array of ISO country codes for delivery |
defaultCountry |
Pre-selected country code |
speechStyle |
formal or informal — controls salutation in templates |
seo.titleAdd |
String appended to every page title |
permissionRedirect |
URL to redirect unauthenticated users to |
pages.* |
Internal URLs for basket, checkout, login, etc. |
images.* |
Image dimensions for list and detail views |
Template override hierarchy
SiteBuilder uses Twig's FilesystemLoader with an ordered list of directories. The first matching template wins.
Default search order:
public/Resources/Templates/— projectpublic/Resources/Partials/— projectpublic/Resources/Layouts/— projectTheme/Resources/Layouts/— themeTheme/Resources/Partials/— themeTheme/Resources/Templates/— theme
A template in public/Resources/Templates/Wines/List.twig will silently override Theme/Resources/Templates/Wines/List.twig.
The same priority applies to route loading:
- SiteBuilder built-in routes (lowest priority, loaded as base)
- Theme routes (override SiteBuilder)
- Project
config/routes.yml(highest priority)
Twig filters
All filters are registered by \Vinou\SiteBuilder\Tools\Render.
| Filter | Signature | Description |
|---|---|---|
image |
src|image(chstamp, dimension) |
Download and cache API image; optionally resize |
pdf |
src|pdf(chstamp) |
Download and cache API PDF |
src |
path|src |
Append file modification timestamp for cache busting |
region |
id|region |
Resolve wine region ID to name |
taste |
id|taste |
Resolve taste ID to label |
grapetypes |
array|grapetypes |
Resolve grape type IDs to name map |
link |
label|link(url, params) |
Render an <a> tag; adds active class on current URL |
language |
value|language(translations, key, current) |
Render language switch link |
currency |
decimal|currency |
Format number as price: 1234.5 → 1.234,50 |
netto |
decimal|netto |
Convert gross to net (÷1.19) |
basePrice |
price|basePrice(unit) |
Format base price with unit label |
price |
items|price |
Sum price × quantity across a basket items array |
nl2p |
string|nl2p |
Convert newlines to <p> tags |
pageTitle |
object|pageTitle |
Build a page title from name, articlenumber, vintage |
groupBy |
array|groupBy(key) |
Group array by a property value |
sortBy |
array|sortBy(prop, dir) |
Sort array by property (ASC|DESC) |
ksort |
array|ksort |
Sort array by key |
withAttribute |
array|withAttribute(attr, val) |
Filter: keep entries where attr == val |
withoutAttribute |
array|withoutAttribute(attr, val) |
Filter: exclude entries where attr == val |
subArray |
array|subArray(index) |
Extract one property from each entry |
addProperty |
array|addProperty(key, val) |
Add a fixed property to every entry |
sum |
array|sum |
Sum an array of numbers |
wines |
items|wines |
Filter basket items to type wine |
packages |
items|packages |
Filter basket items to type package |
getBundle |
id|getBundle |
Fetch a single bundle from the API |
getWinery |
id|getWinery |
Fetch a single winery from the API |
quantityIsAllowed |
qty|quantityIsAllowed |
Check if quantity meets basket rules |
cast_to_array |
obj|cast_to_array |
Cast stdClass to associative array |
arraytocsv |
array|arraytocsv |
Join array to comma-separated string |
filesize |
file|filesize |
Human-readable file size (KB, MB, …) |
bytes |
bytes|bytes(precision) |
Format raw byte count |
cleanup |
string|cleanup |
Strip leading space, commas, @, quotes |
http |
url|http |
Prepend http:// if no protocol present |
base64image |
url|base64image |
Encode image as base64 data URI |
Classlist
| Class | Description |
|---|---|
\Vinou\SiteBuilder\Site |
Main class; combines router, renderer, and settings loader |
\Vinou\SiteBuilder\Loader\Settings |
Collects and merges settings YAML files |
\Vinou\SiteBuilder\Tools\Render |
Initialises Twig, registers filters, executes dataProcessing |
\Vinou\SiteBuilder\Router\DynamicRoutes |
Reads YAML route files and configures the Bramus router |
\Vinou\SiteBuilder\Processors\AbstractProcessor |
Base class for custom processors; provides loadApi() |
\Vinou\SiteBuilder\Processors\Shop |
Shop-specific processing (basket, checkout, campaigns) |
\Vinou\SiteBuilder\Processors\Mailer |
Form dispatch and captcha |
\Vinou\SiteBuilder\Processors\Formatter |
Data combination utilities (mergeData) |
\Vinou\SiteBuilder\Processors\Files |
Local file reading |
\Vinou\SiteBuilder\Processors\External |
External HTTP requests |
\Vinou\SiteBuilder\Processors\Sitemap |
Sitemap XML generation |
Admin panel
The SiteBuilder ships with a built-in admin panel at /system. It is activated automatically when the project settings contain admin credentials and provides a HTMX-driven single-page interface for managing settings, users, redirects, routes, mail configuration, and system state.
Activation
Site::loadAdminPanel() registers the panel routes when either of the following keys is present in config/settings.yml:
# Option A — legacy single-password (useful for initial bootstrap) system: password: 'my-password' # Option B — multi-user (production) system: users: - email: admin@example.com password: '$2y$10$…' # bcrypt hash, auto-generated on first save via the panel
Both keys can coexist temporarily during the bootstrap process. Once system.users is populated, system.password can be removed.
Login flow
Single-password mode (no system.users):
- Enter password.
- If system-level TOTP is configured (
system.totp_secrets): redirect to/system/totp. - Otherwise: panel access granted.
Multi-user mode (system.users present):
- Enter email + password.
- Account has no
totp_secrets→ redirected to/system/totp/first-setup(forced; cannot be skipped). - Account has
totp_secrets→ redirected to/system/totp(TOTP verification). - Correct TOTP code → panel access granted.
Session keys used during the flow:
| Key | Value | Purpose |
|---|---|---|
vinou_admin_auth |
true |
Full authentication complete |
vinou_admin_pw_ok |
true |
Password verified; awaiting TOTP |
vinou_admin_user_email |
email string | Identifies which user is logging in |
admin_csrf_token |
hex string | Per-session CSRF token |
admin_login_attempts |
array | Brute-force counter and lockout timestamp |
Security
| Feature | Implementation |
|---|---|
| Password hashing | password_hash(PASSWORD_BCRYPT) — plaintext passwords in settings.yml are auto-upgraded to bcrypt on first successful login |
| Brute-force protection | 5 failed attempts (password or TOTP) → 15-minute session lockout |
| CSRF | Per-session token via random_bytes(16); HTMX injects it as X-CSRF-Token header via htmx:configRequest; login forms use a hidden _csrf_token field |
| TOTP 2FA | RFC 6238 native implementation — no external library; base32 decode, HMAC-SHA1, dynamic truncation, ±1 window for clock drift; multiple apps per user |
Bootstrap workflow for new projects
- Add
system.password: 'bootstrap'toconfig/settings.ymlto activate the panel. - Log in (single-password mode, no email field shown).
- Open the Benutzer tab → create the first user (email + password).
- Log out, log in as the new user.
- The forced first-time 2FA screen appears — scan the QR code and enter the confirmation code.
- Delete
system.passwordfromconfig/settings.yml. The panel stays active becausesystem.usersis now populated.
URL structure
| URL | Handler | Description |
|---|---|---|
GET/POST /system |
Admin::init() |
Login form or redirect to settings |
GET /system?action=logout |
Admin::init() |
Destroy session |
POST /system/totp |
Admin::verifyTotpStep() |
TOTP code verification |
GET/POST /system/totp/first-setup |
Admin::firstTotpSetup() |
Forced initial 2FA registration |
POST /system/totp/setup |
Admin::setupTotp() |
Add a 2FA app to the current user |
POST /system/totp/disable |
Admin::disableTotp() |
Remove a 2FA app from the current user |
GET /system/settings |
Admin::page() |
Settings editor |
GET /system/users |
Admin::page() |
User management + own 2FA apps |
GET /system/redirects |
Admin::page() |
Redirect management |
GET /system/routes |
Admin::page() |
Route overview |
GET /system/mail |
Admin::page() |
Mail configuration |
GET /system/system |
Admin::page() |
Cache management + PHP environment + Vinou packages |
POST /system/users/save |
Admin::saveUser() |
Create user |
POST /system/users/update |
Admin::updateUser() |
Update user email / password |
POST /system/users/delete |
Admin::deleteUser() |
Delete user |
POST /system/settings/save |
Admin::saveSetting() |
Save a setting value |
POST /system/settings/delete |
Admin::deleteSetting() |
Delete a setting key |
POST /system/settings/add |
Admin::addSetting() |
Append to a settings array |
POST /system/redirects/save |
Admin::saveRedirect() |
Save a redirect rule |
POST /system/redirects/delete |
Admin::deleteRedirect() |
Delete a redirect rule |
GET /system/assets/{file} |
PHP closure | Static assets (CSS, JS) |
Panel tabs
| Tab | URL | Content |
|---|---|---|
| Settings | /system/settings |
Edit whitelisted settings keys (additionalContent, settings, vinou) |
| Benutzer | /system/users |
User list with edit/delete + own TOTP app management |
| Redirects | /system/redirects |
HTTP redirect rules from config/redirects.yml |
| Routes | /system/routes |
Loaded route configuration |
/system/mail |
SMTP config + form definitions from config/mail.yml |
|
| System | /system/system |
Cache clearing + PHP/extension checks + installed Vinou packages |
Settings whitelist
The Settings panel only displays and allows editing of keys listed in SETTINGS_WHITELIST:
private const SETTINGS_WHITELIST = ['additionalContent', 'settings', 'vinou'];
All other top-level keys (system, shop, etc.) are never exposed or writable through the panel UI.
User data structure
system: users: - email: admin@example.com password: '$2y$10$…' # bcrypt totp_secrets: - name: 'My iPhone' secret: 'ABCDEFGHIJKLMNOP' # base32-encoded TOTP secret - name: 'Backup key' secret: 'QRSTUVWXYZ234567'
Each user manages their own totp_secrets. The system-level totp_secrets / totp_secret keys are only used in legacy single-password mode.
Template structure
Admin/Panel.twig Shell: <head> with CSS, HTMX, qrcodejs; navigation; #section-content
Admin/Sections/
Settings.twig Settings editor (section.settings, section.projectPaths)
Users.twig User list + edit forms + own TOTP management
(section.users, section.currentEmail, section.totpApps, …)
Redirects.twig Redirect CRUD (section.redirects)
Routes.twig Route tree (section.routes)
Mail.twig SMTP + form config (section.mailConfig)
System.twig Cache cards + PHP checks + Vinou packages
(section.environment, section.vinouPackages)
qrcodejs is loaded once in Panel.twig's <head> to avoid HTMX script-loading race conditions.
Static assets
CSS (admin.css) and JS (htmx.min.js) are served via a PHP closure from vendor/vinou/site-builder/Resources/Public/. SCSS source: Resources/Sass/Admin/panel.scss.
Provider
This library is developed by Vinou GmbH.
Vinou GmbH
Justus-von-Liebig-Straße 9e
55232 Alzey
E-Mail: kontakt@vinou.de
Phone: +49 6131 6245390