summaryrefslogtreecommitdiff
path: root/src/index.php
diff options
context:
space:
mode:
Diffstat (limited to 'src/index.php')
-rw-r--r--src/index.php1552
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());
}