folderweb/docs/04-development/03-configuration.md
Ruben b03511f99b Update AGENT.md and add architecture documentation
Update AGENT.md to reflect current project structure and philosophy

Add comprehensive architecture documentation covering:
- Directory layout
- Stable contracts
- Request flow
- Module dependencies
- Page vs list detection
- Deployment models
- Demo fallback

Remove outdated plugin system documentation
Add new content system documentation
Add configuration documentation
Add context API documentation
Add hooks and plugins documentation
Add templates documentation
Add rendering documentation
Add development environment documentation
2026-02-05 23:30:44 +01:00

3.9 KiB

Configuration

Config Loading

createContext() in app/config.php handles the full bootstrap:

  1. Parse app/default/config.ini (framework defaults)
  2. If custom/config.ini exists, merge via array_replace_recursive (custom wins)
  3. Load global plugins using merged config
  4. Determine content directory
  5. Create Context object
  6. Fire Hook::CONTEXT_READY — plugins receive the merged $config array

INI Format

Standard PHP parse_ini_file with sections enabled:

[languages]
default = "en"
available = "no,en"

[plugins]
enabled = "languages"

[custom_section]
key = "value"

Built-in Config Keys

Section Key Type Default Purpose
languages default string "en" Default language (no URL prefix)
languages available string "no,en" Comma-separated ISO 639-1 codes
plugins enabled string "languages" Comma-separated plugin names (without .php)

All other sections are custom — define whatever your plugins need.

The custom/ Override System

This is the primary mechanism for using FolderWeb. The custom/ directory is the user layer:

custom/
  config.ini        # Merged over app/default/config.ini
  templates/        # Override by matching filename (e.g., base.php, page.php, list.php)
  styles/           # Served at /app/styles/*
  fonts/            # Served at /app/fonts/*
  assets/           # Served at document root (favicon.ico, robots.txt, etc.)
  languages/        # Merged over app/default/languages/*.ini
  plugins/
    global/         # Loaded alongside app/plugins/global/
    page/           # Reserved for future use

Override Resolution Order

Resource Lookup Fallback
Templates custom/templates/{name}.php app/default/templates/{name}.php
Styles custom/styles/* via /app/styles/* app/default/styles/* via /app/default-styles/*
Languages custom/languages/{lang}.ini merged over app/default/languages/{lang}.ini
Config custom/config.ini merged over app/default/config.ini
Plugins custom/plugins/{scope}/{name}.php app/plugins/{scope}/{name}.php

For plugins, custom takes priority: if both custom/plugins/global/foo.php and app/plugins/global/foo.php exist, the custom version loads.

For full template resolution details, see 06-templates.md "Template Resolution (canonical reference)".

Static Asset Routing

The router checks these locations before content routing:

  1. custom/assets/{requestPath} — served with mime_content_type()
  2. {contentDir}/{requestPath} — served with explicit MIME map for: css, jpg, jpeg, png, gif, webp, svg, pdf, woff, woff2, ttf, otf

/app/* requests are handled by static.php:

  • /app/styles/*custom/styles/*
  • /app/fonts/*custom/fonts/*
  • /app/assets/*custom/assets/*
  • /app/default-styles/*app/default/styles/*

Accessing Config in Plugins

The merged config array is passed to Hook::CONTEXT_READY:

Hooks::add(Hook::CONTEXT_READY, function(Context $ctx, array $config) {
    $val = $config['my_section']['my_key'] ?? 'default';
    $ctx->set('my_val', $val);
    return $ctx;
});

Config is not directly available in templates. Expose values via Hook::TEMPLATE_VARS:

Hooks::add(Hook::TEMPLATE_VARS, function(array $vars, Context $ctx) {
    global $config;
    $vars['siteTitle'] = $config['site']['title'] ?? 'My Site';
    return $vars;
});

Content Directory Resolution

In createContext():

  1. $_SERVER['DOCUMENT_ROOT'] is checked for content (>2 entries in scandir)
  2. If content exists → use document root as contentDir
  3. If empty → fall back to app/default/content/ (demo mode)

Production deployments set Apache's DocumentRoot to the content directory. The app/ and custom/ directories live outside the document root, accessed via Apache aliases.