Skip to content

Commit 94fb2f1

Browse files
"Added Twig because the original template system was a crime against humanity. Now I have to deal with this abomination."
1 parent 3ce8da4 commit 94fb2f1

File tree

5 files changed

+107
-0
lines changed

5 files changed

+107
-0
lines changed
File renamed without changes.
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
<?php
2+
/*
3+
* Spindle CMS
4+
* Copyright (c) 2025. All rights reserved.
5+
*
6+
* This file is part of the Spindle CMS project — a lightweight, modular PHP content framework derived from OpenCart.
7+
*
8+
* @license GNU General Public License v3.0 (GPL-3.0-or-later)
9+
* @link https://github.com/RandomCoderTinker/Spindle
10+
*/
11+
12+
namespace Spindle\System\Library\Template;
13+
14+
use Exception;
15+
use Twig\Environment;
16+
use Twig\Error\Error as TwigError;
17+
use Twig\Extension\DebugExtension;
18+
use Twig\Loader\ArrayLoader;
19+
use Twig\Loader\FilesystemLoader;
20+
21+
class Template
22+
{
23+
protected string $root;
24+
protected FilesystemLoader $loader;
25+
protected Environment $twig;
26+
protected array $paths = [];
27+
protected string $defaultPath = '';
28+
protected $session;
29+
30+
public function __construct ($session)
31+
{
32+
$this->session = $session;
33+
34+
$this->root = rtrim(MAIN_WEB_ROOT, '/'); // no trailing slash
35+
$this->loader = new FilesystemLoader('./', $this->root);
36+
37+
$this->twig = new Environment($this->loader, [
38+
'charset' => 'utf-8',
39+
'autoescape' => FALSE,
40+
'debug' => TRUE,
41+
'auto_reload' => TRUE,
42+
'cache' => DIR_CACHE . 'template/',
43+
]);
44+
45+
$this->twig->addExtension(new DebugExtension());
46+
47+
// Inject globals
48+
$token = $this->session->get('csrf_token');
49+
if (!$token) {
50+
$token = bin2hex(random_bytes(16));
51+
$this->session->lazySet('csrf_token', $token);
52+
}
53+
54+
$this->twig->addGlobal('HONEYPOT', '<div class="honeypot-field"><label for="input-telephone">Leave this field blank</label><input type="text" name="phone_number" id="input-telephone" autocomplete="off" tabindex="-1" /></div>');
55+
$this->twig->addGlobal('CSRF', '<input type="hidden" class="csrf_token" name="csrf_token" value="' . $token . '">');
56+
}
57+
58+
public function addPath (string $namespace, string $directory = ''): void
59+
{
60+
if ($directory === '') {
61+
$this->defaultPath = $namespace;
62+
} else {
63+
$this->paths[$namespace] = $directory;
64+
}
65+
}
66+
67+
public function render (string $filename, array $data = [], string $code = ''): string
68+
{
69+
try {
70+
$file = $this->resolveTemplatePath($filename);
71+
72+
if ($code) {
73+
$loader = new ArrayLoader([$file => $code]);
74+
$env = new Environment($loader, [
75+
'autoescape' => FALSE,
76+
'debug' => TRUE,
77+
]);
78+
$env->addExtension(new DebugExtension());
79+
80+
return $env->render($file, $data);
81+
}
82+
83+
return $this->twig->render($file, $data);
84+
} catch (TwigError $e) {
85+
throw new Exception('Template error in "' . $filename . '": ' . $e->getMessage());
86+
}
87+
}
88+
89+
private function resolveTemplatePath (string $filename): string
90+
{
91+
$file = $this->defaultPath . $filename . '.twig';
92+
$namespace = '';
93+
$parts = explode('/', $filename);
94+
95+
foreach ($parts as $part) {
96+
$namespace .= ($namespace ? '/' : '') . $part;
97+
98+
if (isset($this->paths[$namespace])) {
99+
$file = $this->paths[$namespace] . substr($filename, strlen($namespace) + 1) . '.twig';
100+
break;
101+
}
102+
}
103+
104+
return ltrim(substr($file, strlen($this->root)), '/');
105+
}
106+
107+
}

0 commit comments

Comments
 (0)