twig = new Environment($loader, [ 'cache' => $cacheDir ?? false, 'auto_reload' => (getenv('APP_ENV') ?: 'production') !== 'production', 'strict_variables' => false, 'autoescape' => 'html', ]); // fmt_days(x) — same shape used by the previous PHP templates and the // CapacityCalculator: 0 → "0", whole numbers → integer, halves → x.5. $this->twig->addFunction(new TwigFunction( 'fmt_days', static function (mixed $x): string { $n = (float) $x; if (abs($n - round($n)) < 1e-9) { return (string) (int) round($n); } return number_format($n, 1); } )); // fmt_rtb(x) — two decimals, e.g. 0.05. $this->twig->addFunction(new TwigFunction( 'fmt_rtb', static fn(mixed $x): string => number_format((float) $x, 2, '.', '') )); // pretty_json(raw) — used by audit/index.twig for the diff
blocks.
$this->twig->addFunction(new TwigFunction(
'pretty_json',
static function (?string $raw): string {
if ($raw === null || $raw === '') {
return '';
}
try {
$v = json_decode($raw, true, 64, JSON_THROW_ON_ERROR);
} catch (\JsonException) {
return $raw;
}
return (string) (json_encode(
$v,
JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES
) ?: $raw);
}
));
// query_string(filters, drop, extra) — rebuilds a query string while
// dropping one key and merging extras. Used by /audit pagination.
$this->twig->addFunction(new TwigFunction(
'query_string',
/**
* @param array $filters
* @param array $extra
*/
static function (array $filters, string $drop, array $extra = []): string {
$params = array_filter(
array_merge($filters, $extra),
static fn($v) => $v !== '' && $v !== null
);
unset($params[$drop]);
return $params === [] ? '' : '?' . http_build_query($params);
}
));
}
/**
* Render a view and return the HTML.
*
* @param string $name view name; "home" → views/home.twig
* @param array $data
* @param string|null $layout retained for back-compat; ignored
* by Twig (templates use {% extends %})
*/
public function render(string $name, array $data = [], ?string $layout = 'layout'): string
{
unset($layout);
return $this->twig->render($name . '.twig', $data);
}
/** @param array $data */
public function renderRaw(string $name, array $data): string
{
return $this->twig->render($name . '.twig', $data);
}
/** Direct access to the Twig env (rarely needed; mostly for tests). */
public function twig(): Environment
{
return $this->twig;
}
}
/** Escape for HTML. Kept so legacy callers still resolve; Twig auto-escapes. */
function e(mixed $v): string
{
if ($v === null) {
return '';
}
return htmlspecialchars((string) $v, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}