aboutsummaryrefslogtreecommitdiff
path: root/app/controllers
diff options
context:
space:
mode:
authorRasmus Fuhse <fuhse@data-quest.de>2025-07-04 06:13:27 +0000
committerRasmus Fuhse <fuhse@data-quest.de>2025-07-04 06:13:27 +0000
commitd25a23a626b43baab9714c8a4a68a20144cb3f00 (patch)
treeea948244609f587a3fb9f1e5ab89fd996b30d73c /app/controllers
parentaacbfe703e9e45fd9e8c11a60c3f1ad77593d981 (diff)
Resolve "Forum 3"
Closes #5146 Merge request studip/studip!3845
Diffstat (limited to 'app/controllers')
-rw-r--r--app/controllers/admin/user.php14
-rw-r--r--app/controllers/course/forum/ForumBaseController.php66
-rw-r--r--app/controllers/course/forum/admin.php133
-rw-r--r--app/controllers/course/forum/area.php79
-rw-r--r--app/controllers/course/forum/categories.php122
-rw-r--r--app/controllers/course/forum/configs.php34
-rw-r--r--app/controllers/course/forum/discussion_types.php83
-rw-r--r--app/controllers/course/forum/discussions.php220
-rw-r--r--app/controllers/course/forum/forum_controller.php51
-rw-r--r--app/controllers/course/forum/index.php829
-rw-r--r--app/controllers/course/forum/recent.php24
-rw-r--r--app/controllers/course/forum/search.php217
-rw-r--r--app/controllers/course/forum/subscriptions.php19
-rw-r--r--app/controllers/course/forum/topics.php157
-rw-r--r--app/controllers/course/topics.php3
-rw-r--r--app/controllers/institute/basicdata.php8
-rw-r--r--app/controllers/privacy.php2
17 files changed, 948 insertions, 1113 deletions
diff --git a/app/controllers/admin/user.php b/app/controllers/admin/user.php
index 88b5272..5a0cd9d 100644
--- a/app/controllers/admin/user.php
+++ b/app/controllers/admin/user.php
@@ -1517,15 +1517,11 @@ class Admin_UserController extends AuthenticatedController
'details' => "files",
];
- foreach (PluginEngine::getPlugins(ForumModule::class) as $plugin) {
- $table = $plugin->getEntryTableInfo();
- $queries[] = [
- 'desc' => $plugin->getPluginName() . ' - ' . _("Anzahl der Postings"),
- 'query' => 'SELECT COUNT(*) FROM `' . $table['table'] . '`
- WHERE `' . $table['user_id'] . '` = ?
- GROUP BY `' . $table['user_id'] . '`',
- ];
- }
+ // Forum
+ $queries[] = [
+ 'desc' => _('Forum - Anzahl der Postings'),
+ 'query' => "SELECT COUNT(*) FROM `forum_postings` WHERE `user_id` = ? GROUP BY `user_id`"
+ ];
// Evaluate queries
foreach ($queries as $index => $query) {
diff --git a/app/controllers/course/forum/ForumBaseController.php b/app/controllers/course/forum/ForumBaseController.php
new file mode 100644
index 0000000..cefa5a4
--- /dev/null
+++ b/app/controllers/course/forum/ForumBaseController.php
@@ -0,0 +1,66 @@
+<?php
+namespace Forum;
+
+use ActionsWidget;
+use Context;
+use CoreForum;
+use Icon;
+use Request;
+use SearchWidget;
+use Sidebar;
+use StudipController;
+
+abstract class ForumBaseController extends StudipController
+{
+ protected $with_session = true;
+
+ public function before_filter(&$action, &$args)
+ {
+ object_set_visit_module('forum');
+
+ $this->course_id = Context::getId();
+ $this->is_moderator = CoreForum::isModerator($this->course_id);
+ $this->is_admin = CoreForum::isAdmin($this->course_id);
+
+ $this->buildSidebar();
+
+ parent::before_filter($action, $args);
+ }
+
+ protected function buildSidebar(): void
+ {
+ $actions = new ActionsWidget();
+
+ if ($this->is_moderator) {
+ $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'),
+ 'keyword',
+ true
+ );
+
+ Sidebar::Get()->addWidget($search, 'forum_search');
+ }
+}
diff --git a/app/controllers/course/forum/admin.php b/app/controllers/course/forum/admin.php
deleted file mode 100644
index e4d21d8..0000000
--- a/app/controllers/course/forum/admin.php
+++ /dev/null
@@ -1,133 +0,0 @@
-<?php
-
-/*
- * Copyright (C) 2011 - Till Glöggler <tgloeggl@uos.de>
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation; either version 2 of
- * the License, or (at your option) any later version.
- */
-
-require_once 'forum_controller.php';
-
-class Course_Forum_AdminController extends ForumController
-{
- /* * * * * * * * * * * * * * * * * * * * */
- /* * * A D M I N M E T H O D S * * */
- /* * * * * * * * * * * * * * * * * * * * */
-
- /**
- * show the administration page for mass-editing forum-entries
- */
- function index_action()
- {
- ForumPerm::check('admin', $this->getId());
- $nav = Navigation::getItem('course/forum2');
- $nav->setImage(Icon::create('forum', 'info'));
- Navigation::activateItem('course/forum2/admin');
-
- $list = ForumEntry::getList('flat', $this->getId());
-
- // sort by cat
- $new_list = [];
- $this->categories = [];
- // iterate over all categories and add the belonging areas to them
- $categories = ForumCat::getListWithAreas($this->getId(), false) ;
- foreach ($categories as $category) {
- if (!empty($category['topic_id'])) {
- $new_list[$category['category_id']][$category['topic_id']] = $list['list'][$category['topic_id']];
- unset($list['list'][$category['topic_id']]);
- } else if (ForumPerm::has('add_area', $this->seminar_id)) {
- $new_list[$category['category_id']] = [];
- }
- $this->categories[$category['category_id']] = $category['entry_name'];
- }
-
- if (!empty($list['list'])) {
- // append the remaining entries to the standard category
- $new_list[$this->getId()] = array_merge((array)$new_list[$this->getId()], $list['list']);
- }
-
- $this->list = $new_list;
-
- }
-
- /**
- * show child entries for the passed entry
- *
- * @param string $parent_id id of entry to get the childs for
- */
- function childs_action($parent_id)
- {
- $this->set_layout(null);
-
- // if the parent-id is a category-id, get all areas for this category
- if ($cat = ForumCat::get($parent_id)) {
- ForumPerm::check('admin', $cat['seminar_id']); // check the perms in the categories seminar
- $this->entries = ForumEntry::parseEntries(ForumCat::getAreas($parent_id));
- } else {
- ForumPerm::check('admin', $this->getId(), $parent_id);
- $entries = ForumEntry::getList('flat', $parent_id);
- $this->entries = $entries['list'];
- }
- }
-
- /**
- * move the submitted topics[] to the passed destination
- *
- * @param string $destination id of seminar to move topics to
- */
- function move_action($destination)
- {
- // check if destination is a category_id. if yes, use seminar_id instead
- if (ForumCat::get($destination)) {
- $category_id = $destination;
- $destination = $this->getId();
- } else {
- $category_id = null;
- }
-
- ForumPerm::check('admin', $this->getId(), $destination);
-
- foreach (Request::getArray('topics') as $topic_id) {
- // make sure every passed topic_id is checked against the current seminar
- ForumPerm::check('admin', $this->getId(), $topic_id);
-
- // if the source is an area and the target a category, just move this area to the category
- $entry = ForumEntry::getEntry($topic_id);
- if ($entry['depth'] == 1 && $category_id) {
- ForumCat::removeArea($topic_id);
- ForumCat::addArea($category_id, $topic_id);
- } else {
- // first step: move the whole topic with all childs
- ForumEntry::move($topic_id, $destination);
-
- // if the current topic id is an area, remove it from any categories
- ForumCat::removeArea($topic_id);
-
- // second step: move all to deep childs a level up (depth > 3)
- $data = ForumEntry::getList('depth_to_large', $topic_id);
-
- if (!empty($data['list'])) {
- foreach ($data['list'] as $entry) {
- $path = ForumEntry::getPathToPosting($entry['topic_id']);
- array_shift($path); // Category
- array_shift($path); // Area
-
- $thread = array_shift($path); // Thread
-
- ForumEntry::move($entry['topic_id'], $thread['id']);
- }
- }
-
- // add entry to passed category when moving to the top
- if ($category_id) {
- ForumCat::addArea($category_id, $topic_id);
- }
- }
- }
-
- $this->render_nothing();
- }
-}
diff --git a/app/controllers/course/forum/area.php b/app/controllers/course/forum/area.php
deleted file mode 100644
index 821129f..0000000
--- a/app/controllers/course/forum/area.php
+++ /dev/null
@@ -1,79 +0,0 @@
-<?php
-
-/*
- * Copyright (C) 2011 - Till Glöggler <tgloeggl@uos.de>
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation; either version 2 of
- * the License, or (at your option) any later version.
- */
-
-require_once 'forum_controller.php';
-
-class Course_Forum_AreaController extends ForumController
-{
- function add_action($category_id)
- {
- ForumPerm::check('add_area', $this->getId());
-
- $new_id = md5(uniqid(rand()));
-
- $name = Request::get('name', _('Kein Titel'));
- $content = Request::get('content');
-
- ForumEntry::insert([
- 'topic_id' => $new_id,
- 'seminar_id' => $this->getId(),
- 'user_id' => $GLOBALS['user']->id,
- 'name' => $name,
- 'content' => $content,
- 'author' => get_fullname($GLOBALS['user']->id),
- 'author_host' => ($GLOBALS['user']->id == 'nobody') ? getenv('REMOTE_ADDR') : ''
- ], $this->getId());
-
- ForumCat::addArea($category_id, $new_id);
-
- if (Request::isXhr()) {
- $this->set_layout(null);
- $entries = ForumEntry::parseEntries([ForumEntry::getEntry($new_id)]);
- $this->entry = array_pop($entries);
- $this->visitdate = ForumVisit::getLastVisit($this->getId());
- } else {
- $this->redirect('course/forum/index/index/');
- }
- }
-
- function edit_action($area_id)
- {
- ForumPerm::check('edit_area', $this->getId(), $area_id);
-
- ForumEntry::update($area_id, Request::get('name'), Request::get('content'));
- if (Request::isAjax()) {
- $this->render_json(['content' => ForumEntry::killFormat(ForumEntry::killEdit(Request::get('content')))]);
- } else {
- $this->flash['messages'] = ['success' => _('Die Änderungen am Bereich wurden gespeichert.')];
- $this->redirect('course/forum/index/index');
- }
-
- }
-
- function save_order_action()
- {
- ForumPerm::check('sort_area', $this->getId());
-
- foreach (Request::getArray('areas') as $category_id => $areas) {
- $pos = 0;
- foreach ($areas as $area_id) {
- ForumPerm::checkCategoryId($this->getId(), $category_id);
- ForumPerm::check('sort_area', $this->getId(), $area_id);
-
- ForumCat::addArea($category_id, $area_id);
- ForumCat::setAreaPosition($area_id, $pos);
- $pos++;
- }
- }
-
- $this->render_nothing();
- }
-}
diff --git a/app/controllers/course/forum/categories.php b/app/controllers/course/forum/categories.php
new file mode 100644
index 0000000..2ff5dc6
--- /dev/null
+++ b/app/controllers/course/forum/categories.php
@@ -0,0 +1,122 @@
+<?php
+require_once 'ForumBaseController.php';
+
+use Forum\ForumCategory;
+
+class Course_Forum_CategoriesController extends Forum\ForumBaseController
+{
+ public function before_filter(&$action, &$args)
+ {
+ parent::before_filter($action, $args);
+
+ if (!CourseConfig::get($this->course_id)->FORUM_HIDE_CATEGORIES_NAVIGATION) {
+ Navigation::activateItem('course/forum/categories');
+ } else {
+ Navigation::activateItem('course/forum/topics');
+ }
+ }
+
+ public function index_action()
+ {
+ $this->render_vue_app(
+ Studip\VueApp::create('forum/categories/Index')
+ );
+ }
+
+ public function show_action($category_id)
+ {
+ $category = ForumCategory::find($category_id);
+
+ if (!$category) {
+ throw new AccessDeniedException();
+ }
+
+ PageLayout::setTitle($category->name);
+
+ $this->render_vue_app(
+ Studip\VueApp::create('forum/categories/Show')
+ ->withProps([
+ 'category' => $category->transformData(),
+ 'metadata' => [
+ 'postings_count' => (int) $category->metadata['postings_count'],
+ 'users_count' => (int) $category->metadata['users_count'],
+ 'recent_activity' => date('c', $category->metadata['recent_activity'])
+ ]
+ ])
+ );
+ }
+
+ public function edit_action($category_id = null)
+ {
+ if (!$this->is_moderator) {
+ throw new AccessDeniedException();
+ }
+
+ if ($category_id) {
+ PageLayout::setTitle(_('Kategorie bearbeiten'));
+ $category = ForumCategory::findOneBySQL("range_id = ? AND category_id = ?", [$this->course_id, $category_id]);
+
+ if (!$category) {
+ throw new AccessDeniedException();
+ }
+ } else {
+ PageLayout::setTitle(_('Neue Kategorie anlegen'));
+ $category = new ForumCategory();
+ }
+
+ $this->render_vue_app(
+ Studip\VueApp::create('forum/categories/Edit')
+ ->withProps([
+ 'category' => $category->transformData()
+ ])
+ );
+ }
+
+ public function save_action($category_id = null)
+ {
+ if (!$this->is_moderator) {
+ throw new AccessDeniedException();
+ }
+
+ CSRFProtection::verifyUnsafeRequest();
+
+ if ($category_id) {
+ $category = ForumCategory::findOneBySQL("range_id = ? AND category_id = ?", [$this->course_id, $category_id]);
+ if (!$category) {
+ throw new AccessDeniedException();
+ }
+ } else {
+ $category = new ForumCategory();
+ $category->range_id = $this->course_id;
+ }
+
+ $category->name = Request::get('name');
+ $category->description = Request::get('description');
+ $category->color = Request::get('color');
+
+ $category->store();
+
+ PageLayout::postSuccess(sprintf(_('Die Kategorie „%s“ wurde gespeichert.'), $category->name));
+
+ $this->relocate('course/forum/categories');
+ }
+
+ public function delete_action($category_id)
+ {
+ if (!$this->is_moderator) {
+ throw new AccessDeniedException();
+ }
+
+ $category = ForumCategory::findOneBySQL("range_id = ? AND category_id = ?", [$this->course_id, $category_id]);
+
+ if (!$category) {
+ throw new AccessDeniedException();
+ }
+
+ $category->delete();
+
+ PageLayout::postSuccess(_('Die Kategorie wurde gelöscht.'));
+
+ $this->relocate('course/forum/categories');
+ }
+}
diff --git a/app/controllers/course/forum/configs.php b/app/controllers/course/forum/configs.php
new file mode 100644
index 0000000..70fdb2b
--- /dev/null
+++ b/app/controllers/course/forum/configs.php
@@ -0,0 +1,34 @@
+<?php
+require_once 'ForumBaseController.php';
+
+class Course_Forum_ConfigsController extends Forum\ForumBaseController
+{
+ public function before_filter(&$action, &$args)
+ {
+ parent::before_filter($action, $args);
+
+ if (! $this->is_admin) {
+ throw new AccessDeniedException();
+ }
+ }
+
+ public function edit_action()
+ {
+ $this->config = Context::get()->getConfiguration();
+ }
+
+ public function save_action()
+ {
+ CSRFProtection::verifyUnsafeRequest();
+
+ $this->config = Context::get()->getConfiguration();
+
+ $this->config->store('FORUM_MODERATION_PERMISSION', trim(Request::option('forum_moderation_permission')));
+
+ $this->config->store('FORUM_HIDE_CATEGORIES_NAVIGATION', Request::bool('forum_hide_categories_navigation'));
+
+ PageLayout::postSuccess(_('Die Einstellungen wurden gespeichert.'));
+
+ $this->relocate('course/forum/topics');
+ }
+}
diff --git a/app/controllers/course/forum/discussion_types.php b/app/controllers/course/forum/discussion_types.php
new file mode 100644
index 0000000..da4ea27
--- /dev/null
+++ b/app/controllers/course/forum/discussion_types.php
@@ -0,0 +1,83 @@
+<?php
+use Forum\ForumDiscussionType;
+
+class Course_Forum_DiscussionTypesController extends AuthenticatedController
+{
+ public function before_filter(&$action, &$args)
+ {
+ parent::before_filter($action, $args);
+
+ PageLayout::setTitle(_('Forum Diskussions-Typ'));
+
+ $GLOBALS['perm']->check('root');
+
+ Navigation::activateItem('/admin/locations/forum_discussion_types');
+
+ $actions = new ActionsWidget();
+
+ $actions->addLink(
+ _('Neuen Diskussionstyp anlegen'),
+ $this->url_for('course/forum/discussion_types/edit'),
+ Icon::create('add', Icon::ROLE_CLICKABLE, ['title' => _('Neuen Diskussionstyp anlegen')])
+ )->asDialog('width=700');
+
+ Sidebar::Get()->addWidget($actions);
+ }
+
+ public function index_action()
+ {
+ $this->discussion_types = ForumDiscussionType::findBySQL("TRUE ORDER BY mkdate DESC");
+ }
+
+ public function edit_action(ForumDiscussionType $discussion_type = null)
+ {
+ if ($discussion_type->isNew()) {
+ PageLayout::setTitle(_('Neuen Diskussionstyp anlegen'));
+ } else {
+ PageLayout::setTitle(_('Diskussionstyp bearbeiten'));
+ }
+
+ $icons = [];
+
+ foreach (scandir($GLOBALS['STUDIP_BASE_PATH'] . '/public/assets/images/icons/blue') as $icon_path) {
+ if (!is_dir(
+ $GLOBALS['STUDIP_BASE_PATH'] . '/public/assets/images/icons/blue/'
+ ) . $icon_path && $icon_path[0] !== '.') {
+ if (stripos($icon_path, '.svg')) {
+ $icon_path = substr($icon_path, 0, stripos($icon_path, '.svg'));
+ }
+ $icons[] = $icon_path;
+ }
+ }
+
+ $this->render_vue_app(
+ Studip\VueApp::create('forum/discussions_types/Edit')->withProps([
+ 'icons' => array_unique($icons),
+ 'discussion_type' => $discussion_type->toRawArray()
+ ])
+ );
+ }
+
+ public function save_action(ForumDiscussionType $discussion_type = null)
+ {
+ CSRFProtection::verifyUnsafeRequest();
+
+ $discussion_type->name = Request::get('name');
+ $discussion_type->icon = Request::get('icon');
+
+ $discussion_type->store();
+
+ PageLayout::postSuccess(sprintf(_('Der Diskussionstyp „%s“ wurde gespeichert.'), $discussion_type->name));
+
+ $this->relocate('course/forum/discussion_types/index');
+ }
+
+ public function delete_action(ForumDiscussionType $discussion_type)
+ {
+ $discussion_type->delete();
+
+ PageLayout::postSuccess(_('Der Diskussionstyp wurde gelöscht.'));
+
+ $this->relocate('course/forum/discussion_types/index');
+ }
+}
diff --git a/app/controllers/course/forum/discussions.php b/app/controllers/course/forum/discussions.php
new file mode 100644
index 0000000..745c370
--- /dev/null
+++ b/app/controllers/course/forum/discussions.php
@@ -0,0 +1,220 @@
+<?php
+require_once 'ForumBaseController.php';
+
+use Studip\Markup;
+use Forum\ForumDiscussion;
+use Forum\ForumDiscussionType;
+use Forum\DTO\ForumMember;
+use Forum\ForumPosting;
+use Forum\ForumPostingRead;
+use Forum\ForumSubscription;
+use Forum\DTO\ForumTag;
+use Forum\ForumTopic;
+
+class Course_Forum_DiscussionsController extends Forum\ForumBaseController
+{
+ public function before_filter(&$action, &$args)
+ {
+ parent::before_filter($action, $args);
+
+ Navigation::activateItem('course/forum/topics');
+ }
+
+ public function show_action($discussion_id)
+ {
+ $discussion = ForumDiscussion::find($discussion_id);
+
+ if (!$discussion) {
+ throw new AccessDeniedException();
+ }
+
+ PageLayout::setTitle($discussion->title);
+
+ $auth_user = User::findCurrent();
+
+ $discussion->view_count += 1;
+ $discussion->store();
+
+ $posting_read = ForumPostingRead::findOneBySQL(
+ "discussion_id = :discussion_id AND user_id = :user_id",
+ [
+ 'discussion_id' => $discussion->getId(),
+ 'user_id' => User::findCurrent()->user_id
+ ]
+ );
+
+ $user_subscription = ForumSubscription::findOneBySQL(
+ "subject = :subject AND subject_id = :subject_id AND user_id = :user_id",
+ [
+ 'subject' => 'discussion',
+ 'subject_id' => $discussion->getId(),
+ 'user_id' => $auth_user->user_id
+ ]
+ );
+
+ $category = $discussion->getCategory();
+ $tags = array_map(fn(ForumTag $tag) => $tag->toRawArray(), $discussion->tags);
+ $members = array_map(fn(ForumMember $member) => $member->toRawArray(), $discussion->members);
+
+ $this->render_vue_app(
+ Studip\VueApp::create('forum/discussions/Show')
+ ->withProps([
+ 'auth_user' => [
+ 'id' => $auth_user->id,
+ 'username' => $auth_user->username,
+ 'name' => $auth_user->getFullName(),
+ 'avatar_url' => Avatar::getAvatar($auth_user->user_id)->getURL(Avatar::NORMAL),
+ 'subscription' => $user_subscription ? $user_subscription->toRawArray() : []
+ ],
+ 'discussion' => [
+ ...$discussion->transformData(),
+ 'topic' => $discussion->topic->toRawArray(),
+ 'tags' => $tags,
+ 'members' => $members,
+ 'type' => !empty($discussion->discussion_type) ? $discussion->discussion_type->toRawArray() : []
+ ],
+ 'category' => $category ? $category->toRawArray() : [],
+ 'read_index' => (int) ($posting_read ? $posting_read->read_index : 0),
+ 'redirect' => Request::option('redirect'),
+ 'search_keyword' => $_SESSION['forum'][$this->course_id]['search']['keyword'] ?? ''
+ ])
+ );
+ }
+
+ public function edit_action(ForumDiscussion $discussion = null)
+ {
+ if (!$this->is_moderator) {
+ throw new AccessDeniedException();
+ }
+
+ if ($discussion->isNew()) {
+ PageLayout::setTitle(_('Neue Diskussion starten'));
+ } else {
+ PageLayout::setTitle(_('Diskussion bearbeiten'));
+ }
+
+ $topics = DBManager::get()->fetchAll(
+ "
+ SELECT
+ `ft`.`topic_id`, `ft`.`name`, `fc`.`color`
+ FROM `forum_topics` AS `ft`
+ LEFT JOIN `forum_categories` AS `fc` USING (`category_id`)
+ WHERE `ft`.`range_id` = :course_id
+ ORDER BY `ft`.`position` ASC, `ft`.`mkdate` DESC
+ ",
+ ['course_id' => $this->course_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());
+
+ $this->render_vue_app(
+ Studip\VueApp::create('forum/discussions/Edit')
+ ->withProps([
+ 'discussion' => [
+ ...$discussion->transformData(),
+ 'topic_id' => !empty($discussion->topic_id) ? $discussion->topic_id : Request::option('topic_id'),
+ 'tags' => $discussion_tags
+ ],
+ 'topics' => $topics,
+ 'tags' => $all_tags,
+ 'discussion_types' => $discussion_types
+ ])
+ );
+ }
+
+ public function save_action($discussion_id = null)
+ {
+ if (!$this->is_moderator) {
+ throw new AccessDeniedException();
+ }
+
+ CSRFProtection::verifyUnsafeRequest();
+
+ if ($discussion_id) {
+ $discussion = ForumDiscussion::find($discussion_id);
+ } else {
+ $discussion = new ForumDiscussion();
+ }
+
+ $discussion->title = Request::get('title');
+ $discussion->closed_at = Request::bool('closed_at', false) ? time() : null;
+ $discussion->sticky = Request::bool('sticky', false);
+
+ if (Request::get('type_id')) {
+ $discussion->type_id = Request::get('type_id');
+ }
+
+ $topic = json_decode(Request::get('topic'), true);
+
+ if (empty($topic['topic_id'])) {
+ $newTopic = ForumTopic::create([
+ 'range_id' => $this->course_id,
+ 'name' => $topic['name']
+ ]);
+
+ $topic['topic_id'] = $newTopic->topic_id;
+ }
+
+ $discussion->topic_id = $topic['topic_id'];
+ $discussion->store();
+
+ if (!$discussion_id && Request::get('content')) {
+ ForumPosting::create([
+ 'range_id' => $this->course_id,
+ 'discussion_id' => $discussion->discussion_id,
+ 'content' => Markup::markAsHtml(Request::get('content')),
+ 'user_id' => User::findCurrent()->user_id
+ ]);
+ } else {
+ TagRelation::deleteBySQL("range_id = ? AND range_type = 'forum'", [$discussion->discussion_id]);
+ }
+
+ $tags = json_decode(Request::get('tags'), true);
+
+ foreach ($tags as $tag) {
+ if (empty($tag['tag_id'])) {
+ $newTag = Tag::create([
+ 'name' => $tag['name'],
+ ]);
+
+ $tag['tag_id'] = $newTag->id;
+ }
+
+ TagRelation::create([
+ 'tag_id' => $tag['tag_id'],
+ 'range_id' => $discussion->discussion_id,
+ 'range_type' => 'forum'
+ ]);
+ }
+
+ PageLayout::postSuccess(_('Der Beitrag wurde gespeichert.'));
+
+ $this->relocate(
+ $discussion_id ? 'course/forum/topics/show/' . $discussion->topic_id : 'course/forum/discussions/show/' . $discussion->discussion_id
+ );
+ }
+
+ public function delete_action($discussion_id)
+ {
+ if (!$this->is_moderator) {
+ throw new AccessDeniedException();
+ }
+
+ $discussion = ForumDiscussion::find($discussion_id);
+
+ if (!$discussion) {
+ throw new AccessDeniedException();
+ }
+
+ TagRelation::deleteBySQL("range_id = ? AND range_type = 'forum'", [$discussion->discussion_id]);
+ $topic_id = $discussion->topic_id;
+
+ $discussion->delete();
+
+ PageLayout::postSuccess(_('Die Diskussion wurde gelöscht.'));
+
+ $this->relocate('course/forum/topics/show/' . $topic_id);
+ }
+}
diff --git a/app/controllers/course/forum/forum_controller.php b/app/controllers/course/forum/forum_controller.php
deleted file mode 100644
index 65eec63..0000000
--- a/app/controllers/course/forum/forum_controller.php
+++ /dev/null
@@ -1,51 +0,0 @@
-<?php
-
-abstract class ForumController extends StudipController {
- protected $with_session = true;
-
- /* * * * * * * * * * * * * * * * * * * * * * * * * */
- /* * * * * H E L P E R F U N C T I O N S * * * * */
- /* * * * * * * * * * * * * * * * * * * * * * * * * */
- function getId()
- {
- return ForumHelpers::getSeminarId();
- }
-
- /**
- * Common code for all actions: set default layout and page title.
- *
- * @param type $action
- * @param type $args
- */
- function before_filter(&$action, &$args)
- {
- $this->validate_args($args, ['option', 'option']);
-
- parent::before_filter($action, $args);
-
- $this->flash = Trails\Flash::instance();
-
- // Set help keyword for Stud.IP's user-documentation and page title
- PageLayout::setHelpKeyword('Basis.Forum');
- PageLayout::setTitle(Context::getHeaderLine() .' - '. _('Forum'));
-
- // the default for displaying timestamps
- $this->time_format_string = "%a %d. %B %Y, %H:%M";
- $this->time_format_string_short = "%d.%m.%Y, %H:%M";
-
- //$this->getId() depends on Context::get()
- checkObject();
- ForumVisit::setVisit($this->getId());
- if (Request::int('page')) {
- ForumHelpers::setPage(Request::int('page'));
- }
-
- $this->seminar_id = $this->getId();
-
- $this->no_entries = false;
- $this->highlight = [];
- $this->highlight_topic = '';
- $this->edit_posting = '';
- $this->js = '';
- }
-}
diff --git a/app/controllers/course/forum/index.php b/app/controllers/course/forum/index.php
deleted file mode 100644
index a4f6fb1..0000000
--- a/app/controllers/course/forum/index.php
+++ /dev/null
@@ -1,829 +0,0 @@
-<?php
-
-/*
- * Copyright (C) 2011 - Till Glöggler <tgloeggl@uos.de>
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation; either version 2 of
- * the License, or (at your option) any later version.
- */
-
-require_once 'forum_controller.php';
-
-class Course_Forum_IndexController extends ForumController
-{
- /* * * * * * * * * * * * * * * * * * * * * * * * * */
- /* V I E W - A C T I O N S */
- /* * * * * * * * * * * * * * * * * * * * * * * * * */
-
- /**
- * redirect to correct page (overview or newest entries),
- * depending on whether there are any entries.
- */
- function enter_seminar_action() {
- if (ForumPerm::has('fav_entry', $this->getId())
- && ForumVisit::getCount($this->getId(), ForumVisit::getVisit($this->getId())) > 0) {
- $this->redirect('course/forum/index/newest');
- } else {
- $this->redirect('course/forum/index/index');
- }
- }
-
- /**
- * the main action for the forum. May be called with a topic_id to be displayed
- * and optionally the page to display
- *
- * @param type $topic_id the topic to display, defaults to the main
- * view of the current seminar
- * @param type $page the page to be displayed (for thread-view)
- */
- function index_action($topic_id = null, $page = null)
- {
- $nav = Navigation::getItem('course/forum2');
- $nav->setImage(Icon::create('forum', 'info'));
- Navigation::activateItem('course/forum2/index');
-
- // check, if the root entry is present
- ForumEntry::checkRootEntry($this->getId());
-
- /* * * * * * * * * * * * * * * * * * *
- * V A R I A B L E N F U E L L E N *
- * * * * * * * * * * * * * * * * * * */
-
- $this->section = 'index';
-
- $this->topic_id = $topic_id ? $topic_id : $this->getId();
- $this->constraint = ForumEntry::getConstraints($this->topic_id);
-
- // check if there has been submitted an invalid id and use seminar_id in case
- if (!$this->constraint) {
- $this->topic_id = $this->getId();
- $this->constraint = ForumEntry::getConstraints($this->topic_id);
- }
-
- $this->highlight_topic = Request::option('highlight_topic', null);
-
- // set page to which we shall jump
- if ($page) {
- ForumHelpers::setPage($page);
- }
-
- // we do not crawl deeper than level 2, we show a page chooser instead
- if ($this->constraint['depth'] > 2) {
- ForumHelpers::setPage(ForumEntry::getPostingPage($this->topic_id, $this->constraint));
-
- $path = ForumEntry::getPathToPosting($this->topic_id);
- array_shift($path);array_shift($path);$path_element = array_shift($path);
- $this->child_topic = $this->topic_id;
- $this->topic_id = $path_element['id'];
- $this->constraint = ForumEntry::getConstraints($this->topic_id);
- }
-
- // check if the topic_id matches the currently selected seminar
- ForumPerm::checkTopicId($this->getId(), $this->topic_id);
-
-
- /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
- * B E R E I C H E / T H R E A D S / P O S T I N G S L A D E N *
- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
-
- // load list of areas for use in thread-movement
- if (ForumPerm::has('move_thread', $this->getId())) {
- $this->areas = ForumEntry::getList('flat', $this->getId());
- }
-
- if ($this->constraint['depth'] > 1) { // POSTINGS
- $list = ForumEntry::getList('postings', $this->topic_id);
- if (!empty($list['list'])) {
- $this->postings = $list['list'];
- $this->number_of_entries = $list['count'];
- }
- } else {
- if ($this->constraint['depth'] == 0) { // BEREICHE
- $list = ForumEntry::getList('area', $this->topic_id);
- } else {
- $list = ForumEntry::getList('list', $this->topic_id);
- }
-
- if ($this->constraint['depth'] == 0) { // BEREICHE
- $new_list = [];
- $this->categories = [];
- // iterate over all categories and add the belonging areas to them
- foreach (ForumCat::getListWithAreas($this->getId(), false) as $category) {
- if ($category['topic_id']) {
- $new_list[$category['category_id']][$category['topic_id']] = $list['list'][$category['topic_id']];
- unset($list['list'][$category['topic_id']]);
- } else if (ForumPerm::has('add_area', $this->seminar_id)) {
- $new_list[$category['category_id']] = [];
- }
- $this->categories[$category['category_id']] = $category['entry_name'];
- }
-
- if (!empty($list['list'])) {
- // append the remaining entries to the standard category
- if (!isset($new_list[$this->getId()])) {
- $new_list[$this->getId()] = [];
- }
- $new_list[$this->getId()] = array_merge((array)$new_list[$this->getId()], $list['list']);
- }
-
- // check, if there are any orphaned entries
- foreach ($new_list as $key1 => $list_item) {
- foreach ($list_item as $key2 => $contents) {
- if (empty($contents)) {
- // remove the orphaned entry from the list and from the database
- unset($new_list[$key1][$key2]);
- ForumCat::removeArea($key2);
- }
- }
- }
-
- $this->list = $new_list;
-
- } else if ($this->constraint['depth'] == 1) { // THREADS
- if (!empty($list['list'])) {
- $this->list = [$list['list']];
- }
- }
- $this->number_of_entries = $list['count'];
- }
-
- // set the visit-date and get the stored last_visitdate
- $this->visitdate = ForumVisit::getLastVisit($this->getId());
-
- $this->seminar_id = $this->getId();
-
- // highlight text if passed some words to highlight
- if (Request::getArray('highlight')) {
- $this->highlight = Request::optionArray('highlight');
- }
-
- if (($this->edit_posting = Request::get('edit_posting', null))
- && !ForumPerm::hasEditPerms($this->edit_posting)) {
- $this->edit_posting = null;
- }
-
- // trigger a javascript action, like creating an answer or citing a thread
- if (Request::submitted('answer')) {
- $this->js = 'answer';
- } else if (Request::option('cite')) {
- $this->js = 'cite';
- $this->cite_id = $topic_id;
- }
-
- }
-
- /**
- * show newest entries
- *
- * @param int $page show entries on submitted page
- */
- function newest_action($page = null)
- {
- ForumPerm::check('fav_entry', $this->getId());
-
- $nav = Navigation::getItem('course/forum2');
- $nav->setImage(Icon::create('forum', 'info'));
- Navigation::activateItem('course/forum2/newest');
-
- // set page to which we shall jump
- if ($page) {
- ForumHelpers::setPage($page);
- }
-
- $this->section = 'newest';
- $this->topic_id = $this->getId();
-
- // set the visitdate of the seminar as the last visitdate
- $this->visitdate = ForumVisit::getLastVisit($this->getId());
-
- $list = ForumEntry::getList('newest', $this->topic_id);
- $this->postings = $list['list'] ?? [];
- $this->number_of_entries = $list['count'] ?? 0;
- $this->show_full_path = true;
-
- if (empty($this->postings)) {
- $this->no_entries = true;
- }
-
- $this->render_action('index');
- }
-
- /**
- * show all latest entries as flat list
- *
- * @param int $page show entries on submitted page
- */
- function latest_action($page = null)
- {
- ForumPerm::check('fav_entry', $this->getId());
-
- $nav = Navigation::getItem('course/forum2');
- $nav->setImage(Icon::create('forum', 'info'));
- Navigation::activateItem('course/forum2/latest');
-
- // set page to which we shall jump
- if ($page) {
- ForumHelpers::setPage($page);
- }
-
- $this->section = 'latest';
- $this->topic_id = $this->getId();
-
- // set the visitdate of the seminar as the last visitdate
- $this->visitdate = ForumVisit::getLastVisit($this->getId());
-
- $list = ForumEntry::getList('latest', $this->topic_id);
- $this->postings = $list['list'];
- $this->number_of_entries = $list['count'];
- $this->show_full_path = true;
- $this->no_entries = empty($this->postings);
-
- $this->render_action('index');
- }
-
- /**
- * show the current users favorized entries
- *
- * @param int $page show entries on submitted page
- */
- function favorites_action($page = null)
- {
- ForumPerm::check('fav_entry', $this->getId());
-
- $nav = Navigation::getItem('course/forum2');
- $nav->setImage(Icon::create('forum', 'info'));
- Navigation::activateItem('course/forum2/favorites');
-
- // set page to which we shall jump
- if ($page) {
- ForumHelpers::setPage($page);
- }
-
- $this->section = 'favorites';
- $this->topic_id = $this->getId();
-
- $list = ForumEntry::getList('favorites', $this->topic_id);
- $this->postings = $list['list'];
- $this->number_of_entries = $list['count'];
- $this->show_full_path = true;
- $this->no_entries = empty($this->postings);
-
- // exploit the visitdate for this view
- $this->visitdate = ForumVisit::getLastVisit($this->getId());
-
- $this->render_action('index');
- }
-
- /**
- * show search results
- *
- * @param int $page show entries on submitted page
- */
- function search_action($page = null)
- {
- if (Request::submitted('reset-search')) {
- $this->redirect('course/forum/index/index');
- return;
- }
-
- ForumPerm::check('search', $this->getId());
-
- $nav = Navigation::getItem('course/forum2');
- $nav->setImage(Icon::create('forum', 'info'));
- Navigation::activateItem('course/forum2/index');
-
- // set page to which we shall jump
- if ($page) {
- ForumHelpers::setPage($page);
- }
-
- $this->section = 'search';
- $this->topic_id = $this->getId();
- $this->show_full_path = true;
- $this->options = [];
- // parse filter-options
- foreach (['search_title', 'search_content', 'search_author'] as $option) {
- $this->options[$option] = Request::option($option);
- }
-
- $this->searchfor = Request::get('searchfor');
- if (mb_strlen($this->searchfor) < 3) {
- $this->flash['messages'] = ['error' => _('Ihr Suchbegriff muss mindestens 3 Zeichen lang sein und darf nur Buchstaben und Zahlen enthalten!')];
- } else {
- // get search-results
- $list = ForumEntry::getSearchResults($this->getId(), $this->searchfor, $this->options);
-
- $this->postings = $list['list'];
- $this->number_of_entries = $list['count'] ?? 0;
- $this->highlight = $list['highlight'] ?? false;
-
- if (empty($this->postings)) {
- $this->flash['messages'] = ['info' => _('Es wurden keine Beiträge gefunden, die zu Ihren Suchkriterien passen!')];
- }
- }
-
- // exploit the visitdate for this view
- $this->visitdate = ForumVisit::getLastVisit($this->getId());
-
- $this->render_action('index');
- }
-
- /* * * * * * * * * * * * * * * * * * * * * * * * * */
- /* * * * P O S T I N G - A C T I O N S * * * */
- /* * * * * * * * * * * * * * * * * * * * * * * * * */
-
- /**
- * Add a new entry. Has a simple spambot protection and checks
- * the parent_id to add the entry to, throwing an exception if missing.
- *
- * @throws Exception
- */
- function add_entry_action()
- {
- CSRFProtection::verifyUnsafeRequest();
- // Schutz vor Spambots - diese füllen meistens alle Felder aus, auch "versteckte".
- // Ist dieses Feld gefüllt, war das vermutlich kein Mensch
- if (Request::get('nixda')) {
- throw new Exception('Access denied!');
- }
-
- if (!$parent_id = Request::option('parent')) {
- throw new Exception('missing seminar_id/topic_id while adding a new entry!');
- }
-
- if ($this->seminar_id == $parent_id) {
- ForumPerm::check('add_area', $this->getId(), $parent_id);
- } else {
- ForumPerm::check('add_entry', $this->getId(), $parent_id);
- }
-
- $constraints = ForumEntry::getConstraints($parent_id);
-
- // if we are answering/citing a posting, we want to add it to the thread
- // (which is the parent of passed posting id)
- if ($constraints['depth'] == 3) {
- $parent_id = ForumEntry::getParentTopicId($parent_id);
- }
-
- $new_id = md5(uniqid(rand()));
-
- if ($GLOBALS['user']->id == 'nobody') {
- $fullname = Request::get('author', 'unbekannt');
- } else {
- $fullname = get_fullname($GLOBALS['user']->id);
- }
-
- ForumEntry::insert([
- 'topic_id' => $new_id,
- 'seminar_id' => $this->getId(),
- 'user_id' => $GLOBALS['user']->id,
- 'name' => Request::get('name') ?: '',
- 'content' => Studip\Markup::purifyHtml(Request::get('content')),
- 'author' => $fullname,
- 'author_host' => ($GLOBALS['user']->id == 'nobody') ? getenv('REMOTE_ADDR') : '',
- 'anonymous' => Config::get()->FORUM_ANONYMOUS_POSTINGS ? Request::get('anonymous') ? : 0 : 0
- ], $parent_id);
-
- $this->flash['notify'] = $new_id;
-
- $this->redirect('course/forum/index/index/' . $new_id .'#'. $new_id);
- }
-
- /**
- * Delete the submitted entry.
- *
- * @param string $topic_id the entry to delete
- */
- function delete_entry_action($topic_id)
- {
- // get the page of the posting to be able to jump there again
- $page = ForumEntry::getPostingPage($topic_id);
- URLHelper::addLinkParam('page', $page);
- $parent = null;
- if (
- ForumPerm::hasEditPerms($topic_id)
- || ForumPerm::check('remove_entry', $this->getId(), $topic_id)
- ) {
- CSRFProtection::verifyUnsafeRequest();
- $path = ForumEntry::getPathToPosting($topic_id);
- $topic = array_pop($path);
- $parent = array_pop($path);
-
- if ($topic_id != $this->getId()) {
- ForumEntry::delete($topic_id);
- PageLayout::postSuccess(sprintf(_('Der Eintrag %s wurde gelöscht!'), htmlReady($topic['name'])));
- } else {
- PageLayout::postWarning(_('Sie können nicht die gesamte Veranstaltung löschen!'));
- }
- }
-
- $this->redirect('course/forum/index/index/' . $parent['id'] .'/'. $page);
- }
-
- /**
- * Update the submitted entry.
- *
- * @param string $topic_id id of the entry to update
- * @throws AccessDeniedException
- */
- function update_entry_action($topic_id)
- {
- CSRFProtection::verifyUnsafeRequest();
-
- $name = Request::get('name', _('Kein Titel'));
- $content = Studip\Markup::purifyHtml(Request::get('content', _('Keine Beschreibung')));
-
- ForumPerm::check('add_entry', $this->getId(), $topic_id);
-
- if (ForumPerm::hasEditPerms($topic_id)) {
- ForumEntry::update($topic_id, $name, $content);
- } else {
- throw new AccessDeniedException(_('Sie haben keine Berechtigung, diesen Eintrag zu editieren!'));
- }
-
- if (Request::isXhr()) {
- $this->render_text(json_encode([
- 'name' => htmlReady($name),
- 'content' => formatReady($content)
- ]));
- } else {
- $this->redirect('course/forum/index/index/' . $topic_id .'#'. $topic_id);
- }
- }
-
- /**
- * Move the submitted thread to the submitted parent
- *
- * @param string $thread_id the thread to move
- * @param string $destination the threads new parent
- */
- function move_thread_action($thread_id, $destination) {
- ForumPerm::check('move_thread', $this->getId(), $thread_id);
- ForumPerm::check('move_thread', $this->getId(), $destination);
-
- $current_area = ForumEntry::getParentTopicId($thread_id);
-
- ForumEntry::move($thread_id, $destination);
-
- $this->redirect('course/forum/index/index/' . $current_area .'/'. ForumHelpers::getPage());
- }
-
- /**
- * Mark the submitted entry as favorite
- *
- * @param string $topic_id the entry to mark
- */
- function set_favorite_action($topic_id)
- {
- ForumPerm::check('fav_entry', $this->getId(), $topic_id);
-
- ForumFavorite::set($topic_id);
-
- if (Request::isXhr()) {
- $this->topic_id = $topic_id;
- $this->favorite = true;
- $this->render_template('course/forum/index/_favorite');
- } else {
- $this->redirect('course/forum/index/index/' . $topic_id .'#'. $topic_id);
- }
- }
-
- /**
- * Remove the submtted entry as favorite
- *
- * @param string $topic_id the entry to unmark
- */
- function unset_favorite_action($topic_id) {
- ForumPerm::check('fav_entry', $this->getId(), $topic_id);
-
- ForumFavorite::remove($topic_id);
-
- if (Request::isXhr()) {
- $this->topic_id = $topic_id;
- $this->favorite = false;
- $this->render_template('course/forum/index/_favorite');
- } else {
- $this->redirect('course/forum/index/index/' . $topic_id .'#'. $topic_id);
- }
- }
-
- /**
- * Jump to page in the entries of the submitted parent-entry
- * denoted by the submitted context (section)
- *
- * @param string $topic_id the parent-topic to goto
- * @param string $section the type of view (one of index/search)
- * @param int $page the page to jump to
- */
- function goto_page_action($topic_id, $section, $page)
- {
- switch ($section) {
- case 'index':
- $this->redirect('course/forum/index/index/' . $topic_id .'/'. (int)$page .'#'. $topic_id);
- break;
-
- case 'search':
- $optionlist = [];
-
- foreach (['search_title', 'search_content', 'search_author'] as $option) {
- if (Request::option($option)) {
- $optionlist[] = $option .'='. 1;
- }
- }
-
- $this->redirect('course/forum/index/'. $section .'/'. (int)$page
- .'/?searchfor='. Request::get('searchfor') .'&'. implode('&', $optionlist));
- break;
-
- default:
- $this->redirect('course/forum/index/'. $section .'/'. (int)$page);
- break;
- }
- }
-
- /**
- * Like the submitted topic
- *
- * @param string $topic_id the topic to like
- */
- function like_action($topic_id)
- {
- if (!Request::isPost()) {
- throw new MethodNotAllowedException();
- }
-
- ForumPerm::check('like_entry', $this->getId(), $topic_id);
-
- ForumLike::like($topic_id);
-
- if (Request::isXhr()) {
- $this->topic_id = $topic_id;
- $this->render_template('course/forum/index/_like');
- } else {
- $this->redirect('course/forum/index/index/' . $topic_id .'#'. $topic_id);
- }
- }
-
- /**
- * Remove like for the submitted topic
- *
- * @param string $topic_id the topic to unlike
- */
- function dislike_action($topic_id)
- {
- if (!Request::isPost()) {
- throw new MethodNotAllowedException();
- }
-
- ForumPerm::check('like_entry', $this->getId(), $topic_id);
-
- ForumLike::dislike($topic_id);
-
- if (Request::isXhr()) {
- $this->topic_id = $topic_id;
- $this->render_template('course/forum/index/_like');
- } else {
- $this->redirect('course/forum/index/index/' . $topic_id .'#'. $topic_id);
- }
- }
-
- /**
- * This action is used to close a thread.
- *
- * @param string $topic_id the topic which will be closed
- * @param string $redirect the topic which will be shown after closing the thread
- * @param int $page the page number of the topic $redirect
- */
- function close_thread_action($topic_id, $redirect, $page = 0)
- {
- ForumPerm::check('close_thread', $this->getId(), $topic_id);
-
- ForumEntry::close($topic_id);
-
- $success_text = _('Das Thema wurde erfolgreich geschlossen.');
-
- if (Request::isXhr()) {
- $this->render_text(MessageBox::success($success_text));
- } else {
- PageLayout::postSuccess($success_text);
- $this->redirect('course/forum/index/index/' . $redirect . '/' . $page);
- }
- }
-
- /**
- * This action is used to open a thread.
- *
- * @param string $topic_id the topic which will be opened
- * @param string $redirect the topic which will be shown after opening the thread
- * @param int $page the page number of the topic $redirect
- */
- function open_thread_action($topic_id, $redirect, $page = 0)
- {
- ForumPerm::check('close_thread', $this->getId(), $topic_id);
-
- ForumEntry::open($topic_id);
-
- $success_text = _('Das Thema wurde erfolgreich geöffnet.');
-
- if (Request::isXhr()) {
- $this->render_text(MessageBox::success($success_text));
- } else {
- PageLayout::postSuccess($success_text);
- $this->redirect('course/forum/index/index/' . $redirect . '/' . $page);
- }
- }
-
- /**
- * This action is used to mark a thread as sticky.
- *
- * @param string $topic_id the topic which will be marked as sticky.
- * @param string $redirect the topic which will be shown afterwards
- * @param int $page the page number of the topic $redirect
- */
- function make_sticky_action($topic_id, $redirect, $page = 0)
- {
- ForumPerm::check('make_sticky', $this->getId(), $topic_id);
-
- ForumEntry::sticky($topic_id);
-
- $success_text = _('Das Thema wurde erfolgreich in der Themenliste hervorgehoben.');
-
- if (Request::isXhr()) {
- $this->render_text(MessageBox::success($success_text));
- } else {
- $this->flash['messages'] = ['success' => $success_text];
- $this->redirect('course/forum/index/index/' . $redirect . '/' . $page);
- }
- }
-
- /**
- * This action is used to remove the sticky attribute from a topic.
- *
- * @param string $topic_id the topic which will be marked as unsticky.
- * @param string $redirect the topic which will be shown afterwards
- * @param int $page the page number of the topic $redirect
- */
- function make_unsticky_action($topic_id, $redirect, $page = 0)
- {
- ForumPerm::check('make_sticky', $this->getId(), $topic_id);
-
- ForumEntry::unsticky($topic_id);
-
- $success_text = _('Die Hervorhebung des Themas in der Themenliste wurde entfernt.');
-
- if (Request::isXhr()) {
- $this->render_text(MessageBox::success($success_text));
- } else {
- $this->flash['messages'] = ['success' => $success_text];
- $this->redirect('course/forum/index/index/' . $redirect . '/' . $page);
- }
- }
-
- /* * * * * * * * * * * * * * * * * * * * * * * * * */
- /* * * * C O N F I G - A C T I O N S * * * */
- /* * * * * * * * * * * * * * * * * * * * * * * * * */
-
- /**
- * Add submitted category to current course
- */
- function add_category_action()
- {
- CSRFProtection::verifyUnsafeRequest();
-
- ForumPerm::check('add_category', $this->getId());
-
- $category_id = ForumCat::add($this->getId(), Request::get('category'));
-
- ForumPerm::checkCategoryId($this->getId(), $category_id);
-
- $this->redirect('course/forum/index#cat_'. $category_id);
- }
-
- /*
- * Remove submitted category from current course
- */
- function remove_category_action($category_id)
- {
- CSRFProtection::verifyUnsafeRequest();
-
- ForumPerm::checkCategoryId($this->getId(), $category_id);
- ForumPerm::check('remove_category', $this->getId());
-
- $this->flash['messages'] = ['success' => _('Die Kategorie wurde gelöscht!')];
- ForumCat::remove($category_id, $this->getId());
-
- if (Request::isXhr()) {
- $this->render_template('course/forum/messages');
- } else {
- $this->redirect('course/forum/index/index');
- }
-
- }
-
- /**
- * Change the name of the submitted category
- *
- * @param string $category_id the category to edit
- */
- function edit_category_action($category_id) {
- CSRFProtection::verifyUnsafeRequest();
-
- ForumPerm::checkCategoryId($this->getId(), $category_id);
- ForumPerm::check('edit_category', $this->getId());
-
- if (Request::isXhr()) {
- ForumCat::setName($category_id, Request::get('name'));
- $this->render_nothing();
- } else {
- ForumCat::setName($category_id, Request::get('name'));
- $this->flash['messages'] = ['success' => _('Der Name der Kategorie wurde geändert.')];
- $this->redirect('course/forum/index/index#cat_' . $category_id);
- }
-
- }
-
- /**
- * Save the ordering of the categories
- */
- function savecats_action()
- {
- ForumPerm::check('sort_category', $this->getId());
-
- $pos = 0;
- foreach (Request::getArray('categories') as $category_id) {
- ForumPerm::checkCategoryId($this->getId(), $category_id);
- ForumCat::setPosition($category_id, $pos);
- $pos++;
- }
-
- $this->render_nothing();
- }
-
- /*
- * Subscribe to the submitted topic and receive mails on new postings
- *
- * @param string $topic_id
- */
- function abo_action($topic_id)
- {
- ForumPerm::check('abo', $this->getId(), $topic_id);
-
- ForumAbo::add($topic_id);
- $this->constraint = ForumEntry::getConstraints($topic_id);
-
- if (Request::isXhr()) {
- $this->render_template('course/forum/index/_abo_link');
- } else {
- switch ($this->constraint['depth']) {
- case 0: $msg = _('Sie haben das gesamte Forum abonniert!');break;
- case 1: $msg = _('Sie haben diesen Bereich abonniert.');break;
- default: $msg = _('Sie haben dieses Thema abonniert');break;
- }
- $this->flash['messages'] = ['success' => $msg .' '. _('Sie werden nun über jeden neuen Beitrag informiert.')];
- $this->redirect('course/forum/index/index/' . $topic_id);
- }
- }
-
- /**
- * Unsubscribe from the passed topic
- *
- * @param string $topic_id
- */
- function remove_abo_action($topic_id)
- {
- ForumPerm::check('abo', $this->getId(), $topic_id);
-
- ForumAbo::delete($topic_id);
-
- if (Request::isXhr()) {
- $this->constraint = ForumEntry::getConstraints($topic_id);
- $this->render_template('course/forum/index/_abo_link');
- } else {
- $this->flash['messages'] = ['success' => _('Abonnement aufgehoben.')];
- $this->redirect('course/forum/index/index/' . $topic_id);
- }
- }
-
- /**
- * Generate a pdf-export for the whole forum or the passed subtree
- *
- * @param string $parent_id
- */
- function pdfexport_action($parent_id = null)
- {
- ForumPerm::check('pdfexport', $this->getId(), $parent_id);
-
- ForumHelpers::createPDF($this->getId(), $parent_id);
- }
-
- public function rescue($exception)
- {
- if ($exception instanceof AccessDeniedException) {
- throw new LoginException();
- }
-
- return parent::rescue($exception);
- }
-}
diff --git a/app/controllers/course/forum/recent.php b/app/controllers/course/forum/recent.php
new file mode 100644
index 0000000..2c3fda2
--- /dev/null
+++ b/app/controllers/course/forum/recent.php
@@ -0,0 +1,24 @@
+<?php
+require_once 'ForumBaseController.php';
+
+class Course_Forum_RecentController extends Forum\ForumBaseController
+{
+ public function before_filter(&$action, &$args)
+ {
+ parent::before_filter($action, $args);
+
+ Navigation::activateItem('course/forum/topics');
+ }
+
+ public function index_action()
+ {
+ PageLayout::setTitle(_('Neueste Beiträge'));
+
+ $this->render_vue_app(
+ Studip\VueApp::create('forum/recent/Index')
+ ->withProps([
+ 'last_visit' => Request::int('last_visit')
+ ])
+ );
+ }
+}
diff --git a/app/controllers/course/forum/search.php b/app/controllers/course/forum/search.php
new file mode 100644
index 0000000..84172fb
--- /dev/null
+++ b/app/controllers/course/forum/search.php
@@ -0,0 +1,217 @@
+<?php
+require_once 'ForumBaseController.php';
+
+use Forum\ForumDiscussion;
+use Forum\ForumDiscussionType;
+use Forum\DTO\ForumMember;
+use Forum\DTO\ForumTag;
+
+class Course_Forum_SearchController extends Forum\ForumBaseController
+{
+ public function before_filter(&$action, &$args)
+ {
+ parent::before_filter($action, $args);
+
+ Navigation::activateItem('course/forum');
+ }
+
+ public function index_action()
+ {
+ $topics = DBManager::get()->fetchAll(
+ "SELECT
+ `ft`.`topic_id`, `ft`.`name`, `fc`.`color`
+ FROM `forum_topics` AS `ft`
+ LEFT JOIN `forum_categories` AS `fc` USING (`category_id`)
+ WHERE `ft`.`range_id` = :course_id
+ ORDER BY `ft`.`position` ASC, `ft`.`mkdate` DESC
+ ",
+ ['course_id' => $this->course_id]
+ );
+
+ $course_members = [];
+ foreach (Context::get()->members as $member) {
+ $course_members[] = [
+ 'user_id' => $member['user_id'],
+ 'name' => $member['Vorname'] . ' ' . $member['Nachname'],
+ 'avatar_url' => Avatar::getAvatar($member['user_id'])->getURL(Avatar::NORMAL),
+ 'profile_url' => URLHelper::getLink('dispatch.php/profile', ['username' => $member['username']], true)
+ ];
+ }
+
+ $search_object = $this->buildSearchObject();
+ $all_tags = array_map(fn(ForumTag $tag) => $tag->toRawArray(), ForumTag::getForumTags());
+ $discussion_types = array_map(fn(ForumDiscussionType $discussion_type) => $discussion_type->toRawArray(), ForumDiscussionType::getForumDiscussionType());
+
+ $this->render_vue_app(
+ Studip\VueApp::create('forum/search/Index')
+ ->withProps([
+ 'search' => $search_object,
+ 'discussions' => $this->getResult($search_object),
+ 'topics' => $topics,
+ 'discussion_types' => $discussion_types,
+ 'tags' => $all_tags,
+ 'course_members' => $course_members,
+ ])
+ );
+ }
+
+ private function getResult($search_object): array
+ {
+ if ($this->isSearchObjectEmpty($search_object)) {
+ unset($_SESSION['forum'][$this->course_id]['search']);
+ return [];
+ }
+
+ $query = [
+ "SELECT
+ discussions.discussion_id,
+ COUNT(DISTINCT postings.posting_id) AS 'postings_count'
+ FROM `forum_discussions` AS `discussions`
+ LEFT JOIN `forum_postings` AS `postings` USING(`discussion_id`)
+ LEFT JOIN `tags_relations` ON (`tags_relations`.`range_id` = `discussions`.`discussion_id` AND `range_type` = 'forum')
+ WHERE `postings`.`range_id` = :course_id ",
+ [
+ 'course_id' => $this->course_id
+ ]
+ ];
+
+ $keyword = $search_object['keyword'];
+ if ($keyword) {
+ $query[0] .= " AND (discussions.title LIKE :keyword OR postings.content LIKE :keyword)";
+ $query[1]["keyword"] = "%$keyword%";
+ }
+
+ if ($search_object['begin']) {
+ $query[0] .= " AND postings.mkdate >= :begin";
+ $query[1]['begin'] = $search_object['begin'];
+ }
+
+ if ($search_object['end']) {
+ $query[0] .= " AND postings.mkdate <= :end";
+ $query[1]['end'] = $search_object['end'];
+ }
+
+ if ($search_object['topic_ids']) {
+ $query[0] .= " AND discussions.topic_id IN (:topic_ids)";
+ $query[1]['topic_ids'] = $search_object['topic_ids'];
+ }
+
+ if ($search_object['discussion_type_ids']) {
+ $query[0] .= " AND discussions.type_id IN (:type_ids)";
+ $query[1]['type_ids'] = $search_object['discussion_type_ids'];
+ }
+
+ if ($search_object['tag_ids']) {
+ $query[0] .= " AND tags_relations.tag_id IN (:tag_ids)";
+ $query[1]['tag_ids'] = $search_object['tag_ids'];
+ }
+
+ if ($search_object['user_ids']) {
+ $query[0] .= " AND postings.user_id IN (:user_ids)";
+ $query[1]['user_ids'] = $search_object['user_ids'];
+ }
+
+ $query[0] .= match ($search_object['discussion_status']) {
+ 2 => " AND discussions.closed_at IS NULL", // opens
+ 3 => " AND discussions.closed_at IS NOT NULL", // closed
+ default => ""
+ };
+
+ $result = DBManager::get()->fetchAll(
+ $query[0]." GROUP BY discussions.discussion_id",
+ $query[1]
+ );
+
+ $discussions = ForumDiscussion::findBySQL("discussion_id IN (:discussion_ids)", ['discussion_ids' => array_column($result, 'discussion_id')]);
+
+
+ return array_map(function (ForumDiscussion $discussion) use ($result) {
+ $postings_count = array_find($result, fn($item) => $item['discussion_id'] === $discussion->discussion_id)['postings_count'];
+ $members = array_map(fn(ForumMember $member) => $member->toRawArray(), $discussion->members);
+ $tags = array_map(fn(ForumTag $tag) => $tag->toRawArray(), $discussion->tags);
+
+ return [
+ 'id' => $discussion->discussion_id,
+ 'title' => $discussion->title,
+ 'closed_at' => $discussion->closed_at ? date('c', $discussion->closed_at) : null,
+ 'view_count' => (int) $discussion->view_count,
+ 'sticky' => (bool) $discussion->sticky,
+ 'mkdate' => date('c', $discussion->mkdate),
+ 'chdate' => date('c', $discussion->chdate),
+ 'topic' => $discussion->topic->toRawArray(),
+ 'category' => $discussion->category ? [
+ 'name' => $discussion->category->name,
+ 'color' => $discussion->category->color,
+ ] : [],
+ 'discussion_type' => $discussion->discussion_type ? [
+ 'name' => $discussion->discussion_type->name,
+ 'icon' => $discussion->discussion_type->icon,
+ ] : [],
+ 'members' => $members,
+ 'tags' => $tags,
+ 'meta' => [
+ 'postings_count' => (int) $postings_count,
+ 'recent_activity' => $discussion->metadata['recent_activity'] ? date('c', $discussion->metadata['recent_activity']) : null,
+ ]
+ ];
+ }, $discussions);
+ }
+
+ private function isSearchObjectEmpty($search_object): bool {
+ if (
+ $search_object['keyword'] ||
+ $search_object['begin'] ||
+ $search_object['end'] ||
+ $search_object['discussion_status'] ||
+ $search_object['discussion_type_ids'] ||
+ $search_object['tag_ids'] ||
+ $search_object['topic_ids'] ||
+ $search_object['user_ids']
+ ) {
+ return false;
+ }
+
+ return true;
+ }
+
+ private function buildSearchObject(): array
+ {
+ $request = Request::getInstance();
+ if (
+ $request->offsetExists('keyword') ||
+ $request->offsetExists('begin') ||
+ $request->offsetExists('end') ||
+ $request->offsetExists('discussion_status') ||
+ $request->offsetExists('discussion_type_ids') ||
+ $request->offsetExists('tag_ids') ||
+ $request->offsetExists('topic_ids') ||
+ $request->offsetExists('user_ids')
+ ) {
+ $search_object = [
+ 'keyword' => Request::get('keyword'),
+ 'begin' => Request::int('begin'),
+ 'end' => Request::int('end'),
+ 'discussion_status' => Request::int('discussion_status'),
+ 'discussion_type_ids' => Request::getArray('discussion_type_ids'),
+ 'tag_ids' => Request::getArray('tag_ids'),
+ 'topic_ids' => Request::getArray('topic_ids'),
+ 'user_ids' => Request::getArray('user_ids')
+ ];
+
+ $_SESSION['forum'][$this->course_id]['search'] = $search_object;
+ return $search_object;
+ }
+
+ $session_search = $_SESSION['forum'][$this->course_id]['search'] ?? [];
+ return [
+ 'keyword' => $session_search['keyword'] ?? '',
+ 'begin' => $session_search['begin'] ?? 0,
+ 'end' => $session_search['end'] ?? 0,
+ 'discussion_status' => $session_search['discussion_status'] ?? 0,
+ 'discussion_type_ids' => $session_search['discussion_type_ids'] ?? [],
+ 'tag_ids' => $session_search['tag_ids'] ?? [],
+ 'topic_ids' => $session_search['topic_ids'] ?? [],
+ 'user_ids' => $session_search['user_ids'] ?? []
+ ];
+ }
+}
diff --git a/app/controllers/course/forum/subscriptions.php b/app/controllers/course/forum/subscriptions.php
new file mode 100644
index 0000000..399c072
--- /dev/null
+++ b/app/controllers/course/forum/subscriptions.php
@@ -0,0 +1,19 @@
+<?php
+require_once 'ForumBaseController.php';
+
+class Course_Forum_SubscriptionsController extends Forum\ForumBaseController
+{
+ public function before_filter(&$action, &$args)
+ {
+ parent::before_filter($action, $args);
+
+ Navigation::activateItem('course/forum/subscriptions');
+ }
+
+ public function index_action()
+ {
+ $this->render_vue_app(
+ Studip\VueApp::create('forum/subscriptions/Index')
+ );
+ }
+}
diff --git a/app/controllers/course/forum/topics.php b/app/controllers/course/forum/topics.php
new file mode 100644
index 0000000..0070937
--- /dev/null
+++ b/app/controllers/course/forum/topics.php
@@ -0,0 +1,157 @@
+<?php
+require_once 'ForumBaseController.php';
+
+use Forum\ForumCategory;
+use Forum\ForumSubscription;
+use Forum\ForumTopic;
+
+class Course_Forum_TopicsController extends Forum\ForumBaseController
+{
+ public function before_filter(&$action, &$args)
+ {
+ parent::before_filter($action, $args);
+
+ unset($_SESSION['forum'][$this->course_id]['search']);
+
+ Navigation::activateItem('course/forum/topics');
+ }
+
+ public function index_action()
+ {
+ $this->render_vue_app(
+ Studip\VueApp::create('forum/topics/Index')
+ );
+ }
+
+ public function show_action($topic_id)
+ {
+ $topic = ForumTopic::find($topic_id);
+
+ if (!$topic) {
+ throw new AccessDeniedException();
+ }
+
+ PageLayout::setTitle($topic->name);
+
+ $user_subscription = ForumSubscription::findOneBySQL(
+ "subject = :subject AND subject_id = :subject_id AND user_id = :user_id",
+ [
+ 'subject' => 'topic',
+ 'subject_id' => $topic->getId(),
+ 'user_id' => User::findCurrent()->user_id
+ ]
+ );
+
+ $this->render_vue_app(
+ Studip\VueApp::create('forum/topics/Show')
+ ->withProps([
+ 'topic' => $topic->transformData(),
+ 'category' => $topic->category ? $topic->category->transformData() : [],
+ 'user_subscription' => $user_subscription ? $user_subscription->toRawArray() : [],
+ 'metadata' => [
+ 'postings_count' => (int) $topic->metadata['postings_count'],
+ 'users_count' => (int) $topic->metadata['users_count'],
+ 'recent_activity' => date('c', $topic->metadata['recent_activity'])
+ ]
+ ])
+ );
+ }
+
+ public function edit_action($topic_id = null)
+ {
+ if (!$this->is_moderator) {
+ throw new AccessDeniedException();
+ }
+
+ if ($topic_id) {
+ PageLayout::setTitle(_('Thema bearbeiten'));
+ $topic = ForumTopic::getCourseTopic($this->course_id, $topic_id);
+
+ if (!$topic) {
+ throw new AccessDeniedException();
+ }
+ } else {
+ PageLayout::setTitle(_('Neues Thema anlegen'));
+ $topic = new ForumTopic();
+ $topic['category_id'] = Request::get('category_id');
+ }
+
+ $categories = DBManager::get()->fetchAll(
+ "SELECT * FROM `forum_categories` WHERE `range_id` = ? ORDER BY `position` ASC, `mkdate` DESC",
+ [$this->course_id]
+ );
+
+ $this->render_vue_app(
+ Studip\VueApp::create('forum/topics/Edit')
+ ->withProps([
+ 'topic' => $topic->transformData(),
+ 'categories' => $categories
+ ])
+ );
+ }
+
+ public function save_action($topic_id = null)
+ {
+ if (!$this->is_moderator) {
+ throw new AccessDeniedException();
+ }
+
+ CSRFProtection::verifyUnsafeRequest();
+
+ if ($topic_id) {
+ $topic = ForumTopic::getCourseTopic($this->course_id, $topic_id);
+ if (!$topic) {
+ throw new AccessDeniedException();
+ }
+ } else {
+ $topic = new ForumTopic();
+ $topic->range_id = $this->course_id;
+ }
+
+ $category = json_decode(Request::get('category'), true);
+
+ if (empty($category['category_id']) && !empty($category['name'])) {
+ $newCategory = ForumCategory::create([
+ 'range_id' => $this->course_id,
+ 'color' => '#28497C',
+ 'name' => $category['name']
+ ]);
+
+ $category['category_id'] = $newCategory->category_id;
+ } else {
+ $topic->category_id = null;
+ }
+
+ if (!empty($category['category_id'])) {
+ $topic->category_id = $category['category_id'];
+ }
+
+ $topic->name = Request::get('name');
+ $topic->description = Request::get('description');
+
+ $topic->store();
+
+ PageLayout::postSuccess(_('Das Thema wurde gespeichert.'));
+
+ $this->relocate('course/forum/topics/show/' . $topic->topic_id);
+ }
+
+ public function delete_action($topic_id)
+ {
+ if (!$this->is_moderator) {
+ throw new AccessDeniedException();
+ }
+
+ $topic = ForumTopic::getCourseTopic($this->course_id, $topic_id);
+
+ if (!$topic) {
+ throw new AccessDeniedException();
+ }
+
+ $topic->delete();
+
+ PageLayout::postSuccess(_('Das Thema wurde gelöscht.'));
+
+ $this->relocate('course/forum/topics');
+ }
+}
diff --git a/app/controllers/course/topics.php b/app/controllers/course/topics.php
index 36e25ec..e955ba1 100644
--- a/app/controllers/course/topics.php
+++ b/app/controllers/course/topics.php
@@ -97,9 +97,6 @@ class Course_TopicsController extends AuthenticatedController
if (Request::bool('folder')) {
$topic->connectWithDocumentFolder();
}
-
- // create a connection to the module forum (can be anything)
- // will update title and description automagically
if (Request::bool('forumthread')) {
$topic->connectWithForumThread();
}
diff --git a/app/controllers/institute/basicdata.php b/app/controllers/institute/basicdata.php
index 3a5a751..c1bb1cd 100644
--- a/app/controllers/institute/basicdata.php
+++ b/app/controllers/institute/basicdata.php
@@ -432,14 +432,6 @@ class Institute_BasicdataController extends AuthenticatedController
}
}
- // delete all contents in forum-modules
- foreach (PluginEngine::getPlugins(ForumModule::class) as $plugin) {
- $plugin->deleteContents($i_id); // delete content irrespective of plugin-activation in the seminar
- if ($plugin->isActivated($i_id)) { // only show a message, if the plugin is activated, to not confuse the user
- $details[] = sprintf(_('Einträge in %s gelöscht.'), $plugin->getPluginName());
- }
- }
-
// Alle Pluginzuordnungen entfernen
PluginManager::getInstance()->deactivateAllPluginsForRange('inst', $i_id);
diff --git a/app/controllers/privacy.php b/app/controllers/privacy.php
index 5598f65..7aa8780 100644
--- a/app/controllers/privacy.php
+++ b/app/controllers/privacy.php
@@ -437,7 +437,7 @@ class PrivacyController extends AuthenticatedController
'description' => _('Nachrichten, Kommentare, Blubber, News'),
],
'content' => [
- 'icon' => Icon::create('forum2'),
+ 'icon' => Icon::create('forum'),
'title' => _('Inhalte'),
'description' => _('Courseware, Dateien, Forum, Wiki, Literaturlisten'),
],