Imported templating engine from flash.moe.
This commit is contained in:
parent
d47cdf47ef
commit
5e35ccaf55
17 changed files with 317 additions and 19 deletions
|
@ -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');
|
||||||
|
|
|
@ -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()
|
||||||
|
);
|
||||||
|
|
|
@ -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";
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
50
src/Templating/Template.php
Normal file
50
src/Templating/Template.php
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
22
src/Templating/TemplateBlock.php
Normal file
22
src/Templating/TemplateBlock.php
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
56
src/Templating/TemplateContext.php
Normal file
56
src/Templating/TemplateContext.php
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
32
src/Templating/TemplateSelf.php
Normal file
32
src/Templating/TemplateSelf.php
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
32
src/Templating/TemplateVars.php
Normal file
32
src/Templating/TemplateVars.php
Normal 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
5
templates/errors/400.php
Normal 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
5
templates/errors/401.php
Normal 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
5
templates/errors/403.php
Normal 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
5
templates/errors/404.php
Normal 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
18
templates/errors/500.html
Normal 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
18
templates/errors/503.html
Normal 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>
|
9
templates/errors/master.php
Normal file
9
templates/errors/master.php
Normal 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
11
templates/index.php
Normal 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
11
templates/master.php
Normal 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>
|
Loading…
Reference in a new issue