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:
@@ -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] ?? '',
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user