Table of Contents
Limits and Non-behaviour
The generated reference says what each class does. This page records the things it deliberately does not do, and the bounds that are fixed in code rather than configuration.
1. Configuration is a hard precondition, not a fallback
`lib/main.php` checks for `config.php` before the framework is allowed to exist. If it is missing and `install/` is present, the request is handed to the installer; if the installer is gone too, the request ends there.
The practical consequence: the application cannot boot in a degraded or read-only mode for inspection. There is no “load what you can” path. A deployment either has a complete `config.php` or does not run.
2. The supported-language set is seven codes, hard-coded in five places
The whitelist is literally `en, ar, zh, fr, ru, es, id`, and it appears five times in the tree:
| Location | Form |
| — | — |
| `lib/main.php:129` | `$validLocales = ['en', 'ar', …]` |
| `lib/controller/TranslationController.php:40` | `in_array($_GET['lang'], ['en', 'ar', …])` |
| `lib/controller/LocaleController.php:28` | fallback when `available_locales()` is absent |
| `lib/dao/LanguageDao.php:13` | documented in the class docblock |
| `install/include/setup.php:811` | `$langOrder` for the installer's seed data |
Adding a language therefore means five coordinated edits, and the installer's seed order can drift from the runtime whitelist.
Any locale with a region subtag is rejected. The sanitiser in `lib/main.php` is `preg_replace('/[^a-z]{2}/', '', strtolower($code))`, which only removes *runs of two or more* non-`a-z` characters. A single separator is left alone, so the value reaches the whitelist unchanged and fails:
| Input | After sanitiser | Result |
| — | — | — |
| `en` | `en` | accepted |
| `EN` | `en` | accepted |
| `pt-br` | `pt-br` | rejected — `-` is a run of one |
| `pt_BR` | `pt_br` | rejected |
| `zh-Hans` | `zh-hans` | rejected |
| `en-us` | `en-us` | rejected |
So `pt-BR` cannot be expressed, and adding it would need a real normalisation step rather than a longer whitelist.
3. One URL style at a time, decided by a single setting
`Dispatcher::dispatch()` branches on one configuration value, `rewrite_status`:
- `'yes'` → `handleSeoFriendlyUrl()`, matching the 13 patterns in the route table after stripping an optional locale prefix.
- anything else → `handleQueryStringUrl()`, serving `?pg=`, `?p=`, `?a=`, `?cat=`, `?tag=`, `?search=`, `?q=`.
Both styles are always present in the code; only one is reachable per configuration. A new page type must be added to both the route table in `Bootstrap::defineRoutingRules()` and the query-parameter list in `lib/main.php`, or it will work in one URL style and 404 in the other.
`?download=` is the documented exception: it is handled before the SEO branch specifically so downloads survive either permalink setting.
4. `config.php` is required, and `config.sample.php` is the only template
There is no environment-variable fallback for the database credentials. `.env` is loaded through Dotenv when present, but the application's own settings live in `config.php`, which is derived from `config.sample.php`. See Requirements.
5. FrontHelper fails quietly
This is the most consequential non-behaviour in the codebase, and it is the facade's own documented decision.
`Scriptlog\Core\FrontHelper` is a deprecated facade over `Scriptlog\Service\FrontService`. Every data-access method delegates to the `FrontService` instance registered via `FrontHelper::setFrontService()`. When no service is registered, the methods return `null` or an empty array.
The inline SQL that used to back those methods was removed because it drifted from `FrontService`, used string interpolation, and one path was mysqli-only and broke on PDO. The fix was correct, but the chosen failure mode is silence. A theme calling `FrontHelper` before `Bootstrap` has run gets an empty result and renders a blank region with no error anywhere.
6. `tests` are extensive but not name-mapped to units
83 types have a test whose name suggests it covers them. 113 do not:
| Layer | Types without a matching test name |
| — | — |
| core | 75 |
| controller | 17 |
| dao | 9 |
| model | 9 |
| service | 4 |
This is a name-based heuristic, not proof of a coverage gap: a test may cover a class under a different name, or through an integration test. But the shape is informative — `service` is the best-covered business layer by name (only 4 unmatched) while `core` is by far the largest gap. With 2,649 methods in the suite, the project clearly tests heavily; what it does not do is maintain a one-test-per-class correspondence.
7. `@todo` inventory is empty
There are zero `@todo` annotations in first-party code. The one hit in an earlier pass was inside a vendored HTMLPurifier shim. Either the backlog is tracked outside the source, or it is tracked in issues; it is not in the code.
8. Known inconsistencies, recorded rather than hidden
- `Scriptlog\Service\FrontService::getGalleries(int $start, int $limit)` interpolates its arguments into SQL as `LIMIT “ . $start . ”, “ . $limit`. Both parameters are `int`-typed, so PHP coerces them and this is not currently injectable — but it is precisely the string-interpolation pattern the `FrontHelper` docblock cites as a reason for removing the old inline queries. It is the last place in the tree that still does it that way.
- `Scriptlog\Controller\DownloadController` declares `private $view;` with a bare `/** @deprecated */` and never reads it (`$this→view` appears zero times in the file).
