vinou/site-builder

PHP library to generate template based webpages with dynamic content from the Vinou Plattform

Maintainers

Package info

github.com/vinou-platform/sitebuilder

Homepage

pkg:composer/vinou/site-builder

Statistics

Installs: 1 006

Dependents: 0

Suggesters: 0

Stars: 2

Open Issues: 0


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

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 authid and token in config/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):

  1. Theme: YourTheme/Configuration/settings.yml
  2. 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:

  1. public/Resources/Templates/ — project
  2. public/Resources/Partials/ — project
  3. public/Resources/Layouts/ — project
  4. Theme/Resources/Layouts/ — theme
  5. Theme/Resources/Partials/ — theme
  6. Theme/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:

  1. SiteBuilder built-in routes (lowest priority, loaded as base)
  2. Theme routes (override SiteBuilder)
  3. 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.51.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):

  1. Enter password.
  2. If system-level TOTP is configured (system.totp_secrets): redirect to /system/totp.
  3. Otherwise: panel access granted.

Multi-user mode (system.users present):

  1. Enter email + password.
  2. Account has no totp_secrets → redirected to /system/totp/first-setup (forced; cannot be skipped).
  3. Account has totp_secrets → redirected to /system/totp (TOTP verification).
  4. 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

  1. Add system.password: 'bootstrap' to config/settings.yml to activate the panel.
  2. Log in (single-password mode, no email field shown).
  3. Open the Benutzer tab → create the first user (email + password).
  4. Log out, log in as the new user.
  5. The forced first-time 2FA screen appears — scan the QR code and enter the confirmation code.
  6. Delete system.password from config/settings.yml. The panel stays active because system.users is 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
Mail /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