# 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: ```ini [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`: ```php 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`: ```php 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.