From c3e07e221b0bef64d3ad4da48c6371c75ca12cc3 Mon Sep 17 00:00:00 2001 From: Murtaza Sultani Date: Thu, 15 Jan 2026 17:10:04 +0100 Subject: Resolve "Neues Forum Polishing", #6064, #6063, #6151 Closes #6165 Merge request studip/studip!4671 --- app/controllers/course/forum/categories.php | 22 +- app/controllers/course/forum/configs.php | 6 +- app/controllers/course/forum/discussion_types.php | 18 +- app/controllers/course/forum/discussions.php | 40 ++- app/controllers/course/forum/recent.php | 6 +- app/controllers/course/forum/search.php | 8 +- app/controllers/course/forum/subscriptions.php | 4 +- app/controllers/course/forum/topics.php | 28 +- lib/classes/Forum/BaseController.php | 7 +- lib/exceptions/NotFoundException.php | 29 ++ resources/assets/stylesheets/scss/buttons.scss | 5 + resources/assets/stylesheets/scss/forum.scss | 42 ++- resources/vue/apps/forum/categories/Edit.vue | 16 +- resources/vue/apps/forum/categories/Index.vue | 98 +++--- resources/vue/apps/forum/categories/Show.vue | 29 +- resources/vue/apps/forum/configs/Edit.vue | 10 +- resources/vue/apps/forum/discussions/Edit.vue | 30 +- resources/vue/apps/forum/discussions/Index.vue | 29 +- resources/vue/apps/forum/discussions/Show.vue | 25 +- .../vue/apps/forum/discussions_types/Edit.vue | 24 +- .../vue/apps/forum/discussions_types/Index.vue | 62 ++-- resources/vue/apps/forum/recent/Index.vue | 16 +- resources/vue/apps/forum/search/Index.vue | 49 ++- resources/vue/apps/forum/subscriptions/Index.vue | 87 +++--- resources/vue/apps/forum/topics/Edit.vue | 10 +- resources/vue/apps/forum/topics/Index.vue | 22 +- resources/vue/apps/forum/topics/Show.vue | 40 +-- resources/vue/components/Dropdown.vue | 2 + resources/vue/components/forum/EmptyForum.vue | 35 ++- resources/vue/components/forum/ForumApp.vue | 9 +- resources/vue/components/forum/ForumMembers.vue | 74 ++--- resources/vue/components/forum/SelectTagsInput.vue | 4 +- resources/vue/components/forum/SelectUserInput.vue | 4 +- .../vue/components/forum/SubscriptionDropdown.vue | 41 ++- .../components/forum/categories/CategoryItem.vue | 327 ++++++++++---------- .../vue/components/forum/categories/Create.vue | 30 +- .../components/forum/categories/ShowCategory.vue | 83 ++--- .../vue/components/forum/discussions/Create.vue | 30 +- .../forum/discussions/DiscussionFooter.vue | 18 +- .../forum/discussions/DiscussionIndex.vue | 85 +++--- .../forum/discussions/SelectDiscussionType.vue | 6 +- resources/vue/components/forum/posts/Post.vue | 50 +-- .../vue/components/forum/posts/PostContent.vue | 2 +- .../vue/components/forum/posts/PostCreateForm.vue | 40 +-- .../vue/components/forum/posts/PostEditForm.vue | 24 +- .../components/forum/posts/PostReactionShow.vue | 18 +- .../vue/components/forum/posts/PostReactions.vue | 26 +- .../vue/components/forum/topics/CreateTopic.vue | 28 +- .../components/forum/topics/SelectTopicInput.vue | 4 +- .../vue/components/forum/topics/ShowTopic.vue | 96 +++--- .../vue/components/forum/topics/TopicItem.vue | 335 +++++++++++---------- .../vue/components/forum/topics/TopicsIndex.vue | 142 ++++++--- 52 files changed, 1244 insertions(+), 1031 deletions(-) create mode 100644 lib/exceptions/NotFoundException.php diff --git a/app/controllers/course/forum/categories.php b/app/controllers/course/forum/categories.php index df86089..ff1ddfa 100644 --- a/app/controllers/course/forum/categories.php +++ b/app/controllers/course/forum/categories.php @@ -4,7 +4,7 @@ use Forum\Category; class Course_Forum_CategoriesController extends Forum\BaseController { - public function before_filter(&$action, &$args) + public function before_filter(&$action, &$args): void { parent::before_filter($action, $args); @@ -15,19 +15,17 @@ class Course_Forum_CategoriesController extends Forum\BaseController } } - public function index_action() + public function index_action(): void { $this->render_vue_app( Studip\VueApp::create('forum/categories/Index') ); } - public function show_action($category_id) + public function show_action(Category $category): void { - $category = Category::find($category_id); - if (!$category) { - throw new AccessDeniedException(); + throw new NotFoundException(); } PageLayout::setTitle($category->name); @@ -45,7 +43,7 @@ class Course_Forum_CategoriesController extends Forum\BaseController ); } - public function edit_action($category_id = null) + public function edit_action($category_id = null): void { if (!$this->is_moderator) { throw new AccessDeniedException(); @@ -71,7 +69,7 @@ class Course_Forum_CategoriesController extends Forum\BaseController ); } - public function save_action($category_id = null) + public function save_action($category_id = null): void { if (!$this->is_moderator) { throw new AccessDeniedException(); @@ -100,16 +98,16 @@ class Course_Forum_CategoriesController extends Forum\BaseController $this->relocate('course/forum/categories'); } - public function delete_action($category_id) + public function delete_action(Category $category): void { + CSRFProtection::verifyUnsafeRequest(); + if (!$this->is_moderator) { throw new AccessDeniedException(); } - $category = Category::findOneBySQL("range_id = ? AND category_id = ?", [$this->range_id, $category_id]); - if (!$category) { - throw new AccessDeniedException(); + throw new NotFoundException(); } $category->delete(); diff --git a/app/controllers/course/forum/configs.php b/app/controllers/course/forum/configs.php index a065a6a..03c0951 100644 --- a/app/controllers/course/forum/configs.php +++ b/app/controllers/course/forum/configs.php @@ -2,7 +2,7 @@ class Course_Forum_ConfigsController extends Forum\BaseController { - public function before_filter(&$action, &$args) + public function before_filter(&$action, &$args): void { parent::before_filter($action, $args); @@ -15,7 +15,7 @@ class Course_Forum_ConfigsController extends Forum\BaseController } } - public function edit_action() + public function edit_action(): void { $config = Context::get()->getConfiguration(); @@ -30,7 +30,7 @@ class Course_Forum_ConfigsController extends Forum\BaseController ); } - public function save_action() + public function save_action(): void { CSRFProtection::verifyUnsafeRequest(); diff --git a/app/controllers/course/forum/discussion_types.php b/app/controllers/course/forum/discussion_types.php index bdfd480..0f0f772 100644 --- a/app/controllers/course/forum/discussion_types.php +++ b/app/controllers/course/forum/discussion_types.php @@ -24,16 +24,16 @@ class Course_Forum_DiscussionTypesController extends AuthenticatedController Sidebar::Get()->addWidget($actions); } - public function index_action() + public function index_action(): void { $this->render_vue_app( Studip\VueApp::create('forum/discussions_types/Index') ); } - public function edit_action(?DiscussionType $discussion_type = null) + public function edit_action(?DiscussionType $discussionType = null): void { - if ($discussion_type->isNew()) { + if ($discussionType->isNew()) { PageLayout::setTitle(_('Neuen Diskussionstyp anlegen')); } else { PageLayout::setTitle(_('Diskussionstyp bearbeiten')); @@ -56,21 +56,21 @@ class Course_Forum_DiscussionTypesController extends AuthenticatedController Studip\VueApp::create('forum/discussions_types/Edit') ->withProps([ 'icons' => array_unique($icons), - 'discussionType' => $discussion_type->transformData() + 'discussionType' => $discussionType->transformData() ]) ); } - public function save_action(?DiscussionType $discussion_type = null) + public function save_action(?DiscussionType $discussionType = null): void { CSRFProtection::verifyUnsafeRequest(); - $discussion_type->name = Request::get('name'); - $discussion_type->icon = Request::get('icon'); + $discussionType->name = Request::get('name'); + $discussionType->icon = Request::get('icon'); - $discussion_type->store(); + $discussionType->store(); - PageLayout::postSuccess(sprintf(_('Der Diskussionstyp „%s“ wurde gespeichert.'), $discussion_type->name)); + PageLayout::postSuccess(sprintf(_('Der Diskussionstyp „%s“ wurde gespeichert.'), $discussionType->name)); $this->relocate('course/forum/discussion_types/index'); } diff --git a/app/controllers/course/forum/discussions.php b/app/controllers/course/forum/discussions.php index 91ba8d6..7cf3243 100644 --- a/app/controllers/course/forum/discussions.php +++ b/app/controllers/course/forum/discussions.php @@ -12,7 +12,7 @@ use Forum\Topic; class Course_Forum_DiscussionsController extends Forum\BaseController { - public function before_filter(&$action, &$args) + public function before_filter(&$action, &$args): void { parent::before_filter($action, $args); @@ -23,8 +23,8 @@ class Course_Forum_DiscussionsController extends Forum\BaseController } } - public function index_action() { - + public function index_action(): void + { $metadata = DBManager::get()->fetchOne( "SELECT COUNT(posting_id) as 'postings_count', @@ -48,12 +48,10 @@ class Course_Forum_DiscussionsController extends Forum\BaseController ); } - public function show_action($discussion_id) + public function show_action(Discussion $discussion): void { - $discussion = Discussion::find($discussion_id); - if (!$discussion) { - throw new AccessDeniedException(); + throw new NotFoundException(); } PageLayout::setTitle($discussion->title); @@ -98,7 +96,7 @@ class Course_Forum_DiscussionsController extends Forum\BaseController $this->render_vue_app( Studip\VueApp::create('forum/discussions/Show') ->withProps([ - 'auth_user' => $auth_user, + 'authUser' => $auth_user, 'discussion' => [ ...$discussion->transformData(), 'topic' => $discussion->topic->toRawArray(), @@ -107,14 +105,14 @@ class Course_Forum_DiscussionsController extends Forum\BaseController 'type' => !empty($discussion->discussion_type) ? $discussion->discussion_type->toRawArray() : [] ], 'category' => $category ? $category->toRawArray() : [], - 'read_index' => (int) ($posting_read ? $posting_read->read_index : 0), + 'readIndex' => (int) ($posting_read ? $posting_read->read_index : 0), 'redirect' => Request::option('redirect'), - 'search_keyword' => Request::get('q', $_SESSION['forum'][$this->range_id]['search_filter']['keyword'] ?? '') + 'searchKeyword' => Request::get('q', $_SESSION['forum'][$this->range_id]['search_filter']['keyword'] ?? '') ]) ); } - public function edit_action(?Discussion $discussion = null) + public function edit_action(?Discussion $discussion = null): void { if ($discussion->isNew()) { PageLayout::setTitle(_('Neue Diskussion starten')); @@ -134,9 +132,9 @@ class Course_Forum_DiscussionsController extends Forum\BaseController ['range_id' => $this->range_id] ); - $all_tags = array_map(fn(TagDTO $tag) => $tag->toRawArray(), TagDTO::getForumTags()); - $discussion_tags = array_map(fn(TagDTO $tag) => $tag->toRawArray(), $discussion->tags); - $discussion_types = array_map(fn(DiscussionType $discussion_type) => $discussion_type->toRawArray(), DiscussionType::getAll()); + $allTags = array_map(fn(TagDTO $tag) => $tag->toRawArray(), TagDTO::getForumTags()); + $discussionTags = array_map(fn(TagDTO $tag) => $tag->toRawArray(), $discussion->tags); + $discussionTypes = array_map(fn(DiscussionType $discussion_type) => $discussion_type->toRawArray(), DiscussionType::getAll()); $this->render_vue_app( Studip\VueApp::create('forum/discussions/Edit') @@ -144,16 +142,16 @@ class Course_Forum_DiscussionsController extends Forum\BaseController 'discussion' => [ ...$discussion->transformData(), 'topic_id' => !empty($discussion->topic_id) ? $discussion->topic_id : Request::option('topic_id'), - 'tags' => $discussion_tags + 'tags' => $discussionTags ], 'topics' => $topics, - 'tags' => $all_tags, - 'discussion_types' => $discussion_types + 'tags' => $allTags, + 'discussionTypes' => $discussionTypes ]) ); } - public function save_action($discussion_id = null) + public function save_action($discussion_id = null): void { CSRFProtection::verifyUnsafeRequest(); @@ -223,12 +221,12 @@ class Course_Forum_DiscussionsController extends Forum\BaseController ); } - public function delete_action($discussion_id) + public function delete_action(Discussion $discussion): void { - $discussion = Discussion::find($discussion_id); + CSRFProtection::verifyUnsafeRequest(); if (!$discussion) { - throw new AccessDeniedException(); + throw new NotFoundException(); } if (!$this->is_moderator && $discussion->user_id !== $this->user_id) { diff --git a/app/controllers/course/forum/recent.php b/app/controllers/course/forum/recent.php index 3585c2c..46f6acc 100644 --- a/app/controllers/course/forum/recent.php +++ b/app/controllers/course/forum/recent.php @@ -2,21 +2,21 @@ class Course_Forum_RecentController extends Forum\BaseController { - public function before_filter(&$action, &$args) + public function before_filter(&$action, &$args): void { parent::before_filter($action, $args); Navigation::activateItem('course/forum/topics'); } - public function index_action() + public function index_action(): void { PageLayout::setTitle(_('Neueste Beiträge')); $this->render_vue_app( Studip\VueApp::create('forum/recent/Index') ->withProps([ - 'last_visit' => Request::int('last_visit') + 'lastVisit' => Request::int('last_visit') ]) ); } diff --git a/app/controllers/course/forum/search.php b/app/controllers/course/forum/search.php index 988ca87..8cb8824 100644 --- a/app/controllers/course/forum/search.php +++ b/app/controllers/course/forum/search.php @@ -5,14 +5,14 @@ use Forum\DTO\Tag as TagDTO; class Course_Forum_SearchController extends Forum\BaseController { - public function before_filter(&$action, &$args) + public function before_filter(&$action, &$args): void { parent::before_filter($action, $args); Navigation::activateItem('course/forum'); } - public function index_action() + public function index_action(): void { $topics = DBManager::get()->fetchAll( "SELECT @@ -43,9 +43,9 @@ class Course_Forum_SearchController extends Forum\BaseController ->withProps([ 'filter' => $this->getForumFilter(), 'topics' => $topics, - 'discussion_types' => $discussion_types, + 'discussionTypes' => $discussion_types, 'tags' => $tags, - 'course_members' => $course_members, + 'courseMembers' => $course_members, ]) ); } diff --git a/app/controllers/course/forum/subscriptions.php b/app/controllers/course/forum/subscriptions.php index 13df712..77b5e0f 100644 --- a/app/controllers/course/forum/subscriptions.php +++ b/app/controllers/course/forum/subscriptions.php @@ -2,7 +2,7 @@ class Course_Forum_SubscriptionsController extends Forum\BaseController { - public function before_filter(&$action, &$args) + public function before_filter(&$action, &$args): void { parent::before_filter($action, $args); @@ -13,7 +13,7 @@ class Course_Forum_SubscriptionsController extends Forum\BaseController Navigation::activateItem('course/forum/subscriptions'); } - public function index_action() + public function index_action(): void { $this->render_vue_app( Studip\VueApp::create('forum/subscriptions/Index') diff --git a/app/controllers/course/forum/topics.php b/app/controllers/course/forum/topics.php index ca22f89..56ffeee 100644 --- a/app/controllers/course/forum/topics.php +++ b/app/controllers/course/forum/topics.php @@ -6,7 +6,7 @@ use Forum\Topic; class Course_Forum_TopicsController extends Forum\BaseController { - public function before_filter(&$action, &$args) + public function before_filter(&$action, &$args): void { parent::before_filter($action, $args); @@ -15,19 +15,17 @@ class Course_Forum_TopicsController extends Forum\BaseController Navigation::activateItem('course/forum/topics'); } - public function index_action() + public function index_action(): void { $this->render_vue_app( Studip\VueApp::create('forum/topics/Index') ); } - public function show_action($topic_id) + public function show_action(Topic $topic): void { - $topic = Topic::find($topic_id); - if (!$topic) { - throw new AccessDeniedException(); + throw new NotFoundException(); } PageLayout::setTitle($topic->name); @@ -49,7 +47,7 @@ class Course_Forum_TopicsController extends Forum\BaseController ->withProps([ 'topic' => $topic->transformData(), 'category' => $topic->category ? $topic->category->transformData() : [], - 'user_subscription' => $user_subscription ? $user_subscription->toRawArray() : [], + 'userSubscription' => $user_subscription ? $user_subscription->toRawArray() : [], 'metadata' => [ 'postings_count' => (int) $topic->metadata['postings_count'], 'users_count' => (int) $topic->metadata['users_count'], @@ -59,7 +57,7 @@ class Course_Forum_TopicsController extends Forum\BaseController ); } - public function edit_action($topic_id = null) + public function edit_action($topic_id = null): void { if (!$this->is_moderator) { throw new AccessDeniedException(); @@ -92,7 +90,7 @@ class Course_Forum_TopicsController extends Forum\BaseController ); } - public function save_action($topic_id = null) + public function save_action($topic_id = null): void { if (!$this->is_moderator) { throw new AccessDeniedException(); @@ -138,15 +136,15 @@ class Course_Forum_TopicsController extends Forum\BaseController $this->relocate('course/forum/topics/show/' . $topic->topic_id); } - public function delete_action($topic_id) + public function delete_action(Topic $topic): void { - if (!$this->is_moderator) { - throw new AccessDeniedException(); - } - - $topic = Topic::getCourseTopic($this->range_id, $topic_id); + CSRFProtection::verifyUnsafeRequest(); if (!$topic) { + throw new NotFoundException(); + } + + if (!$this->is_moderator) { throw new AccessDeniedException(); } diff --git a/lib/classes/Forum/BaseController.php b/lib/classes/Forum/BaseController.php index 327e0bc..5a7e8f8 100644 --- a/lib/classes/Forum/BaseController.php +++ b/lib/classes/Forum/BaseController.php @@ -17,7 +17,7 @@ abstract class BaseController extends StudipController protected $is_admin = false; protected $is_moderator = false; - public function before_filter(&$action, &$args) + public function before_filter(&$action, &$args): void { object_set_visit_module('forum'); @@ -49,9 +49,8 @@ abstract class BaseController extends StudipController $actions->addLink( _('Forum verwalten'), $this->url_for('course/forum/configs/edit'), - Icon::create('admin', Icon::ROLE_CLICKABLE, ['title' => _('Forum verwalten')]), - ['data-dialog' => 'width=500;height=350'] - ); + Icon::create('admin', Icon::ROLE_CLICKABLE, ['title' => _('Forum verwalten')]) + )->asDialog('width=500;height=350'); } Sidebar::Get()->addWidget($actions); diff --git a/lib/exceptions/NotFoundException.php b/lib/exceptions/NotFoundException.php new file mode 100644 index 0000000..d1ed900 --- /dev/null +++ b/lib/exceptions/NotFoundException.php @@ -0,0 +1,29 @@ +details = $details; + } + + public function getDetails(): array + { + return $this->details; + } +} diff --git a/resources/assets/stylesheets/scss/buttons.scss b/resources/assets/stylesheets/scss/buttons.scss index e4197c0..3167dd6 100644 --- a/resources/assets/stylesheets/scss/buttons.scss +++ b/resources/assets/stylesheets/scss/buttons.scss @@ -113,6 +113,11 @@ button, border: 0; } + &.styleless { + padding: 0; + margin: 0; + } + &.as-link, &.undecorated { cursor: pointer; diff --git a/resources/assets/stylesheets/scss/forum.scss b/resources/assets/stylesheets/scss/forum.scss index fc53e9c..7788e6d 100644 --- a/resources/assets/stylesheets/scss/forum.scss +++ b/resources/assets/stylesheets/scss/forum.scss @@ -1,5 +1,5 @@ -$card-min-width: 250px; -$card-max-width: 300px; +$cardMinWidth: 300px; +$cardMaxWidth: 350px; .forum { hr { @@ -10,9 +10,9 @@ $card-max-width: 300px; } .forum-table { - &.--discussions-index, - &.--subscription-index, - &.--topics-index { + &--discussions-index, + &--subscription-index, + &--topics-index { .details-xs { display: none; margin-top: 5px; @@ -56,7 +56,7 @@ $card-max-width: 300px; } } - &.--subscription-index { + &--subscription-index { .subscription-button { background-color: transparent; border-color: transparent; @@ -179,16 +179,14 @@ $card-max-width: 300px; } } + table tr.sortable th button { + font-weight: bold; + } + .header { display: flex; background-color: var(--color--fieldset-header); - &.--sticky-top { - position: sticky; - top: 50px; - z-index: 10; - } - .flag { width: 10px; } @@ -374,11 +372,11 @@ $card-max-width: 300px; padding: 0; list-style-type: none; display: grid; - grid-template-columns: repeat(auto-fit, minmax($card-min-width, $card-max-width)); + grid-template-columns: repeat(auto-fit, minmax($cardMinWidth, $cardMaxWidth)); grid-gap: 15px; &.--fill-free-space { - grid-template-columns: repeat(auto-fit, minmax($card-min-width, 1fr)); + grid-template-columns: repeat(auto-fit, minmax($cardMinWidth, 1fr)); } .card-group { @@ -409,7 +407,7 @@ $card-max-width: 300px; width: 10px; } - &.--new-topic { + &--new-topic { display: flex; justify-content: center; align-items: center; @@ -465,8 +463,8 @@ $card-max-width: 300px; background-color: var(--color--tile-background-hover); } - &.--with-hover-style:hover, - &.--with-hover-style:focus { + &--with-hover-style:hover, + &--with-hover-style:focus { outline-color: var(--forum-topic-card-hover-border-color); outline-style: solid; border-color: var(--forum-topic-card-hover-border-color); @@ -481,7 +479,7 @@ $card-max-width: 300px; display: flex; transition: background-color 1s ease; - &.--highlight { + &--highlight { background-color: var(--content-color-10); } @@ -1118,7 +1116,7 @@ $card-max-width: 300px; gap: 5px; cursor: pointer; - &.--active { + &--active { background-color: var(--base-color-20); border-color: var(--base-color-60); } @@ -1645,9 +1643,9 @@ $card-max-width: 300px; } .forum-table { - &.--discussions-index, - &.--subscription-index, - &.--topics-index { + &--discussions-index, + &--subscription-index, + &--topics-index { th:not(:first-child), td:not(:first-child), col:not(:first-child) { diff --git a/resources/vue/apps/forum/categories/Edit.vue b/resources/vue/apps/forum/categories/Edit.vue index b3ca722..742c637 100644 --- a/resources/vue/apps/forum/categories/Edit.vue +++ b/resources/vue/apps/forum/categories/Edit.vue @@ -1,6 +1,6 @@