__("Error")]);
_view("template_navigation_start");
if (!$skipLoginCheck)
_view("template_navigation", ["user" => RequestUtils::getAuthorizedUser($GLOBALS["db"])]);
_view("template_navigation_end");
_view("alert_error", ["message" => $err]);
_view("template_end", [...getThemeAndLangInfo()]);
}
function msg_info(string $msg, bool $skipLoginCheck = false): void {
_view("template_start", ["_title" => __("Information")]);
_view("template_navigation_start");
if (!$skipLoginCheck)
_view("template_navigation", ["user" => RequestUtils::getAuthorizedUser($GLOBALS["db"])]);
_view("template_navigation_end");
_view("alert_info", ["message" => $msg]);
_view("template_end", [...getThemeAndLangInfo()]);
}
function generateCaptchaText(): string {
$phrase = "";
for ($i = 0; $i < CAPTCHA_PHRASE_LENGTH; ++$i)
$phrase .= CAPTCHA_CHARSET[random_int(0, strlen(CAPTCHA_CHARSET) - 1)];
return $phrase;
}
function generatePasswordResetLink(Database &$db, User &$user): ?string {
$token = $db->generateId(20);
$user->passwordResetToken = $token;
$user->passwordResetTokenCreated = new \DateTimeImmutable();
if (!$db->update($user)) {
return null;
}
return env("PUBLIC_URL") . "?_action=pwreset&token=" . urlencode($user->passwordResetToken) . "&sig=" . urlencode(base64_encode(hash("sha256", env("SECRET") . $user->passwordResetToken . $user->id . $user->passwordHash, true)));
}
function decodePasswordResetLink(Database &$db, string $token, string $signature): ?User {
$user = new User();
$user->passwordResetToken = $token;
if (!$db->fetchWhere($user, "password_reset_token")) {
return null;
}
$then = $user->passwordResetTokenCreated;
$now = new \DateTimeImmutable();
if (($now->getTimestamp() - $then->getTimestamp()) >= 3600) {
return null;
}
$expectedSignature = base64_encode(hash("sha256", env("SECRET") . $user->passwordResetToken . $user->id . $user->passwordHash, true));
if ($expectedSignature !== $signature) {
return null;
}
return $user;
}
function getThemeAndLangInfo(): array {
$availableThemes = [];
$currentTheme = $_GET["theme"] ?? $_COOKIE["theme"] ?? env("MYSTIC_FORUM_THEME") ?? "default";
foreach (scandir($dir = __DIR__ . '/themes/') as $ent) {
if ($ent[0] === "." || !is_dir($dir . "/" . $ent) || !is_file($theme_file = $dir . "/" . $ent . "/theme.json"))
continue;
$theme_info = json_decode(file_get_contents($theme_file));
$availableThemes[$ent] = $theme_info;
}
$availableLangs = [
"en" => "English",
];
foreach (i18n_get_available_locales() as $loc) {
if (isset($availableLangs[$loc]))
continue;
$metadata = i18n_metadata($loc);
$availableLangs[$loc] = $metadata["langName"] ?? $loc;
}
return [
"availableThemes" => $availableThemes,
"currentTheme" => $currentTheme,
"availableLangs" => $availableLangs,
"currentLang" => i18n_get_current_locale(),
];
}
function _view(string $___NAME, array $___PARAMS = []): void {
$___PATH = __DIR__ . "/application/views/" . $___NAME . ".php";
if (!is_file($___PATH)) {
echo "\n";
echo "
Failed to include " . htmlentities($___NAME) . "
\n" . implode("\n", $lineBuf) . "\n"; $lineBuf = []; $inQuote = false; if (trim($ln) === "
" . implode("\n", $lineBuf) . ""; } else { $contents .= implode("\n", $lineBuf); } $contents = expandTags($contents); return $contents; } function env(string $key): ?string { $val = getenv($key); if ($val === false) return null; return $val; } require_once __DIR__ . "/vendor/autoload.php"; require_once __DIR__ . "/application/i18n.php"; if ($_SERVER["REQUEST_METHOD"] === "GET" && isset($_GET["lang"])) { parse_str($_SERVER["QUERY_STRING"], $query); if (empty($query["lang"])) { setcookie("lang", "", 100); } else { setcookie("lang", $query["lang"], time()+60*60*24*30); } unset($query["lang"]); $path = strtok($_SERVER["REQUEST_URI"], "?"); header("Location: $path?" . http_build_query($query)); exit; } $user_locale = env("LOCALE") ?? $_COOKIE["lang"] ?? locale_accept_from_http($_SERVER["HTTP_ACCEPT_LANGUAGE"] ?? ""); $chosen_locale = locale_lookup(i18n_get_available_locales(), $user_locale, true, "en"); i18n_locale($user_locale); $db = null; try { $db = new Database(Database::getConnectionString("db", getenv("POSTGRES_USER"), getenv("POSTGRES_PASSWORD"), getenv("POSTGRES_DBNAME"))); } catch (DatabaseConnectionException $ex) { msg_error( __("Failed to connect to database:\n%details%", [ "details" => $ex->getMessage(), ]), true ); exit; } $GLOBALS["db"] = &$db; $db->ensureTable(User::class); $db->ensureTable(Topic::class); $db->ensureTable(Post::class); $db->ensureTable(Attachment::class); $db->ensureTable(TopicLogMessage::class); $superuser = new User(); $superuser->id = "SUPERUSER"; if (!$db->fetch($superuser)) { $superUserPassword = base64_encode(random_bytes(12)); $suEmail = env("MYSTIC_FORUM_SUPERUSER_EMAIL"); if ($suEmail === null) { http_response_code(500); Messaging::error("No superuser email defined. Please set the 'MYSTIC_FORUM_SUPERUSER_EMAIL' environment variable accordingly"); exit; } $superuser->name = "superuser"; $superuser->email = $suEmail; $superuser->passwordHash = password_hash($superUserPassword, PASSWORD_DEFAULT); $superuser->displayName = "SuperUser"; $superuser->created = new \DateTimeImmutable(); $superuser->permissionMask = PHP_INT_MAX; $superuser->passwordResetRequired = false; $superuser->activated = true; $superuser->activationToken = ""; $db->insert($superuser); Messaging::info([ Messaging::bold("Superuser account created"), [ "Username" => $superuser->name, "Password" => $superUserPassword, ], "Please note that the password can only be shown this time, so please note it down!", ]); exit; } $currentUser = RequestUtils::getAuthorizedUser($db); $GLOBALS["currentUser"] = &$currentUser; // initialization finished function invalid_action(string $_action): never { http_response_code(404); msg_error(__("Invalid or unknown action \"%action%\"", [ "action" => $_action, ])); exit; } if ($_action !== null && !preg_match('/^[a-z][a-z0-9_]*$/', $_action)) invalid_action($_action); $_action ??= "_default"; $actionDir = __DIR__ . "/application/actions/$_action"; if (!is_dir($actionDir)) invalid_action($_action); $actionMethod = strtolower(preg_replace('/[^A-Za-z]/', '', $_rq_method)); if (is_file($commonFile = $actionDir . "/_common.php")) include $commonFile; if (is_file($anyFile = $actionDir . "/_any.php")) include $anyFile; elseif (is_file($methodFile = $actionDir . "/$actionMethod.php")) include $methodFile; else { http_response_code(405); Messaging::error("Invalid request method " . RequestUtils::getRequestMethod()); }