Use config library and moved templates to Sasae format.
This commit is contained in:
parent
514e0068a2
commit
db6821dedf
37 changed files with 228 additions and 515 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -4,5 +4,6 @@
|
||||||
/public/robots.txt
|
/public/robots.txt
|
||||||
/lib/index-dev
|
/lib/index-dev
|
||||||
/.debug
|
/.debug
|
||||||
|
/config/config.cfg
|
||||||
/config/config.ini
|
/config/config.ini
|
||||||
/vendor
|
/vendor
|
||||||
|
|
|
@ -7,7 +7,8 @@
|
||||||
"chillerlan/php-qrcode": "^4.3",
|
"chillerlan/php-qrcode": "^4.3",
|
||||||
"symfony/mailer": "^6.0",
|
"symfony/mailer": "^6.0",
|
||||||
"matomo/device-detector": "^6.1",
|
"matomo/device-detector": "^6.1",
|
||||||
"sentry/sdk": "^3.5"
|
"sentry/sdk": "^3.5",
|
||||||
|
"flashwave/syokuhou": "dev-master"
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
"classmap": [
|
"classmap": [
|
||||||
|
|
41
composer.lock
generated
41
composer.lock
generated
|
@ -4,7 +4,7 @@
|
||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"content-hash": "d531fced85cb9df6688044da4d0047d2",
|
"content-hash": "079d926545b1fc9ef6fafe2f4e72caef",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "chillerlan/php-qrcode",
|
"name": "chillerlan/php-qrcode",
|
||||||
|
@ -445,6 +445,45 @@
|
||||||
"homepage": "https://railgun.sh/sasae",
|
"homepage": "https://railgun.sh/sasae",
|
||||||
"time": "2023-08-24T23:24:45+00:00"
|
"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",
|
"name": "guzzlehttp/promises",
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
|
|
3
config/config.example.cfg
Normal file
3
config/config.example.cfg
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
database:dsn mariadb://username:password@:unix:/database?socket=/var/run/mysqld/mysqld.sock&charset=utf8mb4
|
||||||
|
|
||||||
|
site:name Hanyuu
|
|
@ -1,5 +0,0 @@
|
||||||
[database]
|
|
||||||
dsn = "mariadb://username:password@:unix:/database?socket=/var/run/mysqld/mysqld.sock&charset=utf8mb4"
|
|
||||||
|
|
||||||
[site]
|
|
||||||
name = Hanyuu
|
|
|
@ -4,8 +4,7 @@ namespace Hanyuu;
|
||||||
use Index\Autoloader;
|
use Index\Autoloader;
|
||||||
use Index\Environment;
|
use Index\Environment;
|
||||||
use Index\Data\DbTools;
|
use Index\Data\DbTools;
|
||||||
use Hanyuu\Config\IConfig;
|
use Syokuhou\SharpConfig;
|
||||||
use Hanyuu\Config\ArrayConfig;
|
|
||||||
|
|
||||||
define('HAU_STARTUP', microtime(true));
|
define('HAU_STARTUP', microtime(true));
|
||||||
define('HAU_ROOT', __DIR__);
|
define('HAU_ROOT', __DIR__);
|
||||||
|
@ -41,9 +40,9 @@ set_exception_handler(function(\Throwable $ex) {
|
||||||
die('<h2>Hanyuu is sad.</h2>');
|
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\';');
|
$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);
|
$hau = new HanyuuContext($cfg, $dbc);
|
||||||
|
|
|
@ -3,7 +3,27 @@ namespace Hanyuu;
|
||||||
|
|
||||||
require_once __DIR__ . '/../hanyuu.php';
|
require_once __DIR__ . '/../hanyuu.php';
|
||||||
|
|
||||||
$hau->setUpHttp();
|
set_exception_handler(function(\Throwable $ex) {
|
||||||
$hau->dispatchHttp(
|
http_response_code(500);
|
||||||
\Index\Http\HttpRequest::fromRequest()
|
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();
|
||||||
|
|
|
@ -2,10 +2,10 @@
|
||||||
namespace Hanyuu\Auth;
|
namespace Hanyuu\Auth;
|
||||||
|
|
||||||
use Index\Routing\IRouter;
|
use Index\Routing\IRouter;
|
||||||
|
use Syokuhou\IConfig;
|
||||||
use Hanyuu\HanyuuContext;
|
use Hanyuu\HanyuuContext;
|
||||||
use Hanyuu\Auth\Auth;
|
use Hanyuu\Auth\Auth;
|
||||||
use Hanyuu\Auth\IAuthLogin;
|
use Hanyuu\Auth\IAuthLogin;
|
||||||
use Hanyuu\Config\IConfig;
|
|
||||||
use Hanyuu\Users\IUserInfo;
|
use Hanyuu\Users\IUserInfo;
|
||||||
use Hanyuu\Users\IUsers;
|
use Hanyuu\Users\IUsers;
|
||||||
use Hanyuu\Users\UserNotFoundException;
|
use Hanyuu\Users\UserNotFoundException;
|
||||||
|
@ -58,7 +58,7 @@ class AuthRoutes {
|
||||||
}
|
}
|
||||||
|
|
||||||
private function getLoginCookieName(): string {
|
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 {
|
private function destroyLoginSession($response): void {
|
||||||
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
}
|
|
|
@ -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));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,6 +1,7 @@
|
||||||
<?php
|
<?php
|
||||||
namespace Hanyuu;
|
namespace Hanyuu;
|
||||||
|
|
||||||
|
use Index\Environment;
|
||||||
use Index\Data\IDbConnection;
|
use Index\Data\IDbConnection;
|
||||||
use Index\Data\Migration\IDbMigrationRepo;
|
use Index\Data\Migration\IDbMigrationRepo;
|
||||||
use Index\Data\Migration\DbMigrationManager;
|
use Index\Data\Migration\DbMigrationManager;
|
||||||
|
@ -8,11 +9,11 @@ use Index\Data\Migration\FsDbMigrationRepo;
|
||||||
use Index\Http\HttpFx;
|
use Index\Http\HttpFx;
|
||||||
use Index\Http\HttpRequest;
|
use Index\Http\HttpRequest;
|
||||||
use Index\Routing\IRouter;
|
use Index\Routing\IRouter;
|
||||||
|
use Sasae\SasaeEnvironment;
|
||||||
|
use Syokuhou\IConfig;
|
||||||
use Hanyuu\Auth\Auth;
|
use Hanyuu\Auth\Auth;
|
||||||
use Hanyuu\Auth\AuthRoutes;
|
use Hanyuu\Auth\AuthRoutes;
|
||||||
use Hanyuu\Auth\Db\DbAuth;
|
use Hanyuu\Auth\Db\DbAuth;
|
||||||
use Hanyuu\Config\IConfig;
|
|
||||||
use Hanyuu\Templating\TemplateContext;
|
|
||||||
use Hanyuu\Users\IUsers;
|
use Hanyuu\Users\IUsers;
|
||||||
use Hanyuu\Users\Db\DbUsers;
|
use Hanyuu\Users\Db\DbUsers;
|
||||||
|
|
||||||
|
@ -21,17 +22,15 @@ class HanyuuContext {
|
||||||
private IDbConnection $dbConn;
|
private IDbConnection $dbConn;
|
||||||
private IUsers $users;
|
private IUsers $users;
|
||||||
private Auth $auth;
|
private Auth $auth;
|
||||||
private ?TemplateContext $tpl = null;
|
private ?SasaeEnvironment $templating = null;
|
||||||
private ?IRouter $router = null;
|
private SiteInfo $siteInfo;
|
||||||
|
|
||||||
public function __construct(IConfig $config, IDbConnection $dbConn) {
|
public function __construct(IConfig $config, IDbConnection $dbConn) {
|
||||||
$this->config = $config;
|
$this->config = $config;
|
||||||
$this->dbConn = $dbConn;
|
$this->dbConn = $dbConn;
|
||||||
$this->users = new DbUsers($dbConn);
|
$this->users = new DbUsers($dbConn);
|
||||||
}
|
|
||||||
|
|
||||||
public function getSiteName(): string {
|
$this->siteInfo = new SiteInfo($config->scopeTo('site'));
|
||||||
return $this->config->getValue('site:name', IConfig::T_STR, 'Hanyuu');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getUsers(): IUsers {
|
public function getUsers(): IUsers {
|
||||||
|
@ -55,13 +54,23 @@ class HanyuuContext {
|
||||||
return new FsDbMigrationRepo(HAU_DIR_MIGRATIONS);
|
return new FsDbMigrationRepo(HAU_DIR_MIGRATIONS);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getTemplating(): TemplateContext {
|
public function getTemplating(): SasaeEnvironment {
|
||||||
if($this->tpl === null) {
|
if($this->templating === null) {
|
||||||
$this->tpl = new TemplateContext(HAU_DIR_TEMPLATES);
|
$isDebug = Environment::isDebug();
|
||||||
$this->tpl->setGlobal('hau', $this);
|
|
||||||
|
$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 {
|
public function renderTemplate(...$args): string {
|
||||||
|
@ -76,52 +85,16 @@ class HanyuuContext {
|
||||||
return $this->auth;
|
return $this->auth;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getRouter(): IRouter {
|
public function createRouting(): RoutingContext {
|
||||||
return $this->router->getRouter();
|
$routingCtx = new RoutingContext($this->getTemplating());
|
||||||
}
|
$routingCtx->registerDefaultErrorPages();
|
||||||
|
|
||||||
public function setUpHttp(): void {
|
$routingCtx->getRouter()->get('/', function($response, $request) {
|
||||||
$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) {
|
|
||||||
return 503;
|
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
39
src/RoutingContext.php
Normal 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
14
src/SiteInfo.php
Normal 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');
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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]);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,12 +1,10 @@
|
||||||
<?php
|
{% extends 'auth/master.twig' %}
|
||||||
$self->extends('auth/master');
|
|
||||||
|
|
||||||
$self->block('content', function() use ($self) {
|
{% block content %}
|
||||||
?>
|
|
||||||
<form class="auth-login" method="post" action="/login/tfa">
|
<form class="auth-login" method="post" action="/login/tfa">
|
||||||
<div class="auth-avatar">
|
<div class="auth-avatar">
|
||||||
<div class="auth-avatar-image">
|
<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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -16,23 +14,22 @@ $self->block('content', function() use ($self) {
|
||||||
Username
|
Username
|
||||||
</div>
|
</div>
|
||||||
<div class="auth-input-value">
|
<div class="auth-input-value">
|
||||||
<input type="text" value="<?=$self->userInfo->getName();?>" readonly>
|
<input type="text" value="{{ user_info.name }}" readonly>
|
||||||
</div>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="auth-buttons">
|
<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>
|
<a href="/login/tfa/totp" class="auth-button auth-button-primary">Authenticator app (TOTP)</a>
|
||||||
<?php endif; ?>
|
{% endif %}
|
||||||
<?php if(in_array('u2f', $self->authMethodNames)): ?>
|
{% if 'u2f' in auth_method_names %}
|
||||||
<a href="/login/tfa/u2f" class="auth-button auth-button-primary">Security key (U2F)</a>
|
<a href="/login/tfa/u2f" class="auth-button auth-button-primary">Security key (U2F)</a>
|
||||||
<?php endif; ?>
|
{% endif %}
|
||||||
<?php if(in_array('backup', $self->authMethodNames)): ?>
|
{% if 'backup' in auth_method_names %}
|
||||||
<a href="/login/tfa/backup" class="auth-button auth-button-primary">Backup code</a>
|
<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>
|
<a href="/login" class="auth-button">Cancel</a>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
<?php
|
{% endblock %}
|
||||||
});
|
|
|
@ -1,12 +1,10 @@
|
||||||
<?php
|
{% extends 'auth/master.twig' %}
|
||||||
$self->extends('auth/master');
|
|
||||||
|
|
||||||
$self->block('content', function() use ($self) {
|
{% block content %}
|
||||||
?>
|
|
||||||
<form class="auth-login" method="post" action="/login/tfa/totp">
|
<form class="auth-login" method="post" action="/login/tfa/totp">
|
||||||
<div class="auth-avatar">
|
<div class="auth-avatar">
|
||||||
<div class="auth-avatar-image">
|
<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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -16,7 +14,7 @@ $self->block('content', function() use ($self) {
|
||||||
Username
|
Username
|
||||||
</div>
|
</div>
|
||||||
<div class="auth-input-value">
|
<div class="auth-input-value">
|
||||||
<input type="text" value="<?=$self->userInfo->getName();?>" readonly>
|
<input type="text" value="{{ user_info.name }}" readonly>
|
||||||
</div>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
|
@ -35,5 +33,4 @@ $self->block('content', function() use ($self) {
|
||||||
<a href="/login/tfa" class="auth-button">Back</a>
|
<a href="/login/tfa" class="auth-button">Back</a>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
<?php
|
{% endblock %}
|
||||||
});
|
|
|
@ -1,19 +1,19 @@
|
||||||
<?php
|
{% extends 'auth/master.twig' %}
|
||||||
$self->extends('auth/master');
|
|
||||||
|
|
||||||
$self->registerSuffix = '';
|
{% set register_suffix = '' %}
|
||||||
$self->forgotSuffix = '';
|
{% set forgot_suffix = '' %}
|
||||||
if(!empty($self->userName)) {
|
|
||||||
$suffix = '?username=' . rawurlencode($self->userName);
|
|
||||||
|
|
||||||
if($self->errorId === 'user_not_found')
|
{% if user_name is not empty %}
|
||||||
$self->registerSuffix = $suffix;
|
{% set suffix = '?username=' ~ user_name %}
|
||||||
else
|
|
||||||
$self->forgotSuffix = $suffix;
|
|
||||||
}
|
|
||||||
|
|
||||||
$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">
|
<form class="auth-login" method="post" action="/login">
|
||||||
<div class="auth-avatar">
|
<div class="auth-avatar">
|
||||||
<div class="auth-avatar-image">
|
<div class="auth-avatar-image">
|
||||||
|
@ -27,7 +27,7 @@ $self->block('content', function() use ($self) {
|
||||||
Username
|
Username
|
||||||
</div>
|
</div>
|
||||||
<div class="auth-input-value">
|
<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>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
|
@ -43,10 +43,9 @@ $self->block('content', function() use ($self) {
|
||||||
|
|
||||||
<div class="auth-buttons">
|
<div class="auth-buttons">
|
||||||
<input type="submit" value="Next" class="auth-button auth-button-primary">
|
<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-username{{ forgot_suffix }}" class="auth-button">Forgot username?</a>
|
||||||
<a href="/forgot-password<?=$self->forgotSuffix;?>" class="auth-button">Forgot password?</a>
|
<a href="/forgot-password{{ forgot_suffix }}" class="auth-button">Forgot password?</a>
|
||||||
<a href="/register<?=$self->registerSuffix;?>" class="auth-button">Create account</a>
|
<a href="/register{{ register_suffix }}" class="auth-button">Create account</a>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
<?php
|
{% endblock %}
|
||||||
});
|
|
|
@ -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
|
|
||||||
});
|
|
10
templates/auth/master.twig
Normal file
10
templates/auth/master.twig
Normal 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 %}
|
|
@ -1,5 +0,0 @@
|
||||||
<?php
|
|
||||||
$self->extends('errors/master');
|
|
||||||
|
|
||||||
$self->http_error_title = 'Error 400';
|
|
||||||
$self->http_error_desc = 'Request is malformed.';
|
|
4
templates/errors/400.twig
Normal file
4
templates/errors/400.twig
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
{% extends 'errors/master.twig' %}
|
||||||
|
|
||||||
|
{% set http_error_title = 'Error 400' %}
|
||||||
|
{% set http_error_desc = 'Request is malformed.' %}
|
|
@ -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.';
|
|
4
templates/errors/401.twig
Normal file
4
templates/errors/401.twig
Normal 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.' %}
|
|
@ -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.';
|
|
4
templates/errors/403.twig
Normal file
4
templates/errors/403.twig
Normal 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.' %}
|
|
@ -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.';
|
|
4
templates/errors/404.twig
Normal file
4
templates/errors/404.twig
Normal 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.' %}
|
|
@ -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
|
|
||||||
});
|
|
8
templates/errors/master.twig
Normal file
8
templates/errors/master.twig
Normal 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 %}
|
|
@ -1,11 +1,8 @@
|
||||||
<?php
|
{% extends 'master.twig' %}
|
||||||
$self->extends('master');
|
|
||||||
|
|
||||||
$self->block('body', function($self) {
|
{% block body %}
|
||||||
?>
|
|
||||||
<center>
|
<center>
|
||||||
<h1>Under Construction</h1>
|
<h1>Under Construction</h1>
|
||||||
<img src="//static.flash.moe/images/me-tan-2.png" alt="">
|
<img src="//static.flash.moe/images/me-tan-2.png" alt="">
|
||||||
</center>
|
</center>
|
||||||
<?php
|
{% endblock %}
|
||||||
});
|
|
|
@ -3,11 +3,11 @@
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
|
<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">
|
<link href="/assets/hanyuu.css" type="text/css" rel="stylesheet">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<?=$self->getBlock('body');?>
|
{% block body %}{% endblock %}
|
||||||
<script src="/assets/hanyuu.js" type="text/javascript"></script>
|
<script src="/assets/hanyuu.js" type="text/javascript"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
Loading…
Reference in a new issue