Use config library and moved templates to Sasae format.

This commit is contained in:
flash 2023-10-20 21:38:13 +00:00
parent 514e0068a2
commit db6821dedf
37 changed files with 228 additions and 515 deletions

1
.gitignore vendored
View file

@ -4,5 +4,6 @@
/public/robots.txt
/lib/index-dev
/.debug
/config/config.cfg
/config/config.ini
/vendor

View file

@ -7,7 +7,8 @@
"chillerlan/php-qrcode": "^4.3",
"symfony/mailer": "^6.0",
"matomo/device-detector": "^6.1",
"sentry/sdk": "^3.5"
"sentry/sdk": "^3.5",
"flashwave/syokuhou": "dev-master"
},
"autoload": {
"classmap": [

41
composer.lock generated
View file

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "d531fced85cb9df6688044da4d0047d2",
"content-hash": "079d926545b1fc9ef6fafe2f4e72caef",
"packages": [
{
"name": "chillerlan/php-qrcode",
@ -445,6 +445,45 @@
"homepage": "https://railgun.sh/sasae",
"time": "2023-08-24T23:24:45+00:00"
},
{
"name": "flashwave/syokuhou",
"version": "dev-master",
"source": {
"type": "git",
"url": "https://git.flash.moe/flash/syokuhou.git",
"reference": "b3470ad8605b0484294c73cd95be6e7ba4551e5a"
},
"require": {
"flashwave/index": "dev-master",
"php": ">=8.2"
},
"require-dev": {
"phpstan/phpstan": "^1.10",
"phpunit/phpunit": "^10.4"
},
"default-branch": true,
"type": "library",
"autoload": {
"psr-4": {
"Syokuhou\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"bsd-3-clause-clear"
],
"authors": [
{
"name": "flashwave",
"email": "packagist@flash.moe",
"homepage": "https://flash.moe",
"role": "mom"
}
],
"description": "Configuration library for PHP.",
"homepage": "https://railgun.sh/syokuhou",
"time": "2023-10-20T21:26:38+00:00"
},
{
"name": "guzzlehttp/promises",
"version": "2.0.1",

View file

@ -0,0 +1,3 @@
database:dsn mariadb://username:password@:unix:/database?socket=/var/run/mysqld/mysqld.sock&charset=utf8mb4
site:name Hanyuu

View file

@ -1,5 +0,0 @@
[database]
dsn = "mariadb://username:password@:unix:/database?socket=/var/run/mysqld/mysqld.sock&charset=utf8mb4"
[site]
name = Hanyuu

View file

@ -4,8 +4,7 @@ namespace Hanyuu;
use Index\Autoloader;
use Index\Environment;
use Index\Data\DbTools;
use Hanyuu\Config\IConfig;
use Hanyuu\Config\ArrayConfig;
use Syokuhou\SharpConfig;
define('HAU_STARTUP', microtime(true));
define('HAU_ROOT', __DIR__);
@ -41,9 +40,9 @@ set_exception_handler(function(\Throwable $ex) {
die('<h2>Hanyuu is sad.</h2>');
});
$cfg = ArrayConfig::open(HAU_DIR_CONFIG . '/config.ini');
$cfg = SharpConfig::fromFile(HAU_DIR_CONFIG . '/config.cfg');
$dbc = DbTools::create($cfg->getValue('database:dsn', IConfig::T_STR, 'null'));
$dbc = DbTools::create($cfg->getString('database:dsn', 'null'));
$dbc->execute('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\';');
$hau = new HanyuuContext($cfg, $dbc);

View file

@ -3,7 +3,27 @@ namespace Hanyuu;
require_once __DIR__ . '/../hanyuu.php';
$hau->setUpHttp();
$hau->dispatchHttp(
\Index\Http\HttpRequest::fromRequest()
);
set_exception_handler(function(\Throwable $ex) {
http_response_code(500);
ob_clean();
if(HAU_DEBUG) {
header('Content-Type: text/plain; charset=utf-8');
echo (string)$ex;
exit;
}
header('Content-Type: text/html; charset=utf-8');
//echo file_get_contents(HAU_DIR_PUBLIC . '/error-500.html');
echo '500';
exit;
});
if(file_exists(HAU_ROOT . '/.migrating')) {
http_response_code(503);
//echo file_get_contents(HAU_DIR_PUBLIC . '/error-503.html');
echo '503';
exit;
}
$hau->createRouting()->dispatch();

View file

@ -2,10 +2,10 @@
namespace Hanyuu\Auth;
use Index\Routing\IRouter;
use Syokuhou\IConfig;
use Hanyuu\HanyuuContext;
use Hanyuu\Auth\Auth;
use Hanyuu\Auth\IAuthLogin;
use Hanyuu\Config\IConfig;
use Hanyuu\Users\IUserInfo;
use Hanyuu\Users\IUsers;
use Hanyuu\Users\UserNotFoundException;
@ -58,7 +58,7 @@ class AuthRoutes {
}
private function getLoginCookieName(): string {
return $this->config->getValue('login_cookie', IConfig::T_STR, 'hau_login');
return $this->config->getString('login_cookie', 'hau_login');
}
private function destroyLoginSession($response): void {

View file

@ -1,78 +0,0 @@
<?php
namespace Hanyuu\Config;
use InvalidArgumentException;
class ArrayConfig implements IConfig {
private const SCOPE_CHAR = ':';
private const SCANNER_MODE = INI_SCANNER_TYPED;
private const TEST_VALUE = 'X-Config-Test';
private array $cache = [];
private array $exists = [];
public function __construct(
private array $config
) {}
public function scopeTo(string $prefix): IConfig {
return new ScopedConfig($this, $prefix);
}
private function getRaw(string $name): mixed {
$parts = array_reverse(explode(self::SCOPE_CHAR, $name));
$value = $this->config;
while(count($parts) > 1) {
$part = array_pop($parts);
if(!array_key_exists($part, $value))
break;
$value = $value[$part];
}
if($parts[0] === '')
return $value;
return $value[$parts[0]] ?? null;
}
public function getValue(string $name, string $type = IConfig::T_ANY, $default = null): mixed {
if(array_key_exists($name, $this->cache))
$value = $this->cache[$name];
else {
$this->cache[$name] = $value = $this->getRaw($name);
$this->exists[$name] = $value !== null;
}
if($type !== IConfig::T_ANY && CfgTools::type($value) !== $type)
$value = null;
return $value ?? $default ?? CfgTools::default($type);
}
public function hasValue(string $name): bool {
if(array_key_exists($name, $this->exists))
return $this->exists[$name];
$exists = array_key_exists($name, $this->cache)
|| $this->getRaw($name) !== null;
return $this->exists[$name] = $exists;
}
public static function open(string $path): self {
$parsed = parse_ini_file($path, true, self::SCANNER_MODE);
if($parsed === false)
throw new InvalidArgumentException('Unable to parse configuration file in $path.');
return new static($parsed);
}
public static function from(string $string): self {
$parsed = parse_ini_string($string, true, self::SCANNER_MODE);
if($parsed === false)
throw new InvalidArgumentException('Unable to parse configuration string in $string.');
return new static($parsed);
}
}

View file

@ -1,27 +0,0 @@
<?php
namespace Hanyuu\Config;
final class CfgTools {
public static function type($value): string {
return gettype($value);
}
public static function isValidType(string $type): bool {
return $type === IConfig::T_ARR
|| $type === IConfig::T_BOOL
|| $type === IConfig::T_INT
|| $type === IConfig::T_STR;
}
private const DEFAULTS = [
IConfig::T_ANY => null,
IConfig::T_STR => '',
IConfig::T_INT => 0,
IConfig::T_BOOL => false,
IConfig::T_ARR => [],
];
public static function default(string $type) {
return self::DEFAULTS[$type] ?? null;
}
}

View file

@ -1,14 +0,0 @@
<?php
namespace Hanyuu\Config;
interface IConfig {
public const T_ANY = '';
public const T_STR = 'string';
public const T_INT = 'integer';
public const T_BOOL = 'boolean';
public const T_ARR = 'array';
public function scopeTo(string $prefix): IConfig;
public function getValue(string $name, string $type = IConfig::T_ANY, $default = null): mixed;
public function hasValue(string $name): bool;
}

View file

@ -1,37 +0,0 @@
<?php
namespace Hanyuu\Config;
use InvalidArgumentException;
class ScopedConfig implements IConfig {
private IConfig $config;
private string $prefix;
private const SCOPE_CHAR = ':';
public function __construct(IConfig $config, string $prefix) {
if($prefix === '')
throw new InvalidArgumentException('$prefix may not be empty.');
if(!str_ends_with($prefix, self::SCOPE_CHAR))
$prefix .= self::SCOPE_CHAR;
$this->config = $config;
$this->prefix = $prefix;
}
private function getName(string $name): string {
return $this->prefix . $name;
}
public function scopeTo(string $prefix): IConfig {
return $this->config->scopeTo($this->getName($prefix));
}
public function getValue(string $name, string $type = IConfig::T_ANY, $default = null): mixed {
return $this->config->getValue($this->getName($name), $type, $default);
}
public function hasValue(string $name): bool {
return $this->config->hasValue($this->getName($name));
}
}

View file

@ -1,6 +1,7 @@
<?php
namespace Hanyuu;
use Index\Environment;
use Index\Data\IDbConnection;
use Index\Data\Migration\IDbMigrationRepo;
use Index\Data\Migration\DbMigrationManager;
@ -8,11 +9,11 @@ use Index\Data\Migration\FsDbMigrationRepo;
use Index\Http\HttpFx;
use Index\Http\HttpRequest;
use Index\Routing\IRouter;
use Sasae\SasaeEnvironment;
use Syokuhou\IConfig;
use Hanyuu\Auth\Auth;
use Hanyuu\Auth\AuthRoutes;
use Hanyuu\Auth\Db\DbAuth;
use Hanyuu\Config\IConfig;
use Hanyuu\Templating\TemplateContext;
use Hanyuu\Users\IUsers;
use Hanyuu\Users\Db\DbUsers;
@ -21,17 +22,15 @@ class HanyuuContext {
private IDbConnection $dbConn;
private IUsers $users;
private Auth $auth;
private ?TemplateContext $tpl = null;
private ?IRouter $router = null;
private ?SasaeEnvironment $templating = null;
private SiteInfo $siteInfo;
public function __construct(IConfig $config, IDbConnection $dbConn) {
$this->config = $config;
$this->dbConn = $dbConn;
$this->users = new DbUsers($dbConn);
}
public function getSiteName(): string {
return $this->config->getValue('site:name', IConfig::T_STR, 'Hanyuu');
$this->siteInfo = new SiteInfo($config->scopeTo('site'));
}
public function getUsers(): IUsers {
@ -55,13 +54,23 @@ class HanyuuContext {
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);
public function getTemplating(): SasaeEnvironment {
if($this->templating === null) {
$isDebug = Environment::isDebug();
$this->templating = new SasaeEnvironment(
HAU_DIR_TEMPLATES,
cache: null,//$isDebug ? null : ['Hanyuu', GitInfo::hash(true)],
debug: $isDebug,
);
$this->templating->addGlobal('globals', [
'siteInfo' => $this->siteInfo,
//'assetsInfo' => AssetsInfo::fromCurrent(),
]);
}
return $this->tpl;
return $this->templating;
}
public function renderTemplate(...$args): string {
@ -76,52 +85,16 @@ class HanyuuContext {
return $this->auth;
}
public function getRouter(): IRouter {
return $this->router->getRouter();
}
public function createRouting(): RoutingContext {
$routingCtx = new RoutingContext($this->getTemplating());
$routingCtx->registerDefaultErrorPages();
public function setUpHttp(): void {
$this->router = new HttpFx;
$this->router->use('/', function($response) {
$response->setPoweredBy('Hanyuu');
});
$this->registerErrorPages();
$this->registerHttpRoutes();
}
public function dispatchHttp(?HttpRequest $request = null): void {
$this->router->dispatch($request);
}
private function registerErrorPages(): void {
$this->router->addErrorHandler(400, function($response) {
$response->setContent($this->renderTemplate('errors/400'));
});
$this->router->addErrorHandler(401, function($response) {
$response->setContent($this->renderTemplate('errors/401'));
});
$this->router->addErrorHandler(403, function($response) {
$response->setContent($this->renderTemplate('errors/403'));
});
$this->router->addErrorHandler(404, function($response) {
$response->setContent($this->renderTemplate('errors/404'));
});
$this->router->addErrorHandler(500, function($response) {
$response->setContent(file_get_contents(HAU_DIR_TEMPLATES . '/errors/500.html'));
});
$this->router->addErrorHandler(503, function($response) {
$response->setContent(file_get_contents(HAU_DIR_TEMPLATES . '/errors/503.html'));
});
}
private function registerHttpRoutes(): void {
$this->router->get('/', function($response, $request) {
$routingCtx->getRouter()->get('/', function($response, $request) {
return 503;
});
$this->router->get('/coffee', function() { return 418; });
new AuthRoutes($routingCtx->getRouter(), $this, $this->config->scopeTo('auth'));
new AuthRoutes($this->router, $this, $this->config->scopeTo('auth'));
return $routingCtx;
}
}

39
src/RoutingContext.php Normal file
View file

@ -0,0 +1,39 @@
<?php
namespace Hanyuu;
use Index\Http\HttpFx;
use Index\Http\HttpRequest;
use Index\Routing\IRouter;
use Index\Routing\IRouteHandler;
use Sasae\SasaeEnvironment;
class RoutingContext {
private HttpFx $router;
private SasaeEnvironment $templating;
public function __construct(SasaeEnvironment $templating) {
$this->templating = $templating;
$this->router = new HttpFx;
$this->router->use('/', fn($resp) => $resp->setPoweredBy('Hanyuu'));
}
public function getRouter(): IRouter {
return $this->router;
}
public function registerDefaultErrorPages(): void {
$this->router->addErrorHandler(401, fn($resp) => $resp->setContent($this->templating->rendering('errors/401')));
$this->router->addErrorHandler(403, fn($resp) => $resp->setContent($this->templating->rendering('errors/403')));
$this->router->addErrorHandler(404, fn($resp) => $resp->setContent($this->templating->rendering('errors/404')));
$this->router->addErrorHandler(500, fn($resp) => $resp->setContent(file_get_contents(HAU_DIR_TEMPLATES . '/errors/500.html')));
$this->router->addErrorHandler(503, fn($resp) => $resp->setContent(file_get_contents(HAU_DIR_TEMPLATES . '/errors/503.html')));
}
public function register(IRouteHandler $handler): void {
$this->router->register($handler);
}
public function dispatch(?HttpRequest $request = null): void {
$this->router->dispatch($request);
}
}

14
src/SiteInfo.php Normal file
View file

@ -0,0 +1,14 @@
<?php
namespace Hanyuu;
use Syokuhou\IConfig;
class SiteInfo {
public function __construct(
private IConfig $config
) {}
public function getName(): string {
return $this->config->getString('name', 'Hanyuu');
}
}

View file

@ -1,53 +0,0 @@
<?php
namespace Hanyuu\Templating;
use RuntimeException;
// this entire thing needs to be redone and integrated into Index
// take this project as an opportunity to do that
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

@ -1,22 +0,0 @@
<?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

@ -1,56 +0,0 @@
<?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

@ -1,32 +0,0 @@
<?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

@ -1,32 +0,0 @@
<?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]);
}
}

View file

@ -1,12 +1,10 @@
<?php
$self->extends('auth/master');
{% extends 'auth/master.twig' %}
$self->block('content', function() use ($self) {
?>
{% block content %}
<form class="auth-login" method="post" action="/login/tfa">
<div class="auth-avatar">
<div class="auth-avatar-image">
<img src="//flashii.net/assets/avatar/<?=$self->userInfo->getId();?>?res=200" alt="">
<img src="//flashii.net/assets/avatar/{{ user_info.id }}?res=200" alt="">
</div>
</div>
@ -16,23 +14,22 @@ $self->block('content', function() use ($self) {
Username
</div>
<div class="auth-input-value">
<input type="text" value="<?=$self->userInfo->getName();?>" readonly>
<input type="text" value="{{ user_info.name }}" readonly>
</div>
</label>
</div>
<div class="auth-buttons">
<?php if(in_array('totp', $self->authMethodNames)): ?>
{% if 'totp' in auth_method_names %}
<a href="/login/tfa/totp" class="auth-button auth-button-primary">Authenticator app (TOTP)</a>
<?php endif; ?>
<?php if(in_array('u2f', $self->authMethodNames)): ?>
{% endif %}
{% if 'u2f' in auth_method_names %}
<a href="/login/tfa/u2f" class="auth-button auth-button-primary">Security key (U2F)</a>
<?php endif; ?>
<?php if(in_array('backup', $self->authMethodNames)): ?>
{% endif %}
{% if 'backup' in auth_method_names %}
<a href="/login/tfa/backup" class="auth-button auth-button-primary">Backup code</a>
<?php endif; ?>
{% endif %}
<a href="/login" class="auth-button">Cancel</a>
</div>
</form>
<?php
});
{% endblock %}

View file

@ -1,12 +1,10 @@
<?php
$self->extends('auth/master');
{% extends 'auth/master.twig' %}
$self->block('content', function() use ($self) {
?>
{% block content %}
<form class="auth-login" method="post" action="/login/tfa/totp">
<div class="auth-avatar">
<div class="auth-avatar-image">
<img src="//flashii.net/assets/avatar/<?=$self->userInfo->getId();?>?res=200" alt="">
<img src="//flashii.net/assets/avatar/{{ user_info.id }}?res=200" alt="">
</div>
</div>
@ -16,7 +14,7 @@ $self->block('content', function() use ($self) {
Username
</div>
<div class="auth-input-value">
<input type="text" value="<?=$self->userInfo->getName();?>" readonly>
<input type="text" value="{{ user_info.name }}" readonly>
</div>
</label>
@ -35,5 +33,4 @@ $self->block('content', function() use ($self) {
<a href="/login/tfa" class="auth-button">Back</a>
</div>
</form>
<?php
});
{% endblock %}

View file

@ -1,19 +1,19 @@
<?php
$self->extends('auth/master');
{% extends 'auth/master.twig' %}
$self->registerSuffix = '';
$self->forgotSuffix = '';
if(!empty($self->userName)) {
$suffix = '?username=' . rawurlencode($self->userName);
{% set register_suffix = '' %}
{% set forgot_suffix = '' %}
if($self->errorId === 'user_not_found')
$self->registerSuffix = $suffix;
else
$self->forgotSuffix = $suffix;
}
{% if user_name is not empty %}
{% set suffix = '?username=' ~ user_name %}
$self->block('content', function() use ($self) {
?>
{% if error_name == 'user_not_found' %}
{% set register_suffix = suffix %}
{% else %}
{% set forgot_suffix = suffix %}
{% endif %}
{% endif %}
{% block content %}
<form class="auth-login" method="post" action="/login">
<div class="auth-avatar">
<div class="auth-avatar-image">
@ -27,7 +27,7 @@ $self->block('content', function() use ($self) {
Username
</div>
<div class="auth-input-value">
<input type="text" name="username" value="<?=$self->x($self->userName);?>" placeholder="example123">
<input type="text" name="username" value="{{ user_name }}" placeholder="example123">
</div>
</label>
@ -43,10 +43,9 @@ $self->block('content', function() use ($self) {
<div class="auth-buttons">
<input type="submit" value="Next" class="auth-button auth-button-primary">
<a href="/forgot-username<?=$self->forgotSuffix;?>" class="auth-button">Forgot username?</a>
<a href="/forgot-password<?=$self->forgotSuffix;?>" class="auth-button">Forgot password?</a>
<a href="/register<?=$self->registerSuffix;?>" class="auth-button">Create account</a>
<a href="/forgot-username{{ forgot_suffix }}" class="auth-button">Forgot username?</a>
<a href="/forgot-password{{ forgot_suffix }}" class="auth-button">Forgot password?</a>
<a href="/register{{ register_suffix }}" class="auth-button">Create account</a>
</div>
</form>
<?php
});
{% endblock %}

View file

@ -1,13 +0,0 @@
<?php
$self->extends('master');
$self->block('body', function() use ($self) {
?>
<div class="auth">
<div class="auth-header">
<?=$self->hau->getSiteName();?>
</div>
<?=$self->getBlock('content');?>
</div>
<?php
});

View file

@ -0,0 +1,10 @@
{% extends 'master.twig' %}
{% block body %}
<div class="auth">
<div class="auth-header">
{{ globals.siteInfo.name }}
</div>
{% block content %}{% endblock %}
</div>
{% endblock %}

View file

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

View file

@ -0,0 +1,4 @@
{% extends 'errors/master.twig' %}
{% set http_error_title = 'Error 400' %}
{% set http_error_desc = 'Request is malformed.' %}

View file

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

View file

@ -0,0 +1,4 @@
{% extends 'errors/master.twig' %}
{% set http_error_title = 'Error 401' %}
{% set http_error_desc = 'You must be authorised to be here.' %}

View file

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

View file

@ -0,0 +1,4 @@
{% extends 'errors/master.twig' %}
{% set http_error_title = 'Error 403' %}
{% set http_error_desc = 'You are not authorised to be here.' %}

View file

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

View file

@ -0,0 +1,4 @@
{% extends 'errors/master.twig' %}
{% set http_error_title = 'Error 404' %}
{% set http_error_desc = 'Could not find what you were looking for.' %}

View file

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

View file

@ -0,0 +1,8 @@
{% extends 'master.twig' %}
{% block body %}
<div class="http-err">
<h1 class="http-err-title">{{ http_error_title|default('Unknown Error') }}</h1>
<p class="http-err-paragraph">{{ http_error_title|default('No additional information is available.') }}</p>
</div>
{% endblock %}

View file

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

View file

@ -3,11 +3,11 @@
<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>
<title>{% if title is defined %}{{ title }} :: {% endif %}{{ globals.siteInfo.name }}</title>
<link href="/assets/hanyuu.css" type="text/css" rel="stylesheet">
</head>
<body>
<?=$self->getBlock('body');?>
{% block body %}{% endblock %}
<script src="/assets/hanyuu.js" type="text/javascript"></script>
</body>
</html>