475765e31f
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/.
101 lines
2.7 KiB
PHP
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] ?? '',
|
|
);
|
|
}
|
|
}
|