From 9bb68bf5d075ff0e127b9fe5309889f366ccf127 Mon Sep 17 00:00:00 2001
From: Murtaza Sultani
Date: Fri, 5 Sep 2025 15:47:50 +0200
Subject: Resolve "Forum: Discussion-Type Index auf Vue umsetzen"
Closes #5782
Merge request studip/studip!4406
---
app/controllers/course/forum/discussion_types.php | 24 ++-
app/controllers/course/forum/discussions.php | 2 +-
app/controllers/course/forum/search.php | 2 +-
app/views/course/forum/discussion_types/index.php | 79 ----------
lib/classes/JsonApi/RouteMap.php | 2 +
.../JsonApi/Routes/Forum/DiscussionTypeDelete.php | 29 ++++
.../JsonApi/Routes/Forum/DiscussionTypeIndex.php | 6 +-
.../JsonApi/Routes/Forum/DiscussionTypeShow.php | 4 -
.../JsonApi/Routes/Forum/DiscussionTypeStore.php | 46 ++++++
.../JsonApi/Schemas/Forum/DiscussionType.php | 32 ++--
lib/models/Forum/DiscussionType.php | 16 +-
resources/assets/stylesheets/scss/buttons.scss | 2 +-
resources/assets/stylesheets/scss/forum.scss | 9 +-
resources/vue/apps/forum/discussions/Show.vue | 6 +-
.../vue/apps/forum/discussions_types/Edit.vue | 27 ++--
.../vue/apps/forum/discussions_types/Index.vue | 163 +++++++++++++++++++++
resources/vue/components/Dropdown.vue | 2 +-
resources/vue/components/UserAvatar.vue | 4 +-
.../vue/components/forum/SubscriptionDropdown.vue | 4 +
resources/vue/components/forum/helpers/urls.js | 4 +
20 files changed, 316 insertions(+), 147 deletions(-)
delete mode 100644 app/views/course/forum/discussion_types/index.php
create mode 100644 lib/classes/JsonApi/Routes/Forum/DiscussionTypeDelete.php
create mode 100644 lib/classes/JsonApi/Routes/Forum/DiscussionTypeStore.php
create mode 100644 resources/vue/apps/forum/discussions_types/Index.vue
diff --git a/app/controllers/course/forum/discussion_types.php b/app/controllers/course/forum/discussion_types.php
index 68005a5..71a4766 100644
--- a/app/controllers/course/forum/discussion_types.php
+++ b/app/controllers/course/forum/discussion_types.php
@@ -19,14 +19,16 @@ class Course_Forum_DiscussionTypesController extends AuthenticatedController
_('Neuen Diskussionstyp anlegen'),
$this->url_for('course/forum/discussion_types/edit'),
Icon::create('add', Icon::ROLE_CLICKABLE, ['title' => _('Neuen Diskussionstyp anlegen')])
- )->asDialog('width=700');
+ )->asDialog('width=700;height=650');
Sidebar::Get()->addWidget($actions);
}
public function index_action()
{
- $this->discussion_types = DiscussionType::findBySQL("TRUE ORDER BY mkdate DESC");
+ $this->render_vue_app(
+ Studip\VueApp::create('forum/discussions_types/Index')
+ );
}
public function edit_action(DiscussionType $discussion_type = null)
@@ -51,10 +53,11 @@ class Course_Forum_DiscussionTypesController extends AuthenticatedController
}
$this->render_vue_app(
- Studip\VueApp::create('forum/discussions_types/Edit')->withProps([
- 'icons' => array_unique($icons),
- 'discussion_type' => $discussion_type->toRawArray()
- ])
+ Studip\VueApp::create('forum/discussions_types/Edit')
+ ->withProps([
+ 'icons' => array_unique($icons),
+ 'discussionType' => $discussion_type->transformData()
+ ])
);
}
@@ -71,13 +74,4 @@ class Course_Forum_DiscussionTypesController extends AuthenticatedController
$this->relocate('course/forum/discussion_types/index');
}
-
- public function delete_action(DiscussionType $discussion_type)
- {
- $discussion_type->delete();
-
- PageLayout::postSuccess(_('Der Diskussionstyp wurde gelöscht.'));
-
- $this->relocate('course/forum/discussion_types/index');
- }
}
diff --git a/app/controllers/course/forum/discussions.php b/app/controllers/course/forum/discussions.php
index e6cec64..4a71519 100644
--- a/app/controllers/course/forum/discussions.php
+++ b/app/controllers/course/forum/discussions.php
@@ -136,7 +136,7 @@ class Course_Forum_DiscussionsController extends Forum\BaseController
$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::getForumDiscussionType());
+ $discussion_types = array_map(fn(DiscussionType $discussion_type) => $discussion_type->toRawArray(), DiscussionType::getAll());
$this->render_vue_app(
Studip\VueApp::create('forum/discussions/Edit')
diff --git a/app/controllers/course/forum/search.php b/app/controllers/course/forum/search.php
index 706674c..988ca87 100644
--- a/app/controllers/course/forum/search.php
+++ b/app/controllers/course/forum/search.php
@@ -36,7 +36,7 @@ class Course_Forum_SearchController extends Forum\BaseController
}
$tags = array_map(fn(TagDTO $tag) => $tag->toRawArray(), TagDTO::getForumTags());
- $discussion_types = array_map(fn(DiscussionType $discussion_type) => $discussion_type->toRawArray(), DiscussionType::getForumDiscussionType());
+ $discussion_types = array_map(fn(DiscussionType $discussion_type) => $discussion_type->toRawArray(), DiscussionType::getAll());
$this->render_vue_app(
Studip\VueApp::create('forum/search/Index')
diff --git a/app/views/course/forum/discussion_types/index.php b/app/views/course/forum/discussion_types/index.php
deleted file mode 100644
index f6eb534..0000000
--- a/app/views/course/forum/discussion_types/index.php
+++ /dev/null
@@ -1,79 +0,0 @@
-
-
-
-
-
- = _('Diskussiontypen') ?>
-
-
- = Icon::create('add', 'clickable', ['title' => _('Neuen Diskussionstyp anlegen')]) ?>
-
-
-
-
-
-
-
-
-
-
-
-
- | = _('Icon') ?> |
- = _('Name') ?> |
- = _('Aktionen') ?> |
-
-
-
-
-
- |
- icon) : ?>
- = Icon::create($type->icon, ['title' => htmlReady($type->icon)])->asSvg(24) ?>
-
- |
-
-
- = htmlReady($type->name) ?>
-
- |
-
-
- = ActionMenu::get()
- ->addLink(
- $controller->url_for('course/forum/discussion_types/edit', $type),
- _('Bearbeiten'),
- Icon::create('edit', 'clickable', ['title' => _('Diskussionstyp bearbeiten')]),
- ['data-dialog' => 'width=700']
- )
- ->addLink(
- $controller->url_for('course/forum/discussion_types/delete', $type),
- _('Löschen'),
- Icon::create('trash', 'clickable',['title' => _('Diskussionstyp löschen')]),
- ['data-confirm' => sprintf(
- _('Wollen Sie "%s" löschen?'),
- $type->name
- )]
- );
- ?>
- |
-
-
-
-
-
- |
- = _('Es sind noch keine Diskussionstypen vorhanden.') ?>
- |
-
-
-
-
-
diff --git a/lib/classes/JsonApi/RouteMap.php b/lib/classes/JsonApi/RouteMap.php
index 47bd666..ca53a4a 100644
--- a/lib/classes/JsonApi/RouteMap.php
+++ b/lib/classes/JsonApi/RouteMap.php
@@ -695,7 +695,9 @@ class RouteMap
$group->group('/forum-discussion-types', function ($forum) {
$forum->get('', Routes\Forum\DiscussionTypeIndex::class);
+ $forum->post('', Routes\Forum\DiscussionTypeStore::class);
$forum->get('/{type_id}', Routes\Forum\DiscussionTypeShow::class);
+ $forum->delete('/{type_id}', Routes\Forum\DiscussionTypeDelete::class);
});
$group->group('/forum-postings', function ($forum) {
diff --git a/lib/classes/JsonApi/Routes/Forum/DiscussionTypeDelete.php b/lib/classes/JsonApi/Routes/Forum/DiscussionTypeDelete.php
new file mode 100644
index 0000000..2ff19c4
--- /dev/null
+++ b/lib/classes/JsonApi/Routes/Forum/DiscussionTypeDelete.php
@@ -0,0 +1,29 @@
+getUser($request);
+ if (!$GLOBALS['perm']->have_perm('root', $user->id)) {
+ throw new AuthorizationFailedException();
+ }
+
+ $discussion_type = DiscussionType::find($args['type_id']);
+ if (!$discussion_type) {
+ throw new RecordNotFoundException();
+ }
+
+ $discussion_type->delete();
+
+ return $this->getCodeResponse(204);
+ }
+}
diff --git a/lib/classes/JsonApi/Routes/Forum/DiscussionTypeIndex.php b/lib/classes/JsonApi/Routes/Forum/DiscussionTypeIndex.php
index cd32b90..2955c97 100644
--- a/lib/classes/JsonApi/Routes/Forum/DiscussionTypeIndex.php
+++ b/lib/classes/JsonApi/Routes/Forum/DiscussionTypeIndex.php
@@ -2,7 +2,6 @@
namespace JsonApi\Routes\Forum;
use Forum\DiscussionType;
-use JsonApi\Errors\BadRequestException;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Message\ResponseInterface as Response;
use JsonApi\JsonApiController;
@@ -10,13 +9,10 @@ use JsonApi\JsonApiController;
class DiscussionTypeIndex extends JsonApiController
{
protected $allowedPagingParameters = ['offset', 'limit'];
- protected $allowedIncludePaths = [
- \JsonApi\Schemas\Forum\DiscussionType::REL_DISCUSSIONS
- ];
public function __invoke(Request $request, Response $response, $args)
{
- $discussion_types = DiscussionType::findBySQL('1');
+ $discussion_types = DiscussionType::getAll();
return $this->getPaginatedContentResponse(
array_slice($discussion_types, ...$this->getOffsetAndLimit()),
diff --git a/lib/classes/JsonApi/Routes/Forum/DiscussionTypeShow.php b/lib/classes/JsonApi/Routes/Forum/DiscussionTypeShow.php
index 939835a..6bfdc0f 100644
--- a/lib/classes/JsonApi/Routes/Forum/DiscussionTypeShow.php
+++ b/lib/classes/JsonApi/Routes/Forum/DiscussionTypeShow.php
@@ -9,10 +9,6 @@ use JsonApi\JsonApiController;
class DiscussionTypeShow extends JsonApiController
{
- protected $allowedIncludePaths = [
- \JsonApi\Schemas\Forum\DiscussionType::REL_DISCUSSIONS
- ];
-
public function __invoke(Request $request, Response $response, $args)
{
$discussion_type = DiscussionType::find($args['type_id']);
diff --git a/lib/classes/JsonApi/Routes/Forum/DiscussionTypeStore.php b/lib/classes/JsonApi/Routes/Forum/DiscussionTypeStore.php
new file mode 100644
index 0000000..20db2cc
--- /dev/null
+++ b/lib/classes/JsonApi/Routes/Forum/DiscussionTypeStore.php
@@ -0,0 +1,46 @@
+getUser($request);
+ if (!$GLOBALS['perm']->have_perm('root', $user->id)) {
+ throw new AuthorizationFailedException();
+ }
+
+ $json = $this->validate($request);
+ $discussion_type = DiscussionType::create([
+ 'name' => self::arrayGet($json, 'data.attributes.name'),
+ 'icon' => self::arrayGet($json, 'data.attributes.icon')
+ ]);
+
+ return $this->getCreatedResponse($discussion_type);
+ }
+
+ protected function validateResourceDocument($json, $data)
+ {
+ $required_keys = [
+ 'data.attributes.name' => 'Missing `data.attributes.name`',
+ 'data.attributes.icon' => 'Missing `data.attributes.icon`',
+ ];
+
+ foreach ($required_keys as $key => $error_message) {
+ if (!self::arrayHas($json, $key)) {
+ return $error_message;
+ }
+ }
+
+ return null;
+ }
+}
diff --git a/lib/classes/JsonApi/Schemas/Forum/DiscussionType.php b/lib/classes/JsonApi/Schemas/Forum/DiscussionType.php
index 07ea34d..bac3686 100644
--- a/lib/classes/JsonApi/Schemas/Forum/DiscussionType.php
+++ b/lib/classes/JsonApi/Schemas/Forum/DiscussionType.php
@@ -11,34 +11,40 @@ class DiscussionType extends SchemaProvider
const REL_DISCUSSIONS = 'discussions';
- public function getId($discussionType): ?string
+ /**
+ * @inheritDoc
+ * @param \Forum\DiscussionType $resource
+ */
+ public function getId($resource): ?string
{
- return $discussionType->type_id;
+ return $resource->type_id;
}
- public function getAttributes($discussionType, ContextInterface $context): iterable
+ /**
+ * @inheritDoc
+ * @param \Forum\DiscussionType $resource
+ */
+ public function getAttributes($resource, ContextInterface $context): iterable
{
- return [
- 'name' => $discussionType->name,
- 'icon' => $discussionType->icon,
- 'mkdate' => date('c', $discussionType->mkdate),
- 'chdate' => date('c', $discussionType->chdate),
- ];
+ $attributes = $resource->transformData();
+ unset($attributes['id']);
+ return $attributes;
}
/**
- * @SuppressWarnings(PHPMD.UnusedFormalParameter)
+ * @inheritDoc
+ * @param \Forum\DiscussionType $resource
*/
- public function getRelationships($discussionType, ContextInterface $context): iterable
+ public function getRelationships($resource, ContextInterface $context): iterable
{
$relationships = [];
- $relationships = $this->addDiscussionsRelationship($relationships, $discussionType, $this->shouldInclude($context, self::REL_DISCUSSIONS));
+ $relationships = $this->addDiscussionsRelationship($relationships, $resource, $this->shouldInclude($context, self::REL_DISCUSSIONS));
return $relationships;
}
- private function addDiscussionsRelationship($relationships, $discussionType, $withDiscussions = false)
+ private function addDiscussionsRelationship(array $relationships, \Forum\DiscussionType $discussionType, $withDiscussions = false): array
{
if ($withDiscussions) {
$relationships[self::REL_DISCUSSIONS] = [
diff --git a/lib/models/Forum/DiscussionType.php b/lib/models/Forum/DiscussionType.php
index 0462388..127abd3 100644
--- a/lib/models/Forum/DiscussionType.php
+++ b/lib/models/Forum/DiscussionType.php
@@ -30,11 +30,25 @@ class DiscussionType extends SimpleORMap
parent::configure($config);
}
- public static function getForumDiscussionType(): array
+ /**
+ * @return self[]
+ */
+ public static function getAll(): array
{
return self::findBySQL("TRUE ORDER BY `mkdate` DESC");
}
+ public function transformData(): array
+ {
+ return [
+ 'id' => $this->type_id,
+ 'icon' => $this->icon,
+ 'name' => $this->name,
+ 'chdate' => date('c', $this->chdate),
+ 'mkdate' => date('c', $this->mkdate)
+ ];
+ }
+
/**
* @return Discussion[]
*/
diff --git a/resources/assets/stylesheets/scss/buttons.scss b/resources/assets/stylesheets/scss/buttons.scss
index bedd67c..e4197c0 100644
--- a/resources/assets/stylesheets/scss/buttons.scss
+++ b/resources/assets/stylesheets/scss/buttons.scss
@@ -99,7 +99,7 @@ button.button {
}
@mixin button-base() {
- color: var(--base-color);
+ color: var(--color--brand-primary);
transition: color var(--transition-duration);
}
diff --git a/resources/assets/stylesheets/scss/forum.scss b/resources/assets/stylesheets/scss/forum.scss
index b0a02e9..ccfe15f 100644
--- a/resources/assets/stylesheets/scss/forum.scss
+++ b/resources/assets/stylesheets/scss/forum.scss
@@ -665,10 +665,6 @@ $card-max-width: 300px;
.discussion {
background-color: var(--color--main-navigation-background);
- hr {
- margin: 0;
- }
-
&__status,
&__body,
&__form-container {
@@ -1018,8 +1014,11 @@ $card-max-width: 300px;
gap: 15px;
padding: 15px;
justify-content: space-between;
- border: thin solid var(--color--button-inactive-border);
+ border: 1px solid var(--light-gray-color-40);
max-width: calc(48em - 30px);
+ border-radius: var(--border-radius-default);
+ max-height: 300px;
+ overflow-y: scroll;
.button {
display: flex;
diff --git a/resources/vue/apps/forum/discussions/Show.vue b/resources/vue/apps/forum/discussions/Show.vue
index 9c0d1c0..7e52669 100644
--- a/resources/vue/apps/forum/discussions/Show.vue
+++ b/resources/vue/apps/forum/discussions/Show.vue
@@ -216,14 +216,14 @@ onMounted(async () => {
{{ $gettext('Es sind noch keine Beiträge vorhanden.') }}
-
+
-
+
@@ -233,7 +233,7 @@ onMounted(async () => {
:discussion="discussion"
:is_unread="read_index < index + 2"
/>
-
+
diff --git a/resources/vue/apps/forum/discussions_types/Edit.vue b/resources/vue/apps/forum/discussions_types/Edit.vue
index 1d5b48b..40084ea 100644
--- a/resources/vue/apps/forum/discussions_types/Edit.vue
+++ b/resources/vue/apps/forum/discussions_types/Edit.vue
@@ -1,11 +1,12 @@
@@ -48,7 +43,7 @@ const formActionURL = computed(() => {
required
type="text"
name="name"
- v-model="formSate.name"
+ v-model="formState.name"
maxlength="100" />
@@ -60,7 +55,7 @@ const formActionURL = computed(() => {
-
+
@@ -79,7 +74,7 @@ const formActionURL = computed(() => {