Imported templating engine from flash.moe.

This commit is contained in:
flash 2023-01-09 00:02:23 +00:00
parent d47cdf47ef
commit 5e35ccaf55
17 changed files with 317 additions and 19 deletions

View file

@ -14,6 +14,7 @@ define('HAU_DIR_SOURCE', HAU_ROOT . '/src');
define('HAU_DIR_LIBRARIES', HAU_ROOT . '/lib'); define('HAU_DIR_LIBRARIES', HAU_ROOT . '/lib');
define('HAU_DIR_CONFIG', HAU_ROOT . '/config'); define('HAU_DIR_CONFIG', HAU_ROOT . '/config');
define('HAU_DIR_MIGRATIONS', HAU_ROOT . '/database'); define('HAU_DIR_MIGRATIONS', HAU_ROOT . '/database');
define('HAU_DIR_TEMPLATES', HAU_ROOT . '/templates');
define('HAU_NDX_PATH', HAU_DIR_LIBRARIES . '/index'); define('HAU_NDX_PATH', HAU_DIR_LIBRARIES . '/index');
define('HAU_NDX_PATH_DEV', HAU_DIR_LIBRARIES . '/index-dev'); define('HAU_NDX_PATH_DEV', HAU_DIR_LIBRARIES . '/index-dev');

View file

@ -3,7 +3,7 @@ namespace Hanyuu;
require_once __DIR__ . '/../hanyuu.php'; require_once __DIR__ . '/../hanyuu.php';
$request = \Index\Http\HttpRequest::fromRequest();
$hau->setUpHttp(); $hau->setUpHttp();
$hau->dispatchHttp($request); $hau->dispatchHttp(
\Index\Http\HttpRequest::fromRequest()
);

View file

@ -1,7 +1,6 @@
<?php <?php
namespace Hanyuu; namespace Hanyuu;
use RuntimeException;
use Index\Data\IDbConnection; use Index\Data\IDbConnection;
use Index\Data\DbTools; use Index\Data\DbTools;
use Index\Data\Migration\IDbMigrationRepo; use Index\Data\Migration\IDbMigrationRepo;
@ -11,17 +10,23 @@ use Index\Http\HttpFx;
use Index\Http\HttpRequest; use Index\Http\HttpRequest;
use Index\Routing\IRouter; use Index\Routing\IRouter;
use Hanyuu\Config\IConfig; use Hanyuu\Config\IConfig;
use Hanyuu\Templating\TemplateContext;
class HanyuuContext { class HanyuuContext {
private const DB_INIT = 'SET SESSION time_zone = \'+00:00\', sql_mode = \'STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION\';'; private const DB_INIT = 'SET SESSION time_zone = \'+00:00\', sql_mode = \'STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION\';';
private IConfig $config; private IConfig $config;
private IDbConnection $dbConn; private IDbConnection $dbConn;
private ?TemplateContext $tpl = null;
public function __construct(IConfig $config) { public function __construct(IConfig $config) {
$this->config = $config; $this->config = $config;
} }
public function getSiteName(): string {
return $this->config->getValue('site:name', IConfig::T_STR, 'Hanyuu');
}
public function connectDb(?IDbConnection $dbConn = null): void { public function connectDb(?IDbConnection $dbConn = null): void {
$dbConn ??= DbTools::create($this->config->getValue('database:dsn', IConfig::T_STR, 'null')); $dbConn ??= DbTools::create($this->config->getValue('database:dsn', IConfig::T_STR, 'null'));
$dbConn->execute(self::DB_INIT); $dbConn->execute(self::DB_INIT);
@ -45,6 +50,23 @@ class HanyuuContext {
return new FsDbMigrationRepo(HAU_DIR_MIGRATIONS); return new FsDbMigrationRepo(HAU_DIR_MIGRATIONS);
} }
public function getTemplating(): TemplateContext {
if($this->tpl === null) {
$this->tpl = new TemplateContext(HAU_DIR_TEMPLATES);
$this->tpl->setGlobal('hau', $this);
}
return $this->tpl;
}
public function renderTemplate(...$args): string {
return $this->getTemplating()->render(...$args);
}
public function getRouter(): IRouter {
return $this->router->getRouter();
}
public function setUpHttp(): void { public function setUpHttp(): void {
$this->router = new HttpFx; $this->router = new HttpFx;
$this->router->use('/', function($response) { $this->router->use('/', function($response) {
@ -60,33 +82,29 @@ class HanyuuContext {
} }
private function registerErrorPages(): void { private function registerErrorPages(): void {
/*$this->router->addErrorHandler(400, function($response) { $this->router->addErrorHandler(400, function($response) {
$response->setContent(Template::renderRaw('errors.400')); $response->setContent($this->renderTemplate('errors/400'));
});
$this->router->addErrorHandler(401, function($response) {
$response->setContent($this->renderTemplate('errors/401'));
}); });
$this->router->addErrorHandler(403, function($response) { $this->router->addErrorHandler(403, function($response) {
$response->setContent(Template::renderRaw('errors.403')); $response->setContent($this->renderTemplate('errors/403'));
}); });
$this->router->addErrorHandler(404, function($response) { $this->router->addErrorHandler(404, function($response) {
$response->setContent(Template::renderRaw('errors.404')); $response->setContent($this->renderTemplate('errors/404'));
}); });
$this->router->addErrorHandler(500, function($response) { $this->router->addErrorHandler(500, function($response) {
$response->setContent(file_get_contents(MSZ_TEMPLATES . '/500.html')); $response->setContent(file_get_contents(HAU_DIR_TEMPLATES . '/errors/500.html'));
}); });
$this->router->addErrorHandler(503, function($response) { $this->router->addErrorHandler(503, function($response) {
$response->setContent(file_get_contents(MSZ_TEMPLATES . '/503.html')); $response->setContent(file_get_contents(HAU_DIR_TEMPLATES . '/errors/503.html'));
});*/ });
} }
private function registerHttpRoutes(): void { private function registerHttpRoutes(): void {
$this->router->get('/', function($response, $request) { $this->router->get('/', function($response, $request) {
$siteName = $this->config->getValue('site:name', IConfig::T_STR, 'Hanyuu'); return 503;
return "<!doctype html>\r\n"
. "<title>{$siteName}</title>\r\n"
. "<center>\r\n"
. " <h1>Under Construction</h1>\r\n"
. " <img src=\"//static.flash.moe/images/me-tan-2.png\" alt=\"\"/>\r\n"
. "</center>\r\n";
}); });
} }
} }

View file

@ -0,0 +1,50 @@
<?php
namespace Hanyuu\Templating;
use RuntimeException;
class Template {
public function __construct(
private TemplateContext $context,
private TemplateVars $vars,
private string $path
) {}
public function setVar(string $name, mixed $value): void {
$this->vars->setVar($name, $value);
}
public function removeVar(string $name): void {
$this->vars->removeVar($name);
}
public function render(array $vars = [], ?TemplateSelf $self = null): string {
if(!is_file($this->path))
throw new RuntimeException('Template file does not exist: ' . $this->path);
$self = new TemplateSelf($this->context->getFunctions() + $vars + $this->vars->getVars(), $self);
$self->tplPath = $this->path;
$self->var = fn(string $name, mixed $default = null) => $this->vars->getVar($name, $default);
$self->setVar = fn(string $name, mixed $value) => $this->vars->setVar($name, $value);
$self->remVar = fn(string $name) => $this->vars->removeVar($name);
$self->block = fn(string $name, mixed $contents) => $this->context->setBlock($name, new TemplateBlock($self, $contents));
$self->include = fn(string $name) => $this->context->render($name, [], $self);
$self->ctx = $this->context;
$self->extends = fn(string $name) => $self->extends = $name;
ob_start();
(static function() use ($self) { include $self->tplPath; })();
$buffer = ob_get_contents();
ob_end_clean();
if(is_string($self->extends)) {
if(!empty($buffer))
throw new RuntimeException('You cannot output from templates that extend another.');
$buffer = $this->context->render($self->extends, [], $self);
}
return $buffer;
}
}

View file

@ -0,0 +1,22 @@
<?php
namespace Hanyuu\Templating;
use Closure;
class TemplateBlock {
public function __construct(
private object $self,
private mixed $body
) {}
public function render(): string {
if(is_callable($this->body)) {
ob_start();
($this->body)($this->self);
$body = ob_get_contents();
ob_end_clean();
} else $body = strval($this->body);
return $body;
}
}

View file

@ -0,0 +1,56 @@
<?php
namespace Hanyuu\Templating;
use Closure;
class TemplateContext {
private const EXT = '.php';
private string $pathFormat = '%s' . self::EXT;
private TemplateVars $vars;
private array $blocks = [];
private array $functions = [];
public function __construct($path = '') {
if(!empty($path))
$this->pathFormat = rtrim($path, '\\/') . DIRECTORY_SEPARATOR . '%s' . self::EXT;
$this->vars = new TemplateVars;
$this->defineFunction('global', $this->vars->getVar(...));
$this->defineFunction('getBlock', $this->getBlock(...));
$this->defineFunction('x', fn(string $string) => htmlspecialchars($string, ENT_QUOTES, 'utf-8', true));
}
public function setGlobal(string $name, mixed $value): void {
$this->vars->setVar($name, $value);
}
public function removeGlobal(string $name): void {
$this->vars->removeVar($name);
}
public function defineFunction(string $name, Closure|callable $function): void {
$this->functions[$name] = $function;
}
public function getFunctions(): array {
return $this->functions;
}
public function create(string $path): Template {
return new Template($this, new TemplateVars($this->vars), sprintf($this->pathFormat, $path));
}
public function exists(string $path): bool {
return is_file(sprintf($this->pathFormat, $path));
}
public function render(string $path, array $vars = [], ?TemplateSelf $self = null): string {
return $this->create($path)->render($vars, $self);
}
public function getBlock(string $name, mixed $default = ''): string {
return ($this->blocks[$name] ?? new TemplateBlock(new \stdClass, $default))->render();
}
public function setBlock(string $name, TemplateBlock $block): void {
$this->blocks[$name] = $block;
}
}

View file

@ -0,0 +1,32 @@
<?php
namespace Hanyuu\Templating;
class TemplateSelf {
public function __construct(
private array $members = [],
?TemplateSelf $inherit = null
) {
if($inherit !== null)
$this->members += $inherit->members;
}
public function __get(string $name): mixed {
return $this->members[$name];
}
public function __set(string $name, mixed $value): void {
$this->members[$name] = $value;
}
public function __isset(string $name): bool {
return isset($this->members[$name]);
}
public function __unset(string $name): void {
unset($this->members[$name]);
}
public function __call(string $name, array $args): mixed {
return ($this->members[$name])(...$args);
}
}

View file

@ -0,0 +1,32 @@
<?php
namespace Hanyuu\Templating;
class TemplateVars {
public function __construct(
private ?TemplateVars $parent = null,
private array $vars = []
) {}
public function getVars(): array {
$vars = $this->vars;
if($this->parent !== null)
$vars += $this->parent->getVars();
return $vars;
}
public function getVar(string $name, mixed $default = null): mixed {
if(isset($this->vars[$name]))
return $this->vars[$name];
if($this->parent !== null)
return $this->parent->getVar($name, $default);
return $default;
}
public function setVar(string $name, mixed $value): void {
$this->vars[$name] = $value;
}
public function removeVar(string $name): void {
unset($this->vars[$name]);
}
}

5
templates/errors/400.php Normal file
View file

@ -0,0 +1,5 @@
<?php
$self->extends('errors/master');
$self->http_error_title = 'Error 400';
$self->http_error_desc = 'Request is malformed.';

5
templates/errors/401.php Normal file
View file

@ -0,0 +1,5 @@
<?php
$self->extends('errors/master');
$self->http_error_title = 'Error 401';
$self->http_error_desc = 'You must be authorised to be here.';

5
templates/errors/403.php Normal file
View file

@ -0,0 +1,5 @@
<?php
$self->extends('errors/master');
$self->http_error_title = 'Error 403';
$self->http_error_desc = 'You are not authorised to be here.';

5
templates/errors/404.php Normal file
View file

@ -0,0 +1,5 @@
<?php
$self->extends('errors/master');
$self->http_error_title = 'Error 404';
$self->http_error_desc = 'Could not find what you were looking for.';

18
templates/errors/500.html Normal file
View file

@ -0,0 +1,18 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8"/>
<title>Error 500</title>
<style type="text/css">
body {
font: 12px/20px Verdana, Geneva, Arial, Helvetica, sans-serif;
}
</style>
</head>
<body>
<center>
<h1>Error 500</h1>
<p>Something went horrendously wrong! Please <a href="//fii.moe/bugs">report</a> this if the error persists.</p>
</center>
</body>
</html>

18
templates/errors/503.html Normal file
View file

@ -0,0 +1,18 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8"/>
<title>Error 503</title>
<style type="text/css">
body {
font: 12px/20px Verdana, Geneva, Arial, Helvetica, sans-serif;
}
</style>
</head>
<body>
<center>
<h1>Under Construction</h1>
<img src="//static.flash.moe/images/me-tan-2.png" alt="">
</center>
</body>
</html>

View file

@ -0,0 +1,9 @@
<?php
$self->extends('master');
$self->block('body', function($self) {
?>
<h1><?=($self->http_error_title ?? 'Unknown Error');?></h1>
<p><?=($self->http_error_desc ?? 'No additional information is available.');?></p>
<?php
});

11
templates/index.php Normal file
View file

@ -0,0 +1,11 @@
<?php
$self->extends('master');
$self->block('body', function($self) {
?>
<center>
<h1>Under Construction</h1>
<img src="//static.flash.moe/images/me-tan-2.png" alt="">
</center>
<?php
});

11
templates/master.php Normal file
View file

@ -0,0 +1,11 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<title><?=$self->hau->getSiteName();?></title>
</head>
<body>
<?=$self->getBlock('body');?>
</body>
</html>