summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJonas Kohl <git@jonaskohl.de>2024-09-15 19:55:12 +0200
committerJonas Kohl <git@jonaskohl.de>2024-09-15 19:55:12 +0200
commitcb9b87997993702131ca24d4d0e1fd45ef64805c (patch)
tree3ba1725028665f90b546703c461394b577b3e596
parentcc97f36b8c9a9522636d5b50fbcd2f52de06a01a (diff)
Begun i18n work & more
-rw-r--r--.env.example4
-rw-r--r--Dockerfile11
-rw-r--r--src/application/i18n.php98
-rw-r--r--src/application/messages/de.msg117
-rw-r--r--src/application/messages/strings.mst111
-rw-r--r--src/application/mystic/forum/Database.php23
-rw-r--r--src/application/views/form_login.php2
-rw-r--r--src/application/views/form_register.php5
-rw-r--r--src/application/views/nav_guest.php4
-rw-r--r--src/index.php159
-rw-r--r--src/ui/site.css3
11 files changed, 477 insertions, 60 deletions
diff --git a/.env.example b/.env.example
index 0eba495..7682611 100644
--- a/.env.example
+++ b/.env.example
@@ -1,6 +1,10 @@
+SECRET=
POSTGRES_USER=postgres
POSTGRES_DB=postgres
POSTGRES_PASSWORD=postgres
+MAILER_DSN=
+MAILER_FROM=
+PUBLIC_URL=
#MYSTIC_FORUM_THEME=default
#REGISTRATION_ENABLED=1
#MYSTIC_FORUM_TITLE=
diff --git a/Dockerfile b/Dockerfile
index 03f50b5..d422818 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -10,13 +10,18 @@ RUN apt update && apt install -y \
libjpeg62-turbo-dev \
libxmp-dev \
libfreetype6-dev \
- libpq-dev
+ libpq-dev \
+ libicu-dev \
+ ;
RUN docker-php-ext-configure gd \
--with-webp \
--with-jpeg \
--with-freetype
-RUN docker-php-ext-install gd zip pgsql
-#RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
+RUN docker-php-ext-install gd zip pgsql intl
+RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
FROM base
COPY ./000-default.conf /etc/apache2/sites-available/000-default.conf
+#COPY ./src /var/www/html
+#WORKDIR /var/www/html
+#RUN composer install
diff --git a/src/application/i18n.php b/src/application/i18n.php
new file mode 100644
index 0000000..964439f
--- /dev/null
+++ b/src/application/i18n.php
@@ -0,0 +1,98 @@
+<?php
+
+const MESSAGE_DIR = __DIR__ . "/messages";
+
+$__i18n_msg_store = [];
+$__i18n_current_locale = null;
+
+function i18n_parse(string $contents, ?string $filename = null): array {
+ $syntax_error = fn(string $msg, ?int $line = null): never =>
+ throw new Exception("i18n syntax error: $msg (in " . ($filename ?? "unknown") . ":" . ($line ?? 0) . ")");
+
+ $msgs = [];
+ $lines = explode("\n", $contents);
+ $currentId = "";
+ $currentContext = "";
+ $currentMessage = "";
+ $isInMessage = false;
+ foreach ($lines as $i => $ln) {
+ $lnNum = $i + 1;
+ if (trim($ln) === "")
+ continue;
+
+ switch ($ln[0]) {
+ case "#":
+ continue 2;
+ case ":":
+ if ($currentId !== "") {
+ if ($currentContext !== "")
+ $currentId = $currentContext . "\004" . $currentId;
+ $msgs[$currentId] ??= $currentMessage;
+ }
+ $currentId = json_decode(substr($ln, 2));
+ $currentContext = "";
+ $currentMessage = "";
+ $isInMessage = false;
+ break;
+ case "=":
+ $isInMessage = true;
+ $currentMessage = json_decode(substr($ln, 2));
+ break;
+ case "?":
+ if ($isInMessage)
+ $syntax_error("context must be defined before start of message", $lnNum);
+ $currentContext = json_decode(substr($ln, 2));
+ break;
+ case " ":
+ if ($isInMessage)
+ $currentMessage .= json_decode(substr($ln, 2));
+ else
+ $currentId .= json_decode(substr($ln, 2));
+ break;
+ default:
+ $syntax_error("invalid start of line '" . $ln[0] . "' (0x" . str_pad(strtoupper(dechex(ord($ln[0]))), 2, "0", STR_PAD_LEFT) . ")", $lnNum);
+ break;
+ }
+ }
+
+ if ($currentId !== "") {
+ if ($currentContext !== "")
+ $currentId = $currentContext . "\x7F" . $currentId;
+ $msgs[$currentId] ??= $currentMessage;
+ }
+ return $msgs;
+}
+
+function i18n_locale(string $locale) {
+ global $__i18n_current_locale;
+ $__i18n_current_locale = $locale;
+}
+
+function i18n_get(string $msgid, array $params = [], ?string $context = null): string {
+ global $__i18n_current_locale, $__i18n_msg_store;
+
+ $key = $msgid;
+ if ($context !== null)
+ $key = $context . "\x7F" . $msgid;
+
+ $msg = ($__i18n_msg_store[$__i18n_current_locale] ?? [])[$msgid] ?? $key;
+
+ uksort($params, fn(string $a, string $b): int => strlen($b) <=> strlen($a));
+ return str_replace(
+ array_map(fn(string $k): string => "%$k%", array_keys($params)),
+ array_values($params),
+ $msg
+ );
+}
+
+function __(string $msgid, array $params = [], ?string $context = null): string {
+ return i18n_get($msgid, $params, $context);
+}
+
+foreach (scandir(MESSAGE_DIR) as $ent) {
+ $path = MESSAGE_DIR . "/" . $ent;
+ if ($ent[0] === "." || !is_file($path) || strcasecmp(pathinfo($ent, PATHINFO_EXTENSION), "msg") !== 0)
+ continue;
+ $lang = pathinfo($ent, PATHINFO_FILENAME);
+ $__i18n_msg_store[$lang] = i18n_parse(file_get_contents($path), $ent);
+}
diff --git a/src/application/messages/de.msg b/src/application/messages/de.msg
new file mode 100644
index 0000000..5ee2f6c
--- /dev/null
+++ b/src/application/messages/de.msg
@@ -0,0 +1,117 @@
+: "Log in"
+= "Anmelden"
+
+: "Register"
+= "Registrieren"
+
+: "Failed to connect to database!"
+= "Fehler bei Datenbankverbindung"
+
+: "Username or password incorrect!"
+= "Nutzername oder Passwort falsch!"
+
+: "Please activate your user account first!"
+= "Bitte aktivieren Sie Ihr Nutzerkonto zuerst!"
+
+: "Public registration disabled"
+= "Öffentliche Registrierung deaktiviert"
+
+: "Incorrect CAPTCHA text!"
+= "Ungültiger CAPTCHA-Text!"
+
+: "Passwords do not match!"
+= "Passwörter stimmen nicht überein!"
+
+: "Password too short! Your password must consist of 8 or more characters"
+= "Passwort zu kurz! Es muss aus mindestens acht Zeichen bestehen"
+
+: "Username has an invalid format"
+= "Der Nutzername weist ein ungültiges Format auf"
+
+: "Invalid email address"
+= "Ungültige E-Mail-Adresse"
+
+: "This username is already taken!"
+= "Dieser Nutzername ist bereits vergeben!"
+
+: "This email address is already in use!"
+= "Diese E-Mail-Adresse wird bereits verwendet!"
+
+: "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%"
+= "Willkommen bei %forum_title%, %user_display_name%!\n"
+ "\n"
+ "Bitte aktivieren Sie Ihr Nutzerkonto, indem Sie folgenen Link anklicken:\n"
+ "%activation_link%\n"
+ ""
+ "Mit freundlichen Grüßen,\n"
+ "%forum_copyright%"
+
+: "Your account has been created!\nPlease check your emails for an activation link!"
+= "Ihr Nutzerkonto wurde erstellt!\nBitte überprüfen Sie Ihre E-Mails auf einen Aktivierungslink!"
+
+: "Invalid token"
+= "Ungültiges Token"
+
+: "Invalid signature."
+= "Ungültige Signatur"
+
+: "Failed to update user"
+= "Konnte Nutzer nicht aktualisieren"
+
+: "Your account has been activated!\nPlease click %link%here%/link% to log in!"
+= "Ihr Nutzerkonto wurde aktiviert!\nBitte klicken Sie %link%hier%/link%, um sich anzumelden!"
+
+: "Too many attachments"
+= "Zu viele Anhänge"
+
+: "Individual file size exceeded"
+= "Anhang zu groß"
+
+: "Message too short or too long!"
+= "Nachricht zu kurz oder zu lang"
+
+: "Title too short or too long!"
+= "Titel zu kurz oder zu lang"
+
+: "New topic"
+= "Neues Thema"
+
+: "No user with name @%user_handle%"
+= "Kein Nutzer mit dem Namen @%user_handle%"
+
+: "No user exists with this id"
+= "Kein Nutzer existiert mit dieser ID"
+
+: "You can only change your username every 30 days!"
+= "Sie können Ihren Nutzernamen nur alle 30 Tage ändern!"
+
+: "Invalid username!"
+= "Ungültiger Nutzername!"
+
+: "Please upload an image to change your profile picture"
+= "Bitte laden Sie ein Bild hoch, um Ihr Profilbild zu ändern"
+
+: "Please upload a valid PNG or JPEG file"
+= "Bitte laden Sie eine gültige PNG- oder JPEG-Datei hoch"
+
+: "Failed to save changes"
+? "Update profile"
+= "Konnte Änderungen nicht übernehmen"
+
+: "You must be logged in to view attachments"
+= "Sie müssen angemeldet sein, um Anhänge sehen zu können"
+
+: "No attachment exists with this id"
+= "Kein Anhang exsitier mit dieser ID"
+
+: "Attachment is not an image"
+= "Anhang ist kein Bild"
+
+: "Delete post"
+= "Beitrag löschen"
diff --git a/src/application/messages/strings.mst b/src/application/messages/strings.mst
new file mode 100644
index 0000000..6f6ec38
--- /dev/null
+++ b/src/application/messages/strings.mst
@@ -0,0 +1,111 @@
+: "Log in"
+= ""
+
+: "Register"
+= ""
+
+: "Failed to connect to database!"
+= ""
+
+: "Username or password incorrect!"
+= ""
+
+: "Please activate your user account first!"
+= ""
+
+: "Public registration disabled"
+= ""
+
+: "Incorrect CAPTCHA text!"
+= ""
+
+: "Passwords do not match!"
+= ""
+
+: "Password too short! Your password must consist of 8 or more characters"
+= ""
+
+: "Username has an invalid format"
+= ""
+
+: "Invalid email address"
+= ""
+
+: "This username is already taken!"
+= ""
+
+: "This email address is already in use!"
+= ""
+
+: "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%"
+= ""
+
+: "Your account has been created!\nPlease check your emails for an activation link!"
+= ""
+
+: "Invalid token"
+= ""
+
+: "Invalid signature."
+= ""
+
+: "Failed to update user"
+= ""
+
+: "Your account has been activated!\nPlease click %link%here%/link% to log in!"
+= ""
+
+: "Too many attachments"
+= ""
+
+: "Individual file size exceeded"
+= ""
+
+: "Message too short or too long!"
+= ""
+
+: "Title too short or too long!"
+= ""
+
+: "New topic"
+= ""
+
+: "No user with name @%user_handle%"
+= ""
+
+: "No user exists with this id"
+= ""
+
+: "You can only change your username every 30 days!"
+= ""
+
+: "Invalid username!"
+= ""
+
+: "Please upload an image to change your profile picture"
+= ""
+
+: "Please upload a valid PNG or JPEG file"
+= ""
+
+: "Failed to save changes"
+? "Update profile"
+= ""
+
+: "You must be logged in to view attachments"
+= ""
+
+: "No attachment exists with this id"
+= ""
+
+: "Attachment is not an image"
+= ""
+
+: "Delete post"
+= ""
diff --git a/src/application/mystic/forum/Database.php b/src/application/mystic/forum/Database.php
index 7c9ac7a..6d633fa 100644
--- a/src/application/mystic/forum/Database.php
+++ b/src/application/mystic/forum/Database.php
@@ -333,15 +333,28 @@ class Database {
return true;
}
- public function fetchWhere(Entity &$entity, string $columnName): bool {
+ public function fetchWhere(Entity &$entity, string|array $columnNames): bool {
$entityClassName = get_class($entity);
$tableName = self::getTableName($entityClassName);
$reflClass = new ReflectionClass($entityClassName);
$cols = self::getColumns($reflClass);
- if (!isset($cols[$columnName]))
- throw new \RuntimeException("Column $columnName does not exist!");
- $query = "SELECT * FROM $tableName WHERE $columnName = \$1 LIMIT 1;";
- $result = \pg_query_params($this->connection, $query, [ $entity->{$cols[$columnName]["propertyName"]} ]);
+ if (!is_array($columnNames)) {
+ $columnNames = [ $columnNames ];
+ }
+
+ $whereClause = [];
+ $columnValues = [];
+ $count = 0;
+ foreach ($columnNames as $columnName) {
+ ++$count;
+ if (!isset($cols[$columnName]))
+ throw new \RuntimeException("Column $columnName does not exist!");
+ $whereClause []= "$columnName = \$$count";
+ $columnValues []= self::stringifyValue($entity->{$cols[$columnName]["propertyName"]}, $cols[$columnName]["columnType"]);
+ }
+ $whereClause = implode(" AND ", $whereClause);
+ $query = "SELECT * FROM $tableName WHERE $whereClause LIMIT 1;";
+ $result = \pg_query_params($this->connection, $query, $columnValues);
if ($result === false)
throw new \RuntimeException("Fetch failed: " . \pg_last_error($this->connection));
$row = \pg_fetch_assoc($result);
diff --git a/src/application/views/form_login.php b/src/application/views/form_login.php
index 27924e8..de8f28c 100644
--- a/src/application/views/form_login.php
+++ b/src/application/views/form_login.php
@@ -10,7 +10,7 @@ RequestUtils::clearLastForm();
?>
<div class="page-header margin-top-0">
- <h1>Log in</h1>
+ <h1><?= __("Log in") ?></h1>
</div>
<div class="col-md-4"></div>
<div class="well col-md-4">
diff --git a/src/application/views/form_register.php b/src/application/views/form_register.php
index 6f62652..83f3f4e 100644
--- a/src/application/views/form_register.php
+++ b/src/application/views/form_register.php
@@ -10,7 +10,7 @@ RequestUtils::clearLastForm();
?>
<div class="page-header margin-top-0">
- <h1>Register</h1>
+ <h1><?= __("Register") ?></h1>
</div>
<div class="col-md-4"></div>
<div class="well col-md-4">
@@ -22,7 +22,7 @@ if (($_formError = RequestUtils::getAndClearFormError()) !== null) {
<form action="<?= htmlentities($_SERVER["REQUEST_URI"]) ?>" method="post">
<div class="form-group" id="group0">
<label for="i_username">Username:</label>
- <input class="form-control" id="i_username" type="text" name="username" value="<?= htmlentities($lastForm["username"] ?? "") ?>" required>
+ <input class="form-control" id="i_username" type="text" name="username" value="" required>
</div>
<div class="form-group" id="group1">
@@ -79,5 +79,6 @@ $(function() {
$("#btn-refresh-captcha").click(function() {
$("#captcha-img").attr("src", "?_action=captcha&t=" + new Date().getTime().toString());
});
+ $("#i_username").prop("disabled", true).prop("required", false);
});
</script>
diff --git a/src/application/views/nav_guest.php b/src/application/views/nav_guest.php
index f897763..1c65a50 100644
--- a/src/application/views/nav_guest.php
+++ b/src/application/views/nav_guest.php
@@ -1,6 +1,6 @@
<ul class="nav navbar-nav navbar-right">
-<li<?= $GLOBALS["action"] === "auth" ? ' class="active"' : '' ?>><a href="?_action=auth&amp;next=<?= htmlentities(urlencode($_SERVER["REQUEST_URI"])) ?>">Log in</a></li>
+<li<?= $GLOBALS["action"] === "auth" ? ' class="active"' : '' ?>><a href="?_action=auth&amp;next=<?= htmlentities(urlencode($_SERVER["REQUEST_URI"])) ?>"><?= __("Log in") ?></a></li>
<?php if (REGISTRATION_ENABLED): ?>
-<li<?= $GLOBALS["action"] === "register" ? ' class="active"' : '' ?>><a href="?_action=register&amp;next=<?= htmlentities(urlencode($_SERVER["REQUEST_URI"])) ?>">Register</a></li>
+<li<?= $GLOBALS["action"] === "register" ? ' class="active"' : '' ?>><a href="?_action=register&amp;next=<?= htmlentities(urlencode($_SERVER["REQUEST_URI"])) ?>"><?= __("Register") ?></a></li>
<?php endif; ?>
</ul> \ No newline at end of file
diff --git a/src/index.php b/src/index.php
index c75d112..de46204 100644
--- a/src/index.php
+++ b/src/index.php
@@ -12,6 +12,9 @@ use mystic\forum\orm\UserPermissions;
use mystic\forum\utils\FileUtils;
use mystic\forum\utils\RequestUtils;
use mystic\forum\utils\ValidationUtils;
+use Symfony\Component\Mailer\Transport;
+use Symfony\Component\Mime\Address;
+use Symfony\Component\Mime\Email;
header_remove("X-Powered-By");
@@ -122,12 +125,15 @@ function env(string $key): ?string {
require_once __DIR__ . "/vendor/autoload.php";
+require_once __DIR__ . "/application/i18n.php";
+i18n_locale("de");
+
$db = null;
try {
$db = new Database(Database::getConnectionString("db", getenv("POSTGRES_USER"), getenv("POSTGRES_PASSWORD"), getenv("POSTGRES_DBNAME")));
} catch (DatabaseConnectionException $ex) {
Messaging::error([
- Messaging::bold("Failed to connect to database!"),
+ Messaging::bold(__("Failed to connect to database!")),
Messaging::italic($ex->getMessage()),
]);
exit;
@@ -175,7 +181,7 @@ $GLOBALS["currentUser"] = &$currentUser;
if ($_action === "auth") {
if ($currentUser) {
- header("Location: " . $_GET["next"] ?? ".");
+ header("Location: " . ($_GET["next"] ?? "."));
exit;
}
@@ -186,17 +192,17 @@ if ($_action === "auth") {
$user = new User();
$user->name = $username;
if (!$db->fetchWhere($user, "name") || !password_verify($password, $user->passwordHash)) {
- RequestUtils::triggerFormError("Username or password incorrect!");
+ RequestUtils::triggerFormError(__("Username or password incorrect!"));
}
if (!$user->activated) {
- RequestUtils::triggerFormError("Please activate your user account first!");
+ RequestUtils::triggerFormError(__("Please activate your user account first!"));
}
RequestUtils::setAuthorizedUser($user);
- header("Location: " . $_GET["next"] ?? ".");
+ header("Location: " . ($_GET["next"] ?? "."));
} else {
- _view("template_start", ["_title" => "Log in"]);
+ _view("template_start", ["_title" => __("Log in")]);
_view("template_navigation_start");
_view("template_navigation", ["user" => RequestUtils::getAuthorizedUser($db)]);
_view("template_navigation_end");
@@ -205,19 +211,19 @@ if ($_action === "auth") {
}
} elseif ($_action === "register") {
if ($currentUser) {
- header("Location: " . $_GET["next"] ?? ".");
+ header("Location: " . ($_GET["next"] ?? "."));
exit;
}
if (!REGISTRATION_ENABLED) {
http_response_code(403);
- Messaging::error("Public registration disabled");
+ Messaging::error(__("Public registration disabled"));
exit;
}
if (RequestUtils::isRequestMethod("POST")) {
$doNotFill = $_POST["username"] ?? null;
- if ($doNotFill !== null) {
+ if (!empty($doNotFill)) {
sleep(10);
http_response_code(204);
exit;
@@ -229,27 +235,27 @@ if ($_action === "auth") {
$displayName = RequestUtils::getRequiredField("display_name");
$captcha = RequestUtils::getRequiredField("captcha");
- if ($captcha !== $_SESSION["captchaPhrase"]) {
- RequestUtils::triggerFormError("Incorrect CAPTCHA text!");
+ if ($captcha !== ($_SESSION["captchaPhrase"] ?? null)) {
+ RequestUtils::triggerFormError(__("Incorrect CAPTCHA text!"));
}
// usernames are always lowercase
$username = strtolower($username);
if ($password !== $passwordRetype) {
- RequestUtils::triggerFormError("Passwords do not match!");
+ RequestUtils::triggerFormError(__("Passwords do not match!"));
}
if (strlen($password) < 8) {
- RequestUtils::triggerFormError("Password too short! Your password must consist of 8 or more characters");
+ RequestUtils::triggerFormError(__("Password too short! Your password must consist of 8 or more characters"));
}
if (!ValidationUtils::isUsernameValid($username)) {
- RequestUtils::triggerFormError("Username has an invalid format");
+ RequestUtils::triggerFormError(__("Username has an invalid format"));
}
if (filter_var($email, FILTER_VALIDATE_EMAIL) === false) {
- RequestUtils::triggerFormError("Invalid email address");
+ RequestUtils::triggerFormError(__("Invalid email address"));
}
$user = new User();
@@ -257,11 +263,11 @@ if ($_action === "auth") {
$user->email = $email;
if ($db->fetchWhere($user, "name")) {
- RequestUtils::triggerFormError("This username is already taken!");
+ RequestUtils::triggerFormError(__("This username is already taken!"));
}
if ($db->fetchWhere($user, "email")) {
- RequestUtils::triggerFormError("This email address is already in use!");
+ RequestUtils::triggerFormError(__("This email address is already in use!"));
}
// re-create user so we don't forget to clear properties set by the above queries
@@ -278,26 +284,85 @@ if ($_action === "auth") {
$user->activationToken = $db->generateId(12);
$user->created = new \DateTimeImmutable();
- // TODO Send verification email
+ 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);
- Messaging::info([
- "Your account has been created!",
- //"Please check your emails for an activation link!",
- Messaging::html('<p>Please click <a href="?_action=auth">here</a> to log in!</p>'),
- ]);
+ Messaging::info(
+ Messaging::html(nl2br(htmlentities(__("Your account has been created!\nPlease check your emails for an activation link!"), true)))
+ );
} else {
- _view("template_start", ["_title" => "Register"]);
+ _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");
}
+} 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->activated = false;
+ $user->activationToken = $token;
+
+ if (!$db->fetchWhere($user, [ "activated", "activation_token" ])) {
+ http_response_code(400);
+ Messaging::error(__("Invalid token"));
+ exit;
+ }
+
+ $expectedSignature = base64_encode(hash("sha256", env("SECRET") . $user->activationToken . $user->id, true));
+
+ if ($expectedSignature !== $sig) {
+ http_response_code(400);
+ Messaging::error(__("Invalid signature."));
+ exit;
+ }
+
+ $user->activated = true;
+ $user->activationToken = "";
+
+ if (!$db->update($user)) {
+ http_response_code(400);
+ Messaging::error(__("Failed to update user"));
+ exit;
+ }
+
+ Messaging::info([
+ Messaging::html(nl2br(__(
+ "Your account has been activated!\nPlease click %link%here%/link% to log in!",
+ [
+ "link" => '<a href="?_action=auth">',
+ "/link" => '</a>',
+ ]
+ )), true),
+ ]);
} elseif ($_action === "logout") {
RequestUtils::unsetAuthorizedUser();
- header("Location: " . $_GET["next"] ?? ".");
+ header("Location: " . ($_GET["next"] ?? "."));
} elseif ($_action === "viewtopic") {
$topicId = $_GET["topic"] ?? throw new Exception("Missing topic id");
$topic = new Topic();
@@ -318,19 +383,19 @@ if ($_action === "auth") {
$attachments = reArrayFiles($_FILES["files"]);
if (count($attachments) > MAX_ATTACHMENT_COUNT)
- RequestUtils::triggerFormError("Too many attachments");
+ RequestUtils::triggerFormError(__("Too many attachments"));
// check all attachments before saving one
foreach ($attachments as $att) {
if ($att["size"] > MAX_ATTACHMENT_SIZE) {
- RequestUtils::triggerFormError("Individual file size exceeded");
+ RequestUtils::triggerFormError(__("Individual file size exceeded"));
}
}
$message = trim(RequestUtils::getRequiredField("message"));
if (strlen($message) < 1 || strlen($message) > 0x8000) {
- RequestUtils::triggerFormError("Message too short or too long!");
+ RequestUtils::triggerFormError(__("Message too short or too long!"));
}
$post = new Post();
@@ -425,21 +490,21 @@ if ($_action === "auth") {
$attachments = reArrayFiles($_FILES["files"]);
if (count($attachments) > MAX_ATTACHMENT_COUNT)
- RequestUtils::triggerFormError("Too many attachments");
+ RequestUtils::triggerFormError(__("Too many attachments"));
// check all attachments before saving one
foreach ($attachments as $att) {
if ($att["size"] > MAX_ATTACHMENT_SIZE) {
- RequestUtils::triggerFormError("Individual file size exceeded");
+ RequestUtils::triggerFormError(__("Individual file size exceeded"));
}
}
if (strlen($title) < 1 || strlen($title) > 255) {
- RequestUtils::triggerFormError("Title too short or too long!");
+ RequestUtils::triggerFormError(__("Title too short or too long!"));
}
if (strlen($message) < 1 || strlen($message) > 0x8000) {
- RequestUtils::triggerFormError("Message too short or too long!");
+ RequestUtils::triggerFormError(__("Message too short or too long!"));
}
$topic = new Topic();
@@ -479,7 +544,7 @@ if ($_action === "auth") {
header("Location: ?_action=viewtopic&topic=" . urlencode($topic->id));
} else {
- _view("template_start", ["_title" => "New topic"]);
+ _view("template_start", ["_title" => __("New topic")]);
_view("template_navigation_start");
_view("template_navigation", ["user" => RequestUtils::getAuthorizedUser($db)]);
_view("template_navigation_end");
@@ -495,7 +560,7 @@ if ($_action === "auth") {
if (!$db->fetchWhere($user, "name")) {
http_response_code(404);
- Messaging::error("No user with name @$userHandle");
+ Messaging::error(__("No user with name @%user_handle%", [ "user_handle" => $userHandle ]));
exit;
}
@@ -506,7 +571,7 @@ if ($_action === "auth") {
$user->id = $userId;
if (!$db->fetch($user)) {
http_response_code(404);
- Messaging::error("No user exists with this id");
+ Messaging::error(__("No user exists with this id"));
exit;
}
@@ -531,12 +596,12 @@ if ($_action === "auth") {
if ($userName !== $user->name) {
if ($lastNameChangeTooRecent) {
- RequestUtils::triggerFormError("You can only change your username every 30 days!");
+ RequestUtils::triggerFormError(__("You can only change your username every 30 days!"));
} else {
if (!ValidationUtils::isUsernameValid($userName))
- RequestUtils::triggerFormError("Invalid username!");
+ RequestUtils::triggerFormError(__("Invalid username!"));
if (!ValidationUtils::isUsernameAvailable($db, $userName))
- RequestUtils::triggerFormError("This username is already taken!");
+ RequestUtils::triggerFormError(__("This username is already taken!"));
$user->name = $userName;
$user->nameLastChanged = new DateTimeImmutable();
}
@@ -551,13 +616,13 @@ if ($_action === "auth") {
break;
case "replace": {
if (!isset($_FILES["pfp"]) || $_FILES["pfp"]["error"] !== UPLOAD_ERR_OK) {
- RequestUtils::triggerFormError("Please upload an image to change your profile picture");
+ RequestUtils::triggerFormError(__("Please upload an image to change your profile picture"));
}
$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");
+ RequestUtils::triggerFormError(__("Please upload a valid PNG or JPEG file"));
/** @var \GdImage $im */
$thumb = imagecreatetruecolor(64, 64);
imagecopyresampled($thumb, $im, 0, 0, 0, 0, 64, 64, imagesx($im), imagesy($im));
@@ -575,7 +640,7 @@ if ($_action === "auth") {
}
if (!$db->update($user))
- RequestUtils::triggerFormError("Failed to save changes");
+ RequestUtils::triggerFormError(__("Failed to save changes", context: "Update profile"));
header("Location: $_SERVER[REQUEST_URI]");
} else {
@@ -605,7 +670,7 @@ if ($_action === "auth") {
} elseif ($_action === "attachment") {
if (!$currentUser) {
http_response_code(403);
- Messaging::error("You must be logged in to view attachments");
+ Messaging::error(__("You must be logged in to view attachments"));
exit;
}
@@ -614,7 +679,7 @@ if ($_action === "auth") {
$attachment->id = $attId;
if (!$db->fetch($attachment)) {
http_response_code(404);
- Messaging::error("No attachment exists with this id");
+ Messaging::error(__("No attachment exists with this id"));
exit;
}
@@ -645,7 +710,7 @@ if ($_action === "auth") {
$user->id = $userId;
if (!$db->fetch($user)) {
http_response_code(404);
- Messaging::error("No user exists with this id");
+ Messaging::error(__("No user exists with this id"));
exit;
}
@@ -682,13 +747,13 @@ if ($_action === "auth") {
$attachment->id = $attId;
if (!$db->fetch($attachment)) {
http_response_code(404);
- Messaging::error("No attachment exists with this id");
+ Messaging::error(__("No attachment exists with this id"));
exit;
}
if (!str_starts_with($attachment->mimeType, "image/")) {
http_response_code(400);
- Messaging::error("Attachment is not an image");
+ Messaging::error(__("Attachment is not an image"));
exit;
}
@@ -805,7 +870,7 @@ if ($_action === "auth") {
header("Location: ?_action=viewtopic&topic=" . urlencode($post->topicId));
} else {
- _view("template_start", ["_title" => "Delete post"]);
+ _view("template_start", ["_title" => __("Delete post")]);
_view("template_navigation_start");
_view("template_navigation", ["user" => RequestUtils::getAuthorizedUser($db)]);
_view("template_navigation_end");
diff --git a/src/ui/site.css b/src/ui/site.css
index d87fa90..84bba0e 100644
--- a/src/ui/site.css
+++ b/src/ui/site.css
@@ -126,3 +126,6 @@ html.no-js .js-only.js-only.js-only {
border-top-left-radius: inherit;
border-top-right-radius: inherit;
}
+#group0 {
+ display: none !important;
+}