diff --git a/.env.example b/.env.example new file mode 100644 index 00000000..7626471c --- /dev/null +++ b/.env.example @@ -0,0 +1,25 @@ +# Database connection setting +# This uses the Index library's DSN syntax +# Currently Misuzu only supports MariaDB, or Null of course since that does nothing +# +# For normal TCP connection you can use the following syntax: +#DATABASE_DSN="mariadb://username:password@hostname/dbname?charset=utf8mb4" +# +# For a UNIX socket connection you can use the following syntax: +#DATABASE_DSN="mariadb://username:password@:unix:/dbname?socket=/path/to/mysqld.sock&charset=utf8mb4" +# +# And here's your unsensible default: +DATABASE_DSN="null:" + +# Sentry error reporting setting +# I have not idea this works, just shove the value Sentry gives you in here. +# You can also leave it commented. +#SENTRY_DSN="https://6e41e3a2507d1542fd1e9aaf54f05d87@o4505858016870400.ingest.sentry.io/4505858023751680" + +# Domain roles +# This assigns what domain has what role, domains can also have multiple roles! +# Pairs are split by ;, domain and role pairs are split by =, roles are split by , and if you want a prefix use : +# The example below maps everything to localhost for development. +# But to make things more understandable, the value for Flashii is also included +#DOMAIN_ROLES="flashii.net=main; fii.moe=redirect" +DOMAIN_ROLES="localhost=main,redirect:/go" diff --git a/.gitignore b/.gitignore index 49f1e43d..706011ad 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,7 @@ /composer.local.json # Configuration +/.env /config/config.cfg /config/github.cfg /config/config.ini diff --git a/VERSION b/VERSION index 11e801e0..039de05f 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -20250209 +20250210 diff --git a/composer.json b/composer.json index e82149b2..f05be536 100644 --- a/composer.json +++ b/composer.json @@ -10,7 +10,9 @@ "symfony/mailer": "~7.2", "matomo/device-detector": "~6.4", "sentry/sdk": "~4.0", - "nesbot/carbon": "~3.8" + "nesbot/carbon": "~3.8", + "vlucas/phpdotenv": "~5.6", + "filp/whoops": "~2.17" }, "autoload": { "classmap": [ @@ -27,7 +29,6 @@ ] }, "require-dev": { - "phpstan/phpstan": "~2.1", - "filp/whoops": "~2.17" + "phpstan/phpstan": "~2.1" } } diff --git a/composer.lock b/composer.lock index 693b9a9e..ee8ee9c4 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": "3cc1fc369aa12ce9ebbf324bc13a7d9d", + "content-hash": "ba6f5f293f0f3e3c7ad9b5335aee2b36", "packages": [ { "name": "carbonphp/carbon-doctrine-types", @@ -428,6 +428,77 @@ }, "time": "2019-12-30T22:54:17+00:00" }, + { + "name": "filp/whoops", + "version": "2.17.0", + "source": { + "type": "git", + "url": "https://github.com/filp/whoops.git", + "reference": "075bc0c26631110584175de6523ab3f1652eb28e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/filp/whoops/zipball/075bc0c26631110584175de6523ab3f1652eb28e", + "reference": "075bc0c26631110584175de6523ab3f1652eb28e", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0", + "psr/log": "^1.0.1 || ^2.0 || ^3.0" + }, + "require-dev": { + "mockery/mockery": "^1.0", + "phpunit/phpunit": "^7.5.20 || ^8.5.8 || ^9.3.3", + "symfony/var-dumper": "^4.0 || ^5.0" + }, + "suggest": { + "symfony/var-dumper": "Pretty print complex values better with var-dumper available", + "whoops/soap": "Formats errors as SOAP responses" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Whoops\\": "src/Whoops/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Filipe Dobreira", + "homepage": "https://github.com/filp", + "role": "Developer" + } + ], + "description": "php error handling for cool kids", + "homepage": "https://filp.github.io/whoops/", + "keywords": [ + "error", + "exception", + "handling", + "library", + "throwable", + "whoops" + ], + "support": { + "issues": "https://github.com/filp/whoops/issues", + "source": "https://github.com/filp/whoops/tree/2.17.0" + }, + "funding": [ + { + "url": "https://github.com/denis-sokolov", + "type": "github" + } + ], + "time": "2025-01-25T12:00:00+00:00" + }, { "name": "flashii/rpcii", "version": "v4.0.0", @@ -522,6 +593,68 @@ "homepage": "https://railgun.sh/index", "time": "2025-01-22T12:38:11+00:00" }, + { + "name": "graham-campbell/result-type", + "version": "v1.1.3", + "source": { + "type": "git", + "url": "https://github.com/GrahamCampbell/Result-Type.git", + "reference": "3ba905c11371512af9d9bdd27d99b782216b6945" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/3ba905c11371512af9d9bdd27d99b782216b6945", + "reference": "3ba905c11371512af9d9bdd27d99b782216b6945", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0", + "phpoption/phpoption": "^1.9.3" + }, + "require-dev": { + "phpunit/phpunit": "^8.5.39 || ^9.6.20 || ^10.5.28" + }, + "type": "library", + "autoload": { + "psr-4": { + "GrahamCampbell\\ResultType\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + } + ], + "description": "An Implementation Of The Result Type", + "keywords": [ + "Graham Campbell", + "GrahamCampbell", + "Result Type", + "Result-Type", + "result" + ], + "support": { + "issues": "https://github.com/GrahamCampbell/Result-Type/issues", + "source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.1.3" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/graham-campbell/result-type", + "type": "tidelift" + } + ], + "time": "2024-07-20T21:45:45+00:00" + }, { "name": "guzzlehttp/psr7", "version": "2.7.0", @@ -926,6 +1059,81 @@ ], "time": "2024-12-27T09:25:35+00:00" }, + { + "name": "phpoption/phpoption", + "version": "1.9.3", + "source": { + "type": "git", + "url": "https://github.com/schmittjoh/php-option.git", + "reference": "e3fac8b24f56113f7cb96af14958c0dd16330f54" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/e3fac8b24f56113f7cb96af14958c0dd16330f54", + "reference": "e3fac8b24f56113f7cb96af14958c0dd16330f54", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "phpunit/phpunit": "^8.5.39 || ^9.6.20 || ^10.5.28" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + }, + "branch-alias": { + "dev-master": "1.9-dev" + } + }, + "autoload": { + "psr-4": { + "PhpOption\\": "src/PhpOption/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Johannes M. Schmitt", + "email": "schmittjoh@gmail.com", + "homepage": "https://github.com/schmittjoh" + }, + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + } + ], + "description": "Option Type for PHP", + "keywords": [ + "language", + "option", + "php", + "type" + ], + "support": { + "issues": "https://github.com/schmittjoh/php-option/issues", + "source": "https://github.com/schmittjoh/php-option/tree/1.9.3" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpoption/phpoption", + "type": "tidelift" + } + ], + "time": "2024-07-20T21:41:07+00:00" + }, { "name": "psr/clock", "version": "1.0.0", @@ -2274,6 +2482,86 @@ ], "time": "2024-09-09T11:45:10+00:00" }, + { + "name": "symfony/polyfill-php80", + "version": "v1.31.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/60328e362d4c2c802a54fcbf04f9d3fb892b4cf8", + "reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php80/tree/v1.31.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, { "name": "symfony/polyfill-php81", "version": "v1.31.0", @@ -2829,80 +3117,93 @@ } ], "time": "2025-01-29T07:06:14+00:00" - } - ], - "packages-dev": [ + }, { - "name": "filp/whoops", - "version": "2.17.0", + "name": "vlucas/phpdotenv", + "version": "v5.6.1", "source": { "type": "git", - "url": "https://github.com/filp/whoops.git", - "reference": "075bc0c26631110584175de6523ab3f1652eb28e" + "url": "https://github.com/vlucas/phpdotenv.git", + "reference": "a59a13791077fe3d44f90e7133eb68e7d22eaff2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/filp/whoops/zipball/075bc0c26631110584175de6523ab3f1652eb28e", - "reference": "075bc0c26631110584175de6523ab3f1652eb28e", + "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/a59a13791077fe3d44f90e7133eb68e7d22eaff2", + "reference": "a59a13791077fe3d44f90e7133eb68e7d22eaff2", "shasum": "" }, "require": { - "php": "^7.1 || ^8.0", - "psr/log": "^1.0.1 || ^2.0 || ^3.0" + "ext-pcre": "*", + "graham-campbell/result-type": "^1.1.3", + "php": "^7.2.5 || ^8.0", + "phpoption/phpoption": "^1.9.3", + "symfony/polyfill-ctype": "^1.24", + "symfony/polyfill-mbstring": "^1.24", + "symfony/polyfill-php80": "^1.24" }, "require-dev": { - "mockery/mockery": "^1.0", - "phpunit/phpunit": "^7.5.20 || ^8.5.8 || ^9.3.3", - "symfony/var-dumper": "^4.0 || ^5.0" + "bamarni/composer-bin-plugin": "^1.8.2", + "ext-filter": "*", + "phpunit/phpunit": "^8.5.34 || ^9.6.13 || ^10.4.2" }, "suggest": { - "symfony/var-dumper": "Pretty print complex values better with var-dumper available", - "whoops/soap": "Formats errors as SOAP responses" + "ext-filter": "Required to use the boolean validator." }, "type": "library", "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + }, "branch-alias": { - "dev-master": "2.7-dev" + "dev-master": "5.6-dev" } }, "autoload": { "psr-4": { - "Whoops\\": "src/Whoops/" + "Dotenv\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Filipe Dobreira", - "homepage": "https://github.com/filp", - "role": "Developer" + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Vance Lucas", + "email": "vance@vancelucas.com", + "homepage": "https://github.com/vlucas" } ], - "description": "php error handling for cool kids", - "homepage": "https://filp.github.io/whoops/", + "description": "Loads environment variables from `.env` to `getenv()`, `$_ENV` and `$_SERVER` automagically.", "keywords": [ - "error", - "exception", - "handling", - "library", - "throwable", - "whoops" + "dotenv", + "env", + "environment" ], "support": { - "issues": "https://github.com/filp/whoops/issues", - "source": "https://github.com/filp/whoops/tree/2.17.0" + "issues": "https://github.com/vlucas/phpdotenv/issues", + "source": "https://github.com/vlucas/phpdotenv/tree/v5.6.1" }, "funding": [ { - "url": "https://github.com/denis-sokolov", + "url": "https://github.com/GrahamCampbell", "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/vlucas/phpdotenv", + "type": "tidelift" } ], - "time": "2025-01-25T12:00:00+00:00" - }, + "time": "2024-07-20T21:52:34+00:00" + } + ], + "packages-dev": [ { "name": "phpstan/phpstan", "version": "2.1.3", diff --git a/config/config.example.cfg b/config/config.example.cfg deleted file mode 100644 index 21422f97..00000000 --- a/config/config.example.cfg +++ /dev/null @@ -1,11 +0,0 @@ -# Example configuration for Misuzu -# and ; can be used at the start of a line for comments. - -database:dsn mariadb://<user>:<pass>@<host>/<name>?charset=utf8mb4 - -;sentry:dsn https://sentry dsn here -;sentry:tracesRate 1.0 -;sentry:profilesRate 1.0 - -domain:localhost main redirect -domain:localhost:redirect:path /go diff --git a/misuzu.php b/misuzu.php index d60d1c6c..b4501519 100644 --- a/misuzu.php +++ b/misuzu.php @@ -1,26 +1,28 @@ <?php namespace Misuzu; -use Index\Config\Fs\FsConfig; - define('MSZ_STARTUP', microtime(true)); define('MSZ_ROOT', __DIR__); require_once __DIR__ . '/vendor/autoload.php'; -$env = FsConfig::fromFile(Misuzu::PATH_CONFIG . '/config.cfg'); +\Dotenv\Dotenv::createImmutable(Misuzu::PATH_ROOT)->load(); -if($env->hasValues('sentry:dsn')) - (function($env) { - \Sentry\init([ - 'dsn' => $env->getString('dsn'), - 'traces_sample_rate' => $env->getFloat('tracesRate', 0.2), - 'profiles_sample_rate' => $env->getFloat('profilesRate', 0.2), - ]); +if(!empty($_ENV['SENTRY_DSN'])) + \Sentry\init(['dsn' => $_ENV['SENTRY_DSN']]); - set_exception_handler(function(\Throwable $ex) { - \Sentry\captureException($ex); - }); - })($env->scopeTo('sentry')); +(function(\Whoops\RunInterface $whoops) { + if(Misuzu::cli()) + $whoops->pushHandler(new Whoops\SentryPlainTextHandler); + elseif(Misuzu::debug()) + $whoops->pushHandler(new \Whoops\Handler\PrettyPageHandler); + else + $whoops->pushHandler(new Whoops\SentryPageHandler); -$msz = new MisuzuContext($env); + $whoops->register(); +})(new \Whoops\Run); + +$msz = new MisuzuContext( + $_ENV['DATABASE_DSN'] ?? 'null:', + $_ENV['DOMAIN_ROLES'] ?? 'localhost=main,redirect:/go' +); diff --git a/public/index.php b/public/index.php index ef83817c..5171431e 100644 --- a/public/index.php +++ b/public/index.php @@ -9,23 +9,6 @@ require_once __DIR__ . '/../misuzu.php'; if(!isset($msz) || !($msz instanceof \Misuzu\MisuzuContext)) die('Misuzu is not initialised.'); -if(class_exists(\Whoops\Run::class)) - (function($whoops) { - $whoops->pushHandler(new \Whoops\Handler\PrettyPageHandler); - $whoops->register(); - })(new \Whoops\Run); -else - set_exception_handler(function(\Throwable $ex) { - \Sentry\captureException($ex); - - http_response_code(500); - ob_clean(); - - header('Content-Type: text/html; charset=utf-8'); - header('X-Accel-Redirect: /error-500.html'); - exit; - }); - // The whole wall of shit before the router setup and dispatch should be worked away // Lockdown things should be middleware when there's no more legacy files @@ -128,7 +111,7 @@ CSRF::init( $router = $msz->createRouting($request); $msz->startTemplating(); -if(in_array('main', $msz->env->getArray(sprintf('domain:%s', $request->getHeaderLine('Host'))))) { +if($msz->domainRoles->hasRole($request->getHeaderLine('Host'), 'main')) { $mszRequestPath = substr($request->path, 1); $mszLegacyPathPrefix = Misuzu::PATH_PUBLIC_LEGACY . '/'; $mszLegacyPath = $mszLegacyPathPrefix . $mszRequestPath; diff --git a/src/DatabaseContext.php b/src/DatabaseContext.php index 24f9a65b..83db6ff3 100644 --- a/src/DatabaseContext.php +++ b/src/DatabaseContext.php @@ -11,8 +11,10 @@ class DatabaseContext implements RouteHandler { public private(set) DbConnection $conn; - public function __construct(Config $config) { - $this->conn = DbBackends::create($config->getString('dsn', 'null:')); + public function __construct( + #[\SensitiveParameter] string $dsn + ) { + $this->conn = DbBackends::create($dsn); $this->conn->execute(<<<SQL SET SESSION time_zone = '+00:00', diff --git a/src/DomainRoles.php b/src/DomainRoles.php new file mode 100644 index 00000000..40d4090e --- /dev/null +++ b/src/DomainRoles.php @@ -0,0 +1,52 @@ +<?php +namespace Misuzu; + +use Index\XArray; + +class DomainRoles { + /** @var array<string, array<string, string>> */ + private array $domainRoles; + + /** @param string|array<string, array<string, string>> $domainRoles */ + public function __construct(string|array $domainRoles) { + if(is_string($domainRoles)) { + $domains = explode(';', $domainRoles); + $domainRoles = []; + foreach($domains as $domain) { + [$domain, $rolesRaw] = XArray::select(explode('=', $domain, 2), fn($part) => trim($part)); + + $roles = []; + $rolesRaw = explode(',', $rolesRaw); + foreach($rolesRaw as $roleRaw) { + $role = XArray::select(explode(':', $roleRaw, 2), fn($part) => trim($part)); + $roles[$role[0]] = empty($role[1]) ? '' : $role[1]; + } + + $domainRoles[$domain] = $roles; + } + } + + $this->domainRoles = $domainRoles; + } + + public function hasRole(string $domain, string $role): bool { + return array_key_exists($domain, $this->domainRoles) + && array_key_exists($role, $this->domainRoles[$domain]); + } + + /** @return string[] */ + public function getRoles(string $domain): array { + return array_key_exists($domain, $this->domainRoles) + ? array_keys($this->domainRoles[$domain]) + : []; + } + + public function getPrefix(string $domain, string $role): ?string { + if(!array_key_exists($domain, $this->domainRoles)) + return null; + if(!array_key_exists($role, $this->domainRoles[$domain])) + return null; + + return $this->domainRoles[$domain][$role]; + } +} diff --git a/src/Misuzu.php b/src/Misuzu.php index 91699354..7ad32fe1 100644 --- a/src/Misuzu.php +++ b/src/Misuzu.php @@ -39,4 +39,8 @@ final class Misuzu { public static function debug(): bool { return !empty(ini_get('display_errors')); } + + public static function cli(): bool { + return php_sapi_name() === 'cli'; + } } diff --git a/src/MisuzuContext.php b/src/MisuzuContext.php index 16a8b1fd..ed8203c9 100644 --- a/src/MisuzuContext.php +++ b/src/MisuzuContext.php @@ -18,6 +18,7 @@ class MisuzuContext { public private(set) Dependencies $deps; public private(set) Config $config; + public private(set) DomainRoles $domainRoles; public private(set) TplEnvironment $templating; public private(set) AuditLog\AuditLogData $auditLog; @@ -47,17 +48,18 @@ class MisuzuContext { public private(set) UrlRegistry $urls; public function __construct( - public private(set) Config $env + #[\SensitiveParameter] string $dsn, + string $domainRoles ) { $this->deps = new Dependencies; $this->deps->register($this); $this->deps->register($this->deps); - $this->deps->register($this->env); - $this->deps->register($this->dbCtx = new DatabaseContext($this->env->scopeTo('database'))); + $this->deps->register($this->dbCtx = new DatabaseContext($dsn)); $this->deps->register($this->dbCtx->conn); $this->deps->register($this->config = $this->deps->constructLazy(DbConfig::class, tableName: 'msz_config')); + $this->deps->register($this->domainRoles = $this->deps->constructLazy(DomainRoles::class, domainRoles: $domainRoles)); $this->deps->register($this->perms = $this->deps->constructLazy(Perms\PermissionsData::class)); $this->deps->register($this->authInfo = $this->deps->constructLazy(Auth\AuthInfo::class)); @@ -138,9 +140,8 @@ class MisuzuContext { } public function createRouting(HttpRequest $request): BackedRoutingContext { - $prefix = sprintf('domain:%s', $request->getHeaderLine('Host')); - $hostInfo = $this->env->scopeTo($prefix); - $purposes = $this->env->getArray($prefix); + $host = $request->getHeaderLine('Host'); + $roles = $this->domainRoles->getRoles($host); $routingCtx = new BackedRoutingContext; $this->deps->register($this->urls = $routingCtx->urls); @@ -150,15 +151,15 @@ class MisuzuContext { new HmacVerificationProvider(fn() => $this->config->getString('aleister.secret')) )); - if(in_array('main', $purposes)) + if(in_array('main', $roles)) $this->registerMainRoutes( - $routingCtx->scopeTo($hostInfo->getString('main:path')), + $routingCtx->scopeTo($this->domainRoles->getPrefix($host, 'main')), $rpcServer ); - if(in_array('redirect', $purposes)) + if(in_array('redirect', $roles)) $this->registerRedirectorRoutes( - $routingCtx->scopeTo($hostInfo->getString('redirect:path')) + $routingCtx->scopeTo($this->domainRoles->getPrefix($host, 'redirect')) ); return $routingCtx; diff --git a/src/Perms/PermissionsData.php b/src/Perms/PermissionsData.php index 3eb24bce..ddf9ac2e 100644 --- a/src/Perms/PermissionsData.php +++ b/src/Perms/PermissionsData.php @@ -6,6 +6,7 @@ use InvalidArgumentException; use RuntimeException; use Index\XDateTime; use Index\Db\{DbConnection,DbStatement,DbStatementCache,DbTools}; +use Misuzu\Misuzu; use Misuzu\Forum\{ForumCategoriesData,ForumCategoryInfo}; use Misuzu\Users\{RoleInfo,UserInfo}; @@ -381,7 +382,7 @@ class PermissionsData { * @param bool|float|int|string|null ...$args */ private static function precalculatePermissionsLog(string $fmt, ...$args): void { - if(php_sapi_name() === 'cli') + if(!Misuzu::cli()) return; echo XDateTime::now()->format('[H:i:s.u] '); diff --git a/src/Whoops/SentryPageHandler.php b/src/Whoops/SentryPageHandler.php new file mode 100644 index 00000000..12f81a58 --- /dev/null +++ b/src/Whoops/SentryPageHandler.php @@ -0,0 +1,38 @@ +<?php +namespace Misuzu\Whoops; + +use Throwable; +use Misuzu\Misuzu; +use Sentry\EventId; +use Whoops\Handler\Handler; + +class SentryPageHandler extends Handler { + /** @return int */ + public function handle() { + $throwable = $this->getException(); + $eventId = $throwable instanceof Throwable ? \Sentry\captureException($throwable) : null; + $path = Misuzu::PATH_PUBLIC . DIRECTORY_SEPARATOR . 'error-500.html'; + $message = $eventId instanceof EventId + ? sprintf("Error has been reported: <code>%s</code>", (string)$eventId) + : 'Error could not be reported.'; + + http_response_code(500); + if(is_file($path)) { + $body = file_get_contents($path); + if(is_string($body)) + $body = str_replace( + '<span class=error-additional></span>', + sprintf('<br>%s', $message), + $body + ); + } + + if(empty($body) || !is_string($body)) + $body = '<!doctype html>Something went very wrong. Please report what you were trying to do to a developer. ' . $message; + + header('Content-Type: text/html; charset=utf-8'); + echo $body; + + return Handler::QUIT; + } +} diff --git a/src/Whoops/SentryPlainTextHandler.php b/src/Whoops/SentryPlainTextHandler.php new file mode 100644 index 00000000..f001c063 --- /dev/null +++ b/src/Whoops/SentryPlainTextHandler.php @@ -0,0 +1,19 @@ +<?php +namespace Misuzu\Whoops; + +use Throwable; +use Sentry\EventId; +use Whoops\Handler\PlainTextHandler; + +class SentryPlainTextHandler extends PlainTextHandler { + #[\Override] + public function generateResponse() { + $throwable = $this->getException(); + $eventId = $throwable instanceof Throwable ? \Sentry\captureException($throwable) : null; + $message = $eventId instanceof EventId + ? sprintf('Error has been reported: %s', (string)$eventId) + : 'Error could not be reported.'; + + return parent::generateResponse() . $message . "\n"; + } +} diff --git a/templates/errors/master.twig b/templates/errors/master.twig index 89db9d99..a532bbff 100644 --- a/templates/errors/master.twig +++ b/templates/errors/master.twig @@ -38,7 +38,7 @@ <i class="{{ error_icon }}"></i> </div> <h1 class="error-title">{{ error_string }}</h1> - <p class="error-blerb">{{ error_blerb }}</p> + <p class="error-blerb">{{ error_blerb }}<span class=error-additional></span></p> </article> <footer class="error-footer"> <p>