aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMurtaza Sultani <sultani@data-quest.de>2025-11-19 09:33:25 +0100
committerJan-Hendrik Willms <tleilax+studip@gmail.com>2026-02-27 13:54:05 +0100
commite6a106f2314239d8c7f7781058dbf7e99d403675 (patch)
treeff46cfed8ff40dbcc6b075aa5af0178015137208
parent06efdbcb518a77891c748193f6193dc0ec79e2ea (diff)
Resolve "Forum: Refaktorierung der Kategorie, Topic und Diskussions Metadaten sowie Behebung der fehlerhaften Anzeige ungelesener Beiträge"
Closes #6058 Merge request studip/studip!4613
-rw-r--r--lib/classes/JsonApi/Schemas/Forum/Category.php15
-rw-r--r--lib/classes/JsonApi/Schemas/Forum/Discussion.php11
-rw-r--r--lib/classes/JsonApi/Schemas/Forum/Topic.php13
-rw-r--r--lib/models/Forum/Category.php22
-rw-r--r--lib/models/Forum/Discussion.php26
-rw-r--r--lib/models/Forum/Topic.php21
-rw-r--r--resources/vue/components/forum/categories/CategoryItem.vue16
-rw-r--r--resources/vue/components/forum/discussions/DiscussionIndex.vue8
-rw-r--r--resources/vue/components/forum/topics/TopicItem.vue16
-rw-r--r--resources/vue/store/pinia/forum/ForumPost.js9
10 files changed, 106 insertions, 51 deletions
diff --git a/lib/classes/JsonApi/Schemas/Forum/Category.php b/lib/classes/JsonApi/Schemas/Forum/Category.php
index 5e3012b..0835937 100644
--- a/lib/classes/JsonApi/Schemas/Forum/Category.php
+++ b/lib/classes/JsonApi/Schemas/Forum/Category.php
@@ -46,15 +46,16 @@ class Category extends SchemaProvider
*/
public function getResourceMeta($resource)
{
- $metaData = $resource->getMetaData();
+ $metadata = $resource->getMetadata();
return [
- 'topics-count' => (int) $metaData['topics_count'],
- 'discussions-count' => (int) $metaData['discussions_count'],
- 'postings-count' => (int) $metaData['postings_count'],
- 'user-read-index' => (int) $metaData['user_read_index'],
- 'users-count' => (int) $metaData['users_count'],
- 'recent-activity' => $metaData['recent_activity'] ? date('c', $metaData['recent_activity']) : '',
+ 'topics-count' => (int) $metadata['topics_count'],
+ 'discussions-count' => (int) $metadata['discussions_count'],
+ 'postings-count' => (int) $metadata['postings_count'],
+ 'unread-postings-count' => (int) $metadata['unread_postings_count'],
+ 'user-read-index' => (int) $metadata['user_read_index'],
+ 'users-count' => (int) $metadata['users_count'],
+ 'recent-activity' => $metadata['recent_activity'] ? date('c', $metadata['recent_activity']) : '',
];
}
diff --git a/lib/classes/JsonApi/Schemas/Forum/Discussion.php b/lib/classes/JsonApi/Schemas/Forum/Discussion.php
index 9252aaa..a8c5c57 100644
--- a/lib/classes/JsonApi/Schemas/Forum/Discussion.php
+++ b/lib/classes/JsonApi/Schemas/Forum/Discussion.php
@@ -53,13 +53,14 @@ class Discussion extends SchemaProvider
*/
public function getResourceMeta($resource)
{
- $metaData = $resource->getMetaData();
+ $metadata = $resource->getMetadata();
return [
- 'postings-count' => (int) $metaData['postings_count'],
- 'recent-postings-count' => (int) $metaData['recent_postings_count'],
- 'user-read-index' => (int) $metaData['user_read_index'],
- 'recent-activity' => $metaData['recent_activity'] ? date('c', $metaData['recent_activity']) : ''
+ 'postings-count' => (int) $metadata['postings_count'],
+ 'recent-postings-count' => (int) $metadata['recent_postings_count'],
+ 'unread-postings-count' => (int) $metadata['unread_postings_count'],
+ 'user-read-index' => (int) $metadata['user_read_index'],
+ 'recent-activity' => $metadata['recent_activity'] ? date('c', $metadata['recent_activity']) : ''
];
}
diff --git a/lib/classes/JsonApi/Schemas/Forum/Topic.php b/lib/classes/JsonApi/Schemas/Forum/Topic.php
index 22a8c83..6a9659b 100644
--- a/lib/classes/JsonApi/Schemas/Forum/Topic.php
+++ b/lib/classes/JsonApi/Schemas/Forum/Topic.php
@@ -47,14 +47,15 @@ class Topic extends SchemaProvider
*/
public function getResourceMeta($resource)
{
- $metaData = $resource->getMetaData();
+ $metadata = $resource->getMetadata();
return [
- 'discussions-count' => (int) $metaData['discussions_count'],
- 'postings-count' => (int) $metaData['postings_count'],
- 'user-read-index' => (int) $metaData['user_read_index'],
- 'users-count' => (int) $metaData['users_count'],
- 'recent-activity' => $metaData['recent_activity'] ? date('c', $metaData['recent_activity']) : '',
+ 'discussions-count' => (int) $metadata['discussions_count'],
+ 'postings-count' => (int) $metadata['postings_count'],
+ 'unread-postings-count' => (int) $metadata['unread_postings_count'],
+ 'user-read-index' => (int) $metadata['user_read_index'],
+ 'users-count' => (int) $metadata['users_count'],
+ 'recent-activity' => $metadata['recent_activity'] ? date('c', $metadata['recent_activity']) : '',
];
}
diff --git a/lib/models/Forum/Category.php b/lib/models/Forum/Category.php
index 6ee52c8..74cd189 100644
--- a/lib/models/Forum/Category.php
+++ b/lib/models/Forum/Category.php
@@ -56,9 +56,9 @@ class Category extends \SimpleORMap
return self::findBySQL("range_id = ? ORDER BY position ASC, mkdate DESC", [$range_id]);
}
- public function getMetaData(): array
+ public function getMetadata(): array
{
- return DBManager::get()->fetchOne(
+ $metadata= DBManager::get()->fetchOne(
"SELECT
COUNT(DISTINCT`forum_topics`.`topic_id`) AS 'topics_count',
COUNT(DISTINCT `forum_discussions`.`discussion_id`) AS 'discussions_count',
@@ -73,7 +73,15 @@ class Category extends \SimpleORMap
ON fpr.discussion_id = fd2.discussion_id
AND fpr.user_id = :user_id
WHERE ft2.category_id = :category_id
- ) AS 'user_read_index'
+ ) AS 'user_read_index',
+ (
+ SELECT
+ COUNT(DISTINCT fp.posting_id)
+ FROM forum_postings fp
+ JOIN `forum_discussions` fd USING (discussion_id)
+ JOIN `forum_topics` ft USING (topic_id)
+ WHERE ft.category_id = :category_id AND fp.user_id != :user_id
+ ) AS 'others_postings_count'
FROM `forum_topics`
LEFT JOIN `forum_discussions` USING (`topic_id`)
LEFT JOIN `forum_postings` USING (`discussion_id`)
@@ -83,6 +91,14 @@ class Category extends \SimpleORMap
'user_id' => User::findCurrent()?->user_id
]
);
+
+ return [
+ ...$metadata,
+ 'unread_postings_count' => max(
+ 0,
+ $metadata['others_postings_count'] - (int) $metadata['user_read_index']
+ )
+ ];
}
public function transformData(): array
diff --git a/lib/models/Forum/Discussion.php b/lib/models/Forum/Discussion.php
index de82422..5b8edc5 100644
--- a/lib/models/Forum/Discussion.php
+++ b/lib/models/Forum/Discussion.php
@@ -240,7 +240,7 @@ class Discussion extends SimpleORMap
];
}
- public function getMetaData(int $last_visit = 0): array
+ public function getMetadata(int $last_visit = 0): array
{
$user_id = User::findCurrent()?->user_id;
@@ -249,7 +249,7 @@ class Discussion extends SimpleORMap
$last_visit = object_get_visit($this->topic->range_id, $plugin_id, 'last', '', $user_id);
}
- return DBManager::get()->fetchOne(
+ $metadata = DBManager::get()->fetchOne(
"SELECT
COUNT(`posting_id`) 'postings_count',
MAX(`mkdate`) 'recent_activity',
@@ -261,10 +261,16 @@ class Discussion extends SimpleORMap
) 'user_read_index',
(
SELECT
- COUNT(DISTINCT fp.posting_id)
- FROM forum_postings fp
- WHERE fp.discussion_id = :discussion_id AND fp.mkdate > :last_visit
- ) AS 'recent_postings_count'
+ COUNT(DISTINCT posting_id)
+ FROM forum_postings
+ WHERE discussion_id = :discussion_id AND mkdate > :last_visit AND `user_id` != :user_id
+ ) AS 'recent_postings_count',
+ (
+ SELECT
+ COUNT(DISTINCT posting_id)
+ FROM forum_postings
+ WHERE discussion_id = :discussion_id AND `user_id` != :user_id
+ ) AS 'others_postings_count'
FROM `forum_postings` WHERE `discussion_id` = :discussion_id",
[
'discussion_id' => $this->discussion_id,
@@ -272,6 +278,14 @@ class Discussion extends SimpleORMap
'last_visit' => $last_visit
]
);
+
+ return [
+ ...$metadata,
+ 'unread_postings_count' => max(
+ 0,
+ $metadata['others_postings_count'] - (int) $metadata['user_read_index']
+ )
+ ];
}
public function onCreate(): void
diff --git a/lib/models/Forum/Topic.php b/lib/models/Forum/Topic.php
index 2bf4c27..9b5ce7f 100644
--- a/lib/models/Forum/Topic.php
+++ b/lib/models/Forum/Topic.php
@@ -119,9 +119,9 @@ class Topic extends SimpleORMap
);
}
- public function getMetaData(): array
+ public function getMetadata(): array
{
- return DBManager::get()->fetchOne(
+ $metadata = DBManager::get()->fetchOne(
"SELECT
COUNT(DISTINCT `forum_discussions`.`discussion_id`) AS 'discussions_count',
COUNT(DISTINCT `forum_postings`.`posting_id`) AS 'postings_count',
@@ -135,7 +135,14 @@ class Topic extends SimpleORMap
ON fpr.discussion_id = fd2.discussion_id
AND fpr.user_id = :user_id
WHERE fd2.topic_id = :topic_id
- ) AS 'user_read_index'
+ ) AS 'user_read_index',
+ (
+ SELECT
+ COUNT(DISTINCT fp.posting_id)
+ FROM forum_postings fp
+ JOIN `forum_discussions` fd USING (discussion_id)
+ WHERE fd.topic_id = :topic_id AND fp.user_id != :user_id
+ ) AS 'others_postings_count'
FROM `forum_discussions`
LEFT JOIN `forum_postings` USING (`discussion_id`)
WHERE `forum_discussions`.`topic_id` = :topic_id",
@@ -144,6 +151,14 @@ class Topic extends SimpleORMap
'user_id' => User::findCurrent()?->user_id
]
);
+
+ return [
+ ...$metadata,
+ 'unread_postings_count' => max(
+ 0,
+ $metadata['others_postings_count'] - (int) $metadata['user_read_index']
+ )
+ ];
}
public function transformData(): array
diff --git a/resources/vue/components/forum/categories/CategoryItem.vue b/resources/vue/components/forum/categories/CategoryItem.vue
index 9b3bf12..291f14f 100644
--- a/resources/vue/components/forum/categories/CategoryItem.vue
+++ b/resources/vue/components/forum/categories/CategoryItem.vue
@@ -87,14 +87,14 @@ const swapCategory = event => {
:title="$gettext('Zur Kategorie')">
<span class="category-title line-clamp-2">{{ category.name }}</span>
<span
- v-if="!forumConfig.allowGuestAccess && category.meta.postings_count > category.meta.user_read_index"
+ v-if="!forumConfig.allowGuestAccess && category.meta.unread_postings_count"
class="unread-items-badge"
role="status"
aria-live="polite"
- :aria-label="$gettext('Sie haben %{count} ungelesene Beiträge', {count: category.meta.postings_count - category.meta.user_read_index})"
- :title="$gettext('Sie haben %{count} ungelesene Beiträge', {count: category.meta.postings_count - category.meta.user_read_index})"
+ :aria-label="$gettext('Sie haben %{count} ungelesene Beiträge', {count: category.meta.unread_postings_count})"
+ :title="$gettext('Sie haben %{count} ungelesene Beiträge', {count: category.meta.unread_postings_count})"
>
- {{ category.meta.postings_count - category.meta.user_read_index }}
+ {{ category.meta.unread_postings_count }}
</span>
</a>
</div>
@@ -203,14 +203,14 @@ const swapCategory = event => {
</span>
<span
- v-if="!forumConfig.allowGuestAccess && category.meta.postings_count > category.meta.user_read_index"
+ v-if="!forumConfig.allowGuestAccess && category.meta.unread_postings_count"
class="unread-items-badge"
role="status"
aria-live="polite"
- :aria-label="$gettext('Sie haben %{count} ungelesene Beiträge', {count: category.meta.postings_count - category.meta.user_read_index})"
- :title="$gettext('Sie haben %{count} ungelesene Beiträge', {count: category.meta.postings_count - category.meta.user_read_index})"
+ :aria-label="$gettext('Sie haben %{count} ungelesene Beiträge', {count: category.meta.unread_postings_count})"
+ :title="$gettext('Sie haben %{count} ungelesene Beiträge', {count: category.meta.unread_postings_count})"
>
- {{ category.meta.postings_count - category.meta.user_read_index }}
+ {{ category.meta.unread_postings_count }}
</span>
</div>
<div class="actions">
diff --git a/resources/vue/components/forum/discussions/DiscussionIndex.vue b/resources/vue/components/forum/discussions/DiscussionIndex.vue
index f5295fa..f5c5d32 100644
--- a/resources/vue/components/forum/discussions/DiscussionIndex.vue
+++ b/resources/vue/components/forum/discussions/DiscussionIndex.vue
@@ -169,14 +169,14 @@ onMounted(() => {
<span class="title-with-actions_title discussion-title line-clamp-2 m-0">{{ discussion.title }}</span>
<template v-if="!forumConfig.allowGuestAccess">
<span
- v-if="redirect !== 'recent' && discussion.meta.postings_count > discussion.meta.user_read_index"
+ v-if="redirect !== 'recent' && discussion.meta.unread_postings_count"
class="unread-items-badge"
role="status"
aria-live="polite"
- :aria-label="$gettext('Sie haben %{count} ungelesene Beiträge.', {count: discussion.meta.postings_count - discussion.meta.user_read_index})"
- :title="$gettext('Sie haben %{count} ungelesene Beiträge.', {count: discussion.meta.postings_count - discussion.meta.user_read_index})"
+ :aria-label="$gettext('Sie haben %{count} ungelesene Beiträge.', {count: discussion.meta.unread_postings_count})"
+ :title="$gettext('Sie haben %{count} ungelesene Beiträge.', {count: discussion.meta.unread_postings_count})"
>
- {{ discussion.meta.postings_count - discussion.meta.user_read_index }}
+ {{ discussion.meta.unread_postings_count }}
</span>
<span
v-if="redirect === 'recent' && discussion.meta.recent_postings_count"
diff --git a/resources/vue/components/forum/topics/TopicItem.vue b/resources/vue/components/forum/topics/TopicItem.vue
index 06ee10e..a7d62d2 100644
--- a/resources/vue/components/forum/topics/TopicItem.vue
+++ b/resources/vue/components/forum/topics/TopicItem.vue
@@ -83,14 +83,14 @@ const swapTopic = event => {
<a class="title-with-actions__link" :href="getTopicURL(topic.id)" :title="$gettext('Zum Thema')">
<span class="topic-title line-clamp-2">{{ topic.name }}</span>
<span
- v-if="!forumConfig.allowGuestAccess && topic.meta.postings_count > topic.meta.user_read_index"
+ v-if="!forumConfig.allowGuestAccess && topic.meta.unread_postings_count"
class="unread-items-badge"
role="status"
aria-live="polite"
- :aria-label="$gettext('Sie haben %{count} ungelesene Beiträge', {count: topic.meta.postings_count - topic.meta.user_read_index})"
- :title="$gettext('Sie haben %{count} ungelesene Beiträge', {count: topic.meta.postings_count - topic.meta.user_read_index})"
+ :aria-label="$gettext('Sie haben %{count} ungelesene Beiträge', {count: topic.meta.unread_postings_count})"
+ :title="$gettext('Sie haben %{count} ungelesene Beiträge', {count: topic.meta.unread_postings_count})"
>
- {{ topic.meta.postings_count - topic.meta.user_read_index }}
+ {{ topic.meta.unread_postings_count }}
</span>
</a>
</div>
@@ -191,14 +191,14 @@ const swapTopic = event => {
</span>
<span
- v-if="!forumConfig.allowGuestAccess && topic.meta.postings_count > topic.meta.user_read_index"
+ v-if="!forumConfig.allowGuestAccess && topic.meta.unread_postings_count"
class="unread-items-badge"
role="status"
aria-live="polite"
- :aria-label="$gettext('Sie haben %{count} ungelesene Beiträge', {count: topic.meta.postings_count - topic.meta.user_read_index})"
- :title="$gettext('Sie haben %{count} ungelesene Beiträge', {count: topic.meta.postings_count - topic.meta.user_read_index})"
+ :aria-label="$gettext('Sie haben %{count} ungelesene Beiträge', {count: topic.meta.unread_postings_count})"
+ :title="$gettext('Sie haben %{count} ungelesene Beiträge', {count: topic.meta.unread_postings_count})"
>
- {{ topic.meta.postings_count - topic.meta.user_read_index }}
+ {{ topic.meta.unread_postings_count }}
</span>
</div>
diff --git a/resources/vue/store/pinia/forum/ForumPost.js b/resources/vue/store/pinia/forum/ForumPost.js
index 3990ccc..20ede25 100644
--- a/resources/vue/store/pinia/forum/ForumPost.js
+++ b/resources/vue/store/pinia/forum/ForumPost.js
@@ -5,6 +5,7 @@ export const useForumPost = defineStore(
() => {
const posts = ref([]);
+ const firstUnreadPostIndex = ref(-1);
function initPosts(newPosts) {
posts.value = newPosts;
@@ -40,14 +41,20 @@ export const useForumPost = defineStore(
}
}
+ function updateFirstUnreadPostIndex(index) {
+ firstUnreadPostIndex.value = index;
+ }
+
return {
posts,
+ firstUnreadPostIndex,
initPosts,
addPost,
updatePost,
removePost,
addPostReaction,
- removePostReaction
+ removePostReaction,
+ updateFirstUnreadPostIndex
}
}
)