Move rendering into private templates

Add an explicit template renderer with HTML views and partials for the app, bootstrap, package, and catalog pages. Move shared reporting setup into config/reporting.php and relocate stylesheet assets under css/.
This commit is contained in:
www-data
2026-05-26 12:50:26 +02:00
parent 2f2e8869d6
commit 475765e31f
55 changed files with 2328 additions and 1175 deletions
+100
View File
@@ -0,0 +1,100 @@
<?php
/*
* Small explicit template renderer.
*
* Templates use escaped {{name}} placeholders and raw {{{name}}} placeholders.
* Values are provided as an array, so templates do not depend on globals.
*/
final class RacketSandboxTemplate
{
const DATA_DELIMITER = "\n===\n";
public static function renderFile($template, $values)
{
$parts = self::readFile($template);
return self::renderString($parts['html'], $values);
}
public static function dataFile($template)
{
$parts = self::readFile($template);
$json = trim($parts['data']);
if ($json === '') {
return array();
}
$data = json_decode($json, true);
if (!is_array($data)) {
throw new RuntimeException('Template data is invalid JSON: ' . $template);
}
return $data;
}
public static function translationsFile($template)
{
$data = self::dataFile($template);
$translations = $data['translations'] ?? array();
if (!is_array($translations)) {
throw new RuntimeException('Template translations must be an object: ' . $template);
}
return $translations;
}
public static function renderString($source, $values)
{
$source = preg_replace_callback('/\{\{\{\s*([A-Za-z0-9_.-]+)\s*\}\}\}/', function ($match) use ($values) {
return self::value($values, $match[1]);
}, $source);
return preg_replace_callback('/\{\{\s*([A-Za-z0-9_.-]+)\s*\}\}/', function ($match) use ($values) {
return self::escape(self::value($values, $match[1]));
}, $source);
}
public static function escape($value)
{
return htmlspecialchars((string)$value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}
private static function value($values, $name)
{
if (!is_array($values) || !array_key_exists($name, $values)) {
return '';
}
return (string)$values[$name];
}
private static function readFile($template)
{
$file = __DIR__ . '/views/' . ltrim((string)$template, '/');
if (!is_file($file)) {
throw new RuntimeException('Template not found: ' . $template);
}
$source = file_get_contents($file);
if ($source === false) {
throw new RuntimeException('Template could not be read: ' . $template);
}
if (strpos($source, "===\n") === 0) {
$chunks = array('', substr($source, 4));
} else {
$chunks = explode(self::DATA_DELIMITER, $source, 2);
}
return array(
'html' => $chunks[0],
'data' => $chunks[1] ?? '',
);
}
}