diff options
| author | Rasmus Fuhse <fuhse@data-quest.de> | 2025-07-04 06:13:27 +0000 |
|---|---|---|
| committer | Rasmus Fuhse <fuhse@data-quest.de> | 2025-07-04 06:13:27 +0000 |
| commit | d25a23a626b43baab9714c8a4a68a20144cb3f00 (patch) | |
| tree | ea948244609f587a3fb9f1e5ab89fd996b30d73c /app/controllers | |
| parent | aacbfe703e9e45fd9e8c11a60c3f1ad77593d981 (diff) | |
Resolve "Forum 3"
Closes #5146
Merge request studip/studip!3845
Diffstat (limited to 'app/controllers')
| -rw-r--r-- | app/controllers/admin/user.php | 14 | ||||
| -rw-r--r-- | app/controllers/course/forum/ForumBaseController.php | 66 | ||||
| -rw-r--r-- | app/controllers/course/forum/admin.php | 133 | ||||
| -rw-r--r-- | app/controllers/course/forum/area.php | 79 | ||||
| -rw-r--r-- | app/controllers/course/forum/categories.php | 122 | ||||
| -rw-r--r-- | app/controllers/course/forum/configs.php | 34 | ||||
| -rw-r--r-- | app/controllers/course/forum/discussion_types.php | 83 | ||||
| -rw-r--r-- | app/controllers/course/forum/discussions.php | 220 | ||||
| -rw-r--r-- | app/controllers/course/forum/forum_controller.php | 51 | ||||
| -rw-r--r-- | app/controllers/course/forum/index.php | 829 | ||||
| -rw-r--r-- | app/controllers/course/forum/recent.php | 24 | ||||
| -rw-r--r-- | app/controllers/course/forum/search.php | 217 | ||||
| -rw-r--r-- | app/controllers/course/forum/subscriptions.php | 19 | ||||
| -rw-r--r-- | app/controllers/course/forum/topics.php | 157 | ||||
| -rw-r--r-- | app/controllers/course/topics.php | 3 | ||||
| -rw-r--r-- | app/controllers/institute/basicdata.php | 8 | ||||
| -rw-r--r-- | app/controllers/privacy.php | 2 |
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'), ], |
