Files
racket-chatgpt-bootstrap/private/Template.php
T
www-data 475765e31f 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/.
2026-05-26 12:50:26 +02:00

101 lines
2.7 KiB
PHP

<?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] ?? '',
);
}
}