From 686fff945e0b4697aa74da404ce90534bb7b121d Mon Sep 17 00:00:00 2001 From: Jonas Kohl Date: Thu, 26 Dec 2024 20:12:34 +0100 Subject: Add async email and topic subscribing --- src/application/actions/subscribetopic/post.php | 39 ++++++++++++++++ src/application/actions/viewtopic/get.php | 15 +++++++ src/application/actions/viewtopic/post.php | 52 ++++++++++++++++++++++ src/application/appdef.php | 2 +- src/application/common.php | 12 +++++ src/application/cron.php | 42 +++++++++++++++++ src/application/jobs/email.php | 33 ++++++++++++++ src/application/messages/de.msg | 31 +++++++++++++ src/application/mystic/forum/Database.php | 4 +- src/application/mystic/forum/orm/PendingEmail.php | 19 ++++++++ src/application/mystic/forum/orm/Subscription.php | 14 ++++++ src/application/mystic/forum/utils/StringUtils.php | 2 + .../templates/bootstrap-3/view_topic.twig | 40 ++++++++++++++--- src/application/templates/modern/view_topic.twig | 34 +++++++++++++- src/application/templates/old/view_topic.twig | 29 +++++++++++- 15 files changed, 357 insertions(+), 11 deletions(-) create mode 100644 src/application/actions/subscribetopic/post.php create mode 100644 src/application/common.php create mode 100644 src/application/cron.php create mode 100644 src/application/jobs/email.php create mode 100644 src/application/mystic/forum/orm/PendingEmail.php create mode 100644 src/application/mystic/forum/orm/Subscription.php (limited to 'src/application') diff --git a/src/application/actions/subscribetopic/post.php b/src/application/actions/subscribetopic/post.php new file mode 100644 index 0000000..78dad04 --- /dev/null +++ b/src/application/actions/subscribetopic/post.php @@ -0,0 +1,39 @@ +id = $topicId; + +if (!$db->fetch($topic)) { + http_response_code(404); + msg_error(__("No topic exists with this id")); + exit; +} + +$subscription = new Subscription; +$subscription->userId = $currentUser->id; +$subscription->topicId = $topic->id; + +if ($db->fetchWhere($subscription, ["user_id", "topic_id"])) { + $db->delete($subscription); +} else { + $subscription->id = $db->generateId(); + $db->insert($subscription); +} + +header("Location: ?_action=viewtopic&topic=" . urlencode($topic->id)); diff --git a/src/application/actions/viewtopic/get.php b/src/application/actions/viewtopic/get.php index 636d791..56308bf 100644 --- a/src/application/actions/viewtopic/get.php +++ b/src/application/actions/viewtopic/get.php @@ -1,9 +1,12 @@ userId = $currentUser->id; + $subscription->topicId = $topic->id; + if (!$db->fetchWhere($subscription, ["user_id", "topic_id"])) { + $subscription = null; + } +} + render("view_topic.twig", [ "topic" => $topic, "topicAuthor" => $topicAuthor, "allItems" => &$allItems, + "subscription" => $subscription, + "subscription_count" => count($db->fetchCustom(Subscription::class, "WHERE topic_id = $1", [ $topic->id ])), ]); diff --git a/src/application/actions/viewtopic/post.php b/src/application/actions/viewtopic/post.php index 1038222..b50a2e9 100644 --- a/src/application/actions/viewtopic/post.php +++ b/src/application/actions/viewtopic/post.php @@ -1,8 +1,18 @@ insert($attachment); } +if (($_POST["subscribe"] ?? null) === "on") { + $subscription = new Subscription; + $subscription->userId = $currentUser->id; + $subscription->topicId = $topic->id; + + if (!$db->fetchWhere($subscription, ["user_id", "topic_id"])) { + $subscription->id = $db->generateId(); + $db->insert($subscription); + } +} + +/** @var Subscription[] $allSubscriptions */ +$allSubscriptions = $db->fetchCustom(Subscription::class, "WHERE topic_id = $1 AND user_id <> $2", [ $topicId, $currentUser->id ]); + +foreach ($allSubscriptions as $subscription) { + $subUser = new User; + $subUser->id = $subscription->userId; + if (!$db->fetch($subUser)) + continue; + + $email = new PendingEmail; + $email->id = $db->generateId(); + $email->sender = env("MAILER_FROM"); + $email->recipient = (new Address($subUser->email, $subUser->displayName))->toString(); + $email->subject = __("Someone made a new post in \"%topic%\"", [ + "topic" => $topic->title, + ]); + $email->plaintextBody = __("Hello %name%,\n" . + "\n" . + "%otherName% has added the following post to \"%topic%\":\n" . + "%content%\n" . + "\n" . + "Click here to view: %link%\n", [ + "name" => $subUser->displayName, + "otherName" => $currentUser->displayName, + "topic" => $topic->title, + "content" => quote_message(html_entity_decode(renderPostSummary($message))), + "link" => env("PUBLIC_URL") . "?_action=viewtopic&topic=" . urlencode($topicId) . "#post-" . urlencode($item->id), + ]); + $db->insert($email); +} + header("Location: ?_action=viewtopic&topic=" . urlencode($topicId) . "#form"); diff --git a/src/application/appdef.php b/src/application/appdef.php index 1f51d97..d623cd1 100644 --- a/src/application/appdef.php +++ b/src/application/appdef.php @@ -1,3 +1,3 @@ "> $ln", preg_split('~(*BSR_ANYCRLF)\R~', trim($message)))); +} diff --git a/src/application/cron.php b/src/application/cron.php new file mode 100644 index 0000000..094fe50 --- /dev/null +++ b/src/application/cron.php @@ -0,0 +1,42 @@ +\n"); + exit(1); +} + +$jobsDir = __DIR__ . "/jobs"; + +$availableJobs = array_map(fn($i) => pathinfo($i, PATHINFO_FILENAME), array_values(array_filter(scandir($jobsDir), + fn($i) => is_file($jobsDir . "/" . $i) && $i[0] !== "." && pathinfo($i, PATHINFO_EXTENSION) === "php"))); + +if (!in_array($job, $availableJobs)) { + print_error("Error: Invalid job specified\nAvailable jobs are:\n" . implode("", array_map(fn($i) => " $i\n", $availableJobs))); + exit(1); +} + +$jobFile = $jobsDir . "/" . $job . ".php"; + +include $jobFile; diff --git a/src/application/jobs/email.php b/src/application/jobs/email.php new file mode 100644 index 0000000..8bb97dc --- /dev/null +++ b/src/application/jobs/email.php @@ -0,0 +1,33 @@ +fetchAll(PendingEmail::class); + +$transport = Transport::fromDsn(getenv("MAILER_DSN")); + +foreach ($pendingEmails as $pendingEmail) { + try { + $mail = (new Email) + ->from($pendingEmail->sender) + ->to($pendingEmail->recipient) + ->subject($pendingEmail->subject) + ; + if (!empty($pendingEmail->htmlBody)) + $mail->html($pendingEmail->htmlBody); + if (!empty($pendingEmail->plaintextBody)) + $mail->text($pendingEmail->plaintextBody); + $transport->send($mail); + } catch (TransportExceptionInterface $ex) { + print_error("Failed to send mail: " . $ex->getMessage() . "\n"); + } + $db->delete($pendingEmail); +} diff --git a/src/application/messages/de.msg b/src/application/messages/de.msg index 3ce96d4..c16d1d1 100644 --- a/src/application/messages/de.msg +++ b/src/application/messages/de.msg @@ -540,3 +540,34 @@ metadata({ : "Page generation took %dur% second(s)" = "Seitenaufbau dauerte %dur% Sekunde(n)" + +: "Unsubscribe from topic" += "Thema deabonnieren" + +: "Subscribe to topic" += "Thema abonnieren" + +: "Someone made a new post in \"%topic%\"" += "Jemand hat einen neuen Beitrag zu \"%topic%\" hinzugefügt" + +: "Hello %name%,\n" + "\n" + "%otherName% has added the following post to \"%topic%\":\n" + "%content%\n" + "\n" + "Click here to view: %link%\n" += "Hallo %name%,\n" + "\n" + "%otherName% hat den folgenden Beitrag zu \"%topic%\" hinzugefügt:\n" + "%content%\n" + "\n" + "Den Beitrag ansehen: %link%\n" + +:... +- "%n% person is watching this topic" +- "%n% people are watching this topic" +- "Nobody is watching this topic" +=... +- "%n% Person beobachtet dieses Thema" +- "%n% Personen beobachten dieses Thema" +- "Niemand beobachtet dieses Thema" diff --git a/src/application/mystic/forum/Database.php b/src/application/mystic/forum/Database.php index 1c2d710..bca4ac9 100644 --- a/src/application/mystic/forum/Database.php +++ b/src/application/mystic/forum/Database.php @@ -46,12 +46,12 @@ class Database { protected function queryParams(string $query, array $params): Result|false { ++$this->queryCount; - return \pg_query_params ($this->connection, $query, $params); + return \pg_query_params($this->connection, $query, $params); } protected function query(string $query): Result|false { ++$this->queryCount; - return \pg_query ($this->connection, $query); + return \pg_query($this->connection, $query); } public static function generateId(int $length = 64): string { diff --git a/src/application/mystic/forum/orm/PendingEmail.php b/src/application/mystic/forum/orm/PendingEmail.php new file mode 100644 index 0000000..70622b3 --- /dev/null +++ b/src/application/mystic/forum/orm/PendingEmail.php @@ -0,0 +1,19 @@ + {% if canEdit and not ctx.topic.isLocked %} - + {% endif %} {% if canReply %} - + + {% endif %} + {% if canSubscribe %} +
+ + {% if isSubscribed %} + + {% else %} + + {% endif %} +
{% endif %} {% if canEdit %} {% if ctx.topic.isLocked %}
- +
{% else %}
- +
{% endif %} {% endif %} {% if canDelete %}
- +
{% endif %} @@ -209,7 +225,14 @@ {{ __("Started by %user% on %date%", { "user": (ctx.topicAuthor is not null) ? ('' ~ ctx.topicAuthor.displayName|e("html") ~ '') : __("(deleted)"), "date": '' ~ ctx.topic.creationDate.format("c")|e("html") ~ '', - }) }} + }) }} • {{ ___( + "%n% person is watching this topic", + "%n% people are watching this topic", + ctx.subscription_count, + { + n: ctx.subscription_count, + }, + ) }} {% if canEdit %} {% else %} diff --git a/src/application/templates/modern/view_topic.twig b/src/application/templates/modern/view_topic.twig index 733ce1b..c064b1c 100644 --- a/src/application/templates/modern/view_topic.twig +++ b/src/application/templates/modern/view_topic.twig @@ -30,6 +30,12 @@ or currentUser.hasPermission(permission("DELETE_OTHER_TOPIC")) ) %} +{% set canSubscribe = currentUser is not null %} + +{% set isSubscribed = + currentUser is not null + and ctx.subscription is not null %} + {% set title = ctx.topic.title %} {% extends "base.twig" %} @@ -189,6 +195,20 @@ {% endif %} + {% if canSubscribe %} +
+ + {% if isSubscribed %} + + {% else %} + + {% endif %} +
+ {% endif %} {% if canEdit %} {% if ctx.topic.isLocked %}
@@ -221,7 +241,14 @@ {{ __("Started by %user% on %date%", { "user": (ctx.topicAuthor is not null) ? ('' ~ ctx.topicAuthor.displayName|e("html") ~ '') : __("(deleted)"), "date": '' ~ ctx.topic.creationDate.format("c")|e("html") ~ '', - }) }} + }) }} • {{ ___( + "%n% person is watching this topic", + "%n% people are watching this topic", + ctx.subscription_count, + { + n: ctx.subscription_count, + }, + ) }} {% if canEdit %} @@ -348,6 +375,11 @@ document.addEventListener("DOMContentLoaded", function() { }) }} + {% if not isSubscribed %} +
+ +
+ {% endif %}
+ {%- else -%} + + {%- endif -%} + + {%- endif -%} {%- if canDelete -%}
{# #}{# @@ -87,7 +103,14 @@ {{ __("Started by %user% on %date%", { "user": (ctx.topicAuthor is not null) ? ('' ~ ctx.topicAuthor.displayName|e("html") ~ '') : __("(deleted)"), "date": '' ~ ctx.topic.creationDate.format("c")|e("html") ~ '', - }) }} + }) }} • {{ ___( + "%n% person is watching this topic", + "%n% people are watching this topic", + ctx.subscription_count, + { + n: ctx.subscription_count, + }, + ) }} {% if canEdit %} @@ -216,6 +239,10 @@ $(function() { }) }}


+ {% if not isSubscribed %} +
+
+ {% endif %}
{% else %} -- cgit v1.2.3