135 lines
3.6 KiB
PHP
135 lines
3.6 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
function config(string $key, mixed $default = null): mixed
|
|
{
|
|
static $configs = [];
|
|
|
|
[$file, $path] = array_pad(explode('.', $key, 2), 2, null);
|
|
if (!array_key_exists($file, $configs)) {
|
|
$configFile = dirname(__DIR__, 2) . '/config/' . $file . '.php';
|
|
$configs[$file] = is_file($configFile) ? require $configFile : [];
|
|
}
|
|
|
|
$value = $configs[$file];
|
|
if ($path === null) {
|
|
return $value;
|
|
}
|
|
|
|
foreach (explode('.', $path) as $segment) {
|
|
if (!is_array($value) || !array_key_exists($segment, $value)) {
|
|
return $default;
|
|
}
|
|
$value = $value[$segment];
|
|
}
|
|
|
|
return $value;
|
|
}
|
|
|
|
function e(mixed $value): string
|
|
{
|
|
return htmlspecialchars((string) $value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
|
|
}
|
|
|
|
function url(string $path = ''): string
|
|
{
|
|
$basePath = (string) config('app.base_path', '');
|
|
$path = '/' . ltrim($path, '/');
|
|
return ($basePath !== '' ? $basePath : '') . $path;
|
|
}
|
|
|
|
function asset(string $path): string
|
|
{
|
|
return url('assets/' . ltrim($path, '/'));
|
|
}
|
|
|
|
function csrf_token(): string
|
|
{
|
|
if (empty($_SESSION['_csrf'])) {
|
|
$_SESSION['_csrf'] = bin2hex(random_bytes(32));
|
|
}
|
|
|
|
return $_SESSION['_csrf'];
|
|
}
|
|
|
|
function csrf_field(): string
|
|
{
|
|
return '<input type="hidden" name="_csrf" value="' . e(csrf_token()) . '">';
|
|
}
|
|
|
|
function verify_csrf(): void
|
|
{
|
|
$submitted = $_POST['_csrf'] ?? '';
|
|
if (!is_string($submitted) || !hash_equals(csrf_token(), $submitted)) {
|
|
http_response_code(403);
|
|
render('errors/419', ['title' => '요청을 다시 확인해 주세요']);
|
|
exit;
|
|
}
|
|
}
|
|
|
|
function redirect(string $path, int $status = 302): never
|
|
{
|
|
header('Location: ' . url($path), true, $status);
|
|
exit;
|
|
}
|
|
|
|
function render(string $view, array $data = [], string $layout = 'layouts/site'): void
|
|
{
|
|
$viewFile = dirname(__DIR__) . '/Views/' . $view . '.php';
|
|
$layoutFile = dirname(__DIR__) . '/Views/' . $layout . '.php';
|
|
if (!is_file($viewFile) || !is_file($layoutFile)) {
|
|
throw new RuntimeException('View not found.');
|
|
}
|
|
|
|
extract($data, EXTR_SKIP);
|
|
ob_start();
|
|
require $viewFile;
|
|
$content = (string) ob_get_clean();
|
|
ob_start();
|
|
require $layoutFile;
|
|
$html = (string) ob_get_clean();
|
|
|
|
$basePath = (string) config('app.base_path', '');
|
|
if ($basePath !== '') {
|
|
$html = (string) preg_replace_callback(
|
|
'/\b(href|src|action)="(\/(?!\/)[^"]*)"/',
|
|
static function (array $matches) use ($basePath): string {
|
|
$path = $matches[2];
|
|
if ($path === $basePath || str_starts_with($path, $basePath . '/')) return $matches[0];
|
|
return $matches[1] . '="' . $basePath . $path . '"';
|
|
},
|
|
$html
|
|
);
|
|
}
|
|
echo $html;
|
|
}
|
|
|
|
function old(string $key, string $default = ''): string
|
|
{
|
|
return e($_POST[$key] ?? $default);
|
|
}
|
|
|
|
function is_active_path(string $path): bool
|
|
{
|
|
$current = parse_url($_SERVER['REQUEST_URI'] ?? '/', PHP_URL_PATH) ?: '/';
|
|
$basePath = (string) config('app.base_path', '');
|
|
if ($basePath !== '' && ($current === $basePath || str_starts_with($current, $basePath . '/'))) {
|
|
$current = substr($current, strlen($basePath)) ?: '/';
|
|
}
|
|
return $path === '/' ? $current === '/' : str_starts_with($current, $path);
|
|
}
|
|
|
|
function site_setting(string $key, mixed $default = ''): mixed
|
|
{
|
|
static $settings = null;
|
|
if ($settings === null) {
|
|
try {
|
|
$settings = (new \App\Repositories\SiteSettingRepository())->all();
|
|
} catch (\Throwable) {
|
|
$settings = [];
|
|
}
|
|
}
|
|
return $settings[$key] ?? $default;
|
|
}
|