From 28a9e7237135aa6a4f6beb69bda09a28d52d31a4 Mon Sep 17 00:00:00 2001 From: Murtaza Sultani Date: Mon, 28 Jul 2025 17:23:38 +0200 Subject: Resolve "Forum verwendet falsche Rechteabfragen in der JSON-API" Closes #5753 Merge request studip/studip!4386 --- app/controllers/course/forum/BaseController.php | 64 +++++ .../course/forum/ForumBaseController.php | 64 ----- app/controllers/course/forum/categories.php | 18 +- app/controllers/course/forum/configs.php | 4 +- app/controllers/course/forum/discussion_types.php | 10 +- app/controllers/course/forum/discussions.php | 50 ++-- app/controllers/course/forum/recent.php | 4 +- app/controllers/course/forum/search.php | 12 +- app/controllers/course/forum/subscriptions.php | 4 +- app/controllers/course/forum/topics.php | 26 +- app/views/course/forum/configs/edit.php | 1 + app/views/course/forum/discussion_types/index.php | 7 +- lib/classes/Forum/DTO/ForumMember.php | 53 ---- lib/classes/Forum/DTO/ForumTag.php | 45 ---- lib/classes/Forum/DTO/Member.php | 53 ++++ lib/classes/Forum/DTO/Tag.php | 45 ++++ .../Forum/Service/DiscussionNotification.php | 16 +- lib/classes/Forum/Service/PostingNotification.php | 24 +- lib/classes/JsonApi/RouteMap.php | 53 ++-- lib/classes/JsonApi/Routes/Forum/Authority.php | 13 + lib/classes/JsonApi/Routes/Forum/CategoryIndex.php | 37 +++ lib/classes/JsonApi/Routes/Forum/CategoryShow.php | 36 +++ .../JsonApi/Routes/Forum/CategoryTopics.php | 42 +++ .../JsonApi/Routes/Forum/CategoryUpdateSort.php | 63 +++++ lib/classes/JsonApi/Routes/Forum/ConfigIndex.php | 35 +++ .../JsonApi/Routes/Forum/DiscussionIndex.php | 104 ++++++++ .../JsonApi/Routes/Forum/DiscussionPostings.php | 50 ++++ .../JsonApi/Routes/Forum/DiscussionShow.php | 39 +++ .../JsonApi/Routes/Forum/DiscussionTypeIndex.php | 26 ++ .../JsonApi/Routes/Forum/DiscussionTypeShow.php | 25 ++ .../JsonApi/Routes/Forum/ForumCategoryIndex.php | 38 --- .../JsonApi/Routes/Forum/ForumCategoryShow.php | 37 --- .../JsonApi/Routes/Forum/ForumCategoryTopics.php | 43 --- .../Routes/Forum/ForumCategoryUpdateSort.php | 63 ----- .../JsonApi/Routes/Forum/ForumConfigIndex.php | 36 --- .../JsonApi/Routes/Forum/ForumDiscussionIndex.php | 105 -------- .../Routes/Forum/ForumDiscussionPostings.php | 51 ---- .../JsonApi/Routes/Forum/ForumDiscussionShow.php | 40 --- .../Forum/ForumDiscussionTypeDiscussions.php | 32 --- .../Routes/Forum/ForumDiscussionTypeIndex.php | 26 -- .../Routes/Forum/ForumDiscussionTypeShow.php | 25 -- .../JsonApi/Routes/Forum/ForumPostingDelete.php | 38 --- .../Routes/Forum/ForumPostingReactionDelete.php | 32 --- .../Routes/Forum/ForumPostingReactionShow.php | 26 -- .../Routes/Forum/ForumPostingReactionStore.php | 77 ------ .../JsonApi/Routes/Forum/ForumPostingReactions.php | 45 ---- .../JsonApi/Routes/Forum/ForumPostingShow.php | 41 --- .../JsonApi/Routes/Forum/ForumPostingStore.php | 100 ------- .../JsonApi/Routes/Forum/ForumPostingUpdate.php | 69 ----- .../Routes/Forum/ForumSubscriptionDelete.php | 32 --- .../Routes/Forum/ForumSubscriptionIndex.php | 40 --- .../JsonApi/Routes/Forum/ForumSubscriptionShow.php | 36 --- .../Routes/Forum/ForumSubscriptionStore.php | 86 ------ .../JsonApi/Routes/Forum/ForumTopicDiscussions.php | 48 ---- .../JsonApi/Routes/Forum/ForumTopicIndex.php | 39 --- .../JsonApi/Routes/Forum/ForumTopicShow.php | 35 --- .../JsonApi/Routes/Forum/ForumTopicUpdateSort.php | 63 ----- lib/classes/JsonApi/Routes/Forum/PostingDelete.php | 38 +++ .../JsonApi/Routes/Forum/PostingReactionDelete.php | 32 +++ .../JsonApi/Routes/Forum/PostingReactionShow.php | 26 ++ .../JsonApi/Routes/Forum/PostingReactionStore.php | 76 ++++++ .../JsonApi/Routes/Forum/PostingReactions.php | 44 ++++ lib/classes/JsonApi/Routes/Forum/PostingShow.php | 40 +++ lib/classes/JsonApi/Routes/Forum/PostingStore.php | 99 +++++++ lib/classes/JsonApi/Routes/Forum/PostingUpdate.php | 69 +++++ .../JsonApi/Routes/Forum/SubscriptionDelete.php | 32 +++ .../JsonApi/Routes/Forum/SubscriptionIndex.php | 39 +++ .../JsonApi/Routes/Forum/SubscriptionShow.php | 36 +++ .../JsonApi/Routes/Forum/SubscriptionStore.php | 86 ++++++ .../JsonApi/Routes/Forum/TopicDiscussions.php | 47 ++++ lib/classes/JsonApi/Routes/Forum/TopicIndex.php | 38 +++ lib/classes/JsonApi/Routes/Forum/TopicShow.php | 34 +++ .../JsonApi/Routes/Forum/TopicUpdateSort.php | 63 +++++ .../JsonApi/Routes/Institutes/Authority.php | 5 - lib/classes/JsonApi/Routes/RangeAuthority.php | 41 --- lib/classes/JsonApi/SchemaMap.php | 18 +- lib/classes/JsonApi/Schemas/Activity.php | 2 +- lib/classes/JsonApi/Schemas/Forum/Category.php | 73 ++++++ lib/classes/JsonApi/Schemas/Forum/Discussion.php | 170 ++++++++++++ .../JsonApi/Schemas/Forum/DiscussionType.php | 54 ++++ .../JsonApi/Schemas/Forum/ForumCategory.php | 73 ------ .../JsonApi/Schemas/Forum/ForumDiscussion.php | 170 ------------ .../JsonApi/Schemas/Forum/ForumDiscussionType.php | 54 ---- lib/classes/JsonApi/Schemas/Forum/ForumMember.php | 30 --- lib/classes/JsonApi/Schemas/Forum/ForumPosting.php | 123 --------- .../JsonApi/Schemas/Forum/ForumPostingReaction.php | 71 ----- .../JsonApi/Schemas/Forum/ForumSubscription.php | 84 ------ lib/classes/JsonApi/Schemas/Forum/ForumTag.php | 27 -- lib/classes/JsonApi/Schemas/Forum/ForumTopic.php | 106 -------- lib/classes/JsonApi/Schemas/Forum/Member.php | 30 +++ lib/classes/JsonApi/Schemas/Forum/Posting.php | 123 +++++++++ .../JsonApi/Schemas/Forum/PostingReaction.php | 71 +++++ lib/classes/JsonApi/Schemas/Forum/Subscription.php | 84 ++++++ lib/classes/JsonApi/Schemas/Forum/Tag.php | 27 ++ lib/classes/JsonApi/Schemas/Forum/Topic.php | 106 ++++++++ lib/classes/Privacy.php | 2 +- lib/classes/Siteinfo.php | 2 +- lib/middleware/LegacyRedirectorMiddleware.php | 4 +- lib/models/CourseTopic.php | 4 +- lib/models/Forum/Category.php | 109 ++++++++ lib/models/Forum/Discussion.php | 289 +++++++++++++++++++++ lib/models/Forum/DiscussionType.php | 56 ++++ lib/models/Forum/ForumCategory.php | 109 -------- lib/models/Forum/ForumDiscussion.php | 283 -------------------- lib/models/Forum/ForumDiscussionType.php | 56 ---- lib/models/Forum/ForumPosting.php | 120 --------- lib/models/Forum/ForumPostingReaction.php | 49 ---- lib/models/Forum/ForumPostingRead.php | 64 ----- lib/models/Forum/ForumSubscription.php | 70 ----- lib/models/Forum/ForumTopic.php | 168 ------------ lib/models/Forum/Posting.php | 120 +++++++++ lib/models/Forum/PostingReaction.php | 49 ++++ lib/models/Forum/PostingRead.php | 64 +++++ lib/models/Forum/Subscription.php | 70 +++++ lib/models/Forum/Topic.php | 168 ++++++++++++ lib/models/User.php | 6 +- lib/modules/CoreForum.php | 8 +- 117 files changed, 3231 insertions(+), 3306 deletions(-) create mode 100644 app/controllers/course/forum/BaseController.php delete mode 100644 app/controllers/course/forum/ForumBaseController.php delete mode 100644 lib/classes/Forum/DTO/ForumMember.php delete mode 100644 lib/classes/Forum/DTO/ForumTag.php create mode 100644 lib/classes/Forum/DTO/Member.php create mode 100644 lib/classes/Forum/DTO/Tag.php create mode 100644 lib/classes/JsonApi/Routes/Forum/Authority.php create mode 100644 lib/classes/JsonApi/Routes/Forum/CategoryIndex.php create mode 100644 lib/classes/JsonApi/Routes/Forum/CategoryShow.php create mode 100644 lib/classes/JsonApi/Routes/Forum/CategoryTopics.php create mode 100644 lib/classes/JsonApi/Routes/Forum/CategoryUpdateSort.php create mode 100644 lib/classes/JsonApi/Routes/Forum/ConfigIndex.php create mode 100644 lib/classes/JsonApi/Routes/Forum/DiscussionIndex.php create mode 100644 lib/classes/JsonApi/Routes/Forum/DiscussionPostings.php create mode 100644 lib/classes/JsonApi/Routes/Forum/DiscussionShow.php create mode 100644 lib/classes/JsonApi/Routes/Forum/DiscussionTypeIndex.php create mode 100644 lib/classes/JsonApi/Routes/Forum/DiscussionTypeShow.php delete mode 100644 lib/classes/JsonApi/Routes/Forum/ForumCategoryIndex.php delete mode 100644 lib/classes/JsonApi/Routes/Forum/ForumCategoryShow.php delete mode 100644 lib/classes/JsonApi/Routes/Forum/ForumCategoryTopics.php delete mode 100644 lib/classes/JsonApi/Routes/Forum/ForumCategoryUpdateSort.php delete mode 100644 lib/classes/JsonApi/Routes/Forum/ForumConfigIndex.php delete mode 100644 lib/classes/JsonApi/Routes/Forum/ForumDiscussionIndex.php delete mode 100644 lib/classes/JsonApi/Routes/Forum/ForumDiscussionPostings.php delete mode 100644 lib/classes/JsonApi/Routes/Forum/ForumDiscussionShow.php delete mode 100644 lib/classes/JsonApi/Routes/Forum/ForumDiscussionTypeDiscussions.php delete mode 100644 lib/classes/JsonApi/Routes/Forum/ForumDiscussionTypeIndex.php delete mode 100644 lib/classes/JsonApi/Routes/Forum/ForumDiscussionTypeShow.php delete mode 100644 lib/classes/JsonApi/Routes/Forum/ForumPostingDelete.php delete mode 100644 lib/classes/JsonApi/Routes/Forum/ForumPostingReactionDelete.php delete mode 100644 lib/classes/JsonApi/Routes/Forum/ForumPostingReactionShow.php delete mode 100644 lib/classes/JsonApi/Routes/Forum/ForumPostingReactionStore.php delete mode 100644 lib/classes/JsonApi/Routes/Forum/ForumPostingReactions.php delete mode 100644 lib/classes/JsonApi/Routes/Forum/ForumPostingShow.php delete mode 100644 lib/classes/JsonApi/Routes/Forum/ForumPostingStore.php delete mode 100644 lib/classes/JsonApi/Routes/Forum/ForumPostingUpdate.php delete mode 100644 lib/classes/JsonApi/Routes/Forum/ForumSubscriptionDelete.php delete mode 100644 lib/classes/JsonApi/Routes/Forum/ForumSubscriptionIndex.php delete mode 100644 lib/classes/JsonApi/Routes/Forum/ForumSubscriptionShow.php delete mode 100644 lib/classes/JsonApi/Routes/Forum/ForumSubscriptionStore.php delete mode 100644 lib/classes/JsonApi/Routes/Forum/ForumTopicDiscussions.php delete mode 100644 lib/classes/JsonApi/Routes/Forum/ForumTopicIndex.php delete mode 100644 lib/classes/JsonApi/Routes/Forum/ForumTopicShow.php delete mode 100644 lib/classes/JsonApi/Routes/Forum/ForumTopicUpdateSort.php create mode 100644 lib/classes/JsonApi/Routes/Forum/PostingDelete.php create mode 100644 lib/classes/JsonApi/Routes/Forum/PostingReactionDelete.php create mode 100644 lib/classes/JsonApi/Routes/Forum/PostingReactionShow.php create mode 100644 lib/classes/JsonApi/Routes/Forum/PostingReactionStore.php create mode 100644 lib/classes/JsonApi/Routes/Forum/PostingReactions.php create mode 100644 lib/classes/JsonApi/Routes/Forum/PostingShow.php create mode 100644 lib/classes/JsonApi/Routes/Forum/PostingStore.php create mode 100644 lib/classes/JsonApi/Routes/Forum/PostingUpdate.php create mode 100644 lib/classes/JsonApi/Routes/Forum/SubscriptionDelete.php create mode 100644 lib/classes/JsonApi/Routes/Forum/SubscriptionIndex.php create mode 100644 lib/classes/JsonApi/Routes/Forum/SubscriptionShow.php create mode 100644 lib/classes/JsonApi/Routes/Forum/SubscriptionStore.php create mode 100644 lib/classes/JsonApi/Routes/Forum/TopicDiscussions.php create mode 100644 lib/classes/JsonApi/Routes/Forum/TopicIndex.php create mode 100644 lib/classes/JsonApi/Routes/Forum/TopicShow.php create mode 100644 lib/classes/JsonApi/Routes/Forum/TopicUpdateSort.php delete mode 100644 lib/classes/JsonApi/Routes/RangeAuthority.php create mode 100644 lib/classes/JsonApi/Schemas/Forum/Category.php create mode 100644 lib/classes/JsonApi/Schemas/Forum/Discussion.php create mode 100644 lib/classes/JsonApi/Schemas/Forum/DiscussionType.php delete mode 100644 lib/classes/JsonApi/Schemas/Forum/ForumCategory.php delete mode 100644 lib/classes/JsonApi/Schemas/Forum/ForumDiscussion.php delete mode 100644 lib/classes/JsonApi/Schemas/Forum/ForumDiscussionType.php delete mode 100644 lib/classes/JsonApi/Schemas/Forum/ForumMember.php delete mode 100644 lib/classes/JsonApi/Schemas/Forum/ForumPosting.php delete mode 100644 lib/classes/JsonApi/Schemas/Forum/ForumPostingReaction.php delete mode 100644 lib/classes/JsonApi/Schemas/Forum/ForumSubscription.php delete mode 100644 lib/classes/JsonApi/Schemas/Forum/ForumTag.php delete mode 100644 lib/classes/JsonApi/Schemas/Forum/ForumTopic.php create mode 100644 lib/classes/JsonApi/Schemas/Forum/Member.php create mode 100644 lib/classes/JsonApi/Schemas/Forum/Posting.php create mode 100644 lib/classes/JsonApi/Schemas/Forum/PostingReaction.php create mode 100644 lib/classes/JsonApi/Schemas/Forum/Subscription.php create mode 100644 lib/classes/JsonApi/Schemas/Forum/Tag.php create mode 100644 lib/classes/JsonApi/Schemas/Forum/Topic.php create mode 100644 lib/models/Forum/Category.php create mode 100644 lib/models/Forum/Discussion.php create mode 100644 lib/models/Forum/DiscussionType.php delete mode 100644 lib/models/Forum/ForumCategory.php delete mode 100644 lib/models/Forum/ForumDiscussion.php delete mode 100644 lib/models/Forum/ForumDiscussionType.php delete mode 100644 lib/models/Forum/ForumPosting.php delete mode 100644 lib/models/Forum/ForumPostingReaction.php delete mode 100644 lib/models/Forum/ForumPostingRead.php delete mode 100644 lib/models/Forum/ForumSubscription.php delete mode 100644 lib/models/Forum/ForumTopic.php create mode 100644 lib/models/Forum/Posting.php create mode 100644 lib/models/Forum/PostingReaction.php create mode 100644 lib/models/Forum/PostingRead.php create mode 100644 lib/models/Forum/Subscription.php create mode 100644 lib/models/Forum/Topic.php diff --git a/app/controllers/course/forum/BaseController.php b/app/controllers/course/forum/BaseController.php new file mode 100644 index 0000000..6525250 --- /dev/null +++ b/app/controllers/course/forum/BaseController.php @@ -0,0 +1,64 @@ +range_id = Context::getId(); + $this->is_moderator = CoreForum::isModerator($this->range_id); + $this->is_admin = CoreForum::isAdmin($this->range_id); + + $this->buildSidebar(); + + parent::before_filter($action, $args); + } + + protected function buildSidebar(): void + { + $actions = new ActionsWidget(); + + $actions->addLink( + _('Neue Diskussion starten'), + $this->url_for('course/forum/discussions/edit'), + Icon::create('add', Icon::ROLE_CLICKABLE, ['title' => _('Neue Diskussion starten')]) + )->asDialog('width=900;height=750'); + + if ($this->is_admin) { + $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=300'] + ); + } + + Sidebar::Get()->addWidget($actions); + + $search = new SearchWidget($this->url_for('course/forum/search', [ + 'begin' => Request::int('begin'), + 'end' => Request::int('end') + ])); + + $search->addNeedle( + _('Suche nach Diskussionen oder Beiträge'), + 'q', + true + ); + + Sidebar::Get()->addWidget($search, 'forum_search'); + } +} diff --git a/app/controllers/course/forum/ForumBaseController.php b/app/controllers/course/forum/ForumBaseController.php deleted file mode 100644 index 246a8b2..0000000 --- a/app/controllers/course/forum/ForumBaseController.php +++ /dev/null @@ -1,64 +0,0 @@ -range_id = Context::getId(); - $this->is_moderator = CoreForum::isModerator($this->range_id); - $this->is_admin = CoreForum::isAdmin($this->range_id); - - $this->buildSidebar(); - - parent::before_filter($action, $args); - } - - protected function buildSidebar(): void - { - $actions = new ActionsWidget(); - - $actions->addLink( - _('Neue Diskussion starten'), - $this->url_for('course/forum/discussions/edit'), - Icon::create('add', Icon::ROLE_CLICKABLE, ['title' => _('Neue Diskussion starten')]) - )->asDialog('width=900;height=750'); - - if ($this->is_admin) { - $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=300'] - ); - } - - Sidebar::Get()->addWidget($actions); - - $search = new SearchWidget($this->url_for('course/forum/search', [ - 'begin' => Request::int('begin'), - 'end' => Request::int('end') - ])); - - $search->addNeedle( - _('Suche nach Diskussionen oder Beiträge'), - 'q', - true - ); - - Sidebar::Get()->addWidget($search, 'forum_search'); - } -} diff --git a/app/controllers/course/forum/categories.php b/app/controllers/course/forum/categories.php index 4f9f324..8ed7950 100644 --- a/app/controllers/course/forum/categories.php +++ b/app/controllers/course/forum/categories.php @@ -1,9 +1,9 @@ range_id, $category_id]); + $category = Category::findOneBySQL("range_id = ? AND category_id = ?", [$this->range_id, $category_id]); if (!$category) { throw new AccessDeniedException(); } } else { PageLayout::setTitle(_('Neue Kategorie anlegen')); - $category = new ForumCategory(); + $category = new Category(); } $this->render_vue_app( @@ -81,12 +81,12 @@ class Course_Forum_CategoriesController extends Forum\ForumBaseController CSRFProtection::verifyUnsafeRequest(); if ($category_id) { - $category = ForumCategory::findOneBySQL("range_id = ? AND category_id = ?", [$this->range_id, $category_id]); + $category = Category::findOneBySQL("range_id = ? AND category_id = ?", [$this->range_id, $category_id]); if (!$category) { throw new AccessDeniedException(); } } else { - $category = new ForumCategory(); + $category = new Category(); $category->range_id = $this->range_id; } @@ -107,7 +107,7 @@ class Course_Forum_CategoriesController extends Forum\ForumBaseController throw new AccessDeniedException(); } - $category = ForumCategory::findOneBySQL("range_id = ? AND category_id = ?", [$this->range_id, $category_id]); + $category = Category::findOneBySQL("range_id = ? AND category_id = ?", [$this->range_id, $category_id]); if (!$category) { throw new AccessDeniedException(); diff --git a/app/controllers/course/forum/configs.php b/app/controllers/course/forum/configs.php index 70fdb2b..209799f 100644 --- a/app/controllers/course/forum/configs.php +++ b/app/controllers/course/forum/configs.php @@ -1,7 +1,7 @@ discussion_types = ForumDiscussionType::findBySQL("TRUE ORDER BY mkdate DESC"); + $this->discussion_types = DiscussionType::findBySQL("TRUE ORDER BY mkdate DESC"); } - public function edit_action(ForumDiscussionType $discussion_type = null) + public function edit_action(DiscussionType $discussion_type = null) { if ($discussion_type->isNew()) { PageLayout::setTitle(_('Neuen Diskussionstyp anlegen')); @@ -58,7 +58,7 @@ class Course_Forum_DiscussionTypesController extends AuthenticatedController ); } - public function save_action(ForumDiscussionType $discussion_type = null) + public function save_action(DiscussionType $discussion_type = null) { CSRFProtection::verifyUnsafeRequest(); @@ -72,7 +72,7 @@ class Course_Forum_DiscussionTypesController extends AuthenticatedController $this->relocate('course/forum/discussion_types/index'); } - public function delete_action(ForumDiscussionType $discussion_type) + public function delete_action(DiscussionType $discussion_type) { $discussion_type->delete(); diff --git a/app/controllers/course/forum/discussions.php b/app/controllers/course/forum/discussions.php index e86940a..2463d6d 100644 --- a/app/controllers/course/forum/discussions.php +++ b/app/controllers/course/forum/discussions.php @@ -1,17 +1,17 @@ view_count += 1; $discussion->store(); - $posting_read = ForumPostingRead::findOneBySQL( + $posting_read = PostingRead::findOneBySQL( "discussion_id = :discussion_id AND user_id = :user_id", [ 'discussion_id' => $discussion->getId(), @@ -72,7 +72,7 @@ class Course_Forum_DiscussionsController extends Forum\ForumBaseController ] ); - $user_subscription = ForumSubscription::findOneBySQL( + $user_subscription = Subscription::findOneBySQL( "subject = :subject AND subject_id = :subject_id AND user_id = :user_id", [ 'subject' => 'discussion', @@ -82,8 +82,8 @@ class Course_Forum_DiscussionsController extends Forum\ForumBaseController ); $category = $discussion->getCategory(); - $tags = array_map(fn(ForumTag $tag) => $tag->toRawArray(), $discussion->tags); - $members = array_map(fn(ForumMember $member) => $member->toRawArray(), $discussion->members); + $tags = array_map(fn(TagDTO $tag) => $tag->toRawArray(), $discussion->tags); + $members = array_map(fn(MemberDTO $member) => $member->toRawArray(), $discussion->members); $this->render_vue_app( Studip\VueApp::create('forum/discussions/Show') @@ -110,7 +110,7 @@ class Course_Forum_DiscussionsController extends Forum\ForumBaseController ); } - public function edit_action(ForumDiscussion $discussion = null) + public function edit_action(Discussion $discussion = null) { if ($discussion->isNew()) { PageLayout::setTitle(_('Neue Diskussion starten')); @@ -130,9 +130,9 @@ class Course_Forum_DiscussionsController extends Forum\ForumBaseController ['range_id' => $this->range_id] ); - $all_tags = array_map(fn(ForumTag $tag) => $tag->toRawArray(), ForumTag::getForumTags()); - $discussion_tags = array_map(fn(ForumTag $tag) => $tag->toRawArray(), $discussion->tags); - $discussion_types = array_map(fn(ForumDiscussionType $discussion_type) => $discussion_type->toRawArray(), ForumDiscussionType::getForumDiscussionType()); + $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()); $this->render_vue_app( Studip\VueApp::create('forum/discussions/Edit') @@ -154,9 +154,9 @@ class Course_Forum_DiscussionsController extends Forum\ForumBaseController CSRFProtection::verifyUnsafeRequest(); if ($discussion_id) { - $discussion = ForumDiscussion::find($discussion_id); + $discussion = Discussion::find($discussion_id); } else { - $discussion = new ForumDiscussion(); + $discussion = new Discussion(); $discussion->user_id = User::findCurrent()->user_id; } @@ -173,7 +173,7 @@ class Course_Forum_DiscussionsController extends Forum\ForumBaseController $topic = json_decode(Request::get('topic'), true); if (empty($topic['topic_id'])) { - $newTopic = ForumTopic::create([ + $newTopic = Topic::create([ 'range_id' => $this->range_id, 'name' => $topic['name'] ]); @@ -185,7 +185,7 @@ class Course_Forum_DiscussionsController extends Forum\ForumBaseController $discussion->store(); if (!$discussion_id && Request::get('content')) { - ForumPosting::create([ + Posting::create([ 'range_id' => $this->range_id, 'discussion_id' => $discussion->discussion_id, 'content' => Markup::markAsHtml(Request::get('content')), @@ -222,7 +222,7 @@ class Course_Forum_DiscussionsController extends Forum\ForumBaseController public function delete_action($discussion_id) { - $discussion = ForumDiscussion::find($discussion_id); + $discussion = Discussion::find($discussion_id); if (!$discussion) { throw new AccessDeniedException(); diff --git a/app/controllers/course/forum/recent.php b/app/controllers/course/forum/recent.php index 2c3fda2..7a117cd 100644 --- a/app/controllers/course/forum/recent.php +++ b/app/controllers/course/forum/recent.php @@ -1,7 +1,7 @@ $tag->toRawArray(), ForumTag::getForumTags()); - $discussion_types = array_map(fn(ForumDiscussionType $discussion_type) => $discussion_type->toRawArray(), ForumDiscussionType::getForumDiscussionType()); + $tags = array_map(fn(TagDTO $tag) => $tag->toRawArray(), TagDTO::getForumTags()); + $discussion_types = array_map(fn(DiscussionType $discussion_type) => $discussion_type->toRawArray(), DiscussionType::getForumDiscussionType()); $this->render_vue_app( Studip\VueApp::create('forum/search/Index') diff --git a/app/controllers/course/forum/subscriptions.php b/app/controllers/course/forum/subscriptions.php index 399c072..1f5582c 100644 --- a/app/controllers/course/forum/subscriptions.php +++ b/app/controllers/course/forum/subscriptions.php @@ -1,7 +1,7 @@ name); - $user_subscription = ForumSubscription::findOneBySQL( + $user_subscription = Subscription::findOneBySQL( "subject = :subject AND subject_id = :subject_id AND user_id = :user_id", [ 'subject' => 'topic', @@ -65,14 +65,14 @@ class Course_Forum_TopicsController extends Forum\ForumBaseController if ($topic_id) { PageLayout::setTitle(_('Thema bearbeiten')); - $topic = ForumTopic::getCourseTopic($this->range_id, $topic_id); + $topic = Topic::getCourseTopic($this->range_id, $topic_id); if (!$topic) { throw new AccessDeniedException(); } } else { PageLayout::setTitle(_('Neues Thema anlegen')); - $topic = new ForumTopic(); + $topic = new Topic(); $topic['category_id'] = Request::get('category_id'); } @@ -99,19 +99,19 @@ class Course_Forum_TopicsController extends Forum\ForumBaseController CSRFProtection::verifyUnsafeRequest(); if ($topic_id) { - $topic = ForumTopic::getCourseTopic($this->range_id, $topic_id); + $topic = Topic::getCourseTopic($this->range_id, $topic_id); if (!$topic) { throw new AccessDeniedException(); } } else { - $topic = new ForumTopic(); + $topic = new Topic(); $topic->range_id = $this->range_id; } $category = json_decode(Request::get('category'), true); if (empty($category['category_id']) && !empty($category['name'])) { - $newCategory = ForumCategory::create([ + $newCategory = Category::create([ 'range_id' => $this->range_id, 'color' => '#28497C', 'name' => $category['name'] @@ -142,7 +142,7 @@ class Course_Forum_TopicsController extends Forum\ForumBaseController throw new AccessDeniedException(); } - $topic = ForumTopic::getCourseTopic($this->range_id, $topic_id); + $topic = Topic::getCourseTopic($this->range_id, $topic_id); if (!$topic) { throw new AccessDeniedException(); diff --git a/app/views/course/forum/configs/edit.php b/app/views/course/forum/configs/edit.php index 59d7b1d..ceb23d8 100644 --- a/app/views/course/forum/configs/edit.php +++ b/app/views/course/forum/configs/edit.php @@ -4,6 +4,7 @@ * @var CourseConfig $config */ ?> +
diff --git a/app/views/course/forum/discussion_types/index.php b/app/views/course/forum/discussion_types/index.php index a196342..c148d6b 100644 --- a/app/views/course/forum/discussion_types/index.php +++ b/app/views/course/forum/discussion_types/index.php @@ -1,11 +1,10 @@
diff --git a/lib/classes/Forum/DTO/ForumMember.php b/lib/classes/Forum/DTO/ForumMember.php deleted file mode 100644 index a6f4ea4..0000000 --- a/lib/classes/Forum/DTO/ForumMember.php +++ /dev/null @@ -1,53 +0,0 @@ -get_studip_perm($course_id, $user->user_id); - - return self::fromArray([ - 'id' => $user->user_id, - 'username' => $user->username, - 'name' => $user->getFullName(), - 'avatar_url' => Avatar::getAvatar($user->user_id)->getURL(Avatar::NORMAL), - 'role' => in_array($role, ['dozent', 'tutor']) ? 'moderator' : 'author' - ]); - } - - public function toRawArray(): array - { - return [ - 'id' => $this->id, - 'username' => $this->username, - 'name' => $this->name, - 'avatar_url' => $this->avatar_url, - 'role' => $this->role - ]; - } -} diff --git a/lib/classes/Forum/DTO/ForumTag.php b/lib/classes/Forum/DTO/ForumTag.php deleted file mode 100644 index ca0e045..0000000 --- a/lib/classes/Forum/DTO/ForumTag.php +++ /dev/null @@ -1,45 +0,0 @@ - $this->id, - 'name' => $this->name - ]; - } - - public static function getForumTags(): array - { - return DBManager::get()->fetchAll( - "SELECT DISTINCT `tags_relations`.`tag_id`, `tags`.`name` FROM `tags` - LEFT JOIN `tags_relations` ON `tags`.`id` = `tags_relations`.`tag_id` - WHERE `tags_relations`.`range_type` = 'forum' AND `tags`.`active` = TRUE - ORDER BY `tags`.`mkdate` DESC", - [], - function ($tag) { - return self::fromArray([ - 'id' => $tag['tag_id'], - 'name' => $tag['name'] - ]); - } - ); - } -} diff --git a/lib/classes/Forum/DTO/Member.php b/lib/classes/Forum/DTO/Member.php new file mode 100644 index 0000000..2e32b9a --- /dev/null +++ b/lib/classes/Forum/DTO/Member.php @@ -0,0 +1,53 @@ +get_studip_perm($course_id, $user->user_id); + + return self::fromArray([ + 'id' => $user->user_id, + 'username' => $user->username, + 'name' => $user->getFullName(), + 'avatar_url' => Avatar::getAvatar($user->user_id)->getURL(Avatar::NORMAL), + 'role' => in_array($role, ['dozent', 'tutor']) ? 'moderator' : 'author' + ]); + } + + public function toRawArray(): array + { + return [ + 'id' => $this->id, + 'username' => $this->username, + 'name' => $this->name, + 'avatar_url' => $this->avatar_url, + 'role' => $this->role + ]; + } +} diff --git a/lib/classes/Forum/DTO/Tag.php b/lib/classes/Forum/DTO/Tag.php new file mode 100644 index 0000000..93050ba --- /dev/null +++ b/lib/classes/Forum/DTO/Tag.php @@ -0,0 +1,45 @@ + $this->id, + 'name' => $this->name + ]; + } + + public static function getForumTags(): array + { + return DBManager::get()->fetchAll( + "SELECT DISTINCT `tags_relations`.`tag_id`, `tags`.`name` FROM `tags` + LEFT JOIN `tags_relations` ON `tags`.`id` = `tags_relations`.`tag_id` + WHERE `tags_relations`.`range_type` = 'forum' AND `tags`.`active` = TRUE + ORDER BY `tags`.`mkdate` DESC", + [], + function ($tag) { + return self::fromArray([ + 'id' => $tag['tag_id'], + 'name' => $tag['name'] + ]); + } + ); + } +} diff --git a/lib/classes/Forum/Service/DiscussionNotification.php b/lib/classes/Forum/Service/DiscussionNotification.php index c3dcfe8..0daaffb 100644 --- a/lib/classes/Forum/Service/DiscussionNotification.php +++ b/lib/classes/Forum/Service/DiscussionNotification.php @@ -4,17 +4,17 @@ namespace Forum\Service; use Forum\Enum\SubscriptionNotificationType; use Icon; use PersonalNotifications; -use Forum\ForumDiscussion; -use Forum\ForumSubscription; -use Forum\ForumTopic; +use Forum\Discussion; +use Forum\Subscription; +use Forum\Topic; use URLHelper; class DiscussionNotification { - protected ForumTopic $topic; - protected ForumDiscussion $discussion; + protected Topic $topic; + protected Discussion $discussion; - public function __construct(ForumDiscussion $discussion) + public function __construct(Discussion $discussion) { $this->discussion = $discussion; $this->topic = $discussion->topic; @@ -31,7 +31,7 @@ class DiscussionNotification protected function getSubscribers(): array { - return ForumSubscription::findBySQL( + return Subscription::findBySQL( "subject = :subject AND subject_id = :subject_id AND notification_type = :notification_type", [ 'subject' => 'topic', @@ -41,7 +41,7 @@ class DiscussionNotification ); } - protected function sendNotifications(ForumSubscription $subscriber): void + protected function sendNotifications(Subscription $subscriber): void { $url = URLHelper::getURL('dispatch.php/course/forum/discussions/show/'.$this->discussion->discussion_id, ['cid' => $this->topic->range_id], true); diff --git a/lib/classes/Forum/Service/PostingNotification.php b/lib/classes/Forum/Service/PostingNotification.php index 5e46535..9f6d82f 100644 --- a/lib/classes/Forum/Service/PostingNotification.php +++ b/lib/classes/Forum/Service/PostingNotification.php @@ -4,19 +4,19 @@ namespace Forum\Service; use Forum\Enum\SubscriptionNotificationType; use Icon; use PersonalNotifications; -use Forum\ForumDiscussion; -use Forum\ForumPosting; -use Forum\ForumSubscription; -use Forum\ForumTopic; +use Forum\Discussion; +use Forum\Posting; +use Forum\Subscription; +use Forum\Topic; use URLHelper; class PostingNotification { - protected ForumPosting $posting; - protected ForumDiscussion $discussion; - protected ForumTopic $topic; + protected Posting $posting; + protected Discussion $discussion; + protected Topic $topic; - public function __construct(ForumPosting $posting) + public function __construct(Posting $posting) { $this->posting = $posting; $this->topic = $posting->discussion->topic; @@ -60,7 +60,7 @@ class PostingNotification $query[1]['user_id'] = $excludeUserId; } - $subscriptions = ForumSubscription::findBySQL(...$query); + $subscriptions = Subscription::findBySQL(...$query); /** * Allow only one subscription per user. @@ -89,7 +89,7 @@ class PostingNotification return array_values($filteredSubscriptions); } - protected function sendNotifications(ForumSubscription $subscriber): void + protected function sendNotifications(Subscription $subscriber): void { $url = URLHelper::getURL('dispatch.php/course/forum/discussions/show/'.$this->discussion->discussion_id, ['cid' => $this->topic->range_id], true)."#post_" . $this->posting->posting_id; @@ -107,11 +107,11 @@ class PostingNotification ); } - protected function notifyParentPostAuthor(): ?ForumSubscription + protected function notifyParentPostAuthor(): ?Subscription { $parent = $this->posting->posting; - $subscriber = ForumSubscription::findOneBySQL( + $subscriber = Subscription::findOneBySQL( "range_id = :range_id AND subject_id IN (:subject_ids) AND user_id = :user_id AND notification_type != :notification_type ORDER BY subject", [ 'range_id' => $parent->range_id, diff --git a/lib/classes/JsonApi/RouteMap.php b/lib/classes/JsonApi/RouteMap.php index 176c1ea..235f4b6 100644 --- a/lib/classes/JsonApi/RouteMap.php +++ b/lib/classes/JsonApi/RouteMap.php @@ -674,54 +674,53 @@ class RouteMap private function addAuthenticatedForumRoutes(RouteCollectorProxy $group): void { $group->group('/courses/{range_id}', function ($forum) { - $forum->get('/forum-configs', Routes\Forum\ForumConfigIndex::class); - $forum->get('/forum-categories', Routes\Forum\ForumCategoryIndex::class); - $forum->get('/forum-discussions', Routes\Forum\ForumDiscussionIndex::class); - $forum->get('/forum-topics', Routes\Forum\ForumTopicIndex::class); - $forum->get('/forum-subscriptions', Routes\Forum\ForumSubscriptionIndex::class); + $forum->get('/forum-configs', Routes\Forum\ConfigIndex::class); + $forum->get('/forum-categories', Routes\Forum\CategoryIndex::class); + $forum->get('/forum-discussions', Routes\Forum\DiscussionIndex::class); + $forum->get('/forum-topics', Routes\Forum\TopicIndex::class); + $forum->get('/forum-subscriptions', Routes\Forum\SubscriptionIndex::class); }); $group->group('/forum-subscriptions', function ($forum) { - $forum->post('', Routes\Forum\ForumSubscriptionStore::class); - $forum->get('/{subscription_id}', Routes\Forum\ForumSubscriptionShow::class); - $forum->delete('/{subscription_id}', Routes\Forum\ForumSubscriptionDelete::class); + $forum->post('', Routes\Forum\SubscriptionStore::class); + $forum->get('/{subscription_id}', Routes\Forum\SubscriptionShow::class); + $forum->delete('/{subscription_id}', Routes\Forum\SubscriptionDelete::class); }); $group->group('/forum-topics', function ($forum) { - $forum->get('/{topic_id}', Routes\Forum\ForumTopicShow::class); - $forum->get('/{topic_id}/discussions', Routes\Forum\ForumTopicDiscussions::class); - $forum->patch('/sort', Routes\Forum\ForumTopicUpdateSort::class); + $forum->get('/{topic_id}', Routes\Forum\TopicShow::class); + $forum->get('/{topic_id}/discussions', Routes\Forum\TopicDiscussions::class); + $forum->patch('/sort', Routes\Forum\TopicUpdateSort::class); }); $group->group('/forum-categories', function ($forum) { - $forum->get('/{category_id}', Routes\Forum\ForumCategoryShow::class); - $forum->get('/{category_id}/topics', Routes\Forum\ForumCategoryTopics::class); - $forum->patch('/sort', Routes\Forum\ForumCategoryUpdateSort::class); + $forum->get('/{category_id}', Routes\Forum\CategoryShow::class); + $forum->get('/{category_id}/topics', Routes\Forum\CategoryTopics::class); + $forum->patch('/sort', Routes\Forum\CategoryUpdateSort::class); }); $group->group('/forum-discussion-types', function ($forum) { - $forum->get('', Routes\Forum\ForumDiscussionTypeIndex::class); - $forum->get('/{type_id}', Routes\Forum\ForumDiscussionTypeShow::class); - $forum->get('/{type_id}/discussions', Routes\Forum\ForumDiscussionTypeDiscussions::class); + $forum->get('', Routes\Forum\DiscussionTypeIndex::class); + $forum->get('/{type_id}', Routes\Forum\DiscussionTypeShow::class); }); $group->group('/forum-discussions', function ($forum) { - $forum->get('/{discussion_id}', Routes\Forum\ForumDiscussionShow::class); - $forum->get('/{discussion_id}/postings', Routes\Forum\ForumDiscussionPostings::class); + $forum->get('/{discussion_id}', Routes\Forum\DiscussionShow::class); + $forum->get('/{discussion_id}/postings', Routes\Forum\DiscussionPostings::class); }); $group->group('/forum-postings', function ($forum) { - $forum->post('', Routes\Forum\ForumPostingStore::class); - $forum->get('/{posting_id}', Routes\Forum\ForumPostingShow::class); - $forum->get('/{posting_id}/reactions', Routes\Forum\ForumPostingReactions::class); - $forum->patch('/{posting_id}', Routes\Forum\ForumPostingUpdate::class); - $forum->delete('/{posting_id}', Routes\Forum\ForumPostingDelete::class); + $forum->post('', Routes\Forum\PostingStore::class); + $forum->get('/{posting_id}', Routes\Forum\PostingShow::class); + $forum->get('/{posting_id}/reactions', Routes\Forum\PostingReactions::class); + $forum->patch('/{posting_id}', Routes\Forum\PostingUpdate::class); + $forum->delete('/{posting_id}', Routes\Forum\PostingDelete::class); }); $group->group('/forum-posting-reactions', function ($forum) { - $forum->post('', Routes\Forum\ForumPostingReactionStore::class); - $forum->get('/{reaction_id}', Routes\Forum\ForumPostingReactionShow::class); - $forum->delete('/{reaction_id}', Routes\Forum\ForumPostingReactionDelete::class); + $forum->post('', Routes\Forum\PostingReactionStore::class); + $forum->get('/{reaction_id}', Routes\Forum\PostingReactionShow::class); + $forum->delete('/{reaction_id}', Routes\Forum\PostingReactionDelete::class); }); } diff --git a/lib/classes/JsonApi/Routes/Forum/Authority.php b/lib/classes/JsonApi/Routes/Forum/Authority.php new file mode 100644 index 0000000..95091a0 --- /dev/null +++ b/lib/classes/JsonApi/Routes/Forum/Authority.php @@ -0,0 +1,13 @@ +isAccessibleToUser($user?->user_id); + } +} diff --git a/lib/classes/JsonApi/Routes/Forum/CategoryIndex.php b/lib/classes/JsonApi/Routes/Forum/CategoryIndex.php new file mode 100644 index 0000000..caa3c7f --- /dev/null +++ b/lib/classes/JsonApi/Routes/Forum/CategoryIndex.php @@ -0,0 +1,37 @@ +getUser($request); + if (!Authority::canShowForum($user, $range)) { + throw new AuthorizationFailedException(); + } + + $categories = Category::getCourseCategories($range->id); + + return $this->getPaginatedContentResponse( + array_slice($categories, ...$this->getOffsetAndLimit()), + count($categories) + ); + } +} diff --git a/lib/classes/JsonApi/Routes/Forum/CategoryShow.php b/lib/classes/JsonApi/Routes/Forum/CategoryShow.php new file mode 100644 index 0000000..4014536 --- /dev/null +++ b/lib/classes/JsonApi/Routes/Forum/CategoryShow.php @@ -0,0 +1,36 @@ +range_id); + if (!$range) { + throw new RecordNotFoundException(); + } + + $user = $this->getUser($request); + if (!Authority::canShowForum($user, $range)) { + throw new AuthorizationFailedException(); + } + + return $this->getContentResponse($category); + } +} diff --git a/lib/classes/JsonApi/Routes/Forum/CategoryTopics.php b/lib/classes/JsonApi/Routes/Forum/CategoryTopics.php new file mode 100644 index 0000000..10d47b4 --- /dev/null +++ b/lib/classes/JsonApi/Routes/Forum/CategoryTopics.php @@ -0,0 +1,42 @@ +range_id); + if (!$range) { + throw new RecordNotFoundException(); + } + + $user = $this->getUser($request); + if (!Authority::canShowForum($user, $range)) { + throw new AuthorizationFailedException(); + } + + $topics = $category->topics ?? \SimpleORMapCollection::createFromArray([]); + + return $this->getPaginatedContentResponse( + $topics->limit(...$this->getOffsetAndLimit()), + count($topics) + ); + } +} diff --git a/lib/classes/JsonApi/Routes/Forum/CategoryUpdateSort.php b/lib/classes/JsonApi/Routes/Forum/CategoryUpdateSort.php new file mode 100644 index 0000000..b1eb6b2 --- /dev/null +++ b/lib/classes/JsonApi/Routes/Forum/CategoryUpdateSort.php @@ -0,0 +1,63 @@ +validate($request); + $range_id = self::arrayGet($json, 'data.relationships.range.data.id'); + + $range = get_object_by_range_id($range_id); + if (!$range) { + throw new RecordNotFoundException(); + } + + if (!CoreForum::isModerator($range->id)) { + throw new AuthorizationFailedException(); + } + + $category_ids = self::arrayGet($json, 'data.attributes.category-ids'); + + Category::findEachBySQL( + function (Category $category) use ($category_ids) { + $category->position = (int) array_search($category->category_id, $category_ids); + $category->store(); + }, + "category_id IN (:category_ids) AND range_id = :range_id", + [ + "category_ids" => $category_ids, + "range_id" => $range->id + ] + ); + + return $this->getCodeResponse(204); + } + + protected function validateResourceDocument($json, $data) + { + $required_keys = [ + 'data.attributes.category-ids' => 'Missing `data.attributes.category-ids`', + 'data.relationships.range.data.id' => 'Missing `data.relationships.range.data.id`', + ]; + + foreach ($required_keys as $key => $error_message) { + if (!self::arrayHas($json, $key)) { + return $error_message; + } + } + + return null; + } +} diff --git a/lib/classes/JsonApi/Routes/Forum/ConfigIndex.php b/lib/classes/JsonApi/Routes/Forum/ConfigIndex.php new file mode 100644 index 0000000..8383608 --- /dev/null +++ b/lib/classes/JsonApi/Routes/Forum/ConfigIndex.php @@ -0,0 +1,35 @@ +getUser($request); + if (!Authority::canShowForum($user, $range)) { + throw new AuthorizationFailedException(); + } + + return $this->getMetaResponse([ + 'is-admin' => CoreForum::isAdmin($range->id), + 'is-moderator' => CoreForum::isModerator($range->id), + 'anonymous-post' => (bool) Config::get()->FORUM_ANONYMOUS_POSTINGS, + 'tile-layout' => (bool) UserConfig::get($user->user_id)->FORUM_TILE_LAYOUT + ]); + } +} diff --git a/lib/classes/JsonApi/Routes/Forum/DiscussionIndex.php b/lib/classes/JsonApi/Routes/Forum/DiscussionIndex.php new file mode 100644 index 0000000..91b5c06 --- /dev/null +++ b/lib/classes/JsonApi/Routes/Forum/DiscussionIndex.php @@ -0,0 +1,104 @@ +getUser($request); + if (!Authority::canShowForum($user, $range)) { + throw new AuthorizationFailedException(); + } + + $filters = $this->getFilter(); + if ($filters) { + $_SESSION['forum'][$range->id]['search_filter'] = $filters; + } + + $discussions = Discussion::getCourseDiscussions($range->id, $filters); + + return $this->getPaginatedContentResponse( + array_slice($discussions, ...$this->getOffsetAndLimit()), + count($discussions) + ); + } + + private function getFilter(): array + { + $filtering = $this->getQueryParameters()->getFilteringParameters() ?: []; + + $discussion_filter = []; + + if (isset($filtering['last-visit'])) { + $discussion_filter['last_visit'] = (int) $filtering['last-visit']; + } + + if (isset($filtering['keyword'])) { + $discussion_filter['keyword'] = $filtering['keyword']; + } + + if (isset($filtering['status'])) { + $discussion_filter['status'] = (int) $filtering['status']; + } + + if (isset($filtering['begin'])) { + $discussion_filter['begin'] = (int) $filtering['begin']; + } + + if (isset($filtering['end'])) { + $discussion_filter['end'] = (int) $filtering['end']; + } + + if (isset($filtering['topic-ids'])) { + $discussion_filter['topic_ids'] = explode(',', $filtering['topic-ids']); + } + + if (isset($filtering['type-ids'])) { + $discussion_filter['type_ids'] = explode(',', $filtering['type-ids']); + } + + if (isset($filtering['tag-ids'])) { + $discussion_filter['tag_ids'] = explode(',', $filtering['tag-ids']); + } + + if (isset($filtering['user-ids'])) { + $discussion_filter['user_ids'] = explode(',', $filtering['user-ids']); + } + + return $discussion_filter; + } +} diff --git a/lib/classes/JsonApi/Routes/Forum/DiscussionPostings.php b/lib/classes/JsonApi/Routes/Forum/DiscussionPostings.php new file mode 100644 index 0000000..8b64021 --- /dev/null +++ b/lib/classes/JsonApi/Routes/Forum/DiscussionPostings.php @@ -0,0 +1,50 @@ +range_id); + if (!$range) { + throw new RecordNotFoundException(); + } + + $user = $this->getUser($request); + if (!Authority::canShowForum($user, $range)) { + throw new AuthorizationFailedException(); + } + + $postings = $discussion->postings ?? \SimpleORMapCollection::createFromArray([]); + + PostingRead::updateUserReadPoint($user->user_id, $discussion->discussion_id, count($postings)); + + return $this->getPaginatedContentResponse( + $postings->limit(...$this->getOffsetAndLimit()), + count($postings) + ); + } +} diff --git a/lib/classes/JsonApi/Routes/Forum/DiscussionShow.php b/lib/classes/JsonApi/Routes/Forum/DiscussionShow.php new file mode 100644 index 0000000..4dc2821 --- /dev/null +++ b/lib/classes/JsonApi/Routes/Forum/DiscussionShow.php @@ -0,0 +1,39 @@ +range_id); + if (!$range) { + throw new RecordNotFoundException(); + } + + $user = $this->getUser($request); + if (!Authority::canShowForum($user, $range)) { + throw new AuthorizationFailedException(); + } + + return $this->getContentResponse($discussion); + } +} diff --git a/lib/classes/JsonApi/Routes/Forum/DiscussionTypeIndex.php b/lib/classes/JsonApi/Routes/Forum/DiscussionTypeIndex.php new file mode 100644 index 0000000..cd32b90 --- /dev/null +++ b/lib/classes/JsonApi/Routes/Forum/DiscussionTypeIndex.php @@ -0,0 +1,26 @@ +getPaginatedContentResponse( + array_slice($discussion_types, ...$this->getOffsetAndLimit()), + count($discussion_types) + ); + } +} diff --git a/lib/classes/JsonApi/Routes/Forum/DiscussionTypeShow.php b/lib/classes/JsonApi/Routes/Forum/DiscussionTypeShow.php new file mode 100644 index 0000000..939835a --- /dev/null +++ b/lib/classes/JsonApi/Routes/Forum/DiscussionTypeShow.php @@ -0,0 +1,25 @@ +getContentResponse($discussion_type); + } +} diff --git a/lib/classes/JsonApi/Routes/Forum/ForumCategoryIndex.php b/lib/classes/JsonApi/Routes/Forum/ForumCategoryIndex.php deleted file mode 100644 index 9518736..0000000 --- a/lib/classes/JsonApi/Routes/Forum/ForumCategoryIndex.php +++ /dev/null @@ -1,38 +0,0 @@ -getUser($request); - if (!RangeAuthority::canShowRange($user, $range)) { - throw new AuthorizationFailedException(); - } - - $categories = ForumCategory::getCourseCategories($range->id); - - return $this->getPaginatedContentResponse( - array_slice($categories, ...$this->getOffsetAndLimit()), - count($categories) - ); - } -} diff --git a/lib/classes/JsonApi/Routes/Forum/ForumCategoryShow.php b/lib/classes/JsonApi/Routes/Forum/ForumCategoryShow.php deleted file mode 100644 index f0ee1e1..0000000 --- a/lib/classes/JsonApi/Routes/Forum/ForumCategoryShow.php +++ /dev/null @@ -1,37 +0,0 @@ -range_id); - if (!$range) { - throw new RecordNotFoundException(); - } - - $user = $this->getUser($request); - if (!RangeAuthority::canShowRange($user, $range)) { - throw new AuthorizationFailedException(); - } - - return $this->getContentResponse($category); - } -} diff --git a/lib/classes/JsonApi/Routes/Forum/ForumCategoryTopics.php b/lib/classes/JsonApi/Routes/Forum/ForumCategoryTopics.php deleted file mode 100644 index cfa7937..0000000 --- a/lib/classes/JsonApi/Routes/Forum/ForumCategoryTopics.php +++ /dev/null @@ -1,43 +0,0 @@ -range_id); - if (!$range) { - throw new RecordNotFoundException(); - } - - $user = $this->getUser($request); - if (!RangeAuthority::canShowRange($user, $range)) { - throw new AuthorizationFailedException(); - } - - $topics = $category->topics ?? \SimpleORMapCollection::createFromArray([]); - - return $this->getPaginatedContentResponse( - $topics->limit(...$this->getOffsetAndLimit()), - count($topics) - ); - } -} diff --git a/lib/classes/JsonApi/Routes/Forum/ForumCategoryUpdateSort.php b/lib/classes/JsonApi/Routes/Forum/ForumCategoryUpdateSort.php deleted file mode 100644 index 39fbb9d..0000000 --- a/lib/classes/JsonApi/Routes/Forum/ForumCategoryUpdateSort.php +++ /dev/null @@ -1,63 +0,0 @@ -validate($request); - $range_id = self::arrayGet($json, 'data.relationships.range.data.id'); - - $range = get_object_by_range_id($range_id); - if (!$range) { - throw new RecordNotFoundException(); - } - - if (!CoreForum::isModerator($range->id)) { - throw new AuthorizationFailedException(); - } - - $category_ids = self::arrayGet($json, 'data.attributes.category-ids'); - - ForumCategory::findEachBySQL( - function (ForumCategory $category) use ($category_ids) { - $category->position = (int) array_search($category->category_id, $category_ids); - $category->store(); - }, - "category_id IN (:category_ids) AND range_id = :range_id", - [ - "category_ids" => $category_ids, - "range_id" => $range->id - ] - ); - - return $this->getCodeResponse(204); - } - - protected function validateResourceDocument($json, $data) - { - $required_keys = [ - 'data.attributes.category-ids' => 'Missing `data.attributes.category-ids`', - 'data.relationships.range.data.id' => 'Missing `data.relationships.range.data.id`', - ]; - - foreach ($required_keys as $key => $error_message) { - if (!self::arrayHas($json, $key)) { - return $error_message; - } - } - - return null; - } -} diff --git a/lib/classes/JsonApi/Routes/Forum/ForumConfigIndex.php b/lib/classes/JsonApi/Routes/Forum/ForumConfigIndex.php deleted file mode 100644 index 507e2de..0000000 --- a/lib/classes/JsonApi/Routes/Forum/ForumConfigIndex.php +++ /dev/null @@ -1,36 +0,0 @@ -getUser($request); - if (!RangeAuthority::canShowRange($user, $range)) { - throw new AuthorizationFailedException(); - } - - return $this->getMetaResponse([ - 'is-admin' => CoreForum::isAdmin($range->id), - 'is-moderator' => CoreForum::isModerator($range->id), - 'anonymous-post' => (bool) Config::get()->FORUM_ANONYMOUS_POSTINGS, - 'tile-layout' => (bool) UserConfig::get($user->user_id)->FORUM_TILE_LAYOUT - ]); - } -} diff --git a/lib/classes/JsonApi/Routes/Forum/ForumDiscussionIndex.php b/lib/classes/JsonApi/Routes/Forum/ForumDiscussionIndex.php deleted file mode 100644 index 83af8ac..0000000 --- a/lib/classes/JsonApi/Routes/Forum/ForumDiscussionIndex.php +++ /dev/null @@ -1,105 +0,0 @@ -getUser($request); - if (!RangeAuthority::canShowRange($user, $range)) { - throw new AuthorizationFailedException(); - } - - $filters = $this->getFilter(); - if ($filters) { - $_SESSION['forum'][$range->id]['search_filter'] = $filters; - } - - $discussions = ForumDiscussion::getCourseDiscussions($range->id, $filters); - - return $this->getPaginatedContentResponse( - array_slice($discussions, ...$this->getOffsetAndLimit()), - count($discussions) - ); - } - - private function getFilter(): array - { - $filtering = $this->getQueryParameters()->getFilteringParameters() ?: []; - - $discussion_filter = []; - - if (isset($filtering['last-visit'])) { - $discussion_filter['last_visit'] = (int) $filtering['last-visit']; - } - - if (isset($filtering['keyword'])) { - $discussion_filter['keyword'] = $filtering['keyword']; - } - - if (isset($filtering['status'])) { - $discussion_filter['status'] = (int) $filtering['status']; - } - - if (isset($filtering['begin'])) { - $discussion_filter['begin'] = (int) $filtering['begin']; - } - - if (isset($filtering['end'])) { - $discussion_filter['end'] = (int) $filtering['end']; - } - - if (isset($filtering['topic-ids'])) { - $discussion_filter['topic_ids'] = explode(',', $filtering['topic-ids']); - } - - if (isset($filtering['type-ids'])) { - $discussion_filter['type_ids'] = explode(',', $filtering['type-ids']); - } - - if (isset($filtering['tag-ids'])) { - $discussion_filter['tag_ids'] = explode(',', $filtering['tag-ids']); - } - - if (isset($filtering['user-ids'])) { - $discussion_filter['user_ids'] = explode(',', $filtering['user-ids']); - } - - return $discussion_filter; - } -} diff --git a/lib/classes/JsonApi/Routes/Forum/ForumDiscussionPostings.php b/lib/classes/JsonApi/Routes/Forum/ForumDiscussionPostings.php deleted file mode 100644 index 2e07c87..0000000 --- a/lib/classes/JsonApi/Routes/Forum/ForumDiscussionPostings.php +++ /dev/null @@ -1,51 +0,0 @@ -range_id); - if (!$range) { - throw new RecordNotFoundException(); - } - - $user = $this->getUser($request); - if (!RangeAuthority::canShowRange($user, $range)) { - throw new AuthorizationFailedException(); - } - - $postings = $discussion->postings ?? \SimpleORMapCollection::createFromArray([]); - - ForumPostingRead::updateUserReadPoint($user->user_id, $discussion->discussion_id, count($postings)); - - return $this->getPaginatedContentResponse( - $postings->limit(...$this->getOffsetAndLimit()), - count($postings) - ); - } -} diff --git a/lib/classes/JsonApi/Routes/Forum/ForumDiscussionShow.php b/lib/classes/JsonApi/Routes/Forum/ForumDiscussionShow.php deleted file mode 100644 index 93bc970..0000000 --- a/lib/classes/JsonApi/Routes/Forum/ForumDiscussionShow.php +++ /dev/null @@ -1,40 +0,0 @@ -range_id); - if (!$range) { - throw new RecordNotFoundException(); - } - - $user = $this->getUser($request); - if (!RangeAuthority::canShowRange($user, $range)) { - throw new AuthorizationFailedException(); - } - - return $this->getContentResponse($discussion); - } -} diff --git a/lib/classes/JsonApi/Routes/Forum/ForumDiscussionTypeDiscussions.php b/lib/classes/JsonApi/Routes/Forum/ForumDiscussionTypeDiscussions.php deleted file mode 100644 index 6db27ab..0000000 --- a/lib/classes/JsonApi/Routes/Forum/ForumDiscussionTypeDiscussions.php +++ /dev/null @@ -1,32 +0,0 @@ -discussions; - - return $this->getPaginatedContentResponse( - array_slice($discussions, ...$this->getOffsetAndLimit()), - count($discussions) - ); - } -} diff --git a/lib/classes/JsonApi/Routes/Forum/ForumDiscussionTypeIndex.php b/lib/classes/JsonApi/Routes/Forum/ForumDiscussionTypeIndex.php deleted file mode 100644 index 75f9750..0000000 --- a/lib/classes/JsonApi/Routes/Forum/ForumDiscussionTypeIndex.php +++ /dev/null @@ -1,26 +0,0 @@ -getPaginatedContentResponse( - array_slice($discussion_types, ...$this->getOffsetAndLimit()), - count($discussion_types) - ); - } -} diff --git a/lib/classes/JsonApi/Routes/Forum/ForumDiscussionTypeShow.php b/lib/classes/JsonApi/Routes/Forum/ForumDiscussionTypeShow.php deleted file mode 100644 index a3e37d9..0000000 --- a/lib/classes/JsonApi/Routes/Forum/ForumDiscussionTypeShow.php +++ /dev/null @@ -1,25 +0,0 @@ -getContentResponse($discussion_type); - } -} diff --git a/lib/classes/JsonApi/Routes/Forum/ForumPostingDelete.php b/lib/classes/JsonApi/Routes/Forum/ForumPostingDelete.php deleted file mode 100644 index 9929b14..0000000 --- a/lib/classes/JsonApi/Routes/Forum/ForumPostingDelete.php +++ /dev/null @@ -1,38 +0,0 @@ -getUser($request); - - $posting = ForumPosting::findOneBySQL( - "posting_id = :posting_id AND user_id = :user_id", - [ - 'posting_id' => $args['posting_id'], - 'user_id' => $user->user_id - ] - ); - - if (!$posting) { - throw new RecordNotFoundException(); - } - - if ($posting->discussion->closed_at) { - throw new AuthorizationFailedException(); - } - - $posting->delete(); - - return $this->getCodeResponse(204); - } -} diff --git a/lib/classes/JsonApi/Routes/Forum/ForumPostingReactionDelete.php b/lib/classes/JsonApi/Routes/Forum/ForumPostingReactionDelete.php deleted file mode 100644 index 6e5ff35..0000000 --- a/lib/classes/JsonApi/Routes/Forum/ForumPostingReactionDelete.php +++ /dev/null @@ -1,32 +0,0 @@ -getUser($request); - - $posting_reaction = ForumPostingReaction::findOneBySQL( - "id = :reaction_id AND user_id = :user_id", - [ - 'reaction_id' => $args['reaction_id'], - 'user_id' => $user->user_id - ] - ); - - if (!$posting_reaction) { - throw new RecordNotFoundException(); - } - - $posting_reaction->delete(); - - return $this->getCodeResponse(204); - } -} diff --git a/lib/classes/JsonApi/Routes/Forum/ForumPostingReactionShow.php b/lib/classes/JsonApi/Routes/Forum/ForumPostingReactionShow.php deleted file mode 100644 index 0df6931..0000000 --- a/lib/classes/JsonApi/Routes/Forum/ForumPostingReactionShow.php +++ /dev/null @@ -1,26 +0,0 @@ -getContentResponse($posting_reaction); - } -} diff --git a/lib/classes/JsonApi/Routes/Forum/ForumPostingReactionStore.php b/lib/classes/JsonApi/Routes/Forum/ForumPostingReactionStore.php deleted file mode 100644 index a913e9b..0000000 --- a/lib/classes/JsonApi/Routes/Forum/ForumPostingReactionStore.php +++ /dev/null @@ -1,77 +0,0 @@ -validate($request); - $user = $this->getUser($request); - - $posting = ForumPosting::find(self::arrayGet($json, 'data.relationships.posting.data.id')); - if (!$posting) { - throw new BadRequestException(); - } - - $range = get_object_by_range_id($posting->range_id); - if (!$range) { - throw new RecordNotFoundException(); - } - - if (!RangeAuthority::canShowRange($user, $range)) { - throw new AuthorizationFailedException(); - } - - $posting_reaction = ForumPostingReaction::create([ - 'posting_id' => $posting->posting_id, - 'user_id' => $user->user_id, - 'emoji' => self::arrayGet($json, 'data.attributes.emoji') - ]); - - if ($user->user_id !== $posting->user_id) { - \PersonalNotifications::add( - $posting->user_id, - \URLHelper::getURL('dispatch.php/course/forum/discussions/show/'.$posting->discussion_id, ['cid' => $posting->range_id], true)."#post_" . $posting->posting_id, - sprintf(_("%s hat auf deinen Beitrag reagiert."), $user->getFullName()), - null, - self::arrayGet($json, 'data.meta.emoji-icon') - ); - } - - return $this->getCreatedResponse($posting_reaction); - } - - protected function validateResourceDocument($json, $data) - { - $required_keys = [ - 'data.attributes.emoji' => 'Missing `data.attributes.emoji`', - 'data.meta.emoji-icon' => 'Missing `data.meta.emoji-icon`', - 'data.relationships.posting.data.id' => 'Missing `data.relationships.posting.data.id`', - ]; - - foreach ($required_keys as $key => $error_message) { - if (!self::arrayHas($json, $key)) { - return $error_message; - } - } - - return null; - } -} diff --git a/lib/classes/JsonApi/Routes/Forum/ForumPostingReactions.php b/lib/classes/JsonApi/Routes/Forum/ForumPostingReactions.php deleted file mode 100644 index 9c6a182..0000000 --- a/lib/classes/JsonApi/Routes/Forum/ForumPostingReactions.php +++ /dev/null @@ -1,45 +0,0 @@ -range_id); - if (!$range) { - throw new RecordNotFoundException(); - } - - $user = $this->getUser($request); - if (!RangeAuthority::canShowRange($user, $range)) { - throw new AuthorizationFailedException(); - } - - $reactions = $posting->reactions ?? SimpleORMapCollection::createFromArray([]); - - return $this->getPaginatedContentResponse( - $reactions->limit(...$this->getOffsetAndLimit()), - count($reactions) - ); - } -} diff --git a/lib/classes/JsonApi/Routes/Forum/ForumPostingShow.php b/lib/classes/JsonApi/Routes/Forum/ForumPostingShow.php deleted file mode 100644 index 4a810b2..0000000 --- a/lib/classes/JsonApi/Routes/Forum/ForumPostingShow.php +++ /dev/null @@ -1,41 +0,0 @@ -range_id); - if (!$range) { - throw new RecordNotFoundException(); - } - - $user = $this->getUser($request); - if (!RangeAuthority::canShowRange($user, $range)) { - throw new AuthorizationFailedException(); - } - - return $this->getContentResponse($posting); - } -} diff --git a/lib/classes/JsonApi/Routes/Forum/ForumPostingStore.php b/lib/classes/JsonApi/Routes/Forum/ForumPostingStore.php deleted file mode 100644 index 70c5792..0000000 --- a/lib/classes/JsonApi/Routes/Forum/ForumPostingStore.php +++ /dev/null @@ -1,100 +0,0 @@ -validate($request); - $user = $this->getUser($request); - - $discussion = ForumDiscussion::find(self::arrayGet($json, 'data.relationships.discussion.data.id')); - $range = get_object_by_range_id($discussion->range_id); - - if (!$discussion || !$range) { - throw new RecordNotFoundException(); - } - - if ( - !RangeAuthority::canShowRange($user, $range) || - $discussion->closed_at - ) { - throw new AuthorizationFailedException(); - } - - $parent_id = self::arrayGet($json, 'data.relationships.posting.data.id'); - - $psoting = ForumPosting::create([ - 'range_id' => $discussion->range_id, - 'parent_id' => $parent_id ?? null, - 'discussion_id' => $discussion->discussion_id, - 'content' => Markup::markAsHtml(self::arrayGet($json, 'data.attributes.content')), - 'anonymous' => (self::arrayGet($json, 'data.attributes.anonymous') && \Config::get()->FORUM_ANONYMOUS_POSTINGS), - 'user_id' => $user->user_id - ]); - - $subscription = ForumSubscription::findOneBySQL( - "user_id = :user_id AND subject_id IN (:subject_ids)", - [ - 'user_id' => $user->user_id, - 'subject_ids' => [$discussion->discussion_id, $discussion->topic_id] - ] - ); - - if (!$subscription) { - $subscription = new ForumSubscription(); - $subscription->user_id = $user->user_id; - $subscription->range_id = $discussion->range_id; - $subscription->subject_id = $discussion->discussion_id; - $subscription->subject = 'discussion'; - $subscription->notification_type = SubscriptionNotificationType::All->value; - $subscription->store(); - } - - ForumPostingRead::updateUserReadPoint($user->user_id, $discussion->discussion_id); - - return $this->getCreatedResponse($psoting); - } - - protected function validateResourceDocument($json, $data) - { - $required_keys = [ - 'data.attributes.content' => 'Missing `data.attributes.content`', - 'data.attributes.anonymous' => 'Missing `data.attributes.anonymous`', - 'data.relationships.discussion.data.id' => 'Missing `data.relationships.discussion.data.id`', - ]; - - foreach ($required_keys as $key => $error_message) { - if (!self::arrayHas($json, $key)) { - return $error_message; - } - } - - return null; - } -} diff --git a/lib/classes/JsonApi/Routes/Forum/ForumPostingUpdate.php b/lib/classes/JsonApi/Routes/Forum/ForumPostingUpdate.php deleted file mode 100644 index b8470fa..0000000 --- a/lib/classes/JsonApi/Routes/Forum/ForumPostingUpdate.php +++ /dev/null @@ -1,69 +0,0 @@ -validate($request); - $user = $this->getUser($request); - - $posting = ForumPosting::findOneBySQL( - "posting_id = :posting_id AND user_id = :user_id", - [ - 'posting_id' => $args['posting_id'], - 'user_id' => $user->user_id - ] - ); - - if (!$posting) { - throw new RecordNotFoundException(); - } - - if ($posting->discussion->closed_at) { - throw new AuthorizationFailedException(); - } - - $posting->content = Markup::markAsHtml(self::arrayGet($json, 'data.attributes.content')); - $posting->anonymous = (self::arrayGet($json, 'data.attributes.anonymous') && \Config::get()->FORUM_ANONYMOUS_POSTINGS); - $posting->store(); - - return $this->getCreatedResponse($posting); - } - - protected function validateResourceDocument($json, $data) - { - $required_keys = [ - 'data.attributes.content' => 'Missing `data.attributes.content`', - 'data.attributes.anonymous' => 'Missing `data.attributes.anonymous`', - ]; - - foreach ($required_keys as $key => $error_message) { - if (!self::arrayHas($json, $key)) { - return $error_message; - } - } - - return null; - } -} diff --git a/lib/classes/JsonApi/Routes/Forum/ForumSubscriptionDelete.php b/lib/classes/JsonApi/Routes/Forum/ForumSubscriptionDelete.php deleted file mode 100644 index fef6ac0..0000000 --- a/lib/classes/JsonApi/Routes/Forum/ForumSubscriptionDelete.php +++ /dev/null @@ -1,32 +0,0 @@ -getUser($request); - - $subscription = ForumSubscription::findOneBySQL( - "id = :subscription_id AND user_id = :user_id", - [ - 'subscription_id' => $args['subscription_id'], - 'user_id' => $user->user_id - ] - ); - - if (!$subscription) { - throw new RecordNotFoundException(); - } - - $subscription->delete(); - - return $this->getCodeResponse(204); - } -} diff --git a/lib/classes/JsonApi/Routes/Forum/ForumSubscriptionIndex.php b/lib/classes/JsonApi/Routes/Forum/ForumSubscriptionIndex.php deleted file mode 100644 index 8e0cee8..0000000 --- a/lib/classes/JsonApi/Routes/Forum/ForumSubscriptionIndex.php +++ /dev/null @@ -1,40 +0,0 @@ -getUser($request); - if (!RangeAuthority::canShowRange($user, $range)) { - throw new AuthorizationFailedException(); - } - - $subscriptions = ForumSubscription::getUserSubscriptions($range->id, $user->user_id); - - return $this->getPaginatedContentResponse( - array_slice($subscriptions, ...$this->getOffsetAndLimit()), - count($subscriptions) - ); - } -} diff --git a/lib/classes/JsonApi/Routes/Forum/ForumSubscriptionShow.php b/lib/classes/JsonApi/Routes/Forum/ForumSubscriptionShow.php deleted file mode 100644 index dd0854d..0000000 --- a/lib/classes/JsonApi/Routes/Forum/ForumSubscriptionShow.php +++ /dev/null @@ -1,36 +0,0 @@ -getUser($request); - - $subscription = ForumSubscription::findOneBySQL( - "id = :id AND user_id = :user_id", - [ - 'id' => $args['subscription_id'], - 'user_id' => $user->user_id - ] - ); - - if (!$subscription) { - throw new RecordNotFoundException(); - } - - return $this->getContentResponse($subscription); - } -} diff --git a/lib/classes/JsonApi/Routes/Forum/ForumSubscriptionStore.php b/lib/classes/JsonApi/Routes/Forum/ForumSubscriptionStore.php deleted file mode 100644 index ea3f308..0000000 --- a/lib/classes/JsonApi/Routes/Forum/ForumSubscriptionStore.php +++ /dev/null @@ -1,86 +0,0 @@ -validate($request); - $user = $this->getUser($request); - $subjectType = $this->mapSubjectType(self::arrayGet($json, 'data.relationships.subject.data.type')); - - if ($subjectType === 'discussion') { - $discussion = ForumDiscussion::find(self::arrayGet($json, 'data.relationships.subject.data.id')); - - if (!$discussion || $discussion->closed_at) { - throw new AuthorizationFailedException(); - } - } - - if (!self::arrayHas($json, 'data.id')) { - $subscription = new ForumSubscription(); - $subscription->user_id = $user->user_id; - } else { - $subscription = ForumSubscription::findOneBySQL( - "id = :id AND user_id = :user_id", - [ - 'id' => self::arrayGet($json, 'data.id'), - 'user_id' => $user->user_id - ] - ); - - if (!$subscription) { - throw new BadRequestException(); - } - } - - $subscription->range_id = self::arrayGet($json, 'data.relationships.range.data.id'); - $subscription->subject_id = self::arrayGet($json, 'data.relationships.subject.data.id'); - $subscription->subject = $subjectType; - $subscription->notification_type = self::arrayGet($json, 'data.attributes.notification-type'); - - $subscription->store(); - - return $this->getCreatedResponse($subscription); - } - - protected function validateResourceDocument($json, $data) - { - $required_keys = [ - 'data' => 'Missing `data`', - 'data.attributes' => 'Missing `data.attributes`', - 'data.attributes.notification-type' => 'Missing `data.attributes.notification-type`', - 'data.relationships' => 'Missing `data.relationships`', - 'data.relationships.range.data.id' => 'Missing `data.relationships.range.data.id`', - 'data.relationships.subject.data.id' => 'Missing `data.relationships.subject.data.id`', - 'data.relationships.subject.data.type' => 'Missing `data.relationships.subject.data.type`', - ]; - - foreach ($required_keys as $key => $error_message) { - if (!self::arrayHas($json, $key)) { - return $error_message; - } - } - - return null; - } - - private function mapSubjectType($type): string - { - return match ($type) { - 'forum-discussions' => 'discussion', - 'forum-topics' => 'topic' - }; - } -} diff --git a/lib/classes/JsonApi/Routes/Forum/ForumTopicDiscussions.php b/lib/classes/JsonApi/Routes/Forum/ForumTopicDiscussions.php deleted file mode 100644 index c482c0a..0000000 --- a/lib/classes/JsonApi/Routes/Forum/ForumTopicDiscussions.php +++ /dev/null @@ -1,48 +0,0 @@ -range_id); - if (!$range) { - throw new RecordNotFoundException(); - } - - $user = $this->getUser($request); - if (!RangeAuthority::canShowRange($user, $range)) { - throw new AuthorizationFailedException(); - } - - $discussions = $topic->discussions; - - return $this->getPaginatedContentResponse( - array_slice($discussions, ...$this->getOffsetAndLimit()), - count($discussions) - ); - } -} diff --git a/lib/classes/JsonApi/Routes/Forum/ForumTopicIndex.php b/lib/classes/JsonApi/Routes/Forum/ForumTopicIndex.php deleted file mode 100644 index 038d0c9..0000000 --- a/lib/classes/JsonApi/Routes/Forum/ForumTopicIndex.php +++ /dev/null @@ -1,39 +0,0 @@ -getUser($request); - if (!RangeAuthority::canShowRange($user, $range)) { - throw new AuthorizationFailedException(); - } - - $topics = ForumTopic::getCourseTopics($range->id); - - return $this->getPaginatedContentResponse( - array_slice($topics, ...$this->getOffsetAndLimit()), - count($topics) - ); - } -} diff --git a/lib/classes/JsonApi/Routes/Forum/ForumTopicShow.php b/lib/classes/JsonApi/Routes/Forum/ForumTopicShow.php deleted file mode 100644 index 32704b7..0000000 --- a/lib/classes/JsonApi/Routes/Forum/ForumTopicShow.php +++ /dev/null @@ -1,35 +0,0 @@ -range_id); - if (!$range) { - throw new RecordNotFoundException(); - } - - $user = $this->getUser($request); - if (!RangeAuthority::canShowRange($user, $range)) { - throw new AuthorizationFailedException(); - } - - return $this->getContentResponse($topic); - } -} diff --git a/lib/classes/JsonApi/Routes/Forum/ForumTopicUpdateSort.php b/lib/classes/JsonApi/Routes/Forum/ForumTopicUpdateSort.php deleted file mode 100644 index 3e77b40..0000000 --- a/lib/classes/JsonApi/Routes/Forum/ForumTopicUpdateSort.php +++ /dev/null @@ -1,63 +0,0 @@ -validate($request); - $range_id = self::arrayGet($json, 'data.relationships.range.data.id'); - - $range = get_object_by_range_id($range_id); - if (!$range) { - throw new RecordNotFoundException(); - } - - if (!CoreForum::isModerator($range->id)) { - throw new AuthorizationFailedException(); - } - - $topic_ids = self::arrayGet($json, 'data.attributes.topic-ids'); - - ForumTopic::findEachBySQL( - function (ForumTopic $topic) use ($topic_ids) { - $topic->position = (int) array_search($topic->topic_id, $topic_ids); - $topic->store(); - }, - "topic_id IN (:topic_ids) AND range_id = :course_id", - [ - "topic_ids" => $topic_ids, - "course_id" => $range->id - ] - ); - - return $this->getCodeResponse(204); - } - - protected function validateResourceDocument($json, $data) - { - $required_keys = [ - 'data.attributes.topic-ids' => 'Missing `data.attributes.topic-ids`', - 'data.relationships.range.data.id' => 'Missing `data.relationships.range.data.id`', - ]; - - foreach ($required_keys as $key => $error_message) { - if (!self::arrayHas($json, $key)) { - return $error_message; - } - } - - return null; - } -} diff --git a/lib/classes/JsonApi/Routes/Forum/PostingDelete.php b/lib/classes/JsonApi/Routes/Forum/PostingDelete.php new file mode 100644 index 0000000..e49755d --- /dev/null +++ b/lib/classes/JsonApi/Routes/Forum/PostingDelete.php @@ -0,0 +1,38 @@ +getUser($request); + + $posting = Posting::findOneBySQL( + "posting_id = :posting_id AND user_id = :user_id", + [ + 'posting_id' => $args['posting_id'], + 'user_id' => $user->user_id + ] + ); + + if (!$posting) { + throw new RecordNotFoundException(); + } + + if ($posting->discussion->closed_at) { + throw new AuthorizationFailedException(); + } + + $posting->delete(); + + return $this->getCodeResponse(204); + } +} diff --git a/lib/classes/JsonApi/Routes/Forum/PostingReactionDelete.php b/lib/classes/JsonApi/Routes/Forum/PostingReactionDelete.php new file mode 100644 index 0000000..7849815 --- /dev/null +++ b/lib/classes/JsonApi/Routes/Forum/PostingReactionDelete.php @@ -0,0 +1,32 @@ +getUser($request); + + $posting_reaction = PostingReaction::findOneBySQL( + "id = :reaction_id AND user_id = :user_id", + [ + 'reaction_id' => $args['reaction_id'], + 'user_id' => $user->user_id + ] + ); + + if (!$posting_reaction) { + throw new RecordNotFoundException(); + } + + $posting_reaction->delete(); + + return $this->getCodeResponse(204); + } +} diff --git a/lib/classes/JsonApi/Routes/Forum/PostingReactionShow.php b/lib/classes/JsonApi/Routes/Forum/PostingReactionShow.php new file mode 100644 index 0000000..fb658b9 --- /dev/null +++ b/lib/classes/JsonApi/Routes/Forum/PostingReactionShow.php @@ -0,0 +1,26 @@ +getContentResponse($posting_reaction); + } +} diff --git a/lib/classes/JsonApi/Routes/Forum/PostingReactionStore.php b/lib/classes/JsonApi/Routes/Forum/PostingReactionStore.php new file mode 100644 index 0000000..992cf0d --- /dev/null +++ b/lib/classes/JsonApi/Routes/Forum/PostingReactionStore.php @@ -0,0 +1,76 @@ +validate($request); + $user = $this->getUser($request); + + $posting = Posting::find(self::arrayGet($json, 'data.relationships.posting.data.id')); + if (!$posting) { + throw new BadRequestException(); + } + + $range = get_object_by_range_id($posting->range_id); + if (!$range) { + throw new RecordNotFoundException(); + } + + if (!Authority::canShowForum($user, $range)) { + throw new AuthorizationFailedException(); + } + + $posting_reaction = PostingReaction::create([ + 'posting_id' => $posting->posting_id, + 'user_id' => $user->user_id, + 'emoji' => self::arrayGet($json, 'data.attributes.emoji') + ]); + + if ($user->user_id !== $posting->user_id) { + \PersonalNotifications::add( + $posting->user_id, + \URLHelper::getURL('dispatch.php/course/forum/discussions/show/'.$posting->discussion_id, ['cid' => $posting->range_id], true)."#post_" . $posting->posting_id, + sprintf(_("%s hat auf deinen Beitrag reagiert."), $user->getFullName()), + null, + self::arrayGet($json, 'data.meta.emoji-icon') + ); + } + + return $this->getCreatedResponse($posting_reaction); + } + + protected function validateResourceDocument($json, $data) + { + $required_keys = [ + 'data.attributes.emoji' => 'Missing `data.attributes.emoji`', + 'data.meta.emoji-icon' => 'Missing `data.meta.emoji-icon`', + 'data.relationships.posting.data.id' => 'Missing `data.relationships.posting.data.id`', + ]; + + foreach ($required_keys as $key => $error_message) { + if (!self::arrayHas($json, $key)) { + return $error_message; + } + } + + return null; + } +} diff --git a/lib/classes/JsonApi/Routes/Forum/PostingReactions.php b/lib/classes/JsonApi/Routes/Forum/PostingReactions.php new file mode 100644 index 0000000..12caad4 --- /dev/null +++ b/lib/classes/JsonApi/Routes/Forum/PostingReactions.php @@ -0,0 +1,44 @@ +range_id); + if (!$range) { + throw new RecordNotFoundException(); + } + + $user = $this->getUser($request); + if (!Authority::canShowForum($user, $range)) { + throw new AuthorizationFailedException(); + } + + $reactions = $posting->reactions ?? SimpleORMapCollection::createFromArray([]); + + return $this->getPaginatedContentResponse( + $reactions->limit(...$this->getOffsetAndLimit()), + count($reactions) + ); + } +} diff --git a/lib/classes/JsonApi/Routes/Forum/PostingShow.php b/lib/classes/JsonApi/Routes/Forum/PostingShow.php new file mode 100644 index 0000000..c062169 --- /dev/null +++ b/lib/classes/JsonApi/Routes/Forum/PostingShow.php @@ -0,0 +1,40 @@ +range_id); + if (!$range) { + throw new RecordNotFoundException(); + } + + $user = $this->getUser($request); + if (!Authority::canShowForum($user, $range)) { + throw new AuthorizationFailedException(); + } + + return $this->getContentResponse($posting); + } +} diff --git a/lib/classes/JsonApi/Routes/Forum/PostingStore.php b/lib/classes/JsonApi/Routes/Forum/PostingStore.php new file mode 100644 index 0000000..f7a667e --- /dev/null +++ b/lib/classes/JsonApi/Routes/Forum/PostingStore.php @@ -0,0 +1,99 @@ +validate($request); + $user = $this->getUser($request); + + $discussion = Discussion::find(self::arrayGet($json, 'data.relationships.discussion.data.id')); + $range = get_object_by_range_id($discussion->range_id); + + if (!$discussion || !$range) { + throw new RecordNotFoundException(); + } + + if ( + !Authority::canShowForum($user, $range) || + $discussion->closed_at + ) { + throw new AuthorizationFailedException(); + } + + $parent_id = self::arrayGet($json, 'data.relationships.posting.data.id'); + + $psoting = Posting::create([ + 'range_id' => $discussion->range_id, + 'parent_id' => $parent_id ?? null, + 'discussion_id' => $discussion->discussion_id, + 'content' => Markup::markAsHtml(self::arrayGet($json, 'data.attributes.content')), + 'anonymous' => (self::arrayGet($json, 'data.attributes.anonymous') && \Config::get()->FORUM_ANONYMOUS_POSTINGS), + 'user_id' => $user->user_id + ]); + + $subscription = Subscription::findOneBySQL( + "user_id = :user_id AND subject_id IN (:subject_ids)", + [ + 'user_id' => $user->user_id, + 'subject_ids' => [$discussion->discussion_id, $discussion->topic_id] + ] + ); + + if (!$subscription) { + $subscription = new Subscription(); + $subscription->user_id = $user->user_id; + $subscription->range_id = $discussion->range_id; + $subscription->subject_id = $discussion->discussion_id; + $subscription->subject = 'discussion'; + $subscription->notification_type = SubscriptionNotificationType::All->value; + $subscription->store(); + } + + PostingRead::updateUserReadPoint($user->user_id, $discussion->discussion_id); + + return $this->getCreatedResponse($psoting); + } + + protected function validateResourceDocument($json, $data) + { + $required_keys = [ + 'data.attributes.content' => 'Missing `data.attributes.content`', + 'data.attributes.anonymous' => 'Missing `data.attributes.anonymous`', + 'data.relationships.discussion.data.id' => 'Missing `data.relationships.discussion.data.id`', + ]; + + foreach ($required_keys as $key => $error_message) { + if (!self::arrayHas($json, $key)) { + return $error_message; + } + } + + return null; + } +} diff --git a/lib/classes/JsonApi/Routes/Forum/PostingUpdate.php b/lib/classes/JsonApi/Routes/Forum/PostingUpdate.php new file mode 100644 index 0000000..c720bb6 --- /dev/null +++ b/lib/classes/JsonApi/Routes/Forum/PostingUpdate.php @@ -0,0 +1,69 @@ +validate($request); + $user = $this->getUser($request); + + $posting = Posting::findOneBySQL( + "posting_id = :posting_id AND user_id = :user_id", + [ + 'posting_id' => $args['posting_id'], + 'user_id' => $user->user_id + ] + ); + + if (!$posting) { + throw new RecordNotFoundException(); + } + + if ($posting->discussion->closed_at) { + throw new AuthorizationFailedException(); + } + + $posting->content = Markup::markAsHtml(self::arrayGet($json, 'data.attributes.content')); + $posting->anonymous = (self::arrayGet($json, 'data.attributes.anonymous') && \Config::get()->FORUM_ANONYMOUS_POSTINGS); + $posting->store(); + + return $this->getCreatedResponse($posting); + } + + protected function validateResourceDocument($json, $data) + { + $required_keys = [ + 'data.attributes.content' => 'Missing `data.attributes.content`', + 'data.attributes.anonymous' => 'Missing `data.attributes.anonymous`', + ]; + + foreach ($required_keys as $key => $error_message) { + if (!self::arrayHas($json, $key)) { + return $error_message; + } + } + + return null; + } +} diff --git a/lib/classes/JsonApi/Routes/Forum/SubscriptionDelete.php b/lib/classes/JsonApi/Routes/Forum/SubscriptionDelete.php new file mode 100644 index 0000000..21ebe0b --- /dev/null +++ b/lib/classes/JsonApi/Routes/Forum/SubscriptionDelete.php @@ -0,0 +1,32 @@ +getUser($request); + + $subscription = Subscription::findOneBySQL( + "id = :subscription_id AND user_id = :user_id", + [ + 'subscription_id' => $args['subscription_id'], + 'user_id' => $user->user_id + ] + ); + + if (!$subscription) { + throw new RecordNotFoundException(); + } + + $subscription->delete(); + + return $this->getCodeResponse(204); + } +} diff --git a/lib/classes/JsonApi/Routes/Forum/SubscriptionIndex.php b/lib/classes/JsonApi/Routes/Forum/SubscriptionIndex.php new file mode 100644 index 0000000..b653a84 --- /dev/null +++ b/lib/classes/JsonApi/Routes/Forum/SubscriptionIndex.php @@ -0,0 +1,39 @@ +getUser($request); + if (!Authority::canShowForum($user, $range)) { + throw new AuthorizationFailedException(); + } + + $subscriptions = Subscription::getUserSubscriptions($range->id, $user->user_id); + + return $this->getPaginatedContentResponse( + array_slice($subscriptions, ...$this->getOffsetAndLimit()), + count($subscriptions) + ); + } +} diff --git a/lib/classes/JsonApi/Routes/Forum/SubscriptionShow.php b/lib/classes/JsonApi/Routes/Forum/SubscriptionShow.php new file mode 100644 index 0000000..ce98f9a --- /dev/null +++ b/lib/classes/JsonApi/Routes/Forum/SubscriptionShow.php @@ -0,0 +1,36 @@ +getUser($request); + + $subscription = Subscription::findOneBySQL( + "id = :id AND user_id = :user_id", + [ + 'id' => $args['subscription_id'], + 'user_id' => $user->user_id + ] + ); + + if (!$subscription) { + throw new RecordNotFoundException(); + } + + return $this->getContentResponse($subscription); + } +} diff --git a/lib/classes/JsonApi/Routes/Forum/SubscriptionStore.php b/lib/classes/JsonApi/Routes/Forum/SubscriptionStore.php new file mode 100644 index 0000000..f7ba658 --- /dev/null +++ b/lib/classes/JsonApi/Routes/Forum/SubscriptionStore.php @@ -0,0 +1,86 @@ +validate($request); + $user = $this->getUser($request); + $subjectType = $this->mapSubjectType(self::arrayGet($json, 'data.relationships.subject.data.type')); + + if ($subjectType === 'discussion') { + $discussion = Discussion::find(self::arrayGet($json, 'data.relationships.subject.data.id')); + + if (!$discussion || $discussion->closed_at) { + throw new AuthorizationFailedException(); + } + } + + if (!self::arrayHas($json, 'data.id')) { + $subscription = new Subscription(); + $subscription->user_id = $user->user_id; + } else { + $subscription = Subscription::findOneBySQL( + "id = :id AND user_id = :user_id", + [ + 'id' => self::arrayGet($json, 'data.id'), + 'user_id' => $user->user_id + ] + ); + + if (!$subscription) { + throw new BadRequestException(); + } + } + + $subscription->range_id = self::arrayGet($json, 'data.relationships.range.data.id'); + $subscription->subject_id = self::arrayGet($json, 'data.relationships.subject.data.id'); + $subscription->subject = $subjectType; + $subscription->notification_type = self::arrayGet($json, 'data.attributes.notification-type'); + + $subscription->store(); + + return $this->getCreatedResponse($subscription); + } + + protected function validateResourceDocument($json, $data) + { + $required_keys = [ + 'data' => 'Missing `data`', + 'data.attributes' => 'Missing `data.attributes`', + 'data.attributes.notification-type' => 'Missing `data.attributes.notification-type`', + 'data.relationships' => 'Missing `data.relationships`', + 'data.relationships.range.data.id' => 'Missing `data.relationships.range.data.id`', + 'data.relationships.subject.data.id' => 'Missing `data.relationships.subject.data.id`', + 'data.relationships.subject.data.type' => 'Missing `data.relationships.subject.data.type`', + ]; + + foreach ($required_keys as $key => $error_message) { + if (!self::arrayHas($json, $key)) { + return $error_message; + } + } + + return null; + } + + private function mapSubjectType($type): string + { + return match ($type) { + 'forum-discussions' => 'discussion', + 'forum-topics' => 'topic' + }; + } +} diff --git a/lib/classes/JsonApi/Routes/Forum/TopicDiscussions.php b/lib/classes/JsonApi/Routes/Forum/TopicDiscussions.php new file mode 100644 index 0000000..03fa55f --- /dev/null +++ b/lib/classes/JsonApi/Routes/Forum/TopicDiscussions.php @@ -0,0 +1,47 @@ +range_id); + if (!$range) { + throw new RecordNotFoundException(); + } + + $user = $this->getUser($request); + if (!Authority::canShowForum($user, $range)) { + throw new AuthorizationFailedException(); + } + + $discussions = $topic->discussions; + + return $this->getPaginatedContentResponse( + array_slice($discussions, ...$this->getOffsetAndLimit()), + count($discussions) + ); + } +} diff --git a/lib/classes/JsonApi/Routes/Forum/TopicIndex.php b/lib/classes/JsonApi/Routes/Forum/TopicIndex.php new file mode 100644 index 0000000..b46ee72 --- /dev/null +++ b/lib/classes/JsonApi/Routes/Forum/TopicIndex.php @@ -0,0 +1,38 @@ +getUser($request); + if (!Authority::canShowForum($user, $range)) { + throw new AuthorizationFailedException(); + } + + $topics = Topic::getCourseTopics($range->id); + + return $this->getPaginatedContentResponse( + array_slice($topics, ...$this->getOffsetAndLimit()), + count($topics) + ); + } +} diff --git a/lib/classes/JsonApi/Routes/Forum/TopicShow.php b/lib/classes/JsonApi/Routes/Forum/TopicShow.php new file mode 100644 index 0000000..1598ffc --- /dev/null +++ b/lib/classes/JsonApi/Routes/Forum/TopicShow.php @@ -0,0 +1,34 @@ +range_id); + if (!$range) { + throw new RecordNotFoundException(); + } + + $user = $this->getUser($request); + if (!Authority::canShowForum($user, $range)) { + throw new AuthorizationFailedException(); + } + + return $this->getContentResponse($topic); + } +} diff --git a/lib/classes/JsonApi/Routes/Forum/TopicUpdateSort.php b/lib/classes/JsonApi/Routes/Forum/TopicUpdateSort.php new file mode 100644 index 0000000..919d2a9 --- /dev/null +++ b/lib/classes/JsonApi/Routes/Forum/TopicUpdateSort.php @@ -0,0 +1,63 @@ +validate($request); + $range_id = self::arrayGet($json, 'data.relationships.range.data.id'); + + $range = get_object_by_range_id($range_id); + if (!$range) { + throw new RecordNotFoundException(); + } + + if (!CoreForum::isModerator($range->id)) { + throw new AuthorizationFailedException(); + } + + $topic_ids = self::arrayGet($json, 'data.attributes.topic-ids'); + + Topic::findEachBySQL( + function (Topic $topic) use ($topic_ids) { + $topic->position = (int) array_search($topic->topic_id, $topic_ids); + $topic->store(); + }, + "topic_id IN (:topic_ids) AND range_id = :course_id", + [ + "topic_ids" => $topic_ids, + "course_id" => $range->id + ] + ); + + return $this->getCodeResponse(204); + } + + protected function validateResourceDocument($json, $data) + { + $required_keys = [ + 'data.attributes.topic-ids' => 'Missing `data.attributes.topic-ids`', + 'data.relationships.range.data.id' => 'Missing `data.relationships.range.data.id`', + ]; + + foreach ($required_keys as $key => $error_message) { + if (!self::arrayHas($json, $key)) { + return $error_message; + } + } + + return null; + } +} diff --git a/lib/classes/JsonApi/Routes/Institutes/Authority.php b/lib/classes/JsonApi/Routes/Institutes/Authority.php index 2bd68a5..c6ee43b 100644 --- a/lib/classes/JsonApi/Routes/Institutes/Authority.php +++ b/lib/classes/JsonApi/Routes/Institutes/Authority.php @@ -7,11 +7,6 @@ use User; class Authority { - public static function canShowInstitute(User $user, Institute $institute): bool - { - return $institute->isAccessibleToUser($user->id); - } - /** * @SuppressWarnings(PHPMD.Superglobals) */ diff --git a/lib/classes/JsonApi/Routes/RangeAuthority.php b/lib/classes/JsonApi/Routes/RangeAuthority.php deleted file mode 100644 index 447ae60..0000000 --- a/lib/classes/JsonApi/Routes/RangeAuthority.php +++ /dev/null @@ -1,41 +0,0 @@ - Schemas\Degree::class, \FeedbackElement::class => Schemas\FeedbackElement::class, \FeedbackEntry::class => Schemas\FeedbackEntry::class, - \Forum\ForumCategory::class => \JsonApi\Schemas\Forum\ForumCategory::class, - \Forum\ForumTopic::class => \JsonApi\Schemas\Forum\ForumTopic::class, - \Forum\ForumDiscussion::class => \JsonApi\Schemas\Forum\ForumDiscussion::class, - \Forum\ForumDiscussionType::class => \JsonApi\Schemas\Forum\ForumDiscussionType::class, - \Forum\ForumPosting::class => \JsonApi\Schemas\Forum\ForumPosting::class, - \Forum\ForumPostingReaction::class => \JsonApi\Schemas\Forum\ForumPostingReaction::class, - \Forum\ForumSubscription::class => \JsonApi\Schemas\Forum\ForumSubscription::class, - \Forum\DTO\ForumMember::class => \JsonApi\Schemas\Forum\ForumMember::class, - \Forum\DTO\ForumTag::class => \JsonApi\Schemas\Forum\ForumTag::class, + \Forum\Category::class => \JsonApi\Schemas\Forum\Category::class, + \Forum\Topic::class => \JsonApi\Schemas\Forum\Topic::class, + \Forum\Discussion::class => \JsonApi\Schemas\Forum\Discussion::class, + \Forum\DiscussionType::class => \JsonApi\Schemas\Forum\DiscussionType::class, + \Forum\Posting::class => \JsonApi\Schemas\Forum\Posting::class, + \Forum\PostingReaction::class => \JsonApi\Schemas\Forum\PostingReaction::class, + \Forum\Subscription::class => \JsonApi\Schemas\Forum\Subscription::class, + \Forum\DTO\Member::class => \JsonApi\Schemas\Forum\Member::class, + \Forum\DTO\Tag::class => \JsonApi\Schemas\Forum\Tag::class, \Institute::class => Schemas\Institute::class, \InstituteMember::class => Schemas\InstituteMember::class, \LtiTool::class => Schemas\LtiTool::class, diff --git a/lib/classes/JsonApi/Schemas/Activity.php b/lib/classes/JsonApi/Schemas/Activity.php index 3839793..6607a4d 100644 --- a/lib/classes/JsonApi/Schemas/Activity.php +++ b/lib/classes/JsonApi/Schemas/Activity.php @@ -89,7 +89,7 @@ class Activity extends SchemaProvider { $mapping = [ 'documents' => \FileRef::class, - 'forum' => \Forum\ForumPosting::class, + 'forum' => \Forum\Posting::class, 'message' => \Message::class, 'news' => \StudipNews::class, 'participants' => \Course::class, diff --git a/lib/classes/JsonApi/Schemas/Forum/Category.php b/lib/classes/JsonApi/Schemas/Forum/Category.php new file mode 100644 index 0000000..de5aa3e --- /dev/null +++ b/lib/classes/JsonApi/Schemas/Forum/Category.php @@ -0,0 +1,73 @@ +id; + } + + public function getAttributes($category, ContextInterface $context): iterable + { + return [ + 'name' => $category->name, + 'description' => $category->description, + 'color' => $category->color, + 'position' => (int) $category->position, + 'mkdate' => date('c', $category->mkdate), + 'chdate' => date('c', $category->chdate) + ]; + } + + public function hasResourceMeta($category): bool + { + return true; + } + + public function getResourceMeta($category) + { + $metaData = $category->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']) : '', + ]; + } + + public function getRelationships($category, ContextInterface $context): iterable + { + $relationships = []; + $relationships = $this->addTopicsRelationship($relationships, $category, $this->shouldInclude($context, self::REL_TOPICS)); + + return $relationships; + } + + /** + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + private function addTopicsRelationship($relationships, $category, $withTopics = false) + { + if ($withTopics) { + $relationships[self::REL_TOPICS] = [ + self::RELATIONSHIP_LINKS => [ + Link::RELATED => $this->getRelationshipRelatedLink($category, self::REL_TOPICS) + ], + self::RELATIONSHIP_DATA => $category->topics + ]; + } + + return $relationships; + } +} diff --git a/lib/classes/JsonApi/Schemas/Forum/Discussion.php b/lib/classes/JsonApi/Schemas/Forum/Discussion.php new file mode 100644 index 0000000..abac14e --- /dev/null +++ b/lib/classes/JsonApi/Schemas/Forum/Discussion.php @@ -0,0 +1,170 @@ +discussion_id; + } + + public function getAttributes($discussion, ContextInterface $context): iterable + { + return [ + 'title' => $discussion->title, + 'closed-at' => $discussion->closed_at ? date('c', $discussion->closed_at) : null, + 'sticky' => (bool) $discussion->sticky, + 'view-count' => (int) $discussion->view_count, + 'mkdate' => date('c', $discussion->mkdate), + 'chdate' => date('c', $discussion->chdate) + ]; + } + + public function hasResourceMeta($discussion): bool + { + return true; + } + + public function getResourceMeta($discussion) + { + $metaData = $discussion->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']) : '' + ]; + } + + public function getRelationships($discussion, ContextInterface $context): iterable + { + $relationships = []; + + $relationships = $this->addPostingsRelationship($relationships, $discussion, $this->shouldInclude($context, self::REL_POSTINGS)); + $relationships = $this->addTopicRelationship($relationships, $discussion, $this->shouldInclude($context, self::REL_TOPIC)); + $relationships = $this->addCategoryRelationship($relationships, $discussion, $this->shouldInclude($context, self::REL_CATEGORY)); + $relationships = $this->addUserRelationship($relationships, $discussion, $this->shouldInclude($context, self::REL_USER)); + $relationships = $this->addDiscussionTypeRelationship($relationships, $discussion, $this->shouldInclude($context, self::REL_DISCUSSION_TYPE)); + $relationships = $this->addMembersRelationship($relationships, $discussion, $this->shouldInclude($context, self::REL_MEMBERS)); + $relationships = $this->addTagsRelationship($relationships, $discussion, $this->shouldInclude($context, self::REL_TAGS)); + + return $relationships; + } + + private function addPostingsRelationship(array $relationships, $discussion, bool $withPostings = false) + { + if ($withPostings) { + $relationships[self::REL_POSTINGS] = [ + self::RELATIONSHIP_LINKS => [ + Link::RELATED => $this->getRelationshipRelatedLink($discussion, self::REL_POSTINGS) + ], + self::RELATIONSHIP_DATA => $discussion->postings + ]; + } + + return $relationships; + } + + private function addTopicRelationship(array $relationships, $discussion, bool $withTopic = false) + { + if ($withTopic) { + $relationships[self::REL_TOPIC] = [ + self::RELATIONSHIP_LINKS => [ + Link::RELATED => $this->createLinkToResource($discussion->topic) + ], + self::RELATIONSHIP_DATA => $discussion->topic + ]; + } + + return $relationships; + } + + private function addCategoryRelationship(array $relationships, $discussion, bool $withCategory = false) + { + $category = $discussion->category; + if ($withCategory && $category) { + $relationships[self::REL_CATEGORY] = [ + self::RELATIONSHIP_LINKS => [ + Link::RELATED => $this->createLinkToResource($category) + ], + self::RELATIONSHIP_DATA => $category + ]; + } + + return $relationships; + } + + private function addUserRelationship(array $relationships, $discussion, bool $withUser = false) + { + $user = $discussion->user; + if ($withUser && $user) { + $relationships[self::REL_USER] = [ + self::RELATIONSHIP_LINKS => [ + Link::RELATED => $this->createLinkToResource($user) + ], + self::RELATIONSHIP_DATA => $user + ]; + } + + return $relationships; + } + + private function addDiscussionTypeRelationship(array $relationships, $discussion, bool $withDiscussionType = false) + { + $discussionType = $discussion->discussion_type; + + if ($withDiscussionType && $discussionType) { + $relationships[self::REL_DISCUSSION_TYPE] = [ + self::RELATIONSHIP_LINKS => [ + Link::RELATED => $this->createLinkToResource($discussionType) + ], + self::RELATIONSHIP_DATA => $discussionType + ]; + } + + return $relationships; + } + + private function addMembersRelationship(array $relationships, $discussion, bool $withMembers = false) + { + if ($withMembers) { + $relationships[self::REL_MEMBERS] = [ + self::RELATIONSHIP_LINKS => [ + Link::RELATED => $this->getRelationshipRelatedLink($discussion, self::REL_MEMBERS) + ], + self::RELATIONSHIP_DATA => $discussion->members + ]; + } + + return $relationships; + } + + private function addTagsRelationship(array $relationships, $discussion, bool $withTags = false) + { + if ($withTags) { + $relationships[self::REL_TAGS] = [ + self::RELATIONSHIP_LINKS => [ + Link::RELATED => $this->getRelationshipRelatedLink($discussion, self::REL_TAGS) + ], + self::RELATIONSHIP_DATA => $discussion->tags + ]; + } + + return $relationships; + } +} diff --git a/lib/classes/JsonApi/Schemas/Forum/DiscussionType.php b/lib/classes/JsonApi/Schemas/Forum/DiscussionType.php new file mode 100644 index 0000000..07ea34d --- /dev/null +++ b/lib/classes/JsonApi/Schemas/Forum/DiscussionType.php @@ -0,0 +1,54 @@ +type_id; + } + + public function getAttributes($discussionType, ContextInterface $context): iterable + { + return [ + 'name' => $discussionType->name, + 'icon' => $discussionType->icon, + 'mkdate' => date('c', $discussionType->mkdate), + 'chdate' => date('c', $discussionType->chdate), + ]; + } + + /** + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function getRelationships($discussionType, ContextInterface $context): iterable + { + $relationships = []; + + $relationships = $this->addDiscussionsRelationship($relationships, $discussionType, $this->shouldInclude($context, self::REL_DISCUSSIONS)); + + return $relationships; + } + + private function addDiscussionsRelationship($relationships, $discussionType, $withDiscussions = false) + { + if ($withDiscussions) { + $relationships[self::REL_DISCUSSIONS] = [ + self::RELATIONSHIP_LINKS => [ + Link::RELATED => $this->getRelationshipRelatedLink($discussionType, self::REL_DISCUSSIONS) + ], + self::RELATIONSHIP_DATA => $discussionType->discussions + ]; + } + + return $relationships; + } +} diff --git a/lib/classes/JsonApi/Schemas/Forum/ForumCategory.php b/lib/classes/JsonApi/Schemas/Forum/ForumCategory.php deleted file mode 100644 index b75e82b..0000000 --- a/lib/classes/JsonApi/Schemas/Forum/ForumCategory.php +++ /dev/null @@ -1,73 +0,0 @@ -id; - } - - public function getAttributes($category, ContextInterface $context): iterable - { - return [ - 'name' => $category->name, - 'description' => $category->description, - 'color' => $category->color, - 'position' => (int) $category->position, - 'mkdate' => date('c', $category->mkdate), - 'chdate' => date('c', $category->chdate) - ]; - } - - public function hasResourceMeta($category): bool - { - return true; - } - - public function getResourceMeta($category) - { - $metaData = $category->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']) : '', - ]; - } - - public function getRelationships($category, ContextInterface $context): iterable - { - $relationships = []; - $relationships = $this->addTopicsRelationship($relationships, $category, $this->shouldInclude($context, self::REL_TOPICS)); - - return $relationships; - } - - /** - * @SuppressWarnings(PHPMD.UnusedFormalParameter) - */ - private function addTopicsRelationship($relationships, $category, $withTopics = false) - { - if ($withTopics) { - $relationships[self::REL_TOPICS] = [ - self::RELATIONSHIP_LINKS => [ - Link::RELATED => $this->getRelationshipRelatedLink($category, self::REL_TOPICS) - ], - self::RELATIONSHIP_DATA => $category->topics - ]; - } - - return $relationships; - } -} diff --git a/lib/classes/JsonApi/Schemas/Forum/ForumDiscussion.php b/lib/classes/JsonApi/Schemas/Forum/ForumDiscussion.php deleted file mode 100644 index 6c5309e..0000000 --- a/lib/classes/JsonApi/Schemas/Forum/ForumDiscussion.php +++ /dev/null @@ -1,170 +0,0 @@ -discussion_id; - } - - public function getAttributes($discussion, ContextInterface $context): iterable - { - return [ - 'title' => $discussion->title, - 'closed-at' => $discussion->closed_at ? date('c', $discussion->closed_at) : null, - 'sticky' => (bool) $discussion->sticky, - 'view-count' => (int) $discussion->view_count, - 'mkdate' => date('c', $discussion->mkdate), - 'chdate' => date('c', $discussion->chdate) - ]; - } - - public function hasResourceMeta($discussion): bool - { - return true; - } - - public function getResourceMeta($discussion) - { - $metaData = $discussion->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']) : '' - ]; - } - - public function getRelationships($discussion, ContextInterface $context): iterable - { - $relationships = []; - - $relationships = $this->addPostingsRelationship($relationships, $discussion, $this->shouldInclude($context, self::REL_POSTINGS)); - $relationships = $this->addTopicRelationship($relationships, $discussion, $this->shouldInclude($context, self::REL_TOPIC)); - $relationships = $this->addCategoryRelationship($relationships, $discussion, $this->shouldInclude($context, self::REL_CATEGORY)); - $relationships = $this->addUserRelationship($relationships, $discussion, $this->shouldInclude($context, self::REL_USER)); - $relationships = $this->addDiscussionTypeRelationship($relationships, $discussion, $this->shouldInclude($context, self::REL_DISCUSSION_TYPE)); - $relationships = $this->addMembersRelationship($relationships, $discussion, $this->shouldInclude($context, self::REL_MEMBERS)); - $relationships = $this->addTagsRelationship($relationships, $discussion, $this->shouldInclude($context, self::REL_TAGS)); - - return $relationships; - } - - private function addPostingsRelationship(array $relationships, $discussion, bool $withPostings = false) - { - if ($withPostings) { - $relationships[self::REL_POSTINGS] = [ - self::RELATIONSHIP_LINKS => [ - Link::RELATED => $this->getRelationshipRelatedLink($discussion, self::REL_POSTINGS) - ], - self::RELATIONSHIP_DATA => $discussion->postings - ]; - } - - return $relationships; - } - - private function addTopicRelationship(array $relationships, $discussion, bool $withTopic = false) - { - if ($withTopic) { - $relationships[self::REL_TOPIC] = [ - self::RELATIONSHIP_LINKS => [ - Link::RELATED => $this->createLinkToResource($discussion->topic) - ], - self::RELATIONSHIP_DATA => $discussion->topic - ]; - } - - return $relationships; - } - - private function addCategoryRelationship(array $relationships, $discussion, bool $withCategory = false) - { - $category = $discussion->category; - if ($withCategory && $category) { - $relationships[self::REL_CATEGORY] = [ - self::RELATIONSHIP_LINKS => [ - Link::RELATED => $this->createLinkToResource($category) - ], - self::RELATIONSHIP_DATA => $category - ]; - } - - return $relationships; - } - - private function addUserRelationship(array $relationships, $discussion, bool $withUser = false) - { - $user = $discussion->user; - if ($withUser && $user) { - $relationships[self::REL_USER] = [ - self::RELATIONSHIP_LINKS => [ - Link::RELATED => $this->createLinkToResource($user) - ], - self::RELATIONSHIP_DATA => $user - ]; - } - - return $relationships; - } - - private function addDiscussionTypeRelationship(array $relationships, $discussion, bool $withDiscussionType = false) - { - $discussionType = $discussion->discussion_type; - - if ($withDiscussionType && $discussionType) { - $relationships[self::REL_DISCUSSION_TYPE] = [ - self::RELATIONSHIP_LINKS => [ - Link::RELATED => $this->createLinkToResource($discussionType) - ], - self::RELATIONSHIP_DATA => $discussionType - ]; - } - - return $relationships; - } - - private function addMembersRelationship(array $relationships, $discussion, bool $withMembers = false) - { - if ($withMembers) { - $relationships[self::REL_MEMBERS] = [ - self::RELATIONSHIP_LINKS => [ - Link::RELATED => $this->getRelationshipRelatedLink($discussion, self::REL_MEMBERS) - ], - self::RELATIONSHIP_DATA => $discussion->members - ]; - } - - return $relationships; - } - - private function addTagsRelationship(array $relationships, $discussion, bool $withTags = false) - { - if ($withTags) { - $relationships[self::REL_TAGS] = [ - self::RELATIONSHIP_LINKS => [ - Link::RELATED => $this->getRelationshipRelatedLink($discussion, self::REL_TAGS) - ], - self::RELATIONSHIP_DATA => $discussion->tags - ]; - } - - return $relationships; - } -} diff --git a/lib/classes/JsonApi/Schemas/Forum/ForumDiscussionType.php b/lib/classes/JsonApi/Schemas/Forum/ForumDiscussionType.php deleted file mode 100644 index 7bdf6bb..0000000 --- a/lib/classes/JsonApi/Schemas/Forum/ForumDiscussionType.php +++ /dev/null @@ -1,54 +0,0 @@ -type_id; - } - - public function getAttributes($discussionType, ContextInterface $context): iterable - { - return [ - 'name' => $discussionType->name, - 'icon' => $discussionType->icon, - 'mkdate' => date('c', $discussionType->mkdate), - 'chdate' => date('c', $discussionType->chdate), - ]; - } - - /** - * @SuppressWarnings(PHPMD.UnusedFormalParameter) - */ - public function getRelationships($discussionType, ContextInterface $context): iterable - { - $relationships = []; - - $relationships = $this->addDiscussionsRelationship($relationships, $discussionType, $this->shouldInclude($context, self::REL_DISCUSSIONS)); - - return $relationships; - } - - private function addDiscussionsRelationship($relationships, $discussionType, $withDiscussions = false) - { - if ($withDiscussions) { - $relationships[self::REL_DISCUSSIONS] = [ - self::RELATIONSHIP_LINKS => [ - Link::RELATED => $this->getRelationshipRelatedLink($discussionType, self::REL_DISCUSSIONS) - ], - self::RELATIONSHIP_DATA => $discussionType->discussions - ]; - } - - return $relationships; - } -} diff --git a/lib/classes/JsonApi/Schemas/Forum/ForumMember.php b/lib/classes/JsonApi/Schemas/Forum/ForumMember.php deleted file mode 100644 index 71a0226..0000000 --- a/lib/classes/JsonApi/Schemas/Forum/ForumMember.php +++ /dev/null @@ -1,30 +0,0 @@ -id; - } - - public function getAttributes($member, ContextInterface $context): iterable - { - return [ - 'username' => $member->username, - 'name' => $member->name, - 'role' => $member->role, - 'avatar_url' => $member->avatar_url - ]; - } - - public function getRelationships($resource, ContextInterface $context): iterable - { - return []; - } -} diff --git a/lib/classes/JsonApi/Schemas/Forum/ForumPosting.php b/lib/classes/JsonApi/Schemas/Forum/ForumPosting.php deleted file mode 100644 index a03e75d..0000000 --- a/lib/classes/JsonApi/Schemas/Forum/ForumPosting.php +++ /dev/null @@ -1,123 +0,0 @@ -posting_id; - } - - public function getAttributes($posting, ContextInterface $context): iterable - { - return [ - 'content' => formatReady($posting->content), - 'anonymous' => (bool) $posting->anonymous, - 'mkdate' => date('c', $posting->mkdate), - 'chdate' => date('c', $posting->chdate) - ]; - } - - public function hasResourceMeta($posting): bool - { - return true; - } - - public function getResourceMeta($posting) - { - return [ - self::REL_OPENGRAPH_URLS => array_map(fn($og) => [ - 'url' => $og['url'], - 'is-opengraph' => (bool) $og['is_opengraph'], - 'title' => $og['title'], - 'description' => $og['description'], - 'image' => $og['image'], - ], $posting->getOpenGraphURLs()) - ]; - } - - public function getRelationships($posting, ContextInterface $context): iterable - { - $relationships = []; - $relationships = $this->addAuthorRelationship($relationships, $posting, $this->shouldInclude($context, self::REL_AUTHOR)); - $relationships = $this->addDiscussionRelationship($relationships, $posting, $this->shouldInclude($context, self::REL_DISCUSSION)); - $relationships = $this->addPostingRelationship($relationships, $posting, $this->shouldInclude($context, self::REL_POSTING)); - $relationships = $this->addReactionsRelationship($relationships, $posting, $this->shouldInclude($context, self::REL_REACTIONS)); - - return $relationships; - } - - private function addAuthorRelationship($relationships, $posting, $withAuthor = false) - { - $author = $posting->author; - - if ($withAuthor && $author) { - $relationships[self::REL_AUTHOR] = [ - self::RELATIONSHIP_LINKS => [ - Link::RELATED => $this->createLinkToResource($author) - ], - self::RELATIONSHIP_DATA => $author - ]; - } - - return $relationships; - } - - private function addDiscussionRelationship($relationships, $posting, $withDiscussion = false) - { - if ($withDiscussion) { - $relationships[self::REL_DISCUSSION] = [ - self::RELATIONSHIP_LINKS => [ - Link::RELATED => $this->createLinkToResource($posting->discussion) - ], - self::RELATIONSHIP_DATA => $posting->discussion - ]; - } - - return $relationships; - } - - private function addPostingRelationship($relationships, $posting, $withPosting = false) - { - $posting = $posting->posting; - - if ($withPosting && $posting) { - $relationships[self::REL_POSTING] = [ - self::RELATIONSHIP_LINKS => [ - Link::RELATED => $this->createLinkToResource($posting) - ], - self::RELATIONSHIP_DATA => $posting - ]; - } - - return $relationships; - } - - private function addReactionsRelationship($relationships, $posting, $withReactions = false) - { - if ($withReactions) { - $relationships[self::REL_REACTIONS] = [ - self::RELATIONSHIP_LINKS => [ - Link::RELATED => $this->getRelationshipRelatedLink($posting, self::REL_REACTIONS) - ], - self::RELATIONSHIP_DATA => $posting->reactions - ]; - } - - return $relationships; - } -} diff --git a/lib/classes/JsonApi/Schemas/Forum/ForumPostingReaction.php b/lib/classes/JsonApi/Schemas/Forum/ForumPostingReaction.php deleted file mode 100644 index 1b7bfa9..0000000 --- a/lib/classes/JsonApi/Schemas/Forum/ForumPostingReaction.php +++ /dev/null @@ -1,71 +0,0 @@ -id; - } - - public function getAttributes($postingReaction, ContextInterface $context): iterable - { - return [ - 'emoji' => $postingReaction->emoji, - 'mkdate' => date('c', $postingReaction->mkdate), - 'chdate' => date('c', $postingReaction->chdate) - ]; - } - - - /** - * @SuppressWarnings(PHPMD.UnusedFormalParameter) - */ - public function getRelationships($postingReaction, ContextInterface $context): iterable - { - $relationships = []; - - $relationships = $this->addPostingRelationship($relationships, $postingReaction, $this->shouldInclude($context, self::REL_POSTING)); - $relationships = $this->addUserRelationship($relationships, $postingReaction, $this->shouldInclude($context, self::REL_USER)); - - return $relationships; - } - - - private function addPostingRelationship(array $relationships, $postingReaction, bool $withPosting = false) - { - if ($withPosting) { - $relationships[self::REL_POSTING] = [ - self::RELATIONSHIP_LINKS => [ - Link::RELATED => $this->createLinkToResource($postingReaction->posting) - ], - self::RELATIONSHIP_DATA => $postingReaction->posting - ]; - } - - return $relationships; - } - - private function addUserRelationship(array $relationships, $discussion, bool $withUser = false) - { - if ($withUser) { - $relationships[self::REL_USER] = [ - self::RELATIONSHIP_LINKS => [ - Link::RELATED => $this->createLinkToResource($discussion->user) - ], - self::RELATIONSHIP_DATA => $discussion->user - ]; - } - - return $relationships; - } -} diff --git a/lib/classes/JsonApi/Schemas/Forum/ForumSubscription.php b/lib/classes/JsonApi/Schemas/Forum/ForumSubscription.php deleted file mode 100644 index 6cc82e0..0000000 --- a/lib/classes/JsonApi/Schemas/Forum/ForumSubscription.php +++ /dev/null @@ -1,84 +0,0 @@ -id; - } - - public function getAttributes($subscription, ContextInterface $context): iterable - { - return [ - 'notification-type' => $subscription->notification_type, - 'mkdate' => date('c', $subscription->mkdate), - 'chdate' => date('c', $subscription->chdate) - ]; - } - - - /** - * @SuppressWarnings(PHPMD.UnusedFormalParameter) - */ - public function getRelationships($subscription, ContextInterface $context): iterable - { - $isPrimary = $context->getPosition()->getLevel() === 0; - $includeList = $context->getIncludePaths(); - - $relationships = []; - if ($isPrimary) { - $relationships = $this->addUserRelationship($relationships, $subscription, $includeList); - $relationships = $this->addSubjectRelationship($relationships, $subscription, $includeList); - $relationships = $this->addRangeRelationship($relationships, $subscription, $includeList); - } - - return $relationships; - } - - private function addUserRelationship(array $relationships, $subscription, array $includeList) - { - $relationships[self::REL_USER] = [ - self::RELATIONSHIP_LINKS => [ - Link::RELATED => $this->createLinkToResource($subscription->user) - ], - self::RELATIONSHIP_DATA => $subscription->user - ]; - - return $relationships; - } - - private function addSubjectRelationship(array $relationships, $subscription, array $includeList) - { - $relationships[self::REL_SUBJECT] = [ - self::RELATIONSHIP_LINKS => [ - Link::RELATED => $this->createLinkToResource($subscription->subject_object) - ], - self::RELATIONSHIP_DATA => $subscription->subject_object - ]; - - return $relationships; - } - - private function addRangeRelationship(array $relationships, $subscription, $includeList) - { - $relationships[self::REL_RANGE] = [ - self::RELATIONSHIP_LINKS => [ - Link::RELATED => $this->createLinkToResource($subscription->range), - ], - self::RELATIONSHIP_DATA => $subscription->range, - ]; - - return $relationships; - } -} diff --git a/lib/classes/JsonApi/Schemas/Forum/ForumTag.php b/lib/classes/JsonApi/Schemas/Forum/ForumTag.php deleted file mode 100644 index 00bf2e9..0000000 --- a/lib/classes/JsonApi/Schemas/Forum/ForumTag.php +++ /dev/null @@ -1,27 +0,0 @@ -id; - } - - public function getAttributes($tag, ContextInterface $context): iterable - { - return [ - 'name' => $tag->name - ]; - } - - public function getRelationships($resource, ContextInterface $context): iterable - { - return []; - } -} diff --git a/lib/classes/JsonApi/Schemas/Forum/ForumTopic.php b/lib/classes/JsonApi/Schemas/Forum/ForumTopic.php deleted file mode 100644 index 88461a1..0000000 --- a/lib/classes/JsonApi/Schemas/Forum/ForumTopic.php +++ /dev/null @@ -1,106 +0,0 @@ -topic_id; - } - - /** - * @inheritdoc - * - * @param \Forum\ForumTopic $topic - */ - public function getAttributes($topic, ContextInterface $context): iterable - { - return [ - 'name' => $topic->name, - 'description' => $topic->description, - 'position' => (int) $topic->position, - 'mkdate' => date('c', $topic->mkdate), - 'chdate' => date('c', $topic->chdate) - ]; - } - - /** - * @inheritdoc - * - * @param \Forum\ForumTopic $topic - */ - public function hasResourceMeta($topic): bool - { - return true; - } - - /** - * @inheritdoc - * - * @param \Forum\ForumTopic $topic - */ - public function getResourceMeta($topic) - { - $metaData = $topic->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']) : '', - ]; - } - - /** - * @SuppressWarnings(PHPMD.UnusedFormalParameter) - * - * @param \Forum\ForumTopic $topic - */ - public function getRelationships($topic, ContextInterface $context): iterable - { - $relationships = []; - $relationships = $this->addCategoryRelationship($relationships, $topic, $this->shouldInclude($context, self::REL_CATEGORY)); - $relationships = $this->addDiscussionsRelationship($relationships, $topic, $this->shouldInclude($context, self::REL_DISCUSSION)); - - return $relationships; - } - - private function addCategoryRelationship($relationships, $topic, $withCategory = false) - { - if ($withCategory) { - $relationships[self::REL_CATEGORY] = [ - self::RELATIONSHIP_LINKS => [ - Link::RELATED => $this->getRelationshipRelatedLink($topic, self::REL_CATEGORY) - ], - self::RELATIONSHIP_DATA => $topic->category - ]; - } - - return $relationships; - } - - private function addDiscussionsRelationship($relationships, $topic, $withDiscussions = false) - { - if ($withDiscussions) { - $relationships[self::REL_DISCUSSION] = [ - self::RELATIONSHIP_LINKS => [ - Link::RELATED => $this->getRelationshipRelatedLink($topic, self::REL_DISCUSSION) - ], - self::RELATIONSHIP_DATA => $topic->dicussions - ]; - } - - return $relationships; - } -} diff --git a/lib/classes/JsonApi/Schemas/Forum/Member.php b/lib/classes/JsonApi/Schemas/Forum/Member.php new file mode 100644 index 0000000..6798c4e --- /dev/null +++ b/lib/classes/JsonApi/Schemas/Forum/Member.php @@ -0,0 +1,30 @@ +id; + } + + public function getAttributes($member, ContextInterface $context): iterable + { + return [ + 'username' => $member->username, + 'name' => $member->name, + 'role' => $member->role, + 'avatar_url' => $member->avatar_url + ]; + } + + public function getRelationships($resource, ContextInterface $context): iterable + { + return []; + } +} diff --git a/lib/classes/JsonApi/Schemas/Forum/Posting.php b/lib/classes/JsonApi/Schemas/Forum/Posting.php new file mode 100644 index 0000000..426c876 --- /dev/null +++ b/lib/classes/JsonApi/Schemas/Forum/Posting.php @@ -0,0 +1,123 @@ +posting_id; + } + + public function getAttributes($posting, ContextInterface $context): iterable + { + return [ + 'content' => formatReady($posting->content), + 'anonymous' => (bool) $posting->anonymous, + 'mkdate' => date('c', $posting->mkdate), + 'chdate' => date('c', $posting->chdate) + ]; + } + + public function hasResourceMeta($posting): bool + { + return true; + } + + public function getResourceMeta($posting) + { + return [ + self::REL_OPENGRAPH_URLS => array_map(fn($og) => [ + 'url' => $og['url'], + 'is-opengraph' => (bool) $og['is_opengraph'], + 'title' => $og['title'], + 'description' => $og['description'], + 'image' => $og['image'], + ], $posting->getOpenGraphURLs()) + ]; + } + + public function getRelationships($posting, ContextInterface $context): iterable + { + $relationships = []; + $relationships = $this->addAuthorRelationship($relationships, $posting, $this->shouldInclude($context, self::REL_AUTHOR)); + $relationships = $this->addDiscussionRelationship($relationships, $posting, $this->shouldInclude($context, self::REL_DISCUSSION)); + $relationships = $this->addPostingRelationship($relationships, $posting, $this->shouldInclude($context, self::REL_POSTING)); + $relationships = $this->addReactionsRelationship($relationships, $posting, $this->shouldInclude($context, self::REL_REACTIONS)); + + return $relationships; + } + + private function addAuthorRelationship($relationships, $posting, $withAuthor = false) + { + $author = $posting->author; + + if ($withAuthor && $author) { + $relationships[self::REL_AUTHOR] = [ + self::RELATIONSHIP_LINKS => [ + Link::RELATED => $this->createLinkToResource($author) + ], + self::RELATIONSHIP_DATA => $author + ]; + } + + return $relationships; + } + + private function addDiscussionRelationship($relationships, $posting, $withDiscussion = false) + { + if ($withDiscussion) { + $relationships[self::REL_DISCUSSION] = [ + self::RELATIONSHIP_LINKS => [ + Link::RELATED => $this->createLinkToResource($posting->discussion) + ], + self::RELATIONSHIP_DATA => $posting->discussion + ]; + } + + return $relationships; + } + + private function addPostingRelationship($relationships, $posting, $withPosting = false) + { + $posting = $posting->posting; + + if ($withPosting && $posting) { + $relationships[self::REL_POSTING] = [ + self::RELATIONSHIP_LINKS => [ + Link::RELATED => $this->createLinkToResource($posting) + ], + self::RELATIONSHIP_DATA => $posting + ]; + } + + return $relationships; + } + + private function addReactionsRelationship($relationships, $posting, $withReactions = false) + { + if ($withReactions) { + $relationships[self::REL_REACTIONS] = [ + self::RELATIONSHIP_LINKS => [ + Link::RELATED => $this->getRelationshipRelatedLink($posting, self::REL_REACTIONS) + ], + self::RELATIONSHIP_DATA => $posting->reactions + ]; + } + + return $relationships; + } +} diff --git a/lib/classes/JsonApi/Schemas/Forum/PostingReaction.php b/lib/classes/JsonApi/Schemas/Forum/PostingReaction.php new file mode 100644 index 0000000..9441b63 --- /dev/null +++ b/lib/classes/JsonApi/Schemas/Forum/PostingReaction.php @@ -0,0 +1,71 @@ +id; + } + + public function getAttributes($postingReaction, ContextInterface $context): iterable + { + return [ + 'emoji' => $postingReaction->emoji, + 'mkdate' => date('c', $postingReaction->mkdate), + 'chdate' => date('c', $postingReaction->chdate) + ]; + } + + + /** + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function getRelationships($postingReaction, ContextInterface $context): iterable + { + $relationships = []; + + $relationships = $this->addPostingRelationship($relationships, $postingReaction, $this->shouldInclude($context, self::REL_POSTING)); + $relationships = $this->addUserRelationship($relationships, $postingReaction, $this->shouldInclude($context, self::REL_USER)); + + return $relationships; + } + + + private function addPostingRelationship(array $relationships, $postingReaction, bool $withPosting = false) + { + if ($withPosting) { + $relationships[self::REL_POSTING] = [ + self::RELATIONSHIP_LINKS => [ + Link::RELATED => $this->createLinkToResource($postingReaction->posting) + ], + self::RELATIONSHIP_DATA => $postingReaction->posting + ]; + } + + return $relationships; + } + + private function addUserRelationship(array $relationships, $discussion, bool $withUser = false) + { + if ($withUser) { + $relationships[self::REL_USER] = [ + self::RELATIONSHIP_LINKS => [ + Link::RELATED => $this->createLinkToResource($discussion->user) + ], + self::RELATIONSHIP_DATA => $discussion->user + ]; + } + + return $relationships; + } +} diff --git a/lib/classes/JsonApi/Schemas/Forum/Subscription.php b/lib/classes/JsonApi/Schemas/Forum/Subscription.php new file mode 100644 index 0000000..4e5484e --- /dev/null +++ b/lib/classes/JsonApi/Schemas/Forum/Subscription.php @@ -0,0 +1,84 @@ +id; + } + + public function getAttributes($subscription, ContextInterface $context): iterable + { + return [ + 'notification-type' => $subscription->notification_type, + 'mkdate' => date('c', $subscription->mkdate), + 'chdate' => date('c', $subscription->chdate) + ]; + } + + + /** + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function getRelationships($subscription, ContextInterface $context): iterable + { + $isPrimary = $context->getPosition()->getLevel() === 0; + $includeList = $context->getIncludePaths(); + + $relationships = []; + if ($isPrimary) { + $relationships = $this->addUserRelationship($relationships, $subscription, $includeList); + $relationships = $this->addSubjectRelationship($relationships, $subscription, $includeList); + $relationships = $this->addRangeRelationship($relationships, $subscription, $includeList); + } + + return $relationships; + } + + private function addUserRelationship(array $relationships, $subscription, array $includeList) + { + $relationships[self::REL_USER] = [ + self::RELATIONSHIP_LINKS => [ + Link::RELATED => $this->createLinkToResource($subscription->user) + ], + self::RELATIONSHIP_DATA => $subscription->user + ]; + + return $relationships; + } + + private function addSubjectRelationship(array $relationships, $subscription, array $includeList) + { + $relationships[self::REL_SUBJECT] = [ + self::RELATIONSHIP_LINKS => [ + Link::RELATED => $this->createLinkToResource($subscription->subject_object) + ], + self::RELATIONSHIP_DATA => $subscription->subject_object + ]; + + return $relationships; + } + + private function addRangeRelationship(array $relationships, $subscription, $includeList) + { + $relationships[self::REL_RANGE] = [ + self::RELATIONSHIP_LINKS => [ + Link::RELATED => $this->createLinkToResource($subscription->range), + ], + self::RELATIONSHIP_DATA => $subscription->range, + ]; + + return $relationships; + } +} diff --git a/lib/classes/JsonApi/Schemas/Forum/Tag.php b/lib/classes/JsonApi/Schemas/Forum/Tag.php new file mode 100644 index 0000000..1c2e72e --- /dev/null +++ b/lib/classes/JsonApi/Schemas/Forum/Tag.php @@ -0,0 +1,27 @@ +id; + } + + public function getAttributes($tag, ContextInterface $context): iterable + { + return [ + 'name' => $tag->name + ]; + } + + public function getRelationships($resource, ContextInterface $context): iterable + { + return []; + } +} diff --git a/lib/classes/JsonApi/Schemas/Forum/Topic.php b/lib/classes/JsonApi/Schemas/Forum/Topic.php new file mode 100644 index 0000000..624c6e7 --- /dev/null +++ b/lib/classes/JsonApi/Schemas/Forum/Topic.php @@ -0,0 +1,106 @@ +topic_id; + } + + /** + * @inheritdoc + * + * @param \Forum\Topic $topic + */ + public function getAttributes($topic, ContextInterface $context): iterable + { + return [ + 'name' => $topic->name, + 'description' => $topic->description, + 'position' => (int) $topic->position, + 'mkdate' => date('c', $topic->mkdate), + 'chdate' => date('c', $topic->chdate) + ]; + } + + /** + * @inheritdoc + * + * @param \Forum\Topic $topic + */ + public function hasResourceMeta($topic): bool + { + return true; + } + + /** + * @inheritdoc + * + * @param \Forum\Topic $topic + */ + public function getResourceMeta($topic) + { + $metaData = $topic->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']) : '', + ]; + } + + /** + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * + * @param \Forum\Topic $topic + */ + public function getRelationships($topic, ContextInterface $context): iterable + { + $relationships = []; + $relationships = $this->addCategoryRelationship($relationships, $topic, $this->shouldInclude($context, self::REL_CATEGORY)); + $relationships = $this->addDiscussionsRelationship($relationships, $topic, $this->shouldInclude($context, self::REL_DISCUSSION)); + + return $relationships; + } + + private function addCategoryRelationship($relationships, $topic, $withCategory = false) + { + if ($withCategory) { + $relationships[self::REL_CATEGORY] = [ + self::RELATIONSHIP_LINKS => [ + Link::RELATED => $this->getRelationshipRelatedLink($topic, self::REL_CATEGORY) + ], + self::RELATIONSHIP_DATA => $topic->category + ]; + } + + return $relationships; + } + + private function addDiscussionsRelationship($relationships, $topic, $withDiscussions = false) + { + if ($withDiscussions) { + $relationships[self::REL_DISCUSSION] = [ + self::RELATIONSHIP_LINKS => [ + Link::RELATED => $this->getRelationshipRelatedLink($topic, self::REL_DISCUSSION) + ], + self::RELATIONSHIP_DATA => $topic->dicussions + ]; + } + + return $relationships; + } +} diff --git a/lib/classes/Privacy.php b/lib/classes/Privacy.php index d28ecf4..a0c1690 100644 --- a/lib/classes/Privacy.php +++ b/lib/classes/Privacy.php @@ -42,7 +42,7 @@ class Privacy ], 'content' => [ FileRef::class, - \Forum\ForumPosting::class, + \Forum\Posting::class, WikiPage::class, Courseware\Unit::class, Courseware\StructuralElement::class, diff --git a/lib/classes/Siteinfo.php b/lib/classes/Siteinfo.php index 8ca1c08..a11e8cb 100644 --- a/lib/classes/Siteinfo.php +++ b/lib/classes/Siteinfo.php @@ -557,7 +557,7 @@ class SiteinfoMarkupEngine { if ($key === 'posting') { $template->title = _('Forenbeiträge'); $template->detail = _('Anzahl Beiträge aller verwendeten Foren'); - $template->count = \Forum\ForumPosting::countBySql(); + $template->count = \Forum\Posting::countBySql(); } else { // iterate over the other indicators if (in_array($key,array_keys($indicator))) { diff --git a/lib/middleware/LegacyRedirectorMiddleware.php b/lib/middleware/LegacyRedirectorMiddleware.php index 5b9960c..d072e27 100644 --- a/lib/middleware/LegacyRedirectorMiddleware.php +++ b/lib/middleware/LegacyRedirectorMiddleware.php @@ -1,7 +1,7 @@ getPath() ) ); - } elseif (ForumTopic::exists($forum_id)) { + } elseif (Topic::exists($forum_id)) { $redirectUri = $uri->withPath( str_replace( 'course/forum/index/index', diff --git a/lib/models/CourseTopic.php b/lib/models/CourseTopic.php index fb31efc..f61738a 100644 --- a/lib/models/CourseTopic.php +++ b/lib/models/CourseTopic.php @@ -51,7 +51,7 @@ class CourseTopic extends SimpleORMap 'foreign_key' => 'author_id' ]; $config['has_and_belongs_to_many']['forum_topics'] = [ - 'class_name' => \Forum\ForumTopic::class, + 'class_name' => \Forum\Topic::class, 'thru_table' => 'forum_topics_issues', 'on_delete' => 'delete', 'on_store' => 'store' @@ -122,7 +122,7 @@ class CourseTopic extends SimpleORMap public function connectWithForumThread() { if ($this->seminar_id && !$this->forum_thread_url) { - $forum_topic = new \Forum\ForumTopic(); + $forum_topic = new \Forum\Topic(); $forum_topic['range_id'] = $this->seminar_id; $forum_topic['name'] = $this['title']; $forum_topic['description'] = $this['description']; diff --git a/lib/models/Forum/Category.php b/lib/models/Forum/Category.php new file mode 100644 index 0000000..73c96e1 --- /dev/null +++ b/lib/models/Forum/Category.php @@ -0,0 +1,109 @@ + Topic::class, + 'foreign_key' => 'category_id', + 'assoc_foreign_key' => 'category_id', + 'order_by' => 'ORDER BY position ASC, mkdate DESC', + ]; + + $config['additional_fields']['range'] = [ + 'set' => function (Category $category, string $field, Range $range) { + $category->range_id = $range->getRangeId(); + }, + 'get' => function (Category $category): Range { + return get_object_by_range_id($category->range_id); + }, + ]; + + $config['additional_fields']['metadata']['get'] = 'getMetaData'; + + $config['registered_callbacks']['after_delete'][] = 'onDelete'; + + parent::configure($config); + } + + /** + * @return self[] + */ + public static function getCourseCategories($range_id): array + { + return self::findBySQL("range_id = ? ORDER BY position ASC, mkdate DESC", [$range_id]); + } + + public function getMetaData(): array + { + return DBManager::get()->fetchOne( + "SELECT + COUNT(DISTINCT`forum_topics`.`topic_id`) AS 'topics_count', + COUNT(DISTINCT `forum_discussions`.`discussion_id`) AS 'discussions_count', + COUNT(DISTINCT `forum_postings`.`posting_id`) AS 'postings_count', + COUNT(DISTINCT `forum_postings`.`user_id`) AS 'users_count', + MAX(`forum_postings`.`mkdate`) AS 'recent_activity', + ( + SELECT SUM(fpr.read_index) + FROM forum_topics ft2 + LEFT JOIN forum_discussions fd2 USING (`topic_id`) + JOIN forum_posting_reads fpr + ON fpr.discussion_id = fd2.discussion_id + AND fpr.user_id = :user_id + WHERE ft2.category_id = :category_id + ) AS 'user_read_index' + FROM `forum_topics` + LEFT JOIN `forum_discussions` USING (`topic_id`) + LEFT JOIN `forum_postings` USING (`discussion_id`) + WHERE `forum_topics`.`category_id` = :category_id", + [ + 'category_id' => $this->category_id, + 'user_id' => User::findCurrent()->user_id + ] + ); + } + + public function transformData(): array + { + return [ + 'category_id' => $this->category_id, + 'range_id' => $this->range_id, + 'name' => $this->name, + 'description' => $this->description, + 'color' => $this->color, + 'position' => $this->position, + 'chdate' => date('c', $this->chdate), + 'mkdate' => date('c', $this->mkdate) + ]; + } + + public function onDelete(): void + { + DBManager::get()->execute( + "Update `forum_topics` SET `category_id` = null WHERE `category_id` = ?", + [$this->category_id] + ); + } +} diff --git a/lib/models/Forum/Discussion.php b/lib/models/Forum/Discussion.php new file mode 100644 index 0000000..96bd85a --- /dev/null +++ b/lib/models/Forum/Discussion.php @@ -0,0 +1,289 @@ + Topic::class, + 'foreign_key' => 'topic_id', + 'assoc_foreign_key' => 'topic_id' + ]; + + $config['belongs_to']['discussion_type'] = [ + 'class_name' => DiscussionType::class, + 'foreign_key' => 'type_id', + 'assoc_foreign_key' => 'type_id' + ]; + + $config['has_many']['postings'] = [ + 'class_name' => Posting::class, + 'foreign_key' => 'discussion_id', + 'assoc_foreign_key' => 'discussion_id', + 'order_by' => 'ORDER BY mkdate', + ]; + + $config['has_many']['subscribers'] = [ + 'class_name' => Subscription::class, + 'foreign_key' => 'discussion_id', + 'assoc_foreign_key' => 'discussion_id' + ]; + + $config['belongs_to']['user'] = [ + 'class_name' => User::class, + 'foreign_key' => 'user_id', + 'assoc_foreign_key' => 'user_id' + ]; + + $config['additional_fields']['range_id']['get'] = 'getRangeId'; + $config['additional_fields']['category']['get'] = 'getCategory'; + $config['additional_fields']['tags']['get'] = 'getTags'; + $config['additional_fields']['users']['get'] = 'getUsers'; + $config['additional_fields']['members']['get'] = 'getMembers'; + $config['additional_fields']['metadata']['get'] = 'getMetaData'; + + $config['registered_callbacks']['after_create'][] = 'onCreate'; + $config['registered_callbacks']['after_delete'][] = 'onDelete'; + + parent::configure($config); + } + + /** + * @param string $range_id course_id or institute_id. + * @param array $filter Optional: filters to apply. + * + * @return self[] + */ + public static function getCourseDiscussions(string $range_id, array $filter = []): array + { + $query = [ + "SELECT + discussions.*, + MAX(postings.mkdate) AS latest_post_date + FROM forum_discussions AS discussions + JOIN forum_postings as postings USING (discussion_id) + JOIN forum_topics AS topics USING (topic_id) + LEFT JOIN tags_relations ON (tags_relations.range_id = discussions.discussion_id AND range_type = 'forum') + WHERE topics.range_id = :range_id", + ['range_id' => $range_id] + ]; + + if (isset($filter['last_visit'])) { + $query[0] .= " AND postings.mkdate > :last_visit"; + $query[1]["last_visit"] = $filter['last_visit']; + } + + if (isset($filter['keyword'])) { + $keyword = $filter['keyword']; + $query[0] .= " AND (discussions.title LIKE :keyword OR postings.content LIKE :keyword)"; + $query[1]["keyword"] = "%$keyword%"; + } + + if (isset($filter['begin'])) { + $query[0] .= " AND postings.mkdate >= :begin"; + $query[1]['begin'] = $filter['begin']; + } + + if (isset($filter['end'])) { + $query[0] .= " AND postings.mkdate <= :end"; + $query[1]['end'] = $filter['end']; + } + + if (isset($filter['topic_ids'])) { + $query[0] .= " AND discussions.topic_id IN (:topic_ids)"; + $query[1]['topic_ids'] = $filter['topic_ids']; + } + + if (isset($filter['type_ids'])) { + $query[0] .= " AND discussions.type_id IN (:type_ids)"; + $query[1]['type_ids'] = $filter['type_ids']; + } + + if (isset($filter['tag_ids'])) { + $query[0] .= " AND tags_relations.tag_id IN (:tag_ids)"; + $query[1]['tag_ids'] = $filter['tag_ids']; + } + + if (isset($filter['user_ids'])) { + $query[0] .= " AND postings.user_id IN (:user_ids)"; + $query[1]['user_ids'] = $filter['user_ids']; + } + + if (isset($filter['status'])) { + $query[0] .= match ($filter['status']) { + 2 => " AND discussions.closed_at IS NULL", // opens + 3 => " AND discussions.closed_at IS NOT NULL", // closed + default => "" + }; + } + + return \DBManager::get()->fetchAll( + $query[0]." GROUP BY discussions.discussion_id ORDER BY latest_post_date DESC", + $query[1], + self::buildExisting(...) + ); + } + + /** + * @return TagDTO[] + */ + public function getTags(): array + { + return DBManager::get()->fetchAll( + "SELECT DISTINCT `tags_relations`.`tag_id`, `tags`.`name` FROM `tags` + LEFT JOIN `tags_relations` ON `tags`.`id` = `tags_relations`.`tag_id` + WHERE `tags_relations`.`range_id` = :discussion_id AND `tags`.`active` = TRUE + ORDER BY `tags`.`mkdate` DESC", + ['discussion_id' => $this->discussion_id], + function ($tag) { + return TagDTO::fromArray([ + 'id' => $tag['tag_id'], + 'name' => $tag['name'] + ]); + } + ); + } + + public function getCategory(): ?Category + { + return Category::findOneBySQL("JOIN forum_topics USING (category_id) WHERE forum_topics.topic_id = :topic_id", ['topic_id' => $this->topic_id]); + } + + public function getRangeId(): string + { + return $this->topic->range_id; + } + + public function getUsers($last_visit = null): array + { + $query = [ + "JOIN forum_postings USING(user_id) + WHERE forum_postings.discussion_id = :discussion_id + AND forum_postings.anonymous = FALSE", + ['discussion_id' => $this->discussion_id] + ]; + + if ($last_visit) { + $query[0] .= " AND forum_postings.mkdate > :last_visit"; + $query[1]["last_visit"] = $last_visit; + } + + $users = User::findBySQL($query[0]." ORDER BY forum_postings.mkdate DESC", $query[1]); + + $unique_users = []; + foreach ($users as $user) { + $unique_users[$user->user_id] = $user; + } + + return array_values($unique_users); + } + + /** + * @return MemberDTO[] + */ + public function getMembers($last_visit = null): array + { + $users = $this->getUsers($last_visit); + + $members = []; + foreach ($users as $user) { + $members[] = MemberDTO::fromUser($user, $this->range_id); + } + + return $members; + } + + public function transformData(): array + { + return [ + 'discussion_id' => $this->discussion_id, + 'topic_id' => $this->topic_id, + 'type_id' => $this->type_id, + 'user_id' => $this->user_id, + 'title' => $this->title, + 'sticky' => (int) $this->sticky, + 'closed_at' => $this->closed_at ? date('c', $this->closed_at) : '', + 'view_count' => (int) $this->view_count, + 'chdate' => date('c', $this->chdate), + 'mkdate' => date('c', $this->mkdate) + ]; + } + + public function getMetaData(int $last_visit = 0): array + { + $user_id = \User::findCurrent()->user_id; + + if (!$last_visit) { + $plugin_id = \PluginEngine::getPlugin(\CoreForum::class)->getPluginId(); + $last_visit = object_get_visit($this->topic->range_id, $plugin_id, 'last', '', $user_id); + } + + return DBManager::get()->fetchOne( + "SELECT + COUNT(`posting_id`) 'postings_count', + MAX(`mkdate`) 'recent_activity', + ( + SELECT + `read_index` + FROM `forum_posting_reads` + WHERE `discussion_id` = :discussion_id AND `user_id` = :user_id + ) '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' + FROM `forum_postings` WHERE `discussion_id` = :discussion_id", + [ + 'discussion_id' => $this->discussion_id, + 'user_id' => $user_id, + 'last_visit' => $last_visit + ] + ); + } + + public function onCreate(): void + { + $discussionNotification = new DiscussionNotification($this); + $discussionNotification->notifySubscribers(); + } + + public function onDelete(): void + { + Subscription::deleteBySQL("subject_id = ?", [$this->discussion_id]); + Posting::deleteBySQL("discussion_id = ?", [$this->discussion_id]); + PostingRead::deleteBySQL("discussion_id = ?", [$this->discussion_id]); + } +} diff --git a/lib/models/Forum/DiscussionType.php b/lib/models/Forum/DiscussionType.php new file mode 100644 index 0000000..0462388 --- /dev/null +++ b/lib/models/Forum/DiscussionType.php @@ -0,0 +1,56 @@ + Discussion::class, + 'foreign_key' => 'type_id' , + 'assoc_foreign_key' => 'type_id' + ]; + + parent::configure($config); + } + + public static function getForumDiscussionType(): array + { + return self::findBySQL("TRUE ORDER BY `mkdate` DESC"); + } + + /** + * @return Discussion[] + */ + public function getDiscussions(): array + { + return DBManager::get()->fetchAll( + "SELECT + discussions.*, + MAX(postings.mkdate) AS latest_post_date + FROM forum_discussions AS discussions + JOIN forum_postings as postings USING (discussion_id) + WHERE discussions.type_id = :type_id + GROUP BY discussions.discussion_id + ORDER BY discussions.sticky DESC, latest_post_date DESC", + ['type_id' => $this->type_id], + Discussion::buildExisting(...) + ); + } +} diff --git a/lib/models/Forum/ForumCategory.php b/lib/models/Forum/ForumCategory.php deleted file mode 100644 index bf42092..0000000 --- a/lib/models/Forum/ForumCategory.php +++ /dev/null @@ -1,109 +0,0 @@ - ForumTopic::class, - 'foreign_key' => 'category_id', - 'assoc_foreign_key' => 'category_id', - 'order_by' => 'ORDER BY position ASC, mkdate DESC', - ]; - - $config['additional_fields']['range'] = [ - 'set' => function (ForumCategory $category, string $field, Range $range) { - $category->range_id = $range->getRangeId(); - }, - 'get' => function (ForumCategory $category): Range { - return get_object_by_range_id($category->range_id); - }, - ]; - - $config['additional_fields']['metadata']['get'] = 'getMetaData'; - - $config['registered_callbacks']['after_delete'][] = 'onDelete'; - - parent::configure($config); - } - - /** - * @return self[] - */ - public static function getCourseCategories($range_id): array - { - return self::findBySQL("range_id = ? ORDER BY position ASC, mkdate DESC", [$range_id]); - } - - public function getMetaData(): array - { - return DBManager::get()->fetchOne( - "SELECT - COUNT(DISTINCT`forum_topics`.`topic_id`) AS 'topics_count', - COUNT(DISTINCT `forum_discussions`.`discussion_id`) AS 'discussions_count', - COUNT(DISTINCT `forum_postings`.`posting_id`) AS 'postings_count', - COUNT(DISTINCT `forum_postings`.`user_id`) AS 'users_count', - MAX(`forum_postings`.`mkdate`) AS 'recent_activity', - ( - SELECT SUM(fpr.read_index) - FROM forum_topics ft2 - LEFT JOIN forum_discussions fd2 USING (`topic_id`) - JOIN forum_posting_reads fpr - ON fpr.discussion_id = fd2.discussion_id - AND fpr.user_id = :user_id - WHERE ft2.category_id = :category_id - ) AS 'user_read_index' - FROM `forum_topics` - LEFT JOIN `forum_discussions` USING (`topic_id`) - LEFT JOIN `forum_postings` USING (`discussion_id`) - WHERE `forum_topics`.`category_id` = :category_id", - [ - 'category_id' => $this->category_id, - 'user_id' => User::findCurrent()->user_id - ] - ); - } - - public function transformData(): array - { - return [ - 'category_id' => $this->category_id, - 'range_id' => $this->range_id, - 'name' => $this->name, - 'description' => $this->description, - 'color' => $this->color, - 'position' => $this->position, - 'chdate' => date('c', $this->chdate), - 'mkdate' => date('c', $this->mkdate) - ]; - } - - public function onDelete(): void - { - DBManager::get()->execute( - "Update `forum_topics` SET `category_id` = null WHERE `category_id` = ?", - [$this->category_id] - ); - } -} diff --git a/lib/models/Forum/ForumDiscussion.php b/lib/models/Forum/ForumDiscussion.php deleted file mode 100644 index 0dae82e..0000000 --- a/lib/models/Forum/ForumDiscussion.php +++ /dev/null @@ -1,283 +0,0 @@ - ForumTopic::class, - 'foreign_key' => 'topic_id', - 'assoc_foreign_key' => 'topic_id' - ]; - - $config['belongs_to']['discussion_type'] = [ - 'class_name' => ForumDiscussionType::class, - 'foreign_key' => 'type_id', - 'assoc_foreign_key' => 'type_id' - ]; - - $config['has_many']['postings'] = [ - 'class_name' => ForumPosting::class, - 'foreign_key' => 'discussion_id', - 'assoc_foreign_key' => 'discussion_id', - 'order_by' => 'ORDER BY mkdate', - ]; - - $config['has_many']['subscribers'] = [ - 'class_name' => ForumSubscription::class, - 'foreign_key' => 'discussion_id', - 'assoc_foreign_key' => 'discussion_id' - ]; - - $config['belongs_to']['user'] = [ - 'class_name' => User::class, - 'foreign_key' => 'user_id', - 'assoc_foreign_key' => 'user_id' - ]; - - $config['additional_fields']['range_id']['get'] = 'getRangeId'; - $config['additional_fields']['category']['get'] = 'getCategory'; - $config['additional_fields']['tags']['get'] = 'getTags'; - $config['additional_fields']['users']['get'] = 'getUsers'; - $config['additional_fields']['members']['get'] = 'getMembers'; - $config['additional_fields']['metadata']['get'] = 'getMetaData'; - - $config['registered_callbacks']['after_create'][] = 'onCreate'; - $config['registered_callbacks']['after_delete'][] = 'onDelete'; - - parent::configure($config); - } - - /** - * @param string $range_id course_id or institute_id. - * @param array $filter Optional: filters to apply. - * - * @return self[] - */ - public static function getCourseDiscussions(string $range_id, array $filter = []): array - { - $query = [ - "SELECT - discussions.*, - MAX(postings.mkdate) AS latest_post_date - FROM forum_discussions AS discussions - JOIN forum_postings as postings USING (discussion_id) - JOIN forum_topics AS topics USING (topic_id) - LEFT JOIN tags_relations ON (tags_relations.range_id = discussions.discussion_id AND range_type = 'forum') - WHERE topics.range_id = :range_id", - ['range_id' => $range_id] - ]; - - if (isset($filter['last_visit'])) { - $query[0] .= " AND postings.mkdate > :last_visit"; - $query[1]["last_visit"] = $filter['last_visit']; - } - - if (isset($filter['keyword'])) { - $keyword = $filter['keyword']; - $query[0] .= " AND (discussions.title LIKE :keyword OR postings.content LIKE :keyword)"; - $query[1]["keyword"] = "%$keyword%"; - } - - if (isset($filter['begin'])) { - $query[0] .= " AND postings.mkdate >= :begin"; - $query[1]['begin'] = $filter['begin']; - } - - if (isset($filter['end'])) { - $query[0] .= " AND postings.mkdate <= :end"; - $query[1]['end'] = $filter['end']; - } - - if (isset($filter['topic_ids'])) { - $query[0] .= " AND discussions.topic_id IN (:topic_ids)"; - $query[1]['topic_ids'] = $filter['topic_ids']; - } - - if (isset($filter['type_ids'])) { - $query[0] .= " AND discussions.type_id IN (:type_ids)"; - $query[1]['type_ids'] = $filter['type_ids']; - } - - if (isset($filter['tag_ids'])) { - $query[0] .= " AND tags_relations.tag_id IN (:tag_ids)"; - $query[1]['tag_ids'] = $filter['tag_ids']; - } - - if (isset($filter['user_ids'])) { - $query[0] .= " AND postings.user_id IN (:user_ids)"; - $query[1]['user_ids'] = $filter['user_ids']; - } - - if (isset($filter['status'])) { - $query[0] .= match ($filter['status']) { - 2 => " AND discussions.closed_at IS NULL", // opens - 3 => " AND discussions.closed_at IS NOT NULL", // closed - default => "" - }; - } - - return \DBManager::get()->fetchAll( - $query[0]." GROUP BY discussions.discussion_id ORDER BY latest_post_date DESC", - $query[1], - self::buildExisting(...) - ); - } - - public function getTags(): array - { - return DBManager::get()->fetchAll( - "SELECT DISTINCT `tags_relations`.`tag_id`, `tags`.`name` FROM `tags` - LEFT JOIN `tags_relations` ON `tags`.`id` = `tags_relations`.`tag_id` - WHERE `tags_relations`.`range_id` = :discussion_id AND `tags`.`active` = TRUE - ORDER BY `tags`.`mkdate` DESC", - ['discussion_id' => $this->discussion_id], - function ($tag) { - return ForumTag::fromArray([ - 'id' => $tag['tag_id'], - 'name' => $tag['name'] - ]); - } - ); - } - - public function getCategory(): ?ForumCategory - { - return ForumCategory::findOneBySQL("JOIN forum_topics USING (category_id) WHERE forum_topics.topic_id = :topic_id", ['topic_id' => $this->topic_id]); - } - - public function getRangeId(): string - { - return $this->topic->range_id; - } - - public function getUsers($last_visit = null): array - { - $query = [ - "JOIN forum_postings USING(user_id) - WHERE forum_postings.discussion_id = :discussion_id - AND forum_postings.anonymous = FALSE", - ['discussion_id' => $this->discussion_id] - ]; - - if ($last_visit) { - $query[0] .= " AND forum_postings.mkdate > :last_visit"; - $query[1]["last_visit"] = $last_visit; - } - - $users = User::findBySQL($query[0]." ORDER BY forum_postings.mkdate DESC", $query[1]); - - $unique_users = []; - foreach ($users as $user) { - $unique_users[$user->user_id] = $user; - } - - return array_values($unique_users); - } - - public function getMembers($last_visit = null): array - { - $users = $this->getUsers($last_visit); - - $members = []; - foreach ($users as $user) { - $members[] = ForumMember::fromUser($user, $this->range_id); - } - - return $members; - } - - public function transformData(): array - { - return [ - 'discussion_id' => $this->discussion_id, - 'topic_id' => $this->topic_id, - 'type_id' => $this->type_id, - 'user_id' => $this->user_id, - 'title' => $this->title, - 'sticky' => (int) $this->sticky, - 'closed_at' => $this->closed_at ? date('c', $this->closed_at) : '', - 'view_count' => (int) $this->view_count, - 'chdate' => date('c', $this->chdate), - 'mkdate' => date('c', $this->mkdate) - ]; - } - - public function getMetaData(int $last_visit = 0): array - { - $user_id = \User::findCurrent()->user_id; - - if (!$last_visit) { - $plugin_id = \PluginEngine::getPlugin(\CoreForum::class)->getPluginId(); - $last_visit = object_get_visit($this->topic->range_id, $plugin_id, 'last', '', $user_id); - } - - return DBManager::get()->fetchOne( - "SELECT - COUNT(`posting_id`) 'postings_count', - MAX(`mkdate`) 'recent_activity', - ( - SELECT - `read_index` - FROM `forum_posting_reads` - WHERE `discussion_id` = :discussion_id AND `user_id` = :user_id - ) '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' - FROM `forum_postings` WHERE `discussion_id` = :discussion_id", - [ - 'discussion_id' => $this->discussion_id, - 'user_id' => $user_id, - 'last_visit' => $last_visit - ] - ); - } - - public function onCreate(): void - { - $discussionNotification = new DiscussionNotification($this); - $discussionNotification->notifySubscribers(); - } - - public function onDelete(): void - { - ForumSubscription::deleteBySQL("subject_id = ?", [$this->discussion_id]); - ForumPosting::deleteBySQL("discussion_id = ?", [$this->discussion_id]); - ForumPostingRead::deleteBySQL("discussion_id = ?", [$this->discussion_id]); - } -} diff --git a/lib/models/Forum/ForumDiscussionType.php b/lib/models/Forum/ForumDiscussionType.php deleted file mode 100644 index e3914b5..0000000 --- a/lib/models/Forum/ForumDiscussionType.php +++ /dev/null @@ -1,56 +0,0 @@ - ForumDiscussion::class, - 'foreign_key' => 'type_id' , - 'assoc_foreign_key' => 'type_id' - ]; - - parent::configure($config); - } - - public static function getForumDiscussionType(): array - { - return self::findBySQL("TRUE ORDER BY `mkdate` DESC"); - } - - /** - * @return ForumDiscussion[] - */ - public function getDiscussions(): array - { - return DBManager::get()->fetchAll( - "SELECT - discussions.*, - MAX(postings.mkdate) AS latest_post_date - FROM forum_discussions AS discussions - JOIN forum_postings as postings USING (discussion_id) - WHERE discussions.type_id = :type_id - GROUP BY discussions.discussion_id - ORDER BY discussions.sticky DESC, latest_post_date DESC", - ['type_id' => $this->type_id], - ForumDiscussion::buildExisting(...) - ); - } -} diff --git a/lib/models/Forum/ForumPosting.php b/lib/models/Forum/ForumPosting.php deleted file mode 100644 index b442046..0000000 --- a/lib/models/Forum/ForumPosting.php +++ /dev/null @@ -1,120 +0,0 @@ - ForumDiscussion::class, - 'foreign_key' => 'discussion_id', - 'assoc_foreign_key' => 'discussion_id' - ]; - - $config['belongs_to']['posting'] = [ - 'class_name' => ForumPosting::class, - 'foreign_key' => 'parent_id', - 'assoc_foreign_key' => 'posting_id' - ]; - - $config['belongs_to']['user'] = [ - 'class_name' => User::class, - 'foreign_key' => 'user_id', - 'assoc_foreign_key' => 'user_id' - ]; - - $config['has_many']['reactions'] = [ - 'class_name' => ForumPostingReaction::class, - 'foreign_key' => 'posting_id', - 'assoc_foreign_key' => 'posting_id' - ]; - - $config['additional_fields']['author']['get'] = 'getAuthor'; - $config['registered_callbacks']['after_create'][] = 'onCreate'; - $config['registered_callbacks']['after_delete'][] = 'onDelete'; - - parent::configure($config); - } - - public function getAuthor(): ?ForumMember - { - if ($this->anonymous && $this->user_id !== User::findCurrent()->user_id) { - return ForumMember::fromArray(); - } - - $user = $this->user; - if ($user) { - return ForumMember::fromUser($user, $this->range_id); - } - - return null; - } - - public static function getRecentPosts($range_id, int $last_visit = 0): array - { - $query = [ - "SELECT - forum_discussions.*, - COUNT(DISTINCT forum_postings.posting_id) AS 'posts' - FROM forum_topics - JOIN forum_discussions USING(topic_id) - JOIN forum_postings USING(discussion_id) - WHERE forum_topics.range_id = :range_id AND forum_postings.user_id != :user_id - ", - [ - 'range_id' => $range_id, - 'user_id' => User::findCurrent()->user_id - ] - ]; - - if ($last_visit) { - $query[0] .= " AND forum_postings.mkdate > :last_visit"; - $query[1]["last_visit"] = $last_visit; - } - - return \DBManager::get()->fetchAll( - $query[0]." GROUP BY discussion_id ORDER BY forum_postings.mkdate DESC", - $query[1] - ); - } - - public function getOpenGraphURLs(): array - { - $content = preg_replace("~(.*)~si", '', $this['content']); - return array_filter(OpenGraph::extract($content)->toArray(), fn($og) => $og['is_opengraph']); - } - - public function onCreate(): void - { - $postingNotification = new PostingNotification($this); - $postingNotification->notifySubscribers(); - } - - public function onDelete(): void - { - ForumPostingReaction::deleteBySQL("posting_id = ?", [$this->posting_id]); - } -} diff --git a/lib/models/Forum/ForumPostingReaction.php b/lib/models/Forum/ForumPostingReaction.php deleted file mode 100644 index f7141a9..0000000 --- a/lib/models/Forum/ForumPostingReaction.php +++ /dev/null @@ -1,49 +0,0 @@ - ForumPosting::class, - 'foreign_key' => 'posting_id', - 'assoc_foreign_key' => 'posting_id' - ]; - - $config['belongs_to']['user'] = [ - 'class_name' => User::class, - 'foreign_key' => 'user_id', - 'assoc_foreign_key' => 'user_id' - ]; - - parent::configure($config); - } -} diff --git a/lib/models/Forum/ForumPostingRead.php b/lib/models/Forum/ForumPostingRead.php deleted file mode 100644 index 8232236..0000000 --- a/lib/models/Forum/ForumPostingRead.php +++ /dev/null @@ -1,64 +0,0 @@ - ForumDiscussion::class, - 'foreign_key' => 'discussion_id', - 'assoc_foreign_key' => 'discussion_id' - ]; - - $config['belongs_to']['user'] = [ - 'class_name' => User::class, - 'foreign_key' => 'user_id', - 'assoc_foreign_key' => 'user_id' - ]; - - parent::configure($config); - } - - public static function updateUserReadPoint($user_id, $discussion_id, int $read_index = 0): ForumPostingRead - { - $postingRead = ForumPostingRead::findOneBySQL( - "discussion_id = :discussion_id AND user_id = :user_id", - [ - 'discussion_id' => $discussion_id, - 'user_id' => $user_id - ] - ); - - if (!$postingRead) { - $postingRead = new ForumPostingRead(); - $postingRead->discussion_id = $discussion_id; - $postingRead->user_id = $user_id; - } - - if (!$read_index) { - $read_index = $postingRead->read_index + 1; - } - - $postingRead->read_index = $read_index; - - $postingRead->store(); - - return $postingRead; - } -} diff --git a/lib/models/Forum/ForumSubscription.php b/lib/models/Forum/ForumSubscription.php deleted file mode 100644 index 2ba8335..0000000 --- a/lib/models/Forum/ForumSubscription.php +++ /dev/null @@ -1,70 +0,0 @@ - User::class, - 'foreign_key' => 'user_id', - 'assoc_foreign_key' => 'user_id' - ]; - - $config['additional_fields']['range'] = [ - 'set' => function (ForumSubscription $subscription, string $field, Range $range) { - $subscription->range_id = $range->getRangeId(); - }, - 'get' => function (ForumSubscription $subscription): Range { - return get_object_by_range_id($subscription->range_id); - }, - ]; - - $config['additional_fields']['subject_object']['get'] = 'getSubjectObject'; - - parent::configure($config); - } - - /** - * @return self[] - */ - public static function getUserSubscriptions(string $range_id, string $user_id): array - { - return self::findBySQL( - "range_id = :range_id AND user_id = :user_id ORDER BY mkdate DESC", - [ - 'range_id' => $range_id, - 'user_id' => $user_id - ] - ); - } - - public function getSubjectObject(): ForumDiscussion | ForumTopic - { - return match ($this->subject) { - 'topic' => ForumTopic::find($this->subject_id), - 'discussion' => ForumDiscussion::find($this->subject_id) - }; - } -} diff --git a/lib/models/Forum/ForumTopic.php b/lib/models/Forum/ForumTopic.php deleted file mode 100644 index 8dd5404..0000000 --- a/lib/models/Forum/ForumTopic.php +++ /dev/null @@ -1,168 +0,0 @@ - ForumCategory::class, - 'foreign_key' => 'category_id', - 'assoc_foreign_key' => 'category_id' - ]; - - $config['has_many']['discussions'] = [ - 'class_name' => ForumDiscussion::class, - 'foreign_key' => 'topic_id', - 'assoc_func' => 'getDiscussions', - 'assoc_foreign_key' => 'topic_id', - ]; - - $config['additional_fields']['range'] = [ - 'set' => function (ForumTopic $topic, string $field, Range $range) { - $topic->range_id = $range->getRangeId(); - }, - 'get' => function (ForumTopic $topic): Range { - return get_object_by_range_id($topic->range_id); - }, - ]; - - $config['additional_fields']['users']['get'] = 'getUsers'; - $config['additional_fields']['metadata']['get'] = 'getMetaData'; - $config['registered_callbacks']['after_delete'][] = 'onDelete'; - - parent::configure($config); - } - - /** - * @return self[] - */ - public static function getCourseTopics(string $range_id): array - { - return self::findBySQL( - "range_id = :range_id - GROUP BY CASE WHEN category_id IS NULL THEN topic_id ELSE category_id END - ORDER BY position ASC, mkdate DESC", - ["range_id" => $range_id] - ); - } - - public static function getCourseTopic(string $range_id, string $topic_id): self - { - return self::findOneBySQL("range_id = ? AND topic_id = ?", [$range_id, $topic_id]); - } - - public function getUsers($last_visit = null): array - { - $query = [ - "JOIN forum_postings USING(user_id) - JOIN forum_discussions USING(discussion_id) - WHERE forum_discussions.topic_id = :topic_id ", - ['topic_id' => $this->topic_id] - ]; - - if ($last_visit) { - $query[0] .= " AND forum_postings.mkdate > :last_visit"; - $query[1]["last_visit"] = $last_visit; - } - - $users = User::findBySQL($query[0]." ORDER BY forum_postings.mkdate DESC", $query[1]); - - $unique_users = []; - foreach ($users as $user) { - $unique_users[$user->user_id] = $user; - } - - return array_values($unique_users); - } - - /** - * @return ForumDiscussion[] - */ - public function getDiscussions(): array - { - return DBManager::get()->fetchAll( - "SELECT - discussions.*, - MAX(postings.mkdate) AS latest_post_date - FROM forum_discussions AS discussions - JOIN forum_postings as postings USING (discussion_id) - WHERE discussions.topic_id = :topic_id - GROUP BY discussions.discussion_id - ORDER BY discussions.sticky DESC, latest_post_date DESC", - ['topic_id' => $this->topic_id], - ForumDiscussion::buildExisting(...) - ); - } - - public function getMetaData(): array - { - return DBManager::get()->fetchOne( - "SELECT - COUNT(DISTINCT `forum_discussions`.`discussion_id`) AS 'discussions_count', - COUNT(DISTINCT `forum_postings`.`posting_id`) AS 'postings_count', - COUNT(DISTINCT `forum_postings`.`user_id`) AS 'users_count', - MAX(`forum_postings`.`mkdate`) AS 'recent_activity', - ( - SELECT - SUM(fpr.read_index) - FROM forum_discussions fd2 - JOIN forum_posting_reads fpr - ON fpr.discussion_id = fd2.discussion_id - AND fpr.user_id = :user_id - WHERE fd2.topic_id = :topic_id - ) AS 'user_read_index' - FROM `forum_discussions` - LEFT JOIN `forum_postings` USING (`discussion_id`) - WHERE `forum_discussions`.`topic_id` = :topic_id", - [ - 'topic_id' => $this->topic_id, - 'user_id' => User::findCurrent()->user_id - ] - ); - } - - public function transformData(): array - { - return [ - 'topic_id' => $this->topic_id, - 'category_id' => $this->category_id, - 'range_id' => $this->range_id, - 'name' => $this->name, - 'description' => $this->description, - 'position' => $this->position, - 'chdate' => date('c', $this->chdate), - 'mkdate' => date('c', $this->mkdate) - ]; - } - - public function onDelete(): void - { - ForumSubscription::deleteBySQL("subject_id = ?", [$this->topic_id]); - ForumDiscussion::deleteBySQL("topic_id = ?", [$this->topic_id]); - } -} diff --git a/lib/models/Forum/Posting.php b/lib/models/Forum/Posting.php new file mode 100644 index 0000000..b0ae0e8 --- /dev/null +++ b/lib/models/Forum/Posting.php @@ -0,0 +1,120 @@ + Discussion::class, + 'foreign_key' => 'discussion_id', + 'assoc_foreign_key' => 'discussion_id' + ]; + + $config['belongs_to']['posting'] = [ + 'class_name' => Posting::class, + 'foreign_key' => 'parent_id', + 'assoc_foreign_key' => 'posting_id' + ]; + + $config['belongs_to']['user'] = [ + 'class_name' => User::class, + 'foreign_key' => 'user_id', + 'assoc_foreign_key' => 'user_id' + ]; + + $config['has_many']['reactions'] = [ + 'class_name' => PostingReaction::class, + 'foreign_key' => 'posting_id', + 'assoc_foreign_key' => 'posting_id' + ]; + + $config['additional_fields']['author']['get'] = 'getAuthor'; + $config['registered_callbacks']['after_create'][] = 'onCreate'; + $config['registered_callbacks']['after_delete'][] = 'onDelete'; + + parent::configure($config); + } + + public function getAuthor(): ?MemberDTO + { + if ($this->anonymous && $this->user_id !== User::findCurrent()->user_id) { + return MemberDTO::fromArray(); + } + + $user = $this->user; + if ($user) { + return MemberDTO::fromUser($user, $this->range_id); + } + + return null; + } + + public static function getRecentPosts($range_id, int $last_visit = 0): array + { + $query = [ + "SELECT + forum_discussions.*, + COUNT(DISTINCT forum_postings.posting_id) AS 'posts' + FROM forum_topics + JOIN forum_discussions USING(topic_id) + JOIN forum_postings USING(discussion_id) + WHERE forum_topics.range_id = :range_id AND forum_postings.user_id != :user_id + ", + [ + 'range_id' => $range_id, + 'user_id' => User::findCurrent()->user_id + ] + ]; + + if ($last_visit) { + $query[0] .= " AND forum_postings.mkdate > :last_visit"; + $query[1]["last_visit"] = $last_visit; + } + + return \DBManager::get()->fetchAll( + $query[0]." GROUP BY discussion_id ORDER BY forum_postings.mkdate DESC", + $query[1] + ); + } + + public function getOpenGraphURLs(): array + { + $content = preg_replace("~(.*)~si", '', $this['content']); + return array_filter(OpenGraph::extract($content)->toArray(), fn($og) => $og['is_opengraph']); + } + + public function onCreate(): void + { + $postingNotification = new PostingNotification($this); + $postingNotification->notifySubscribers(); + } + + public function onDelete(): void + { + PostingReaction::deleteBySQL("posting_id = ?", [$this->posting_id]); + } +} diff --git a/lib/models/Forum/PostingReaction.php b/lib/models/Forum/PostingReaction.php new file mode 100644 index 0000000..643e564 --- /dev/null +++ b/lib/models/Forum/PostingReaction.php @@ -0,0 +1,49 @@ + Posting::class, + 'foreign_key' => 'posting_id', + 'assoc_foreign_key' => 'posting_id' + ]; + + $config['belongs_to']['user'] = [ + 'class_name' => User::class, + 'foreign_key' => 'user_id', + 'assoc_foreign_key' => 'user_id' + ]; + + parent::configure($config); + } +} diff --git a/lib/models/Forum/PostingRead.php b/lib/models/Forum/PostingRead.php new file mode 100644 index 0000000..f690d1b --- /dev/null +++ b/lib/models/Forum/PostingRead.php @@ -0,0 +1,64 @@ + Discussion::class, + 'foreign_key' => 'discussion_id', + 'assoc_foreign_key' => 'discussion_id' + ]; + + $config['belongs_to']['user'] = [ + 'class_name' => User::class, + 'foreign_key' => 'user_id', + 'assoc_foreign_key' => 'user_id' + ]; + + parent::configure($config); + } + + public static function updateUserReadPoint($user_id, $discussion_id, int $read_index = 0): PostingRead + { + $postingRead = PostingRead::findOneBySQL( + "discussion_id = :discussion_id AND user_id = :user_id", + [ + 'discussion_id' => $discussion_id, + 'user_id' => $user_id + ] + ); + + if (!$postingRead) { + $postingRead = new PostingRead(); + $postingRead->discussion_id = $discussion_id; + $postingRead->user_id = $user_id; + } + + if (!$read_index) { + $read_index = $postingRead->read_index + 1; + } + + $postingRead->read_index = $read_index; + + $postingRead->store(); + + return $postingRead; + } +} diff --git a/lib/models/Forum/Subscription.php b/lib/models/Forum/Subscription.php new file mode 100644 index 0000000..47ec6b2 --- /dev/null +++ b/lib/models/Forum/Subscription.php @@ -0,0 +1,70 @@ + User::class, + 'foreign_key' => 'user_id', + 'assoc_foreign_key' => 'user_id' + ]; + + $config['additional_fields']['range'] = [ + 'set' => function (Subscription $subscription, string $field, Range $range) { + $subscription->range_id = $range->getRangeId(); + }, + 'get' => function (Subscription $subscription): Range { + return get_object_by_range_id($subscription->range_id); + }, + ]; + + $config['additional_fields']['subject_object']['get'] = 'getSubjectObject'; + + parent::configure($config); + } + + /** + * @return self[] + */ + public static function getUserSubscriptions(string $range_id, string $user_id): array + { + return self::findBySQL( + "range_id = :range_id AND user_id = :user_id ORDER BY mkdate DESC", + [ + 'range_id' => $range_id, + 'user_id' => $user_id + ] + ); + } + + public function getSubjectObject(): Discussion | Topic + { + return match ($this->subject) { + 'topic' => Topic::find($this->subject_id), + 'discussion' => Discussion::find($this->subject_id) + }; + } +} diff --git a/lib/models/Forum/Topic.php b/lib/models/Forum/Topic.php new file mode 100644 index 0000000..a6b298f --- /dev/null +++ b/lib/models/Forum/Topic.php @@ -0,0 +1,168 @@ + Category::class, + 'foreign_key' => 'category_id', + 'assoc_foreign_key' => 'category_id' + ]; + + $config['has_many']['discussions'] = [ + 'class_name' => Discussion::class, + 'foreign_key' => 'topic_id', + 'assoc_func' => 'getDiscussions', + 'assoc_foreign_key' => 'topic_id', + ]; + + $config['additional_fields']['range'] = [ + 'set' => function (Topic $topic, string $field, Range $range) { + $topic->range_id = $range->getRangeId(); + }, + 'get' => function (Topic $topic): Range { + return get_object_by_range_id($topic->range_id); + }, + ]; + + $config['additional_fields']['users']['get'] = 'getUsers'; + $config['additional_fields']['metadata']['get'] = 'getMetaData'; + $config['registered_callbacks']['after_delete'][] = 'onDelete'; + + parent::configure($config); + } + + /** + * @return self[] + */ + public static function getCourseTopics(string $range_id): array + { + return self::findBySQL( + "range_id = :range_id + GROUP BY CASE WHEN category_id IS NULL THEN topic_id ELSE category_id END + ORDER BY position ASC, mkdate DESC", + ["range_id" => $range_id] + ); + } + + public static function getCourseTopic(string $range_id, string $topic_id): self + { + return self::findOneBySQL("range_id = ? AND topic_id = ?", [$range_id, $topic_id]); + } + + public function getUsers($last_visit = null): array + { + $query = [ + "JOIN forum_postings USING(user_id) + JOIN forum_discussions USING(discussion_id) + WHERE forum_discussions.topic_id = :topic_id ", + ['topic_id' => $this->topic_id] + ]; + + if ($last_visit) { + $query[0] .= " AND forum_postings.mkdate > :last_visit"; + $query[1]["last_visit"] = $last_visit; + } + + $users = User::findBySQL($query[0]." ORDER BY forum_postings.mkdate DESC", $query[1]); + + $unique_users = []; + foreach ($users as $user) { + $unique_users[$user->user_id] = $user; + } + + return array_values($unique_users); + } + + /** + * @return Discussion[] + */ + public function getDiscussions(): array + { + return DBManager::get()->fetchAll( + "SELECT + discussions.*, + MAX(postings.mkdate) AS latest_post_date + FROM forum_discussions AS discussions + JOIN forum_postings as postings USING (discussion_id) + WHERE discussions.topic_id = :topic_id + GROUP BY discussions.discussion_id + ORDER BY discussions.sticky DESC, latest_post_date DESC", + ['topic_id' => $this->topic_id], + Discussion::buildExisting(...) + ); + } + + public function getMetaData(): array + { + return DBManager::get()->fetchOne( + "SELECT + COUNT(DISTINCT `forum_discussions`.`discussion_id`) AS 'discussions_count', + COUNT(DISTINCT `forum_postings`.`posting_id`) AS 'postings_count', + COUNT(DISTINCT `forum_postings`.`user_id`) AS 'users_count', + MAX(`forum_postings`.`mkdate`) AS 'recent_activity', + ( + SELECT + SUM(fpr.read_index) + FROM forum_discussions fd2 + JOIN forum_posting_reads fpr + ON fpr.discussion_id = fd2.discussion_id + AND fpr.user_id = :user_id + WHERE fd2.topic_id = :topic_id + ) AS 'user_read_index' + FROM `forum_discussions` + LEFT JOIN `forum_postings` USING (`discussion_id`) + WHERE `forum_discussions`.`topic_id` = :topic_id", + [ + 'topic_id' => $this->topic_id, + 'user_id' => User::findCurrent()->user_id + ] + ); + } + + public function transformData(): array + { + return [ + 'topic_id' => $this->topic_id, + 'category_id' => $this->category_id, + 'range_id' => $this->range_id, + 'name' => $this->name, + 'description' => $this->description, + 'position' => $this->position, + 'chdate' => date('c', $this->chdate), + 'mkdate' => date('c', $this->mkdate) + ]; + } + + public function onDelete(): void + { + Subscription::deleteBySQL("subject_id = ?", [$this->topic_id]); + Discussion::deleteBySQL("topic_id = ?", [$this->topic_id]); + } +} diff --git a/lib/models/User.php b/lib/models/User.php index 96bfb7b..1741253 100644 --- a/lib/models/User.php +++ b/lib/models/User.php @@ -234,17 +234,17 @@ class User extends AuthUserMd5 implements Range, PrivacyObject, Studip\Calendar\ // Forum $config['has_many']['forum_posting_reads'] = [ - 'class_name' => Forum\ForumPostingRead::class, + 'class_name' => Forum\PostingRead::class, 'assoc_foreign_key' => 'user_id', 'on_delete' => 'delete', ]; $config['has_many']['forum_posting_reactions'] = [ - 'class_name' => Forum\ForumPostingReaction::class, + 'class_name' => Forum\PostingReaction::class, 'assoc_foreign_key' => 'user_id', 'on_delete' => 'delete', ]; $config['has_many']['forum_subscriptions'] = [ - 'class_name' => Forum\ForumSubscription::class, + 'class_name' => Forum\Subscription::class, 'assoc_foreign_key' => 'user_id', 'on_delete' => 'delete', ]; diff --git a/lib/modules/CoreForum.php b/lib/modules/CoreForum.php index 138fc60..838362b 100644 --- a/lib/modules/CoreForum.php +++ b/lib/modules/CoreForum.php @@ -8,7 +8,7 @@ * @since Stud.IP 6.1 */ -use Forum\ForumPosting; +use Forum\Posting; class CoreForum extends CorePlugin implements StudipModule { @@ -49,7 +49,7 @@ class CoreForum extends CorePlugin implements StudipModule $navigation_title = _('Forum'); if ($GLOBALS['perm']->have_studip_perm('user', $course_id)) { - $recent_posts = ForumPosting::getRecentPosts($course_id, $last_visit); + $recent_posts = Posting::getRecentPosts($course_id, $last_visit); $recent_posts_count = array_sum(array_column($recent_posts, 'posts')); if ($recent_posts_count > 0) { @@ -120,7 +120,7 @@ class CoreForum extends CorePlugin implements StudipModule public static function deleteCourseContents($course_id): void { - \Forum\ForumCategory::deleteBySQL("range_id = ?", [$course_id]); - \Forum\ForumTopic::deleteBySQL("range_id = ?", [$course_id]); + \Forum\Category::deleteBySQL("range_id = ?", [$course_id]); + \Forum\Topic::deleteBySQL("range_id = ?", [$course_id]); } } -- cgit v1.0