diff options
author | Jonas Kohl | 2024-10-10 17:33:13 +0200 |
---|---|---|
committer | Jonas Kohl | 2024-10-10 17:33:13 +0200 |
commit | 64b1ec0fabbf7328a79a20ff58502ebfa80fad8b (patch) | |
tree | 88f2281295b347bdd3beee5bc45f68314f2051dc /src/index.php | |
parent | 4ffc399a847ce4f328d4f14adebb48d06ad033f9 (diff) |
Break up actions into individual files
Diffstat (limited to 'src/index.php')
-rw-r--r-- | src/index.php | 1552 |
1 files changed, 24 insertions, 1528 deletions
diff --git a/src/index.php b/src/index.php index 39ff77a..88c555e 100644 --- a/src/index.php +++ b/src/index.php @@ -1,9 +1,5 @@ <?php -use FFMpeg\Coordinate\TimeCode; -use FFMpeg\FFMpeg; -use FFMpeg\FFProbe; -use Gregwar\Captcha\CaptchaBuilder; use mystic\forum\Database; use mystic\forum\exceptions\DatabaseConnectionException; use mystic\forum\Messaging; @@ -12,19 +8,11 @@ use mystic\forum\orm\Post; use mystic\forum\orm\Topic; use mystic\forum\orm\TopicLogMessage; use mystic\forum\orm\User; -use mystic\forum\orm\UserPermissions; -use mystic\forum\utils\FileUtils; use mystic\forum\utils\RequestUtils; -use mystic\forum\utils\ValidationUtils; -use Symfony\Component\Mailer\Exception\TransportException; -use Symfony\Component\Mailer\Transport; -use Symfony\Component\Mime\Address; -use Symfony\Component\Mime\Email; -use Symfony\Contracts\Service\Attribute\Required; header_remove("X-Powered-By"); -const MYSTICBB_VERSION = "0.3.1"; +const MYSTICBB_VERSION = "0.4.0"; if (($_SERVER["HTTP_USER_AGENT"] ?? "") === "") { http_response_code(403); @@ -33,6 +21,8 @@ if (($_SERVER["HTTP_USER_AGENT"] ?? "") === "") { define("REGISTRATION_ENABLED", isTrue(env("REGISTRATION_ENABLED") ?? "")); +const __ROOT__ = __DIR__; + session_name("fsid"); session_start(); @@ -299,1526 +289,32 @@ $GLOBALS["currentUser"] = &$currentUser; // initialization finished -if ($_action === "auth") { - if ($currentUser) { - header("Location: " . ($_GET["next"] ?? ".")); - exit; - } - - if (RequestUtils::isRequestMethod("POST")) { - $formId = "login"; - $username = RequestUtils::getRequiredField("username", $formId); - $password = RequestUtils::getRequiredField("password", $formId); - - $user = new User(); - $user->name = $username; - if (!$db->fetchWhere($user, "name") || !password_verify($password, $user->passwordHash)) { - RequestUtils::triggerFormError(__("Username or password incorrect!"), $formId); - } - - if (!$user->activated) { - RequestUtils::triggerFormError(__("Please activate your user account first!"), $formId); - } - - RequestUtils::setAuthorizedUser($user); - header("Location: " . ($_GET["next"] ?? ".")); - } else { - _view("template_start", ["_title" => __("Log in")]); - _view("template_navigation_start"); - _view("template_navigation", ["user" => RequestUtils::getAuthorizedUser($db)]); - _view("template_navigation_end"); - _view("form_login"); - _view("template_end", [...getThemeAndLangInfo()]); - } -} elseif ($_action === "register") { - if ($currentUser) { - header("Location: " . ($_GET["next"] ?? ".")); - exit; - } - - if (!REGISTRATION_ENABLED) { - http_response_code(403); - msg_error(__("Public registration disabled")); - exit; - } - - if (RequestUtils::isRequestMethod("POST")) { - $formId = "register"; - $doNotFill = $_POST["username"] ?? null; - if (!empty($doNotFill)) { - sleep(10); - http_response_code(204); - exit; - } - $username = RequestUtils::getRequiredField("df82a9bc21", $formId); - $password = RequestUtils::getRequiredField("password", $formId); - $passwordRetype = RequestUtils::getRequiredField("password_retype", $formId); - $email = trim(RequestUtils::getRequiredField("email", $formId)); - $displayName = RequestUtils::getRequiredField("display_name", $formId); - $captcha = RequestUtils::getRequiredField("captcha", $formId); - - if ($captcha !== ($_SESSION["captchaPhrase"] ?? null)) { - RequestUtils::triggerFormError(__("Incorrect CAPTCHA text!"), $formId); - } - - // usernames are always lowercase - $username = strtolower($username); - - if ($password !== $passwordRetype) { - RequestUtils::triggerFormError(__("Passwords do not match!"), $formId); - } - - if (strlen($password) < 8) { - RequestUtils::triggerFormError(__("Password too short! Your password must consist of 8 or more characters"), $formId); - } - - if (!ValidationUtils::isUsernameValid($username)) { - RequestUtils::triggerFormError(__("Username has an invalid format"), $formId); - } - - if (filter_var($email, FILTER_VALIDATE_EMAIL) === false) { - RequestUtils::triggerFormError(__("Invalid email address"), $formId); - } - - $user = new User(); - $user->name = $username; - $user->email = $email; - - if ($db->fetchWhere($user, "name")) { - RequestUtils::triggerFormError(__("This username is already taken!"), $formId); - } - - if ($db->fetchWhere($user, "email")) { - RequestUtils::triggerFormError(__("This email address is already in use!"), $formId); - } - - // re-create user so we don't forget to clear properties set by the above queries - - $user = new User(); - $user->id = $db->generateId(); - $user->displayName = $displayName; - $user->name = $username; - $user->email = $email; - $user->passwordHash = password_hash($password, PASSWORD_DEFAULT); - $user->permissionMask = UserPermissions::GROUP_USER; - $user->passwordResetRequired = false; - $user->activated = false; - $user->activationToken = $db->generateId(12); - $user->created = new \DateTimeImmutable(); - - Transport::fromDsn(env("MAILER_DSN"))->send( - (new Email()) - ->from(env("MAILER_FROM")) - ->to(new Address($email, $displayName)) - ->text(__( - "Welcome to %forum_title%, %user_display_name%!\n" . - "\n" . - "Please activate your account by clicking the link below:\n" . - "%activation_link%\n" . - "\n" . - "Kind regards,\n" . - "%forum_copyright%", - params: [ - "forum_title" => (env("MYSTIC_FORUM_TITLE") ?? "Forum"), - "user_display_name" => $displayName, - "activation_link" => env("PUBLIC_URL") . "?_action=verifyemail&token=" . urlencode($user->activationToken) . "&sig=" . urlencode(base64_encode(hash("sha256", env("SECRET") . $user->activationToken . $user->id, true))), - "forum_copyright" => (env("MYSTIC_FORUM_COPYRIGHT") ?? env("MYSTIC_FORUM_TITLE") ?? "Forum") - ] - )) - ->subject(__("Please activate your account")) - ); - - $db->insert($user); - - msg_info(__("Your account has been created!\nPlease check your emails for an activation link!")); - } else { - _view("template_start", ["_title" => __("Register")]); - _view("template_navigation_start"); - _view("template_navigation", ["user" => RequestUtils::getAuthorizedUser($db)]); - _view("template_navigation_end"); - _view("form_register"); - _view("template_end", [...getThemeAndLangInfo()]); - } -} elseif ($_action === "verifyemail") { - RequestUtils::ensureRequestMethod("GET"); - $token = $_GET["token"] ?? throw new Exception("Missing token"); - $sig = $_GET["sig"] ?? throw new Exception("Missing signature"); - - $user = new User(); - $user->activationToken = $token; - - if (!$db->fetchWhere($user, "activation_token")) { - http_response_code(400); - msg_error(__("Invalid token")); - exit; - } - - $expectedSignature = base64_encode(hash("sha256", env("SECRET") . $user->activationToken . $user->id, true)); - - if ($expectedSignature !== $sig) { - http_response_code(400); - msg_error(__("Invalid signature.")); - exit; - } - - $isActivation = !$user->activated; - if ($isActivation) { - $user->activated = true; - $user->activationToken = ""; - - if (!$db->update($user)) { - http_response_code(400); - msg_error(__("Failed to update user")); - exit; - } - - msg_info("?!HTML::" . __( - "Your account has been activated!\nPlease click %link%here%/link% to log in!", - [ - "link" => '<a href="?_action=auth">', - "/link" => '</a>', - ] - )); - } else { - $oldEmail = $user->email; - $newEmail = $user->pendingEmail; - - $user->activationToken = ""; - $user->email = $user->pendingEmail; - $user->pendingEmail = null; - $user->pendingEmailCreated = null; - - if (!$db->update($user)) { - http_response_code(400); - msg_error(__("Failed to update user")); - exit; - } - - $transport = Transport::fromDsn(env("MAILER_DSN")); - - try { - $transport->send( - (new Email()) - ->from(env("MAILER_FROM")) - ->to(new Address($oldEmail, $user->displayName)) - ->text(__( - "Hello, %user_display_name%!\n" . - "\n" . - "Your email address has been successfully changed from %old_email% to %new_email%!\n" . - "\n" . - "Kind regards,\n" . - "%forum_copyright%", - params: [ - "forum_title" => (env("MYSTIC_FORUM_TITLE") ?? "Forum"), - "user_display_name" => $user->displayName, - "old_email" => $oldEmail, - "new_email" => $newEmail, - "forum_copyright" => (env("MYSTIC_FORUM_COPYRIGHT") ?? env("MYSTIC_FORUM_TITLE") ?? "Forum") - ] - )) - ->subject(__("Email address changed")) - ); - } catch (TransportException $_) { - // fail silently - } - - try { - $transport->send( - (new Email()) - ->from(env("MAILER_FROM")) - ->to(new Address($newEmail, $user->displayName)) - ->text(__( - "Hello, %user_display_name%!\n" . - "\n" . - "Your email address has been successfully changed from %old_email% to %new_email%!\n" . - "\n" . - "Kind regards,\n" . - "%forum_copyright%", - params: [ - "forum_title" => (env("MYSTIC_FORUM_TITLE") ?? "Forum"), - "user_display_name" => $user->displayName, - "old_email" => $oldEmail, - "new_email" => $newEmail, - "forum_copyright" => (env("MYSTIC_FORUM_COPYRIGHT") ?? env("MYSTIC_FORUM_TITLE") ?? "Forum") - ] - )) - ->subject(__("Email address changed")) - ); - } catch (TransportException $_) { - // fail silently - } - - msg_info("?!HTML::" . __( - "Your email address has been changed successfully!\nPlease click %link%here%/link% to return to your profile!", - [ - "link" => '<a href="?_action=viewuser&user=' . htmlentities(urlencode($user->id)) . '">', - "/link" => '</a>', - ] - )); - } -} elseif ($_action === "logout") { - RequestUtils::unsetAuthorizedUser(); - header("Location: " . ($_GET["next"] ?? ".")); -} elseif ($_action === "viewtopic") { - $formId = "addpost"; - $topicId = $_GET["topic"] ?? throw new Exception("Missing topic id"); - $topic = new Topic(); - $topic->id = $topicId; - if (!$db->fetch($topic)) { - http_response_code(404); - msg_error("No topic exists with this id"); - exit; - } - - if (RequestUtils::isRequestMethod("POST")) { - if (!$currentUser) { - http_response_code(403); - msg_error("You need to be logged in to add new posts!"); - exit; - } - - if ($topic->isLocked) { - http_response_code(403); - msg_error("This topic is locked!"); - exit; - } - - $attachments = reArrayFiles($_FILES["files"]); - - if (count($attachments) > MAX_ATTACHMENT_COUNT) - RequestUtils::triggerFormError(__("Too many attachments"), $formId); - - // check all attachments before saving one - foreach ($attachments as $att) { - if ($att["size"] > MAX_ATTACHMENT_SIZE) { - RequestUtils::triggerFormError(__("Individual file size exceeded"), $formId); - } - } - - $message = trim(RequestUtils::getRequiredField("message", $formId)); - - if (strlen($message) < 1 || strlen($message) > 0x8000) { - RequestUtils::triggerFormError(__("Message too short or too long!"), $formId); - } - - $item = new Post(); - $item->id = $db->generateId(); - $item->authorId = $currentUser->id; - $item->topicId = $topicId; - $item->content = $message; - $item->postDate = new DateTimeImmutable(); - $item->deleted = false; - $item->edited = false; - - $db->insert($item); - - foreach ($attachments as $att) { - [ - "name" => $name, - "type" => $type, - "tmp_name" => $tmpName, - ] = $att; - $attachment = new Attachment(); - $attachment->id = $db->generateId(); - $attachment->name = $name; - $attachment->mimeType = $type; - $attachment->postId = $item->id; - $attachment->contents = file_get_contents($tmpName); - - $db->insert($attachment); - } - - header("Location: ?_action=viewtopic&topic=" . urlencode($topicId) . "#form"); - } else { - /** @var Post[] $posts */ - $posts = $db->fetchCustom(Post::class, 'WHERE topic_id = $1 ORDER BY post_date', [ $topicId ]); - /** @var TopicLogMessage[] $logMessages */ - $logMessages = $db->fetchCustom(TopicLogMessage::class, 'WHERE topic_id = $1 ORDER BY post_date', [ $topicId ]); - $userCache = []; - - $topicAuthor = null; - if ($topic->createdBy !== null) { - $topicAuthor = new User(); - $topicAuthor->id = $topic->createdBy; - if (!$db->fetch($topicAuthor)) { - $topicAuthor = null; - } - } - - $allItems = [...$posts, ...$logMessages]; - usort($allItems, fn(Post|TopicLogMessage $a, Post|TopicLogMessage $b): int => $a->postDate <=> $b->postDate); - - _view("template_start", ["_title" => $topic->title]); - _view("template_navigation_start"); - _view("template_navigation", ["user" => RequestUtils::getAuthorizedUser($db)]); - _view("template_navigation_end"); - _view("view_topic_start", ["topic" => $topic, "topicAuthor" => $topicAuthor]); - - foreach ($allItems as $item) { - /** @var ?User $postAuthor */ - $postAuthor = null; - if ($item->authorId !== null && !isset($userCache[$item->authorId])) { - $usr = new User(); - $usr->id = $item->authorId; - if ($db->fetch($usr)) - $userCache[$item->authorId] = &$usr; - } - if (isset($userCache[$item->authorId])) - $postAuthor = &$userCache[$item->authorId]; - - if ($item instanceof Post) { - $attachments = $db->fetchCustom(Attachment::class, 'WHERE post_id = $1', [ $item->id ]); - - _view("view_post", [ - "post" => $item, - "postAuthor" => $postAuthor, - "topicAuthor" => $topicAuthor, - "attachments" => $attachments, - "topic" => $topic, - ]); - } else { - _view("view_topiclog", [ - "logMessage" => $item, - "postAuthor" => $postAuthor, - "topicAuthor" => $topicAuthor, - "topic" => $topic, - ]); - } - } - - _view("view_topic_end"); - - if ($topic->isLocked) { - _view("view_topic_locked"); - } elseif ($currentUser) { - _view("form_addpost"); - } else { - _view("view_logintoreply"); - } - - _view("template_end", [...getThemeAndLangInfo()]); - } - -} elseif ($_action === "newtopic") { - if (!$currentUser) { - http_response_code(403); - msg_error("You need to be logged in to create new topics!"); - exit; - } - - if (RequestUtils::isRequestMethod("POST")) { - $formId = "newtopic"; - $title = trim(RequestUtils::getRequiredField("title", $formId)); - $message = trim(RequestUtils::getRequiredField("message", $formId)); - - $attachments = reArrayFiles($_FILES["files"]); - - if (count($attachments) > MAX_ATTACHMENT_COUNT) - RequestUtils::triggerFormError(__("Too many attachments"), $formId); - - // check all attachments before saving one - foreach ($attachments as $att) { - if ($att["size"] > MAX_ATTACHMENT_SIZE) { - RequestUtils::triggerFormError(__("Individual file size exceeded"), $formId); - } - } - - if (strlen($title) < 1 || strlen($title) > 255) { - RequestUtils::triggerFormError(__("Title too short or too long!"), $formId); - } - - if (strlen($message) < 1 || strlen($message) > 0x8000) { - RequestUtils::triggerFormError(__("Message too short or too long!"), $formId); - } - - $topic = new Topic(); - $topic->createdBy = $currentUser->id; - $topic->id = $db->generateId(); - $topic->title = $title; - $topic->creationDate = new DateTimeImmutable(); - $topic->isLocked = false; - - $db->insert($topic); - - $item = new Post(); - $item->id = $db->generateId(); - $item->authorId = $currentUser->id; - $item->topicId = $topic->id; - $item->content = $message; - $item->postDate = $topic->creationDate; - $item->deleted = false; - $item->edited = false; - - $db->insert($item); - - foreach ($attachments as $att) { - [ - "name" => $name, - "type" => $type, - "tmp_name" => $tmpName, - ] = $att; - $attachment = new Attachment(); - $attachment->id = $db->generateId(); - $attachment->name = $name; - $attachment->mimeType = $type; - $attachment->postId = $item->id; - $attachment->contents = file_get_contents($tmpName); - - $db->insert($attachment); - } - - header("Location: ?_action=viewtopic&topic=" . urlencode($topic->id)); - } else { - _view("template_start", ["_title" => __("New topic")]); - _view("template_navigation_start"); - _view("template_navigation", ["user" => RequestUtils::getAuthorizedUser($db)]); - _view("template_navigation_end"); - _view("form_newtopic"); - _view("template_end", [...getThemeAndLangInfo()]); - } -} elseif ($_action === "lookupuser") { - RequestUtils::ensureRequestMethod("GET"); - $userHandle = $_GET["handle"] ?? throw new Exception("Missing handle"); - - $user = new User(); - $user->name = $userHandle; - - if (!$db->fetchWhere($user, "name")) { - http_response_code(404); - msg_error(__("No user with name @%user_handle%", [ "user_handle" => $userHandle ])); - exit; - } - - header("Location: ./?_action=viewuser&user=" . urlencode($user->id)); -} elseif ($_action === "viewuser") { - $userId = $_GET["user"] ?? throw new Exception("Missing user id"); - $user = new User(); - $user->id = $userId; - if (!$db->fetch($user)) { - http_response_code(404); - msg_error(__("No user exists with this id")); - exit; - } - - $lastNameChangeTooRecent = false; - $isOwnProfile = $user->id === $currentUser?->id; - if ($isOwnProfile && $user->nameLastChanged !== null) { - $diff = $user->nameLastChanged->diff(new DateTime()); - $diffSeconds = (new DateTime())->setTimestamp(0)->add($diff)->getTimestamp(); - $diffDays = $diffSeconds / 60.0 / 60.0 / 24.0 / 30.0; - $lastNameChangeTooRecent = $diffDays <= 30; - } - - if (RequestUtils::isRequestMethod("POST")) { - $formId = $_POST["form_id"] ?? null; - if ($formId === null) { - http_response_code(400); - msg_error("Missing form_id"); - exit; - } - - if ($formId === "update_password") { - if (!$currentUser) { - http_response_code(403); - msg_error(__("You must be logged in to update your password")); - exit; - } - - if (!$isOwnProfile) { - RequestUtils::triggerFormError(__("You don't have permission to update this user's password"), $formId); - } - - RequestUtils::ensureRequestMethod("POST"); - $currentPassword = RequestUtils::getRequiredField("current_password", $formId); - $newPassword = RequestUtils::getRequiredField("new_password", $formId); - $retypePassword = RequestUtils::getRequiredField("retype_password", $formId); - - if (!password_verify($currentPassword, $currentUser->passwordHash)) { - RequestUtils::triggerFormError(__("Current password is incorrect"), $formId); - } - - if ($newPassword !== $retypePassword) { - RequestUtils::triggerFormError(__("New passwords don't match"), $formId); - } - - if (strlen($newPassword) < 8) { - RequestUtils::triggerFormError(__("Password too short! Your password must consist of 8 or more characters"), $formId); - } - - $currentUser->passwordHash = password_hash($newPassword, PASSWORD_DEFAULT); - - if (!$db->update($currentUser)) { - RequestUtils::triggerFormError(__("Failed to update password"), $formId); - } - - header("Location: $_SERVER[REQUEST_URI]"); - } elseif ($formId === "update_profile") { - if (!$currentUser) { - http_response_code(403); - msg_error(__("You must be logged in to update your profile")); - exit; - } - - $canEdit = ($currentUser?->id === $user?->id && $user?->hasPermission(UserPermissions::EDIT_OWN_USER)) - || ($currentUser?->hasPermission(UserPermissions::EDIT_OTHER_USER)); - - if (!$canEdit) { - http_response_code(403); - msg_error(__("You don't have permission to update this profile")); - exit; - } - - $displayName = RequestUtils::getRequiredField("display_name", $formId); - $pfpAction = RequestUtils::getRequiredField("pfp_action", $formId); - - $userName = $_POST["name"] ?? $user->name; - $email = $_POST["email"] ?? $user->email; - - $user->displayName = $displayName; - - $userName = strtolower($userName); - - if ($userName !== $user->name) { - if ($lastNameChangeTooRecent) { - RequestUtils::triggerFormError(__("You can only change your username every 30 days!"), $formId); - } else { - if (!ValidationUtils::isUsernameValid($userName)) - RequestUtils::triggerFormError(__("Invalid username!"), $formId); - if (!ValidationUtils::isUsernameAvailable($db, $userName)) - RequestUtils::triggerFormError(__("This username is already taken!"), $formId); - $user->name = $userName; - $user->nameLastChanged = new DateTimeImmutable(); - } - } - - if ($email !== $user->email) { - if ($user->pendingEmailCreated !== null) { - RequestUtils::triggerFormError(__("Please verify your email first!"), $formId); - } else { - $queryUser = new User(); - $queryUser->email = $email; - $queryUser->pendingEmail = $email; - if ($db->fetchWhere($queryUser, "email") || $db->fetchWhere($queryUser, "pending_email")) { - RequestUtils::triggerFormError(__("This email address is already in use!"), $formId); - } - $user->pendingEmail = $email; - $user->pendingEmailCreated = new DateTimeImmutable(); - $user->activationToken = $db->generateId(12); - - try { - Transport::fromDsn(env("MAILER_DSN"))->send( - (new Email()) - ->from(env("MAILER_FROM")) - ->to(new Address($email, $displayName)) - ->text(__( - "Hello, %user_display_name%!\n" . - "\n" . - "Please verify your new email address by clicking the link below:\n" . - "%verify_link%\n" . - "\n" . - "Kind regards,\n" . - "%forum_copyright%", - params: [ - "forum_title" => (env("MYSTIC_FORUM_TITLE") ?? "Forum"), - "user_display_name" => $displayName, - "verify_link" => env("PUBLIC_URL") . "?_action=verifyemail&token=" . urlencode($user->activationToken) . "&sig=" . urlencode(base64_encode(hash("sha256", env("SECRET") . $user->activationToken . $user->id, true))), - "forum_copyright" => (env("MYSTIC_FORUM_COPYRIGHT") ?? env("MYSTIC_FORUM_TITLE") ?? "Forum") - ] - )) - ->subject(__("Please verify your email address")) - ); - } catch (TransportException $_) { - RequestUtils::triggerFormError(__("Failed to send verification email"), $formId); - } - } - } - - switch ($pfpAction) { - case "keep": - // Do nothing - break; - case "remove": - $user->profilePicture = null; - break; - case "replace": { - if (!isset($_FILES["pfp"]) || $_FILES["pfp"]["error"] !== UPLOAD_ERR_OK) { - RequestUtils::triggerFormError(__("Please upload an image to change your profile picture"), $formId); - } - $im = @imagecreatefromjpeg($_FILES["pfp"]["tmp_name"]); - if ($im === false) - $im = @imagecreatefrompng($_FILES["pfp"]["tmp_name"]); - if ($im === false) - RequestUtils::triggerFormError(__("Please upload a valid PNG or JPEG file"), $formId); - /** @var \GdImage $im */ - $thumb = imagecreatetruecolor(64, 64); - imagecopyresampled($thumb, $im, 0, 0, 0, 0, 64, 64, imagesx($im), imagesy($im)); - imagedestroy($im); - $stream = fopen("php://memory", "w+"); - imagejpeg($thumb, $stream, 50); - rewind($stream); - imagedestroy($thumb); - $user->profilePicture = stream_get_contents($stream); - fclose($stream); - } break; - default: - RequestUtils::triggerFormError("Invalid value for pfp_action", $formId); - break; - } - - if (!$db->update($user)) - RequestUtils::triggerFormError(__("Failed to save changes", context: "Update profile"), $formId); - - header("Location: $_SERVER[REQUEST_URI]"); - } else { - msg_error("Invalid formId"); - } - } else { - $posts = $db->fetchCustom(Post::class, 'WHERE author_id = $1 ORDER BY post_date DESC', [ $userId ]); - $topics = []; - $attachments = []; - foreach ($posts as $item) { - if (!isset($topics[$item->topicId])) { - $topic = new Topic(); - $topic->id = $item->topicId; - if ($db->fetch($topic)) - $topics[$item->topicId] = $topic; - } - $attachs = $db->fetchCustom(Attachment::class, 'WHERE post_id = $1', [ $item->id ]); - $attachments[$item->id] = $attachs; - } - _view("template_start", ["_title" => $user->displayName]); - _view("template_navigation_start"); - _view("template_navigation", [ - "user" => $currentUser, - "isViewingOwnProfile" => $isOwnProfile, - ]); - _view("template_navigation_end"); - _view("view_user", [ - "user" => $user, - "posts" => $posts, - "topics" => $topics, - "attachments" => $attachments, - "lastNameChangeTooRecent" => $lastNameChangeTooRecent, - ]); - _view("template_end", [...getThemeAndLangInfo()]); - } -} elseif ($_action === "attachment") { - if (!$currentUser) { - http_response_code(403); - msg_error(__("You must be logged in to view attachments")); - exit; - } - - $attId = $_GET["attachment"] ?? throw new Exception("Missing attachment id"); - $attachment = new Attachment(); - $attachment->id = $attId; - if (!$db->fetch($attachment)) { - http_response_code(404); - msg_error(__("No attachment exists with this id")); - exit; - } - - $name = preg_replace('/[\r\n\t\/]/', '_', $attachment->name); - - $extension = pathinfo($attachment->name, PATHINFO_EXTENSION); - - $mime = FileUtils::getMimeTypeForExtension($extension); - switch ($mime) { - case "text/html": - case "text/css": - case "text/javascript": - case "text/xml": - case "application/css": - case "application/javascript": - case "application/xml": - $mime = "text/plain"; - break; - } - header("Content-Type: " . $mime); - header("Content-Length: " . strlen($attachment->contents)); - header("Cache-Control: no-cache"); - header("Content-Disposition: inline; filename=\"" . $name . "\""); - echo $attachment->contents; -} elseif ($_action === "profilepicture") { - $userId = $_GET["user"] ?? throw new Exception("Missing user id"); - $user = new User(); - $user->id = $userId; - if (!$db->fetch($user)) { - http_response_code(404); - msg_error(__("No user exists with this id")); - exit; - } - - $ifNoneMatch = $_SERVER["HTTP_IF_NONE_MATCH"] ?? null; - if ($ifNoneMatch !== null) - $ifNoneMatch = trim($ifNoneMatch, '"'); - - if ($user->profilePicture === null) { - $fallback = __DIR__ . "/application/assets/user-fallback.jpg"; - $etag = md5("\0"); - header("Content-Type: image/jpeg"); - header("Content-Length: " . filesize($fallback)); - header("Cache-Control: no-cache"); - header("ETag: \"" . $etag . "\""); - if ($ifNoneMatch === $etag) - http_response_code(304); - else - readfile($fallback); - } else { - $etag = md5($user->profilePicture); - header("Content-Type: image/jpeg"); - header("Content-Length: " . strlen($user->profilePicture)); - header("Cache-Control: no-cache"); - header("ETag: \"" . $etag . "\""); - if ($ifNoneMatch === $etag) - http_response_code(304); - else - echo $user->profilePicture; - } -} elseif ($_action === "thumb") { - - $attId = $_GET["attachment"] ?? throw new Exception("Missing attachment id"); - $attachment = new Attachment(); - $attachment->id = $attId; - if (!$db->fetch($attachment)) { - http_response_code(404); - msg_error(__("No attachment exists with this id")); - exit; - } - - $isImage = str_starts_with($attachment->mimeType, "image/"); - $isVideo = str_starts_with($attachment->mimeType, "video/"); - - if (!$isImage && !$isVideo) { - http_response_code(400); - msg_error(__("Attachment is neither an image nor a video")); - exit; - } - - $contentHash = hash("sha256", $attachment->contents); - - $cacheId = bin2hex($attachment->id); - $cacheDir = sys_get_temp_dir() . "/mystic/forum/0/cache/thumbs/" . substr($cacheId, 0, 2) . "/" . substr($cacheId, 0, 8) . "/"; - if (!is_dir($cacheDir)) - mkdir($cacheDir, recursive: true); - - $cacheFileData = $cacheDir . $cacheId . ".data"; - $cacheFileInfo = $cacheDir . $cacheId . ".info"; - - if (is_file($cacheFileData) && is_file($cacheFileInfo)) { - $info = json_decode(file_get_contents($cacheFileInfo)); - if ($info->contentHash === $contentHash) { - header("Content-Type: image/jpeg"); - header("Cache-Control: max-age=86400"); - //header("X-Debug-Content: $cacheFileData"); - readfile($cacheFileData); - exit; - } - } - - if ($isVideo) { - $suffix = (microtime(true) * 1000) . "-" . random_int(0, 99999); - $tempVid = sys_get_temp_dir() . "/video_" . $suffix; - file_put_contents($tempVid, $attachment->contents); - $tempImg = sys_get_temp_dir() . "/image_" . $suffix . ".jpg"; - - try { - $ffprobe = FFProbe::create(); - /** @var string $duration */ - $duration = $ffprobe - ->format($tempVid) - ->get("duration", "0"); - - $screenshotFramePoint = TimeCode::fromSeconds(floatval($duration) / 2.0); - - $ffmpeg = FFMpeg::create(); - $video = $ffmpeg->open($tempVid); - $screenshot = $video - ->frame($screenshotFramePoint) - ->save($tempImg); - $im = imagecreatefromjpeg($tempImg); - } finally { - if (is_file($tempVid)) unlink($tempVid); - if (is_file($tempImg)) unlink($tempImg); - } - } elseif ($isImage) { - $im = imagecreatefromstring($attachment->contents); - } - - $w = imagesx($im); - $h = imagesy($im); - $r = $w / floatval($h); - - if ($w > $h) { - $nw = THUMB_MAX_DIM; - $nh = floor($nw / $r); - } else { - $nh = THUMB_MAX_DIM; - $nw = floor($r * $nh); - } - - $thumb = imagecreatetruecolor($nw, $nh); - imagecopyresampled($thumb, $im, 0, 0, 0, 0, $nw, $nh, $w, $h); - imagedestroy($im); - - header("Content-Type: image/jpeg"); - header("Cache-Control: max-age=86400"); - imagejpeg($thumb, $cacheFileData, 40); - imagedestroy($thumb); - file_put_contents($cacheFileInfo, json_encode([ - "format" => 1, - "contentHash" => $contentHash, - "created" => time(), - ], JSON_UNESCAPED_SLASHES)); - readfile($cacheFileData); -} elseif ($_action === "deletepost") { - RequestUtils::ensureRequestMethod("POST"); - - if (!$currentUser) { - http_response_code(403); - msg_error("You need to be logged in to delete posts!"); - exit; - } - $formId = "deletepost"; - $postId = RequestUtils::getRequiredField("post", $formId); - - $item = new Post(); - $item->id = $postId; - - if (!$db->fetch($item) || $item->deleted) { - http_response_code(404); - msg_error("No post exists with this id"); - exit; - } - - $topicAuthor = new User(); - $topicAuthor->id = $item->authorId; - - if (!$db->fetch($topicAuthor)) - $topicAuthor = null; - - $topic = new Topic(); - $topic->id = $item->topicId; - - if (!$db->fetch($topic)) - $topic = null; - - $canEdit = ($currentUser->id === $topicAuthor?->id && $topicAuthor?->hasPermission(UserPermissions::DELETE_OWN_POST)) - || ($currentUser->hasPermission(UserPermissions::DELETE_OTHER_POST)); - - if (!$canEdit) { - http_response_code(403); - msg_error("You don't have permission to delete this post"); - exit; - } - - $attachments = $db->fetchCustom(Attachment::class, 'WHERE post_id = $1', [ $item->id ]); - - $confirm = $_POST["confirm"] ?? null; - if ($confirm !== null) { - $expectedConfirm = base64_encode(hash("sha256", "confirm" . $item->id, true)); - if ($confirm !== $expectedConfirm) { - http_response_code(400); - msg_error("Invalid confirmation"); - exit; - } - - $item->deleted = true; - $item->content = ""; - - if (!$db->update($item)) { - http_response_code(500); - msg_error("Failed to delete post"); - exit; - } - - foreach ($attachments as $attachment) { - if (!$db->delete($attachment)) { - http_response_code(500); - msg_error("Failed to delete attachment"); - exit; - } - } - - header("Location: ?_action=viewtopic&topic=" . urlencode($item->topicId)); - } else { - _view("template_start", ["_title" => __("Delete post")]); - _view("template_navigation_start"); - _view("template_navigation", ["user" => RequestUtils::getAuthorizedUser($db)]); - _view("template_navigation_end"); - _view("form_delete_post_confirm", [ - "post" => $item, - "postAuthor" => $topicAuthor, - "topicAuthor" => null, - "attachments" => $attachments, - "topic" => $topic, - ]); - _view("template_end", [...getThemeAndLangInfo()]); - } -} elseif ($_action === "updatepost") { - RequestUtils::ensureRequestMethod("POST"); - - if (!$currentUser) { - http_response_code(403); - msg_error(__("You need to be logged in to update posts!")); - exit; - } - - $formId = "updatepost"; - $postId = RequestUtils::getRequiredField("post", $formId); - $message = RequestUtils::getRequiredField("message", $formId); - - $item = new Post(); - $item->id = $postId; - - if (!$db->fetch($item) || $item->deleted) { - http_response_code(404); - msg_error(__("No post exists with this id")); - exit; - } - - $topicAuthor = new User(); - $topicAuthor->id = $item->authorId; - - if (!$db->fetch($topicAuthor)) - $topicAuthor = null; - - $canEdit = ($currentUser->id === $topicAuthor?->id && $topicAuthor?->hasPermission(UserPermissions::EDIT_OWN_POST)) - || ($currentUser->hasPermission(UserPermissions::EDIT_OTHER_POST)); - - $topic = new Topic(); - $topic->id = $item->topicId; - - if (!$db->fetch($topic)) - $topic = null; - - if ($topic->isLocked) { - http_response_code(403); - msg_error(__("This topic has been locked")); - exit; - } - - if (!$canEdit) { - http_response_code(403); - msg_error(__("You don't have permission to edit this post")); - exit; - } - - $confirm = $_POST["confirm"] ?? null; - - $item->content = $message; - $item->edited = true; - - if (!$db->update($item)) { - http_response_code(500); - msg_error(__("Failed to update post")); - exit; - } - - header("Location: ?_action=viewtopic&topic=" . urlencode($item->topicId) . "#post-" . urlencode($postId)); -} elseif ($_action === "deletetopic") { - RequestUtils::ensureRequestMethod("POST"); - - if (!$currentUser) { - http_response_code(403); - msg_error(__("You need to be logged in to delete topics!")); - exit; - } - - $formId = "deletetopic"; - $topicId = RequestUtils::getRequiredField("topic", $formId); - - $topic = new Topic(); - $topic->id = $topicId; - - if (!$db->fetch($topic)) { - http_response_code(404); - msg_error(__("No topic exists with this id")); - exit; - } - - $topicAuthor = new User(); - $topicAuthor->id = $topic->createdBy; - - if (!$db->fetch($topicAuthor)) - $topicAuthor = null; - - $canEdit = ($currentUser->id === $topicAuthor?->id && $topicAuthor?->hasPermission(UserPermissions::DELETE_OWN_TOPIC)) - || ($currentUser->hasPermission(UserPermissions::DELETE_OTHER_TOPIC)); - - if (!$canEdit) { - http_response_code(403); - msg_error(__("You don't have permission to delete this topic")); - exit; - } - - $confirm = $_POST["confirm"] ?? null; - if ($confirm !== null) { - $expectedConfirm = base64_encode(hash("sha256", "confirm" . $topic->id, true)); - if ($confirm !== $expectedConfirm) { - http_response_code(400); - msg_error(__("Invalid confirmation")); - exit; - } - - if (!$db->delete($topic)) { - http_response_code(500); - msg_error(__("Failed to delete topic")); - exit; - } - - header("Location: ."); - } else { - _view("template_start", ["_title" => "Delete topic"]); - _view("template_navigation_start"); - _view("template_navigation", ["user" => RequestUtils::getAuthorizedUser($db)]); - _view("template_navigation_end"); - _view("form_delete_topic_confirm", [ - "topic" => $topic, - "topicAuthor" => $topicAuthor, - ]); - _view("template_end", [...getThemeAndLangInfo()]); - } -} elseif ($_action === "updatetopic") { - RequestUtils::ensureRequestMethod("POST"); - - if (!$currentUser) { - http_response_code(403); - msg_error(__("You need to be logged in to update topics!")); - exit; - } - - $formId = "updatetopic"; - $topicId = RequestUtils::getRequiredField("topic", $formId); - $title = RequestUtils::getRequiredField("title", $formId); - - $topic = new Topic(); - $topic->id = $topicId; - - if (!$db->fetch($topic)) { - http_response_code(404); - msg_error(__("No topic exists with this id")); - exit; - } - - $topicAuthor = new User(); - $topicAuthor->id = $topic->createdBy; - - if (!$db->fetch($topicAuthor)) - $topicAuthor = null; - - if ($topic->isLocked) { - http_response_code(403); - msg_error(__("This topic has been locked")); - exit; - } - - $canEdit = ($currentUser->id === $topicAuthor?->id && $topicAuthor?->hasPermission(UserPermissions::EDIT_OWN_TOPIC)) - || ($currentUser->hasPermission(UserPermissions::EDIT_OTHER_TOPIC)); - - if (!$canEdit) { - http_response_code(403); - msg_error(__("You don't have permission to update this topic")); - exit; - } - - $prevTitle = $topic->title; - $topic->title = $title; - - $log = new TopicLogMessage(); - $log->id = $db->generateId(); - $log->topicId = $topic->id; - $log->authorId = $currentUser->id; - $log->params = [ - "old_value" => $prevTitle, - "new_value" => $title, - ]; - $log->type = TopicLogMessage::TITLE_CHANGED; - $log->postDate = new \DateTimeImmutable(); - - $db->insert($log); - - if (!$db->update($topic)) { - http_response_code(500); - msg_error(__("Failed to update topic")); - exit; - } - - header("Location: ./?_action=viewtopic&topic=" . urlencode($topicId)); -} elseif ($_action === "locktopic") { - RequestUtils::ensureRequestMethod("POST"); - $topicId = $_POST["topic"] ?? null; - if ($topicId === null) { - http_response_code(400); - msg_error(__("Missing topic id")); - exit; - } - RequestUtils::setFormErrorDestination($dest = "Location: ./?_action=viewtopic&topic=" . urlencode($topicId)); - - if (!$currentUser) { - http_response_code(403); - msg_error(__("You need to be logged in to lock topics!")); - exit; - } - - $formId = "locktopic"; - $locked = RequestUtils::getRequiredField("locked", $formId); - if ($locked === "true") { - $locked = true; - } elseif ($locked === "false") { - $locked = false; - } else RequestUtils::triggerFormError("Invalid value", $formId); - - $topic = new Topic(); - $topic->id = $topicId; - - if (!$db->fetch($topic)) { - http_response_code(404); - msg_error(__("No topic exists with this id")); - exit; - } - - $topicAuthor = new User(); - $topicAuthor->id = $topic->createdBy; - - if (!$db->fetch($topicAuthor)) - $topicAuthor = null; - - $canEdit = ($currentUser->id === $topicAuthor?->id && $topicAuthor?->hasPermission(UserPermissions::EDIT_OWN_TOPIC)) - || ($currentUser->hasPermission(UserPermissions::EDIT_OTHER_TOPIC)); - - if (!$canEdit) { - http_response_code(403); - msg_error(__("You don't have permission to lock or unlock this topic")); - exit; - } - - $topic->isLocked = $locked; - - $log = new TopicLogMessage(); - $log->id = $db->generateId(); - $log->topicId = $topic->id; - $log->authorId = $currentUser->id; - $log->params = []; - $log->type = $locked ? TopicLogMessage::LOCKED : TopicLogMessage::UNLOCKED; - $log->postDate = new \DateTimeImmutable(); - - $db->insert($log); - - if (!$db->update($topic)) { - http_response_code(500); - msg_error(__("Failed to lock or unlock topic")); - exit; - } - - header($dest); -} elseif ($_action === "search") { - $query = $_GET["query"] ?? null; - if ($query !== null) { - $start_time = microtime(true); - /** @var Post[] $posts */ - $posts = $db->execCustomQuery(<<<SQL - SELECT posts.* FROM topics, posts - WHERE - NOT posts.deleted - AND to_tsvector('english', topics.title || ' ' || posts.content) @@ websearch_to_tsquery('english', $1) - ORDER BY posts.post_date DESC - ; - SQL, [ $query ], Post::class); - - $topicLookup = []; - $attachmentLookup = []; - $userLookup = []; - foreach ($posts as $item) { - if (!isset($topicLookup[$item->topicId])) { - $topic = new Topic; - $topic->id = $item->topicId; - if ($db->fetch($topic)) - $topicLookup[$topic->id] = &$topic; - } - if (!isset($attachmentLookup[$item->id])) { - $attachmentLookup[$item->id] = $db->fetchCustom(Attachment::class, 'WHERE post_id = $1', [ $item->id ]); - } - if (!isset($userLookup[$item->authorId])) { - $user = new User; - $user->id = $item->authorId; - if ($db->fetch($user)) - $userLookup[$item->authorId] = $user; - } - } - $end_time = microtime(true); - $search_duration = $end_time - $start_time; - - _view("template_start", ["_title" => __("Search results for “%query%”", [ "query" => $query ])]); - _view("template_navigation_start"); - _view("template_navigation", ["user" => RequestUtils::getAuthorizedUser($db)]); - _view("template_navigation_end"); - _view("form_search", [ "query" => $query ]); - _view("view_search_results", [ - "posts" => &$posts, - "topics" => &$topicLookup, - "users" => &$userLookup, - "attachments" => &$attachmentLookup, - "search_duration" => $search_duration, - ]); - _view("template_end", [...getThemeAndLangInfo()]); - } else { - _view("template_start", ["_title" => __("Search")]); - _view("template_navigation_start"); - _view("template_navigation", ["user" => RequestUtils::getAuthorizedUser($db)]); - _view("template_navigation_end"); - _view("form_search"); - _view("template_end", [...getThemeAndLangInfo()]); - } -} elseif ($_action === "captcha") { - $phrase = generateCaptchaText(); - $builder = new CaptchaBuilder($phrase); - $builder->build(192, 48); - $_SESSION["captchaPhrase"] = $phrase; - header("Content-Type: image/jpeg"); - header("Pragma: no-cache"); - header("Cache-Control: no-cache"); - $builder->save(null, 40); -} elseif ($_action === "ji18n") { - header("Content-Type: application/javascript; charset=UTF-8"); - echo 'var I18N_MESSAGES = ' . json_encode(i18n_get_message_store(i18n_get_current_locale()), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . ";\n"; -} elseif ($_action === "debug/list-themes") { - header("Content-Type: text/plain"); - 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)); - echo $ent . "\t" . $theme_info->name . "\n"; - } -} elseif ($_action === "settheme") { - RequestUtils::ensureRequestMethod("POST"); - $theme = $_POST["theme"] ?? exit(msg_error("Missing required field 'theme'") ?? 1); - $next = $_POST["next"] ?? "."; - setcookie("theme", $theme, time()+60*60*24*30); - header("Location: $next"); -} elseif ($_action === "setlang") { - RequestUtils::ensureRequestMethod("POST"); - $lang = $_POST["lang"] ?? exit(msg_error("Missing required field 'lang'") ?? 1); - $next = $_POST["next"] ?? "."; - setcookie("lang", $lang, time()+60*60*24*30); - header("Location: $next"); -} elseif ($_action === "pwreset") { - if ($currentUser) { - header("Location: ."); - exit; - } - - if (RequestUtils::isRequestMethod("POST")) { - $token = $_GET["token"] ?? null; - $signature = $_GET["sig"] ?? null; - - if ($token !== null && $signature !== null) { - RequestUtils::setFormErrorDestination("?_action=pwreset&token=" . urlencode($token) . "&sig=" . urlencode($signature)); - $formId = "pwnew"; - $newPassword = RequestUtils::getRequiredField("new_password", $formId); - $retypePassword = RequestUtils::getRequiredField("retype_password", $formId); - $resetUser = decodePasswordResetLink($db, $token, $signature); - - if ($resetUser === null) { - http_response_code(400); - msg_error(__("The password reset link is either invalid or it expired"), true); - exit; - } - - if ($newPassword !== $retypePassword) { - RequestUtils::triggerFormError(__("New passwords don't match"), $formId); - } - - if (strlen($newPassword) < 8) { - RequestUtils::triggerFormError(__("Password too short! Your password must consist of 8 or more characters"), $formId); - } - - $resetUser->passwordHash = password_hash($newPassword, PASSWORD_DEFAULT); - $resetUser->passwordResetToken = null; - $resetUser->passwordResetTokenCreated = null; - - if (!$db->update($resetUser)) { - RequestUtils::triggerFormError(__("Failed to update password"), $formId); - } - - Transport::fromDsn(env("MAILER_DSN"))->send( - (new Email()) - ->from(env("MAILER_FROM")) - ->to(new Address($resetUser->email, $resetUser->displayName)) - ->text(__( - "Hello, %user_display_name%!\n" . - "\n" . - "We are sending this email to let you know your passwort has been reset successfully!\n" . - "\n" . - "Kind regards,\n" . - "%forum_copyright%", - params: [ - "forum_title" => (env("MYSTIC_FORUM_TITLE") ?? "Forum"), - "user_display_name" => $resetUser->displayName, - "forum_copyright" => (env("MYSTIC_FORUM_COPYRIGHT") ?? env("MYSTIC_FORUM_TITLE") ?? "Forum") - ] - )) - ->subject(__("Password reset successfully!")) - ); - - msg_info(__("Password reset successfully!"), true); - } else { - $formId = "pwreset"; - $email = RequestUtils::getRequiredField("email", $formId); - - $user = new User(); - $user->email = $email; - - if ($db->fetchWhere($user, "email")) { - try { - Transport::fromDsn(env("MAILER_DSN"))->send( - (new Email()) - ->from(env("MAILER_FROM")) - ->to(new Address($user->email, $user->displayName)) - ->text(__( - "Hello, %user_display_name%!\n" . - "\n" . - "A password reset has been requested successfully! Please click the link below to set a new password:\n" . - "%reset_link%\n" . - "\n" . - "If this wasn't you, you can safely ignore this email. The link will only be valid for one hour.\n" . - "\n" . - "Kind regards,\n" . - "%forum_copyright%", - params: [ - "forum_title" => (env("MYSTIC_FORUM_TITLE") ?? "Forum"), - "user_display_name" => $user->displayName, - "reset_link" => generatePasswordResetLink($db, $user), - "forum_copyright" => (env("MYSTIC_FORUM_COPYRIGHT") ?? env("MYSTIC_FORUM_TITLE") ?? "Forum") - ] - )) - ->subject(__("Forgot your password? No problem!")) - ); - } catch (TransportException $_) { - // fail silently - } - } else { - // don't make the delay difference too obvious - usleep(random_int(900, 4500) * 1000); - - // ideally, at some point we would just want to queue up the email - // and send it asynchronously, but this'll have to do for now - } - - msg_info(__("If an account exists with the given email address, we will have sent a password reset link to that email address."), true); - } - } else { - $token = $_GET["token"] ?? null; - $signature = $_GET["sig"] ?? null; - - if ($token !== null && $signature !== null) { - $resetUser = decodePasswordResetLink($db, $token, $signature); - if ($resetUser === null) { - http_response_code(400); - msg_error(__("The password reset link is either invalid or it expired"), true); - exit; - } - - _view("template_start", [ "_title" => __("Reset password") ]); - _view("template_navigation_start"); - _view("template_navigation_end"); - _view("form_new_password", [ - "token" => $token, - "signature" => $signature, - ]); - _view("template_end", [...getThemeAndLangInfo()]); - } else { - _view("template_start", [ "_title" => __("Reset password") ]); - _view("template_navigation_start"); - _view("template_navigation", ["user" => null]); - _view("template_navigation_end"); - _view("form_password_reset"); - _view("template_end", [...getThemeAndLangInfo()]); - } - } -} elseif ($_action === "ctheme") { - // options - $enableLogging = true; - $etag_strip_gzip_suffix = true; +function invalid_action(string $_action): never { + http_response_code(404); + msg_error(__("Invalid or unknown action $_action")); + exit; +} - $cssFatal = function(string $msg): never { - if (!headers_sent()) - http_response_code(500); - echo "/*!FATAL $msg */\n"; - exit; - }; - $cssError = function(string &$buffer, string $msg) use($enableLogging): void { - if ($enableLogging) - $buffer .= "/*!ERROR $msg */\n"; - }; - $cssWarning = function(string &$buffer, string $msg) use ($enableLogging): void { - if ($enableLogging) - $buffer .= "/*!WARN $msg */\n"; - }; +if ($_action !== null && !preg_match('/^[a-z][a-z0-9_]*$/', $_action)) + invalid_action($_action); - $buffer = ""; +$_action ??= "_default"; - header("Content-Type: text/css; charset=UTF-8"); - header("Cache-Control: no-cache"); - // Disable Apache's gzip filter, as it interferes with our ETag - // (Apache adds '-gzip' as a ETag suffix) - if (!$etag_strip_gzip_suffix) - apache_setenv('no-gzip', '1'); +$actionDir = __DIR__ . "/application/actions/$_action"; - $themeName = $_GET["theme"] ?? $_COOKIE["theme"] ?? env("MYSTIC_FORUM_THEME") ?? "default"; - if (!preg_match('/^[a-z0-9_-]+$/i', $themeName)) { - $cssWarning($buffer, "Invalid theme '" . str_replace('*/', '*\\/', $themeName) . "'"); - $cssWarning($buffer, "Loading default theme"); - $themeName = "default"; - } - $themePath = __DIR__ . '/themes/' . $themeName . '/theme.json'; - $themeDefaultPath = __DIR__ . '/themes/default/theme.json'; - if (!is_file($themePath) && is_file($themeDefaultPath)) { - $cssWarning($buffer, "Invalid theme '" . str_replace('*/', '*\\/', $themeName) . "'"); - $cssWarning($buffer, "Loading default theme"); - $themePath = $themeDefaultPath; - } elseif (!is_file($themePath) && !is_file($themeDefaultPath)) { - $cssFatal("Failed to load default theme"); - } - $themeDir = dirname($themePath); - $theme = json_decode(file_get_contents($themePath)); - if ($theme->{'$format'} !== 1) - $cssFatal("Invalid theme format"); - foreach ($theme->files as $file) { - if (is_array($file)) { - if ($enableLogging) $buffer .= "/*!INLINE start */\n"; - $buffer .= implode("\n", $file); - if ($enableLogging) $buffer .= "/*!INLINE end */\n"; - } elseif (is_file($filePath = $themeDir . "/" . $file)) { - if ($enableLogging) $buffer .= "/*!INCLUDE " . basename($file) . " */\n"; - $buffer .= file_get_contents($filePath); - if ($enableLogging) $buffer .= "/*!INCLUDE end */\n"; - } else - $cssError($buffer, "Could not include file $file"); - } - $etag = md5($buffer); +if (!is_dir($actionDir)) + invalid_action($_action); - $ifNoneMatch = $_SERVER["HTTP_IF_NONE_MATCH"] ?? null; - if ($ifNoneMatch !== null) { - $ifNoneMatch = trim($ifNoneMatch, '"'); - if ($etag_strip_gzip_suffix) - $ifNoneMatch = preg_replace('/-gzip$/', '', $ifNoneMatch); - } +$actionMethod = strtolower(preg_replace('/[^A-Za-z]/', '', $_rq_method)); - header("ETag: \"$etag\""); - if ($ifNoneMatch === $etag) - http_response_code(304); - else - echo $buffer; +if (is_file($commonFile = $actionDir . "/_common.php")) + include $commonFile; -} elseif ($_action === null) { - _view("template_start"); - _view("template_navigation_start"); - _view("template_navigation", ["user" => RequestUtils::getAuthorizedUser($db)]); - _view("template_navigation_end"); - _view("view_topics", ["topics" => $db->fetchCustom(Topic::class, "ORDER BY creation_date DESC")]); - _view("template_end", [...getThemeAndLangInfo()]); -} else { - http_response_code(404); - msg_error(__("Invalid or unknown action $_action")); +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()); } |