diff --git a/.gitignore b/.gitignore
index 354ac86..bc429f0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,5 +4,6 @@
/public/robots.txt
/lib/index-dev
/.debug
+/config/config.cfg
/config/config.ini
/vendor
diff --git a/composer.json b/composer.json
index 1732429..230b61e 100644
--- a/composer.json
+++ b/composer.json
@@ -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": [
diff --git a/composer.lock b/composer.lock
index af333ee..2fa0e8c 100644
--- a/composer.lock
+++ b/composer.lock
@@ -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",
diff --git a/config/config.example.cfg b/config/config.example.cfg
new file mode 100644
index 0000000..58164db
--- /dev/null
+++ b/config/config.example.cfg
@@ -0,0 +1,3 @@
+database:dsn mariadb://username:password@:unix:/database?socket=/var/run/mysqld/mysqld.sock&charset=utf8mb4
+
+site:name Hanyuu
diff --git a/config/config.example.ini b/config/config.example.ini
deleted file mode 100644
index 1e8a1b6..0000000
--- a/config/config.example.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[database]
-dsn = "mariadb://username:password@:unix:/database?socket=/var/run/mysqld/mysqld.sock&charset=utf8mb4"
-
-[site]
-name = Hanyuu
diff --git a/hanyuu.php b/hanyuu.php
index 9a270e8..9ce27b6 100644
--- a/hanyuu.php
+++ b/hanyuu.php
@@ -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('
Hanyuu is sad.
');
});
-$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);
diff --git a/public/index.php b/public/index.php
index 6a3d888..86381b1 100644
--- a/public/index.php
+++ b/public/index.php
@@ -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();
diff --git a/src/Auth/AuthRoutes.php b/src/Auth/AuthRoutes.php
index 410a412..786f771 100644
--- a/src/Auth/AuthRoutes.php
+++ b/src/Auth/AuthRoutes.php
@@ -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 {
diff --git a/src/Config/ArrayConfig.php b/src/Config/ArrayConfig.php
deleted file mode 100644
index c0431a8..0000000
--- a/src/Config/ArrayConfig.php
+++ /dev/null
@@ -1,78 +0,0 @@
-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);
- }
-}
diff --git a/src/Config/CfgTools.php b/src/Config/CfgTools.php
deleted file mode 100644
index 8aba0bc..0000000
--- a/src/Config/CfgTools.php
+++ /dev/null
@@ -1,27 +0,0 @@
- 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;
- }
-}
diff --git a/src/Config/IConfig.php b/src/Config/IConfig.php
deleted file mode 100644
index c8f2b32..0000000
--- a/src/Config/IConfig.php
+++ /dev/null
@@ -1,14 +0,0 @@
-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));
- }
-}
diff --git a/src/HanyuuContext.php b/src/HanyuuContext.php
index 4af3e72..b49b147 100644
--- a/src/HanyuuContext.php
+++ b/src/HanyuuContext.php
@@ -1,6 +1,7 @@
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;
}
}
diff --git a/src/RoutingContext.php b/src/RoutingContext.php
new file mode 100644
index 0000000..27342aa
--- /dev/null
+++ b/src/RoutingContext.php
@@ -0,0 +1,39 @@
+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);
+ }
+}
diff --git a/src/SiteInfo.php b/src/SiteInfo.php
new file mode 100644
index 0000000..3050abd
--- /dev/null
+++ b/src/SiteInfo.php
@@ -0,0 +1,14 @@
+config->getString('name', 'Hanyuu');
+ }
+}
diff --git a/src/Templating/Template.php b/src/Templating/Template.php
deleted file mode 100644
index 2e1e141..0000000
--- a/src/Templating/Template.php
+++ /dev/null
@@ -1,53 +0,0 @@
-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;
- }
-}
diff --git a/src/Templating/TemplateBlock.php b/src/Templating/TemplateBlock.php
deleted file mode 100644
index 664fc93..0000000
--- a/src/Templating/TemplateBlock.php
+++ /dev/null
@@ -1,22 +0,0 @@
-body)) {
- ob_start();
- ($this->body)($this->self);
- $body = ob_get_contents();
- ob_end_clean();
- } else $body = strval($this->body);
-
- return $body;
- }
-}
diff --git a/src/Templating/TemplateContext.php b/src/Templating/TemplateContext.php
deleted file mode 100644
index b25909b..0000000
--- a/src/Templating/TemplateContext.php
+++ /dev/null
@@ -1,56 +0,0 @@
-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;
- }
-}
diff --git a/src/Templating/TemplateSelf.php b/src/Templating/TemplateSelf.php
deleted file mode 100644
index 4ce9c31..0000000
--- a/src/Templating/TemplateSelf.php
+++ /dev/null
@@ -1,32 +0,0 @@
-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);
- }
-}
diff --git a/src/Templating/TemplateVars.php b/src/Templating/TemplateVars.php
deleted file mode 100644
index 0b4fcf8..0000000
--- a/src/Templating/TemplateVars.php
+++ /dev/null
@@ -1,32 +0,0 @@
-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]);
- }
-}
diff --git a/templates/auth/login-tfa.php b/templates/auth/login-tfa.twig
similarity index 60%
rename from templates/auth/login-tfa.php
rename to templates/auth/login-tfa.twig
index 58f1288..97ada08 100644
--- a/templates/auth/login-tfa.php
+++ b/templates/auth/login-tfa.twig
@@ -1,12 +1,10 @@
-extends('auth/master');
+{% extends 'auth/master.twig' %}
-$self->block('content', function() use ($self) {
-?>
+{% block content %}
-extends('auth/master');
+{% extends 'auth/master.twig' %}
-$self->block('content', function() use ($self) {
-?>
+{% block content %}
-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 %}