39638-vm/includes/app.php
Flatlogic Bot 1fafa67d4f kei
2026-04-14 13:52:40 +00:00

267 lines
12 KiB
PHP

<?php
declare(strict_types=1);
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
require_once __DIR__ . '/../db/config.php';
require_once __DIR__ . '/content.php';
function h(?string $value): string
{
return htmlspecialchars((string) $value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}
function copy_text(array $copy, string $lang = 'id'): string
{
return $copy[$lang] ?? ($copy['id'] ?? '');
}
function copy_attrs(array $copy, bool $html = false): string
{
$extra = $html ? ' data-copy-html="1"' : '';
return ' data-copy-id="' . h($copy['id'] ?? '') . '" data-copy-jp="' . h($copy['jp'] ?? '') . '"' . $extra;
}
function placeholder_attrs(array $copy): string
{
return ' data-placeholder-id="' . h($copy['id'] ?? '') . '" data-placeholder-jp="' . h($copy['jp'] ?? '') . '"';
}
function asset_url(string $path): string
{
$full = __DIR__ . '/../' . ltrim($path, '/');
$version = is_file($full) ? (string) filemtime($full) : (string) time();
return $path . '?v=' . $version;
}
function project_name(): string
{
return $_SERVER['PROJECT_NAME'] ?? 'KOBA Entertainment Indonesia';
}
function page_title(string $title): string
{
return $title . ' | ' . project_name();
}
function render_head(string $title, string $description, array $options = []): void
{
$pageDescription = $description;
$projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? $pageDescription;
$projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
$robots = $options['robots'] ?? 'index, follow';
echo '<!doctype html><html lang="id"><head>';
echo '<meta charset="utf-8" />';
echo '<meta name="viewport" content="width=device-width, initial-scale=1" />';
echo '<title>' . h(page_title($title)) . '</title>';
echo '<meta name="description" content="' . h($projectDescription ?: $pageDescription) . '" />';
echo '<meta name="robots" content="' . h($robots) . '" />';
echo '<meta name="theme-color" content="#0e2a66" />';
echo '<meta property="og:title" content="' . h(page_title($title)) . '" />';
echo '<meta property="og:description" content="' . h($projectDescription ?: $pageDescription) . '" />';
echo '<meta property="og:type" content="website" />';
echo '<meta property="twitter:card" content="summary_large_image" />';
echo '<meta property="twitter:title" content="' . h(page_title($title)) . '" />';
echo '<meta property="twitter:description" content="' . h($projectDescription ?: $pageDescription) . '" />';
if ($projectImageUrl) {
echo '<meta property="og:image" content="' . h($projectImageUrl) . '" />';
echo '<meta property="twitter:image" content="' . h($projectImageUrl) . '" />';
}
echo '<link rel="preconnect" href="https://fonts.googleapis.com">';
echo '<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>';
echo '<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">';
echo '<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">';
echo '<link rel="stylesheet" href="' . h(asset_url('assets/css/custom.css')) . '">';
echo '</head><body>';
}
function render_header(string $active = 'home'): void
{
$navItems = kei_nav_items();
?>
<header class="site-header">
<nav class="navbar navbar-expand-lg navbar-light py-0">
<div class="container-fluid kei-container">
<a class="navbar-brand site-brand" href="index.php" aria-label="KOBA Entertainment Indonesia homepage">
<span class="brand-mark">KEI</span>
<span class="brand-copy">
<strong>KOBA Entertainment Indonesia</strong>
<small>Modern entertainment bridge</small>
</span>
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#keiNav" aria-controls="keiNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse justify-content-end" id="keiNav">
<ul class="navbar-nav align-items-lg-center gap-lg-1 mb-3 mb-lg-0">
<?php foreach ($navItems as $item): ?>
<li class="nav-item">
<a class="nav-link <?= $active === $item['key'] ? 'active' : '' ?>" href="<?= h($item['href']) ?>"<?= copy_attrs($item['label']) ?>><?= h(copy_text($item['label'])) ?></a>
</li>
<?php endforeach; ?>
</ul>
<div class="d-flex align-items-center gap-2 ms-lg-3">
<button class="btn btn-ghost btn-sm" type="button" data-lang-toggle aria-label="Switch language">
<svg width="16" height="16" viewBox="0 0 16 16" aria-hidden="true"><path fill="currentColor" d="M8 1a7 7 0 1 0 0 14A7 7 0 0 0 8 1Zm4.95 6h-2.16a11.8 11.8 0 0 0-.74-3.16A5.53 5.53 0 0 1 12.95 7ZM8 2.52c.6.82 1.25 2.34 1.55 4.48H6.45C6.75 4.86 7.4 3.34 8 2.52ZM5.95 3.84A11.8 11.8 0 0 0 5.21 7H3.05a5.53 5.53 0 0 1 2.9-3.16ZM3.05 9h2.16c.1 1.12.36 2.2.74 3.16A5.53 5.53 0 0 1 3.05 9Zm4.95 4.48c-.6-.82-1.25-2.34-1.55-4.48h3.1c-.3 2.14-.95 3.66-1.55 4.48Zm2.05-1.32c.38-.96.64-2.04.74-3.16h2.16a5.53 5.53 0 0 1-2.9 3.16Z"/></svg>
<span class="ms-2" data-lang-current>ID</span>
</button>
<a class="btn btn-brand btn-sm" href="register.php"<?= copy_attrs(['id' => 'Daftar Sekarang', 'jp' => '今すぐ応募']) ?>>Daftar Sekarang</a>
</div>
</div>
</div>
</nav>
</header>
<?php
}
function render_footer(): void
{
?>
<footer class="site-footer section-space-sm">
<div class="container-fluid kei-container">
<div class="row g-4 align-items-end">
<div class="col-lg-6">
<p class="eyebrow mb-3"<?= copy_attrs(['id' => 'KOBA Entertainment Indonesia', 'jp' => 'KOBA Entertainment Indonesia']) ?>>KOBA Entertainment Indonesia</p>
<h2 class="footer-title"<?= copy_attrs(['id' => 'Building global entertainment from Indonesia.', 'jp' => 'インドネシアから世界水準のエンターテインメントを築く。']) ?>>Building global entertainment from Indonesia.</h2>
<p class="footer-copy"<?= copy_attrs(['id' => 'Situs resmi untuk branding perusahaan, kolaborasi proyek, dan konversi talent baru yang siap tumbuh bersama KEI.', 'jp' => '本サイトは、企業ブランディング、プロジェクト連携、新しいタレント募集のための公式窓口です。']) ?>>Situs resmi untuk branding perusahaan, kolaborasi proyek, dan konversi talent baru yang siap tumbuh bersama KEI.</p>
</div>
<div class="col-lg-6">
<div class="footer-grid">
<div>
<span class="footer-label"<?= copy_attrs(['id' => 'Kontak', 'jp' => '連絡先']) ?>>Kontak</span>
<a href="mailto:hello@kobaentertainment.id">hello@kobaentertainment.id</a>
<span>Jakarta, Indonesia</span>
</div>
<div>
<span class="footer-label"<?= copy_attrs(['id' => 'Navigasi', 'jp' => 'ナビゲーション']) ?>>Navigasi</span>
<a href="profile.php">Profil</a>
<a href="news.php">Berita</a>
<a href="admin/login.php">Admin Login</a>
</div>
</div>
</div>
</div>
<div class="footer-bottom">
<span>&copy; <?= date('Y') ?> KOBA Entertainment Indonesia</span>
<a href="healthz.php">/healthz</a>
</div>
</div>
</footer>
<div class="toast-container position-fixed top-0 end-0 p-3">
<div id="keiToast" class="toast align-items-center text-bg-dark border-0" role="status" aria-live="polite" aria-atomic="true">
<div class="d-flex">
<div class="toast-body">Bahasa Indonesia aktif.</div>
<button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast" aria-label="Close"></button>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous" defer></script>
<script src="<?= h(asset_url('assets/js/main.js')) ?>" defer></script>
</body></html>
<?php
}
function flash_set(string $key, $value): void
{
$_SESSION['_flash'][$key] = $value;
}
function flash_get(string $key)
{
$value = $_SESSION['_flash'][$key] ?? null;
unset($_SESSION['_flash'][$key]);
return $value;
}
function ensure_applications_table(): void
{
$sql = "CREATE TABLE IF NOT EXISTS talent_applications (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
full_name VARCHAR(190) NOT NULL,
email VARCHAR(190) NOT NULL,
whatsapp VARCHAR(50) NOT NULL,
category VARCHAR(50) NOT NULL,
photo_url VARCHAR(255) DEFAULT NULL,
video_url VARCHAR(255) DEFAULT NULL,
consent_accuracy TINYINT(1) NOT NULL DEFAULT 0,
consent_audition TINYINT(1) NOT NULL DEFAULT 0,
consent_contract TINYINT(1) NOT NULL DEFAULT 0,
consent_data TINYINT(1) NOT NULL DEFAULT 0,
status VARCHAR(20) NOT NULL DEFAULT 'review',
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci";
db()->exec($sql);
}
function normalize_whatsapp(string $value): string
{
$value = preg_replace('/[^0-9+]/', '', $value) ?? '';
return trim($value);
}
function handle_optional_upload(string $field, array $allowedMimeMap, int $maxBytes, string $subdir): ?string
{
if (empty($_FILES[$field]) || (int) $_FILES[$field]['error'] === UPLOAD_ERR_NO_FILE) {
return null;
}
$file = $_FILES[$field];
if ((int) $file['error'] !== UPLOAD_ERR_OK) {
throw new RuntimeException('Upload gagal untuk ' . $field . '.');
}
if ((int) $file['size'] > $maxBytes) {
throw new RuntimeException('Ukuran file ' . $field . ' terlalu besar.');
}
$finfo = new finfo(FILEINFO_MIME_TYPE);
$mime = (string) $finfo->file($file['tmp_name']);
if (!isset($allowedMimeMap[$mime])) {
throw new RuntimeException('Format file ' . $field . ' tidak didukung.');
}
$targetDir = __DIR__ . '/../assets/uploads/' . $subdir;
if (!is_dir($targetDir) && !mkdir($targetDir, 0775, true) && !is_dir($targetDir)) {
throw new RuntimeException('Folder upload tidak dapat dibuat.');
}
$filename = $subdir . '-' . date('YmdHis') . '-' . bin2hex(random_bytes(4)) . '.' . $allowedMimeMap[$mime];
$target = $targetDir . '/' . $filename;
if (!move_uploaded_file($file['tmp_name'], $target)) {
throw new RuntimeException('Gagal menyimpan file ' . $field . '.');
}
return 'assets/uploads/' . $subdir . '/' . $filename;
}
function admin_credentials(): array
{
$user = getenv('KEI_ADMIN_USER') ?: (getenv('ADMIN_EMAIL') ?: 'admin@kei.local');
$pass = getenv('KEI_ADMIN_PASSWORD') ?: (getenv('ADMIN_PASSWORD') ?: 'KEIadmin2026!');
return [$user, $pass];
}
function using_default_admin_credentials(): bool
{
return !getenv('KEI_ADMIN_USER') && !getenv('ADMIN_EMAIL') && !getenv('KEI_ADMIN_PASSWORD') && !getenv('ADMIN_PASSWORD');
}
function is_admin(): bool
{
return !empty($_SESSION['kei_admin_logged_in']);
}
function require_admin(): void
{
if (!is_admin()) {
header('Location: login.php');
exit;
}
}
function status_badge_class(string $status): string
{
return match ($status) {
'accepted' => 'badge bg-success-subtle text-success-emphasis',
'rejected' => 'badge bg-danger-subtle text-danger-emphasis',
default => 'badge bg-secondary-subtle text-secondary-emphasis',
};
}