From 20240b2aacb15ab3264afbfbbc9dae952db4bb63 Mon Sep 17 00:00:00 2001 From: Elmar Ludwig Date: Wed, 11 May 2022 07:19:42 +0000 Subject: convert old core plugins to new model, re #814 Merge request studip/studip!440 --- .gitignore | 1 - app/controllers/activityfeed.php | 95 ++ app/controllers/course/forum/admin.php | 126 ++ app/controllers/course/forum/area.php | 80 ++ app/controllers/course/forum/forum_controller.php | 45 + app/controllers/course/forum/index.php | 835 ++++++++++++ app/controllers/course/messenger.php | 78 ++ app/controllers/news.php | 14 + app/controllers/quickselection.php | 45 + app/routes/Forum.php | 8 - app/views/activityfeed/configuration.php | 23 + app/views/course/forum/admin/childs.php | 31 + app/views/course/forum/admin/index.php | 31 + app/views/course/forum/area/_add_area_form.php | 15 + app/views/course/forum/area/_edit_area_form.php | 7 + .../course/forum/area/_edit_category_form.php | 8 + app/views/course/forum/area/_js_templates.php | 45 + app/views/course/forum/area/add.php | 87 ++ app/views/course/forum/index/_abo_link.php | 23 + app/views/course/forum/index/_areas.php | 102 ++ app/views/course/forum/index/_breadcrumb.php | 16 + app/views/course/forum/index/_favorite.php | 12 + app/views/course/forum/index/_js_templates.php | 16 + app/views/course/forum/index/_last_post.php | 21 + app/views/course/forum/index/_like.php | 47 + app/views/course/forum/index/_new_category.php | 19 + app/views/course/forum/index/_new_entry.php | 67 + app/views/course/forum/index/_post.php | 270 ++++ app/views/course/forum/index/_postings.php | 15 + app/views/course/forum/index/_preview.php | 11 + app/views/course/forum/index/_smiley_favorites.php | 27 + app/views/course/forum/index/_threads.php | 196 +++ app/views/course/forum/index/index.php | 265 ++++ app/views/course/forum/messages.php | 7 + app/views/course/statusgroups/groupinfo.php | 2 +- app/views/quickselection/configuration.php | 22 + db/migrations/5.2.6_update_core_plugins.php | 48 + lib/activities/ForumProvider.php | 4 +- lib/calendar/CalendarWidgetView.php | 52 + lib/classes/ForumAbo.php | 186 +++ lib/classes/ForumActivity.php | 149 +++ lib/classes/ForumBulkMail.php | 135 ++ lib/classes/ForumEntry.php | 1385 ++++++++++++++++++++ lib/classes/ForumFavorite.php | 41 + lib/classes/ForumHelpers.php | 282 ++++ lib/classes/ForumIssue.php | 96 ++ lib/classes/ForumLike.php | 99 ++ lib/classes/ForumPerm.php | 215 +++ lib/classes/ForumVisit.php | 162 +++ .../JsonApi/Routes/Forum/ForumAuthority.php | 2 - .../JsonApi/Routes/Forum/ForumCategoriesShow.php | 2 - lib/classes/Privacy.php | 2 - lib/classes/globalsearch/GlobalSearchForum.php | 4 +- lib/models/BlubberThread.php | 2 +- lib/models/ForumCat.php | 252 ++++ lib/modules/ActivityFeed.php | 77 ++ lib/modules/Blubber.class.php | 126 ++ lib/modules/ContentsWidget.php | 30 + lib/modules/CoreForum.class.php | 209 +++ lib/modules/EvaluationsWidget.php | 62 + lib/modules/NewsWidget.php | 65 + lib/modules/QuickSelection.php | 57 + lib/modules/ScheduleWidget.php | 54 + lib/modules/TerminWidget.php | 43 + lib/plugins/engine/PluginManager.class.php | 2 +- public/plugins.php | 11 +- .../core/ActivityFeed/ActivityFeed.php | 171 --- .../core/ActivityFeed/css/style.less | 197 --- .../core/ActivityFeed/javascript/activityfeed.js | 127 -- .../core/ActivityFeed/plugin.manifest | 7 - .../core/ActivityFeed/templates/_jstemplates.php | 100 -- .../core/ActivityFeed/templates/activity_feed.php | 19 - .../core/ActivityFeed/templates/edit.php | 23 - .../core/Blubber/Blubber.class.php | 113 -- .../core/Blubber/controllers/messenger.php | 84 -- .../plugins_packages/core/Blubber/plugin.manifest | 16 - .../core/Blubber/views/messenger/course.php | 1 - .../core/ContentsWidget/ContentsWidget.php | 24 - .../core/ContentsWidget/plugin.manifest | 6 - .../core/ContentsWidget/templates/index.php | 28 - .../core/EvaluationsWidget/EvaluationsWidget.php | 55 - .../core/EvaluationsWidget/plugin.manifest | 6 - .../core/Forum/CoreForum.class.php | 199 --- .../core/Forum/controllers/admin.php | 123 -- .../core/Forum/controllers/area.php | 78 -- .../core/Forum/controllers/forum_controller.php | 67 - .../core/Forum/controllers/index.php | 849 ------------ .../core/Forum/models/ForumAbo.php | 189 --- .../core/Forum/models/ForumActivity.php | 149 --- .../core/Forum/models/ForumBulkMail.php | 137 -- .../core/Forum/models/ForumCat.php | 252 ---- .../core/Forum/models/ForumEntry.php | 1385 -------------------- .../core/Forum/models/ForumFavorite.php | 41 - .../core/Forum/models/ForumHelpers.php | 282 ---- .../core/Forum/models/ForumIssue.php | 98 -- .../core/Forum/models/ForumLike.php | 99 -- .../core/Forum/models/ForumPerm.php | 215 --- .../core/Forum/models/ForumVisit.php | 162 --- public/plugins_packages/core/Forum/plugin.manifest | 17 - .../core/Forum/views/admin/childs.php | 31 - .../core/Forum/views/admin/index.php | 31 - .../core/Forum/views/area/_add_area_form.php | 15 - .../core/Forum/views/area/_edit_area_form.php | 7 - .../core/Forum/views/area/_edit_category_form.php | 8 - .../core/Forum/views/area/_js_templates.php | 45 - .../plugins_packages/core/Forum/views/area/add.php | 87 -- .../core/Forum/views/index/_abo_link.php | 23 - .../core/Forum/views/index/_areas.php | 102 -- .../core/Forum/views/index/_breadcrumb.php | 16 - .../core/Forum/views/index/_favorite.php | 12 - .../core/Forum/views/index/_js_templates.php | 16 - .../core/Forum/views/index/_last_post.php | 21 - .../core/Forum/views/index/_like.php | 47 - .../core/Forum/views/index/_mail_notification.php | 31 - .../core/Forum/views/index/_new_category.php | 19 - .../core/Forum/views/index/_new_entry.php | 67 - .../core/Forum/views/index/_post.php | 270 ---- .../core/Forum/views/index/_postings.php | 15 - .../core/Forum/views/index/_preview.php | 11 - .../core/Forum/views/index/_smiley_favorites.php | 27 - .../core/Forum/views/index/_threads.php | 196 --- .../core/Forum/views/index/index.php | 265 ---- .../plugins_packages/core/Forum/views/messages.php | 7 - .../core/NewsWidget/NewsWidget.php | 79 -- .../core/NewsWidget/plugin.manifest | 6 - .../core/QuickSelection/QuickSelection.php | 99 -- .../core/QuickSelection/js/QuickSelection.js | 8 - .../core/QuickSelection/plugin.manifest | 6 - .../core/QuickSelection/templates/edit.php | 22 - .../core/QuickSelection/templates/list.php | 28 - .../core/ScheduleWidget/CalendarWidgetView.php | 52 - .../core/ScheduleWidget/ScheduleWidget.php | 49 - .../core/ScheduleWidget/plugin.manifest | 6 - .../core/TerminWidget/TerminWidget.php | 36 - .../core/TerminWidget/plugin.manifest | 6 - resources/assets/javascripts/init.js | 4 + resources/assets/javascripts/lib/activityfeed.js | 127 ++ resources/assets/javascripts/lib/forum.js | 38 +- .../assets/javascripts/lib/quick_selection.js | 7 + .../assets/stylesheets/less/activityfeed.less | 195 +++ resources/assets/stylesheets/studip.less | 1 + templates/mail/forum_notification.php | 31 + templates/online/user.php | 2 +- templates/start/_jstemplates.php | 100 ++ templates/start/activityfeed.php | 19 + templates/start/contents.php | 28 + templates/start/quickselection.php | 28 + tests/jsonapi/ForumEntriesShowTest.php | 1 - 148 files changed, 7101 insertions(+), 7131 deletions(-) create mode 100644 app/controllers/activityfeed.php create mode 100644 app/controllers/course/forum/admin.php create mode 100644 app/controllers/course/forum/area.php create mode 100644 app/controllers/course/forum/forum_controller.php create mode 100644 app/controllers/course/forum/index.php create mode 100644 app/controllers/course/messenger.php create mode 100644 app/controllers/quickselection.php create mode 100644 app/views/activityfeed/configuration.php create mode 100644 app/views/course/forum/admin/childs.php create mode 100644 app/views/course/forum/admin/index.php create mode 100644 app/views/course/forum/area/_add_area_form.php create mode 100644 app/views/course/forum/area/_edit_area_form.php create mode 100644 app/views/course/forum/area/_edit_category_form.php create mode 100644 app/views/course/forum/area/_js_templates.php create mode 100644 app/views/course/forum/area/add.php create mode 100644 app/views/course/forum/index/_abo_link.php create mode 100644 app/views/course/forum/index/_areas.php create mode 100644 app/views/course/forum/index/_breadcrumb.php create mode 100644 app/views/course/forum/index/_favorite.php create mode 100644 app/views/course/forum/index/_js_templates.php create mode 100644 app/views/course/forum/index/_last_post.php create mode 100644 app/views/course/forum/index/_like.php create mode 100644 app/views/course/forum/index/_new_category.php create mode 100644 app/views/course/forum/index/_new_entry.php create mode 100644 app/views/course/forum/index/_post.php create mode 100644 app/views/course/forum/index/_postings.php create mode 100644 app/views/course/forum/index/_preview.php create mode 100644 app/views/course/forum/index/_smiley_favorites.php create mode 100644 app/views/course/forum/index/_threads.php create mode 100644 app/views/course/forum/index/index.php create mode 100644 app/views/course/forum/messages.php create mode 100644 app/views/quickselection/configuration.php create mode 100644 db/migrations/5.2.6_update_core_plugins.php create mode 100644 lib/calendar/CalendarWidgetView.php create mode 100644 lib/classes/ForumAbo.php create mode 100644 lib/classes/ForumActivity.php create mode 100644 lib/classes/ForumBulkMail.php create mode 100644 lib/classes/ForumEntry.php create mode 100644 lib/classes/ForumFavorite.php create mode 100644 lib/classes/ForumHelpers.php create mode 100644 lib/classes/ForumIssue.php create mode 100644 lib/classes/ForumLike.php create mode 100644 lib/classes/ForumPerm.php create mode 100644 lib/classes/ForumVisit.php create mode 100644 lib/models/ForumCat.php create mode 100644 lib/modules/ActivityFeed.php create mode 100644 lib/modules/Blubber.class.php create mode 100644 lib/modules/ContentsWidget.php create mode 100644 lib/modules/CoreForum.class.php create mode 100644 lib/modules/EvaluationsWidget.php create mode 100644 lib/modules/NewsWidget.php create mode 100644 lib/modules/QuickSelection.php create mode 100644 lib/modules/ScheduleWidget.php create mode 100644 lib/modules/TerminWidget.php delete mode 100644 public/plugins_packages/core/ActivityFeed/ActivityFeed.php delete mode 100644 public/plugins_packages/core/ActivityFeed/css/style.less delete mode 100644 public/plugins_packages/core/ActivityFeed/javascript/activityfeed.js delete mode 100644 public/plugins_packages/core/ActivityFeed/plugin.manifest delete mode 100644 public/plugins_packages/core/ActivityFeed/templates/_jstemplates.php delete mode 100644 public/plugins_packages/core/ActivityFeed/templates/activity_feed.php delete mode 100644 public/plugins_packages/core/ActivityFeed/templates/edit.php delete mode 100644 public/plugins_packages/core/Blubber/Blubber.class.php delete mode 100644 public/plugins_packages/core/Blubber/controllers/messenger.php delete mode 100644 public/plugins_packages/core/Blubber/plugin.manifest delete mode 100644 public/plugins_packages/core/Blubber/views/messenger/course.php delete mode 100644 public/plugins_packages/core/ContentsWidget/ContentsWidget.php delete mode 100644 public/plugins_packages/core/ContentsWidget/plugin.manifest delete mode 100644 public/plugins_packages/core/ContentsWidget/templates/index.php delete mode 100644 public/plugins_packages/core/EvaluationsWidget/EvaluationsWidget.php delete mode 100644 public/plugins_packages/core/EvaluationsWidget/plugin.manifest delete mode 100644 public/plugins_packages/core/Forum/CoreForum.class.php delete mode 100644 public/plugins_packages/core/Forum/controllers/admin.php delete mode 100644 public/plugins_packages/core/Forum/controllers/area.php delete mode 100644 public/plugins_packages/core/Forum/controllers/forum_controller.php delete mode 100644 public/plugins_packages/core/Forum/controllers/index.php delete mode 100644 public/plugins_packages/core/Forum/models/ForumAbo.php delete mode 100644 public/plugins_packages/core/Forum/models/ForumActivity.php delete mode 100644 public/plugins_packages/core/Forum/models/ForumBulkMail.php delete mode 100644 public/plugins_packages/core/Forum/models/ForumCat.php delete mode 100644 public/plugins_packages/core/Forum/models/ForumEntry.php delete mode 100644 public/plugins_packages/core/Forum/models/ForumFavorite.php delete mode 100644 public/plugins_packages/core/Forum/models/ForumHelpers.php delete mode 100644 public/plugins_packages/core/Forum/models/ForumIssue.php delete mode 100644 public/plugins_packages/core/Forum/models/ForumLike.php delete mode 100644 public/plugins_packages/core/Forum/models/ForumPerm.php delete mode 100644 public/plugins_packages/core/Forum/models/ForumVisit.php delete mode 100644 public/plugins_packages/core/Forum/plugin.manifest delete mode 100644 public/plugins_packages/core/Forum/views/admin/childs.php delete mode 100644 public/plugins_packages/core/Forum/views/admin/index.php delete mode 100644 public/plugins_packages/core/Forum/views/area/_add_area_form.php delete mode 100644 public/plugins_packages/core/Forum/views/area/_edit_area_form.php delete mode 100644 public/plugins_packages/core/Forum/views/area/_edit_category_form.php delete mode 100644 public/plugins_packages/core/Forum/views/area/_js_templates.php delete mode 100644 public/plugins_packages/core/Forum/views/area/add.php delete mode 100644 public/plugins_packages/core/Forum/views/index/_abo_link.php delete mode 100644 public/plugins_packages/core/Forum/views/index/_areas.php delete mode 100644 public/plugins_packages/core/Forum/views/index/_breadcrumb.php delete mode 100644 public/plugins_packages/core/Forum/views/index/_favorite.php delete mode 100644 public/plugins_packages/core/Forum/views/index/_js_templates.php delete mode 100644 public/plugins_packages/core/Forum/views/index/_last_post.php delete mode 100644 public/plugins_packages/core/Forum/views/index/_like.php delete mode 100644 public/plugins_packages/core/Forum/views/index/_mail_notification.php delete mode 100644 public/plugins_packages/core/Forum/views/index/_new_category.php delete mode 100644 public/plugins_packages/core/Forum/views/index/_new_entry.php delete mode 100644 public/plugins_packages/core/Forum/views/index/_post.php delete mode 100644 public/plugins_packages/core/Forum/views/index/_postings.php delete mode 100644 public/plugins_packages/core/Forum/views/index/_preview.php delete mode 100644 public/plugins_packages/core/Forum/views/index/_smiley_favorites.php delete mode 100644 public/plugins_packages/core/Forum/views/index/_threads.php delete mode 100644 public/plugins_packages/core/Forum/views/index/index.php delete mode 100644 public/plugins_packages/core/Forum/views/messages.php delete mode 100644 public/plugins_packages/core/NewsWidget/NewsWidget.php delete mode 100644 public/plugins_packages/core/NewsWidget/plugin.manifest delete mode 100644 public/plugins_packages/core/QuickSelection/QuickSelection.php delete mode 100644 public/plugins_packages/core/QuickSelection/js/QuickSelection.js delete mode 100644 public/plugins_packages/core/QuickSelection/plugin.manifest delete mode 100644 public/plugins_packages/core/QuickSelection/templates/edit.php delete mode 100644 public/plugins_packages/core/QuickSelection/templates/list.php delete mode 100644 public/plugins_packages/core/ScheduleWidget/CalendarWidgetView.php delete mode 100644 public/plugins_packages/core/ScheduleWidget/ScheduleWidget.php delete mode 100644 public/plugins_packages/core/ScheduleWidget/plugin.manifest delete mode 100644 public/plugins_packages/core/TerminWidget/TerminWidget.php delete mode 100644 public/plugins_packages/core/TerminWidget/plugin.manifest create mode 100644 resources/assets/javascripts/lib/activityfeed.js create mode 100644 resources/assets/javascripts/lib/quick_selection.js create mode 100644 resources/assets/stylesheets/less/activityfeed.less create mode 100644 templates/mail/forum_notification.php create mode 100644 templates/start/_jstemplates.php create mode 100644 templates/start/activityfeed.php create mode 100644 templates/start/contents.php create mode 100644 templates/start/quickselection.php diff --git a/.gitignore b/.gitignore index 8ff7023..3791bd1 100644 --- a/.gitignore +++ b/.gitignore @@ -34,7 +34,6 @@ public/pictures/smile/*.jpg public/pictures/smile/*.png public/pictures/user/[0-9a-f]*.png public/plugins_packages/* -!public/plugins_packages/core tests/_log tests/_helpers/CodeGuy.php diff --git a/app/controllers/activityfeed.php b/app/controllers/activityfeed.php new file mode 100644 index 0000000..1ea00a0 --- /dev/null +++ b/app/controllers/activityfeed.php @@ -0,0 +1,95 @@ +ACTIVITY_FEED === NULL) { + Config::get()->create('ACTIVITY_FEED', [ + 'range' => 'user', + 'type' => 'array', + 'description' => 'Einstellungen des Activity-Widgets'] + ); + } + + $provider = Request::getArray('provider'); + + WidgetHelper::addWidgetUserConfig($GLOBALS['user']->id, 'ACTIVITY_FEED', $provider); + + $this->response->add_header('X-Dialog-Close', 1); + $this->response->add_header('X-Dialog-Execute', 'STUDIP.ActivityFeed.updateFilter'); + + $this->render_json($provider); + } + + /** + * return a list for all providers for every context + * + * @return array + */ + private function getAllModules() + { + $modules = []; + + $modules['system'] = [ + 'news' => _('Ankündigungen'), + 'blubber' => _('Blubber') + ]; + + $modules[Context::COURSE] = [ + 'forum' => _('Forum'), + 'participants' => _('Teilnehmende'), + 'documents' => _('Dateien'), + 'wiki' => _('Wiki'), + 'schedule' => _('Ablaufplan'), + 'news' => _('Ankündigungen'), + 'blubber' => _('Blubber'), + 'courseware' => _('Courseware') + ]; + + $modules[Context::INSTITUTE] = $modules[Context::COURSE]; + unset($modules[Context::INSTITUTE]['participants']); + unset($modules[Context::INSTITUTE]['schedule']); + + $standard_plugins = PluginManager::getInstance()->getPlugins("StandardPlugin"); + foreach ($standard_plugins as $plugin) { + if ($plugin instanceof ActivityProvider) { + $modules[Context::COURSE][$plugin->getPluginName()] = $plugin->getPluginName(); + $modules[Context::INSTITUTE][$plugin->getPluginName()] = $plugin->getPluginName(); + } + } + + $modules[Context::USER] = [ + 'message' => _('Nachrichten'), + 'news' => _('Ankündigungen'), + 'blubber' => _('Blubber'), + ]; + + $homepage_plugins = PluginEngine::getPlugins('HomepagePlugin'); + foreach ($homepage_plugins as $plugin) { + if ($plugin->isActivated($GLOBALS['user']->id, 'user')) { + if ($plugin instanceof ActivityProvider) { + $modules[Context::USER][] = $plugin; + } + } + } + + return $modules; + } + + public function configuration_action() + { + $this->config = WidgetHelper::getWidgetUserConfig($GLOBALS['user']->id, 'ACTIVITY_FEED'); + $this->modules = $this->getAllModules(); + $this->context_translations = [ + Context::COURSE => _('Veranstaltungen'), + Context::INSTITUTE => _('Einrichtungen'), + Context::USER => _('Persönlich'), + 'system' => _('Global') + ]; + + PageLayout::setTitle(_('Aktivitäten konfigurieren')); + } +} diff --git a/app/controllers/course/forum/admin.php b/app/controllers/course/forum/admin.php new file mode 100644 index 0000000..a7fff71 --- /dev/null +++ b/app/controllers/course/forum/admin.php @@ -0,0 +1,126 @@ + + * + * 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 = []; + // iterate over all categories and add the belonging areas to them + foreach ($categories = 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 + $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(); + } + + 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); + 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 new file mode 100644 index 0000000..a93860a --- /dev/null +++ b/app/controllers/course/forum/area.php @@ -0,0 +1,80 @@ + + * + * 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); + + if (Request::isAjax()) { + ForumEntry::update($area_id, Request::get('name'), Request::get('content')); + $this->render_json(['content' => ForumEntry::killFormat(ForumEntry::killEdit(Request::get('content')))]); + } else { + ForumEntry::update($area_id, Request::get('name'), Request::get('content')); + $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/forum_controller.php b/app/controllers/course/forum/forum_controller.php new file mode 100644 index 0000000..1812be9 --- /dev/null +++ b/app/controllers/course/forum/forum_controller.php @@ -0,0 +1,45 @@ +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(); + } +} diff --git a/app/controllers/course/forum/index.php b/app/controllers/course/forum/index.php new file mode 100644 index 0000000..c3b665e --- /dev/null +++ b/app/controllers/course/forum/index.php @@ -0,0 +1,835 @@ + + * + * 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 = []; + // iterate over all categories and add the belonging areas to them + foreach ($categories = 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 + $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) + { + $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']; + $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) + { + $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; + + if (empty($this->postings)) { + $this->no_entries = true; + } + + $this->render_action('index'); + } + + /** + * show the current users favorized entries + * + * @param int $page show entries on submitted page + */ + function favorites_action($page = null) + { + $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; + + if (empty($this->postings)) { + $this->no_entries = true; + } + + // 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('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; + + // 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']; + $this->highlight = $list['highlight']; + + 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 * * * */ + /* * * * * * * * * * * * * * * * * * * * * * * * * */ + + /** + * this action renders a preview of the submitted text + */ + function preview_action() { + if (Request::isXhr()) { + $this->set_content_type('text/html; charset=UTF-8'); + $this->render_text(formatReady(transformBeforeSave(Request::get('posting')))); + } else { + $this->render_text( + ForumEntry::getContentAsHtml( + transformBeforeSave(Request::get('posting')) + ) + ); + } + } + + /** + * 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); + + if (ForumPerm::hasEditPerms($topic_id) || ForumPerm::check('remove_entry', $this->getId(), $topic_id)) { + $path = ForumEntry::getPathToPosting($topic_id); + $topic = array_pop($path); + $parent = array_pop($path); + + if ($topic_id != $this->getId()) { + // only delete directly if passed by ajax, otherwise ask for confirmation + if (Request::isXhr() || Request::isPost() || Request::get('approve_delete')) { + CSRFProtection::verifyUnsafeRequest(); + ForumEntry::delete($topic_id); + $this->flash['messages'] = ['success' => sprintf(_('Der Eintrag %s wurde gelöscht!'), $topic['name'])]; + } else { + $this->flash['messages'] = ['info_html' => + sprintf(_('Sind sie sicher dass Sie den Eintrag %s löschen möchten?'), $topic['name']) + . '
'. \Studip\LinkButton::createAccept(_('Ja'), $this->url_for('course/forum/index/delete_entry/'. $topic_id .'?approve_delete=1')) + . \Studip\LinkButton::createCancel(_('Nein'), $this->url_for('course/forum/index/index/'. ForumEntry::getParentTopicId($topic_id) .'/'. $page)) + ]; + } + } else { + $this->flash['messages'] = ['success' => _('Sie können nicht die gesamte Veranstaltung löschen!')]; + } + } + + if (Request::isXhr()) { + $this->render_template('course/forum/messages'); + $this->flash['messages'] = null; + } else { + $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) + { + 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) + { + 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 { + $this->flash['messages'] = ['success' => $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 { + $this->flash['messages'] = ['success' => $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); + } +} diff --git a/app/controllers/course/messenger.php b/app/controllers/course/messenger.php new file mode 100644 index 0000000..0bfd3f5 --- /dev/null +++ b/app/controllers/course/messenger.php @@ -0,0 +1,78 @@ +threads = BlubberThread::findByContext(Context::get()->id, true, Context::getType()); + + if (!$thread_id) { + $thread_id = $GLOBALS['user']->cfg->BLUBBER_DEFAULT_THREAD; + } + if ($thread_id) { + foreach ($this->threads as $thread) { + if ($thread->getId() === $thread_id) { + $this->thread = $thread; + break; + } + } + } + if (!$this->thread || Request::get("thread") === "new") { + $this->thread = array_pop(array_reverse($this->threads)); + } + $this->thread->markAsRead(); + + $this->thread_data = $this->thread->getJSONData(); + + if (!Avatar::getAvatar($GLOBALS['user']->id)->is_customized() && !$_SESSION['already_asked_for_avatar']) { + $_SESSION['already_asked_for_avatar'] = true; + PageLayout::postInfo(sprintf(_("Wollen Sie ein Avatar-Bild nutzen? %sLaden Sie jetzt ein Bild hoch%s."), 'id).'" data-dialog>', '')); + } + $this->buildSidebar(); + + if (Request::isDialog()) { + PageLayout::setTitle($this->thread->getName()); + $this->render_template('blubber/dialog'); + } else { + $this->render_template('blubber/index', $this->layout); + } + } + + protected function buildSidebar() + { + $sidebar = Sidebar::Get(); + $search = new SearchWidget("#"); + $search->addNeedle( + _("Suche nach ..."), + "search", + true, + null, + null, + null, + [] + ); + $sidebar->addWidget($search, "blubbersearch"); + + $threads_widget = new BlubberThreadsWidget(); + foreach ($this->threads as $thread) { + $threads_widget->addThread($thread); + } + if ($this->thread) { + $threads_widget->setActive($this->thread->getId()); + } + $sidebar->addWidget($threads_widget, "threads"); + } +} diff --git a/app/controllers/news.php b/app/controllers/news.php index 4244b88..acb4399 100644 --- a/app/controllers/news.php +++ b/app/controllers/news.php @@ -165,6 +165,20 @@ class NewsController extends StudipController $this->render_nothing(); } + public function visit_all_action() + { + $global_news = StudipNews::GetNewsByRange('studip', true); + + if ($GLOBALS['user']->id && $GLOBALS['user']->id !== 'nobody') { + foreach ($global_news as $news) { + object_add_view($news['news_id']); + object_set_visit($news['news_id'], 'news'); + } + } + + $this->render_nothing(); + } + /** * Builds news dialog for editing / adding news * diff --git a/app/controllers/quickselection.php b/app/controllers/quickselection.php new file mode 100644 index 0000000..88d24f0 --- /dev/null +++ b/app/controllers/quickselection.php @@ -0,0 +1,45 @@ +QUICK_SELECTION === null) { + Config::get()->create('QUICK_SELECTION', [ + 'range' => 'user', + 'type' => 'array', + 'description' => 'Einstellungen des QuickSelection-Widgets', + ]); + } + + $add_removes = Request::optionArray('add_removes'); + + // invert add_removes so that only unchecked values are stored into config + $names = []; + + $navigation = Navigation::getItem('/start'); + foreach ($navigation as $name => $nav) { + if (!in_array($name, $add_removes)) { + $names[$name] = 'deactivated'; + } + + } + + WidgetHelper::addWidgetUserConfig($GLOBALS['user']->id, 'QUICK_SELECTION', $names); + + $template = PluginEngine::getPlugin('QuickSelection')->getPortalTemplate(); + + $this->response->add_header('X-Dialog-Close', 1); + $this->response->add_header('X-Dialog-Execute', 'STUDIP.QuickSelection.update'); + + $this->render_template($template); + } + + public function configuration_action() + { + $this->links = Navigation::getItem('/start'); + $this->config = WidgetHelper::getWidgetUserConfig($GLOBALS['user']->id, 'QUICK_SELECTION'); + + PageLayout::setTitle(_('Schnellzugriff konfigurieren')); + } +} diff --git a/app/routes/Forum.php b/app/routes/Forum.php index 8d02727..d5fd341 100644 --- a/app/routes/Forum.php +++ b/app/routes/Forum.php @@ -10,14 +10,6 @@ namespace RESTAPI\Routes; */ class Forum extends \RESTAPI\RouteMap { - - public static function before() - { - require_once 'public/plugins_packages/core/Forum/models/ForumCat.php'; - require_once 'public/plugins_packages/core/Forum/models/ForumEntry.php'; - require_once 'public/plugins_packages/core/Forum/models/ForumPerm.php'; - } - /** * List all categories of a forum * diff --git a/app/views/activityfeed/configuration.php b/app/views/activityfeed/configuration.php new file mode 100644 index 0000000..945f109 --- /dev/null +++ b/app/views/activityfeed/configuration.php @@ -0,0 +1,23 @@ +
+
+

+ + $provider): ?> +
+ + $prv_name) : ?> + + +
+ + +
+ + +
+
+
diff --git a/app/views/course/forum/admin/childs.php b/app/views/course/forum/admin/childs.php new file mode 100644 index 0000000..3becc1f --- /dev/null +++ b/app/views/course/forum/admin/childs.php @@ -0,0 +1,31 @@ + + + \ No newline at end of file diff --git a/app/views/course/forum/admin/index.php b/app/views/course/forum/admin/index.php new file mode 100644 index 0000000..154e44c --- /dev/null +++ b/app/views/course/forum/admin/index.php @@ -0,0 +1,31 @@ +addPlainText( + _('Bedienungshinweise'), + _('Sie befinden sich hier in der Administrationsansicht des Forums. ' + . 'Mit den blauen Pfeilen können Sie einen oder mehrere Einträge auswählen, welche dann verschoben werden können. '), + Icon::create('info', 'info_alt') +); +Helpbar::get()->addPlainText( + '', + _('Sie sollten nicht mehr als 20 Einträge gleichzeitig auswählen, da das verschieben sonst sehr lange dauern kann.') +); +?> +
+ +
+ diff --git a/app/views/course/forum/area/_add_area_form.php b/app/views/course/forum/area/_add_area_form.php new file mode 100644 index 0000000..4dd6781 --- /dev/null +++ b/app/views/course/forum/area/_add_area_form.php @@ -0,0 +1,15 @@ + + + +
+ +
+ + + + url_for('course/forum/index/index#cat_'. $category_id)) ?> +
+ + 0 +
+ diff --git a/app/views/course/forum/area/_edit_area_form.php b/app/views/course/forum/area/_edit_area_form.php new file mode 100644 index 0000000..9919d69 --- /dev/null +++ b/app/views/course/forum/area/_edit_area_form.php @@ -0,0 +1,7 @@ +
+
+ + + + url_for('course/forum/index')) ?> +
diff --git a/app/views/course/forum/area/_edit_category_form.php b/app/views/course/forum/area/_edit_category_form.php new file mode 100644 index 0000000..054c8d6 --- /dev/null +++ b/app/views/course/forum/area/_edit_category_form.php @@ -0,0 +1,8 @@ +
+ + + "javascript:STUDIP.Forum.saveCategoryName('". $category_id ."'); return false;"]) ?> + url_for('course/forum/index/index#cat_'. $category_id), + ['onClick' => "STUDIP.Forum.cancelEditCategoryName('". $category_id ."'); return false;"]) ?> +
diff --git a/app/views/course/forum/area/_js_templates.php b/app/views/course/forum/area/_js_templates.php new file mode 100644 index 0000000..975f42a --- /dev/null +++ b/app/views/course/forum/area/_js_templates.php @@ -0,0 +1,45 @@ + + + + + diff --git a/app/views/course/forum/area/add.php b/app/views/course/forum/area/add.php new file mode 100644 index 0000000..01cf3dc --- /dev/null +++ b/app/views/course/forum/area/add.php @@ -0,0 +1,87 @@ +> + + + 'handle js']) ?> + + + "> + = $visitdate && $entry['user_id'] !== $GLOBALS['user']->id): ?> + asImg([ + 'title' => _('Dieser Eintrag ist neu!'), + 'style' => 'margin-bottom: 15px', + ]) ?> + + + 0 ? Icon::ROLE_ATTENTION : Icon::ROLE_INFO)->asImg([ + 'title' => htmlReady(ForumHelpers::getVisitText($num_postings, $entry['topic_id'], $constraint['depth'])), + 'style' => 'margin-bottom: 15px;', + ]) ?> + + + + +
+ + > + "> + + +
+ + +
+
+ + + + +
+ render_partial('course/forum/area/_edit_area_form', compact('entry')) ?> +
+
+ +
+ + + + + + + + render_partial('course/forum/index/_last_post.php', compact('entry')) ?> + + + + addLink( + $controller->url_for("course/forum/index/index/{$entry['last_posting']['topic_id']}#{$entry['last_posting']['topic_id']}"), + _('Zur letzten Antwort'), + Icon::create('forum'), + is_array($entry['last_posting']) ? ['class' => 'hidden-small-up'] : ['disabled' => ''] + )->condition(ForumPerm::has('edit_area', $seminar_id) && $issue_id = ForumIssue::getIssueIdForThread($entry['topic_id'])) + ->addLink( + URLHelper::getURL("dispatch.php/course/topics/edit/{$issue_id}"), + _('Zum Ablaufplan'), + Icon::create('info-circle', Icon::ROLE_STATUS_RED), + ['title' => _('Dieser Bereich ist einem Thema zugeordnet und kann hier nicht editiert werden. Die Angaben können im Ablaufplan angepasst werden.')] + )->condition(ForumPerm::has('edit_area', $seminar_id) && !$issue_id) + ->addLink( + $controller->url_for('course/forum/index', ['edit_area' => $entry['topic_id']]), + _('Name/Beschreibung des Bereichs ändern'), + Icon::create('edit'), + [ + 'class' => 'edit-area', + 'onclick' => "STUDIP.Forum.editArea('{$entry['topic_id']}');return false;", + ] + )->condition(ForumPerm::has('remove_area', $seminar_id)) + ->addLink( + $controller->url_for("course/forum/index/delete_entry/{$entry['topic_id']}"), + _('Bereich mitsamt allen Einträgen löschen!'), + Icon::create('trash'), + [ + 'class' => 'delete-area', + 'onclick' => "STUDIP.Forum.deleteArea(this, '{$entry['topic_id']}'); return false;", + ] + ) ?> + + + diff --git a/app/views/course/forum/index/_abo_link.php b/app/views/course/forum/index/_abo_link.php new file mode 100644 index 0000000..ccd689a --- /dev/null +++ b/app/views/course/forum/index/_abo_link.php @@ -0,0 +1,23 @@ +url_for('course/forum/index/' + . (ForumAbo::has($constraint['topic_id']) ? 'remove_' : '') + . 'abo/'. $constraint['topic_id']); +?> + + + + + + _('Wenn sie diesen Bereich abonnieren, erhalten Sie eine ' + . 'Stud.IP-interne Nachricht sobald in diesem Bereich ' + . 'ein neuer Beitrag erstellt wurde.'), + 'onClick' => $js]) ?> + + $js]) ?> + diff --git a/app/views/course/forum/index/_areas.php b/app/views/course/forum/index/_areas.php new file mode 100644 index 0000000..20aa345 --- /dev/null +++ b/app/views/course/forum/index/_areas.php @@ -0,0 +1,102 @@ + +
+ $entries) : ?> + + + + + + + + + + + + + + + + + + + + + + + + + + render_partial('course/forum/area/add', compact('entry')) ?> + + + + render_partial('course/forum/area/_add_area_form') ?> + + + + + + + + + + + + + + + + + + + + + +
+ + asImg() ?> + asImg() ?> + + + + + + + + + + 'Name der Kategorie ändern'])->asImg() ?> + + + + + + 'Kategorie entfernen'])->asImg() ?> + + + + + + + + + render_partial('course/forum/area/_edit_category_form', compact('category_id', 'categories')) ?> + + + + +
+ +
+ + + asImg(["id" => 'tutorAddArea']) ?> + +
+ +
+ +render_partial('course/forum/area/_js_templates') ?> diff --git a/app/views/course/forum/index/_breadcrumb.php b/app/views/course/forum/index/_breadcrumb.php new file mode 100644 index 0000000..0b3a9b4 --- /dev/null +++ b/app/views/course/forum/index/_breadcrumb.php @@ -0,0 +1,16 @@ + +
+ + + + + + + 1) :?>/ + + + + + +
+ diff --git a/app/views/course/forum/index/_favorite.php b/app/views/course/forum/index/_favorite.php new file mode 100644 index 0000000..63da116 --- /dev/null +++ b/app/views/course/forum/index/_favorite.php @@ -0,0 +1,12 @@ + + + + + + _('Beitrag merken')])->asImg() ?> + + + + _('Beitrag nicht mehr merken')])->asImg() ?> + + diff --git a/app/views/course/forum/index/_js_templates.php b/app/views/course/forum/index/_js_templates.php new file mode 100644 index 0000000..3ee18ff --- /dev/null +++ b/app/views/course/forum/index/_js_templates.php @@ -0,0 +1,16 @@ + diff --git a/app/views/course/forum/index/_last_post.php b/app/views/course/forum/index/_last_post.php new file mode 100644 index 0000000..50c5eb2 --- /dev/null +++ b/app/views/course/forum/index/_last_post.php @@ -0,0 +1,21 @@ + + + + + + id || $GLOBALS['perm']->have_perm('root')): ?> + + getFullname() : $entry['last_posting']['user_fullname']) ?> + + +
+ + + "> + asImg([ + 'title' => _('Direkt zum Beitrag...'), + ]) ?> + + + + diff --git a/app/views/course/forum/index/_like.php b/app/views/course/forum/index/_like.php new file mode 100644 index 0000000..eac19a2 --- /dev/null +++ b/app/views/course/forum/index/_like.php @@ -0,0 +1,47 @@ + + + + + id, $likes) !== false) { + if (sizeof($likes) > 1) { + $text = '' . sprintf(_('Dir und %s weiteren gefällt das.'), (sizeof($likes) - 1)); + $text .= ''; + foreach ($likes as $user_id) { + if ($user_id != $GLOBALS['user']->id) { + $text .= htmlReady(get_fullname($user_id)) .'
'; + } + } + $text .= '
'; + } else { + $text = _('Dir gefällt das.'); + } + } else { + $text = '' . sprintf(_('%s gefällt das.'), sizeof($likes)); + $text .= ''; + foreach ($likes as $user_id) { + $text .= htmlReady(get_fullname($user_id)) .'
'; + } + $text .= '
'; + } + + $text .= '
'; +endif ?> + + + +id, $likes)) : ?> + + + + + + + + diff --git a/app/views/course/forum/index/_new_category.php b/app/views/course/forum/index/_new_category.php new file mode 100644 index 0000000..7048a6c --- /dev/null +++ b/app/views/course/forum/index/_new_category.php @@ -0,0 +1,19 @@ + + +
+ +
+ + + +
+ +
+ +
+
+
+ diff --git a/app/views/course/forum/index/_new_entry.php b/app/views/course/forum/index/_new_entry.php new file mode 100644 index 0000000..7c2a9ab --- /dev/null +++ b/app/views/course/forum/index/_new_entry.php @@ -0,0 +1,67 @@ +flash['new_entry_title'] */ ?> + diff --git a/app/views/course/forum/index/_post.php b/app/views/course/forum/index/_post.php new file mode 100644 index 0000000..2e78b11 --- /dev/null +++ b/app/views/course/forum/index/_post.php @@ -0,0 +1,270 @@ + += $visitdate) || !(isset($visitdate))) ?> + + + + + ForumPerm::hasEditPerms($post['topic_id']), + 'edit_closed' => ForumPerm::has('edit_closed', $constraint['seminar_id']), + 'remove_entry' => ForumPerm::has('remove_entry', $constraint['seminar_id']), +] ?> + + + + +
+ + +
+ data-topic-id=""> +
+
+ +
+
+ +
+ + + + + getImageTag(Avatar::SMALL, + ['title' => _('Stud.IP')]) ?> + , + + + + getImageTag(Avatar::SMALL, + ['title' => get_username($post['user_id'])]) ?> + + + , + + , + + + + + +
+
+ + + > + + + + + + + _('Dieses Thema wurde geschlossen. Sie können daher nicht auf diesen Beitrag antworten.')])->asImg(16) ?> + + + + + + + + + + + > ', ForumEntry::getFlatPathToPosting($post['topic_id']))), $highlight) ?> + + + + + + + + + +
+ + +
+ > + + + + > + + render() ?> + +
+ + +
+
+ + > + + "STUDIP.Forum.saveEntry('". $post['topic_id'] ."'); return false;"]) ?> + + link_for('course/forum/index/index/'. $post['topic_id'] .'#'. $post['topic_id']), + ['onClick' => "STUDIP.Forum.cancelEditEntry('". $post['topic_id'] ."'); return false;"]) ?> + + + + + > + + + + + url_for('course/forum/index/index/' . $post['topic_id'] .'?cite=1'), [ + 'onClick' => "javascript:STUDIP.Forum.citeEntry('". $post['topic_id'] ."'); return false;", + 'class' => !$perms['edit_closed'] ? 'hideWhenClosed' : '', + 'style' => !$can_edit_closed ? 'display: none' : '' + ]) ?> + + + + url_for('course/forum/index/index/' + . $post['topic_id'] .'/?edit_posting=' . $post['topic_id']), [ + 'onClick' => "STUDIP.Forum.editEntry('". $post['topic_id'] ."'); return false;", + 'class' => !$perms['edit_closed'] ? 'hideWhenClosed' : '', + 'style' => !$can_edit_closed ? 'display: none' : '' + ]) ?> + + + + > + url_for('course/forum/index/delete_entry/' . $post['topic_id']) ?> + url_for('course/forum/index/delete_entry/' . + $post['topic_id'] . '?approve_delete=1§ion=' . $section .'&page=' . ForumHelpers::getPage()) ?> + + + "STUDIP.Forum.showDialog('$confirmText', '$confirmLinkApproved'); return false;"]) ?> + + + "STUDIP.Forum.showDialog('$confirmText', '$confirmLinkApproved'); return false;"]) ?> + + + + + 'js']) ?> + + +
+
+ +
+ + + > +
+
+ WYSIWYG): ?> + render_partial('course/forum/index/_smiley_favorites', ['textarea_id' => $post['topic_id']]) ?> + +
+
+
+ + + + > +
+ +
+ + id || $GLOBALS['perm']->have_perm('root')): ?> +
+ + + getImageTag(Avatar::MEDIUM, + ['title' => get_username($post['user_id'])]) ?> + +
+ + + + asImg() ?> + + + + + + + + + _('Online')]) ?> + + _('Abwesend')]) ?> + + _('Offline')]) ?> + + + + + + + + +
+ +
+ get_studip_perm($constraint['seminar_id'], $post['user_id']))?> +
+ +
+ Beiträge: +
+ + +
+ + +
+ +
+ +
+ +
+ + + render_partial('course/forum/index/_favorite', ['topic_id' => $post['topic_id'], 'favorite' => $post['fav']]) ?> + + + + + _('Link zu diesem Beitrag')])->asImg() ?> + +
+ + + +
+ + link_for('course/forum/index/index/' . $post['topic_id'] .'#'. $post['topic_id']), + $post['user_id']) as $applet_data) : ?> +
+ +
+ +
+ + + + _("Dieser Beitrag ist seit Ihrem letzten Besuch hinzugekommen.")])->asImg(16) ?> + + +
+ +
+
+
+ +render_partial('course/forum/index/_preview', ['preview_id' => 'preview_' . $post['topic_id']]) ?> diff --git a/app/views/course/forum/index/_postings.php b/app/views/course/forum/index/_postings.php new file mode 100644 index 0000000..1888ae0 --- /dev/null +++ b/app/views/course/forum/index/_postings.php @@ -0,0 +1,15 @@ +
+ +render_partial('course/forum/index/_post', compact('post', 'visitdate', 'section')); + + $posting_num++; +endforeach +?> + +
diff --git a/app/views/course/forum/index/_preview.php b/app/views/course/forum/index/_preview.php new file mode 100644 index 0000000..e39f621 --- /dev/null +++ b/app/views/course/forum/index/_preview.php @@ -0,0 +1,11 @@ + \ No newline at end of file diff --git a/app/views/course/forum/index/_smiley_favorites.php b/app/views/course/forum/index/_smiley_favorites.php new file mode 100644 index 0000000..fbe48b4 --- /dev/null +++ b/app/views/course/forum/index/_smiley_favorites.php @@ -0,0 +1,27 @@ +id); +?> +
+ + + | + " target="new"> +
+ get()) ?> + + + + + id != 'nobody') : ?> + +
+
+
+ + + +
+ +
+
diff --git a/app/views/course/forum/index/_threads.php b/app/views/course/forum/index/_threads.php new file mode 100644 index 0000000..97f0c4c --- /dev/null +++ b/app/views/course/forum/index/_threads.php @@ -0,0 +1,196 @@ +
+ +
+
+
+
+
+ + +
+ + $entries) : ?> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ "> + = $visitdate && $entry['user_id'] != $GLOBALS['user']->id): ?> + asImg([ + 'title' => _('Dieser Eintrag ist neu!'), + ]) ?> + + + 0 ? Icon::ROLE_ATTENTION : Icon::ROLE_INFO)->asImg([ + 'title' => htmlReady(ForumHelpers::getVisitText($num_postings, $entry['topic_id'])), + ]) ?> + + +
+ + asImg([ + 'title' => _('Dieses Thema ist geschlossen, es können keine neuen Beiträge erstellt werden.'), + 'id' => "img-locked-{$entry['topic_id']}", + 'style' => $entry['closed'] ? '' : 'display: none', + ]) ?> + + asImg([ + 'title' => _('Dieses Thema wurde hervorgehoben.'), + 'id' => "img-sticky-{$entry['topic_id']}", + 'style' => $entry['sticky'] ? '' : 'display: none', + ]) ?> +
+
+
+ + + + + + + + + id || $GLOBALS['perm']->have_perm('root')): ?> + + getFullname() : $entry['author']) ?> + + + +
+ + +
+
+ + + render_partial('course/forum/index/_last_post.php', compact('entry')) ?> + + addLink( + $controller->url_for("course/forum/index/index/{$entry['last_posting']['topic_id']}#{$entry['last_posting']['topic_id']}"), + _('Zur letzten Antwort'), + Icon::create('forum'), + ['class' => 'hidden-small-up'] + ) + // Make thread sticky/unsticky + ->conditionAll(ForumPerm::has('make_sticky', $seminar_id) && $constraint['depth'] >= 1) + ->condition(!$entry['sticky']) + ->addLink( + $controller->url_for('course/forum/index/make_sticky', $entry['topic_id'], $constraint['topic_id'], 0), + _('Thema hervorheben'), + Icon::create('staple'), + ['id' => "stickyButton-{$entry['topic_id']}"] + ) + ->condition($entry['sticky']) + ->addLink( + $controller->url_for('course/forum/index/make_unsticky', $entry['topic_id'], $constraint['topic_id'], 0), + _('Hervorhebung aufheben'), + Icon::create('staple'), + ['id' => "stickyButton-{$entry['topic_id']}"] + ) + ->conditionAll(null) + // Move thread + ->condition(ForumPerm::has('move_thread', $seminar_id)) + ->addLink( + "javascript:STUDIP.Forum.moveThreadDialog('{$entry['topic_id']}');", + _('Dieses Thema verschieben'), + Icon::create('folder-full+move_right'), + ['class' => 'js'] + ) + // Open/close thread + ->conditionAll(ForumPerm::has('close_thread', $seminar_id) && $constraint['depth'] >= 1) + ->condition(!$entry['closed']) + ->addLink( + $controller->url_for('course/forum/index/close_thread', $entry['topic_id'], $constraint['topic_id'], ForumHelpers::getPage()), + _('Thema schließen'), + Icon::create('lock-locked'), + [ + 'id' => "closeButton-{$entry['topic_id']}", + 'onclick' => "STUDIP.Forum.closeThreadFromOverview('{$entry['topic_id']}', '{$constraint['topic_id']}', " . ForumHelpers::getPage() . "); return false;", + ] + ) + ->condition($entry['closed']) + ->addLink( + $controller->url_for('course/forum/index/open_thread', $entry['topic_id'], $constraint['topic_id'], ForumHelpers::getPage()), + _('Thema öffnen'), + Icon::create('lock-unlocked'), + [ + 'id' => "closeButton-{$entry['topic_id']}", + 'onclick' => "STUDIP.Forum.openThreadFromOverview('{$entry['topic_id']}', '{$constraint['topic_id']}', " . ForumHelpers::getPage() . "); return false;", + ] + ) + ->conditionAll(null) + // Delete thread + ->condition(ForumPerm::has('remove_thread', $seminar_id)) + ->addButton( + 'delete', + _('Dieses Thema löschen'), + Icon::create('trash'), + [ + 'formaction' => $controller->url_for("course/forum/index/delete_entry/{$entry['topic_id']}"), + 'data-confirm' => sprintf( + _('Sind sie sicher dass Sie den Eintrag %s löschen möchten?'), + htmlReady($entry['name']) + ) + ] + ) + ?> + + + + +
+ +
diff --git a/app/views/course/forum/index/index.php b/app/views/course/forum/index/index.php new file mode 100644 index 0000000..69f4b94 --- /dev/null +++ b/app/views/course/forum/index/index.php @@ -0,0 +1,265 @@ + + +render_partial('course/forum/index/_js_templates') ?> + + +
+url_for('course/forum/index/search?backend=search')); + $search->setId('tutorSearchInfobox'); + $search->addNeedle(_('Beiträge durchsuchen'), 'searchfor', true); + $search->addFilter(_('Titel'), 'search_title'); + $search->addFilter(_('Inhalt'), 'search_content'); + $search->addFilter(_('Autor/-in'), 'search_author'); + $sidebar->addWidget($search); +} + +$actions = new ActionsWidget(); + +if ($section == 'index') { + if (ForumPerm::has('abo', $seminar_id)) { + if (ForumAbo::has($constraint['topic_id'])) : + $abo_text = _('Nicht mehr abonnieren'); + $abo_url = $controller->url_for('course/forum/index/remove_abo/' . $constraint['topic_id']); + else : + switch ($constraint['depth']) { + case '0': $abo_text = _('Komplettes Forum abonnieren');break; + case '1': $abo_text = _('Diesen Bereich abonnieren');break; + default: $abo_text = _('Dieses Thema abonnieren');break; + } + + $abo_url = $controller->url_for('course/forum/index/abo/' . $constraint['topic_id']); + endif; + + $actions->addLink($abo_text, $abo_url, Icon::create('link-intern', 'clickable')); + } + + if (ForumPerm::has('close_thread', $seminar_id) && $constraint['depth'] > 1) { + if ($constraint['closed'] == 0) { + $close_url = $controller->url_for('course/forum/index/close_thread/' + . $constraint['topic_id'] .'/'. $constraint['topic_id'] .'/'. ForumHelpers::getPage()); + $close = new LinkElement( + _('Thema schließen'), + $close_url, + Icon::create('lock-locked', 'clickable'), + [ + 'onclick' => 'STUDIP.Forum.closeThreadFromThread(\'' . $constraint['topic_id'] . '\', ' + . ForumHelpers::getPage() . '); return false;', + 'class' => "closeButtons" + ] + ); + $actions->addElement($close, 'closethread'); + } else { + $open_url = $controller->url_for('course/forum/index/open_thread/' + . $constraint['topic_id'] .'/'. $constraint['topic_id'] .'/'. ForumHelpers::getPage()); + $open = new LinkElement( + _('Thema öffnen'), + $open_url, + Icon::create('lock-unlocked', 'clickable'), + [ + 'onclick' => 'STUDIP.Forum.openThreadFromThread(\'' . $constraint['topic_id'] . '\', ' + . ForumHelpers::getPage() . '); return false;', + 'class' => "closeButtons" + ] + ); + $actions->addElement($open, 'closethread'); + } + } + + if (ForumPerm::has('make_sticky', $seminar_id) && $constraint['depth'] > 1) { + if ($constraint['sticky'] == 0) { + $emphasize_url = $controller->url_for('course/forum/index/make_sticky/' + . $constraint['topic_id'] .'/'. $constraint['topic_id'] .'/'. ForumHelpers::getPage()); + $emphasize = new LinkElement( + _('Thema hervorheben'), + $emphasize_url, + Icon::create('staple', 'clickable'), + [ + 'onclick' => 'STUDIP.Forum.makeThreadStickyFromThread(\'' . $constraint['topic_id'] . '\', ' + . ForumHelpers::getPage() . '); return false;', + 'id' => "stickyButton" + ] + ); + $actions->addElement($emphasize, 'emphasize'); + } else { + $unemphasize_url = $controller->url_for('course/forum/index/make_unsticky/' + . $constraint['topic_id'] .'/'. $constraint['topic_id'] .'/'. ForumHelpers::getPage()); + $emphasize = new LinkElement( + _('Hervorhebung aufheben'), + $unemphasize_url, + Icon::create('staple', 'clickable'), + [ + 'onclick' => 'STUDIP.Forum.makeThreadUnstickyFromThread(\'' . $constraint['topic_id'] . '\', ' + . ForumHelpers::getPage() . '); return false;', + 'id' => "stickyButton" + ] + ); + $actions->addElement($emphasize, 'emphasize'); + } + } + + if ($constraint['depth'] == 0 && ForumPerm::has('add_category', $seminar_id)) { + $actions->addLink(_('Neue Kategorie erstellen'), "#create", Icon::create('link-intern', 'clickable')); + } +} + +$sidebar->addWidget($actions); + +if ($section === 'index' && ForumPerm::has('pdfexport', $seminar_id)) { + $export = new ExportWidget(); + $export->addLink(_('Beiträge als PDF exportieren'), + $controller->url_for('course/forum/index/pdfexport/' . $constraint['topic_id']), + Icon::create('file-pdf', 'clickable')); + $sidebar->addWidget($export); +} +?> + + +render_partial('course/forum/index/_breadcrumb') ?> + + + ForumEntry::POSTINGS_PER_PAGE) : ?> +
+ 0 || !isset($constraint)) : ?> + render('shared/pagechooser', [ + 'page' => ForumHelpers::getPage(), + 'num_postings' => $number_of_entries, + 'perPage' => ForumEntry::POSTINGS_PER_PAGE, + 'pagelink' => str_replace('%%s', '%s', str_replace('%', '%%', $controller->url_for('course/forum/index/goto_page/'. $topic_id .'/'. $section + .'/%s/?searchfor=' . $searchfor . (!empty($options) ? '&'. http_build_query($options) : '' )))) + ]); ?> + + +
+ + + +
+ render_partial('course/forum/messages') ?> +
+ + + + + + + + render_partial('course/forum/index/_areas') ?> + + render_partial('course/forum/index/_threads') ?> + + + + + render_partial('course/forum/index/_postings') ?> + + + + +
+ +
+ + + + +
+
+ + + render_partial('course/forum/index/_abo_link', compact('constraint')) ?> + + + + + url_for('course/forum/index/pdfexport'), ['target' => '_blank']) ?> + +
+
+ + + render_partial('course/forum/index/_new_category') ?> + + + + +
+
+
+ + url_for('course/forum/index/index/'. $topic_id .'?answer=1'), + ['onClick' => 'STUDIP.Forum.answerEntry(); return false;', + 'class' => 'hideWhenClosed',]) ?> + + + 1 && ($constraint['closed'] == 1)) : ?> + url_for('course/forum/index/index/' . $topic_id. '?answer=1'), + ['onClick' => 'STUDIP.Forum.answerEntry(); return false;', + 'class' => 'hideWhenClosed', + 'style' => 'display:none;' + ]) ?> + + + 1) : ?> + + url_for('course/forum/index/close_thread/' . $topic_id .'/'. $topic_id .'/'. ForumHelpers::getPage()), [ + 'onClick' => 'STUDIP.Forum.closeThreadFromThread("'. $topic_id .'"); return false;', + 'class' => 'closeButtons'] + ) ?> + + url_for('course/forum/index/open_thread/' . $topic_id .'/'. $topic_id .'/'. ForumHelpers::getPage()), [ + 'onClick' => 'STUDIP.Forum.openThreadFromThread("'. $topic_id .'"); return false;', + 'class' => 'closeButtons'] + ) ?> + + + + 0 && ForumPerm::has('abo', $seminar_id)) : ?> + + render_partial('course/forum/index/_abo_link', compact('constraint')) ?> + + + + + url_for('course/forum/index/pdfexport/' . $topic_id), ['target' => '_blank']) ?> + +
+
+ +
+ + + + +seminar_id)) + || ($constraint['depth'] >= 1 && ForumPerm::has('add_entry', $seminar_id)) ): ?> + render_partial('course/forum/index/_new_entry') ?> + +
+ + + + + + + + + diff --git a/app/views/course/forum/messages.php b/app/views/course/forum/messages.php new file mode 100644 index 0000000..246a85d --- /dev/null +++ b/app/views/course/forum/messages.php @@ -0,0 +1,7 @@ + $message): ?> + + + + + + diff --git a/app/views/course/statusgroups/groupinfo.php b/app/views/course/statusgroups/groupinfo.php index 32890bf..3fff472 100644 --- a/app/views/course/statusgroups/groupinfo.php +++ b/app/views/course/statusgroups/groupinfo.php @@ -59,7 +59,7 @@ blubberthread) : ?>

id, [ + URLHelper::getURL('dispatch.php/course/messenger/course/' . $thread->id, [ 'cid' => $course_id, ]))) ?>

diff --git a/app/views/quickselection/configuration.php b/app/views/quickselection/configuration.php new file mode 100644 index 0000000..8f25f4b --- /dev/null +++ b/app/views/quickselection/configuration.php @@ -0,0 +1,22 @@ +
+
+
+ +
+ $nav) : ?> + + +
+
+
+ + +
+
+
diff --git a/db/migrations/5.2.6_update_core_plugins.php b/db/migrations/5.2.6_update_core_plugins.php new file mode 100644 index 0000000..a9f5048 --- /dev/null +++ b/db/migrations/5.2.6_update_core_plugins.php @@ -0,0 +1,48 @@ + 'ActivityFeed', + 'core/Blubber' => 'Blubber', + 'core/ContentsWidget' => 'ContentsWidget', + 'core/Forum' => 'CoreForum', + 'core/EvaluationsWidget' => 'EvaluationsWidget', + 'core/NewsWidget' => 'NewsWidget', + 'core/QuickSelection' => 'QuickSelection', + 'core/ScheduleWidget' => 'ScheduleWidget', + 'core/TerminWidget' => 'TerminWidget' + ]; + + public function description() + { + return 'convert old core plugins into new core pugins'; + } + + public function up() + { + $db = DBManager::get(); + $stmt = $db->prepare('UPDATE plugins SET pluginpath = ? WHERE pluginclassname = ?'); + + foreach (self::$core_widgets as $core_widget) { + $stmt->execute(['', $core_widget]); + } + + $db->exec("UPDATE help_content SET route = REPLACE(route, 'plugins.php/coreforum', 'dispatch.php/course/forum')"); + $db->exec("UPDATE help_tour_steps SET route = REPLACE(route, 'plugins.php/coreforum', 'dispatch.php/course/forum')"); + } + + public function down() + { + $db = DBManager::get(); + + $stmt = $db->prepare('UPDATE plugins SET pluginpath = ? WHERE pluginclassname = ?'); + + foreach (self::$core_widgets as $pluginpath => $core_widget) { + $stmt->execute([$pluginpath, $core_widget]); + } + + $db->exec("UPDATE help_content SET route = REPLACE(route, 'dispatch.php/course/forum', 'plugins.php/coreforum')"); + $db->exec("UPDATE help_tour_steps SET route = REPLACE(route, 'dispatch.php/course/forum', 'plugins.php/coreforum')"); + } +} diff --git a/lib/activities/ForumProvider.php b/lib/activities/ForumProvider.php index 49260c3..f543947 100644 --- a/lib/activities/ForumProvider.php +++ b/lib/activities/ForumProvider.php @@ -9,8 +9,6 @@ namespace Studip\Activity; -require_once 'public/plugins_packages/core/Forum/models/ForumEntry.php'; - class ForumProvider implements ActivityProvider { /** @@ -28,7 +26,7 @@ class ForumProvider implements ActivityProvider $activity->content = formatReady($post['content']); - $url = \PluginEngine::getURL('CoreForum', [], 'index/index/' . $post['topic_id'] + $url = \URLHelper::getURL('dispatch.php/course/forum/index/index/' . $post['topic_id'] .'?cid='. $post['seminar_id'] .'&highlight_topic='. $post['topic_id'] .'#'. $post['topic_id']); diff --git a/lib/calendar/CalendarWidgetView.php b/lib/calendar/CalendarWidgetView.php new file mode 100644 index 0000000..946592c --- /dev/null +++ b/lib/calendar/CalendarWidgetView.php @@ -0,0 +1,52 @@ + + * @license GPL2 or any later version + * @since Stud.IP 3.4 + */ +class CalendarWidgetView extends CalendarWeekView +{ + /** + * Creates a widget view from a week view. + * + * @param CalendarWeekView $view The CalendarWeekView object + * @return CalendarWidgetView object with the data from the + * CalendarWeekView + */ + public static function createFromWeekView(CalendarWeekView $view) + { + $new_view = new self($view->getColumns(), $view->getContext()); + $new_view->setReadOnly(true); + return $new_view; + } + + /** + * Returns all columns of the calendar-view and removes everything that + * is not needed and links the entry to the details page of the course. + * + * @return array of CalendarColumn + */ + public function getColumns() + { + foreach ($this->entries as $column) { + $column->setURL(false); + foreach ($column->entries as $key => $entry) { + if (isset($entry['cycle_id'])) { + list($course_id, $cycle_id) = explode('-', $entry['id']); + + $url = URLHelper::getLink('dispatch.php/course/details/?sem_id=' . $course_id); + $column->entries[$key]['url'] = $url; + } else { + unset($column->entries[$key]['url']); + } + + unset($column->entries[$key]['onClick']); + unset($column->entries[$key]['icons']); + } + } + + return $this->entries; + } +} diff --git a/lib/classes/ForumAbo.php b/lib/classes/ForumAbo.php new file mode 100644 index 0000000..fa84a48 --- /dev/null +++ b/lib/classes/ForumAbo.php @@ -0,0 +1,186 @@ + + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL version 3 + * @category Stud.IP + */ + +class ForumAbo +{ + /** + * add the passed user as a watcher for the passed topic (including all + * current and future childs) + * + * @param string $topic_id + * @param string $user_id + */ + public static function add($topic_id, $user_id = null) + { + if (!$user_id) $user_id = $GLOBALS['user']->id; + + $stmt = DBManager::get()->prepare("REPLACE INTO forum_abo_users + (topic_id, user_id) VALUEs (?, ?)"); + $stmt->execute([$topic_id, $user_id]); + } + + /** + * remove the passed user as a watcher from the passed topic (including all + * current and future childs) + * + * @param string $topic_id + * @param string $user_id + */ + public static function delete($topic_id, $user_id = null) + { + if (!$user_id) $user_id = $GLOBALS['user']->id; + + $stmt = DBManager::get()->prepare("DELETE FROM forum_abo_users + WHERE topic_id = ? AND user_id = ?"); + $stmt->execute([$topic_id, $user_id]); + } + + /** + * check, if the passed user watches the passed topic. If no user_id is passed, + * the currently logged in user is used + * + * @param string $topic_id + * @param string $user_id + * + * @return boolean returns true if user is watching, false otherwise + */ + public static function has($topic_id, $user_id = null) + { + if (!$user_id) $user_id = $GLOBALS['user']->id; + + $stmt = DBManager::get()->prepare("SELECT COUNT(*) FROM forum_abo_users + WHERE topic_id = ? AND user_id = ?"); + $stmt->execute([$topic_id, $user_id]); + + return $stmt->fetchColumn() > 0 ? true : false; + } + + /** + * send out the notification messages for the passed topic. The contents + * and a link directly to the topic are added to the message. + * + * @param string $topic_id + */ + public static function notify($topic_id) + { + // send message to all abo-users + $db = DBManager::get(); + $messaging = new ForumBulkMail(); + // $messaging = new Messaging(); + + // get all parent topic-ids, to find out which users to notify + $path = ForumEntry::getPathToPosting($topic_id); + + // fetch all users to notify, exclude current user + $stmt = $db->prepare("SELECT DISTINCT user_id + FROM forum_abo_users + WHERE topic_id IN (:topic_ids) + AND user_id != :user_id"); + $stmt->bindParam(':topic_ids', array_keys($path), StudipPDO::PARAM_ARRAY); + $stmt->bindParam(':user_id', $GLOBALS['user']->id); + $stmt->execute(); + + // get details for topic + $topic = ForumEntry::getConstraints($topic_id); + + $template = $GLOBALS['template_factory']->open('mail/forum_notification'); + + // notify users + while ($data = $stmt->fetch(PDO::FETCH_ASSOC)) { + $user_id = $data['user_id']; + + // don't notify user if view permission is not granted + if (!ForumPerm::has('view', $topic['seminar_id'], $user_id)) { + continue; + } + + $user = User::find($user_id); + + // check if user wants an email for all or selected messages only + $force_email = false; + if ($messaging->user_wants_email($user_id)) { + $force_email = true; + } + // do not send mails when account is locked or expired + $expiration = UserConfig::get($user->id)->EXPIRATION_DATE; + if ($user->locked || ($expiration > 0 && $expiration < time())) { + $force_email = false; + } + $parent_id = ForumEntry::getParentTopicId($topic['topic_id']); + + setTempLanguage($data['user_id']); + $notification = sprintf(_("%s hat einen Beitrag geschrieben"), ($topic['anonymous'] ? _('Anonym') : $topic['author'])); + restoreLanguage(); + + PersonalNotifications::add( + $user_id, + URLHelper::getURL( + 'dispatch.php/course/forum/index/index/' . $topic['topic_id'] . '#' . $topic['topic_id'], + ['cid' => $topic['seminar_id']], + true + ), + $notification, + "forumposting_" . $topic['topic_id'], + Icon::create('forum', 'clickable') + ); + + if ($force_email) { + $title = implode(' >> ', ForumEntry::getFlatPathToPosting($topic_id)); + + $subject = _('[Forum]') . ' ' . ($title ?: _('Neuer Beitrag')); + + $htmlMessage = $template->render( + compact('user_id', 'topic', 'path') + ); + + $textMessage = trim(kill_format($htmlMessage)); + + $userWantsHtml = UserConfig::get($user_id)->MAIL_AS_HTML; + + StudipMail::sendMessage( + $user->email, + $subject, + $textMessage, + $userWantsHtml ? $htmlMessage : null + ); + } + } + + $messaging->bulkSend(); + } + + /** + * Removes all abos for a given course and user + * + * @param String $course_id Id of the course + * @param String $user_id Id of the user + * @return int number of removed abos + */ + public static function removeForCourseAndUser($course_id, $user_id) + { + $query = "DELETE FROM `forum_abo_users` + WHERE `user_id` = :user_id + AND `topic_id` IN ( + SELECT `topic_id` + FROM `forum_entries` + WHERE `seminar_id` = :course_id + )"; + $statement = DBManager::get()->prepare($query); + $statement->bindValue(':course_id', $course_id); + $statement->bindValue(':user_id', $user_id); + $statement->execute(); + return $statement->rowCount(); + } +} diff --git a/lib/classes/ForumActivity.php b/lib/classes/ForumActivity.php new file mode 100644 index 0000000..20ebeb6 --- /dev/null +++ b/lib/classes/ForumActivity.php @@ -0,0 +1,149 @@ + + * @license https://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + */ + +class ForumActivity +{ + /** + * Post activity for new forum post + * + * @param string $event + * @param string $topic_id + * @param array $post + */ + public static function newEntry($event, $topic_id, $post) + { + $verb = $post['depth'] == 3 ? 'answered' : 'created'; + + if ($verb == 'created') { + if ($post['depth'] == 1) { + $summary = _('%s hat im Forum der Veranstaltung "%s" einen Bereich erstellt.'); + } else { + $summary = _('%s hat im Forum der Veranstaltung "%s" ein Thema erstellt.'); + } + } else { + $summary = _('%s hat im Forum der Veranstaltung "%s" auf ein Thema geantwortet.'); + } + + self::post($post, $verb, $summary); + } + + /** + * Post activity for updating a forum post + * @param string $event + * @param string $topic_id + * @param string $post + */ + public static function updateEntry($event, $topic_id, $post) + { + $summary = _('%s hat im Forum der Veranstaltung "%s" einen Beitrag editiert.'); + + if ($post['user_id'] == $GLOBALS['user']->id) { + $content = sprintf( + _('%s hat seinen eigenen Beitrag vom %s editiert.'), + self::getPostUsername($post), + date('d.m.y, H:i', $post['mkdate']) + ); + } else { + $content = sprintf( + _('%s hat den Beitrag von %s vom %s editiert.'), + get_fullname($GLOBALS['user']->id), + self::getPostUsername($post), + date('d.m.y, H:i', $post['mkdate']) + ); + } + + self::post($post, 'edited', $summary, $content); + } + + /** + * Post activity for deleting a forum post + * $param string $event + * @param string $topic_id + * @param string $post + */ + public static function deleteEntry($event, $topic_id, $post) + { + // Remove all previous activities for the post + Studip\Activity\Activity::deleteBySQL( + "provider = ? AND object_type = 'forum' AND object_id = ?", + [Studip\Activity\ForumProvider::class, $topic_id] + ); + + $summary = _('%s hat im Forum der Veranstaltung "%s" einen Beitrag gelöscht.'); + + if ($post['user_id'] == $GLOBALS['user']->id) { + $content = sprintf( + _('%s hat seinen Beitrag vom %s gelöscht.'), + self::getPostUsername($post), + date('d.m.y, H:i', $post['mkdate']) + ); + } else { + $content = sprintf( + _('%s hat den Beitrag von %s vom %s gelöscht.'), + get_fullname($GLOBALS['user']->id), + self::getPostUsername($post), + date('d.m.y, H:i', $post['mkdate']) + ); + } + + self::post($post, 'deleted', $summary, $content); + } + + private static function post($post, $verb, $summary, $content = null) + { + // skip system-created entries like "Allgemeine Diskussionen" + if (!$post['user_id']) { + return; + } + + $range_id = $post['seminar_id']; + $type = get_object_type($range_id); + + $obj = get_object_name($range_id, $type); + + $data = [ + 'provider' => 'Studip\Activity\ForumProvider', + 'context' => $type === 'sem' ? 'course' : 'institute', + 'context_id' => $post['seminar_id'], + 'content' => null, + 'actor_type' => 'user', // who initiated the activity? + 'actor_id' => $post['user_id'], // id of initiator + 'verb' => $verb, // the activity type + 'object_id' => $post['topic_id'], // the id of the referenced object + 'object_type' => 'forum', // type of activity object + 'mkdate' => $post['mkdate'] ?: time() + ]; + + if ($post['anonymous']) { + $data['actor_type'] = 'anonymous'; + $data['actor_id'] = ''; + } + + $activity = Studip\Activity\Activity::create($data); + } + + /** + * Returns the poster's name for a forum post. + * + * @param array $post + * @return string + */ + private static function getPostUsername($post) + { + if ($post['anonymous']) { + return _('Anonym'); + } + + return get_fullname($post['user_id']); + } +} diff --git a/lib/classes/ForumBulkMail.php b/lib/classes/ForumBulkMail.php new file mode 100644 index 0000000..ec18de9 --- /dev/null +++ b/lib/classes/ForumBulkMail.php @@ -0,0 +1,135 @@ + + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL version 3 + * @category Stud.IP + */ + +class ForumBulkMail extends Messaging { + var $bulk_mail; + + /** + * Overwrites the parent method. This method combines messages with the same + * content and prepares them for sending them as a mail with multiple + * recepients instead of one mail for each recipient. + * The actual sending task is done bulkSend(). + * + * @global object $user + * + * @param string $rec_user_id user_id of recipient + * @param string $snd_user_id user_id of sender + * @param string $message the message + * @param string $subject subject for the message + * @param string $message_id the message_id in the database + */ + function sendingEmail($rec_user_id, $snd_user_id, $message, $subject, $message_id) + { + $receiver = User::find($rec_user_id); + + if ($receiver && $receiver->email) { + $rec_fullname = 'Sie'; + + setTempLanguage($receiver->id); + + if (empty($this->bulk_mail[md5($message)][getenv('LANG')])) { + + $title = "[Stud.IP - " . Config::get()->UNI_NAME_CLEAN . "] ".stripslashes(kill_format(str_replace(["\r","\n"], '', $subject))); + + if ($snd_user_id != "____%system%____") { + $sender = User::find($snd_user_id); + $reply_to = $sender->email; + } + + $template = $GLOBALS['template_factory']->open('mail/text'); + $template->message = kill_format(stripslashes($message)); + $template->rec_fullname = $receiver->getFullname(); + $mailmessage = $template->render(); + + $template = $GLOBALS['template_factory']->open('mail/html'); + $template->lang = getUserLanguagePath($rec_user_id); + $template->message = stripslashes($message); + $template->rec_fullname = $receiver->getFullname(); + $mailhtml = $template->render(); + + $this->bulk_mail[md5($message)][getenv('LANG')] = [ + 'text' => $mailmessage, + 'html' => $mailhtml, + 'title' => $title, + 'reply_to' => $reply_to, + 'message_id' => $message_id, + 'users' => [] + ]; + } + + $this->bulk_mail[md5($message)][getenv('LANG')]['users'][$receiver->id] = $receiver->email; + + restoreLanguage(); + } + } + + + /** + * Sends the collected messages from sendingMail as e-mail. + */ + function bulkSend() + { + // if nothing to do, return + if (empty($this->bulk_mail)) return; + + // send a mail, for each language one + foreach ($this->bulk_mail as $lang_data) { + foreach ($lang_data as $data) { + $mail = new StudipMail(); + $mail->setSubject($data['title']); + + foreach ($data['users'] as $user_id => $to) { + $mail->addRecipient($to, get_fullname($user_id), 'Bcc'); + } + + $mail->setReplyToEmail('') + ->setBodyText($data['text']); + + if (mb_strlen($data['reply_to'])) { + $mail->setSenderEmail($data['reply_to']) + ->setSenderName($snd_fullname); + } + + $user_cfg = UserConfig::get($user_id); + if ($user_cfg->getValue('MAIL_AS_HTML')) { + $mail->setBodyHtml($mailhtml); + } + + if($GLOBALS["ENABLE_EMAIL_ATTACHMENTS"]){ + $message = Message::find($data['message_id']); + + $current_user = User::findCurrent(); + + $message_folder = MessageFolder::findMessageTopFolder( + $message->id, + $current_user->id + ); + + $message_folder = $message_folder->getTypedFolder(); + + $attachments = FileManager::getFolderFilesRecursive( + $message_folder, + $current_user->id + ); + + + foreach($attachments as $attachment) { + $mail->addStudipAttachment($attachment); + } + } + $mail->send(); + } + } + } +} diff --git a/lib/classes/ForumEntry.php b/lib/classes/ForumEntry.php new file mode 100644 index 0000000..bd62b08 --- /dev/null +++ b/lib/classes/ForumEntry.php @@ -0,0 +1,1385 @@ + + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL version 3 + * @category Stud.IP + */ + +class ForumEntry implements PrivacyObject +{ + const WITH_CHILDS = true; + const WITHOUT_CHILDS = false; + const THREAD_PREVIEW_LENGTH = 100; + const POSTINGS_PER_PAGE = 10; + const FEED_POSTINGS = 100; + + + /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * H E L P E R - F U N C T I O N S * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + + /** + * is used for posting-preview. replaces all newlines with spaces + * + * @param string $text the text to work on + * @returns string + */ + public static function br2space($text) + { + return str_replace("\n", ' ', str_replace("\r", '', $text)); + } + + /** + * remove the edit-html from a posting + * + * @param string $description the posting-content + * @return string the content stripped by the edit-mark + */ + public static function killEdit($description) + { + // wurde schon mal editiert + if (preg_match('/^(.*)("; + return $description . $edit; + } + + /** + * convert the edit-html to raw text + * + * @param string $description the posting-content + * @return string the content with the raw text version of the edit-mark + */ + public static function parseEdit($description, $anonymous = false) + { + // TODO figure out if this function can be removed + // has been replaced with getContentAsHTML in core code + $content = ForumEntry::killEdit($description); + $comment = ForumEntry::getEditComment($description, $anonymous); + return $content . ($comment ? "\n\n%%" . $comment .'%%' : ''); + } + + /** + * Get content with appended edit comment as HTML. + * + * @param string $description Database entry of forum entry's body. + * @param bool $anonymous True, if only root is allowed to see + * authors. + * @return string Content and edit comment as HTML. + */ + public static function getContentAsHtml($description, $anonymous = false) + { + $raw_content = ForumEntry::killEdit($description); + + $comment = ForumEntry::getEditComment($description, $anonymous); + $content = formatReady($raw_content); + + if ($comment) { + $content .= '
' . htmlReady($comment) . ''; + } + + return $content; + } + + /** + * Get author and time of an edited forum entry as a string. + * + * @param string $description Database entry of forum entry's body. + * @param bool $anonymous True, if only root is allowed to see + * authors. + * @return string Author and time or empty string if not edited. + */ + public static function getEditComment($description, $anonymous = false) + { + $info = ForumEntry::getEditInfo($description); + if ($info) { + $root = $GLOBALS['perm']->have_perm('root'); + $author = ($anonymous && !$root) ? _('Anonym') : $info['author']; + $time = date('d.m.y - H:i', $info['time']); + return '[' . _('Zuletzt editiert von') . " $author - $time]"; + } + return ''; + } + + /** + * Get author and time of an edited forum entry. + * + * @param string $description Database entry of forum entry's body. + * @return array Associative array containing author and time. + * boolean False if edit tag was not found. + */ + public static function getEditInfo($description) { + if (preg_match('/\s*$/i', $description, $matches)) { + // wurde schon mal editiert + return ['author' => $matches[1], 'time' => $matches[2]]; + } + return false; + } + + /** + * Remove all quote blocks AND the quoted text from a forum post. + * + * @param String $string The string to remove the quote blocks from + * @return String the posting without the [quote]-blocks (not just tags!) + */ + public static function removeQuotes($description) + { + if (Studip\Markup::isHtml($description)) { + // remove all blockquote tags + $dom = new DOMDocument(); + $dom->loadHtml($description, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD); + $nodes = iterator_to_array($dom->getElementsByTagName('blockquote')); + + foreach ($nodes as $node) { + $node->parentNode->removeChild($node); + } + + return $dom->saveHTML(); + } else { + $description = preg_replace('/\[quote(=.*)\].*\[\/quote\]/isU', '', $description); + $description = str_replace('[/quote]', '', $description); + } + return $description; + } + + + /** + * calls Stud.IP's kill_format and additionally removes any found smiley-tag + * + * @param string $text the text to parse + * @return string the text without format-tags and without smileys + */ + public static function killFormat($text) + { + $text = kill_format($text); + + // find stuff which is enclosed between to colons + preg_match('/' . SmileyFormat::REGEXP . '/U', $text, $matches); + + // remove the match if it is a smiley + foreach ($matches as $match) { + if (Smiley::getByName($match) || Smiley::getByShort($match)) { + $text = str_replace($match, '', $text); + } + } + + return $text; + } + + /** + * returns the entry for the passed topic_id + * + * @param string $topic_id + * @return array array('lft' => ..., 'rgt' => ..., seminar_id => ...) + * + * @throws Exception + */ + public static function getConstraints($topic_id) + { + //very bad performance if topic_id is 0 or false + if (!$topic_id) return false; + + // look up the range of postings + $range_stmt = DBManager::get()->prepare("SELECT * + FROM forum_entries WHERE topic_id = ?"); + $range_stmt->execute([$topic_id]); + if (!$data = $range_stmt->fetch(PDO::FETCH_ASSOC)) { + return false; + // throw new Exception("Could not find entry with id >>$topic_id<< in forum_entries, " . __FILE__ . " on line " . __LINE__); + } + + if ($data['depth'] == 1) { + $data['area'] = 1; + } + + return $data; + } + + /** + * return the topic_id of the parent element, false if there is none (ie the + * passed topic_id is already the upper-most node in the tree) + * + * @param string $topic_id the topic_id for which the parent shall be found + * + * @return string the topic_id of the parent element or false + */ + public static function getParentTopicId($topic_id) + { + $path = ForumEntry::getPathToPosting($topic_id); + array_pop($path); + $data = array_pop($path); + + return $data['id'] ?: false; + } + + + /** + * get the topic_ids of all childs of the passed topic including itself + * + * @param string $topic_id the topic_id to find the childs for + * @return array a list if topic_ids + */ + public static function getChildTopicIds($topic_id) + { + $constraints = ForumEntry::getConstraints($topic_id); + + $stmt = DBManager::get()->prepare("SELECT topic_id + FROM forum_entries WHERE lft >= ? AND rgt <= ? + AND seminar_id = ?"); + $stmt->execute([$constraints['lft'], $constraints['rgt'], $constraints['seminar_id']]); + + return $stmt->fetchAll(PDO::FETCH_COLUMN); + } + + /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * D A T A - R E T R I E V A L * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + + /** + * get the page the passed posting is on + * + * @param string $topic_id + * @return int + */ + public static function getPostingPage($topic_id, $constraint = null) + { + if (!$constraint) { + $constraint = ForumEntry::getConstraints($topic_id); + } + + // this calculation only works for postings + if ($constraint['depth'] <= 2) return ForumHelpers::getPage(); + + if ($parent_id = ForumEntry::getParentTopicId($topic_id)) { + $parent_constraint = ForumEntry::getConstraints($parent_id); + + return ceil((($constraint['lft'] - $parent_constraint['lft'] + 3) / 2) / ForumEntry::POSTINGS_PER_PAGE); + } + + return 0; + } + + /** + * return the id for the oldest unread child-posting for the passed topic. + * + * @param string $parent_id + * @return string id of oldest unread posting + */ + public static function getLastUnread($parent_id) + { + $constraint = ForumEntry::getConstraints($parent_id); + + // take users visitdate into account + $visitdate = ForumVisit::getLastVisit($constraint['seminar_id']); + + // get the first unread entry + $stmt = DBManager::get()->prepare("SELECT * FROM forum_entries + WHERE lft > ? AND rgt < ? AND seminar_id = ? + AND mkdate >= ? + ORDER BY mkdate ASC LIMIT 1"); + $stmt->execute([$constraint['lft'], $constraint['rgt'], $constraint['seminar_id'], $visitdate]); + $last_unread = $stmt->fetch(PDO::FETCH_ASSOC); + + return $last_unread ? $last_unread['topic_id'] : null; + } + + /** + * retrieve the the latest posting under $parent_id + * or false if the postings itself is the latest + * + * @param string $parent_id the node to lookup the childs in + * @return mixed the data for the latest postings or false + */ + public static function getLatestPosting($parent_id) + { + $constraint = ForumEntry::getConstraints($parent_id); + + // get last entry + $stmt = DBManager::get()->prepare("SELECT * FROM forum_entries + WHERE lft > ? AND rgt < ? AND seminar_id = ? + ORDER BY mkdate DESC LIMIT 1"); + $stmt->execute([$constraint['lft'], $constraint['rgt'], $constraint['seminar_id']]); + + if (!$data = $stmt->fetch(PDO::FETCH_ASSOC)) { + return false; + } + + return $data; + } + + /** + * returns a hashmap with arrays containing id and name with the entries + * which lead to the passed topic + * + * @param string $topic_id the topic to get the path for + * + * @return array + */ + public static function getPathToPosting($topic_id) + { + $data = ForumEntry::getConstraints($topic_id); + $ret = []; + + $stmt = DBManager::get()->prepare("SELECT * FROM forum_entries + WHERE lft <= ? AND rgt >= ? AND seminar_id = ? ORDER BY lft ASC"); + $stmt->execute([$data['lft'], $data['rgt'], $data['seminar_id']]); + + while ($data = $stmt->fetch(PDO::FETCH_ASSOC)) { + $ret[$data['topic_id']] = $data; + $ret[$data['topic_id']]['id'] = $data['topic_id']; + } + + // set the name of the first entry to the name of the category the entry is in + if (sizeof($ret) > 1) { + reset($ret); + $tmp = array_slice($ret, 1, 1); + $area = array_pop($tmp); + $top = current($ret); + $ret[$top['id']]['name'] = ForumCat::getCategoryNameForArea($area['id']) ?: _('Allgemein'); + } + + return $ret; + } + + /** + * returns a hashmap where key is topic_id and value a posting-title from the + * entries which lead to the passed topic. + * + * WARNING: This function ommits postings with an empty title. For a full + * list please use ForumEntry::getPathToPosting()! + * + * @param string $topic_id the topic to get the path for + * + * @return array + */ + public static function getFlatPathToPosting($topic_id) + { + // use only the part of the path until the thread, no posting title + $postings = array_slice(self::getPathToPosting($topic_id), 0, 3); + + // var_dump($postings); + + foreach ($postings as $post) { + if ($post['name']) { + $ret[$post['id']] = $post['name']; + } + } + + return $ret; + } + + /** + * fill the passed postings with additional data + * + * @param array $postings + * @return array + */ + public static function parseEntries($postings) + { + $posting_list = []; + + // retrieve the postings + foreach ($postings as $data) { + // we throw away all formatting stuff, tags, etc, leaving the important bit of information + $desc_short = ForumEntry::br2space(ForumEntry::killFormat(strip_tags($data['content']))); + if (mb_strlen($desc_short) > (ForumEntry::THREAD_PREVIEW_LENGTH + 2)) { + $desc_short = mb_substr($desc_short, 0, ForumEntry::THREAD_PREVIEW_LENGTH) . '...'; + } else { + $desc_short = $desc_short; + } + + $posting_list[$data['topic_id']] = [ + 'author' => $data['author'], + 'topic_id' => $data['topic_id'], + 'name' => formatReady($data['name']), + 'name_raw' => $data['name'], + 'content' => ForumEntry::getContentAsHtml($data['content'], $data['anonymous']), + 'content_raw' => ForumEntry::killEdit($data['content']), + 'content_short' => $desc_short, + 'chdate' => $data['chdate'], + 'mkdate' => $data['mkdate'], + 'user_id' => $data['user_id'], + 'raw_title' => $data['name'], + 'raw_description' => ForumEntry::killEdit($data['content']), + 'fav' => ($data['fav'] == 'fav'), + 'depth' => $data['depth'], + 'anonymous' => $data['anonymous'], + 'closed' => $data['closed'], + 'sticky' => $data['sticky'], + 'seminar_id' => $data['seminar_id'] + ]; + } // retrieve the postings + + return $posting_list; + } + + /** + * Get all entries for the passed parent_id. + * Returns an array of the following structure: + * Array ( + * 'list' => Array ( + * 'author' => + * 'topic_id' => + * 'name' => formatReady() + * 'name_raw' => + * 'content' => formatReady() + * 'content_raw' => + * 'content_short' => + * 'chdate' => + * 'mkdate' => + * 'user_id' => + * 'raw_title' => + * 'raw_description' => + * 'fav' => + * 'depth' => + * 'sticky' => + * 'closed' => + * 'seminar_id' => + * ) + * 'count' => + * ) + * + * @param string $parent_id id of parent-element to get entries for. + * @param boolean $with_childs if true, the whole subtree is fetched + * @param string $add for additional constraints in the WHERE-part of the query + * @param string $sort_order can be ASC or DESC + * @param int $start can be used for pagination, is used for the LIMIT-part of the query + * @param int $limit number of entries to fetch, defaults to ForumEntry::POSTINGS_PER_PAGE + * + * @return array + * + * @throws Exception if the retrieval failed, an Exception is thrown + */ + public static function getEntries($parent_id, $with_childs = false, $add = '', + $sort_order = 'DESC', $start = 0, $limit = ForumEntry::POSTINGS_PER_PAGE) + { + $constraint = ForumEntry::getConstraints($parent_id); + $seminar_id = $constraint['seminar_id']; + $depth = $constraint['depth'] + 1; + + // count the entries and set correct page if necessary + if ($with_childs) { + $count_stmt = DBManager::get()->prepare("SELECT COUNT(*) FROM forum_entries + LEFT JOIN forum_favorites as ou ON (ou.topic_id = forum_entries.topic_id AND ou.user_id = ?) + WHERE (forum_entries.seminar_id = ? + AND forum_entries.seminar_id != forum_entries.topic_id + AND lft > ? AND rgt < ?) " + . ($depth > 2 ? " OR forum_entries.topic_id = ". DBManager::get()->quote($parent_id) : '') + . $add + . " ORDER BY forum_entries.mkdate $sort_order"); + $count_stmt->execute([$GLOBALS['user']->id, $seminar_id, $constraint['lft'], $constraint['rgt']]); + $count = $count_stmt->fetchColumn(); + } else { + $count_stmt = DBManager::get()->prepare("SELECT COUNT(*) FROM forum_entries + LEFT JOIN forum_favorites as ou ON (ou.topic_id = forum_entries.topic_id AND ou.user_id = ?) + WHERE ((depth = ? AND forum_entries.seminar_id = ? + AND forum_entries.seminar_id != forum_entries.topic_id + AND lft > ? AND rgt < ?) " + . ($depth > 2 ? " OR forum_entries.topic_id = ". DBManager::get()->quote($parent_id) : '') + . ') '. $add + . " ORDER BY forum_entries.mkdate $sort_order"); + $count_stmt->execute([$GLOBALS['user']->id, $depth, $seminar_id, $constraint['lft'], $constraint['rgt']]); + $count = $count_stmt->fetchColumn(); + } + + // use the last page if the requested page does not exist + if ($start > $count) { + $page = ceil($count / ForumEntry::POSTINGS_PER_PAGE); + ForumHelpers::setPage($page); + $start = max(1, $page - 1) * ForumEntry::POSTINGS_PER_PAGE; + } + + if ($with_childs) { + $stmt = DBManager::get()->prepare("SELECT forum_entries.*, IF(ou.topic_id IS NOT NULL, 'fav', NULL) as fav + FROM forum_entries + LEFT JOIN forum_favorites as ou ON (ou.topic_id = forum_entries.topic_id AND ou.user_id = ?) + WHERE (forum_entries.seminar_id = ? + AND forum_entries.seminar_id != forum_entries.topic_id + AND lft > ? AND rgt < ?) " + . ($depth > 2 ? " OR forum_entries.topic_id = ". DBManager::get()->quote($parent_id) : '') + . $add + . " ORDER BY forum_entries.mkdate $sort_order" + . ($limit ? " LIMIT $start, $limit" : '')); + $stmt->execute([$GLOBALS['user']->id, $seminar_id, $constraint['lft'], $constraint['rgt']]); + } else { + $stmt = DBManager::get()->prepare("SELECT forum_entries.*, IF(ou.topic_id IS NOT NULL, 'fav', NULL) as fav + FROM forum_entries + LEFT JOIN forum_favorites as ou ON (ou.topic_id = forum_entries.topic_id AND ou.user_id = ?) + WHERE ((depth = ? AND forum_entries.seminar_id = ? + AND lft > ? AND rgt < ?) " + . ($depth > 2 ? " OR forum_entries.topic_id = ". DBManager::get()->quote($parent_id) : '') + . ') '. $add + . " ORDER BY forum_entries.mkdate $sort_order" + . ($limit ? " LIMIT $start, $limit" : '')); + $stmt->execute([$GLOBALS['user']->id, $depth, $seminar_id, $constraint['lft'], $constraint['rgt']]); + } + + if (!$stmt) { + throw new Exception("Error while retrieving postings in " . __FILE__ . " on line " . __LINE__); + } + + return ['list' => ForumEntry::parseEntries($stmt->fetchAll(PDO::FETCH_ASSOC)), 'count' => $count]; + } + + + /** + * Takes a posting-array like the one generated by ForumEntry::getList() + * and adds the child-posting with the freshest creation-date to it. + * + * @param array $postings + * @return array + */ + public static function getLastPostings($postings) + { + foreach ($postings as $key => $posting) { + + if ($data = ForumEntry::getLatestPosting($posting['topic_id'])) { + $last_posting['topic_id'] = $data['topic_id']; + $last_posting['date'] = $data['mkdate']; + $last_posting['user_id'] = $data['user_id']; + $last_posting['user_fullname'] = $data['author']; + $last_posting['username'] = get_username($data['user_id']); + $last_posting['anonymous'] = $data['anonymous']; + + // we throw away all formatting stuff, tags, etc, so we have just the important bit of information + $text = strip_tags($data['name']); + $text = ForumEntry::br2space($text); + $text = ForumEntry::killFormat(ForumEntry::removeQuotes($text)); + + if (mb_strlen($text) > 42) { + $text = mb_substr($text, 0, 40) . '...'; + } + + $last_posting['text'] = $text; + } + + $postings[$key]['last_posting'] = $last_posting; + if (!$postings[$key]['last_unread'] = ForumEntry::getLastUnread($posting['topic_id'])) { + $postings[$key]['last_unread'] = $last_posting['topic_id']; + } + $postings[$key]['num_postings'] = ForumEntry::countEntries($posting['topic_id']); + + unset($last_posting); + } + + return $postings; + } + + /** + * get a list of postings of a special type + * + * @param string $type one of 'area', 'list', 'postings', 'latest', 'favorites', 'dump', 'flat' + * @param string $parent_id the are to fetch from + * @return array array('list' => ..., 'count' => ...); + */ + public static function getList($type, $parent_id) + { + $start = (ForumHelpers::getPage() - 1) * ForumEntry::POSTINGS_PER_PAGE; + + switch ($type) { + case 'area': + $list = ForumEntry::getEntries($parent_id, ForumEntry::WITHOUT_CHILDS, '', 'DESC', 0, 1000); + $postings = $list['list']; + + $postings = ForumEntry::getLastPostings($postings); + return ['list' => $postings, 'count' => $list['count']]; + + break; + + case 'list': + $constraint = ForumEntry::getConstraints($parent_id); + + // purpose of the following query is to retrieve the threads + // for an area ordered by the mkdate of their latest posting + $stmt = DBManager::get()->prepare("SELECT SQL_CALC_FOUND_ROWS + fe.*, IF(ou.topic_id IS NOT NULL, 'fav', NULL) as fav + FROM forum_entries AS fe + LEFT JOIN forum_favorites as ou ON (ou.topic_id = fe.topic_id AND ou.user_id = :user_id) + WHERE fe.seminar_id = :seminar_id AND fe.lft > :left + AND fe.rgt < :right AND fe.depth = 2 + ORDER BY sticky DESC, latest_chdate DESC + LIMIT $start, ". ForumEntry::POSTINGS_PER_PAGE); + $stmt->bindParam(':seminar_id', $constraint['seminar_id']); + $stmt->bindParam(':left', $constraint['lft'], PDO::PARAM_INT); + $stmt->bindParam(':right', $constraint['rgt'], PDO::PARAM_INT); + $stmt->bindParam(':user_id', $GLOBALS['user']->id); + $stmt->execute(); + + $postings = $stmt->fetchAll(PDO::FETCH_ASSOC); + $count = DBManager::get()->query("SELECT FOUND_ROWS()")->fetchColumn(); + $postings = ForumEntry::parseEntries($postings); + $postings = ForumEntry::getLastPostings($postings); + + return ['list' => $postings, 'count' => $count]; + break; + + case 'postings': + return ForumEntry::getEntries($parent_id, ForumEntry::WITH_CHILDS, '', 'ASC', $start); + break; + + case 'newest': + $constraint = ForumEntry::getConstraints($parent_id); + + // get postings + $stmt = DBManager::get()->prepare("SELECT forum_entries.*, IF(ou.topic_id IS NOT NULL, 'fav', NULL) as fav + FROM forum_entries + LEFT JOIN forum_favorites as ou ON (ou.topic_id = forum_entries.topic_id AND ou.user_id = :user_id) + WHERE seminar_id = :seminar_id AND lft > :left + AND rgt < :right AND (mkdate >= :mkdate OR chdate >= :mkdate) + ORDER BY mkdate ASC + LIMIT $start, ". ForumEntry::POSTINGS_PER_PAGE); + + $stmt->bindParam(':seminar_id', $constraint['seminar_id']); + $stmt->bindParam(':left', $constraint['lft']); + $stmt->bindParam(':right', $constraint['rgt']); + $stmt->bindParam(':mkdate', ForumVisit::getLastVisit($constraint['seminar_id'])); + $stmt->bindParam(':user_id', $GLOBALS['user']->id); + $stmt->execute(); + + $postings = $stmt->fetchAll(PDO::FETCH_ASSOC); + + $postings = ForumEntry::parseEntries($postings); + // var_dump($postings); + + // count found postings + $stmt_count = DBManager::get()->prepare("SELECT COUNT(*) + FROM forum_entries + WHERE seminar_id = :seminar_id AND lft > :left + AND rgt < :right AND mkdate >= :mkdate + ORDER BY mkdate ASC"); + + $stmt_count->bindParam(':seminar_id', $constraint['seminar_id']); + $stmt_count->bindParam(':left', $constraint['lft']); + $stmt_count->bindParam(':right', $constraint['rgt']); + $stmt_count->bindParam(':mkdate', ForumVisit::getLastVisit($constraint['seminar_id'])); + $stmt_count->execute(); + + + // return results + return ['list' => $postings, 'count' => $stmt_count->fetchColumn()]; + break; + + case 'latest': + return ForumEntry::getEntries($parent_id, ForumEntry::WITH_CHILDS, '', 'DESC', $start); + break; + + case 'favorites': + $add = "AND ou.topic_id IS NOT NULL"; + return ForumEntry::getEntries($parent_id, ForumEntry::WITH_CHILDS, $add, 'DESC', $start); + break; + + case 'dump': + $constraint = ForumEntry::getConstraints($parent_id); + $seminar_id = $constraint['seminar_id']; + $depth = $constraint['depth'] + 1; + + $stmt = DBManager::get()->prepare("SELECT * FROM forum_entries + WHERE (forum_entries.seminar_id = ? + AND forum_entries.seminar_id != forum_entries.topic_id + AND lft > ? AND rgt < ?) " + . ($depth > 2 ? " OR forum_entries.topic_id = ". DBManager::get()->quote($parent_id) : '') + . " ORDER BY forum_entries.lft ASC"); + $stmt->execute([$seminar_id, $constraint['lft'], $constraint['rgt']]); + + return ForumEntry::parseEntries($stmt->fetchAll(PDO::FETCH_ASSOC)); + break; + + case 'flat': + $constraint = ForumEntry::getConstraints($parent_id); + + $stmt = DBManager::get()->prepare("SELECT SQL_CALC_FOUND_ROWS * FROM forum_entries + WHERE lft > ? AND rgt < ? AND seminar_id = ? AND depth = ? + ORDER BY name ASC"); + $stmt->execute([$constraint['lft'], $constraint['rgt'], $constraint['seminar_id'], $constraint['depth'] + 1]); + + $count = DBManager::get()->query("SELECT FOUND_ROWS()")->fetchColumn(); + + $posting_list = []; + + // speed up things a bit by leaving out the formatReady fields + foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $data) { + // we throw away all formatting stuff, tags, etc, leaving the important bit of information + $desc_short = ForumEntry::br2space(ForumEntry::killFormat(strip_tags($data['content']))); + if (mb_strlen($desc_short) > (ForumEntry::THREAD_PREVIEW_LENGTH + 2)) { + $desc_short = mb_substr($desc_short, 0, ForumEntry::THREAD_PREVIEW_LENGTH) . '...'; + } else { + $desc_short = $desc_short; + } + $posting_list[$data['topic_id']] = [ + 'author' => $data['author'], + 'topic_id' => $data['topic_id'], + 'name_raw' => $data['name'], + 'content_raw' => ForumEntry::killEdit($data['content']), + 'content_short' => $desc_short, + 'chdate' => $data['chdate'], + 'mkdate' => $data['mkdate'], + 'user_id' => $data['user_id'], + 'raw_title' => $data['name'], + 'raw_description' => ForumEntry::killEdit($data['content']), + 'fav' => ($data['fav'] == 'fav'), + 'depth' => $data['depth'], + 'seminar_id' => $data['seminar_id'] + ]; + } + + return ['list' => $posting_list, 'count' => $count]; + break; + + case 'depth_to_large': + $constraint = ForumEntry::getConstraints($parent_id); + + $stmt = DBManager::get()->prepare("SELECT SQL_CALC_FOUND_ROWS * FROM forum_entries + WHERE lft > ? AND rgt < ? AND seminar_id = ? AND depth > 3 + ORDER BY name ASC"); + $stmt->execute([$constraint['lft'], $constraint['rgt'], $constraint['seminar_id']]); + + $count = DBManager::get()->query("SELECT FOUND_ROWS()")->fetchColumn(); + + return ['list' => $stmt->fetchAll(PDO::FETCH_ASSOC), 'count' => $count]; + break; + } + } + + /** + * Get the latest forum entries for the passed entries childs + * + * @param string $parent_id + * @param int $start_date timestamp + * @param int $end_date timestamp + * + * @return array list of postings + */ + public static function getLatestSince($parent_id, $start_date, $end_date) + { + $constraint = ForumEntry::getConstraints($parent_id); + + $stmt = DBManager::get()->prepare("SELECT SQL_CALC_FOUND_ROWS * FROM forum_entries + WHERE lft > ? AND rgt < ? AND seminar_id = ? + AND mkdate BETWEEN ? AND ? + ORDER BY name ASC"); + $stmt->execute([$constraint['lft'], $constraint['rgt'], $constraint['seminar_id'], $start_date, $end_date]); + + return $stmt->fetchAll(PDO::FETCH_ASSOC); + } + + /** + ** returns a list of postings for the passed search-term + * + * @param string $parent_id the area to search in (can be a whole seminar) + * @param string $_searchfor the term to search for + * @param array $options filter-options: search_title, search_content, search_author + * @return array array('list' => ..., 'count' => ...); + */ + public static function getSearchResults($parent_id, $_searchfor, $options) + { + $start = (ForumHelpers::getPage() - 1) * ForumEntry::POSTINGS_PER_PAGE; + + // if there are quoted parts, they should not be separated + $suchmuster = '/".*"/U'; + preg_match_all($suchmuster, $_searchfor, $treffer); + array_walk($treffer[0], function(&$value) { $value = trim($value, '"'); }); + + // remove the quoted parts from $_searchfor + $_searchfor = trim(preg_replace($suchmuster, '', $_searchfor)); + + // split the searchstring $_searchfor at every space + $parts = explode(' ', $_searchfor); + + foreach ($parts as $key => $val) { + if ($val == '') { + unset($parts[$key]); + } + } + + if (!empty($parts)) { + $_searchfor = array_merge($parts, $treffer[0]); + } else { + $_searchfor = $treffer[0]; + } + + // make an SQL-statement out of the searchstring + $search_string = []; + foreach ($_searchfor as $key => $val) { + if (!$val) { + unset($_searchfor[$key]); + } else { + $search_word = '%'. $val .'%'; + $zw_search_string = []; + if ($options['search_title']) { + $zw_search_string[] .= "name LIKE " . DBManager::get()->quote($search_word); + } + + if ($options['search_content']) { + $zw_search_string[] .= "content LIKE " . DBManager::get()->quote($search_word); + } + + if ($options['search_author']) { + $zw_search_string[] .= "author LIKE " . DBManager::get()->quote($search_word); + } + + if (!empty($zw_search_string)) { + $search_string[] = '(' . implode(' OR ', $zw_search_string) . ')'; + } + } + } + + if (!empty($search_string)) { + $add = "AND (" . implode(' AND ', $search_string) . ")"; + return array_merge( + ['highlight' => $_searchfor], + ForumEntry::getEntries($parent_id, ForumEntry::WITH_CHILDS, $add, 'DESC', $start) + ); + } + + return ['num_postings' => 0, 'list' => []]; + } + + /** + * returns the entry for the passed topic_id + * + * @param string $topic_id + * @return array hash-array with the entries fields + */ + public static function getEntry($topic_id) + { + return ForumEntry::getConstraints($topic_id); + } + + /** + * Count the number of child-elements that the passed entry has and return it. + * + * @param string $parent_id + * + * @return int the number of child entries for the passed entry + */ + public static function countEntries($parent_id) + { + $data = ForumEntry::getConstraints($parent_id); + return max((($data['rgt'] - $data['lft'] - 1) / 2) + 1, 0); + } + + /** + * Count the number of postings in a given course and return it. + * + * @param string $course_id the id of the given course + * + * @return int the number of postings in the course + */ + public static function countPostings($course_id) + { + $stmt = DBManager::get()->prepare("SELECT COUNT(*) FROM forum_entries + WHERE seminar_id = ? AND depth >= 2"); + $stmt->execute([$course_id]); + + return $stmt->fetchColumn(0); + } + + /** + * Count all entries the passed user has ever written and return the result + * + * @staticvar type $entries + * + * @param string $user_id + * + * @return int number of entries user has ever written + */ + public static function countUserEntries($user_id, $seminar_id = null) + { + static $entries; + + if (!$entries[$user_id]) { + $stmt = DBManager::get()->prepare("SELECT COUNT(*) + FROM forum_entries + WHERE user_id = ? AND seminar_id = IFNULL(?, seminar_id)"); + $stmt->execute([$user_id, $seminar_id]); + + $entries[$user_id] = $stmt->fetchColumn(); + } + + return $entries[$user_id]; + } + + /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * D A T A - C R E A T I O N * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + + /** + * insert a node into the table + * + * @param array $data an array containing the following fields: + * topic_id the id of the new topic + * seminar_id the id of the seminar to add the topic to + * user_id the id of the user who created the topic + * name the title of the entry + * content the content of the entry + * author the author's name as a plaintext string + * author_host ip-address of creator + * @param string $parent_id the node to add the topic to + * + * @return void + */ + public static function insert($data, $parent_id) + { + $constraint = ForumEntry::getConstraints($parent_id); + + // #TODO: Zusammenfassen in eine Transaktion!!! + DBManager::get()->exec('UPDATE forum_entries SET lft = lft + 2 + WHERE lft > '. $constraint['rgt'] ." AND seminar_id = '". $constraint['seminar_id'] ."'"); + DBManager::get()->exec('UPDATE forum_entries SET rgt = rgt + 2 + WHERE rgt >= '. $constraint['rgt'] ." AND seminar_id = '". $constraint['seminar_id'] ."'"); + + $stmt = DBManager::get()->prepare("INSERT INTO forum_entries + (topic_id, seminar_id, user_id, name, content, mkdate, latest_chdate, + chdate, author, author_host, lft, rgt, depth, anonymous) + VALUES (? ,?, ?, ?, ?, UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), ?, ?, ?, ?, ?, ?)"); + $stmt->execute([$data['topic_id'], $data['seminar_id'], $data['user_id'], + $data['name'], transformBeforeSave($data['content']), $data['author'], $data['author_host'], + $constraint['rgt'], $constraint['rgt'] + 1, $constraint['depth'] + 1, $data['anonymous'] ? : 0]); + + // update "latest_chdate" for easier sorting of actual threads + DBManager::get()->exec("UPDATE forum_entries SET latest_chdate = UNIX_TIMESTAMP() + WHERE topic_id = '" . $constraint['topic_id'] . "'"); + + NotificationCenter::postNotification('ForumAfterInsert', $data['topic_id'], $data); + } + + + /** + * update the passed topic + * + * @param string $topic_id the id of the topic to update + * @param string $name the new name + * @param string $content the new content + * + * @return void + */ + public static function update($topic_id, $name, $content) + { + $post = ForumEntry::getConstraints($topic_id); + + if (time() - $post['mkdate'] > 5 * 60) { + $content = ForumEntry::appendEdit($content); + } + + $stmt = DBManager::get()->prepare("UPDATE forum_entries + SET name = ?, content = ?, chdate = UNIX_TIMESTAMP(), latest_chdate = UNIX_TIMESTAMP() + WHERE topic_id = ?"); + $stmt->execute([$name, transformBeforeSave($content), $topic_id]); + + // update "latest_chdate" for easier sorting of actual threads + $parent_id = ForumEntry::getParentTopicId($topic_id); + DBManager::get()->exec("UPDATE forum_entries SET latest_chdate = UNIX_TIMESTAMP() + WHERE topic_id = '" . $parent_id . "'"); + + $post['name'] = $name; + $post['content'] = $content; + + NotificationCenter::postNotification('ForumAfterUpdate', $topic_id, $post); + } + + /** + * delete an entry and all his descendants from the mptt-table + * + * @param string $topic_id the id of the entry to delete + * + * @return void + */ + public static function delete($topic_id) + { + $post = ForumEntry::getConstraints($topic_id); + $parent = ForumEntry::getConstraints(ForumEntry::getParentTopicId($topic_id)); + + NotificationCenter::postNotification('ForumBeforeDelete', $topic_id, $post); + + // #TODO: Zusammenfassen in eine Transaktion!!! + // get all entry-ids to delete them from the category-reference-table + $stmt = DBManager::get()->prepare("SELECT topic_id FROM forum_entries + WHERE seminar_id = ? AND lft >= ? AND rgt <= ? AND depth = 1"); + $stmt->execute([$post['seminar_id'], $post['lft'], $post['rgt']]); + $ids = $stmt->fetchAll(PDO::FETCH_COLUMN); + + if ($ids != false && !is_array($ids)) $ids = [$ids]; + + if (!empty($ids)) { + $stmt = DBManager::get()->prepare("DELETE FROM forum_categories_entries + WHERE topic_id IN (:ids)"); + $stmt->bindParam(':ids', $ids, StudipPDO::PARAM_ARRAY); + $stmt->execute(); + } + + // delete all entries + $stmt = DBManager::get()->prepare("DELETE FROM forum_entries + WHERE seminar_id = ? AND lft >= ? AND rgt <= ?"); + + $stmt->execute([$post['seminar_id'], $post['lft'], $post['rgt']]); + + // update lft and rgt + $diff = $post['rgt'] - $post['lft'] + 1; + $stmt = DBManager::get()->prepare("UPDATE forum_entries SET lft = lft - $diff + WHERE lft > ? AND seminar_id = ?"); + $stmt->execute([$post['rgt'], $post['seminar_id']]); + + $stmt = DBManager::get()->prepare("UPDATE forum_entries SET rgt = rgt - $diff + WHERE rgt > ? AND seminar_id = ?"); + $stmt->execute([$post['rgt'], $post['seminar_id']]); + + + // set the latest_chdate to the latest child's chdate + $stmt = DBManager::get()->prepare("SELECT chdate FROM forum_entries + WHERE lft > ? AND rgt < ? AND seminar_id = ? + ORDER BY chdate DESC LIMIT 1"); + $stmt->execute([$parent['lft'], $parent['rgt'], $parent['seminar_id']]); + $chdate = $stmt->fetchColumn(); + + $stmt_insert = DBManager::get()->prepare("UPDATE forum_entries + SET chdate = ? WHERE topic_id = ?"); + if ($chdate) { + $stmt_insert->execute([$chdate, $parent['topic_id']]); + } else { + $stmt_insert->execute([$parent['chdate'], $parent['topic_id']]); + } + } + + /** + * move the passed topic to the passed area + * + * @param string $topic_id the topic to move + * @param string $destination the area_id where the topic is moved to + * + * @return void + */ + public static function move($topic_id, $destination) + { + // #TODO: Zusammenfassen in eine Transaktion!!! + $constraints = ForumEntry::getConstraints($topic_id); + + // move the affected entries "outside" the tree + $stmt = DBManager::get()->prepare("UPDATE forum_entries + SET lft = lft * -1, rgt = rgt * -1 + WHERE seminar_id = ? AND lft >= ? AND rgt <= ?"); + $stmt->execute([$constraints['seminar_id'], $constraints['lft'], $constraints['rgt']]); + + // update the lft and rgt values of the parent to reflect the "deletion" + $diff = $constraints['rgt'] - $constraints['lft'] + 1; + $stmt = DBManager::get()->prepare("UPDATE forum_entries SET lft = lft - ? + WHERE lft > ? AND seminar_id = ?"); + $stmt->execute([$diff, $constraints['rgt'], $constraints['seminar_id']]); + + $stmt = DBManager::get()->prepare("UPDATE forum_entries SET rgt = rgt - ? + WHERE rgt > ? AND seminar_id = ?"); + $stmt->execute([$diff, $constraints['rgt'], $constraints['seminar_id']]); + + // make some space by updating the lft and rgt values of the target node + $constraints_destination = ForumEntry::getConstraints($destination); + $size = $constraints['rgt'] - $constraints['lft'] + 1; + + $stmt = DBManager::get()->prepare("UPDATE forum_entries SET lft = lft + ? + WHERE lft > ? AND seminar_id = ?"); + $stmt->execute([$size, $constraints_destination['rgt'], $constraints_destination['seminar_id']]); + + $stmt = DBManager::get()->prepare("UPDATE forum_entries SET rgt = rgt + ? + WHERE rgt >= ? AND seminar_id = ?"); + $stmt->execute([$size, $constraints_destination['rgt'], $constraints_destination['seminar_id']]); + + //move the entries from "outside" the tree to the target node + $constraints_destination = ForumEntry::getConstraints($destination); + + + // update the depth to reflect the new position in the tree + // determine if we need to add, subtract or even do nothing to/from the depth + $depth_mod = $constraints_destination['depth'] - $constraints['depth'] + 1; + + $stmt = DBManager::get()->prepare("UPDATE forum_entries + SET depth = depth + ? + WHERE seminar_id = ? AND lft < 0"); + $stmt->execute([$depth_mod, $constraints_destination['seminar_id']]); + + // if the depth is larger than 3, fix it + $stmt = DBManager::get()->prepare("UPDATE forum_entries + SET depth = 3 + WHERE seminar_id = ? AND depth > 3 AND lft < 0"); + $stmt->execute([$constraints_destination['seminar_id']]); + + // move the tree to its destination + $diff = ($constraints_destination['rgt'] - ($constraints['rgt'] - $constraints['lft'])) - 1 - $constraints['lft']; + + $stmt = DBManager::get()->prepare("UPDATE forum_entries + SET lft = (lft * -1) + ?, rgt = (rgt * -1) + ? + WHERE seminar_id = ? AND lft < 0"); + $stmt->execute([$diff, $diff, $constraints_destination['seminar_id']]); + + if ($depth_mod != 0) { + self::fix_ordering($topic_id); + } + } + + private static function fix_ordering($parent_id) + { + $db = DBManager::get(); + + $entry = ForumEntry::getConstraints($parent_id); + + $stmt= $db->prepare('SELECT topic_id FROM forum_entries + WHERE lft > ? AND rgt < ? AND depth = 3 + AND seminar_id = ? + ORDER BY mkdate'); + + $stmt->execute([$entry['lft'], $entry['rgt'], $entry['seminar_id']]); + + $lft = $entry['lft'] + 1; + $rgt = $lft + 1; + + $inner_stmt = $db->prepare("UPDATE forum_entries SET lft=?, rgt=? + WHERE topic_id = ?"); + while ($topic_id = $stmt->fetchColumn()) { + $inner_stmt->execute([$lft, $rgt, $topic_id]); + + $lft += 2; + $rgt += 2; + } + } + + /** + * close the passed topic + * + * @param string $topic_id the topic to close + * + * @return void + */ + public static function close($topic_id) + { + // close all entries belonging to the topic + $stmt = DBManager::get()->prepare("UPDATE forum_entries + SET closed = 1 + WHERE topic_id = ?"); + $stmt->execute([$topic_id]); + } + + /** + * open the passed topic + * + * @param string $topic_id the topic to open + * + * @return void + */ + public static function open($topic_id) + { + // open all entries belonging to the topic + $stmt = DBManager::get()->prepare("UPDATE forum_entries + SET closed = 0 + WHERE topic_id = ?"); + $stmt->execute([$topic_id]); + } + + /** + * make the passed topic sticky + * + * @param string $topic_id the topic to make sticky + * + * @return void + */ + public static function sticky($topic_id) + { + // open all entries belonging to the topic + $stmt = DBManager::get()->prepare("UPDATE forum_entries + SET sticky = 1 + WHERE topic_id = ?"); + $stmt->execute([$topic_id]); + } + + /** + * make the passed topic unsticky + * + * @param string $topic_id the topic to make unsticky + * + * @return void + */ + public static function unsticky($topic_id) + { + // open all entries belonging to the topic + $stmt = DBManager::get()->prepare("UPDATE forum_entries + SET sticky = 0 + WHERE topic_id = ?"); + $stmt->execute([$topic_id]); + } + + /** + * check, if the default root-node for this seminar exists and make sure + * the default category exists as well + * + * @param string $seminar_id + * + * @return void + */ + public static function checkRootEntry($seminar_id) + { + setTempLanguage(); + + // check, if the root entry in the topic tree exists + $stmt = DBManager::get()->prepare("SELECT COUNT(*) FROM forum_entries + WHERE topic_id = ? AND seminar_id = ?"); + $stmt->execute([$seminar_id, $seminar_id]); + if ($stmt->fetchColumn() == 0) { + $stmt = DBManager::get()->prepare("INSERT INTO forum_entries + (topic_id, seminar_id, name, mkdate, chdate, lft, rgt, depth) + VALUES (?, ?, 'Übersicht', UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), 0, 1, 0)"); + $stmt->execute([$seminar_id, $seminar_id]); + } + + + // make sure, that the category "Allgemein" exists + $stmt = DBManager::get()->prepare("INSERT IGNORE INTO forum_categories + (category_id, seminar_id, entry_name) VALUES (?, ?, ?)"); + $stmt->execute([$seminar_id, $seminar_id, _('Allgemein')]); + + // make sure that the default area "Allgemeine Diskussionen" exists, if there is nothing else present + $stmt = DBManager::get()->prepare("SELECT COUNT(*) FROM forum_entries + WHERE seminar_id = ? AND depth = 1"); + $stmt->execute([$seminar_id]); + + // add default area + if ($stmt->fetchColumn() == 0) { + $data = [ + 'topic_id' => md5(uniqid()), + 'seminar_id' => $seminar_id, + 'user_id' => '', + 'name' => _('Allgemeine Diskussion'), + 'content' => _('Hier ist Raum für allgemeine Diskussionen'), + 'author' => '', + 'author_host' => '' + ]; + ForumEntry::insert($data, $seminar_id); + } + + restoreLanguage(); + } + + /** + * returns the ten most active seminars + * + * @return array + */ + public static function getTopTenSeminars() + { + return DBManager::get()->query("SELECT a.seminar_id, b.name AS display, + count( a.seminar_id ) AS count FROM forum_entries a + INNER JOIN seminare b USING ( seminar_id ) + WHERE b.visible = 1 + AND a.mkdate > UNIX_TIMESTAMP( NOW( ) - INTERVAL 2 WEEK ) + GROUP BY a.seminar_id + ORDER BY count DESC + LIMIT 10")->fetchAll(PDO::FETCH_ASSOC); + } + + /** + * count all entries that exists in the whole installation and return it. + * + * @return int + */ + public static function countAllEntries() + { + return count_table_rows('forum_entries'); + } + + /** + * updates the user-entries and replaces the old user-id by the new one + * + * @param string $user_from + * @param string $user_to + */ + public static function migrateUser($user_from, $user_to) + { + $stmt = DBManager::get()->prepare("UPDATE forum_entries SET user_id = ? WHERE user_id = ?"); + $stmt->execute([$user_to, $user_from]); + + $stmt = DBManager::get()->prepare("UPDATE IGNORE forum_favorites SET user_id = ? WHERE user_id = ?"); + $stmt->execute([$user_to, $user_from]); + + $stmt = DBManager::get()->prepare("UPDATE IGNORE forum_visits SET user_id = ? WHERE user_id = ?"); + $stmt->execute([$user_to, $user_from]); + + $stmt = DBManager::get()->prepare("UPDATE IGNORE forum_likes SET user_id = ? WHERE user_id = ?"); + $stmt->execute([$user_to, $user_from]); + + $stmt = DBManager::get()->prepare("UPDATE IGNORE forum_abo_users SET user_id = ? WHERE user_id = ?"); + $stmt->execute([$user_to, $user_from]); + } + + /** + * returns the complete seminar or only the passed sub-tree as a html-string + * + * @param string $seminar_id + * + * @return string + */ + public static function getDump($seminar_id, $parent_id = null) + { + $seminar_name = get_object_name($seminar_id, 'sem'); + $content = '

'. _('Forum') .': ' . $seminar_name['name'] .'

'; + $data = ForumEntry::getList('dump', $parent_id ?: $seminar_id); + + foreach ($data as $entry) { + if ($entry['depth'] == 1) { + $content .= '

'. _('Bereich') .': '. $entry['name'] .'

'; + $content .= $entry['content'] .'

'; + } else if ($entry['depth'] == 2) { + $content .= '

'. _('Thema') .': '. $entry['name'] .'

'; + $content .= '' . sprintf(_('erstellt von %s am %s'), htmlReady($entry['author']), + strftime('%A %d. %B %Y, %H:%M', (int)$entry['mkdate'])) . '
'; + $content .= $entry['content'] .'

'; + } else if ($entry['depth'] == 3) { + $content .= ''.$entry['name'] .'
'; + $content .= '' . sprintf(_('erstellt von %s am %s'), htmlReady($entry['author']), + strftime('%A %d. %B %Y, %H:%M', (int)$entry['mkdate'])) . '
'; + $content .= $entry['content'] .'

'; + } + } + + return $content; + } + + public static function isClosed($topic_id) + { + foreach(ForumEntry::getPathToPosting($topic_id) as $entry) { + if ($entry['closed']) { + return true; + } + } + + return false; + } + + /** + * Export available data of a given user into a storage object + * (an instance of the StoredUserData class) for that user. + * + * @param StoredUserData $storage object to store data into + */ + public static function exportUserData(StoredUserData $storage) + { + $field_data = DBManager::get()->fetchAll("SELECT * FROM forum_entries WHERE user_id = ?", [$storage->user_id]); + if ($field_data) { + $storage->addTabularData(_('Forum Einträge'), 'forum_entries', $field_data); + } + } + +} diff --git a/lib/classes/ForumFavorite.php b/lib/classes/ForumFavorite.php new file mode 100644 index 0000000..9ffebf5 --- /dev/null +++ b/lib/classes/ForumFavorite.php @@ -0,0 +1,41 @@ + + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL version 3 + * @category Stud.IP + */ + +class ForumFavorite { + + /** + * Set the topic denoted by the passed id as favorite for the + * currently logged in user + * + * @param string $topic_id + */ + static function set($topic_id) { + $stmt = DBManager::get()->prepare("REPLACE INTO + forum_favorites (topic_id, user_id) + VALUES (?, ?)"); + $stmt->execute([$topic_id, $GLOBALS['user']->id]); + } + + /** + * Remove the topic denoted by the passed id as favorite for the + * currently logged in user + * + * @param string $topic_id + */ + static function remove($topic_id) { + $stmt = DBManager::get()->prepare("DELETE FROM forum_favorites + WHERE topic_id = ? AND user_id = ?"); + $stmt->execute([$topic_id, $GLOBALS['user']->id]); + } +} \ No newline at end of file diff --git a/lib/classes/ForumHelpers.php b/lib/classes/ForumHelpers.php new file mode 100644 index 0000000..ed478ad --- /dev/null +++ b/lib/classes/ForumHelpers.php @@ -0,0 +1,282 @@ + + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL version 3 + * @category Stud.IP + */ + +class ForumHelpers { + + /** + * The page for the current script run, modified by a global page-handle + * @var int + */ + static $page = 1; + + /** + * helper_function for highlight($text, $highlight) + * + * @param string $text + * @param array $highlight + * @return string + */ + public static function do_highlight($text, $highlight) + { + foreach ($highlight as $hl) { + $text = preg_replace( + '/' . preg_quote(htmlReady($hl), '/') . '/i', + '$0', + $text + ); + } + return $text; + } + + /** + * This function highlights Text HTML-safe + * (tags or words in tags are not highlighted, words between tags ARE highlighted) + * + * @param string $text the text where to words shall be highlighted, may contain tags + * @param array $highlight an array of words to be highlighted + * @return string the highlighted text + */ + public static function highlight($text, $highlight) + { + if (empty($highlight)) { + return $text; + } + + $data = []; + $treffer = []; + + // split text at every tag + $pattern = '/<[^<]*>/U'; + preg_match_all($pattern, $text, $treffer, PREG_OFFSET_CAPTURE); + + if (sizeof($treffer[0]) == 0) { + return self::do_highlight($text, $highlight); + } + + // cycle trough the text between the tags and highlight all hits + $last_pos = 0; + foreach ($treffer[0] as $taginfo) { + $size = mb_strlen($taginfo[0]); + if ($taginfo[1] != 0) { + $data[] = self::do_highlight(mb_substr($text, $last_pos, $taginfo[1] - $last_pos), $highlight); + } + + $data[] = mb_substr($text, $taginfo[1], $size); + $last_pos = $taginfo[1] + $size; + } + + // don't miss the last portion of a posting + if ($last_pos < mb_strlen($text)) { + $data[] = self::do_highlight(mb_substr($text, $last_pos, mb_strlen($text) - $last_pos), $highlight); + } + + return implode('', $data); + } + + /** + * Returns a human-readable version of the passed global Stud.IP permission. + * + * @param string $perm + * @return string + */ + public static function translate_perm($perm) + { + $mapping = [ + 'root' => _('Root'), + 'admin' => _('Administrator/-in'), + 'dozent' => _('Lehrende/-r'), + 'tutor' => _('Tutor/-in'), + 'autor' => _('Autor/-in'), + 'user' => _('Leser/-in'), + ]; + + // TODO: Activate next when devboard reliably runs on PHP7 + // return $mapping[$perm] ?? ''; + + return isset($mapping[$perm]) ? $mapping[$perm] : ''; + } + + /** + * return the currently chosen page + * + * @return int + */ + public static function getPage() + { + return self::$page; + } + + /** + * set the current page + * + * @param int $page_num the page + */ + public static function setPage($page_num) + { + self::$page = $page_num; + } + + /** + * Return an info-text explaining the visit-status of the passed topic_di + * which has the passed number of new entries. + * + * @param string $num_entries the number of new entries + * @param string $topic_id the id of the topic + * + * @return string a human readable, localized text + */ + public static function getVisitText($num_entries, $topic_id) + { + if ($num_entries > 0) { + $text = sprintf(_('Seit Ihrem letzten Besuch gibt es %s neue Beiträge'), $num_entries); + } else { + $all_entries = ForumEntry::countPostings($topic_id); + + if ($all_entries == 0) { + $text = sprintf(_('Es gibt bisher keine Beiträge.')); + } else if ($all_entries == 1) { + $text = sprintf(_('Seit Ihrem letzten Besuch gab es nichts Neues.' + . ' Es ist ein alter Beitrag vorhanden.')); + } else { + $text = sprintf(_('Seit Ihrem letzten Besuch gab es nichts Neues.' + . ' Es sind %s alte Beiträge vorhanden.'), $all_entries); + } + } + + return $text; + } + + /** + * return the online status of the passed user, one of three possible + * states is returned: + * - available + * - away + * - offline + * + * @staticvar type $online_status + * + * @param string $user_id + * + * @return string + */ + public static function getOnlineStatus($user_id) + { + static $online_status; + + // check if the corresponding user's profile is visible + if (get_visibility_by_id($user_id) == false) { + return 'offline'; + } + + if ($GLOBALS['user']->id == $user_id) { + return 'available'; + } + + if (!$online_status) { + $online_users = get_users_online(10); + foreach ($online_users as $username => $data) { + if ($data['last_action'] >= 300) { + $online_status[$data['user_id']] = 'away'; + } else { + $online_status[$data['user_id']] = 'available'; + } + } + } + + return $online_status[$user_id] ?: 'offline'; + } + + /** + * Create a pdf of all postings belonging to the passed seminar located + * under the passed topic_id. The PDF is dispatched automatically. + * + * BEWARE: This function never returns, it dies after the PDF has been + * (succesfully or not) dispatched. + * + * @param string $seminar_id + * @param string $parent_id + */ + public static function createPdf($seminar_id, $parent_id = null) + { + $seminar_name = get_object_name($seminar_id, 'sem'); + $data = ForumEntry::getList('dump', $parent_id ?: $seminar_id); + $first_page = true; + + $document = new ExportPDF(); + $document->SetTitle(_('Forum')); + $document->setHeaderTitle(sprintf(_("Forum \"%s\""), $seminar_name['name'])); + $document->addPage(); + + foreach ($data as $entry) { + if (Config::get()->FORUM_ANONYMOUS_POSTINGS && $entry['anonymous']) { + $author = _('anonym'); + } else { + $author = $entry['author']; + } + if ($entry['depth'] == 1) { + if (!$first_page) { + $document->addPage(); + } + $first_page = false; + $document->addContent('!! '. _('Bereich') . ": {$entry['name_raw']}\n"); + $document->addContent($entry['content_raw']); + $document->addContent("\n\n"); + } else if ($entry['depth'] == 2) { + $document->addContent('! '. _('Thema') . ": {$entry['name_raw']}\n"); + $document->addContent('%%' . sprintf(_('erstellt von %s am %s'), $author, + strftime('%A %d. %B %Y, %H:%M', (int)$entry['mkdate'])) . '%%' . "\n"); + $document->addContent($entry['content_raw']); + $document->addContent("\n--\n"); + } else if ($entry['depth'] == 3) { + $document->addContent("**{$entry['name_raw']}**\n"); + $document->addContent('%%' . sprintf(_('erstellt von %s am %s'), $author, + strftime('%A %d. %B %Y, %H:%M', (int)$entry['mkdate'])) . '%%' . "\n"); + $document->addContent($entry['content_raw']); + $document->addContent("\n--\n"); + } + } + + $document->dispatch($seminar_name['name'] ." - Forum"); + die; + } + + + /** + * Returns the id of the currently selected seminar or false, if no seminar + * is selected + * + * @return mixed seminar_id or false + */ + public static function getSeminarId() + { + return Context::getId(); + } + + /** + * replace in the passed text every %%% with <% and every ### with %> + * This is used to work around a limitation of the Button-API in combination + * with the underscore.js way of inserting template vars. + * + * The Button-API correctly replaces < > with tags, but underscore.js is + * unable to find them in their tag-represenation + * + * @param string $text the text to apply the replacements on + * + * @return string the modified text + */ + public static function replace($text) + { + return str_replace('%%%', '<%', str_replace('###', '%>', $text)); + } +} diff --git a/lib/classes/ForumIssue.php b/lib/classes/ForumIssue.php new file mode 100644 index 0000000..8be894f --- /dev/null +++ b/lib/classes/ForumIssue.php @@ -0,0 +1,96 @@ + + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL version 3 + * @category Stud.IP + */ + +class ForumIssue +{ + /** + * Get the id of the topic linked to the issue denoted by the passed id. + * + * @param string $issue_id + * @return string the id of the linked topic + */ + static function getThreadIdForIssue($issue_id) + { + $stmt = DBManager::get()->prepare("SELECT topic_id FROM forum_entries_issues + WHERE issue_id = ?"); + $stmt->execute([$issue_id]); + + return ($stmt->fetchColumn()); + } + + + /** + * Get the id of the issue linked to the topic denoted by the passed id. + * + * @param string $topic_id + * @return string the id of the linked topic + */ + static function getIssueIdForThread($topic_id) + { + $stmt = DBManager::get()->prepare("SELECT issue_id FROM forum_entries_issues + WHERE topic_id = ?"); + $stmt->execute([$topic_id]); + + return ($stmt->fetchColumn()); + } + + + /** + * Create/Update the linked posting for the passed issue_id + * + * @param string $seminar_id + * @param string $issue_id issue id to link to + * @param string $title (new) title of the posting + * @param string $content (new) content of the posting + */ + static function setThreadForIssue($seminar_id, $issue_id, $title, $content) + { + if ($topic_id = self::getThreadIdForIssue($issue_id)) { // update + ForumEntry::update($topic_id, $title ?: _('Ohne Titel'), $content); + + } else { // create + // make sure the forum is set up properly + ForumEntry::checkRootEntry($seminar_id); + + $topic_id = md5(uniqid(rand())); + + ForumEntry::insert([ + 'topic_id' => $topic_id, + 'seminar_id' => $seminar_id, + 'user_id' => $GLOBALS['user']->id, + 'name' => $title ?: _('Ohne Titel'), + 'content' => $content, + 'author' => get_fullname($GLOBALS['user']->id), + 'author_host' => ($GLOBALS['user']->id == 'nobody') ? getenv('REMOTE_ADDR') : '' + ], $seminar_id); + + $stmt = DBManager::get()->prepare("INSERT INTO forum_entries_issues + (issue_id, topic_id) VALUES (?, ?)"); + $stmt->execute([$issue_id, $topic_id]); + } + } + + /** + * Remove the link for the posting denoted by the passed topic_id + * + * @param object $notification + * @param string $topic_id + */ + static function unlinkIssue($notification, $topic_id) + { + $stmt = DBManager::get()->prepare("DELETE FROM forum_entries_issues + WHERE topic_id = ?"); + $stmt->execute([$topic_id]); + } +} diff --git a/lib/classes/ForumLike.php b/lib/classes/ForumLike.php new file mode 100644 index 0000000..24cb478 --- /dev/null +++ b/lib/classes/ForumLike.php @@ -0,0 +1,99 @@ + + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL version 3 + * @category Stud.IP + */ + +class ForumLike { + + /** + * Set the posting denoted by the passed topic_id as liked for the + * currently logged in user + * + * @param string $topic_id + */ + static function like($topic_id) { + $stmt = DBManager::get()->prepare("REPLACE INTO + forum_likes (topic_id, user_id) + VALUES (?, ?)"); + $stmt->execute([$topic_id, $GLOBALS['user']->id]); + + // get posting owner + $data = ForumEntry::getConstraints($topic_id); + + // notify owner of posting about the like + setTempLanguage($data['user_id']); + $notification = get_fullname($GLOBALS['user']->id) . _(' gefällt einer deiner Forenbeiträge!'); + restoreLanguage(); + + PersonalNotifications::add( + $data['user_id'], URLHelper::getURL('dispatch.php/course/forum/index/index/' . $topic_id .'?highlight_topic='. $topic_id .'#'. $topic_id), + $notification, $topic_id, + Icon::create('forum', 'clickable') + ); + } + + /** + * Revoke the liking of the posting denoted by the passed topic_id for the + * currently logged in user + * + * @param string $topic_id + */ + static function dislike($topic_id) { + $stmt = DBManager::get()->prepare("DELETE FROM forum_likes + WHERE topic_id = ? AND user_id = ?"); + $stmt->execute([$topic_id, $GLOBALS['user']->id]); + } + + /** + * Get the user_id for all likers of the topic denoted by the passed id + * + * @param string $topic_id + * @return array an array of user_id's + */ + static function getLikes($topic_id) { + $stmt = DBManager::get()->prepare("SELECT + auth_user_md5.user_id FROM forum_likes + LEFT JOIN auth_user_md5 USING (user_id) + LEFT JOIN user_info USING (user_id) + WHERE topic_id = ?"); + $stmt->execute([$topic_id]); + + return $stmt->fetchAll(PDO::FETCH_COLUMN); + } + + /** + * count the number of likes the user has received - system-wide + * + * @staticvar type $entries + * @param string $user_id the user's id to count the received likes for + * + * @return int the number of likes received + */ + static function receivedForUser($user_id) + { + static $entries; + + if (!$entries[$user_id]) { + $stmt = DBManager::get()->prepare("SELECT COUNT(*) + FROM forum_entries + LEFT JOIN forum_likes USING (topic_id) + WHERE forum_entries.user_id = ? + AND forum_likes.topic_id IS NOT NULL + AND forum_likes.user_id != ?"); + $stmt->execute([$user_id, $user_id]); + + $entries[$user_id] = $stmt->fetchColumn(); + } + + return $entries[$user_id]; + } +} diff --git a/lib/classes/ForumPerm.php b/lib/classes/ForumPerm.php new file mode 100644 index 0000000..a5441fa --- /dev/null +++ b/lib/classes/ForumPerm.php @@ -0,0 +1,215 @@ + + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL version 3 + * @category Stud.IP + */ + +class ForumPerm { + + /** + * Check, if the a user has the passed permission in a seminar. + * Possible permissions are: + * edit_category - Editing the name of a category
+ * add_category - Adding a new category
+ * remove_category - Removing an existing category
+ * sort_category - Sorting categories
+ * edit_area - Editing an area (title + content)
+ * add_area - Adding a new area
+ * remove_area - Removing an area and all belonging threads
+ * sort_area - Sorting of areas in categories and between categories
+ * search - Searching in postings
+ * edit_entry - Editing of foreign threads/postings
+ * add_entry - Creating a new thread/posting
+ * remove_entry - Removing of foreign threads/postings
+ * fav_entry - Marking a Posting as "favorite"
+ * like_entry - Liking a posting
+ * move_thread - Moving a thrad between ares
+ * close_thread - Close or open a thread
+ * make_sticky - Make a thread sticky
+ * abo - Signing up for mail-notifications for new entries
+ * forward_entry - Forwarding an existing entry as a message
+ * pdfexport - Exporting parts of the forum as PDF
+ * admin - Allowed to mass-administrate the forum
+ * view - Allowed to view the forum at all
+ * edit_closed - Editing entries in a closed thread + * + * @param string $perm one of the modular permissions + * @param string $seminar_id the seminar to check for + * @param string $user_id the user to check for + * @return boolean true, if the user has the perms, false otherwise + */ + public static function has($perm, $seminar_id, $user_id = null) + { + static $permissions = []; + + // if no user-id is passed, use the current user (for your convenience) + if (!$user_id) { + $user_id = $GLOBALS['user']->id; + } + + // get the status for the user in the passed seminar + if (!$permissions[$seminar_id][$user_id]) { + $permissions[$seminar_id][$user_id] = $GLOBALS['perm']->get_studip_perm($seminar_id, $user_id); + } + + $status = $permissions[$seminar_id][$user_id]; + + // take care of the not logged in user + if ($user_id == 'nobody' || $status == false) { + // which status has nobody - read only or read/write? + if (get_object_type($seminar_id) == 'sem') { + $sem = Seminar::getInstance($seminar_id); + + if ($sem->write_level == 0) { + $status = 'nobody_write'; + } else if ($sem->read_level == 0) { + $status = 'nobody_read'; + } else { + return false; + } + } else { + return false; + } + } + + // root and admins have all possible perms + if (in_array($status, words('root admin')) !== false) { + return true; + } + + // eCULT Notlösung + if ($status == 'tutor' && $seminar_id == '30e0b89dcc9173d5fccf9f22b13b87bd') { + $status = 'autor'; + } + + // check the status and the passed permission + if (($status == 'dozent' || $status == 'tutor') && in_array($perm, + words('edit_category add_category remove_category sort_category ' + . 'edit_area add_area remove_area sort_area ' + . 'search edit_entry add_entry remove_entry fav_entry like_entry move_thread ' + . 'make_sticky close_thread abo forward_entry pdfexport view edit_closed') + ) !== false) { + return true; + } else if ($status == 'autor' && in_array($perm, words('search add_entry fav_entry like_entry forward_entry abo pdfexport view')) !== false) { + return true; + } else if ($status == 'user' && in_array($perm, words('search forward_entry pdfexport view')) !== false) { + return true; + } else if ($status == 'nobody_write' && in_array($perm, words('search add_entry pdfexport view')) !== false) { + return true; + } else if ($status == 'nobody_read' && in_array($perm, words('search pdfexport view')) !== false) { + return true; + } + + // user has no permission + return false; + } + + /** + * If the user has not the passed perm in a seminar, an AccessDeniedException + * is thrown. + * An optional topic_id can be passed which is checked against the passed + * seminar if the topic_id belongs to that seminar + * + * @param string $perm for the list of possible perms and their function see @ForumPerm::hasPerm() + * @param string $seminar_id the seminar to check for + * @param string $topic_id if passed, this topic_id is checked if it belongs to the passed seminar + * + * @throws AccessDeniedException + */ + public static function check($perm, $seminar_id, $topic_id = null) + { + if (!self::has($perm, $seminar_id)) { + throw new AccessDeniedException(sprintf( + _("Sie haben keine Berechtigung für diese Aktion! Benötigte Berechtigung: %s"), + $perm) + ); + } + + // check the topic id (if any) + if ($topic_id) { + self::checkTopicId($seminar_id, $topic_id); + } + } + + /** + * Check if the current user is allowed to edit the topic + * denoted by the passed id + * + * @staticvar array $perms + * + * @param string $topic_id the id for the topic to check for + * + * @return bool true if the user has the necessary perms, false otherwise + */ + public static function hasEditPerms($topic_id) + { + static $perms = []; + + if (!$perms[$topic_id]) { + // find out if the posting is the last in the thread + $constraints = ForumEntry::getConstraints($topic_id); + + $stmt = DBManager::get()->prepare("SELECT user_id, seminar_id + FROM forum_entries WHERE topic_id = ?"); + $stmt->execute([$topic_id]); + + $data = $stmt->fetch(); + + $closed = ForumEntry::isClosed($topic_id); + + $perms[$topic_id] = (($GLOBALS['user']->id == $data['user_id'] && $GLOBALS['user']->id != 'nobody') || + ForumPerm::has('edit_entry', $constraints['seminar_id'])) + && (!$closed || $closed && ForumPerm::has('edit_closed', $constraints['seminar_id'])); + } + + return $perms[$topic_id]; + } + + /** + * check if the passed category_id belongs to the passed seminar_id. + * Throws an AccessDenied denied exception if this is not the case + * + * @param string $seminar_id id of the seminar, the category should belong to + * @param string $category_id the id of the category to check + */ + public static function checkCategoryId($seminar_id, $category_id) + { + $data = ForumCat::get($category_id); + + if ($data['seminar_id'] != $seminar_id) { + throw new AccessDeniedException(sprintf( + _('Forum: Sie haben keine Berechtigung auf die Kategorie mit der ID %s zuzugreifen!'), + $category_id + )); + } + } + + /** + * check if the passed topic_id belongs to the passed seminar_id. + * Throws an AccessDenied denied exception if this is not the case + * + * @param string $seminar_id id of the seminar, the category should belong to + * @param string $topic_id the id of the topic to check + */ + public static function checkTopicId($seminar_id, $topic_id) + { + $data = ForumEntry::getConstraints($topic_id); + + if ($data['seminar_id'] != $seminar_id) { + throw new AccessDeniedException(sprintf( + _('Forum: Sie haben keine Berechtigung auf den Eintrag mit der ID %s zuzugreifen!'), + $topic_id + )); + } + } +} diff --git a/lib/classes/ForumVisit.php b/lib/classes/ForumVisit.php new file mode 100644 index 0000000..befe116 --- /dev/null +++ b/lib/classes/ForumVisit.php @@ -0,0 +1,162 @@ + + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL version 3 + * @category Stud.IP + */ + +class ForumVisit { + + /** + * This is the maximum number of seconds that unread entries are + * marked as new. + */ + const LAST_VISIT_MAX = 7776000; // 90 days + + /** + * return number of new entries since last visit up to 3 month ago + * + * @param string $parent_id the seminar_id for the entries + * @param string $visitdate count all entries newer than this timestamp + * + * @return int the number of entries + */ + static function getCount($parent_id, $visitdate) + { + if ($visitdate < time() - ForumVisit::LAST_VISIT_MAX) { + $visitdate = time() - ForumVisit::LAST_VISIT_MAX; + } + + $constraints = ForumEntry::getConstraints($parent_id); + + $stmt = DBManager::get()->prepare("SELECT COUNT(*) FROM forum_entries + WHERE lft >= :lft AND rgt <= :rgt AND user_id != :user_id + AND seminar_id = :seminar_id + AND topic_id != seminar_id + AND chdate > :lastvisit"); + + $stmt->bindParam(':user_id', $GLOBALS['user']->id); + $stmt->bindParam(':lft', $constraints['lft']); + $stmt->bindParam(':rgt', $constraints['rgt']); + $stmt->bindParam(':seminar_id', $constraints['seminar_id']); + $stmt->bindParam(':lastvisit', $visitdate); + + $stmt->execute(); + + return $stmt->fetchColumn(); + } + + /** + * Set the seminar denoted by the passed id as visited by the currently + * logged in user + * + * @param string $seminar_id + */ + static function setVisit($seminar_id) { + $type = get_object_type($seminar_id, words('fak inst sem')); + if ($type === 'fak') { + $type = 'inst'; + } + if (self::getVisit($seminar_id) < object_get_visit($seminar_id, $type, false, false)) { + self::setVisitdates($seminar_id); + } + } + + /** + * Stores the visitdate in last_visitdate and sets the current time for as new visitdate + * + * @param string $seminar_id the seminar that has been entered + */ + static function setVisitdates($seminar_id) { + $stmt = DBManager::get()->prepare('SELECT visitdate FROM forum_visits + WHERE user_id = ? AND seminar_id = ?'); + $stmt->execute([$GLOBALS['user']->id, $seminar_id]); + $visitdate = $stmt->fetchColumn(); + + $stmt = DBManager::get()->prepare("REPLACE INTO forum_visits + (user_id, seminar_id, visitdate, last_visitdate) + VALUES (?, ?, UNIX_TIMESTAMP(), ?)"); + $stmt->execute([$GLOBALS['user']->id, $seminar_id, $visitdate]); + + } + + + /** + * returns visitdate and last_visitdate for the passed seminar and the + * currently logged in user + * + * @staticvar array $visit + * + * @param string $seminar_id the seminar to fetch the visitdates for + * @return mixed an array containing visitdate and last_visitdate + */ + private static function getVisitDates($seminar_id) + { + static $visit = []; + + // no costly checking for root or nobody necessary + if ($GLOBALS['perm']->have_perm('root') || $GLOBALS['user']->id == 'nobody') { + $tstamp = mktime(23, 59, 00, date('m'), 31, date('y')); + return ['visit' => $tstamp, 'last_visitdate' => $tstamp]; + } + + if (!isset($visit[$seminar_id])) { + $visit[$seminar_id] = []; + } + if (!isset($visit[$seminar_id][$GLOBALS['user']->id])) { + $stmt = DBManager::get()->prepare("SELECT visitdate, last_visitdate FROM forum_visits + WHERE seminar_id = ? AND user_id = ?"); + $stmt->execute([$seminar_id, $GLOBALS['user']->id]); + $visit[$seminar_id][$GLOBALS['user']->id] = $stmt->fetch(PDO::FETCH_ASSOC); + + // no entry for this seminar yet present + if (!$visit[$seminar_id][$GLOBALS['user']->id]) { + // set visitdate to current time + $visit[$seminar_id][$GLOBALS['user']->id] = [ + 'visit' => time() - ForumVisit::LAST_VISIT_MAX, + 'last_visitdate' => time() - ForumVisit::LAST_VISIT_MAX + ]; + } + + // prevent visit-dates from being older than LAST_VISIT_MAX allows + foreach ($visit[$seminar_id][$GLOBALS['user']->id] as $type => $date) { + if ($date < time() - ForumVisit::LAST_VISIT_MAX) { + $visit[$seminar_id][$GLOBALS['user']->id][$type] = time() - ForumVisit::LAST_VISIT_MAX; + } + } + } + + return $visit[$seminar_id][$GLOBALS['user']->id]; + } + + /** + * return the last_visitdate for the passed seminar and currently logged in user + * + * @param string $seminar_id the seminar to get the last_visitdate for + * @return int a timestamp + */ + static function getLastVisit($seminar_id) + { + $visit = self::getVisitDates($seminar_id); + return $visit['last_visitdate']; + } + + /** + * return the visitdate for the passed seminar and currently logged in user + * + * @param string $seminar_id the seminar to get the visitdate for + * @return int a timestamp + */ + static function getVisit($seminar_id) + { + $visit = self::getVisitDates($seminar_id); + return $visit['visitdate']; + } +} diff --git a/lib/classes/JsonApi/Routes/Forum/ForumAuthority.php b/lib/classes/JsonApi/Routes/Forum/ForumAuthority.php index c895a7b..acc5d2a 100644 --- a/lib/classes/JsonApi/Routes/Forum/ForumAuthority.php +++ b/lib/classes/JsonApi/Routes/Forum/ForumAuthority.php @@ -8,8 +8,6 @@ class ForumAuthority { public static function has(\User $user, $perm, \Course $course, ForumEntry $topic = null) { - require_once 'public/plugins_packages/core/Forum/models/ForumPerm.php'; - if (!\ForumPerm::has($perm, $course->id, $user->id)) { return false; } diff --git a/lib/classes/JsonApi/Routes/Forum/ForumCategoriesShow.php b/lib/classes/JsonApi/Routes/Forum/ForumCategoriesShow.php index 9e935e0..190efb2 100644 --- a/lib/classes/JsonApi/Routes/Forum/ForumCategoriesShow.php +++ b/lib/classes/JsonApi/Routes/Forum/ForumCategoriesShow.php @@ -2,8 +2,6 @@ namespace JsonApi\Routes\Forum; -// require_once 'public/plugins_packages/core/Forum/models/ForumCat.php'; - use Psr\Http\Message\ServerRequestInterface as Request; use Psr\Http\Message\ResponseInterface as Response; use JsonApi\Errors\AuthorizationFailedException; diff --git a/lib/classes/Privacy.php b/lib/classes/Privacy.php index 90ba9f7..bb3bcdd 100644 --- a/lib/classes/Privacy.php +++ b/lib/classes/Privacy.php @@ -85,8 +85,6 @@ class Privacy */ public static function getUserdataInformation($user_id, $section = null) { - //workaround make Forum Model available - PluginEngine::getPlugin('CoreForum'); $storage = new StoredUserData($user_id); if ($section && !isset(self::$privacy_classes[$section])) { diff --git a/lib/classes/globalsearch/GlobalSearchForum.php b/lib/classes/globalsearch/GlobalSearchForum.php index 83558bd..37efd26 100644 --- a/lib/classes/globalsearch/GlobalSearchForum.php +++ b/lib/classes/globalsearch/GlobalSearchForum.php @@ -144,7 +144,7 @@ class GlobalSearchForum extends GlobalSearchModule implements GlobalSearchFullte 'id' => $data['topic_id'], 'name' => $name, 'url' => URLHelper::getURL( - "plugins.php/coreforum/index/index/{$data['topic_id']}#{$data['topic_id']}", + "dispatch.php/course/forum/index/index/{$data['topic_id']}#{$data['topic_id']}", ['cid' => $data['seminar_id'], 'highlight_topic' => $data['topic_id']], true ), @@ -152,7 +152,7 @@ class GlobalSearchForum extends GlobalSearchModule implements GlobalSearchFullte 'date' => strftime('%x', $data['chdate']), 'description' => self::mark($filtered_content, $search, true), 'additional' => htmlReady($additional), - 'expand' => URLHelper::getURL('plugins.php/coreforum/index/search', [ + 'expand' => URLHelper::getURL('dispatch.php/course/forum/index/search', [ 'cid' => $data['seminar_id'], 'backend' => 'search', 'searchfor' => $search, diff --git a/lib/models/BlubberThread.php b/lib/models/BlubberThread.php index 0be5cd2..430950f 100644 --- a/lib/models/BlubberThread.php +++ b/lib/models/BlubberThread.php @@ -593,7 +593,7 @@ class BlubberThread extends SimpleORMap implements PrivacyObject public function getURL() { if (($this['context_type'] === "course") || ($this['context_type'] === "institute")) { - return URLHelper::getURL('plugins.php/blubber/messenger/course/' . $this->getId(), ['cid' => $this['context_id']]); + return URLHelper::getURL('dispatch.php/course/messenger/course/' . $this->getId(), ['cid' => $this['context_id']]); } return URLHelper::getURL('dispatch.php/blubber/index/' . $this->getId()); } diff --git a/lib/models/ForumCat.php b/lib/models/ForumCat.php new file mode 100644 index 0000000..c963b69 --- /dev/null +++ b/lib/models/ForumCat.php @@ -0,0 +1,252 @@ + + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL version 3 + * @category Stud.IP + */ + +class ForumCat extends SimpleORMap +{ + /** + * Configures this model. + * + * @param array $config Configuration array + */ + protected static function configure($config = []) + { + $config['db_table'] = 'forum_categories'; + parent::configure($config); + } + + /** + * Return a list of all areas with their categories. + * Empty categories are excluded by default + * + * @param string $seminar_id the seminar_id the retrieve the categories for + * @param string $exclude_null if false, empty categories are returned as well + * @return array list of categories + */ + public static function getListWithAreas($seminar_id, $exclude_null = true) + { + $stmt = DBManager::get()->prepare("SELECT * FROM forum_categories AS fc + LEFT JOIN forum_categories_entries AS fce USING (category_id) + WHERE seminar_id = ? " + . ($exclude_null ? 'AND fce.topic_id IS NOT NULL ' : '') + . "ORDER BY fc.pos ASC, fce.pos ASC"); + + $stmt->execute([$seminar_id]); + + return $stmt->fetchAll(PDO::FETCH_ASSOC); + } + + /** + * Returns the name of the associated category for an area denoted by the + * passed topic_id + * + * @param string $topic_id + * @return string the name of the category + */ + public static function getCategoryNameForArea($topic_id) + { + $stmt = DBManager::get()->prepare("SELECT fc.entry_name FROM forum_categories AS fc + LEFT JOIN forum_categories_entries AS fce USING (category_id) + WHERE fce.topic_id = ?"); + $stmt->execute([$topic_id]); + + return $stmt->fetchColumn(); + } + + + /** + * Adds a new category with the passed name to the passed seminar and + * returns the id of the newly created category + * + * @param string $seminar_id + * @param string $name the name of the new category + * @return string the id of the newly created category + */ + public static function add($seminar_id, $name) + { + $stmt = DBManager::get()->prepare("INSERT INTO forum_categories + (category_id, seminar_id, entry_name) + VALUES (?, ?, ?)"); + + $category_id = md5(uniqid(rand())); + + $stmt->execute([$category_id, $seminar_id, $name]); + + return $category_id; + } + + + /** + * Remove the category with the passed id. The seminar_id is used only + * to be certain. + * + * @param string $category_id The ID of the category to be deleted + * @param string $seminar_id Seminar-ID the category belongs to + */ + public static function remove($category_id, $seminar_id) + { + // delete the category itself + $stmt = DBManager::get()->prepare("DELETE FROM + forum_categories + WHERE category_id = ?"); + $stmt->execute([$category_id]); + + // set all entries to default category + $stmt = DBManager::get()->prepare("UPDATE + forum_categories_entries + SET category_id = ?, pos = 999 + WHERE category_id = ?"); + $stmt->execute([$seminar_id, $category_id]); + } + + + /** + * Set the position for the passed category to the passed value + * + * @param string $category_id the ID of the category to update + * @param int $pos the new position + */ + public static function setPosition($category_id, $pos) + { + $stmt = DBManager::get()->prepare("UPDATE + forum_categories + SET pos = ? WHERE category_id = ?"); + $stmt->execute([$pos, $category_id]); + } + + + /** + * Add the passed area to the passed category and remove it from all + * other categories. + * + * @param string $category_id the ID of the category + * @param string $area_id the ID of the area to add the category to + */ + public static function addArea($category_id, $area_id) + { + // remove area from all other categories + $stmt = DBManager::get()->prepare("DELETE FROM + forum_categories_entries + WHERE topic_id = ?"); + $stmt->execute([$area_id]); + + // add area to this category, make sure it is at the end + $stmt = DBManager::get()->prepare("SELECT COUNT(*) FROM + forum_categories_entries + WHERE category_id = ?"); + $stmt->execute([$category_id]); + $new_pos = $stmt->fetchColumn() + 1; + + $stmt = DBManager::get()->prepare("REPLACE INTO + forum_categories_entries + (category_id, topic_id, pos) VALUES (?, ?, ?)"); + $stmt->execute([$category_id, $area_id, $new_pos]); + } + + + /** + * Remove the passed area from all categories. + * + * @param string $area_id the ID of the area to be removed + */ + public static function removeArea($area_id) + { + $stmt = DBManager::get()->prepare("DELETE FROM + forum_categories_entries + WHERE topic_id = ?"); + $stmt->execute([$area_id]); + } + + + /** + * Set the position for the passed category to the passed value + * + * @param string $area_id the ID of the area to update + * @param int $pos the new position + */ + public static function setAreaPosition($area_id, $pos) + { + $stmt = DBManager::get()->prepare("UPDATE + forum_categories_entries + SET pos = ? WHERE topic_id = ?"); + $stmt->execute([$pos, $area_id]); + } + + + /** + * Set the name for the passed category + * + * @param string $category_id the ID of the category to update + * @param string $name the name to set + */ + public static function setName($category_id, $name) + { + $stmt = DBManager::get()->prepare("UPDATE + forum_categories + SET entry_name = ? WHERE category_id = ?"); + $stmt->execute([$name, $category_id]); + } + + /** + * Return the data for the passed category_id + * + * @param string $category_id + * + * @return array the data for the passed category_id + */ + public static function get($category_id) + { + $stmt = DBManager::get()->prepare("SELECT * FROM forum_categories + WHERE category_id = ?"); + $stmt->execute([$category_id]); + + return $stmt->fetch(PDO::FETCH_ASSOC); + } + + /** + * Return the areas for the passed category_id + * + * @param string $category_id + * @param int $start limit start (optional) + * @param int $num number of entries to fetch (optional, default is 20) + * + * @return array the data for the passed category_id + */ + public static function getAreas($category_id, $start = null, $num = 20) + { + $category = self::get($category_id); + + $limit = ''; + if ($start !== null && $num) { + $limit = " LIMIT $start, $num"; + } + + if ($category_id == $category['seminar_id']) { + $stmt = DBManager::get()->prepare("SELECT fe.* FROM forum_entries AS fe + LEFT JOIN forum_categories_entries AS fce USING (topic_id) + WHERE seminar_id = ? AND depth = 1 AND ( + fce.category_id = ? OR fce.category_id IS NULL + ) ORDER BY category_id DESC, pos ASC" . $limit); + $stmt->execute([$category_id, $category_id]); + } else { + $stmt = DBManager::get()->prepare("SELECT forum_entries.* FROM forum_categories_entries + LEFT JOIN forum_entries USING(topic_id) + WHERE category_id = ? + ORDER BY pos ASC" . $limit); + + $stmt->execute([$category_id]); + } + + return $stmt->fetchAll(PDO::FETCH_ASSOC); + } +} diff --git a/lib/modules/ActivityFeed.php b/lib/modules/ActivityFeed.php new file mode 100644 index 0000000..6fa9ce3 --- /dev/null +++ b/lib/modules/ActivityFeed.php @@ -0,0 +1,77 @@ + + * @author Till Glöggler + * @license GPL 2 or later + */ +class ActivityFeed extends CorePlugin implements PortalPlugin +{ + public function getPluginName() + { + return _('Aktivitäten'); + } + + public function getMetadata() + { + return [ + 'description' => _('Mit diesem Widget haben Sie alle Aktivitäten im Überblick.') + ]; + } + + public function getPortalTemplate() + { + $template = $GLOBALS['template_factory']->open('start/activityfeed'); + + $template->user_id = $GLOBALS['user']->id; + $template->scrolledfrom = strtotime('+1 day'); + $template->config = WidgetHelper::getWidgetUserConfig($GLOBALS['user']->id, 'ACTIVITY_FEED'); + + $navigation = new Navigation('', 'dispatch.php/activityfeed/configuration'); + $navigation->setImage(Icon::create('edit', 'clickable', ["title" => _('Konfigurieren')]), ['data-dialog'=>'size=auto']); + $icons[] = $navigation; + + $navigation = new Navigation('', '#', ['cid' => null]); + $navigation->setImage(Icon::create('headache+visibility-visible', 'clickable')); + $navigation->setLinkAttributes([ + 'id' => 'toggle-user-activities', + 'title' => _('Eigene Aktivitäten ein-/ausblenden'), + ]); + $icons[] = $navigation; + + $navigation = new Navigation('', '#', ['cid' => null]); + $navigation->setImage(Icon::create('no-activity', 'clickable')); + $navigation->setLinkAttributes([ + 'id' => 'toggle-all-activities', + 'title' => _('Aktivitätsdetails ein-/ausblenden'), + ]); + $icons[] = $navigation; + + $template->icons = $icons; + + return $template; + } + + public static function onEnable($pluginId) + { + $errors = []; + if (!Config::get()->API_ENABLED) { + $errors[] = sprintf( + _('Die REST-API ist nicht aktiviert (%s "API_ENABLED")'), + formatReady(sprintf('[%s]%s', + _('Konfiguration'), + URLHelper::getLink('dispatch.php/admin/configuration/configuration') + )) + ); + } elseif (!RESTAPI\ConsumerPermissions::get('global')->check('/user/:user_id/activitystream', 'get')) { + $errors[] = sprintf( + _('Die REST-API-Route ist nicht aktiviert (%s "/user/:user_id/activitystream"")'), + formatReady(sprintf('[%s]%s', + _('Konfiguration'), + URLHelper::getLink('dispatch.php/admin/api/permissions') + )) + ); + } + + return count($errors) === 0; + } +} diff --git a/lib/modules/Blubber.class.php b/lib/modules/Blubber.class.php new file mode 100644 index 0000000..d9b5579 --- /dev/null +++ b/lib/modules/Blubber.class.php @@ -0,0 +1,126 @@ + + * + * 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. + */ + +/** + * Class Blubber - the Blubber-plugin + * This is only used to manage blubber within a course. + */ +class Blubber extends CorePlugin implements StudipModule +{ + /** + * Returns a navigation for the tab displayed in the course. + * @param string $course_id of the course + * @return \Navigation + */ + public function getTabNavigation($course_id) + { + $tab = new Navigation( + _('Blubber'), + 'dispatch.php/course/messenger/course' + ); + $tab->setImage(Icon::create('blubber', Icon::ROLE_INFO_ALT)); + return ['blubber' => $tab]; + } + + /** + * Returns a navigation-object with the grey/red icon for displaying in the + * my_courses.php page. + * @param string $course_id + * @param int $last_visit + * @param string|null $user_id + * @return \Navigation + */ + public function getIconNavigation($course_id, $last_visit, $user_id = null) + { + $user_id || $user_id = $GLOBALS['user']->id; + $icon = new Navigation( + _('Blubber'), + 'dispatch.php/course/messenger/course' + ); + $icon->setImage(Icon::create('blubber', Icon::ROLE_CLICKABLE, ['title' => _('Blubber-Messenger')])); + + $condition = "INNER JOIN blubber_threads USING (thread_id) + WHERE blubber_threads.context_type = 'course' + AND blubber_threads.context_id = :course_id + AND blubber_comments.mkdate >= :last_visit + AND blubber_comments.user_id != :me + AND blubber_threads.visible_in_stream = 1 + "; + $comments = BlubberComment::findBySQL($condition, [ + 'course_id' => $course_id, + 'last_visit' => $last_visit, + 'me' => $user_id, + ]); + foreach ($comments as $comment) { + if ($comment->thread->isVisibleInStream() && $comment->thread->isReadable() && ($comment->thread->getLatestActivity() > UserConfig::get($user_id)->getValue("BLUBBERTHREAD_VISITED_".$comment['thread_id']))) { + $icon->setImage(Icon::create('blubber', Icon::ROLE_NEW, ['title' => _('Es gibt neue Blubber')])); + $icon->setTitle(_('Es gibt neue Blubber')); + $icon->setBadgeNumber(count($comments)); + $icon->setURL('dispatch.php/course/messenger/course', ['thread' => 'new']); + break; + } + } + + $condition = "context_type = 'course' + AND context_id = :course_id + AND mkdate >= :last_visit + AND user_id != :me + AND visible_in_stream = 1 + AND ( + blubber_threads.display_class IS NOT NULL + OR blubber_threads.`content` IS NOT NULL + )"; + $threads = BlubberThread::findBySQL($condition, [ + 'course_id' => $course_id, + 'last_visit' => $last_visit, + 'me' => $GLOBALS['user']->id, + ]); + foreach ($threads as $thread) { + if ($thread->isVisibleInStream() && $thread->isReadable() && ($thread['mkdate'] > UserConfig::get($user_id)->getValue("BLUBBERTHREAD_VISITED_".$thread->getId()))) { + $icon->setImage(Icon::create('blubber', Icon::ROLE_ATTENTION, ['title' => _('Es gibt neue Blubber')])); + $icon->setTitle(_('Es gibt neue Blubber')); + break; + } + } + return $icon; + } + + /** + * Returns no template, because this plugin doesn't want to insert an + * info-template in the course-overview. + * @param string $course_id + * @return null + */ + public function getInfoTemplate($course_id) + { + return null; + } + + /** + * {@inheritdoc} + */ + public function getMetadata() + { + return [ + 'summary' => _('Schneller und einfacher Austausch von Informationen in Gesprächsform'), + 'description' => _('Blubber ist eine Kommunikationsform mit Ähnlichkeiten zu einem Forum, in dem aber in Echtzeit miteinander kommuniziert werden kann und das durch den etwas informelleren Charakter eher einem Chat anmutet. Anders als im Forum ist es nicht notwendig, die Seiten neu zu laden, um die neuesten Einträge (z. B. Antworten auf eigene Postings) sehen zu können: Die Seite aktualisiert sich selbst bei neuen Einträgen. Dateien (z.B. Fotos, Audiodateien, Links) können per Drag and Drop in das Feld gezogen und somit verlinkt werden. Auch Textformatierungen und das Verwenden von Smileys sind möglich.'), + 'descriptionlong' => _('Kommunikationsform mit Ähnlichkeiten zu einem Forum. Im Gegensatz zum Forum kann mit Blubber jedoch in Echtzeit miteinander kommuniziert werden. Das Tool ähnelt durch den etwas informelleren Charakter einem Messenger. Anders als im Forum ist es nicht notwendig, die Seiten neu zu laden, um die neuesten Einträge (z. B. Antworten auf eigene Postings) sehen zu können. Dateien (z. B. Fotos, Audiodateien, Links) können per drag and drop in das Feld gezogen und somit verlinkt werden. Auch Textformatierungen und das Verwenden von Smileys sind möglich.'), + 'category' => _('Kommunikation und Zusammenarbeit'), + 'keywords' => _('Einfach Text schreiben und mit abschicken; Direktes Kontaktieren anderer Stud.IP-NutzerInnen (@Vorname Nachname); Setzen von und Suche nach Stichworten über Hashtags (#Stichwort); Einbinden von Dateien per drag and drop'), + 'icon' => Icon::create('blubber', Icon::ROLE_INFO), + 'screenshots' => [ + 'path' => 'assets/images/plus/screenshots/Blubber', + 'pictures' => [ + ['source' => 'blubberscreenshot.png', 'title' => 'Blubbern'] + ] + ] + ]; + } +} diff --git a/lib/modules/ContentsWidget.php b/lib/modules/ContentsWidget.php new file mode 100644 index 0000000..1907ba6 --- /dev/null +++ b/lib/modules/ContentsWidget.php @@ -0,0 +1,30 @@ + + * @license GPL2 or any later version + * @since Stud.IP 5.0 + */ + +class ContentsWidget extends CorePlugin implements PortalPlugin +{ + public function getPluginName() + { + return _('Mein Arbeitsplatz'); + } + + public function getMetadata() + { + return [ + 'description' => _('Mit diesem Widget haben Sie einen Überblick über Ihre Inhalte.') + ]; + } + + public function getPortalTemplate() + { + $template = $GLOBALS['template_factory']->open('start/contents'); + $template->tiles = Navigation::getItem('/contents'); + return $template; + } +} diff --git a/lib/modules/CoreForum.class.php b/lib/modules/CoreForum.class.php new file mode 100644 index 0000000..88dd5dd --- /dev/null +++ b/lib/modules/CoreForum.class.php @@ -0,0 +1,209 @@ + + * @copyright 2011 ELAN e.V. + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + */ + +// Notifications +NotificationCenter::addObserver('CoreForum', 'overviewDidClear', 'OverviewDidClear'); +NotificationCenter::addObserver('CoreForum', 'removeAbosForUserAndCourse', 'UserDidLeaveCourse'); + +NotificationCenter::addObserver('ForumActivity', 'newEntry', 'ForumAfterInsert'); +NotificationCenter::addObserver('ForumActivity', 'updateEntry', 'ForumAfterUpdate'); +NotificationCenter::addObserver('ForumActivity', 'deleteEntry', 'ForumBeforeDelete'); + +NotificationCenter::addObserver('ForumIssue', 'unlinkIssue', 'ForumBeforeDelete'); + +class CoreForum extends CorePlugin implements ForumModule +{ + /* interface method */ + public function getTabNavigation($course_id) + { + $navigation = new Navigation(_('Forum'), 'dispatch.php/course/forum/index'); + $navigation->setImage(Icon::create('forum', 'info_alt')); + + // add main third-level navigation-item + $navigation->addSubNavigation('index', new Navigation(_('Übersicht'), 'dispatch.php/course/forum/index')); + + if (ForumPerm::has('fav_entry', $course_id)) { + $navigation->addSubNavigation('newest', new Navigation(_("Neue Beiträge"), 'dispatch.php/course/forum/index/newest')); + $navigation->addSubNavigation('latest', new Navigation(_("Letzte Beiträge"), 'dispatch.php/course/forum/index/latest')); + $navigation->addSubNavigation('favorites', new Navigation(_('Gemerkte Beiträge'), 'dispatch.php/course/forum/index/favorites')); + + // mass-administrate the forum + if (ForumPerm::has('admin', $course_id)) { + $navigation->addSubNavigation('admin', new Navigation(_('Administration'), 'dispatch.php/course/forum/admin')); + } + } + + return ['forum2' => $navigation]; + } + + /* interface method */ + public function getIconNavigation($course_id, $last_visit, $user_id = null) + { + if ($GLOBALS['perm']->have_studip_perm('user', $course_id)) { + $num_entries = ForumVisit::getCount($course_id, ForumVisit::getVisit($course_id)); + $text = ForumHelpers::getVisitText($num_entries, $course_id); + } else { + $num_entries = 0; + $text = 'Forum'; + } + + $navigation = new Navigation('forum', 'dispatch.php/course/forum/index/enter_seminar'); + $navigation->setBadgeNumber($num_entries); + + if ($num_entries > 0) { + $navigation->setImage(Icon::create('forum', Icon::ROLE_ATTENTION, ['title' => $text])); + } else { + $navigation->setImage(Icon::create('forum', Icon::ROLE_CLICKABLE, ['title' => $text])); + } + + return $navigation; + } + + /** + * This method is called, whenever an user clicked to clear the visit timestamps + * and set everything as visited + * + * @param object $notification + * @param string $user_id + */ + public static function overviewDidClear($notification, $user_id) + { + $query = "REPLACE INTO `forum_visits` + SELECT `user_id`, `Seminar_id`, UNIX_TIMESTAMP(), UNIX_TIMESTAMP() + FROM `seminar_user` + WHERE `user_id` = ?"; + DBManager::get()->execute($query, [$user_id]); + } + + /** + * This method is called whenever a user is removed from a course and thus + * the forum abos will be removed. + * + * @param object $notification + * @param string $course_id + * @param string $user_id + */ + public static function removeAbosForUserAndCourse($notification, $course_id, $user_id) + { + ForumAbo::removeForCourseAndUser($course_id, $user_id); + } + + public function getInfoTemplate($course_id) + { + return null; + } + + /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + /* * IMPLEMENTATION OF METHODS FROM FORUMMODULE-INTERFACE * */ + /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + + public function getLinkToThread($issue_id) + { + if ($topic_id = ForumIssue::getThreadIdForIssue($issue_id)) { + return URLHelper::getLink('dispatch.php/course/forum/index/index/' . $topic_id); + } + + return false; + } + + public function setThreadForIssue($issue_id, $title, $content) + { + ForumIssue::setThreadForIssue(Context::getId(), $issue_id, $title, $content); + } + + public function getNumberOfPostingsForUser($user_id, $seminar_id = null) + { + return ForumEntry::countUserEntries($user_id, $seminar_id); + } + + public function getNumberOfPostingsForIssue($issue_id) + { + $topic_id = ForumIssue::getThreadIdForIssue($issue_id); + + return $topic_id ? ForumEntry::countEntries($topic_id) : 0; + } + + public function getNumberOfPostingsForSeminar($seminar_id) + { + return floor(ForumEntry::countEntries($seminar_id)); + } + + public function getNumberOfPostings() + { + return ForumEntry::countAllEntries(); + } + + public function getEntryTableInfo() + { + return [ + 'table' => 'forum_entries', + 'content' => 'content', + 'chdate' => 'chdate', + 'seminar_id' => 'seminar_id', + 'user_id' => 'user_id' + ]; + } + + public function getTopTenSeminars() + { + return ForumEntry::getTopTenSeminars(); + } + + public function migrateUser($user_from, $user_to) + { + return ForumEntry::migrateUser($user_from, $user_to); + } + + public function deleteContents($seminar_id) + { + return ForumEntry::delete($seminar_id); + } + + public function getDump($seminar_id) + { + return ForumEntry::getDump($seminar_id); + } + + public static function getDescription() + { + return _('Textbasierte und zeit- und ortsunabhängige '. + 'Diskursmöglichkeit. Lehrende können parallel zu '. + 'Veranstaltungsthemen Fragen stellen, die von den Studierenden '. + 'per Meinungsaustausch besprochen werden.'); + } + + /** + * {@inheritdoc} + */ + public function getMetadata() + { + return [ + 'summary' => _('Veranstaltungsbegleitender Meinungsaustausch zu bestimmten Themen'), + 'description' => _('Textbasierte zeit- und ortsunabhängige Möglichkeit zum Austausch von Gedanken, Meinungen und Erfahrungen. Lehrende und/oder Studierende können parallel zu Veranstaltungsthemen Fragen stellen, die in Form von Textbeiträgen besprochen werden können. Diese Beiträge können von allen Teilnehmenden der Veranstaltung gemerkt, verlinkt, positiv bewertet (sog. "Gefällt mir") und editiert werden (Letzeres nur von Lehrenden). Lehrende können zusätzlich Themen in Bereiche gliedern, zwischen den Bereichen verschiebe, im Bereich hervorheben und diesen öffnen und schließen.'), + 'descriptionlong' => _('Textbasierte zeit- und ortsunabhängige Möglichkeit zum Austausch von Gedanken, Meinungen und Erfahrungen. Lehrende und/oder Studierende können parallel zu Veranstaltungsthemen Fragen stellen, die in Form von Textbeiträgen besprochen werden können. Diese Beiträge können von allen Teilnehmenden der Veranstaltung gemerkt, verlinkt, positiv bewertet (sog. "Gefällt mir") und editiert werden (Letzeres nur von Lehrenden). Lehrende können zusätzlich Themen in Bereiche gliedern, zwischen den Bereichen verschieben, im Bereich hervorheben und diesen öffnen und schließen.'), + 'category' => _('Kommunikation und Zusammenarbeit'), + 'keywords' => _('Möglichkeit zum intensiven, nachhaltigen textbasierten Austausch; (nachträgliche) Strukturierung der Beiträge; Editierfunktion für Lehrende'), + 'icon' => Icon::create('forum', Icon::ROLE_INFO), + 'screenshots' => [ + 'path' => 'assets/images/plus/screenshots/Forum', + 'pictures' => [ + ['source' => 'Lehrendensicht_-_Kategorien_mit_Bereichen_und_Beitraegen.jpg'], + ['source' => 'Studentische_Sicht_-_Kategorien_mit_Bereichen_und_Beitraegen.jpg'], + ['source' => 'Einen_Forumsbeitrag_erstellen.jpg'], + ] + ] + ]; + } +} diff --git a/lib/modules/EvaluationsWidget.php b/lib/modules/EvaluationsWidget.php new file mode 100644 index 0000000..4c902c7 --- /dev/null +++ b/lib/modules/EvaluationsWidget.php @@ -0,0 +1,62 @@ + + * 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. + */ + +class EvaluationsWidget extends CorePlugin implements PortalPlugin +{ + public function getPluginName() + { + return _('Fragebögen'); + } + + public function getMetadata() + { + return [ + 'description' => _('Mit diesem Widget haben Sie Zugriff auf systemweite Umfragen.') + ]; + } + + /** + * Returns the portal widget template. + * + * Due to a seriously messed up architecture, the suppress_empty_output + * variable is used to determine when an according message should be + * presented to the user. If no evaluations and questionnaires are present, + * the message from the questionnaires should be displayed. The according + * message from evaluations should never be shown in the widget. Thus, the + * evaluation controller will always suppress this message and the variable + * is adjusted if the evaluation returned no content so that the + * questionnaire controller will display it's message. If you think, we're + * slowly running out of duct tape, you might be absolutely right... + */ + public function getPortalTemplate() + { + // include and show votes and tests + if (Config::get()->VOTE_ENABLE) { + $controller = new AuthenticatedController(new StudipDispatcher()); + $controller->suppress_empty_output = true; + + $response = $controller->relay('evaluation/display/studip')->body; + + $controller->suppress_empty_output = (bool)$response; + $response .= $controller->relay('questionnaire/widget/start')->body; + + $template = $GLOBALS['template_factory']->open('shared/string'); + $template->content = $response; + + if ($GLOBALS['perm']->have_perm('root')) { + $navigation = new Navigation('', 'dispatch.php/questionnaire/overview'); + $navigation->setImage(Icon::create('add', 'clickable', ["title" => _('Umfragen bearbeiten')])); + $template->icons = [$navigation]; + } + return $template; + } + } +} diff --git a/lib/modules/NewsWidget.php b/lib/modules/NewsWidget.php new file mode 100644 index 0000000..60af752 --- /dev/null +++ b/lib/modules/NewsWidget.php @@ -0,0 +1,65 @@ + + * 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 'app/controllers/news.php'; + +class NewsWidget extends CorePlugin implements PortalPlugin +{ + public function getPluginName() + { + return _('Ankündigungen'); + } + + public function getMetadata() + { + return [ + 'description' => _('Mit diesem Widget haben Sie Zugriff auf systemweite Ankündigungen.') + ]; + } + + function getPortalTemplate() + { + $dispatcher = new StudipDispatcher(); + $controller = new NewsController($dispatcher); + $response = $controller->relay('news/display/studip'); + $template = $GLOBALS['template_factory']->open('shared/string'); + $template->content = $response->body; + + if (StudipNews::CountUnread() > 0) { + $navigation = new Navigation('', 'dispatch.php/news/visit_all'); + $navigation->setImage(Icon::create('refresh', 'clickable', ["title" => _('Alle als gelesen markieren')])); + $icons[] = $navigation; + } + + if (Config::get()->NEWS_RSS_EXPORT_ENABLE) { + if ($rss_id = StudipNews::GetRssIdFromRangeId('studip')) { + $navigation = new Navigation('', 'rss.php', ['id' => $rss_id]); + $navigation->setImage(Icon::create('rss', 'clickable', ["title" => _('RSS-Feed')])); + $icons[] = $navigation; + } + } + + if ($GLOBALS['perm']->have_perm('root')) { + $navigation = new Navigation('', 'dispatch.php/news/edit_news/new/studip'); + $navigation->setImage(Icon::create('add', 'clickable', ["title" => _('Ankündigungen bearbeiten')]), ['rel' => 'get_dialog']); + $icons[] = $navigation; + if (Config::get()->NEWS_RSS_EXPORT_ENABLE) { + $navigation = new Navigation('', 'dispatch.php/news/rss_config/studip'); + $navigation->setImage(Icon::create('rss+add', 'clickable', ["title" => _('RSS-Feed konfigurieren')]), ["rel" => 'size=auto']); + $icons[] = $navigation; + } + } + + $template->icons = $icons; + + return $template; + } +} diff --git a/lib/modules/QuickSelection.php b/lib/modules/QuickSelection.php new file mode 100644 index 0000000..f1a7a2c --- /dev/null +++ b/lib/modules/QuickSelection.php @@ -0,0 +1,57 @@ + + * 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. + */ + +class QuickSelection extends CorePlugin implements PortalPlugin +{ + public function getPluginName() + { + return _('Schnellzugriff'); + } + + public function getMetadata() + { + return [ + 'description' => _('Mit dem Schnellzugriff-Widget können Sie Links zu bestimmten Bereichen in Stud.IP verwalten.') + ]; + } + + public function getPortalTemplate() + { + $names = WidgetHelper::getWidgetUserConfig($GLOBALS['user']->id, 'QUICK_SELECTION'); + + $template = $GLOBALS['template_factory']->open('start/quickselection'); + $template->navigation = $this->getFilteredNavigation($names); + + $navigation = new Navigation('', 'dispatch.php/quickselection/configuration'); + $navigation->setImage(Icon::create('edit', 'clickable', ["title" => _('Konfigurieren')]), ['data-dialog'=>'size=auto']); + + $template->icons = [$navigation]; + + return $template; + } + + private function getFilteredNavigation($items) + { + $result = []; + + $navigation = Navigation::getItem('/start'); + foreach ($navigation as $name => $nav) { + // if config is new (key:value) display values which are not in config array + // otherwise hide items which are not in config array + // This is important for patching. + if (!isset($items[$name]) || $items[$name] !== 'deactivated') { + $result[] = $nav; + } + } + + return $result; + } +} diff --git a/lib/modules/ScheduleWidget.php b/lib/modules/ScheduleWidget.php new file mode 100644 index 0000000..339beb3 --- /dev/null +++ b/lib/modules/ScheduleWidget.php @@ -0,0 +1,54 @@ + + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + */ + +class ScheduleWidget extends CorePlugin implements PortalPlugin +{ + /** + * Returns the name of the plugin/widget. + * + * @return String containing the name + */ + public function getPluginName() + { + return _('Mein Stundenplan'); + } + + public function getMetadata() + { + return [ + 'description' => _('Mit diesem Widget haben Sie eine Übersicht Ihres aktuellen Stundenplans.') + ]; + } + + /** + * Return the template for the widget. + * + * @return Flexi_PhpTemplate The template containing the widget contents + */ + public function getPortalTemplate() + { + $view = CalendarScheduleModel::getUserCalendarView( + $GLOBALS['user']->id, + false, + false, + $days = array(0,1,2,3,4) + ); + + $template = $GLOBALS['template_factory']->open('shared/string'); + $template->content = CalendarWidgetView::createFromWeekView($view)->render(); + + return $template; + } +} diff --git a/lib/modules/TerminWidget.php b/lib/modules/TerminWidget.php new file mode 100644 index 0000000..c92f1d2 --- /dev/null +++ b/lib/modules/TerminWidget.php @@ -0,0 +1,43 @@ + + * + * 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 'app/controllers/calendar/contentbox.php'; + +class TerminWidget extends CorePlugin implements PortalPlugin +{ + public function getPluginName() + { + return _('Meine aktuellen Termine'); + } + + public function getMetadata() + { + return [ + 'description' => _('Mit diesem Widget haben Sie ihre aktuellen Termine im Überlick.') + ]; + } + + public function getPortalTemplate() + { + $dispatcher = new StudipDispatcher(); + $controller = new Calendar_ContentboxController($dispatcher); + $response = $controller->relay('calendar/contentbox/display/'.$GLOBALS['user']->id); + $template = $GLOBALS['template_factory']->open('shared/string'); + $template->content = $response->body; + + $navigation = new Navigation('', 'dispatch.php/calendar/single/week', ['self' => true]); + $navigation->setImage(Icon::create('add', 'clickable', ["title" => _('Neuen Termin anlegen')])); + $template->icons = [$navigation]; + + return $template; + } +} diff --git a/lib/plugins/engine/PluginManager.class.php b/lib/plugins/engine/PluginManager.class.php index ee5b43f..7193f31 100644 --- a/lib/plugins/engine/PluginManager.class.php +++ b/lib/plugins/engine/PluginManager.class.php @@ -83,7 +83,7 @@ class PluginManager 'enabled' => $plugin['enabled'] === 'yes', 'position' => $plugin['navigationpos'], 'depends' => (int) $plugin['dependentonid'], - 'core' => $plugin['pluginpath'] === '' || strpos($plugin['pluginpath'], 'core/') === 0, + 'core' => $plugin['pluginpath'] === '', 'automatic_update_url' => $plugin['automatic_update_url'], 'automatic_update_secret' => $plugin['automatic_update_secret'] ]; diff --git a/public/plugins.php b/public/plugins.php index 97aee03..1488118 100644 --- a/public/plugins.php +++ b/public/plugins.php @@ -27,9 +27,15 @@ try { require_once 'lib/seminar_open.php'; // get plugin class from request - $dispatch_to = isset($_SERVER['PATH_INFO']) ?$_SERVER['PATH_INFO'] : ''; + $dispatch_to = isset($_SERVER['PATH_INFO']) ? $_SERVER['PATH_INFO'] : ''; list($plugin_class, $unconsumed) = PluginEngine::routeRequest($dispatch_to); + // handle legacy forum plugin URLs + if ($plugin_class === 'coreforum') { + header('Location: ' . URLHelper::getURL('dispatch.php/course/forum/' . $unconsumed)); + die(); + } + // retrieve corresponding plugin info $plugin_manager = PluginManager::getInstance(); $plugin_info = $plugin_manager->getPluginInfo($plugin_class); @@ -46,8 +52,9 @@ try { // set default page title PageLayout::setTitle($plugin->getPluginName()); + // deprecated, the plugin should override perform() instead if (is_callable([$plugin, 'initialize'])) { - $plugin->initialize(); + $plugin->initialize(); } // let the show begin diff --git a/public/plugins_packages/core/ActivityFeed/ActivityFeed.php b/public/plugins_packages/core/ActivityFeed/ActivityFeed.php deleted file mode 100644 index f42b34b..0000000 --- a/public/plugins_packages/core/ActivityFeed/ActivityFeed.php +++ /dev/null @@ -1,171 +0,0 @@ - - * @author Till Glöggler - * @license GPL 2 or later - */ -class ActivityFeed extends StudIPPlugin implements PortalPlugin -{ - public function getPluginName() - { - return _('Aktivitäten'); - } - - public function getPortalTemplate() - { - $this->addStylesheet('css/style.less'); - PageLayout::addScript($this->getPluginUrl() . '/javascript/activityfeed.js'); - - $template_factory = new Flexi_TemplateFactory(__DIR__ . '/templates'); - $template = $template_factory->open('activity_feed'); - - $template->user_id = $GLOBALS['user']->id; - $template->scrolledfrom = strtotime('+1 day'); - $template->config = WidgetHelper::getWidgetUserConfig($GLOBALS['user']->id, 'ACTIVITY_FEED'); - - $navigation = new Navigation('', PluginEngine::getLink($this, [], 'configuration')); - $navigation->setImage(Icon::create('edit', 'clickable', ["title" => _('Konfigurieren')]), ['data-dialog'=>'size=auto']); - $icons[] = $navigation; - - $navigation = new Navigation('', '#', ['cid' => null]); - $navigation->setImage(Icon::create('headache+visibility-visible', 'clickable')); - $navigation->setLinkAttributes([ - 'id' => 'toggle-user-activities', - 'title' => _('Eigene Aktivitäten ein-/ausblenden'), - ]); - $icons[] = $navigation; - - $navigation = new Navigation('', '#', ['cid' => null]); - $navigation->setImage(Icon::create('no-activity', 'clickable')); - $navigation->setLinkAttributes([ - 'id' => 'toggle-all-activities', - 'title' => _('Aktivitätsdetails ein-/ausblenden'), - ]); - $icons[] = $navigation; - - $template->icons = $icons; - - return $template; - } - - public static function onEnable($pluginId) - { - $errors = []; - if (!Config::get()->API_ENABLED) { - $errors[] = sprintf( - _('Die REST-API ist nicht aktiviert (%s "API_ENABLED")'), - formatReady(sprintf('[%s]%s', - _('Konfiguration'), - URLHelper::getLink('dispatch.php/admin/configuration/configuration') - )) - ); - } elseif (!RESTAPI\ConsumerPermissions::get('global')->check('/user/:user_id/activitystream', 'get')) { - $errors[] = sprintf( - _('Die REST-API-Route ist nicht aktiviert (%s "/user/:user_id/activitystream"")'), - formatReady(sprintf('[%s]%s', - _('Konfiguration'), - URLHelper::getLink('dispatch.php/admin/api/permissions') - )) - ); - } - - return count($errors) === 0; - } - - public function save_action() - { - if (Config::get()->ACTIVITY_FEED === NULL) { - Config::get()->create('ACTIVITY_FEED', [ - 'range' => 'user', - 'type' => 'array', - 'description' => 'Einstellungen des Activity-Widgets'] - ); - } - - $provider = Request::getArray('provider'); - - WidgetHelper::addWidgetUserConfig($GLOBALS['user']->id, 'ACTIVITY_FEED', $provider); - - header('X-Dialog-Close: 1'); - header('X-Dialog-Execute: STUDIP.ActivityFeed.updateFilter'); - - echo json_encode($provider); - } - - /** - * return a list for all providers for every context - * - * @return array - */ - private function getAllModules() - { - $modules = []; - - $modules['system'] = [ - 'news' => _('Ankündigungen'), - 'blubber' => _('Blubber') - ]; - - $modules[\Context::COURSE] = [ - 'forum' => _('Forum'), - 'participants' => _('Teilnehmende'), - 'documents' => _('Dateien'), - 'wiki' => _('Wiki'), - 'schedule' => _('Ablaufplan'), - 'news' => _('Ankündigungen'), - 'blubber' => _('Blubber'), - 'courseware' => _('Courseware') - ]; - - $modules[\Context::INSTITUTE] = $modules[\Context::COURSE]; - unset($modules[\Context::INSTITUTE]['participants']); - unset($modules[\Context::INSTITUTE]['schedule']); - - $standard_plugins = \PluginManager::getInstance()->getPlugins("StandardPlugin"); - foreach ($standard_plugins as $plugin) { - if ($plugin instanceof ActivityProvider) { - $modules[\Context::COURSE][$plugin->getPluginName()] = $plugin->getPluginName(); - $modules[\Context::INSTITUTE][$plugin->getPluginName()] = $plugin->getPluginName(); - } - } - - $modules[\Context::USER] = [ - 'message' => _('Nachrichten'), - 'news' => _('Ankündigungen'), - 'blubber' => _('Blubber'), - ]; - - $homepage_plugins = \PluginEngine::getPlugins('HomepagePlugin'); - foreach ($homepage_plugins as $plugin) { - if ($plugin->isActivated($GLOBALS['user']->id, 'user')) { - if ($plugin instanceof ActivityProvider) { - $modules[\Context::USER][] = $plugin; - } - } - } - - return $modules; - } - - public function configuration_action() - { - $template_factory = new Flexi_TemplateFactory(__DIR__ . '/templates'); - $template = $template_factory->open('edit'); - $template->config = WidgetHelper::getWidgetUserConfig($GLOBALS['user']->id, 'ACTIVITY_FEED'); - $template->plugin = $this; - $template->modules = $this->getAllModules(); - $template->context_translations = [ - \Context::COURSE => _('Veranstaltungen'), - \Context::INSTITUTE => _('Einrichtungen'), - \Context::USER => _('Persönlich'), - 'system' => _('Global') - ]; - - PageLayout::setTitle(_('Aktivitäten konfigurieren')); - header('X-Title: ' . rawurlencode(PageLayout::getTitle())); - - echo $template->render(); - } -} diff --git a/public/plugins_packages/core/ActivityFeed/css/style.less b/public/plugins_packages/core/ActivityFeed/css/style.less deleted file mode 100644 index a1ca7ad..0000000 --- a/public/plugins_packages/core/ActivityFeed/css/style.less +++ /dev/null @@ -1,197 +0,0 @@ -// out: style.css - -#stream-container { - padding: 5px; - margin: 5px; - max-height: 600px; - overflow-y: auto; - - .activity { - border: 1px solid #d0d7e3; - width: 95%; - margin: 30px auto; - - } - - .activity header { - color: #28497c; - padding: 5px; - display: flex; - position: relative; - flex-direction: column; - - .provider_circle { - border-radius: 50%; - width: 42px; - height: 42px; - background: @base-color; - position: absolute; - top: -20px; - border: 3px solid white; - cursor: pointer; - - &.left { - left: -15px; - } - - &.right { - right: -15px; - } - - img { - padding: 12px; - .square(18px); - } - } - - .activity-heading { - padding: 5px; - margin: 0 0 0 25px; - flex-grow: 1; - img { - display: inline; - } - h3 { - display: inline; - color: #28497c; - border-bottom: none; - font-size: 14px; - vertical-align: 6px; - - } - } - .activity-avatar { - max-width: 25px; - } - .activity-date{ - color: gray; - font-size: 0.75em ; - margin: 0 0 0 30px; - display: block; - flex-grow: 1; - - } - } - - .activity-content{ - padding: 5px; - background-color: white; - margin-left: 30px; - display: none; - } - - .clear { - clear: both; - } - - .activity-details { - display: block; - } - - - footer { - padding: 5px; - display: flex; - flex-direction: row; - flex-wrap: nowrap; - justify-content: space-between; - align-content: space-around; - align-items: center; - margin-left: 30px; - - .activity-object-link { - order: 0; - flex: 0 1 auto; - align-self: auto; - } - } - - - .activity-object-link ul { - padding: 0px; - } - - .activity-object-link ul li { - list-style-type: none; - display: inline; - padding-right: 15px; - } - - - .activity-day { - color: gray; - font-size: 0.75em ; - margin: 0 45%; - } - - /* Loading animation */ - .loading-indicator { - text-align: center; - padding: 1em 0; - } - - .loading-indicator span { - background-color: #CCCCDD; - border-radius: 50%; - height: 10px; - position: relative; - width: 10px; - display: inline-block; - } - - .loading-indicator span.load-1 { - animation: loading-animation-1 1s linear 20; - } - - .loading-indicator span.load-2 { - animation: loading-animation-2 1s linear 20; - } - - .loading-indicator span.load-3 { - animation: loading-animation-3 1s linear 20; - } -} - -@keyframes loading-animation-1 { - 0% { transform: scale(1); } - 16% { transform: scale(1.3); } - 33% { transform: scale(1); } - 100% { transform: scale(1); } -} - -@keyframes loading-animation-2 { - 0% { transform: scale(1); } - 33% { transform: scale(1); } - 49% { transform: scale(1.3); } - 65% { transform: scale(1); } - 100% { transform: scale(1); } -} - -@keyframes loading-animation-3 { - 0% { transform: scale(1); } - 66% { transform: scale(1); } - 81% { transform: scale(1.3); } - 100% { transform: scale(1); } -} - -// Mixin that replaces the navigation icon tags with css images -.navigation-icon(@default-icon-name, @toggled-icon-name) { - img { display: none; } - - display: inline-block; - - .background-icon(@default-icon-name, 'clickable'); - .hide-text(); - .square(16px); - - &.toggled { - .background-icon(@toggled-icon-name, 'clickable'); - } -} - -#toggle-all-activities { - .navigation-icon('no-activity', 'activity'); -} -#toggle-user-activities { - .navigation-icon('visibility-invisible/headache', 'visibility-visible/headache'); -} diff --git a/public/plugins_packages/core/ActivityFeed/javascript/activityfeed.js b/public/plugins_packages/core/ActivityFeed/javascript/activityfeed.js deleted file mode 100644 index 876db4e..0000000 --- a/public/plugins_packages/core/ActivityFeed/javascript/activityfeed.js +++ /dev/null @@ -1,127 +0,0 @@ -(function($, STUDIP) { - STUDIP.ActivityFeed = { - user_id : null, - polling: null, - initial: true, - scrolledfrom: null, - maxheight: null, - filter: null, - - init: function() { - STUDIP.ActivityFeed.maxheight = parseInt($('#stream-container').css('max-height').replace(/[^-\d\.]/g, '')); - - STUDIP.ActivityFeed.loadFeed(STUDIP.ActivityFeed.filter); - - $('#stream-container').scroll(function () { - var scrollBottom = $('#stream-container').scrollTop() + $('#stream-container').height() + 250; - - if ($('#stream-container').prop('scrollHeight') < scrollBottom) { - STUDIP.ActivityFeed.loadFeed(STUDIP.ActivityFeed.filter); - } - }); - - - $(document).on('click', '.provider_circle', function () { - $(this).parent().parent().children('.activity-content').toggle(); - }).on('click', '#toggle-all-activities,#toggle-user-activities', function () { - var toggled = $(this).is(':not(.toggled)'); - $(this).toggleClass('toggled', toggled); - - STUDIP.ActivityFeed.setToggleStatus(); - - return false; - }); - }, - - getTemplate: _.memoize(function(name) { - return _.template($("script." + name).html()); - }), - - loadFeed: function(filtertype) { - if (STUDIP.ActivityFeed.user_id === null) { - console.log('Could not retrieve activities, no valid user id found!'); - return false; - } - - if (STUDIP.ActivityFeed.polling || !STUDIP.ActivityFeed.scrolledfrom) { - return false; - } - - STUDIP.ActivityFeed.polling = true; - - STUDIP.api.GET(['user', STUDIP.ActivityFeed.user_id, 'activitystream'], { - data: { - filtertype: filtertype, - scrollfrom: STUDIP.ActivityFeed.scrolledfrom - } - }).done(function (activities) { - var stream = STUDIP.ActivityFeed.getTemplate('activity_stream'); - var activity = STUDIP.ActivityFeed.getTemplate('activity'); - var activity_urls = STUDIP.ActivityFeed.getTemplate('activity-urls'); - var num_entries = Object.keys(activities).length; - var lastelem = $(activities).last(); - - if (lastelem[0]) { - STUDIP.ActivityFeed.scrolledfrom = lastelem[0].mkdate; - } else { - STUDIP.ActivityFeed.scrolledfrom = false; - } - - STUDIP.ActivityFeed.writeToStream(stream({ - stream : activities, - num_entries : num_entries, - activity : activity, - activity_urls : activity_urls, - user_id : STUDIP.ActivityFeed.user_id - })); - - STUDIP.ActivityFeed.setToggleStatus(); - - if ($('#stream-container').height() < STUDIP.ActivityFeed.maxheight) { - STUDIP.ActivityFeed.loadFeed(''); - } - }).fail(function () { - var template = STUDIP.ActivityFeed.getTemplate('activity-load-error'); - STUDIP.ActivityFeed.writeToStream(template()); - }).always(function () { - STUDIP.ActivityFeed.polling = false; - }); - }, - - writeToStream: function (html) { - if (STUDIP.ActivityFeed.initial) { - // replace data in DOM - $('#stream-container').html(''); - - STUDIP.ActivityFeed.initial = false; - } - - $('#stream-container').append(html); - }, - - setToggleStatus: function() { - var show_details = $('#toggle-all-activities').is('.toggled'), - show_own = $('#toggle-user-activities').is('.toggled'); - - // update toggle status fir activity contents - $('.activity-content').toggle(show_details); - - // update toggle status for user's own activities - $('.activity:has(.provider_circle.right)').toggle(show_own); - }, - - updateFilter: function(filter) { - STUDIP.ActivityFeed.filter = filter; - STUDIP.ActivityFeed.initial = true; - STUDIP.ActivityFeed.scrolledfrom = Math.floor(Date.now() / 1000); - - $('#stream-container').html('
' - + '' - + '' - + '' - + '
'); - - STUDIP.ActivityFeed.init(); - } - }; -})(jQuery, STUDIP); diff --git a/public/plugins_packages/core/ActivityFeed/plugin.manifest b/public/plugins_packages/core/ActivityFeed/plugin.manifest deleted file mode 100644 index e763516..0000000 --- a/public/plugins_packages/core/ActivityFeed/plugin.manifest +++ /dev/null @@ -1,7 +0,0 @@ -pluginclassname=ActivityFeed -pluginname=ActivityFeed -origin=core -version=1.0 -studipMinVersion=3.4 -plugincontext=Startseite -description=Mit diesem Widget haben Sie alle Aktivitäten im Überblick. diff --git a/public/plugins_packages/core/ActivityFeed/templates/_jstemplates.php b/public/plugins_packages/core/ActivityFeed/templates/_jstemplates.php deleted file mode 100644 index 7bccfe3..0000000 --- a/public/plugins_packages/core/ActivityFeed/templates/_jstemplates.php +++ /dev/null @@ -1,100 +0,0 @@ - - - - - - - diff --git a/public/plugins_packages/core/ActivityFeed/templates/activity_feed.php b/public/plugins_packages/core/ActivityFeed/templates/activity_feed.php deleted file mode 100644 index c6bae30..0000000 --- a/public/plugins_packages/core/ActivityFeed/templates/activity_feed.php +++ /dev/null @@ -1,19 +0,0 @@ -
- -
-
- - - -
-
- -render_partial('_jstemplates'); ?> -
diff --git a/public/plugins_packages/core/ActivityFeed/templates/edit.php b/public/plugins_packages/core/ActivityFeed/templates/edit.php deleted file mode 100644 index 4fab911..0000000 --- a/public/plugins_packages/core/ActivityFeed/templates/edit.php +++ /dev/null @@ -1,23 +0,0 @@ -
-
-

- - $provider): ?> -
- - $prv_name) : ?> - - -
- - -
- - -
-
-
diff --git a/public/plugins_packages/core/Blubber/Blubber.class.php b/public/plugins_packages/core/Blubber/Blubber.class.php deleted file mode 100644 index ce317ad..0000000 --- a/public/plugins_packages/core/Blubber/Blubber.class.php +++ /dev/null @@ -1,113 +0,0 @@ - - * - * 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. - */ - -/** - * Class Blubber - the Blubber-plugin - * This is only used to manage blubber within a course. - */ -class Blubber extends StudIPPlugin implements StandardPlugin -{ - /** - * Plugin URL for CorePlugins is ABSOLUTE_URI_STUDIP. - */ - public function getPluginURL() - { - return $GLOBALS['ABSOLUTE_URI_STUDIP']; - } - - /** - * Returns a navigation for the tab displayed in the course. - * @param string $course_id of the course - * @return \Navigation - */ - public function getTabNavigation($course_id) - { - $tab = new Navigation( - _('Blubber'), - PluginEngine::getURL($this, [], 'messenger/course') - ); - $tab->setImage(Icon::create('blubber', Icon::ROLE_INFO_ALT)); - return ['blubber' => $tab]; - } - - /** - * Returns a navigation-object with the grey/red icon for displaying in the - * my_courses.php page. - * @param string $course_id - * @param int $last_visit - * @param string|null $user_id - * @return \Navigation - */ - public function getIconNavigation($course_id, $last_visit, $user_id = null) - { - $user_id || $user_id = $GLOBALS['user']->id; - $icon = new Navigation( - _('Blubber'), - PluginEngine::getURL($this, [], 'messenger/course') - ); - $icon->setImage(Icon::create('blubber', Icon::ROLE_CLICKABLE, ['title' => _('Blubber-Messenger')])); - - $condition = "INNER JOIN blubber_threads USING (thread_id) - WHERE blubber_threads.context_type = 'course' - AND blubber_threads.context_id = :course_id - AND blubber_comments.mkdate >= :last_visit - AND blubber_comments.user_id != :me - AND blubber_threads.visible_in_stream = 1 - "; - $comments = BlubberComment::findBySQL($condition, [ - 'course_id' => $course_id, - 'last_visit' => $last_visit, - 'me' => $user_id, - ]); - foreach ($comments as $comment) { - if ($comment->thread->isVisibleInStream() && $comment->thread->isReadable() && ($comment->thread->getLatestActivity() > UserConfig::get($user_id)->getValue("BLUBBERTHREAD_VISITED_".$comment['thread_id']))) { - $icon->setImage(Icon::create('blubber', Icon::ROLE_NEW, ['title' => _('Es gibt neue Blubber')])); - $icon->setTitle(_('Es gibt neue Blubber')); - $icon->setBadgeNumber(count($comments)); - $icon->setURL(PluginEngine::getURL($this, ['thread' => 'new'], 'messenger/course')); - break; - } - } - - $condition = "context_type = 'course' - AND context_id = :course_id - AND mkdate >= :last_visit - AND user_id != :me - AND visible_in_stream = 1 - AND ( - blubber_threads.display_class IS NOT NULL - OR blubber_threads.`content` IS NOT NULL - )"; - $threads = BlubberThread::findBySQL($condition, [ - 'course_id' => $course_id, - 'last_visit' => $last_visit, - 'me' => $GLOBALS['user']->id, - ]); - foreach ($threads as $thread) { - if ($thread->isVisibleInStream() && $thread->isReadable() && ($thread['mkdate'] > UserConfig::get($user_id)->getValue("BLUBBERTHREAD_VISITED_".$thread->getId()))) { - $icon->setImage(Icon::create('blubber', Icon::ROLE_ATTENTION, ['title' => _('Es gibt neue Blubber')])); - $icon->setTitle(_('Es gibt neue Blubber')); - break; - } - } - return $icon; - } - - /** - * Returns no template, because this plugin doesn't want to insert an - * info-template in the course-overview. - * @param string $course_id - * @return null - */ - public function getInfoTemplate($course_id) - { - return null; - } -} diff --git a/public/plugins_packages/core/Blubber/controllers/messenger.php b/public/plugins_packages/core/Blubber/controllers/messenger.php deleted file mode 100644 index 8f6a0d2..0000000 --- a/public/plugins_packages/core/Blubber/controllers/messenger.php +++ /dev/null @@ -1,84 +0,0 @@ -assets_url = $this->plugin->getPluginURL() . '/assets/'; - - PageLayout::setBodyElementId('blubber-index'); - PageLayout::setHelpKeyword("Basis/InteraktionBlubber"); - } - - public function course_action($thread_id = null) - { - PageLayout::setTitle(_("Blubber")); - - if (Navigation::hasItem('/course/blubber')) { - Navigation::activateItem("/course/blubber"); - } - - $this->threads = BlubberThread::findByContext(Context::get()->id, true, Context::getType()); - - if (!$thread_id) { - $thread_id = $GLOBALS['user']->cfg->BLUBBER_DEFAULT_THREAD; - } - if ($thread_id) { - foreach ($this->threads as $thread) { - if ($thread->getId() === $thread_id) { - $this->thread = $thread; - break; - } - } - } - if (!$this->thread || Request::get("thread") === "new") { - $this->thread = array_pop(array_reverse($this->threads)); - } - $this->thread->markAsRead(); - - $this->thread_data = $this->thread->getJSONData(); - - if (!Avatar::getAvatar($GLOBALS['user']->id)->is_customized() && !$_SESSION['already_asked_for_avatar']) { - $_SESSION['already_asked_for_avatar'] = true; - PageLayout::postInfo(sprintf(_("Wollen Sie ein Avatar-Bild nutzen? %sLaden Sie jetzt ein Bild hoch%s."), 'id).'" data-dialog>', '')); - } - $this->buildSidebar(); - - $tf = new Flexi_TemplateFactory($GLOBALS['STUDIP_BASE_PATH']."/app/views"); - if (Request::isDialog()) { - PageLayout::setTitle($this->thread->getName()); - $template = $tf->open("blubber/dialog"); - } else { - $template = $tf->open("blubber/index"); - $template->set_layout($GLOBALS['template_factory']->open("layouts/base")); - } - - $template->set_attributes($this->get_assigned_variables()); - $this->render_text($template->render()); - } - - protected function buildSidebar() - { - $sidebar = Sidebar::Get(); - $search = new SearchWidget("#"); - $search->addNeedle( - _("Suche nach ..."), - "search", - true, - null, - null, - null, - [] - ); - $sidebar->addWidget($search, "blubbersearch"); - - $threads_widget = new BlubberThreadsWidget(); - foreach ($this->threads as $thread) { - $threads_widget->addThread($thread); - } - if ($this->thread) { - $threads_widget->setActive($this->thread->getId()); - } - $sidebar->addWidget($threads_widget, "threads"); - } -} diff --git a/public/plugins_packages/core/Blubber/plugin.manifest b/public/plugins_packages/core/Blubber/plugin.manifest deleted file mode 100644 index 3cfbf1d..0000000 --- a/public/plugins_packages/core/Blubber/plugin.manifest +++ /dev/null @@ -1,16 +0,0 @@ -pluginclassname=Blubber -pluginname=Blubber -origin=core -version=3 -summary=Schneller und einfacher Austausch von Informationen -description=Blubber ist eine Kommunikationsform mit Ähnlichkeiten zu einem Forum, in dem aber in Echtzeit miteinander kommuniziert werden kann und das durch den etwas informelleren Charakter eher einem Chat anmutet. Anders als im Forum ist es nicht notwendig, die Seiten neu zu laden, um die neuesten Einträge (z. B. Antworten auf eigene Postings) sehen zu können: Die Seite aktualisiert sich selbst bei neuen Einträgen. Dateien (z.B. Fotos, Audiodateien, Links) können per Drag and Drop in das Feld gezogen und somit verlinkt werden. Auch Textformatierungen und das Verwenden von Smileys sind möglich. - -screenshots = assets/images/plus/screenshots/Blubber -screenshots.blubberscreenshot.png = Blubbern - -displayname = Blubber -icon = assets/images/icons/black/blubber.svg -keywords = Einfach Text schreiben und mit abschicken; Direktes Kontaktieren anderer Stud.IP-NutzerInnen (@Vorname Nachname); Setzen von und Suche nach Stichworten über Hashtags (#Stichwort); Einbinden von Dateien per drag and drop -category = Kommunikation und Zusammenarbeit -descriptionshort = Schneller und einfacher Austausch von Informationen in Gesprächsform -descriptionlong = Kommunikationsform mit Ähnlichkeiten zu einem Forum. Im Gegensatz zum Forum kann mit Blubber jedoch in Echtzeit miteinander kommuniziert werden. Das Tool ähnelt durch den etwas informelleren Charakter einem Messenger. Anders als im Forum ist es nicht notwendig, die Seiten neu zu laden, um die neuesten Einträge (z. B. Antworten auf eigene Postings) sehen zu können. Dateien (z. B. Fotos, Audiodateien, Links) können per drag and drop in das Feld gezogen und somit verlinkt werden. Auch Textformatierungen und das Verwenden von Smileys sind möglich. diff --git a/public/plugins_packages/core/Blubber/views/messenger/course.php b/public/plugins_packages/core/Blubber/views/messenger/course.php deleted file mode 100644 index 2b31011..0000000 --- a/public/plugins_packages/core/Blubber/views/messenger/course.php +++ /dev/null @@ -1 +0,0 @@ -hey \ No newline at end of file diff --git a/public/plugins_packages/core/ContentsWidget/ContentsWidget.php b/public/plugins_packages/core/ContentsWidget/ContentsWidget.php deleted file mode 100644 index 4061081..0000000 --- a/public/plugins_packages/core/ContentsWidget/ContentsWidget.php +++ /dev/null @@ -1,24 +0,0 @@ - - * @license GPL2 or any later version - * @since Stud.IP 5.0 - */ - -class ContentsWidget extends StudIPPlugin implements PortalPlugin -{ - public function getPluginName() - { - return _('Mein Arbeitsplatz'); - } - - public function getPortalTemplate() - { - $template_factory = new Flexi_TemplateFactory(__DIR__ . '/templates'); - $template = $template_factory->open('index'); - $template->tiles = Navigation::getItem('/contents'); - return $template; - } -} diff --git a/public/plugins_packages/core/ContentsWidget/plugin.manifest b/public/plugins_packages/core/ContentsWidget/plugin.manifest deleted file mode 100644 index b05cd4b..0000000 --- a/public/plugins_packages/core/ContentsWidget/plugin.manifest +++ /dev/null @@ -1,6 +0,0 @@ -pluginclassname=ContentsWidget -pluginname=ContentsWidget -origin=core -version=1.0 -plugincontext=Startseite -description=Mit diesem Widget haben Sie einen Überblick über Ihre Inhalte diff --git a/public/plugins_packages/core/ContentsWidget/templates/index.php b/public/plugins_packages/core/ContentsWidget/templates/index.php deleted file mode 100644 index b03df2a..0000000 --- a/public/plugins_packages/core/ContentsWidget/templates/index.php +++ /dev/null @@ -1,28 +0,0 @@ - diff --git a/public/plugins_packages/core/EvaluationsWidget/EvaluationsWidget.php b/public/plugins_packages/core/EvaluationsWidget/EvaluationsWidget.php deleted file mode 100644 index 1dd7d3b..0000000 --- a/public/plugins_packages/core/EvaluationsWidget/EvaluationsWidget.php +++ /dev/null @@ -1,55 +0,0 @@ - - * 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. - */ - -class EvaluationsWidget extends StudIPPlugin implements PortalPlugin -{ - public function getPluginName() - { - return _('Fragebögen'); - } - - /** - * Returns the portal widget template. - * - * Due to a seriously messed up architecture, the suppress_empty_output - * variable is used to determine when an according message should be - * presented to the user. If no evaluations and questionnaires are present, - * the message from the questionnaires should be displayed. The according - * message from evaluations should never be shown in the widget. Thus, the - * evaluation controller will always suppress this message and the variable - * is adjusted if the evaluation returned no content so that the - * questionnaire controller will display it's message. If you think, we're - * slowly running out of duct tape, you might be absolutely right... - */ - public function getPortalTemplate() - { - // include and show votes and tests - if (Config::get()->VOTE_ENABLE) { - $controller = new AuthenticatedController(new StudipDispatcher()); - $controller->suppress_empty_output = true; - - $response = $controller->relay('evaluation/display/studip')->body; - - $controller->suppress_empty_output = (bool)$response; - $response .= $controller->relay('questionnaire/widget/start')->body; - - $template = $GLOBALS['template_factory']->open('shared/string'); - $template->content = $response; - - if ($GLOBALS['perm']->have_perm('root')) { - $navigation = new Navigation('', 'dispatch.php/questionnaire/overview'); - $navigation->setImage(Icon::create('add', 'clickable', ["title" => _('Umfragen bearbeiten')])); - $template->icons = [$navigation]; - } - return $template; - } - } -} diff --git a/public/plugins_packages/core/EvaluationsWidget/plugin.manifest b/public/plugins_packages/core/EvaluationsWidget/plugin.manifest deleted file mode 100644 index f25701c..0000000 --- a/public/plugins_packages/core/EvaluationsWidget/plugin.manifest +++ /dev/null @@ -1,6 +0,0 @@ -pluginclassname=EvaluationsWidget -pluginname=EvaluationsWidget -origin=core -version=1.0 -studipMinVersion=3.1 -description=Mit diesem Widget haben Sie Zugriff auf systemweite Umfragen. diff --git a/public/plugins_packages/core/Forum/CoreForum.class.php b/public/plugins_packages/core/Forum/CoreForum.class.php deleted file mode 100644 index 9ab7b4f..0000000 --- a/public/plugins_packages/core/Forum/CoreForum.class.php +++ /dev/null @@ -1,199 +0,0 @@ - - * @copyright 2011 ELAN e.V. - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - */ - -require_once 'controllers/forum_controller.php'; -require_once 'lib/activities/Activity.php'; -require_once 'models/ForumEntry.php'; - -// Setup autoloader -StudipAutoloader::addAutoloadPath(__DIR__ . '/models'); - -// Notifications -NotificationCenter::addObserver('CoreForum', 'overviewDidClear', "OverviewDidClear"); -NotificationCenter::addObserver('CoreForum', 'removeAbosForUserAndCourse', 'UserDidLeaveCourse'); - -NotificationCenter::addObserver('ForumActivity', 'newEntry', 'ForumAfterInsert'); -NotificationCenter::addObserver('ForumActivity', 'updateEntry', 'ForumAfterUpdate'); -NotificationCenter::addObserver('ForumActivity', 'deleteEntry', 'ForumBeforeDelete'); - -class CoreForum extends StudipPlugin implements ForumModule -{ - /** - * Plugin URL for CorePlugins is ABSOLUTE_URI_STUDIP. - */ - public function getPluginURL() - { - return $GLOBALS['ABSOLUTE_URI_STUDIP']; - } - - /* interface method */ - public function getTabNavigation($course_id) - { - $navigation = new Navigation(_('Forum'), PluginEngine::getURL($this, [], 'index')); - $navigation->setImage(Icon::create('forum', 'info_alt')); - - // add main third-level navigation-item - $navigation->addSubNavigation('index', new Navigation(_('Übersicht'), PluginEngine::getURL($this, [], 'index'))); - - if (ForumPerm::has('fav_entry', $course_id)) { - $navigation->addSubNavigation('newest', new Navigation(_("Neue Beiträge"), PluginEngine::getURL($this, [], 'index/newest'))); - $navigation->addSubNavigation('latest', new Navigation(_("Letzte Beiträge"), PluginEngine::getURL($this, [], 'index/latest'))); - $navigation->addSubNavigation('favorites', new Navigation(_('Gemerkte Beiträge'), PluginEngine::getURL($this, [], 'index/favorites'))); - - // mass-administrate the forum - if (ForumPerm::has('admin', $course_id)) { - $navigation->addSubNavigation('admin', new Navigation(_('Administration'), PluginEngine::getURL($this, [], 'admin'))); - } - } - - return ['forum2' => $navigation]; - } - - /* interface method */ - public function getIconNavigation($course_id, $last_visit, $user_id = null) - { - if ($GLOBALS['perm']->have_studip_perm('user', $course_id)) { - $num_entries = ForumVisit::getCount($course_id, ForumVisit::getVisit($course_id)); - $text = ForumHelpers::getVisitText($num_entries, $course_id); - } else { - $num_entries = 0; - $text = 'Forum'; - } - - $navigation = new Navigation('forum', PluginEngine::getURL($this, [], 'index/enter_seminar')); - $navigation->setBadgeNumber($num_entries); - - if ($num_entries > 0) { - $navigation->setImage(Icon::create('forum', Icon::ROLE_ATTENTION, ['title' => $text])); - } else { - $navigation->setImage(Icon::create('forum', Icon::ROLE_CLICKABLE, ['title' => $text])); - } - - return $navigation; - } - - /** - * This method is called, whenever an user clicked to clear the visit timestamps - * and set everything as visited - * - * @param object $notification - * @param string $user_id - */ - public static function overviewDidClear($notification, $user_id) - { - $query = "REPLACE INTO `forum_visits` - SELECT `user_id`, `Seminar_id`, UNIX_TIMESTAMP(), UNIX_TIMESTAMP() - FROM `seminar_user` - WHERE `user_id` = ?"; - DBManager::get()->execute($query, [$user_id]); - } - - /** - * This method is called whenever a user is removed from a course and thus - * the forum abos will be removed. - * - * @param object $notification - * @param string $course_id - * @param string $user_id - */ - public static function removeAbosForUserAndCourse($notification, $course_id, $user_id) - { - ForumAbo::removeForCourseAndUser($course_id, $user_id); - } - - public function getInfoTemplate($course_id) - { - return null; - } - - /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ - /* * IMPLEMENTATION OF METHODS FROM FORUMMODULE-INTERFACE * */ - /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ - - public function getLinkToThread($issue_id) - { - if ($topic_id = ForumIssue::getThreadIdForIssue($issue_id)) { - return PluginEngine::getLink($this, [], '/index/index/' . $topic_id); - } - - return false; - } - - public function setThreadForIssue($issue_id, $title, $content) - { - ForumIssue::setThreadForIssue(Context::getId(), $issue_id, $title, $content); - } - - public function getNumberOfPostingsForUser($user_id, $seminar_id = null) - { - return ForumEntry::countUserEntries($user_id, $seminar_id); - } - - public function getNumberOfPostingsForIssue($issue_id) - { - $topic_id = ForumIssue::getThreadIdForIssue($issue_id); - - return $topic_id ? ForumEntry::countEntries($topic_id) : 0; - } - - public function getNumberOfPostingsForSeminar($seminar_id) - { - return floor(ForumEntry::countEntries($seminar_id)); - } - - public function getNumberOfPostings() - { - return ForumEntry::countAllEntries(); - } - - public function getEntryTableInfo() - { - return [ - 'table' => 'forum_entries', - 'content' => 'content', - 'chdate' => 'chdate', - 'seminar_id' => 'seminar_id', - 'user_id' => 'user_id' - ]; - } - - public function getTopTenSeminars() - { - return ForumEntry::getTopTenSeminars(); - } - - public function migrateUser($user_from, $user_to) - { - return ForumEntry::migrateUser($user_from, $user_to); - } - - public function deleteContents($seminar_id) - { - return ForumEntry::delete($seminar_id); - } - - public function getDump($seminar_id) - { - return ForumEntry::getDump($seminar_id); - } - - public static function getDescription() - { - return _('Textbasierte und zeit- und ortsunabhängige '. - 'Diskursmöglichkeit. Lehrende können parallel zu '. - 'Veranstaltungsthemen Fragen stellen, die von den Studierenden '. - 'per Meinungsaustausch besprochen werden.'); - } -} diff --git a/public/plugins_packages/core/Forum/controllers/admin.php b/public/plugins_packages/core/Forum/controllers/admin.php deleted file mode 100644 index 765c2b8..0000000 --- a/public/plugins_packages/core/Forum/controllers/admin.php +++ /dev/null @@ -1,123 +0,0 @@ - - * - * 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. - */ -class 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 = []; - // iterate over all categories and add the belonging areas to them - foreach ($categories = 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 - $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(); - } - - 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); - 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/public/plugins_packages/core/Forum/controllers/area.php b/public/plugins_packages/core/Forum/controllers/area.php deleted file mode 100644 index df11912..0000000 --- a/public/plugins_packages/core/Forum/controllers/area.php +++ /dev/null @@ -1,78 +0,0 @@ - - * - * 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. - */ - -class 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(PluginEngine::getLink('coreforum/index/index/')); - } - } - - function edit_action($area_id) - { - ForumPerm::check('edit_area', $this->getId(), $area_id); - - if (Request::isAjax()) { - ForumEntry::update($area_id, Request::get('name'), Request::get('content')); - $this->render_json(['content' => ForumEntry::killFormat(ForumEntry::killEdit(Request::get('content')))]); - } else { - ForumEntry::update($area_id, Request::get('name'), Request::get('content')); - $this->flash['messages'] = ['success' => _('Die Änderungen am Bereich wurden gespeichert.')]; - $this->redirect(PluginEngine::getLink('coreforum/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/public/plugins_packages/core/Forum/controllers/forum_controller.php b/public/plugins_packages/core/Forum/controllers/forum_controller.php deleted file mode 100644 index d25aa55..0000000 --- a/public/plugins_packages/core/Forum/controllers/forum_controller.php +++ /dev/null @@ -1,67 +0,0 @@ -dispatcher->current_plugin, $params, join('/', $args)); - } - - /* * * * * * * * * * * * * * * * * * * * * * * * * */ - /* * * * * 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->template_factory = - new Flexi_TemplateFactory(dirname(__FILE__) . '/../templates'); - - //$this->check_token(); - - //$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(); - } -} diff --git a/public/plugins_packages/core/Forum/controllers/index.php b/public/plugins_packages/core/Forum/controllers/index.php deleted file mode 100644 index c149e13..0000000 --- a/public/plugins_packages/core/Forum/controllers/index.php +++ /dev/null @@ -1,849 +0,0 @@ - - * - * 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. - */ - -class 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(PluginEngine::getLink('coreforum/index/newest')); - } else { - $this->redirect(PluginEngine::getLink('coreforum/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 = []; - // iterate over all categories and add the belonging areas to them - foreach ($categories = 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 - $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) - { - $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']; - $this->show_full_path = true; - - // set default layout - $layout = $GLOBALS['template_factory']->open('layouts/base'); - $this->set_layout($layout); - - 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) - { - $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; - - // set default layout - $layout = $GLOBALS['template_factory']->open('layouts/base'); - $this->set_layout($layout); - - if (empty($this->postings)) { - $this->no_entries = true; - } - - $this->render_action('index'); - } - - /** - * show the current users favorized entries - * - * @param int $page show entries on submitted page - */ - function favorites_action($page = null) - { - $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; - - // set default layout - $layout = $GLOBALS['template_factory']->open('layouts/base'); - $this->set_layout($layout); - - if (empty($this->postings)) { - $this->no_entries = true; - } - - // 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('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; - - // 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']; - $this->highlight = $list['highlight']; - - if (empty($this->postings)) { - $this->flash['messages'] = ['info' => _('Es wurden keine Beiträge gefunden, die zu Ihren Suchkriterien passen!')]; - } - } - - // set default layout - $layout = $GLOBALS['template_factory']->open('layouts/base'); - $this->set_layout($layout); - - // 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 * * * */ - /* * * * * * * * * * * * * * * * * * * * * * * * * */ - - /** - * this action renders a preview of the submitted text - */ - function preview_action() { - if (Request::isXhr()) { - $this->set_content_type('text/html; charset=UTF-8'); - $this->render_text(formatReady(transformBeforeSave(Request::get('posting')))); - } else { - $this->render_text( - ForumEntry::getContentAsHtml( - transformBeforeSave(Request::get('posting')) - ) - ); - } - } - - /** - * 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(PluginEngine::getLink('coreforum/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); - - if (ForumPerm::hasEditPerms($topic_id) || ForumPerm::check('remove_entry', $this->getId(), $topic_id)) { - $path = ForumEntry::getPathToPosting($topic_id); - $topic = array_pop($path); - $parent = array_pop($path); - - if ($topic_id != $this->getId()) { - // only delete directly if passed by ajax, otherwise ask for confirmation - if (Request::isXhr() || Request::isPost() || Request::get('approve_delete')) { - CSRFProtection::verifyUnsafeRequest(); - ForumEntry::delete($topic_id); - $this->flash['messages'] = ['success' => sprintf(_('Der Eintrag %s wurde gelöscht!'), $topic['name'])]; - } else { - $this->flash['messages'] = ['info_html' => - sprintf(_('Sind sie sicher dass Sie den Eintrag %s löschen möchten?'), $topic['name']) - . '
'. \Studip\LinkButton::createAccept(_('Ja'), PluginEngine::getUrl('coreforum/index/delete_entry/'. $topic_id .'?approve_delete=1')) - . \Studip\LinkButton::createCancel(_('Nein'), PluginEngine::getUrl('coreforum/index/index/'. ForumEntry::getParentTopicId($topic_id) .'/'. $page)) - ]; - } - } else { - $this->flash['messages'] = ['success' => _('Sie können nicht die gesamte Veranstaltung löschen!')]; - } - } - - if (Request::isXhr()) { - $this->render_template('messages'); - $this->flash['messages'] = null; - } else { - $this->redirect('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(PluginEngine::getLink('coreforum/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(PluginEngine::getLink('coreforum/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('index/_favorite'); - } else { - $this->redirect(PluginEngine::getLink('coreforum/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('index/_favorite'); - } else { - $this->redirect(PluginEngine::getLink('coreforum/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(PluginEngine::getLink('coreforum/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(PluginEngine::getURL('coreforum/index/'. $section .'/'. (int)$page - .'/?searchfor='. Request::get('searchfor') .'&'. implode('&', $optionlist))); - break; - - default: - $this->redirect(PluginEngine::getLink('coreforum/index/'. $section .'/'. (int)$page)); - break; - } - } - - /** - * Like the submitted topic - * - * @param string $topic_id the topic to like - */ - function like_action($topic_id) - { - ForumPerm::check('like_entry', $this->getId(), $topic_id); - - ForumLike::like($topic_id); - - if (Request::isXhr()) { - $this->topic_id = $topic_id; - $this->render_template('index/_like'); - } else { - $this->redirect(PluginEngine::getLink('coreforum/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) - { - ForumPerm::check('like_entry', $this->getId(), $topic_id); - - ForumLike::dislike($topic_id); - - if (Request::isXhr()) { - $this->topic_id = $topic_id; - $this->render_template('index/_like'); - } else { - $this->redirect(PluginEngine::getLink('coreforum/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 { - $this->flash['messages'] = ['success' => $success_text]; - $this->redirect(PluginEngine::getLink('coreforum/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 { - $this->flash['messages'] = ['success' => $success_text]; - $this->redirect(PluginEngine::getLink('coreforum/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(PluginEngine::getLink('coreforum/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(PluginEngine::getLink('coreforum/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(PluginEngine::getLink('coreforum/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('messages'); - } else { - $this->redirect(PluginEngine::getLink('coreforum/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(PluginEngine::getLink('coreforum/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('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(PluginEngine::getLink('coreforum/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('index/_abo_link'); - } else { - $this->flash['messages'] = ['success' => _('Abonnement aufgehoben.')]; - $this->redirect(PluginEngine::getLink('coreforum/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); - } -} diff --git a/public/plugins_packages/core/Forum/models/ForumAbo.php b/public/plugins_packages/core/Forum/models/ForumAbo.php deleted file mode 100644 index 6aee647..0000000 --- a/public/plugins_packages/core/Forum/models/ForumAbo.php +++ /dev/null @@ -1,189 +0,0 @@ - - * @license http://www.gnu.org/licenses/gpl-3.0.html GPL version 3 - * @category Stud.IP - */ - -require_once('lib/messaging.inc.php'); - -class ForumAbo -{ - /** - * add the passed user as a watcher for the passed topic (including all - * current and future childs) - * - * @param string $topic_id - * @param string $user_id - */ - public static function add($topic_id, $user_id = null) - { - if (!$user_id) $user_id = $GLOBALS['user']->id; - - $stmt = DBManager::get()->prepare("REPLACE INTO forum_abo_users - (topic_id, user_id) VALUEs (?, ?)"); - $stmt->execute([$topic_id, $user_id]); - } - - /** - * remove the passed user as a watcher from the passed topic (including all - * current and future childs) - * - * @param string $topic_id - * @param string $user_id - */ - public static function delete($topic_id, $user_id = null) - { - if (!$user_id) $user_id = $GLOBALS['user']->id; - - $stmt = DBManager::get()->prepare("DELETE FROM forum_abo_users - WHERE topic_id = ? AND user_id = ?"); - $stmt->execute([$topic_id, $user_id]); - } - - /** - * check, if the passed user watches the passed topic. If no user_id is passed, - * the currently logged in user is used - * - * @param string $topic_id - * @param string $user_id - * - * @return boolean returns true if user is watching, false otherwise - */ - public static function has($topic_id, $user_id = null) - { - if (!$user_id) $user_id = $GLOBALS['user']->id; - - $stmt = DBManager::get()->prepare("SELECT COUNT(*) FROM forum_abo_users - WHERE topic_id = ? AND user_id = ?"); - $stmt->execute([$topic_id, $user_id]); - - return $stmt->fetchColumn() > 0 ? true : false; - } - - /** - * send out the notification messages for the passed topic. The contents - * and a link directly to the topic are added to the message. - * - * @param string $topic_id - */ - public static function notify($topic_id) - { - // send message to all abo-users - $db = DBManager::get(); - $messaging = new ForumBulkMail(); - // $messaging = new Messaging(); - - // get all parent topic-ids, to find out which users to notify - $path = ForumEntry::getPathToPosting($topic_id); - - // fetch all users to notify, exclude current user - $stmt = $db->prepare("SELECT DISTINCT user_id - FROM forum_abo_users - WHERE topic_id IN (:topic_ids) - AND user_id != :user_id"); - $stmt->bindParam(':topic_ids', array_keys($path), StudipPDO::PARAM_ARRAY); - $stmt->bindParam(':user_id', $GLOBALS['user']->id); - $stmt->execute(); - - // get details for topic - $topic = ForumEntry::getConstraints($topic_id); - - $template_factory = new Flexi_TemplateFactory(dirname(__FILE__) . '/../views'); - $template = $template_factory->open('index/_mail_notification'); - - // notify users - while ($data = $stmt->fetch(PDO::FETCH_ASSOC)) { - $user_id = $data['user_id']; - - // don't notify user if view permission is not granted - if (!ForumPerm::has('view', $topic['seminar_id'], $user_id)) { - continue; - } - - $user = User::find($user_id); - - // check if user wants an email for all or selected messages only - $force_email = false; - if ($messaging->user_wants_email($user_id)) { - $force_email = true; - } - // do not send mails when account is locked or expired - $expiration = UserConfig::get($user->id)->EXPIRATION_DATE; - if ($user->locked || ($expiration > 0 && $expiration < time())) { - $force_email = false; - } - $parent_id = ForumEntry::getParentTopicId($topic['topic_id']); - - setTempLanguage($data['user_id']); - $notification = sprintf(_("%s hat einen Beitrag geschrieben"), ($topic['anonymous'] ? _('Anonym') : $topic['author'])); - restoreLanguage(); - - PersonalNotifications::add( - $user_id, - UrlHelper::getUrl( - 'plugins.php/coreforum/index/index/' . $topic['topic_id'] . '#' . $topic['topic_id'], - ['cid' => $topic['seminar_id']], - true - ), - $notification, - "forumposting_" . $topic['topic_id'], - Icon::create('forum', 'clickable') - ); - - if ($force_email) { - $title = implode(' >> ', ForumEntry::getFlatPathToPosting($topic_id)); - - $subject = _('[Forum]') . ' ' . ($title ?: _('Neuer Beitrag')); - - $htmlMessage = $template->render( - compact('user_id', 'topic', 'path') - ); - - $textMessage = trim(kill_format($htmlMessage)); - - $userWantsHtml = UserConfig::get($user_id)->MAIL_AS_HTML; - - StudipMail::sendMessage( - $user->email, - $subject, - $textMessage, - $userWantsHtml ? $htmlMessage : null - ); - } - } - - $messaging->bulkSend(); - } - - /** - * Removes all abos for a given course and user - * - * @param String $course_id Id of the course - * @param String $user_id Id of the user - * @return int number of removed abos - */ - public static function removeForCourseAndUser($course_id, $user_id) - { - $query = "DELETE FROM `forum_abo_users` - WHERE `user_id` = :user_id - AND `topic_id` IN ( - SELECT `topic_id` - FROM `forum_entries` - WHERE `seminar_id` = :course_id - )"; - $statement = DBManager::get()->prepare($query); - $statement->bindValue(':course_id', $course_id); - $statement->bindValue(':user_id', $user_id); - $statement->execute(); - return $statement->rowCount(); - } -} diff --git a/public/plugins_packages/core/Forum/models/ForumActivity.php b/public/plugins_packages/core/Forum/models/ForumActivity.php deleted file mode 100644 index 20ebeb6..0000000 --- a/public/plugins_packages/core/Forum/models/ForumActivity.php +++ /dev/null @@ -1,149 +0,0 @@ - - * @license https://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - */ - -class ForumActivity -{ - /** - * Post activity for new forum post - * - * @param string $event - * @param string $topic_id - * @param array $post - */ - public static function newEntry($event, $topic_id, $post) - { - $verb = $post['depth'] == 3 ? 'answered' : 'created'; - - if ($verb == 'created') { - if ($post['depth'] == 1) { - $summary = _('%s hat im Forum der Veranstaltung "%s" einen Bereich erstellt.'); - } else { - $summary = _('%s hat im Forum der Veranstaltung "%s" ein Thema erstellt.'); - } - } else { - $summary = _('%s hat im Forum der Veranstaltung "%s" auf ein Thema geantwortet.'); - } - - self::post($post, $verb, $summary); - } - - /** - * Post activity for updating a forum post - * @param string $event - * @param string $topic_id - * @param string $post - */ - public static function updateEntry($event, $topic_id, $post) - { - $summary = _('%s hat im Forum der Veranstaltung "%s" einen Beitrag editiert.'); - - if ($post['user_id'] == $GLOBALS['user']->id) { - $content = sprintf( - _('%s hat seinen eigenen Beitrag vom %s editiert.'), - self::getPostUsername($post), - date('d.m.y, H:i', $post['mkdate']) - ); - } else { - $content = sprintf( - _('%s hat den Beitrag von %s vom %s editiert.'), - get_fullname($GLOBALS['user']->id), - self::getPostUsername($post), - date('d.m.y, H:i', $post['mkdate']) - ); - } - - self::post($post, 'edited', $summary, $content); - } - - /** - * Post activity for deleting a forum post - * $param string $event - * @param string $topic_id - * @param string $post - */ - public static function deleteEntry($event, $topic_id, $post) - { - // Remove all previous activities for the post - Studip\Activity\Activity::deleteBySQL( - "provider = ? AND object_type = 'forum' AND object_id = ?", - [Studip\Activity\ForumProvider::class, $topic_id] - ); - - $summary = _('%s hat im Forum der Veranstaltung "%s" einen Beitrag gelöscht.'); - - if ($post['user_id'] == $GLOBALS['user']->id) { - $content = sprintf( - _('%s hat seinen Beitrag vom %s gelöscht.'), - self::getPostUsername($post), - date('d.m.y, H:i', $post['mkdate']) - ); - } else { - $content = sprintf( - _('%s hat den Beitrag von %s vom %s gelöscht.'), - get_fullname($GLOBALS['user']->id), - self::getPostUsername($post), - date('d.m.y, H:i', $post['mkdate']) - ); - } - - self::post($post, 'deleted', $summary, $content); - } - - private static function post($post, $verb, $summary, $content = null) - { - // skip system-created entries like "Allgemeine Diskussionen" - if (!$post['user_id']) { - return; - } - - $range_id = $post['seminar_id']; - $type = get_object_type($range_id); - - $obj = get_object_name($range_id, $type); - - $data = [ - 'provider' => 'Studip\Activity\ForumProvider', - 'context' => $type === 'sem' ? 'course' : 'institute', - 'context_id' => $post['seminar_id'], - 'content' => null, - 'actor_type' => 'user', // who initiated the activity? - 'actor_id' => $post['user_id'], // id of initiator - 'verb' => $verb, // the activity type - 'object_id' => $post['topic_id'], // the id of the referenced object - 'object_type' => 'forum', // type of activity object - 'mkdate' => $post['mkdate'] ?: time() - ]; - - if ($post['anonymous']) { - $data['actor_type'] = 'anonymous'; - $data['actor_id'] = ''; - } - - $activity = Studip\Activity\Activity::create($data); - } - - /** - * Returns the poster's name for a forum post. - * - * @param array $post - * @return string - */ - private static function getPostUsername($post) - { - if ($post['anonymous']) { - return _('Anonym'); - } - - return get_fullname($post['user_id']); - } -} diff --git a/public/plugins_packages/core/Forum/models/ForumBulkMail.php b/public/plugins_packages/core/Forum/models/ForumBulkMail.php deleted file mode 100644 index abde366..0000000 --- a/public/plugins_packages/core/Forum/models/ForumBulkMail.php +++ /dev/null @@ -1,137 +0,0 @@ - - * @license http://www.gnu.org/licenses/gpl-3.0.html GPL version 3 - * @category Stud.IP - */ - -require_once 'lib/messaging.inc.php'; - -class ForumBulkMail extends Messaging { - var $bulk_mail; - - /** - * Overwrites the parent method. This method combines messages with the same - * content and prepares them for sending them as a mail with multiple - * recepients instead of one mail for each recipient. - * The actual sending task is done bulkSend(). - * - * @global object $user - * - * @param string $rec_user_id user_id of recipient - * @param string $snd_user_id user_id of sender - * @param string $message the message - * @param string $subject subject for the message - * @param string $message_id the message_id in the database - */ - function sendingEmail($rec_user_id, $snd_user_id, $message, $subject, $message_id) - { - $receiver = User::find($rec_user_id); - - if ($receiver && $receiver->email) { - $rec_fullname = 'Sie'; - - setTempLanguage($receiver->id); - - if (empty($this->bulk_mail[md5($message)][getenv('LANG')])) { - - $title = "[Stud.IP - " . Config::get()->UNI_NAME_CLEAN . "] ".stripslashes(kill_format(str_replace(["\r","\n"], '', $subject))); - - if ($snd_user_id != "____%system%____") { - $sender = User::find($snd_user_id); - $reply_to = $sender->email; - } - - $template = $GLOBALS['template_factory']->open('mail/text'); - $template->message = kill_format(stripslashes($message)); - $template->rec_fullname = $receiver->getFullname(); - $mailmessage = $template->render(); - - $template = $GLOBALS['template_factory']->open('mail/html'); - $template->lang = getUserLanguagePath($rec_user_id); - $template->message = stripslashes($message); - $template->rec_fullname = $receiver->getFullname(); - $mailhtml = $template->render(); - - $this->bulk_mail[md5($message)][getenv('LANG')] = [ - 'text' => $mailmessage, - 'html' => $mailhtml, - 'title' => $title, - 'reply_to' => $reply_to, - 'message_id' => $message_id, - 'users' => [] - ]; - } - - $this->bulk_mail[md5($message)][getenv('LANG')]['users'][$receiver->id] = $receiver->email; - - restoreLanguage(); - } - } - - - /** - * Sends the collected messages from sendingMail as e-mail. - */ - function bulkSend() - { - // if nothing to do, return - if (empty($this->bulk_mail)) return; - - // send a mail, for each language one - foreach ($this->bulk_mail as $lang_data) { - foreach ($lang_data as $data) { - $mail = new StudipMail(); - $mail->setSubject($data['title']); - - foreach ($data['users'] as $user_id => $to) { - $mail->addRecipient($to, get_fullname($user_id), 'Bcc'); - } - - $mail->setReplyToEmail('') - ->setBodyText($data['text']); - - if (mb_strlen($data['reply_to'])) { - $mail->setSenderEmail($data['reply_to']) - ->setSenderName($snd_fullname); - } - - $user_cfg = UserConfig::get($user_id); - if ($user_cfg->getValue('MAIL_AS_HTML')) { - $mail->setBodyHtml($mailhtml); - } - - if($GLOBALS["ENABLE_EMAIL_ATTACHMENTS"]){ - $message = Message::find($data['message_id']); - - $current_user = User::findCurrent(); - - $message_folder = MessageFolder::findMessageTopFolder( - $message->id, - $current_user->id - ); - - $message_folder = $message_folder->getTypedFolder(); - - $attachments = FileManager::getFolderFilesRecursive( - $message_folder, - $current_user->id - ); - - - foreach($attachments as $attachment) { - $mail->addStudipAttachment($attachment); - } - } - $mail->send(); - } - } - } -} diff --git a/public/plugins_packages/core/Forum/models/ForumCat.php b/public/plugins_packages/core/Forum/models/ForumCat.php deleted file mode 100644 index c963b69..0000000 --- a/public/plugins_packages/core/Forum/models/ForumCat.php +++ /dev/null @@ -1,252 +0,0 @@ - - * @license http://www.gnu.org/licenses/gpl-3.0.html GPL version 3 - * @category Stud.IP - */ - -class ForumCat extends SimpleORMap -{ - /** - * Configures this model. - * - * @param array $config Configuration array - */ - protected static function configure($config = []) - { - $config['db_table'] = 'forum_categories'; - parent::configure($config); - } - - /** - * Return a list of all areas with their categories. - * Empty categories are excluded by default - * - * @param string $seminar_id the seminar_id the retrieve the categories for - * @param string $exclude_null if false, empty categories are returned as well - * @return array list of categories - */ - public static function getListWithAreas($seminar_id, $exclude_null = true) - { - $stmt = DBManager::get()->prepare("SELECT * FROM forum_categories AS fc - LEFT JOIN forum_categories_entries AS fce USING (category_id) - WHERE seminar_id = ? " - . ($exclude_null ? 'AND fce.topic_id IS NOT NULL ' : '') - . "ORDER BY fc.pos ASC, fce.pos ASC"); - - $stmt->execute([$seminar_id]); - - return $stmt->fetchAll(PDO::FETCH_ASSOC); - } - - /** - * Returns the name of the associated category for an area denoted by the - * passed topic_id - * - * @param string $topic_id - * @return string the name of the category - */ - public static function getCategoryNameForArea($topic_id) - { - $stmt = DBManager::get()->prepare("SELECT fc.entry_name FROM forum_categories AS fc - LEFT JOIN forum_categories_entries AS fce USING (category_id) - WHERE fce.topic_id = ?"); - $stmt->execute([$topic_id]); - - return $stmt->fetchColumn(); - } - - - /** - * Adds a new category with the passed name to the passed seminar and - * returns the id of the newly created category - * - * @param string $seminar_id - * @param string $name the name of the new category - * @return string the id of the newly created category - */ - public static function add($seminar_id, $name) - { - $stmt = DBManager::get()->prepare("INSERT INTO forum_categories - (category_id, seminar_id, entry_name) - VALUES (?, ?, ?)"); - - $category_id = md5(uniqid(rand())); - - $stmt->execute([$category_id, $seminar_id, $name]); - - return $category_id; - } - - - /** - * Remove the category with the passed id. The seminar_id is used only - * to be certain. - * - * @param string $category_id The ID of the category to be deleted - * @param string $seminar_id Seminar-ID the category belongs to - */ - public static function remove($category_id, $seminar_id) - { - // delete the category itself - $stmt = DBManager::get()->prepare("DELETE FROM - forum_categories - WHERE category_id = ?"); - $stmt->execute([$category_id]); - - // set all entries to default category - $stmt = DBManager::get()->prepare("UPDATE - forum_categories_entries - SET category_id = ?, pos = 999 - WHERE category_id = ?"); - $stmt->execute([$seminar_id, $category_id]); - } - - - /** - * Set the position for the passed category to the passed value - * - * @param string $category_id the ID of the category to update - * @param int $pos the new position - */ - public static function setPosition($category_id, $pos) - { - $stmt = DBManager::get()->prepare("UPDATE - forum_categories - SET pos = ? WHERE category_id = ?"); - $stmt->execute([$pos, $category_id]); - } - - - /** - * Add the passed area to the passed category and remove it from all - * other categories. - * - * @param string $category_id the ID of the category - * @param string $area_id the ID of the area to add the category to - */ - public static function addArea($category_id, $area_id) - { - // remove area from all other categories - $stmt = DBManager::get()->prepare("DELETE FROM - forum_categories_entries - WHERE topic_id = ?"); - $stmt->execute([$area_id]); - - // add area to this category, make sure it is at the end - $stmt = DBManager::get()->prepare("SELECT COUNT(*) FROM - forum_categories_entries - WHERE category_id = ?"); - $stmt->execute([$category_id]); - $new_pos = $stmt->fetchColumn() + 1; - - $stmt = DBManager::get()->prepare("REPLACE INTO - forum_categories_entries - (category_id, topic_id, pos) VALUES (?, ?, ?)"); - $stmt->execute([$category_id, $area_id, $new_pos]); - } - - - /** - * Remove the passed area from all categories. - * - * @param string $area_id the ID of the area to be removed - */ - public static function removeArea($area_id) - { - $stmt = DBManager::get()->prepare("DELETE FROM - forum_categories_entries - WHERE topic_id = ?"); - $stmt->execute([$area_id]); - } - - - /** - * Set the position for the passed category to the passed value - * - * @param string $area_id the ID of the area to update - * @param int $pos the new position - */ - public static function setAreaPosition($area_id, $pos) - { - $stmt = DBManager::get()->prepare("UPDATE - forum_categories_entries - SET pos = ? WHERE topic_id = ?"); - $stmt->execute([$pos, $area_id]); - } - - - /** - * Set the name for the passed category - * - * @param string $category_id the ID of the category to update - * @param string $name the name to set - */ - public static function setName($category_id, $name) - { - $stmt = DBManager::get()->prepare("UPDATE - forum_categories - SET entry_name = ? WHERE category_id = ?"); - $stmt->execute([$name, $category_id]); - } - - /** - * Return the data for the passed category_id - * - * @param string $category_id - * - * @return array the data for the passed category_id - */ - public static function get($category_id) - { - $stmt = DBManager::get()->prepare("SELECT * FROM forum_categories - WHERE category_id = ?"); - $stmt->execute([$category_id]); - - return $stmt->fetch(PDO::FETCH_ASSOC); - } - - /** - * Return the areas for the passed category_id - * - * @param string $category_id - * @param int $start limit start (optional) - * @param int $num number of entries to fetch (optional, default is 20) - * - * @return array the data for the passed category_id - */ - public static function getAreas($category_id, $start = null, $num = 20) - { - $category = self::get($category_id); - - $limit = ''; - if ($start !== null && $num) { - $limit = " LIMIT $start, $num"; - } - - if ($category_id == $category['seminar_id']) { - $stmt = DBManager::get()->prepare("SELECT fe.* FROM forum_entries AS fe - LEFT JOIN forum_categories_entries AS fce USING (topic_id) - WHERE seminar_id = ? AND depth = 1 AND ( - fce.category_id = ? OR fce.category_id IS NULL - ) ORDER BY category_id DESC, pos ASC" . $limit); - $stmt->execute([$category_id, $category_id]); - } else { - $stmt = DBManager::get()->prepare("SELECT forum_entries.* FROM forum_categories_entries - LEFT JOIN forum_entries USING(topic_id) - WHERE category_id = ? - ORDER BY pos ASC" . $limit); - - $stmt->execute([$category_id]); - } - - return $stmt->fetchAll(PDO::FETCH_ASSOC); - } -} diff --git a/public/plugins_packages/core/Forum/models/ForumEntry.php b/public/plugins_packages/core/Forum/models/ForumEntry.php deleted file mode 100644 index bd62b08..0000000 --- a/public/plugins_packages/core/Forum/models/ForumEntry.php +++ /dev/null @@ -1,1385 +0,0 @@ - - * @license http://www.gnu.org/licenses/gpl-3.0.html GPL version 3 - * @category Stud.IP - */ - -class ForumEntry implements PrivacyObject -{ - const WITH_CHILDS = true; - const WITHOUT_CHILDS = false; - const THREAD_PREVIEW_LENGTH = 100; - const POSTINGS_PER_PAGE = 10; - const FEED_POSTINGS = 100; - - - /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - * H E L P E R - F U N C T I O N S * - * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ - - /** - * is used for posting-preview. replaces all newlines with spaces - * - * @param string $text the text to work on - * @returns string - */ - public static function br2space($text) - { - return str_replace("\n", ' ', str_replace("\r", '', $text)); - } - - /** - * remove the edit-html from a posting - * - * @param string $description the posting-content - * @return string the content stripped by the edit-mark - */ - public static function killEdit($description) - { - // wurde schon mal editiert - if (preg_match('/^(.*)("; - return $description . $edit; - } - - /** - * convert the edit-html to raw text - * - * @param string $description the posting-content - * @return string the content with the raw text version of the edit-mark - */ - public static function parseEdit($description, $anonymous = false) - { - // TODO figure out if this function can be removed - // has been replaced with getContentAsHTML in core code - $content = ForumEntry::killEdit($description); - $comment = ForumEntry::getEditComment($description, $anonymous); - return $content . ($comment ? "\n\n%%" . $comment .'%%' : ''); - } - - /** - * Get content with appended edit comment as HTML. - * - * @param string $description Database entry of forum entry's body. - * @param bool $anonymous True, if only root is allowed to see - * authors. - * @return string Content and edit comment as HTML. - */ - public static function getContentAsHtml($description, $anonymous = false) - { - $raw_content = ForumEntry::killEdit($description); - - $comment = ForumEntry::getEditComment($description, $anonymous); - $content = formatReady($raw_content); - - if ($comment) { - $content .= '
' . htmlReady($comment) . ''; - } - - return $content; - } - - /** - * Get author and time of an edited forum entry as a string. - * - * @param string $description Database entry of forum entry's body. - * @param bool $anonymous True, if only root is allowed to see - * authors. - * @return string Author and time or empty string if not edited. - */ - public static function getEditComment($description, $anonymous = false) - { - $info = ForumEntry::getEditInfo($description); - if ($info) { - $root = $GLOBALS['perm']->have_perm('root'); - $author = ($anonymous && !$root) ? _('Anonym') : $info['author']; - $time = date('d.m.y - H:i', $info['time']); - return '[' . _('Zuletzt editiert von') . " $author - $time]"; - } - return ''; - } - - /** - * Get author and time of an edited forum entry. - * - * @param string $description Database entry of forum entry's body. - * @return array Associative array containing author and time. - * boolean False if edit tag was not found. - */ - public static function getEditInfo($description) { - if (preg_match('/\s*$/i', $description, $matches)) { - // wurde schon mal editiert - return ['author' => $matches[1], 'time' => $matches[2]]; - } - return false; - } - - /** - * Remove all quote blocks AND the quoted text from a forum post. - * - * @param String $string The string to remove the quote blocks from - * @return String the posting without the [quote]-blocks (not just tags!) - */ - public static function removeQuotes($description) - { - if (Studip\Markup::isHtml($description)) { - // remove all blockquote tags - $dom = new DOMDocument(); - $dom->loadHtml($description, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD); - $nodes = iterator_to_array($dom->getElementsByTagName('blockquote')); - - foreach ($nodes as $node) { - $node->parentNode->removeChild($node); - } - - return $dom->saveHTML(); - } else { - $description = preg_replace('/\[quote(=.*)\].*\[\/quote\]/isU', '', $description); - $description = str_replace('[/quote]', '', $description); - } - return $description; - } - - - /** - * calls Stud.IP's kill_format and additionally removes any found smiley-tag - * - * @param string $text the text to parse - * @return string the text without format-tags and without smileys - */ - public static function killFormat($text) - { - $text = kill_format($text); - - // find stuff which is enclosed between to colons - preg_match('/' . SmileyFormat::REGEXP . '/U', $text, $matches); - - // remove the match if it is a smiley - foreach ($matches as $match) { - if (Smiley::getByName($match) || Smiley::getByShort($match)) { - $text = str_replace($match, '', $text); - } - } - - return $text; - } - - /** - * returns the entry for the passed topic_id - * - * @param string $topic_id - * @return array array('lft' => ..., 'rgt' => ..., seminar_id => ...) - * - * @throws Exception - */ - public static function getConstraints($topic_id) - { - //very bad performance if topic_id is 0 or false - if (!$topic_id) return false; - - // look up the range of postings - $range_stmt = DBManager::get()->prepare("SELECT * - FROM forum_entries WHERE topic_id = ?"); - $range_stmt->execute([$topic_id]); - if (!$data = $range_stmt->fetch(PDO::FETCH_ASSOC)) { - return false; - // throw new Exception("Could not find entry with id >>$topic_id<< in forum_entries, " . __FILE__ . " on line " . __LINE__); - } - - if ($data['depth'] == 1) { - $data['area'] = 1; - } - - return $data; - } - - /** - * return the topic_id of the parent element, false if there is none (ie the - * passed topic_id is already the upper-most node in the tree) - * - * @param string $topic_id the topic_id for which the parent shall be found - * - * @return string the topic_id of the parent element or false - */ - public static function getParentTopicId($topic_id) - { - $path = ForumEntry::getPathToPosting($topic_id); - array_pop($path); - $data = array_pop($path); - - return $data['id'] ?: false; - } - - - /** - * get the topic_ids of all childs of the passed topic including itself - * - * @param string $topic_id the topic_id to find the childs for - * @return array a list if topic_ids - */ - public static function getChildTopicIds($topic_id) - { - $constraints = ForumEntry::getConstraints($topic_id); - - $stmt = DBManager::get()->prepare("SELECT topic_id - FROM forum_entries WHERE lft >= ? AND rgt <= ? - AND seminar_id = ?"); - $stmt->execute([$constraints['lft'], $constraints['rgt'], $constraints['seminar_id']]); - - return $stmt->fetchAll(PDO::FETCH_COLUMN); - } - - /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * - * D A T A - R E T R I E V A L * - * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ - - /** - * get the page the passed posting is on - * - * @param string $topic_id - * @return int - */ - public static function getPostingPage($topic_id, $constraint = null) - { - if (!$constraint) { - $constraint = ForumEntry::getConstraints($topic_id); - } - - // this calculation only works for postings - if ($constraint['depth'] <= 2) return ForumHelpers::getPage(); - - if ($parent_id = ForumEntry::getParentTopicId($topic_id)) { - $parent_constraint = ForumEntry::getConstraints($parent_id); - - return ceil((($constraint['lft'] - $parent_constraint['lft'] + 3) / 2) / ForumEntry::POSTINGS_PER_PAGE); - } - - return 0; - } - - /** - * return the id for the oldest unread child-posting for the passed topic. - * - * @param string $parent_id - * @return string id of oldest unread posting - */ - public static function getLastUnread($parent_id) - { - $constraint = ForumEntry::getConstraints($parent_id); - - // take users visitdate into account - $visitdate = ForumVisit::getLastVisit($constraint['seminar_id']); - - // get the first unread entry - $stmt = DBManager::get()->prepare("SELECT * FROM forum_entries - WHERE lft > ? AND rgt < ? AND seminar_id = ? - AND mkdate >= ? - ORDER BY mkdate ASC LIMIT 1"); - $stmt->execute([$constraint['lft'], $constraint['rgt'], $constraint['seminar_id'], $visitdate]); - $last_unread = $stmt->fetch(PDO::FETCH_ASSOC); - - return $last_unread ? $last_unread['topic_id'] : null; - } - - /** - * retrieve the the latest posting under $parent_id - * or false if the postings itself is the latest - * - * @param string $parent_id the node to lookup the childs in - * @return mixed the data for the latest postings or false - */ - public static function getLatestPosting($parent_id) - { - $constraint = ForumEntry::getConstraints($parent_id); - - // get last entry - $stmt = DBManager::get()->prepare("SELECT * FROM forum_entries - WHERE lft > ? AND rgt < ? AND seminar_id = ? - ORDER BY mkdate DESC LIMIT 1"); - $stmt->execute([$constraint['lft'], $constraint['rgt'], $constraint['seminar_id']]); - - if (!$data = $stmt->fetch(PDO::FETCH_ASSOC)) { - return false; - } - - return $data; - } - - /** - * returns a hashmap with arrays containing id and name with the entries - * which lead to the passed topic - * - * @param string $topic_id the topic to get the path for - * - * @return array - */ - public static function getPathToPosting($topic_id) - { - $data = ForumEntry::getConstraints($topic_id); - $ret = []; - - $stmt = DBManager::get()->prepare("SELECT * FROM forum_entries - WHERE lft <= ? AND rgt >= ? AND seminar_id = ? ORDER BY lft ASC"); - $stmt->execute([$data['lft'], $data['rgt'], $data['seminar_id']]); - - while ($data = $stmt->fetch(PDO::FETCH_ASSOC)) { - $ret[$data['topic_id']] = $data; - $ret[$data['topic_id']]['id'] = $data['topic_id']; - } - - // set the name of the first entry to the name of the category the entry is in - if (sizeof($ret) > 1) { - reset($ret); - $tmp = array_slice($ret, 1, 1); - $area = array_pop($tmp); - $top = current($ret); - $ret[$top['id']]['name'] = ForumCat::getCategoryNameForArea($area['id']) ?: _('Allgemein'); - } - - return $ret; - } - - /** - * returns a hashmap where key is topic_id and value a posting-title from the - * entries which lead to the passed topic. - * - * WARNING: This function ommits postings with an empty title. For a full - * list please use ForumEntry::getPathToPosting()! - * - * @param string $topic_id the topic to get the path for - * - * @return array - */ - public static function getFlatPathToPosting($topic_id) - { - // use only the part of the path until the thread, no posting title - $postings = array_slice(self::getPathToPosting($topic_id), 0, 3); - - // var_dump($postings); - - foreach ($postings as $post) { - if ($post['name']) { - $ret[$post['id']] = $post['name']; - } - } - - return $ret; - } - - /** - * fill the passed postings with additional data - * - * @param array $postings - * @return array - */ - public static function parseEntries($postings) - { - $posting_list = []; - - // retrieve the postings - foreach ($postings as $data) { - // we throw away all formatting stuff, tags, etc, leaving the important bit of information - $desc_short = ForumEntry::br2space(ForumEntry::killFormat(strip_tags($data['content']))); - if (mb_strlen($desc_short) > (ForumEntry::THREAD_PREVIEW_LENGTH + 2)) { - $desc_short = mb_substr($desc_short, 0, ForumEntry::THREAD_PREVIEW_LENGTH) . '...'; - } else { - $desc_short = $desc_short; - } - - $posting_list[$data['topic_id']] = [ - 'author' => $data['author'], - 'topic_id' => $data['topic_id'], - 'name' => formatReady($data['name']), - 'name_raw' => $data['name'], - 'content' => ForumEntry::getContentAsHtml($data['content'], $data['anonymous']), - 'content_raw' => ForumEntry::killEdit($data['content']), - 'content_short' => $desc_short, - 'chdate' => $data['chdate'], - 'mkdate' => $data['mkdate'], - 'user_id' => $data['user_id'], - 'raw_title' => $data['name'], - 'raw_description' => ForumEntry::killEdit($data['content']), - 'fav' => ($data['fav'] == 'fav'), - 'depth' => $data['depth'], - 'anonymous' => $data['anonymous'], - 'closed' => $data['closed'], - 'sticky' => $data['sticky'], - 'seminar_id' => $data['seminar_id'] - ]; - } // retrieve the postings - - return $posting_list; - } - - /** - * Get all entries for the passed parent_id. - * Returns an array of the following structure: - * Array ( - * 'list' => Array ( - * 'author' => - * 'topic_id' => - * 'name' => formatReady() - * 'name_raw' => - * 'content' => formatReady() - * 'content_raw' => - * 'content_short' => - * 'chdate' => - * 'mkdate' => - * 'user_id' => - * 'raw_title' => - * 'raw_description' => - * 'fav' => - * 'depth' => - * 'sticky' => - * 'closed' => - * 'seminar_id' => - * ) - * 'count' => - * ) - * - * @param string $parent_id id of parent-element to get entries for. - * @param boolean $with_childs if true, the whole subtree is fetched - * @param string $add for additional constraints in the WHERE-part of the query - * @param string $sort_order can be ASC or DESC - * @param int $start can be used for pagination, is used for the LIMIT-part of the query - * @param int $limit number of entries to fetch, defaults to ForumEntry::POSTINGS_PER_PAGE - * - * @return array - * - * @throws Exception if the retrieval failed, an Exception is thrown - */ - public static function getEntries($parent_id, $with_childs = false, $add = '', - $sort_order = 'DESC', $start = 0, $limit = ForumEntry::POSTINGS_PER_PAGE) - { - $constraint = ForumEntry::getConstraints($parent_id); - $seminar_id = $constraint['seminar_id']; - $depth = $constraint['depth'] + 1; - - // count the entries and set correct page if necessary - if ($with_childs) { - $count_stmt = DBManager::get()->prepare("SELECT COUNT(*) FROM forum_entries - LEFT JOIN forum_favorites as ou ON (ou.topic_id = forum_entries.topic_id AND ou.user_id = ?) - WHERE (forum_entries.seminar_id = ? - AND forum_entries.seminar_id != forum_entries.topic_id - AND lft > ? AND rgt < ?) " - . ($depth > 2 ? " OR forum_entries.topic_id = ". DBManager::get()->quote($parent_id) : '') - . $add - . " ORDER BY forum_entries.mkdate $sort_order"); - $count_stmt->execute([$GLOBALS['user']->id, $seminar_id, $constraint['lft'], $constraint['rgt']]); - $count = $count_stmt->fetchColumn(); - } else { - $count_stmt = DBManager::get()->prepare("SELECT COUNT(*) FROM forum_entries - LEFT JOIN forum_favorites as ou ON (ou.topic_id = forum_entries.topic_id AND ou.user_id = ?) - WHERE ((depth = ? AND forum_entries.seminar_id = ? - AND forum_entries.seminar_id != forum_entries.topic_id - AND lft > ? AND rgt < ?) " - . ($depth > 2 ? " OR forum_entries.topic_id = ". DBManager::get()->quote($parent_id) : '') - . ') '. $add - . " ORDER BY forum_entries.mkdate $sort_order"); - $count_stmt->execute([$GLOBALS['user']->id, $depth, $seminar_id, $constraint['lft'], $constraint['rgt']]); - $count = $count_stmt->fetchColumn(); - } - - // use the last page if the requested page does not exist - if ($start > $count) { - $page = ceil($count / ForumEntry::POSTINGS_PER_PAGE); - ForumHelpers::setPage($page); - $start = max(1, $page - 1) * ForumEntry::POSTINGS_PER_PAGE; - } - - if ($with_childs) { - $stmt = DBManager::get()->prepare("SELECT forum_entries.*, IF(ou.topic_id IS NOT NULL, 'fav', NULL) as fav - FROM forum_entries - LEFT JOIN forum_favorites as ou ON (ou.topic_id = forum_entries.topic_id AND ou.user_id = ?) - WHERE (forum_entries.seminar_id = ? - AND forum_entries.seminar_id != forum_entries.topic_id - AND lft > ? AND rgt < ?) " - . ($depth > 2 ? " OR forum_entries.topic_id = ". DBManager::get()->quote($parent_id) : '') - . $add - . " ORDER BY forum_entries.mkdate $sort_order" - . ($limit ? " LIMIT $start, $limit" : '')); - $stmt->execute([$GLOBALS['user']->id, $seminar_id, $constraint['lft'], $constraint['rgt']]); - } else { - $stmt = DBManager::get()->prepare("SELECT forum_entries.*, IF(ou.topic_id IS NOT NULL, 'fav', NULL) as fav - FROM forum_entries - LEFT JOIN forum_favorites as ou ON (ou.topic_id = forum_entries.topic_id AND ou.user_id = ?) - WHERE ((depth = ? AND forum_entries.seminar_id = ? - AND lft > ? AND rgt < ?) " - . ($depth > 2 ? " OR forum_entries.topic_id = ". DBManager::get()->quote($parent_id) : '') - . ') '. $add - . " ORDER BY forum_entries.mkdate $sort_order" - . ($limit ? " LIMIT $start, $limit" : '')); - $stmt->execute([$GLOBALS['user']->id, $depth, $seminar_id, $constraint['lft'], $constraint['rgt']]); - } - - if (!$stmt) { - throw new Exception("Error while retrieving postings in " . __FILE__ . " on line " . __LINE__); - } - - return ['list' => ForumEntry::parseEntries($stmt->fetchAll(PDO::FETCH_ASSOC)), 'count' => $count]; - } - - - /** - * Takes a posting-array like the one generated by ForumEntry::getList() - * and adds the child-posting with the freshest creation-date to it. - * - * @param array $postings - * @return array - */ - public static function getLastPostings($postings) - { - foreach ($postings as $key => $posting) { - - if ($data = ForumEntry::getLatestPosting($posting['topic_id'])) { - $last_posting['topic_id'] = $data['topic_id']; - $last_posting['date'] = $data['mkdate']; - $last_posting['user_id'] = $data['user_id']; - $last_posting['user_fullname'] = $data['author']; - $last_posting['username'] = get_username($data['user_id']); - $last_posting['anonymous'] = $data['anonymous']; - - // we throw away all formatting stuff, tags, etc, so we have just the important bit of information - $text = strip_tags($data['name']); - $text = ForumEntry::br2space($text); - $text = ForumEntry::killFormat(ForumEntry::removeQuotes($text)); - - if (mb_strlen($text) > 42) { - $text = mb_substr($text, 0, 40) . '...'; - } - - $last_posting['text'] = $text; - } - - $postings[$key]['last_posting'] = $last_posting; - if (!$postings[$key]['last_unread'] = ForumEntry::getLastUnread($posting['topic_id'])) { - $postings[$key]['last_unread'] = $last_posting['topic_id']; - } - $postings[$key]['num_postings'] = ForumEntry::countEntries($posting['topic_id']); - - unset($last_posting); - } - - return $postings; - } - - /** - * get a list of postings of a special type - * - * @param string $type one of 'area', 'list', 'postings', 'latest', 'favorites', 'dump', 'flat' - * @param string $parent_id the are to fetch from - * @return array array('list' => ..., 'count' => ...); - */ - public static function getList($type, $parent_id) - { - $start = (ForumHelpers::getPage() - 1) * ForumEntry::POSTINGS_PER_PAGE; - - switch ($type) { - case 'area': - $list = ForumEntry::getEntries($parent_id, ForumEntry::WITHOUT_CHILDS, '', 'DESC', 0, 1000); - $postings = $list['list']; - - $postings = ForumEntry::getLastPostings($postings); - return ['list' => $postings, 'count' => $list['count']]; - - break; - - case 'list': - $constraint = ForumEntry::getConstraints($parent_id); - - // purpose of the following query is to retrieve the threads - // for an area ordered by the mkdate of their latest posting - $stmt = DBManager::get()->prepare("SELECT SQL_CALC_FOUND_ROWS - fe.*, IF(ou.topic_id IS NOT NULL, 'fav', NULL) as fav - FROM forum_entries AS fe - LEFT JOIN forum_favorites as ou ON (ou.topic_id = fe.topic_id AND ou.user_id = :user_id) - WHERE fe.seminar_id = :seminar_id AND fe.lft > :left - AND fe.rgt < :right AND fe.depth = 2 - ORDER BY sticky DESC, latest_chdate DESC - LIMIT $start, ". ForumEntry::POSTINGS_PER_PAGE); - $stmt->bindParam(':seminar_id', $constraint['seminar_id']); - $stmt->bindParam(':left', $constraint['lft'], PDO::PARAM_INT); - $stmt->bindParam(':right', $constraint['rgt'], PDO::PARAM_INT); - $stmt->bindParam(':user_id', $GLOBALS['user']->id); - $stmt->execute(); - - $postings = $stmt->fetchAll(PDO::FETCH_ASSOC); - $count = DBManager::get()->query("SELECT FOUND_ROWS()")->fetchColumn(); - $postings = ForumEntry::parseEntries($postings); - $postings = ForumEntry::getLastPostings($postings); - - return ['list' => $postings, 'count' => $count]; - break; - - case 'postings': - return ForumEntry::getEntries($parent_id, ForumEntry::WITH_CHILDS, '', 'ASC', $start); - break; - - case 'newest': - $constraint = ForumEntry::getConstraints($parent_id); - - // get postings - $stmt = DBManager::get()->prepare("SELECT forum_entries.*, IF(ou.topic_id IS NOT NULL, 'fav', NULL) as fav - FROM forum_entries - LEFT JOIN forum_favorites as ou ON (ou.topic_id = forum_entries.topic_id AND ou.user_id = :user_id) - WHERE seminar_id = :seminar_id AND lft > :left - AND rgt < :right AND (mkdate >= :mkdate OR chdate >= :mkdate) - ORDER BY mkdate ASC - LIMIT $start, ". ForumEntry::POSTINGS_PER_PAGE); - - $stmt->bindParam(':seminar_id', $constraint['seminar_id']); - $stmt->bindParam(':left', $constraint['lft']); - $stmt->bindParam(':right', $constraint['rgt']); - $stmt->bindParam(':mkdate', ForumVisit::getLastVisit($constraint['seminar_id'])); - $stmt->bindParam(':user_id', $GLOBALS['user']->id); - $stmt->execute(); - - $postings = $stmt->fetchAll(PDO::FETCH_ASSOC); - - $postings = ForumEntry::parseEntries($postings); - // var_dump($postings); - - // count found postings - $stmt_count = DBManager::get()->prepare("SELECT COUNT(*) - FROM forum_entries - WHERE seminar_id = :seminar_id AND lft > :left - AND rgt < :right AND mkdate >= :mkdate - ORDER BY mkdate ASC"); - - $stmt_count->bindParam(':seminar_id', $constraint['seminar_id']); - $stmt_count->bindParam(':left', $constraint['lft']); - $stmt_count->bindParam(':right', $constraint['rgt']); - $stmt_count->bindParam(':mkdate', ForumVisit::getLastVisit($constraint['seminar_id'])); - $stmt_count->execute(); - - - // return results - return ['list' => $postings, 'count' => $stmt_count->fetchColumn()]; - break; - - case 'latest': - return ForumEntry::getEntries($parent_id, ForumEntry::WITH_CHILDS, '', 'DESC', $start); - break; - - case 'favorites': - $add = "AND ou.topic_id IS NOT NULL"; - return ForumEntry::getEntries($parent_id, ForumEntry::WITH_CHILDS, $add, 'DESC', $start); - break; - - case 'dump': - $constraint = ForumEntry::getConstraints($parent_id); - $seminar_id = $constraint['seminar_id']; - $depth = $constraint['depth'] + 1; - - $stmt = DBManager::get()->prepare("SELECT * FROM forum_entries - WHERE (forum_entries.seminar_id = ? - AND forum_entries.seminar_id != forum_entries.topic_id - AND lft > ? AND rgt < ?) " - . ($depth > 2 ? " OR forum_entries.topic_id = ". DBManager::get()->quote($parent_id) : '') - . " ORDER BY forum_entries.lft ASC"); - $stmt->execute([$seminar_id, $constraint['lft'], $constraint['rgt']]); - - return ForumEntry::parseEntries($stmt->fetchAll(PDO::FETCH_ASSOC)); - break; - - case 'flat': - $constraint = ForumEntry::getConstraints($parent_id); - - $stmt = DBManager::get()->prepare("SELECT SQL_CALC_FOUND_ROWS * FROM forum_entries - WHERE lft > ? AND rgt < ? AND seminar_id = ? AND depth = ? - ORDER BY name ASC"); - $stmt->execute([$constraint['lft'], $constraint['rgt'], $constraint['seminar_id'], $constraint['depth'] + 1]); - - $count = DBManager::get()->query("SELECT FOUND_ROWS()")->fetchColumn(); - - $posting_list = []; - - // speed up things a bit by leaving out the formatReady fields - foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $data) { - // we throw away all formatting stuff, tags, etc, leaving the important bit of information - $desc_short = ForumEntry::br2space(ForumEntry::killFormat(strip_tags($data['content']))); - if (mb_strlen($desc_short) > (ForumEntry::THREAD_PREVIEW_LENGTH + 2)) { - $desc_short = mb_substr($desc_short, 0, ForumEntry::THREAD_PREVIEW_LENGTH) . '...'; - } else { - $desc_short = $desc_short; - } - $posting_list[$data['topic_id']] = [ - 'author' => $data['author'], - 'topic_id' => $data['topic_id'], - 'name_raw' => $data['name'], - 'content_raw' => ForumEntry::killEdit($data['content']), - 'content_short' => $desc_short, - 'chdate' => $data['chdate'], - 'mkdate' => $data['mkdate'], - 'user_id' => $data['user_id'], - 'raw_title' => $data['name'], - 'raw_description' => ForumEntry::killEdit($data['content']), - 'fav' => ($data['fav'] == 'fav'), - 'depth' => $data['depth'], - 'seminar_id' => $data['seminar_id'] - ]; - } - - return ['list' => $posting_list, 'count' => $count]; - break; - - case 'depth_to_large': - $constraint = ForumEntry::getConstraints($parent_id); - - $stmt = DBManager::get()->prepare("SELECT SQL_CALC_FOUND_ROWS * FROM forum_entries - WHERE lft > ? AND rgt < ? AND seminar_id = ? AND depth > 3 - ORDER BY name ASC"); - $stmt->execute([$constraint['lft'], $constraint['rgt'], $constraint['seminar_id']]); - - $count = DBManager::get()->query("SELECT FOUND_ROWS()")->fetchColumn(); - - return ['list' => $stmt->fetchAll(PDO::FETCH_ASSOC), 'count' => $count]; - break; - } - } - - /** - * Get the latest forum entries for the passed entries childs - * - * @param string $parent_id - * @param int $start_date timestamp - * @param int $end_date timestamp - * - * @return array list of postings - */ - public static function getLatestSince($parent_id, $start_date, $end_date) - { - $constraint = ForumEntry::getConstraints($parent_id); - - $stmt = DBManager::get()->prepare("SELECT SQL_CALC_FOUND_ROWS * FROM forum_entries - WHERE lft > ? AND rgt < ? AND seminar_id = ? - AND mkdate BETWEEN ? AND ? - ORDER BY name ASC"); - $stmt->execute([$constraint['lft'], $constraint['rgt'], $constraint['seminar_id'], $start_date, $end_date]); - - return $stmt->fetchAll(PDO::FETCH_ASSOC); - } - - /** - ** returns a list of postings for the passed search-term - * - * @param string $parent_id the area to search in (can be a whole seminar) - * @param string $_searchfor the term to search for - * @param array $options filter-options: search_title, search_content, search_author - * @return array array('list' => ..., 'count' => ...); - */ - public static function getSearchResults($parent_id, $_searchfor, $options) - { - $start = (ForumHelpers::getPage() - 1) * ForumEntry::POSTINGS_PER_PAGE; - - // if there are quoted parts, they should not be separated - $suchmuster = '/".*"/U'; - preg_match_all($suchmuster, $_searchfor, $treffer); - array_walk($treffer[0], function(&$value) { $value = trim($value, '"'); }); - - // remove the quoted parts from $_searchfor - $_searchfor = trim(preg_replace($suchmuster, '', $_searchfor)); - - // split the searchstring $_searchfor at every space - $parts = explode(' ', $_searchfor); - - foreach ($parts as $key => $val) { - if ($val == '') { - unset($parts[$key]); - } - } - - if (!empty($parts)) { - $_searchfor = array_merge($parts, $treffer[0]); - } else { - $_searchfor = $treffer[0]; - } - - // make an SQL-statement out of the searchstring - $search_string = []; - foreach ($_searchfor as $key => $val) { - if (!$val) { - unset($_searchfor[$key]); - } else { - $search_word = '%'. $val .'%'; - $zw_search_string = []; - if ($options['search_title']) { - $zw_search_string[] .= "name LIKE " . DBManager::get()->quote($search_word); - } - - if ($options['search_content']) { - $zw_search_string[] .= "content LIKE " . DBManager::get()->quote($search_word); - } - - if ($options['search_author']) { - $zw_search_string[] .= "author LIKE " . DBManager::get()->quote($search_word); - } - - if (!empty($zw_search_string)) { - $search_string[] = '(' . implode(' OR ', $zw_search_string) . ')'; - } - } - } - - if (!empty($search_string)) { - $add = "AND (" . implode(' AND ', $search_string) . ")"; - return array_merge( - ['highlight' => $_searchfor], - ForumEntry::getEntries($parent_id, ForumEntry::WITH_CHILDS, $add, 'DESC', $start) - ); - } - - return ['num_postings' => 0, 'list' => []]; - } - - /** - * returns the entry for the passed topic_id - * - * @param string $topic_id - * @return array hash-array with the entries fields - */ - public static function getEntry($topic_id) - { - return ForumEntry::getConstraints($topic_id); - } - - /** - * Count the number of child-elements that the passed entry has and return it. - * - * @param string $parent_id - * - * @return int the number of child entries for the passed entry - */ - public static function countEntries($parent_id) - { - $data = ForumEntry::getConstraints($parent_id); - return max((($data['rgt'] - $data['lft'] - 1) / 2) + 1, 0); - } - - /** - * Count the number of postings in a given course and return it. - * - * @param string $course_id the id of the given course - * - * @return int the number of postings in the course - */ - public static function countPostings($course_id) - { - $stmt = DBManager::get()->prepare("SELECT COUNT(*) FROM forum_entries - WHERE seminar_id = ? AND depth >= 2"); - $stmt->execute([$course_id]); - - return $stmt->fetchColumn(0); - } - - /** - * Count all entries the passed user has ever written and return the result - * - * @staticvar type $entries - * - * @param string $user_id - * - * @return int number of entries user has ever written - */ - public static function countUserEntries($user_id, $seminar_id = null) - { - static $entries; - - if (!$entries[$user_id]) { - $stmt = DBManager::get()->prepare("SELECT COUNT(*) - FROM forum_entries - WHERE user_id = ? AND seminar_id = IFNULL(?, seminar_id)"); - $stmt->execute([$user_id, $seminar_id]); - - $entries[$user_id] = $stmt->fetchColumn(); - } - - return $entries[$user_id]; - } - - /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * - * D A T A - C R E A T I O N * - * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ - - /** - * insert a node into the table - * - * @param array $data an array containing the following fields: - * topic_id the id of the new topic - * seminar_id the id of the seminar to add the topic to - * user_id the id of the user who created the topic - * name the title of the entry - * content the content of the entry - * author the author's name as a plaintext string - * author_host ip-address of creator - * @param string $parent_id the node to add the topic to - * - * @return void - */ - public static function insert($data, $parent_id) - { - $constraint = ForumEntry::getConstraints($parent_id); - - // #TODO: Zusammenfassen in eine Transaktion!!! - DBManager::get()->exec('UPDATE forum_entries SET lft = lft + 2 - WHERE lft > '. $constraint['rgt'] ." AND seminar_id = '". $constraint['seminar_id'] ."'"); - DBManager::get()->exec('UPDATE forum_entries SET rgt = rgt + 2 - WHERE rgt >= '. $constraint['rgt'] ." AND seminar_id = '". $constraint['seminar_id'] ."'"); - - $stmt = DBManager::get()->prepare("INSERT INTO forum_entries - (topic_id, seminar_id, user_id, name, content, mkdate, latest_chdate, - chdate, author, author_host, lft, rgt, depth, anonymous) - VALUES (? ,?, ?, ?, ?, UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), ?, ?, ?, ?, ?, ?)"); - $stmt->execute([$data['topic_id'], $data['seminar_id'], $data['user_id'], - $data['name'], transformBeforeSave($data['content']), $data['author'], $data['author_host'], - $constraint['rgt'], $constraint['rgt'] + 1, $constraint['depth'] + 1, $data['anonymous'] ? : 0]); - - // update "latest_chdate" for easier sorting of actual threads - DBManager::get()->exec("UPDATE forum_entries SET latest_chdate = UNIX_TIMESTAMP() - WHERE topic_id = '" . $constraint['topic_id'] . "'"); - - NotificationCenter::postNotification('ForumAfterInsert', $data['topic_id'], $data); - } - - - /** - * update the passed topic - * - * @param string $topic_id the id of the topic to update - * @param string $name the new name - * @param string $content the new content - * - * @return void - */ - public static function update($topic_id, $name, $content) - { - $post = ForumEntry::getConstraints($topic_id); - - if (time() - $post['mkdate'] > 5 * 60) { - $content = ForumEntry::appendEdit($content); - } - - $stmt = DBManager::get()->prepare("UPDATE forum_entries - SET name = ?, content = ?, chdate = UNIX_TIMESTAMP(), latest_chdate = UNIX_TIMESTAMP() - WHERE topic_id = ?"); - $stmt->execute([$name, transformBeforeSave($content), $topic_id]); - - // update "latest_chdate" for easier sorting of actual threads - $parent_id = ForumEntry::getParentTopicId($topic_id); - DBManager::get()->exec("UPDATE forum_entries SET latest_chdate = UNIX_TIMESTAMP() - WHERE topic_id = '" . $parent_id . "'"); - - $post['name'] = $name; - $post['content'] = $content; - - NotificationCenter::postNotification('ForumAfterUpdate', $topic_id, $post); - } - - /** - * delete an entry and all his descendants from the mptt-table - * - * @param string $topic_id the id of the entry to delete - * - * @return void - */ - public static function delete($topic_id) - { - $post = ForumEntry::getConstraints($topic_id); - $parent = ForumEntry::getConstraints(ForumEntry::getParentTopicId($topic_id)); - - NotificationCenter::postNotification('ForumBeforeDelete', $topic_id, $post); - - // #TODO: Zusammenfassen in eine Transaktion!!! - // get all entry-ids to delete them from the category-reference-table - $stmt = DBManager::get()->prepare("SELECT topic_id FROM forum_entries - WHERE seminar_id = ? AND lft >= ? AND rgt <= ? AND depth = 1"); - $stmt->execute([$post['seminar_id'], $post['lft'], $post['rgt']]); - $ids = $stmt->fetchAll(PDO::FETCH_COLUMN); - - if ($ids != false && !is_array($ids)) $ids = [$ids]; - - if (!empty($ids)) { - $stmt = DBManager::get()->prepare("DELETE FROM forum_categories_entries - WHERE topic_id IN (:ids)"); - $stmt->bindParam(':ids', $ids, StudipPDO::PARAM_ARRAY); - $stmt->execute(); - } - - // delete all entries - $stmt = DBManager::get()->prepare("DELETE FROM forum_entries - WHERE seminar_id = ? AND lft >= ? AND rgt <= ?"); - - $stmt->execute([$post['seminar_id'], $post['lft'], $post['rgt']]); - - // update lft and rgt - $diff = $post['rgt'] - $post['lft'] + 1; - $stmt = DBManager::get()->prepare("UPDATE forum_entries SET lft = lft - $diff - WHERE lft > ? AND seminar_id = ?"); - $stmt->execute([$post['rgt'], $post['seminar_id']]); - - $stmt = DBManager::get()->prepare("UPDATE forum_entries SET rgt = rgt - $diff - WHERE rgt > ? AND seminar_id = ?"); - $stmt->execute([$post['rgt'], $post['seminar_id']]); - - - // set the latest_chdate to the latest child's chdate - $stmt = DBManager::get()->prepare("SELECT chdate FROM forum_entries - WHERE lft > ? AND rgt < ? AND seminar_id = ? - ORDER BY chdate DESC LIMIT 1"); - $stmt->execute([$parent['lft'], $parent['rgt'], $parent['seminar_id']]); - $chdate = $stmt->fetchColumn(); - - $stmt_insert = DBManager::get()->prepare("UPDATE forum_entries - SET chdate = ? WHERE topic_id = ?"); - if ($chdate) { - $stmt_insert->execute([$chdate, $parent['topic_id']]); - } else { - $stmt_insert->execute([$parent['chdate'], $parent['topic_id']]); - } - } - - /** - * move the passed topic to the passed area - * - * @param string $topic_id the topic to move - * @param string $destination the area_id where the topic is moved to - * - * @return void - */ - public static function move($topic_id, $destination) - { - // #TODO: Zusammenfassen in eine Transaktion!!! - $constraints = ForumEntry::getConstraints($topic_id); - - // move the affected entries "outside" the tree - $stmt = DBManager::get()->prepare("UPDATE forum_entries - SET lft = lft * -1, rgt = rgt * -1 - WHERE seminar_id = ? AND lft >= ? AND rgt <= ?"); - $stmt->execute([$constraints['seminar_id'], $constraints['lft'], $constraints['rgt']]); - - // update the lft and rgt values of the parent to reflect the "deletion" - $diff = $constraints['rgt'] - $constraints['lft'] + 1; - $stmt = DBManager::get()->prepare("UPDATE forum_entries SET lft = lft - ? - WHERE lft > ? AND seminar_id = ?"); - $stmt->execute([$diff, $constraints['rgt'], $constraints['seminar_id']]); - - $stmt = DBManager::get()->prepare("UPDATE forum_entries SET rgt = rgt - ? - WHERE rgt > ? AND seminar_id = ?"); - $stmt->execute([$diff, $constraints['rgt'], $constraints['seminar_id']]); - - // make some space by updating the lft and rgt values of the target node - $constraints_destination = ForumEntry::getConstraints($destination); - $size = $constraints['rgt'] - $constraints['lft'] + 1; - - $stmt = DBManager::get()->prepare("UPDATE forum_entries SET lft = lft + ? - WHERE lft > ? AND seminar_id = ?"); - $stmt->execute([$size, $constraints_destination['rgt'], $constraints_destination['seminar_id']]); - - $stmt = DBManager::get()->prepare("UPDATE forum_entries SET rgt = rgt + ? - WHERE rgt >= ? AND seminar_id = ?"); - $stmt->execute([$size, $constraints_destination['rgt'], $constraints_destination['seminar_id']]); - - //move the entries from "outside" the tree to the target node - $constraints_destination = ForumEntry::getConstraints($destination); - - - // update the depth to reflect the new position in the tree - // determine if we need to add, subtract or even do nothing to/from the depth - $depth_mod = $constraints_destination['depth'] - $constraints['depth'] + 1; - - $stmt = DBManager::get()->prepare("UPDATE forum_entries - SET depth = depth + ? - WHERE seminar_id = ? AND lft < 0"); - $stmt->execute([$depth_mod, $constraints_destination['seminar_id']]); - - // if the depth is larger than 3, fix it - $stmt = DBManager::get()->prepare("UPDATE forum_entries - SET depth = 3 - WHERE seminar_id = ? AND depth > 3 AND lft < 0"); - $stmt->execute([$constraints_destination['seminar_id']]); - - // move the tree to its destination - $diff = ($constraints_destination['rgt'] - ($constraints['rgt'] - $constraints['lft'])) - 1 - $constraints['lft']; - - $stmt = DBManager::get()->prepare("UPDATE forum_entries - SET lft = (lft * -1) + ?, rgt = (rgt * -1) + ? - WHERE seminar_id = ? AND lft < 0"); - $stmt->execute([$diff, $diff, $constraints_destination['seminar_id']]); - - if ($depth_mod != 0) { - self::fix_ordering($topic_id); - } - } - - private static function fix_ordering($parent_id) - { - $db = DBManager::get(); - - $entry = ForumEntry::getConstraints($parent_id); - - $stmt= $db->prepare('SELECT topic_id FROM forum_entries - WHERE lft > ? AND rgt < ? AND depth = 3 - AND seminar_id = ? - ORDER BY mkdate'); - - $stmt->execute([$entry['lft'], $entry['rgt'], $entry['seminar_id']]); - - $lft = $entry['lft'] + 1; - $rgt = $lft + 1; - - $inner_stmt = $db->prepare("UPDATE forum_entries SET lft=?, rgt=? - WHERE topic_id = ?"); - while ($topic_id = $stmt->fetchColumn()) { - $inner_stmt->execute([$lft, $rgt, $topic_id]); - - $lft += 2; - $rgt += 2; - } - } - - /** - * close the passed topic - * - * @param string $topic_id the topic to close - * - * @return void - */ - public static function close($topic_id) - { - // close all entries belonging to the topic - $stmt = DBManager::get()->prepare("UPDATE forum_entries - SET closed = 1 - WHERE topic_id = ?"); - $stmt->execute([$topic_id]); - } - - /** - * open the passed topic - * - * @param string $topic_id the topic to open - * - * @return void - */ - public static function open($topic_id) - { - // open all entries belonging to the topic - $stmt = DBManager::get()->prepare("UPDATE forum_entries - SET closed = 0 - WHERE topic_id = ?"); - $stmt->execute([$topic_id]); - } - - /** - * make the passed topic sticky - * - * @param string $topic_id the topic to make sticky - * - * @return void - */ - public static function sticky($topic_id) - { - // open all entries belonging to the topic - $stmt = DBManager::get()->prepare("UPDATE forum_entries - SET sticky = 1 - WHERE topic_id = ?"); - $stmt->execute([$topic_id]); - } - - /** - * make the passed topic unsticky - * - * @param string $topic_id the topic to make unsticky - * - * @return void - */ - public static function unsticky($topic_id) - { - // open all entries belonging to the topic - $stmt = DBManager::get()->prepare("UPDATE forum_entries - SET sticky = 0 - WHERE topic_id = ?"); - $stmt->execute([$topic_id]); - } - - /** - * check, if the default root-node for this seminar exists and make sure - * the default category exists as well - * - * @param string $seminar_id - * - * @return void - */ - public static function checkRootEntry($seminar_id) - { - setTempLanguage(); - - // check, if the root entry in the topic tree exists - $stmt = DBManager::get()->prepare("SELECT COUNT(*) FROM forum_entries - WHERE topic_id = ? AND seminar_id = ?"); - $stmt->execute([$seminar_id, $seminar_id]); - if ($stmt->fetchColumn() == 0) { - $stmt = DBManager::get()->prepare("INSERT INTO forum_entries - (topic_id, seminar_id, name, mkdate, chdate, lft, rgt, depth) - VALUES (?, ?, 'Übersicht', UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), 0, 1, 0)"); - $stmt->execute([$seminar_id, $seminar_id]); - } - - - // make sure, that the category "Allgemein" exists - $stmt = DBManager::get()->prepare("INSERT IGNORE INTO forum_categories - (category_id, seminar_id, entry_name) VALUES (?, ?, ?)"); - $stmt->execute([$seminar_id, $seminar_id, _('Allgemein')]); - - // make sure that the default area "Allgemeine Diskussionen" exists, if there is nothing else present - $stmt = DBManager::get()->prepare("SELECT COUNT(*) FROM forum_entries - WHERE seminar_id = ? AND depth = 1"); - $stmt->execute([$seminar_id]); - - // add default area - if ($stmt->fetchColumn() == 0) { - $data = [ - 'topic_id' => md5(uniqid()), - 'seminar_id' => $seminar_id, - 'user_id' => '', - 'name' => _('Allgemeine Diskussion'), - 'content' => _('Hier ist Raum für allgemeine Diskussionen'), - 'author' => '', - 'author_host' => '' - ]; - ForumEntry::insert($data, $seminar_id); - } - - restoreLanguage(); - } - - /** - * returns the ten most active seminars - * - * @return array - */ - public static function getTopTenSeminars() - { - return DBManager::get()->query("SELECT a.seminar_id, b.name AS display, - count( a.seminar_id ) AS count FROM forum_entries a - INNER JOIN seminare b USING ( seminar_id ) - WHERE b.visible = 1 - AND a.mkdate > UNIX_TIMESTAMP( NOW( ) - INTERVAL 2 WEEK ) - GROUP BY a.seminar_id - ORDER BY count DESC - LIMIT 10")->fetchAll(PDO::FETCH_ASSOC); - } - - /** - * count all entries that exists in the whole installation and return it. - * - * @return int - */ - public static function countAllEntries() - { - return count_table_rows('forum_entries'); - } - - /** - * updates the user-entries and replaces the old user-id by the new one - * - * @param string $user_from - * @param string $user_to - */ - public static function migrateUser($user_from, $user_to) - { - $stmt = DBManager::get()->prepare("UPDATE forum_entries SET user_id = ? WHERE user_id = ?"); - $stmt->execute([$user_to, $user_from]); - - $stmt = DBManager::get()->prepare("UPDATE IGNORE forum_favorites SET user_id = ? WHERE user_id = ?"); - $stmt->execute([$user_to, $user_from]); - - $stmt = DBManager::get()->prepare("UPDATE IGNORE forum_visits SET user_id = ? WHERE user_id = ?"); - $stmt->execute([$user_to, $user_from]); - - $stmt = DBManager::get()->prepare("UPDATE IGNORE forum_likes SET user_id = ? WHERE user_id = ?"); - $stmt->execute([$user_to, $user_from]); - - $stmt = DBManager::get()->prepare("UPDATE IGNORE forum_abo_users SET user_id = ? WHERE user_id = ?"); - $stmt->execute([$user_to, $user_from]); - } - - /** - * returns the complete seminar or only the passed sub-tree as a html-string - * - * @param string $seminar_id - * - * @return string - */ - public static function getDump($seminar_id, $parent_id = null) - { - $seminar_name = get_object_name($seminar_id, 'sem'); - $content = '

'. _('Forum') .': ' . $seminar_name['name'] .'

'; - $data = ForumEntry::getList('dump', $parent_id ?: $seminar_id); - - foreach ($data as $entry) { - if ($entry['depth'] == 1) { - $content .= '

'. _('Bereich') .': '. $entry['name'] .'

'; - $content .= $entry['content'] .'

'; - } else if ($entry['depth'] == 2) { - $content .= '

'. _('Thema') .': '. $entry['name'] .'

'; - $content .= '' . sprintf(_('erstellt von %s am %s'), htmlReady($entry['author']), - strftime('%A %d. %B %Y, %H:%M', (int)$entry['mkdate'])) . '
'; - $content .= $entry['content'] .'

'; - } else if ($entry['depth'] == 3) { - $content .= ''.$entry['name'] .'
'; - $content .= '' . sprintf(_('erstellt von %s am %s'), htmlReady($entry['author']), - strftime('%A %d. %B %Y, %H:%M', (int)$entry['mkdate'])) . '
'; - $content .= $entry['content'] .'

'; - } - } - - return $content; - } - - public static function isClosed($topic_id) - { - foreach(ForumEntry::getPathToPosting($topic_id) as $entry) { - if ($entry['closed']) { - return true; - } - } - - return false; - } - - /** - * Export available data of a given user into a storage object - * (an instance of the StoredUserData class) for that user. - * - * @param StoredUserData $storage object to store data into - */ - public static function exportUserData(StoredUserData $storage) - { - $field_data = DBManager::get()->fetchAll("SELECT * FROM forum_entries WHERE user_id = ?", [$storage->user_id]); - if ($field_data) { - $storage->addTabularData(_('Forum Einträge'), 'forum_entries', $field_data); - } - } - -} diff --git a/public/plugins_packages/core/Forum/models/ForumFavorite.php b/public/plugins_packages/core/Forum/models/ForumFavorite.php deleted file mode 100644 index 9ffebf5..0000000 --- a/public/plugins_packages/core/Forum/models/ForumFavorite.php +++ /dev/null @@ -1,41 +0,0 @@ - - * @license http://www.gnu.org/licenses/gpl-3.0.html GPL version 3 - * @category Stud.IP - */ - -class ForumFavorite { - - /** - * Set the topic denoted by the passed id as favorite for the - * currently logged in user - * - * @param string $topic_id - */ - static function set($topic_id) { - $stmt = DBManager::get()->prepare("REPLACE INTO - forum_favorites (topic_id, user_id) - VALUES (?, ?)"); - $stmt->execute([$topic_id, $GLOBALS['user']->id]); - } - - /** - * Remove the topic denoted by the passed id as favorite for the - * currently logged in user - * - * @param string $topic_id - */ - static function remove($topic_id) { - $stmt = DBManager::get()->prepare("DELETE FROM forum_favorites - WHERE topic_id = ? AND user_id = ?"); - $stmt->execute([$topic_id, $GLOBALS['user']->id]); - } -} \ No newline at end of file diff --git a/public/plugins_packages/core/Forum/models/ForumHelpers.php b/public/plugins_packages/core/Forum/models/ForumHelpers.php deleted file mode 100644 index ed478ad..0000000 --- a/public/plugins_packages/core/Forum/models/ForumHelpers.php +++ /dev/null @@ -1,282 +0,0 @@ - - * @license http://www.gnu.org/licenses/gpl-3.0.html GPL version 3 - * @category Stud.IP - */ - -class ForumHelpers { - - /** - * The page for the current script run, modified by a global page-handle - * @var int - */ - static $page = 1; - - /** - * helper_function for highlight($text, $highlight) - * - * @param string $text - * @param array $highlight - * @return string - */ - public static function do_highlight($text, $highlight) - { - foreach ($highlight as $hl) { - $text = preg_replace( - '/' . preg_quote(htmlReady($hl), '/') . '/i', - '$0', - $text - ); - } - return $text; - } - - /** - * This function highlights Text HTML-safe - * (tags or words in tags are not highlighted, words between tags ARE highlighted) - * - * @param string $text the text where to words shall be highlighted, may contain tags - * @param array $highlight an array of words to be highlighted - * @return string the highlighted text - */ - public static function highlight($text, $highlight) - { - if (empty($highlight)) { - return $text; - } - - $data = []; - $treffer = []; - - // split text at every tag - $pattern = '/<[^<]*>/U'; - preg_match_all($pattern, $text, $treffer, PREG_OFFSET_CAPTURE); - - if (sizeof($treffer[0]) == 0) { - return self::do_highlight($text, $highlight); - } - - // cycle trough the text between the tags and highlight all hits - $last_pos = 0; - foreach ($treffer[0] as $taginfo) { - $size = mb_strlen($taginfo[0]); - if ($taginfo[1] != 0) { - $data[] = self::do_highlight(mb_substr($text, $last_pos, $taginfo[1] - $last_pos), $highlight); - } - - $data[] = mb_substr($text, $taginfo[1], $size); - $last_pos = $taginfo[1] + $size; - } - - // don't miss the last portion of a posting - if ($last_pos < mb_strlen($text)) { - $data[] = self::do_highlight(mb_substr($text, $last_pos, mb_strlen($text) - $last_pos), $highlight); - } - - return implode('', $data); - } - - /** - * Returns a human-readable version of the passed global Stud.IP permission. - * - * @param string $perm - * @return string - */ - public static function translate_perm($perm) - { - $mapping = [ - 'root' => _('Root'), - 'admin' => _('Administrator/-in'), - 'dozent' => _('Lehrende/-r'), - 'tutor' => _('Tutor/-in'), - 'autor' => _('Autor/-in'), - 'user' => _('Leser/-in'), - ]; - - // TODO: Activate next when devboard reliably runs on PHP7 - // return $mapping[$perm] ?? ''; - - return isset($mapping[$perm]) ? $mapping[$perm] : ''; - } - - /** - * return the currently chosen page - * - * @return int - */ - public static function getPage() - { - return self::$page; - } - - /** - * set the current page - * - * @param int $page_num the page - */ - public static function setPage($page_num) - { - self::$page = $page_num; - } - - /** - * Return an info-text explaining the visit-status of the passed topic_di - * which has the passed number of new entries. - * - * @param string $num_entries the number of new entries - * @param string $topic_id the id of the topic - * - * @return string a human readable, localized text - */ - public static function getVisitText($num_entries, $topic_id) - { - if ($num_entries > 0) { - $text = sprintf(_('Seit Ihrem letzten Besuch gibt es %s neue Beiträge'), $num_entries); - } else { - $all_entries = ForumEntry::countPostings($topic_id); - - if ($all_entries == 0) { - $text = sprintf(_('Es gibt bisher keine Beiträge.')); - } else if ($all_entries == 1) { - $text = sprintf(_('Seit Ihrem letzten Besuch gab es nichts Neues.' - . ' Es ist ein alter Beitrag vorhanden.')); - } else { - $text = sprintf(_('Seit Ihrem letzten Besuch gab es nichts Neues.' - . ' Es sind %s alte Beiträge vorhanden.'), $all_entries); - } - } - - return $text; - } - - /** - * return the online status of the passed user, one of three possible - * states is returned: - * - available - * - away - * - offline - * - * @staticvar type $online_status - * - * @param string $user_id - * - * @return string - */ - public static function getOnlineStatus($user_id) - { - static $online_status; - - // check if the corresponding user's profile is visible - if (get_visibility_by_id($user_id) == false) { - return 'offline'; - } - - if ($GLOBALS['user']->id == $user_id) { - return 'available'; - } - - if (!$online_status) { - $online_users = get_users_online(10); - foreach ($online_users as $username => $data) { - if ($data['last_action'] >= 300) { - $online_status[$data['user_id']] = 'away'; - } else { - $online_status[$data['user_id']] = 'available'; - } - } - } - - return $online_status[$user_id] ?: 'offline'; - } - - /** - * Create a pdf of all postings belonging to the passed seminar located - * under the passed topic_id. The PDF is dispatched automatically. - * - * BEWARE: This function never returns, it dies after the PDF has been - * (succesfully or not) dispatched. - * - * @param string $seminar_id - * @param string $parent_id - */ - public static function createPdf($seminar_id, $parent_id = null) - { - $seminar_name = get_object_name($seminar_id, 'sem'); - $data = ForumEntry::getList('dump', $parent_id ?: $seminar_id); - $first_page = true; - - $document = new ExportPDF(); - $document->SetTitle(_('Forum')); - $document->setHeaderTitle(sprintf(_("Forum \"%s\""), $seminar_name['name'])); - $document->addPage(); - - foreach ($data as $entry) { - if (Config::get()->FORUM_ANONYMOUS_POSTINGS && $entry['anonymous']) { - $author = _('anonym'); - } else { - $author = $entry['author']; - } - if ($entry['depth'] == 1) { - if (!$first_page) { - $document->addPage(); - } - $first_page = false; - $document->addContent('!! '. _('Bereich') . ": {$entry['name_raw']}\n"); - $document->addContent($entry['content_raw']); - $document->addContent("\n\n"); - } else if ($entry['depth'] == 2) { - $document->addContent('! '. _('Thema') . ": {$entry['name_raw']}\n"); - $document->addContent('%%' . sprintf(_('erstellt von %s am %s'), $author, - strftime('%A %d. %B %Y, %H:%M', (int)$entry['mkdate'])) . '%%' . "\n"); - $document->addContent($entry['content_raw']); - $document->addContent("\n--\n"); - } else if ($entry['depth'] == 3) { - $document->addContent("**{$entry['name_raw']}**\n"); - $document->addContent('%%' . sprintf(_('erstellt von %s am %s'), $author, - strftime('%A %d. %B %Y, %H:%M', (int)$entry['mkdate'])) . '%%' . "\n"); - $document->addContent($entry['content_raw']); - $document->addContent("\n--\n"); - } - } - - $document->dispatch($seminar_name['name'] ." - Forum"); - die; - } - - - /** - * Returns the id of the currently selected seminar or false, if no seminar - * is selected - * - * @return mixed seminar_id or false - */ - public static function getSeminarId() - { - return Context::getId(); - } - - /** - * replace in the passed text every %%% with <% and every ### with %> - * This is used to work around a limitation of the Button-API in combination - * with the underscore.js way of inserting template vars. - * - * The Button-API correctly replaces < > with tags, but underscore.js is - * unable to find them in their tag-represenation - * - * @param string $text the text to apply the replacements on - * - * @return string the modified text - */ - public static function replace($text) - { - return str_replace('%%%', '<%', str_replace('###', '%>', $text)); - } -} diff --git a/public/plugins_packages/core/Forum/models/ForumIssue.php b/public/plugins_packages/core/Forum/models/ForumIssue.php deleted file mode 100644 index 7f98c7d..0000000 --- a/public/plugins_packages/core/Forum/models/ForumIssue.php +++ /dev/null @@ -1,98 +0,0 @@ - - * @license http://www.gnu.org/licenses/gpl-3.0.html GPL version 3 - * @category Stud.IP - */ - -NotificationCenter::addObserver('ForumIssue', 'unlinkIssue', 'ForumBeforeDelete'); - -class ForumIssue -{ - /** - * Get the id of the topic linked to the issue denoted by the passed id. - * - * @param string $issue_id - * @return string the id of the linked topic - */ - static function getThreadIdForIssue($issue_id) - { - $stmt = DBManager::get()->prepare("SELECT topic_id FROM forum_entries_issues - WHERE issue_id = ?"); - $stmt->execute([$issue_id]); - - return ($stmt->fetchColumn()); - } - - - /** - * Get the id of the issue linked to the topic denoted by the passed id. - * - * @param string $topic_id - * @return string the id of the linked topic - */ - static function getIssueIdForThread($topic_id) - { - $stmt = DBManager::get()->prepare("SELECT issue_id FROM forum_entries_issues - WHERE topic_id = ?"); - $stmt->execute([$topic_id]); - - return ($stmt->fetchColumn()); - } - - - /** - * Create/Update the linked posting for the passed issue_id - * - * @param string $seminar_id - * @param string $issue_id issue id to link to - * @param string $title (new) title of the posting - * @param string $content (new) content of the posting - */ - static function setThreadForIssue($seminar_id, $issue_id, $title, $content) - { - if ($topic_id = self::getThreadIdForIssue($issue_id)) { // update - ForumEntry::update($topic_id, $title ?: _('Ohne Titel'), $content); - - } else { // create - // make sure the forum is set up properly - ForumEntry::checkRootEntry($seminar_id); - - $topic_id = md5(uniqid(rand())); - - ForumEntry::insert([ - 'topic_id' => $topic_id, - 'seminar_id' => $seminar_id, - 'user_id' => $GLOBALS['user']->id, - 'name' => $title ?: _('Ohne Titel'), - 'content' => $content, - 'author' => get_fullname($GLOBALS['user']->id), - 'author_host' => ($GLOBALS['user']->id == 'nobody') ? getenv('REMOTE_ADDR') : '' - ], $seminar_id); - - $stmt = DBManager::get()->prepare("INSERT INTO forum_entries_issues - (issue_id, topic_id) VALUES (?, ?)"); - $stmt->execute([$issue_id, $topic_id]); - } - } - - /** - * Remove the link for the posting denoted by the passed topic_id - * - * @param object $notification - * @param string $topic_id - */ - static function unlinkIssue($notification, $topic_id) - { - $stmt = DBManager::get()->prepare("DELETE FROM forum_entries_issues - WHERE topic_id = ?"); - $stmt->execute([$topic_id]); - } -} diff --git a/public/plugins_packages/core/Forum/models/ForumLike.php b/public/plugins_packages/core/Forum/models/ForumLike.php deleted file mode 100644 index 735d7b5..0000000 --- a/public/plugins_packages/core/Forum/models/ForumLike.php +++ /dev/null @@ -1,99 +0,0 @@ - - * @license http://www.gnu.org/licenses/gpl-3.0.html GPL version 3 - * @category Stud.IP - */ - -class ForumLike { - - /** - * Set the posting denoted by the passed topic_id as liked for the - * currently logged in user - * - * @param string $topic_id - */ - static function like($topic_id) { - $stmt = DBManager::get()->prepare("REPLACE INTO - forum_likes (topic_id, user_id) - VALUES (?, ?)"); - $stmt->execute([$topic_id, $GLOBALS['user']->id]); - - // get posting owner - $data = ForumEntry::getConstraints($topic_id); - - // notify owner of posting about the like - setTempLanguage($data['user_id']); - $notification = get_fullname($GLOBALS['user']->id) . _(' gefällt einer deiner Forenbeiträge!'); - restoreLanguage(); - - PersonalNotifications::add( - $data['user_id'], PluginEngine::getURL('coreforum/index/index/' . $topic_id .'?highlight_topic='. $topic_id .'#'. $topic_id), - $notification, $topic_id, - Icon::create('forum', 'clickable') - ); - } - - /** - * Revoke the liking of the posting denoted by the passed topic_id for the - * currently logged in user - * - * @param string $topic_id - */ - static function dislike($topic_id) { - $stmt = DBManager::get()->prepare("DELETE FROM forum_likes - WHERE topic_id = ? AND user_id = ?"); - $stmt->execute([$topic_id, $GLOBALS['user']->id]); - } - - /** - * Get the user_id for all likers of the topic denoted by the passed id - * - * @param string $topic_id - * @return array an array of user_id's - */ - static function getLikes($topic_id) { - $stmt = DBManager::get()->prepare("SELECT - auth_user_md5.user_id FROM forum_likes - LEFT JOIN auth_user_md5 USING (user_id) - LEFT JOIN user_info USING (user_id) - WHERE topic_id = ?"); - $stmt->execute([$topic_id]); - - return $stmt->fetchAll(PDO::FETCH_COLUMN); - } - - /** - * count the number of likes the user has received - system-wide - * - * @staticvar type $entries - * @param string $user_id the user's id to count the received likes for - * - * @return int the number of likes received - */ - static function receivedForUser($user_id) - { - static $entries; - - if (!$entries[$user_id]) { - $stmt = DBManager::get()->prepare("SELECT COUNT(*) - FROM forum_entries - LEFT JOIN forum_likes USING (topic_id) - WHERE forum_entries.user_id = ? - AND forum_likes.topic_id IS NOT NULL - AND forum_likes.user_id != ?"); - $stmt->execute([$user_id, $user_id]); - - $entries[$user_id] = $stmt->fetchColumn(); - } - - return $entries[$user_id]; - } -} \ No newline at end of file diff --git a/public/plugins_packages/core/Forum/models/ForumPerm.php b/public/plugins_packages/core/Forum/models/ForumPerm.php deleted file mode 100644 index a5441fa..0000000 --- a/public/plugins_packages/core/Forum/models/ForumPerm.php +++ /dev/null @@ -1,215 +0,0 @@ - - * @license http://www.gnu.org/licenses/gpl-3.0.html GPL version 3 - * @category Stud.IP - */ - -class ForumPerm { - - /** - * Check, if the a user has the passed permission in a seminar. - * Possible permissions are: - * edit_category - Editing the name of a category
- * add_category - Adding a new category
- * remove_category - Removing an existing category
- * sort_category - Sorting categories
- * edit_area - Editing an area (title + content)
- * add_area - Adding a new area
- * remove_area - Removing an area and all belonging threads
- * sort_area - Sorting of areas in categories and between categories
- * search - Searching in postings
- * edit_entry - Editing of foreign threads/postings
- * add_entry - Creating a new thread/posting
- * remove_entry - Removing of foreign threads/postings
- * fav_entry - Marking a Posting as "favorite"
- * like_entry - Liking a posting
- * move_thread - Moving a thrad between ares
- * close_thread - Close or open a thread
- * make_sticky - Make a thread sticky
- * abo - Signing up for mail-notifications for new entries
- * forward_entry - Forwarding an existing entry as a message
- * pdfexport - Exporting parts of the forum as PDF
- * admin - Allowed to mass-administrate the forum
- * view - Allowed to view the forum at all
- * edit_closed - Editing entries in a closed thread - * - * @param string $perm one of the modular permissions - * @param string $seminar_id the seminar to check for - * @param string $user_id the user to check for - * @return boolean true, if the user has the perms, false otherwise - */ - public static function has($perm, $seminar_id, $user_id = null) - { - static $permissions = []; - - // if no user-id is passed, use the current user (for your convenience) - if (!$user_id) { - $user_id = $GLOBALS['user']->id; - } - - // get the status for the user in the passed seminar - if (!$permissions[$seminar_id][$user_id]) { - $permissions[$seminar_id][$user_id] = $GLOBALS['perm']->get_studip_perm($seminar_id, $user_id); - } - - $status = $permissions[$seminar_id][$user_id]; - - // take care of the not logged in user - if ($user_id == 'nobody' || $status == false) { - // which status has nobody - read only or read/write? - if (get_object_type($seminar_id) == 'sem') { - $sem = Seminar::getInstance($seminar_id); - - if ($sem->write_level == 0) { - $status = 'nobody_write'; - } else if ($sem->read_level == 0) { - $status = 'nobody_read'; - } else { - return false; - } - } else { - return false; - } - } - - // root and admins have all possible perms - if (in_array($status, words('root admin')) !== false) { - return true; - } - - // eCULT Notlösung - if ($status == 'tutor' && $seminar_id == '30e0b89dcc9173d5fccf9f22b13b87bd') { - $status = 'autor'; - } - - // check the status and the passed permission - if (($status == 'dozent' || $status == 'tutor') && in_array($perm, - words('edit_category add_category remove_category sort_category ' - . 'edit_area add_area remove_area sort_area ' - . 'search edit_entry add_entry remove_entry fav_entry like_entry move_thread ' - . 'make_sticky close_thread abo forward_entry pdfexport view edit_closed') - ) !== false) { - return true; - } else if ($status == 'autor' && in_array($perm, words('search add_entry fav_entry like_entry forward_entry abo pdfexport view')) !== false) { - return true; - } else if ($status == 'user' && in_array($perm, words('search forward_entry pdfexport view')) !== false) { - return true; - } else if ($status == 'nobody_write' && in_array($perm, words('search add_entry pdfexport view')) !== false) { - return true; - } else if ($status == 'nobody_read' && in_array($perm, words('search pdfexport view')) !== false) { - return true; - } - - // user has no permission - return false; - } - - /** - * If the user has not the passed perm in a seminar, an AccessDeniedException - * is thrown. - * An optional topic_id can be passed which is checked against the passed - * seminar if the topic_id belongs to that seminar - * - * @param string $perm for the list of possible perms and their function see @ForumPerm::hasPerm() - * @param string $seminar_id the seminar to check for - * @param string $topic_id if passed, this topic_id is checked if it belongs to the passed seminar - * - * @throws AccessDeniedException - */ - public static function check($perm, $seminar_id, $topic_id = null) - { - if (!self::has($perm, $seminar_id)) { - throw new AccessDeniedException(sprintf( - _("Sie haben keine Berechtigung für diese Aktion! Benötigte Berechtigung: %s"), - $perm) - ); - } - - // check the topic id (if any) - if ($topic_id) { - self::checkTopicId($seminar_id, $topic_id); - } - } - - /** - * Check if the current user is allowed to edit the topic - * denoted by the passed id - * - * @staticvar array $perms - * - * @param string $topic_id the id for the topic to check for - * - * @return bool true if the user has the necessary perms, false otherwise - */ - public static function hasEditPerms($topic_id) - { - static $perms = []; - - if (!$perms[$topic_id]) { - // find out if the posting is the last in the thread - $constraints = ForumEntry::getConstraints($topic_id); - - $stmt = DBManager::get()->prepare("SELECT user_id, seminar_id - FROM forum_entries WHERE topic_id = ?"); - $stmt->execute([$topic_id]); - - $data = $stmt->fetch(); - - $closed = ForumEntry::isClosed($topic_id); - - $perms[$topic_id] = (($GLOBALS['user']->id == $data['user_id'] && $GLOBALS['user']->id != 'nobody') || - ForumPerm::has('edit_entry', $constraints['seminar_id'])) - && (!$closed || $closed && ForumPerm::has('edit_closed', $constraints['seminar_id'])); - } - - return $perms[$topic_id]; - } - - /** - * check if the passed category_id belongs to the passed seminar_id. - * Throws an AccessDenied denied exception if this is not the case - * - * @param string $seminar_id id of the seminar, the category should belong to - * @param string $category_id the id of the category to check - */ - public static function checkCategoryId($seminar_id, $category_id) - { - $data = ForumCat::get($category_id); - - if ($data['seminar_id'] != $seminar_id) { - throw new AccessDeniedException(sprintf( - _('Forum: Sie haben keine Berechtigung auf die Kategorie mit der ID %s zuzugreifen!'), - $category_id - )); - } - } - - /** - * check if the passed topic_id belongs to the passed seminar_id. - * Throws an AccessDenied denied exception if this is not the case - * - * @param string $seminar_id id of the seminar, the category should belong to - * @param string $topic_id the id of the topic to check - */ - public static function checkTopicId($seminar_id, $topic_id) - { - $data = ForumEntry::getConstraints($topic_id); - - if ($data['seminar_id'] != $seminar_id) { - throw new AccessDeniedException(sprintf( - _('Forum: Sie haben keine Berechtigung auf den Eintrag mit der ID %s zuzugreifen!'), - $topic_id - )); - } - } -} diff --git a/public/plugins_packages/core/Forum/models/ForumVisit.php b/public/plugins_packages/core/Forum/models/ForumVisit.php deleted file mode 100644 index befe116..0000000 --- a/public/plugins_packages/core/Forum/models/ForumVisit.php +++ /dev/null @@ -1,162 +0,0 @@ - - * @license http://www.gnu.org/licenses/gpl-3.0.html GPL version 3 - * @category Stud.IP - */ - -class ForumVisit { - - /** - * This is the maximum number of seconds that unread entries are - * marked as new. - */ - const LAST_VISIT_MAX = 7776000; // 90 days - - /** - * return number of new entries since last visit up to 3 month ago - * - * @param string $parent_id the seminar_id for the entries - * @param string $visitdate count all entries newer than this timestamp - * - * @return int the number of entries - */ - static function getCount($parent_id, $visitdate) - { - if ($visitdate < time() - ForumVisit::LAST_VISIT_MAX) { - $visitdate = time() - ForumVisit::LAST_VISIT_MAX; - } - - $constraints = ForumEntry::getConstraints($parent_id); - - $stmt = DBManager::get()->prepare("SELECT COUNT(*) FROM forum_entries - WHERE lft >= :lft AND rgt <= :rgt AND user_id != :user_id - AND seminar_id = :seminar_id - AND topic_id != seminar_id - AND chdate > :lastvisit"); - - $stmt->bindParam(':user_id', $GLOBALS['user']->id); - $stmt->bindParam(':lft', $constraints['lft']); - $stmt->bindParam(':rgt', $constraints['rgt']); - $stmt->bindParam(':seminar_id', $constraints['seminar_id']); - $stmt->bindParam(':lastvisit', $visitdate); - - $stmt->execute(); - - return $stmt->fetchColumn(); - } - - /** - * Set the seminar denoted by the passed id as visited by the currently - * logged in user - * - * @param string $seminar_id - */ - static function setVisit($seminar_id) { - $type = get_object_type($seminar_id, words('fak inst sem')); - if ($type === 'fak') { - $type = 'inst'; - } - if (self::getVisit($seminar_id) < object_get_visit($seminar_id, $type, false, false)) { - self::setVisitdates($seminar_id); - } - } - - /** - * Stores the visitdate in last_visitdate and sets the current time for as new visitdate - * - * @param string $seminar_id the seminar that has been entered - */ - static function setVisitdates($seminar_id) { - $stmt = DBManager::get()->prepare('SELECT visitdate FROM forum_visits - WHERE user_id = ? AND seminar_id = ?'); - $stmt->execute([$GLOBALS['user']->id, $seminar_id]); - $visitdate = $stmt->fetchColumn(); - - $stmt = DBManager::get()->prepare("REPLACE INTO forum_visits - (user_id, seminar_id, visitdate, last_visitdate) - VALUES (?, ?, UNIX_TIMESTAMP(), ?)"); - $stmt->execute([$GLOBALS['user']->id, $seminar_id, $visitdate]); - - } - - - /** - * returns visitdate and last_visitdate for the passed seminar and the - * currently logged in user - * - * @staticvar array $visit - * - * @param string $seminar_id the seminar to fetch the visitdates for - * @return mixed an array containing visitdate and last_visitdate - */ - private static function getVisitDates($seminar_id) - { - static $visit = []; - - // no costly checking for root or nobody necessary - if ($GLOBALS['perm']->have_perm('root') || $GLOBALS['user']->id == 'nobody') { - $tstamp = mktime(23, 59, 00, date('m'), 31, date('y')); - return ['visit' => $tstamp, 'last_visitdate' => $tstamp]; - } - - if (!isset($visit[$seminar_id])) { - $visit[$seminar_id] = []; - } - if (!isset($visit[$seminar_id][$GLOBALS['user']->id])) { - $stmt = DBManager::get()->prepare("SELECT visitdate, last_visitdate FROM forum_visits - WHERE seminar_id = ? AND user_id = ?"); - $stmt->execute([$seminar_id, $GLOBALS['user']->id]); - $visit[$seminar_id][$GLOBALS['user']->id] = $stmt->fetch(PDO::FETCH_ASSOC); - - // no entry for this seminar yet present - if (!$visit[$seminar_id][$GLOBALS['user']->id]) { - // set visitdate to current time - $visit[$seminar_id][$GLOBALS['user']->id] = [ - 'visit' => time() - ForumVisit::LAST_VISIT_MAX, - 'last_visitdate' => time() - ForumVisit::LAST_VISIT_MAX - ]; - } - - // prevent visit-dates from being older than LAST_VISIT_MAX allows - foreach ($visit[$seminar_id][$GLOBALS['user']->id] as $type => $date) { - if ($date < time() - ForumVisit::LAST_VISIT_MAX) { - $visit[$seminar_id][$GLOBALS['user']->id][$type] = time() - ForumVisit::LAST_VISIT_MAX; - } - } - } - - return $visit[$seminar_id][$GLOBALS['user']->id]; - } - - /** - * return the last_visitdate for the passed seminar and currently logged in user - * - * @param string $seminar_id the seminar to get the last_visitdate for - * @return int a timestamp - */ - static function getLastVisit($seminar_id) - { - $visit = self::getVisitDates($seminar_id); - return $visit['last_visitdate']; - } - - /** - * return the visitdate for the passed seminar and currently logged in user - * - * @param string $seminar_id the seminar to get the visitdate for - * @return int a timestamp - */ - static function getVisit($seminar_id) - { - $visit = self::getVisitDates($seminar_id); - return $visit['visitdate']; - } -} diff --git a/public/plugins_packages/core/Forum/plugin.manifest b/public/plugins_packages/core/Forum/plugin.manifest deleted file mode 100644 index 10df6d7..0000000 --- a/public/plugins_packages/core/Forum/plugin.manifest +++ /dev/null @@ -1,17 +0,0 @@ -pluginclassname=CoreForum -pluginname=Forum -origin=core -version=3.0.3 -summary=Diskussionen und veranstaltungsbegleitender Meinungsaustausch -description=Textbasierte zeit- und ortsunabhängige Möglichkeit zum Austausch von Gedanken, Meinungen und Erfahrungen. Lehrende und/oder Studierende können parallel zu Veranstaltungsthemen Fragen stellen, die in Form von Textbeiträgen besprochen werden können. Diese Beiträge können von allen Teilnehmenden der Veranstaltung gemerkt, verlinkt, positiv bewertet (sog. "Gefällt mir") und editiert werden (Letzeres nur von Lehrenden). Lehrende können zusätzlich Themen in Bereiche gliedern, zwischen den Bereichen verschiebe, im Bereich hervorheben und diesen öffnen und schließen. - -displayname = Forum -icon = assets/images/icons/black/forum.svg -keywords = Möglichkeit zum intensiven, nachhaltigen textbasierten Austausch; (nachträgliche) Strukturierung der Beiträge; Editierfunktion für Lehrende -category = Kommunikation und Zusammenarbeit -descriptionshort = Veranstaltungsbegleitender Meinungsaustausch zu bestimmten Themen -descriptionlong = Textbasierte zeit- und ortsunabhängige Möglichkeit zum Austausch von Gedanken, Meinungen und Erfahrungen. Lehrende und/oder Studierende können parallel zu Veranstaltungsthemen Fragen stellen, die in Form von Textbeiträgen besprochen werden können. Diese Beiträge können von allen Teilnehmenden der Veranstaltung gemerkt, verlinkt, positiv bewertet (sog. "Gefällt mir") und editiert werden (Letzeres nur von Lehrenden). Lehrende können zusätzlich Themen in Bereiche gliedern, zwischen den Bereichen verschieben, im Bereich hervorheben und diesen öffnen und schließen. - -screenshot = assets/images/plus/screenshots/Forum/Lehrendensicht_-_Kategorien_mit_Bereichen_und_Beitraegen.jpg -screenshot = assets/images/plus/screenshots/Forum/Studentische_Sicht_-_Kategorien_mit_Bereichen_und_Beitraegen.jpg -screenshot = assets/images/plus/screenshots/Forum/Einen_Forumsbeitrag_erstellen.jpg diff --git a/public/plugins_packages/core/Forum/views/admin/childs.php b/public/plugins_packages/core/Forum/views/admin/childs.php deleted file mode 100644 index 3becc1f..0000000 --- a/public/plugins_packages/core/Forum/views/admin/childs.php +++ /dev/null @@ -1,31 +0,0 @@ - - - \ No newline at end of file diff --git a/public/plugins_packages/core/Forum/views/admin/index.php b/public/plugins_packages/core/Forum/views/admin/index.php deleted file mode 100644 index 7082e22..0000000 --- a/public/plugins_packages/core/Forum/views/admin/index.php +++ /dev/null @@ -1,31 +0,0 @@ -addPlainText( - _('Bedienungshinweise'), - _('Sie befinden sich hier in der Administrationsansicht des Forums. ' - . 'Mit den blauen Pfeilen können Sie einen oder mehrere Einträge auswählen, welche dann verschoben werden können. '), - Icon::create('info', 'info_alt') -); -Helpbar::get()->addPlainText( - '', - _('Sie sollten nicht mehr als 20 Einträge gleichzeitig auswählen, da das verschieben sonst sehr lange dauern kann.') -); -?> -
-
    - $entries) : ?> -
  • - - - - asImg() ?> - -
    - - render_partial('admin/childs', compact('entries')) ?> -
  • - -
-
- \ No newline at end of file diff --git a/public/plugins_packages/core/Forum/views/area/_add_area_form.php b/public/plugins_packages/core/Forum/views/area/_add_area_form.php deleted file mode 100644 index f445569..0000000 --- a/public/plugins_packages/core/Forum/views/area/_add_area_form.php +++ /dev/null @@ -1,15 +0,0 @@ - - - -
- -
- - - - -
- - 0 -
- diff --git a/public/plugins_packages/core/Forum/views/area/_edit_area_form.php b/public/plugins_packages/core/Forum/views/area/_edit_area_form.php deleted file mode 100644 index 8604064..0000000 --- a/public/plugins_packages/core/Forum/views/area/_edit_area_form.php +++ /dev/null @@ -1,7 +0,0 @@ -
-
- - - - -
diff --git a/public/plugins_packages/core/Forum/views/area/_edit_category_form.php b/public/plugins_packages/core/Forum/views/area/_edit_category_form.php deleted file mode 100644 index 79d9d87..0000000 --- a/public/plugins_packages/core/Forum/views/area/_edit_category_form.php +++ /dev/null @@ -1,8 +0,0 @@ -
- - - "javascript:STUDIP.Forum.saveCategoryName('". $category_id ."'); return false;"]) ?> - "STUDIP.Forum.cancelEditCategoryName('". $category_id ."'); return false;"]) ?> -
diff --git a/public/plugins_packages/core/Forum/views/area/_js_templates.php b/public/plugins_packages/core/Forum/views/area/_js_templates.php deleted file mode 100644 index 975f42a..0000000 --- a/public/plugins_packages/core/Forum/views/area/_js_templates.php +++ /dev/null @@ -1,45 +0,0 @@ - - - - - diff --git a/public/plugins_packages/core/Forum/views/area/add.php b/public/plugins_packages/core/Forum/views/area/add.php deleted file mode 100644 index ba777fb..0000000 --- a/public/plugins_packages/core/Forum/views/area/add.php +++ /dev/null @@ -1,87 +0,0 @@ -> - - - 'handle js']) ?> - - - "> - = $visitdate && $entry['user_id'] !== $GLOBALS['user']->id): ?> - asImg([ - 'title' => _('Dieser Eintrag ist neu!'), - 'style' => 'margin-bottom: 15px', - ]) ?> - - - 0 ? Icon::ROLE_ATTENTION : Icon::ROLE_INFO)->asImg([ - 'title' => htmlReady(ForumHelpers::getVisitText($num_postings, $entry['topic_id'], $constraint['depth'])), - 'style' => 'margin-bottom: 15px;', - ]) ?> - - - - -
- - > - "> - - -
- - -
-
- - - - -
- render_partial('area/_edit_area_form', compact('entry')) ?> -
-
- -
- - - - - - - - render_partial('index/_last_post.php', compact('entry')) ?> - - - - addLink( - $controller->url_for("index/index/{$entry['last_posting']['topic_id']}#{$entry['last_posting']['topic_id']}"), - _('Zur letzten Antwort'), - Icon::create('forum'), - is_array($entry['last_posting']) ? ['class' => 'hidden-small-up'] : ['disabled' => ''] - )->condition(ForumPerm::has('edit_area', $seminar_id) && $issue_id = ForumIssue::getIssueIdForThread($entry['topic_id'])) - ->addLink( - URLHelper::getURL("dispatch.php/course/topics/edit/{$issue_id}"), - _('Zum Ablaufplan'), - Icon::create('info-circle', Icon::ROLE_STATUS_RED), - ['title' => _('Dieser Bereich ist einem Thema zugeordnet und kann hier nicht editiert werden. Die Angaben können im Ablaufplan angepasst werden.')] - )->condition(ForumPerm::has('edit_area', $seminar_id) && !$issue_id) - ->addLink( - $controller->url_for('index', ['edit_area' => $entry['topic_id']]), - _('Name/Beschreibung des Bereichs ändern'), - Icon::create('edit'), - [ - 'class' => 'edit-area', - 'onclick' => "STUDIP.Forum.editArea('{$entry['topic_id']}');return false;", - ] - )->condition(ForumPerm::has('remove_area', $seminar_id)) - ->addLink( - $controller->url_for("index/delete_entry/{$entry['topic_id']}"), - _('Bereich mitsamt allen Einträgen löschen!'), - Icon::create('trash'), - [ - 'class' => 'delete-area', - 'onclick' => "STUDIP.Forum.deleteArea(this, '{$entry['topic_id']}'); return false;", - ] - ) ?> - - - diff --git a/public/plugins_packages/core/Forum/views/index/_abo_link.php b/public/plugins_packages/core/Forum/views/index/_abo_link.php deleted file mode 100644 index fee4559..0000000 --- a/public/plugins_packages/core/Forum/views/index/_abo_link.php +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - _('Wenn sie diesen Bereich abonnieren, erhalten Sie eine ' - . 'Stud.IP-interne Nachricht sobald in diesem Bereich ' - . 'ein neuer Beitrag erstellt wurde.'), - 'onClick' => $js]) ?> - - $js]) ?> - diff --git a/public/plugins_packages/core/Forum/views/index/_areas.php b/public/plugins_packages/core/Forum/views/index/_areas.php deleted file mode 100644 index e5f7171..0000000 --- a/public/plugins_packages/core/Forum/views/index/_areas.php +++ /dev/null @@ -1,102 +0,0 @@ - -
- $entries) : ?> - - - - - - - - - - - - - - - - - - - - - - - - - - render_partial('area/add', compact('entry')) ?> - - - - render_partial('area/_add_area_form') ?> - - - - - - - - - - - - - - - - - - - - - -
- - asImg() ?> - asImg() ?> - - - - - - - - - - 'Name der Kategorie ändern'])->asImg() ?> - - - - - - 'Kategorie entfernen'])->asImg() ?> - - - - - - - - - render_partial('area/_edit_category_form', compact('category_id', 'categories')) ?> - - - - -
- -
- - - asImg(["id" => 'tutorAddArea']) ?> - -
- -
- -render_partial('area/_js_templates') ?> diff --git a/public/plugins_packages/core/Forum/views/index/_breadcrumb.php b/public/plugins_packages/core/Forum/views/index/_breadcrumb.php deleted file mode 100644 index 2f7738b..0000000 --- a/public/plugins_packages/core/Forum/views/index/_breadcrumb.php +++ /dev/null @@ -1,16 +0,0 @@ - -
- - - - - - - 1) :?>/ - - - - - -
- diff --git a/public/plugins_packages/core/Forum/views/index/_favorite.php b/public/plugins_packages/core/Forum/views/index/_favorite.php deleted file mode 100644 index f6bd4cb..0000000 --- a/public/plugins_packages/core/Forum/views/index/_favorite.php +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - _('Beitrag merken')])->asImg() ?> - - - - _('Beitrag nicht mehr merken')])->asImg() ?> - - diff --git a/public/plugins_packages/core/Forum/views/index/_js_templates.php b/public/plugins_packages/core/Forum/views/index/_js_templates.php deleted file mode 100644 index 3ee18ff..0000000 --- a/public/plugins_packages/core/Forum/views/index/_js_templates.php +++ /dev/null @@ -1,16 +0,0 @@ - diff --git a/public/plugins_packages/core/Forum/views/index/_last_post.php b/public/plugins_packages/core/Forum/views/index/_last_post.php deleted file mode 100644 index 956c6c1..0000000 --- a/public/plugins_packages/core/Forum/views/index/_last_post.php +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - id || $GLOBALS['perm']->have_perm('root')): ?> - - getFullname() : $entry['last_posting']['user_fullname']) ?> - - -
- - - "> - asImg([ - 'title' => _('Direkt zum Beitrag...'), - ]) ?> - - - - diff --git a/public/plugins_packages/core/Forum/views/index/_like.php b/public/plugins_packages/core/Forum/views/index/_like.php deleted file mode 100644 index c876311..0000000 --- a/public/plugins_packages/core/Forum/views/index/_like.php +++ /dev/null @@ -1,47 +0,0 @@ - - - - - id, $likes) !== false) { - if (sizeof($likes) > 1) { - $text = '' . sprintf(_('Dir und %s weiteren gefällt das.'), (sizeof($likes) - 1)); - $text .= ''; - foreach ($likes as $user_id) { - if ($user_id != $GLOBALS['user']->id) { - $text .= htmlReady(get_fullname($user_id)) .'
'; - } - } - $text .= '
'; - } else { - $text = _('Dir gefällt das.'); - } - } else { - $text = '' . sprintf(_('%s gefällt das.'), sizeof($likes)); - $text .= ''; - foreach ($likes as $user_id) { - $text .= htmlReady(get_fullname($user_id)) .'
'; - } - $text .= '
'; - } - - $text .= '
'; -endif ?> - - - -id, $likes)) : ?> - - - - - - - - diff --git a/public/plugins_packages/core/Forum/views/index/_mail_notification.php b/public/plugins_packages/core/Forum/views/index/_mail_notification.php deleted file mode 100644 index 9ce1d42..0000000 --- a/public/plugins_packages/core/Forum/views/index/_mail_notification.php +++ /dev/null @@ -1,31 +0,0 @@ - sprintf( - _('Im Forum der Veranstaltung **%s** gibt es einen neuen Beitrag unter **%s** von **%s**'), - $seminar['name'], - implode(' > ', array_map(function ($p) { return $p['name']; }, $path)), - $topic['anonymous'] ? _('Anonym') : $topic['author'] - ), - 'title' => $topic['name'] ? '**' . $topic['name'] ."** \n\n" : '', - 'content' => $topic['content'], - 'url' => _('Beitrag im Forum ansehen:') .' '. UrlHelper::getUrl( - $GLOBALS['ABSOLUTE_URI_STUDIP'] - . 'plugins.php/coreforum/index/index/' - . $topic['topic_id'] - .'?cid=' - . $topic['seminar_id'] - .'&again=yes#' - . $topic['topic_id'] - ) -]; - -// since we've possibly got a mixup of HTML and Stud.IP markup, -// create a pure HTML message step by step -$htmlMessage = Studip\Markup::markAsHtml( - implode('

', array_map('formatReady', $message)) -); - -echo $htmlMessage; diff --git a/public/plugins_packages/core/Forum/views/index/_new_category.php b/public/plugins_packages/core/Forum/views/index/_new_category.php deleted file mode 100644 index f633d44..0000000 --- a/public/plugins_packages/core/Forum/views/index/_new_category.php +++ /dev/null @@ -1,19 +0,0 @@ - - -
- -
- - - -
- -
- -
-
-
- diff --git a/public/plugins_packages/core/Forum/views/index/_new_entry.php b/public/plugins_packages/core/Forum/views/index/_new_entry.php deleted file mode 100644 index a926a3f..0000000 --- a/public/plugins_packages/core/Forum/views/index/_new_entry.php +++ /dev/null @@ -1,67 +0,0 @@ -flash['new_entry_title'] */ ?> - diff --git a/public/plugins_packages/core/Forum/views/index/_post.php b/public/plugins_packages/core/Forum/views/index/_post.php deleted file mode 100644 index 21a2dc3..0000000 --- a/public/plugins_packages/core/Forum/views/index/_post.php +++ /dev/null @@ -1,270 +0,0 @@ - -= $visitdate) || !(isset($visitdate))) ?> - - - - - ForumPerm::hasEditPerms($post['topic_id']), - 'edit_closed' => ForumPerm::has('edit_closed', $constraint['seminar_id']), - 'remove_entry' => ForumPerm::has('remove_entry', $constraint['seminar_id']), -] ?> - - - - -
- - -
- data-topic-id=""> -
-
- -
-
- -
- - - - - getImageTag(Avatar::SMALL, - ['title' => _('Stud.IP')]) ?> - , - - - - getImageTag(Avatar::SMALL, - ['title' => get_username($post['user_id'])]) ?> - - - , - - , - - - - - -
-
- - - > - - - - - - - _('Dieses Thema wurde geschlossen. Sie können daher nicht auf diesen Beitrag antworten.')])->asImg(16) ?> - - - - - - - - - - - > ', ForumEntry::getFlatPathToPosting($post['topic_id']))), $highlight) ?> - - - - - - - - - -
- - -
- > - - - - > - - render() ?> - -
- - -
-
- - > - - "STUDIP.Forum.saveEntry('". $post['topic_id'] ."'); return false;"]) ?> - - "STUDIP.Forum.cancelEditEntry('". $post['topic_id'] ."'); return false;"]) ?> - - - - - > - - - - - "javascript:STUDIP.Forum.citeEntry('". $post['topic_id'] ."'); return false;", - 'class' => !$perms['edit_closed'] ? 'hideWhenClosed' : '', - 'style' => !$can_edit_closed ? 'display: none' : '' - ]) ?> - - - - "STUDIP.Forum.editEntry('". $post['topic_id'] ."'); return false;", - 'class' => !$perms['edit_closed'] ? 'hideWhenClosed' : '', - 'style' => !$can_edit_closed ? 'display: none' : '' - ]) ?> - - - - > - - - - - "STUDIP.Forum.showDialog('$confirmText', '$confirmLinkApproved'); return false;"]) ?> - - - "STUDIP.Forum.showDialog('$confirmText', '$confirmLinkApproved'); return false;"]) ?> - - - - - 'js']) ?> - - -
-
- -
- - - > -
-
- WYSIWYG): ?> - render_partial('index/_smiley_favorites', ['textarea_id' => $post['topic_id']]) ?> - -
-
-
- - - - > -
- -
- - id || $GLOBALS['perm']->have_perm('root')): ?> -
- - - getImageTag(Avatar::MEDIUM, - ['title' => get_username($post['user_id'])]) ?> - -
- - - - asImg() ?> - - - - - - - - - _('Online')]) ?> - - _('Abwesend')]) ?> - - _('Offline')]) ?> - - - - - - - - -
- -
- get_studip_perm($constraint['seminar_id'], $post['user_id']))?> -
- -
- Beiträge: -
- - -
- - -
- -
- -
- -
- - - render_partial('index/_favorite', ['topic_id' => $post['topic_id'], 'favorite' => $post['fav']]) ?> - - - - - _('Link zu diesem Beitrag')])->asImg() ?> - -
- - - -
- - -
- -
- -
- - - - _("Dieser Beitrag ist seit Ihrem letzten Besuch hinzugekommen.")])->asImg(16) ?> - - -
- -
-
-
- -render_partial('index/_preview', ['preview_id' => 'preview_' . $post['topic_id']]) ?> diff --git a/public/plugins_packages/core/Forum/views/index/_postings.php b/public/plugins_packages/core/Forum/views/index/_postings.php deleted file mode 100644 index a2ca6d2..0000000 --- a/public/plugins_packages/core/Forum/views/index/_postings.php +++ /dev/null @@ -1,15 +0,0 @@ -
- -render_partial('index/_post', compact('post', 'visitdate', 'section')); - - $posting_num++; -endforeach -?> - -
diff --git a/public/plugins_packages/core/Forum/views/index/_preview.php b/public/plugins_packages/core/Forum/views/index/_preview.php deleted file mode 100644 index e39f621..0000000 --- a/public/plugins_packages/core/Forum/views/index/_preview.php +++ /dev/null @@ -1,11 +0,0 @@ - \ No newline at end of file diff --git a/public/plugins_packages/core/Forum/views/index/_smiley_favorites.php b/public/plugins_packages/core/Forum/views/index/_smiley_favorites.php deleted file mode 100644 index fbe48b4..0000000 --- a/public/plugins_packages/core/Forum/views/index/_smiley_favorites.php +++ /dev/null @@ -1,27 +0,0 @@ -id); -?> -
- - - | - " target="new"> -
- get()) ?> - - - - - id != 'nobody') : ?> - -
-
-
- - - -
- -
-
diff --git a/public/plugins_packages/core/Forum/views/index/_threads.php b/public/plugins_packages/core/Forum/views/index/_threads.php deleted file mode 100644 index d3ca4a2..0000000 --- a/public/plugins_packages/core/Forum/views/index/_threads.php +++ /dev/null @@ -1,196 +0,0 @@ -
- -
-
-
-
-
- - -
- - $entries) : ?> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
- "> - = $visitdate && $entry['user_id'] != $GLOBALS['user']->id): ?> - asImg([ - 'title' => _('Dieser Eintrag ist neu!'), - ]) ?> - - - 0 ? Icon::ROLE_ATTENTION : Icon::ROLE_INFO)->asImg([ - 'title' => htmlReady(ForumHelpers::getVisitText($num_postings, $entry['topic_id'])), - ]) ?> - - -
- - asImg([ - 'title' => _('Dieses Thema ist geschlossen, es können keine neuen Beiträge erstellt werden.'), - 'id' => "img-locked-{$entry['topic_id']}", - 'style' => $entry['closed'] ? '' : 'display: none', - ]) ?> - - asImg([ - 'title' => _('Dieses Thema wurde hervorgehoben.'), - 'id' => "img-sticky-{$entry['topic_id']}", - 'style' => $entry['sticky'] ? '' : 'display: none', - ]) ?> -
-
-
- - - - - - - - - id || $GLOBALS['perm']->have_perm('root')): ?> - - getFullname() : $entry['author']) ?> - - - -
- - -
-
- - - render_partial('index/_last_post.php', compact('entry')) ?> - - addLink( - $controller->url_for("index/index/{$entry['last_posting']['topic_id']}#{$entry['last_posting']['topic_id']}"), - _('Zur letzten Antwort'), - Icon::create('forum'), - ['class' => 'hidden-small-up'] - ) - // Make thread sticky/unsticky - ->conditionAll(ForumPerm::has('make_sticky', $seminar_id) && $constraint['depth'] >= 1) - ->condition(!$entry['sticky']) - ->addLink( - $controller->url_for('index/make_sticky', $entry['topic_id'], $constraint['topic_id'], 0), - _('Thema hervorheben'), - Icon::create('staple'), - ['id' => "stickyButton-{$entry['topic_id']}"] - ) - ->condition($entry['sticky']) - ->addLink( - $controller->url_for('index/make_unsticky', $entry['topic_id'], $constraint['topic_id'], 0), - _('Hervorhebung aufheben'), - Icon::create('staple'), - ['id' => "stickyButton-{$entry['topic_id']}"] - ) - ->conditionAll(null) - // Move thread - ->condition(ForumPerm::has('move_thread', $seminar_id)) - ->addLink( - "javascript:STUDIP.Forum.moveThreadDialog('{$entry['topic_id']}');", - _('Dieses Thema verschieben'), - Icon::create('folder-full+move_right'), - ['class' => 'js'] - ) - // Open/close thread - ->conditionAll(ForumPerm::has('close_thread', $seminar_id) && $constraint['depth'] >= 1) - ->condition(!$entry['closed']) - ->addLink( - $controller->url_for('index/close_thread', $entry['topic_id'], $constraint['topic_id'], ForumHelpers::getPage()), - _('Thema schließen'), - Icon::create('lock-locked'), - [ - 'id' => "closeButton-{$entry['topic_id']}", - 'onclick' => "STUDIP.Forum.closeThreadFromOverview('{$entry['topic_id']}', '{$constraint['topic_id']}', " . ForumHelpers::getPage() . "); return false;", - ] - ) - ->condition($entry['closed']) - ->addLink( - $controller->url_for('index/open_thread', $entry['topic_id'], $constraint['topic_id'], ForumHelpers::getPage()), - _('Thema öffnen'), - Icon::create('lock-unlocked'), - [ - 'id' => "closeButton-{$entry['topic_id']}", - 'onclick' => "STUDIP.Forum.openThreadFromOverview('{$entry['topic_id']}', '{$constraint['topic_id']}', " . ForumHelpers::getPage() . "); return false;", - ] - ) - ->conditionAll(null) - // Delete thread - ->condition(ForumPerm::has('remove_thread', $seminar_id)) - ->addButton( - 'delete', - _('Dieses Thema löschen'), - Icon::create('trash'), - [ - 'formaction' => $controller->url_for("index/delete_entry/{$entry['topic_id']}"), - 'data-confirm' => sprintf( - _('Sind sie sicher dass Sie den Eintrag %s löschen möchten?'), - htmlReady($entry['name']) - ) - ] - ) - ?> - - - - -
- -
diff --git a/public/plugins_packages/core/Forum/views/index/index.php b/public/plugins_packages/core/Forum/views/index/index.php deleted file mode 100644 index d1b9ecc..0000000 --- a/public/plugins_packages/core/Forum/views/index/index.php +++ /dev/null @@ -1,265 +0,0 @@ - - -render_partial('index/_js_templates') ?> - - -
-setId('tutorSearchInfobox'); - $search->addNeedle(_('Beiträge durchsuchen'), 'searchfor', true); - $search->addFilter(_('Titel'), 'search_title'); - $search->addFilter(_('Inhalt'), 'search_content'); - $search->addFilter(_('Autor/-in'), 'search_author'); - $sidebar->addWidget($search); -} - -$actions = new ActionsWidget(); - -if ($section == 'index') { - if (ForumPerm::has('abo', $seminar_id)) { - if (ForumAbo::has($constraint['topic_id'])) : - $abo_text = _('Nicht mehr abonnieren'); - $abo_url = PluginEngine::getURL('coreforum/index/remove_abo/' . $constraint['topic_id']); - else : - switch ($constraint['depth']) { - case '0': $abo_text = _('Komplettes Forum abonnieren');break; - case '1': $abo_text = _('Diesen Bereich abonnieren');break; - default: $abo_text = _('Dieses Thema abonnieren');break; - } - - $abo_url = PluginEngine::getURL('coreforum/index/abo/' . $constraint['topic_id']); - endif; - - $actions->addLink($abo_text, $abo_url, Icon::create('link-intern', 'clickable')); - } - - if (ForumPerm::has('close_thread', $seminar_id) && $constraint['depth'] > 1) { - if ($constraint['closed'] == 0) { - $close_url = PluginEngine::getURL('coreforum/index/close_thread/' - . $constraint['topic_id'] .'/'. $constraint['topic_id'] .'/'. ForumHelpers::getPage()); - $close = new LinkElement( - _('Thema schließen'), - $close_url, - Icon::create('lock-locked', 'clickable'), - [ - 'onclick' => 'STUDIP.Forum.closeThreadFromThread(\'' . $constraint['topic_id'] . '\', ' - . ForumHelpers::getPage() . '); return false;', - 'class' => "closeButtons" - ] - ); - $actions->addElement($close, 'closethread'); - } else { - $open_url = PluginEngine::getURL('coreforum/index/open_thread/' - . $constraint['topic_id'] .'/'. $constraint['topic_id'] .'/'. ForumHelpers::getPage()); - $open = new LinkElement( - _('Thema öffnen'), - $open_url, - Icon::create('lock-unlocked', 'clickable'), - [ - 'onclick' => 'STUDIP.Forum.openThreadFromThread(\'' . $constraint['topic_id'] . '\', ' - . ForumHelpers::getPage() . '); return false;', - 'class' => "closeButtons" - ] - ); - $actions->addElement($open, 'closethread'); - } - } - - if (ForumPerm::has('make_sticky', $seminar_id) && $constraint['depth'] > 1) { - if ($constraint['sticky'] == 0) { - $emphasize_url = PluginEngine::getURL('coreforum/index/make_sticky/' - . $constraint['topic_id'] .'/'. $constraint['topic_id'] .'/'. ForumHelpers::getPage()); - $emphasize = new LinkElement( - _('Thema hervorheben'), - $emphasize_url, - Icon::create('staple', 'clickable'), - [ - 'onclick' => 'STUDIP.Forum.makeThreadStickyFromThread(\'' . $constraint['topic_id'] . '\', ' - . ForumHelpers::getPage() . '); return false;', - 'id' => "stickyButton" - ] - ); - $actions->addElement($emphasize, 'emphasize'); - } else { - $unemphasize_url = PluginEngine::getURL('coreforum/index/make_unsticky/' - . $constraint['topic_id'] .'/'. $constraint['topic_id'] .'/'. ForumHelpers::getPage()); - $emphasize = new LinkElement( - _('Hervorhebung aufheben'), - $unemphasize_url, - Icon::create('staple', 'clickable'), - [ - 'onclick' => 'STUDIP.Forum.makeThreadUnstickyFromThread(\'' . $constraint['topic_id'] . '\', ' - . ForumHelpers::getPage() . '); return false;', - 'id' => "stickyButton" - ] - ); - $actions->addElement($emphasize, 'emphasize'); - } - } - - if ($constraint['depth'] == 0 && ForumPerm::has('add_category', $seminar_id)) { - $actions->addLink(_('Neue Kategorie erstellen'), "#create", Icon::create('link-intern', 'clickable')); - } -} - -$sidebar->addWidget($actions); - -if ($section === 'index' && ForumPerm::has('pdfexport', $seminar_id)) { - $export = new ExportWidget(); - $export->addLink(_('Beiträge als PDF exportieren'), - $controller->url_for('index/pdfexport/' . $constraint['topic_id']), - Icon::create('file-pdf', 'clickable')); - $sidebar->addWidget($export); -} -?> - - -render_partial('index/_breadcrumb') ?> - - - ForumEntry::POSTINGS_PER_PAGE) : ?> -
- 0 || !isset($constraint)) : ?> - render('shared/pagechooser', [ - 'page' => ForumHelpers::getPage(), - 'num_postings' => $number_of_entries, - 'perPage' => ForumEntry::POSTINGS_PER_PAGE, - 'pagelink' => str_replace('%%s', '%s', str_replace('%', '%%', PluginEngine::getURL('coreforum/index/goto_page/'. $topic_id .'/'. $section - .'/%s/?searchfor=' . $searchfor . (!empty($options) ? '&'. http_build_query($options) : '' )))) - ]); ?> - - -
- - - -
- render_partial('messages') ?> -
- - - - - - - - render_partial('index/_areas') ?> - - render_partial('index/_threads') ?> - - - - - render_partial('index/_postings') ?> - - - - -
- -
- - - - -
-
- - - render_partial('index/_abo_link', compact('constraint')) ?> - - - - - '_blank']) ?> - -
-
- - - render_partial('index/_new_category') ?> - - - - -
-
-
- - 'STUDIP.Forum.answerEntry(); return false;', - 'class' => 'hideWhenClosed',]) ?> - - - 1 && ($constraint['closed'] == 1)) : ?> - 'STUDIP.Forum.answerEntry(); return false;', - 'class' => 'hideWhenClosed', - 'style' => 'display:none;' - ]) ?> - - - 1) : ?> - - 'STUDIP.Forum.closeThreadFromThread("'. $topic_id .'"); return false;', - 'class' => 'closeButtons'] - ) ?> - - 'STUDIP.Forum.openThreadFromThread("'. $topic_id .'"); return false;', - 'class' => 'closeButtons'] - ) ?> - - - - 0 && ForumPerm::has('abo', $seminar_id)) : ?> - - render_partial('index/_abo_link', compact('constraint')) ?> - - - - - '_blank']) ?> - -
-
- -
- - - - -seminar_id)) - || ($constraint['depth'] >= 1 && ForumPerm::has('add_entry', $seminar_id)) ): ?> - render_partial('index/_new_entry') ?> - -
- - - - - - - - - diff --git a/public/plugins_packages/core/Forum/views/messages.php b/public/plugins_packages/core/Forum/views/messages.php deleted file mode 100644 index 246a85d..0000000 --- a/public/plugins_packages/core/Forum/views/messages.php +++ /dev/null @@ -1,7 +0,0 @@ - $message): ?> - - - - - - diff --git a/public/plugins_packages/core/NewsWidget/NewsWidget.php b/public/plugins_packages/core/NewsWidget/NewsWidget.php deleted file mode 100644 index 8dd150b..0000000 --- a/public/plugins_packages/core/NewsWidget/NewsWidget.php +++ /dev/null @@ -1,79 +0,0 @@ - - * 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 'app/controllers/news.php'; - -class NewsWidget extends StudIPPlugin implements PortalPlugin -{ - public function getPluginName() - { - return _('Ankündigungen'); - } - - function getPortalTemplate() - { - $dispatcher = new StudipDispatcher(); - $controller = new NewsController($dispatcher); - $response = $controller->relay('news/display/studip'); - $template = $GLOBALS['template_factory']->open('shared/string'); - $template->content = $response->body; - - if (StudipNews::CountUnread() > 0) { - $navigation = new Navigation('', PluginEngine::getLink($this, [], 'read_all')); - $navigation->setImage(Icon::create('refresh', 'clickable', ["title" => _('Alle als gelesen markieren')])); - $icons[] = $navigation; - } - - if (Config::get()->NEWS_RSS_EXPORT_ENABLE) { - if ($rss_id = StudipNews::GetRssIdFromRangeId('studip')) { - $navigation = new Navigation('', 'rss.php', ['id' => $rss_id]); - $navigation->setImage(Icon::create('rss', 'clickable', ["title" => _('RSS-Feed')])); - $icons[] = $navigation; - } - } - - if ($GLOBALS['perm']->have_perm('root')) { - $navigation = new Navigation('', 'dispatch.php/news/edit_news/new/studip'); - $navigation->setImage(Icon::create('add', 'clickable', ["title" => _('Ankündigungen bearbeiten')]), ['rel' => 'get_dialog']); - $icons[] = $navigation; - if (Config::get()->NEWS_RSS_EXPORT_ENABLE) { - $navigation = new Navigation('', 'dispatch.php/news/rss_config/studip'); - $navigation->setImage(Icon::create('rss+add', 'clickable', ["title" => _('RSS-Feed konfigurieren')]), ["rel" => 'size=auto']); - $icons[] = $navigation; - } - } - - $template->icons = $icons; - - return $template; - } - - - public function perform($unconsumed) - { - if ($unconsumed !== 'read_all') { - return; - } - - $global_news = StudipNews::GetNewsByRange('studip', true); - foreach ($global_news as $news) { - object_add_view($news['news_id']); - object_set_visit($news['news_id'], 'news'); - } - - if (Request::isXhr()) { - echo json_encode(true); - } else { - PageLayout::postMessage(MessageBox::success(_('Alle Ankündigungen wurden als gelesen markiert.'))); - header('Location: '. URLHelper::getLink('dispatch.php/start')); - } - } -} diff --git a/public/plugins_packages/core/NewsWidget/plugin.manifest b/public/plugins_packages/core/NewsWidget/plugin.manifest deleted file mode 100644 index 406309a..0000000 --- a/public/plugins_packages/core/NewsWidget/plugin.manifest +++ /dev/null @@ -1,6 +0,0 @@ -pluginclassname=NewsWidget -pluginname=NewsWidget -origin=core -version=1.0 -studipMinVersion=3.1 -description=Mit diesem Widget haben Sie Zugriff auf systemweite Ankündigungen. diff --git a/public/plugins_packages/core/QuickSelection/QuickSelection.php b/public/plugins_packages/core/QuickSelection/QuickSelection.php deleted file mode 100644 index 70590b2..0000000 --- a/public/plugins_packages/core/QuickSelection/QuickSelection.php +++ /dev/null @@ -1,99 +0,0 @@ - - * 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. - */ - -class QuickSelection extends StudIPPlugin implements PortalPlugin -{ - public function getPluginName() - { - return _('Schnellzugriff'); - } - - public function getPortalTemplate() - { - PageLayout::addScript($this->getPluginUrl() . '/js/QuickSelection.js'); - $names = WidgetHelper::getWidgetUserConfig($GLOBALS['user']->id, 'QUICK_SELECTION'); - - $template_factory = new Flexi_TemplateFactory(__DIR__ . '/templates'); - $template = $template_factory->open('list'); - $template->navigation = $this->getFilteredNavigation($names); - - $navigation = new Navigation('', PluginEngine::getLink($this, [], 'configuration')); - $navigation->setImage(Icon::create('edit', 'clickable', ["title" => _('Konfigurieren')]), ['data-dialog'=>'size=auto']); - - $template->icons = [$navigation]; - - return $template; - } - - private function getFilteredNavigation($items) - { - $result = []; - - $navigation = Navigation::getItem('/start'); - foreach ($navigation as $name => $nav) { - // if config is new (key:value) display values which are not in config array - // otherwise hide items which are not in config array - // This is important for patching. - if (!isset($items[$name]) || $items[$name] !== 'deactivated') { - $result[] = $nav; - } - } - - return $result; - } - - public function save_action() - { - if (Config::get()->QUICK_SELECTION === null) { - Config::get()->create('QUICK_SELECTION', [ - 'range' => 'user', - 'type' => 'array', - 'description' => 'Einstellungen des QuickSelection-Widgets', - ]); - } - - $add_removes = Request::optionArray('add_removes'); - - // invert add_removes so that only unchecked values are stored into config - $names = []; - - $navigation = Navigation::getItem('/start'); - foreach ($navigation as $name => $nav) { - if (!in_array($name, $add_removes)) { - $names[$name] = 'deactivated'; - } - - } - - WidgetHelper::addWidgetUserConfig($GLOBALS['user']->id, 'QUICK_SELECTION', $names); - - $template_factory = new Flexi_TemplateFactory(__DIR__ . '/templates'); - $template = $template_factory->open('list'); - $template->navigation = $this->getFilteredNavigation($names); - - header('X-Dialog-Close: 1'); - header('X-Dialog-Execute: STUDIP.QuickSelection.update'); - - echo $template->render(); - } - - public function configuration_action() - { - $template_factory = new Flexi_TemplateFactory(__DIR__ . '/templates'); - $template = $template_factory->open('edit'); - $template->links = Navigation::getItem('/start'); - $template->config = WidgetHelper::getWidgetUserConfig($GLOBALS['user']->id, 'QUICK_SELECTION'); - $template->plugin = $this; - - header('X-Title: ' . _('Schnellzugriff konfigurieren')); - echo $template->render(); - } -} diff --git a/public/plugins_packages/core/QuickSelection/js/QuickSelection.js b/public/plugins_packages/core/QuickSelection/js/QuickSelection.js deleted file mode 100644 index c15676b..0000000 --- a/public/plugins_packages/core/QuickSelection/js/QuickSelection.js +++ /dev/null @@ -1,8 +0,0 @@ -/*jslint browser: true */ -/*global jQuery, STUDIP */ - -STUDIP.QuickSelection = { - update: function (html) { - jQuery('#quickSelectionWrap').replaceWith(html); - } -}; diff --git a/public/plugins_packages/core/QuickSelection/plugin.manifest b/public/plugins_packages/core/QuickSelection/plugin.manifest deleted file mode 100644 index 00280f0..0000000 --- a/public/plugins_packages/core/QuickSelection/plugin.manifest +++ /dev/null @@ -1,6 +0,0 @@ -pluginclassname=QuickSelection -pluginname=QuickSelection -origin=core -version=1.0 -studipMinVersion=3.1 -description=Mit dem Schnellzugriff-Widget können Sie Links zu bestimmten Bereichen in Stud.IP verwalten. diff --git a/public/plugins_packages/core/QuickSelection/templates/edit.php b/public/plugins_packages/core/QuickSelection/templates/edit.php deleted file mode 100644 index 6608ee0..0000000 --- a/public/plugins_packages/core/QuickSelection/templates/edit.php +++ /dev/null @@ -1,22 +0,0 @@ -
-
-
- -
- $nav) : ?> - - -
-
-
- - -
-
-
diff --git a/public/plugins_packages/core/QuickSelection/templates/list.php b/public/plugins_packages/core/QuickSelection/templates/list.php deleted file mode 100644 index e465d8d..0000000 --- a/public/plugins_packages/core/QuickSelection/templates/list.php +++ /dev/null @@ -1,28 +0,0 @@ -
- - isVisible()) : ?> - - - -
diff --git a/public/plugins_packages/core/ScheduleWidget/CalendarWidgetView.php b/public/plugins_packages/core/ScheduleWidget/CalendarWidgetView.php deleted file mode 100644 index 946592c..0000000 --- a/public/plugins_packages/core/ScheduleWidget/CalendarWidgetView.php +++ /dev/null @@ -1,52 +0,0 @@ - - * @license GPL2 or any later version - * @since Stud.IP 3.4 - */ -class CalendarWidgetView extends CalendarWeekView -{ - /** - * Creates a widget view from a week view. - * - * @param CalendarWeekView $view The CalendarWeekView object - * @return CalendarWidgetView object with the data from the - * CalendarWeekView - */ - public static function createFromWeekView(CalendarWeekView $view) - { - $new_view = new self($view->getColumns(), $view->getContext()); - $new_view->setReadOnly(true); - return $new_view; - } - - /** - * Returns all columns of the calendar-view and removes everything that - * is not needed and links the entry to the details page of the course. - * - * @return array of CalendarColumn - */ - public function getColumns() - { - foreach ($this->entries as $column) { - $column->setURL(false); - foreach ($column->entries as $key => $entry) { - if (isset($entry['cycle_id'])) { - list($course_id, $cycle_id) = explode('-', $entry['id']); - - $url = URLHelper::getLink('dispatch.php/course/details/?sem_id=' . $course_id); - $column->entries[$key]['url'] = $url; - } else { - unset($column->entries[$key]['url']); - } - - unset($column->entries[$key]['onClick']); - unset($column->entries[$key]['icons']); - } - } - - return $this->entries; - } -} diff --git a/public/plugins_packages/core/ScheduleWidget/ScheduleWidget.php b/public/plugins_packages/core/ScheduleWidget/ScheduleWidget.php deleted file mode 100644 index 72343e7..0000000 --- a/public/plugins_packages/core/ScheduleWidget/ScheduleWidget.php +++ /dev/null @@ -1,49 +0,0 @@ - - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - */ - -require_once __DIR__ . '/CalendarWidgetView.php'; - -class ScheduleWidget extends StudIPPlugin implements PortalPlugin -{ - /** - * Returns the name of the plugin/widget. - * - * @return String containing the name - */ - public function getPluginName() - { - return _('Mein Stundenplan'); - } - - /** - * Return the template for the widget. - * - * @return Flexi_PhpTemplate The template containing the widget contents - */ - public function getPortalTemplate() - { - $view = CalendarScheduleModel::getUserCalendarView( - $GLOBALS['user']->id, - false, - false, - $days = array(0,1,2,3,4) - ); - - $template = $GLOBALS['template_factory']->open('shared/string'); - $template->content = CalendarWidgetView::createFromWeekView($view)->render(); - - return $template; - } -} diff --git a/public/plugins_packages/core/ScheduleWidget/plugin.manifest b/public/plugins_packages/core/ScheduleWidget/plugin.manifest deleted file mode 100644 index 3b7a03e..0000000 --- a/public/plugins_packages/core/ScheduleWidget/plugin.manifest +++ /dev/null @@ -1,6 +0,0 @@ -pluginclassname=ScheduleWidget -pluginname=ScheduleWidget -origin=core -version=3.0 -plugincontext=Startseite -description=Mit diesem Widget haben Sie eine Übersicht Ihres aktuellen Stundenplans. diff --git a/public/plugins_packages/core/TerminWidget/TerminWidget.php b/public/plugins_packages/core/TerminWidget/TerminWidget.php deleted file mode 100644 index 0206597..0000000 --- a/public/plugins_packages/core/TerminWidget/TerminWidget.php +++ /dev/null @@ -1,36 +0,0 @@ - - * - * 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 'app/controllers/calendar/contentbox.php'; - -class TerminWidget extends StudIPPlugin implements PortalPlugin -{ - public function getPluginName() - { - return _('Meine aktuellen Termine'); - } - - public function getPortalTemplate() - { - $dispatcher = new StudipDispatcher(); - $controller = new Calendar_ContentboxController($dispatcher); - $response = $controller->relay('calendar/contentbox/display/'.$GLOBALS['user']->id); - $template = $GLOBALS['template_factory']->open('shared/string'); - $template->content = $response->body; - - $navigation = new Navigation('', 'dispatch.php/calendar/single/week', ['self' => true]); - $navigation->setImage(Icon::create('add', 'clickable', ["title" => _('Neuen Termin anlegen')])); - $template->icons = [$navigation]; - - return $template; - } -} diff --git a/public/plugins_packages/core/TerminWidget/plugin.manifest b/public/plugins_packages/core/TerminWidget/plugin.manifest deleted file mode 100644 index ed0e091..0000000 --- a/public/plugins_packages/core/TerminWidget/plugin.manifest +++ /dev/null @@ -1,6 +0,0 @@ -pluginclassname=TerminWidget -pluginname=TerminWidget -origin=core -version=3.0 -plugincontext=Startseite -description=Mit diesem Widget haben Sie ihre aktuellen Termine im Überlick. diff --git a/resources/assets/javascripts/init.js b/resources/assets/javascripts/init.js index a1000e0..f7c3dda 100644 --- a/resources/assets/javascripts/init.js +++ b/resources/assets/javascripts/init.js @@ -1,6 +1,7 @@ import Vue from './lib/studip-vue.js'; import ActionMenu from './lib/actionmenu.js'; +import ActivityFeed from './lib/activityfeed.js'; import admin_sem_class from './lib/admin_sem_class.js'; import Admission from './lib/admission.js'; import Arbeitsgruppen from './lib/arbeitsgruppen.js'; @@ -56,6 +57,7 @@ import Plus from './lib/plus.js'; import QRCode from './lib/qr_code.js'; import Questionnaire from './lib/questionnaire.js'; import QuickSearch from './lib/quick_search.js'; +import QuickSelection from './lib/quick_selection.js'; import Raumzeit from './lib/raumzeit.js'; import {ready, domReady, dialogReady} from './lib/ready.js'; import register from './lib/register.js'; @@ -87,6 +89,7 @@ const URLHelper = createURLHelper(configURLHelper); window.STUDIP = _.assign(window.STUDIP || {}, { ActionMenu, + ActivityFeed, admin_sem_class, Admission, api, @@ -143,6 +146,7 @@ window.STUDIP = _.assign(window.STUDIP || {}, { QRCode, Questionnaire, QuickSearch, + QuickSelection, Raumzeit, register, Report, diff --git a/resources/assets/javascripts/lib/activityfeed.js b/resources/assets/javascripts/lib/activityfeed.js new file mode 100644 index 0000000..95145cb --- /dev/null +++ b/resources/assets/javascripts/lib/activityfeed.js @@ -0,0 +1,127 @@ +const ActivityFeed = { + user_id : null, + polling: null, + initial: true, + scrolledfrom: null, + maxheight: null, + filter: null, + + init: function() { + STUDIP.ActivityFeed.maxheight = parseInt($('#stream-container').css('max-height').replace(/[^-\d\.]/g, '')); + + STUDIP.ActivityFeed.loadFeed(STUDIP.ActivityFeed.filter); + + $('#stream-container').scroll(function () { + var scrollBottom = $('#stream-container').scrollTop() + $('#stream-container').height() + 250; + + if ($('#stream-container').prop('scrollHeight') < scrollBottom) { + STUDIP.ActivityFeed.loadFeed(STUDIP.ActivityFeed.filter); + } + }); + + + $(document).on('click', '.provider_circle', function () { + $(this).parent().parent().children('.activity-content').toggle(); + }).on('click', '#toggle-all-activities,#toggle-user-activities', function () { + var toggled = $(this).is(':not(.toggled)'); + $(this).toggleClass('toggled', toggled); + + STUDIP.ActivityFeed.setToggleStatus(); + + return false; + }); + }, + + getTemplate: _.memoize(function(name) { + return _.template($("script." + name).html()); + }), + + loadFeed: function(filtertype) { + if (STUDIP.ActivityFeed.user_id === null) { + console.log('Could not retrieve activities, no valid user id found!'); + return false; + } + + if (STUDIP.ActivityFeed.polling || !STUDIP.ActivityFeed.scrolledfrom) { + return false; + } + + STUDIP.ActivityFeed.polling = true; + + STUDIP.api.GET(['user', STUDIP.ActivityFeed.user_id, 'activitystream'], { + data: { + filtertype: filtertype, + scrollfrom: STUDIP.ActivityFeed.scrolledfrom + } + }).done(function (activities) { + var stream = STUDIP.ActivityFeed.getTemplate('activity_stream'); + var activity = STUDIP.ActivityFeed.getTemplate('activity'); + var activity_urls = STUDIP.ActivityFeed.getTemplate('activity-urls'); + var num_entries = Object.keys(activities).length; + var lastelem = $(activities).last(); + + if (lastelem[0]) { + STUDIP.ActivityFeed.scrolledfrom = lastelem[0].mkdate; + } else { + STUDIP.ActivityFeed.scrolledfrom = false; + } + + STUDIP.ActivityFeed.writeToStream(stream({ + stream : activities, + num_entries : num_entries, + activity : activity, + activity_urls : activity_urls, + user_id : STUDIP.ActivityFeed.user_id + })); + + STUDIP.ActivityFeed.setToggleStatus(); + + if ($('#stream-container').height() < STUDIP.ActivityFeed.maxheight) { + STUDIP.ActivityFeed.loadFeed(''); + } + }).fail(function () { + var template = STUDIP.ActivityFeed.getTemplate('activity-load-error'); + STUDIP.ActivityFeed.writeToStream(template()); + }).always(function () { + STUDIP.ActivityFeed.polling = false; + }); + }, + + writeToStream: function (html) { + if (STUDIP.ActivityFeed.initial) { + // replace data in DOM + $('#stream-container').html(''); + + STUDIP.ActivityFeed.initial = false; + } + + $('#stream-container').append(html); + }, + + setToggleStatus: function() { + var show_details = $('#toggle-all-activities').is('.toggled'), + show_own = $('#toggle-user-activities').is('.toggled'); + + // update toggle status fir activity contents + $('.activity-content').toggle(show_details); + + // update toggle status for user's own activities + $('.activity:has(.provider_circle.right)').toggle(show_own); + }, + + updateFilter: function(filter) { + STUDIP.ActivityFeed.filter = filter; + STUDIP.ActivityFeed.initial = true; + STUDIP.ActivityFeed.scrolledfrom = Math.floor(Date.now() / 1000); + + $('#stream-container').html('
' + + '' + + '' + + '' + + '
'); + + STUDIP.ActivityFeed.init(); + } +}; + +export default ActivityFeed; diff --git a/resources/assets/javascripts/lib/forum.js b/resources/assets/javascripts/lib/forum.js index 9a6ff17..7763527 100644 --- a/resources/assets/javascripts/lib/forum.js +++ b/resources/assets/javascripts/lib/forum.js @@ -30,7 +30,7 @@ const Forum = { jQuery.ajax({ type: 'POST', - url: STUDIP.URLHelper.getURL('plugins.php/coreforum/index/savecats?cid=' + STUDIP.Forum.seminar_id), + url: STUDIP.URLHelper.getURL('dispatch.php/course/forum/index/savecats?cid=' + STUDIP.Forum.seminar_id), data: categories }); } @@ -69,7 +69,7 @@ const Forum = { STUDIP.Forum.closeDialog(); // ajax call to make the deletion permanent - jQuery.ajax(STUDIP.URLHelper.getURL('plugins.php/coreforum/index/delete_entry/' + jQuery.ajax(STUDIP.URLHelper.getURL('dispatch.php/course/forum/index/delete_entry/' + STUDIP.Forum.current_area_id + '?cid=' + STUDIP.Forum.seminar_id), { method: 'post', data: {'security_token' : STUDIP.CSRF_TOKEN.value}, @@ -92,7 +92,7 @@ const Forum = { }); // ajax call to make the deletion permanent - jQuery.ajax(STUDIP.URLHelper.getURL('plugins.php/coreforum/index/remove_category/' + jQuery.ajax(STUDIP.URLHelper.getURL('dispatch.php/course/forum/index/remove_category/' + STUDIP.Forum.current_category_id + '?cid=' + STUDIP.Forum.seminar_id), { method: 'post', data: {'security_token' : STUDIP.CSRF_TOKEN.value}, @@ -150,7 +150,7 @@ const Forum = { jQuery('table[data-category-id=' + category_id + '] span.edit_category').remove(); jQuery('table[data-category-id=' + category_id + '] span.category_name').show(); - jQuery.ajax(STUDIP.URLHelper.getURL('plugins.php/coreforum/index/edit_category/' + category_id + '?cid=' + STUDIP.Forum.seminar_id), { + jQuery.ajax(STUDIP.URLHelper.getURL('dispatch.php/course/forum/index/edit_category/' + category_id + '?cid=' + STUDIP.Forum.seminar_id), { type: 'POST', data: name }); @@ -173,7 +173,7 @@ const Forum = { jQuery.ajax({ type: 'POST', - url: STUDIP.URLHelper.getURL('plugins.php/coreforum/area/save_order?cid=' + STUDIP.Forum.seminar_id), + url: STUDIP.URLHelper.getURL('dispatch.php/course/forum/area/save_order?cid=' + STUDIP.Forum.seminar_id), data: areas }); }, @@ -209,7 +209,7 @@ const Forum = { // disable submit and cancel buttons, there is no turning back now $('.button', this).prop('disabled', true); - jQuery.ajax(STUDIP.URLHelper.getURL('plugins.php/coreforum/area/add/' + values.category_id + '?cid=' + STUDIP.Forum.seminar_id), { + jQuery.ajax(STUDIP.URLHelper.getURL('dispatch.php/course/forum/area/add/' + values.category_id + '?cid=' + STUDIP.Forum.seminar_id), { type: 'POST', data: values, success: function(data) { @@ -269,7 +269,7 @@ const Forum = { jQuery('tr[data-area-id=' + area_id + '] div.areacontent').data('content', name.content); // store the modified area and get formatted content-text from server - jQuery.ajax(STUDIP.URLHelper.getURL('plugins.php/coreforum/area/edit/' + area_id + '?cid=' + STUDIP.Forum.seminar_id), { + jQuery.ajax(STUDIP.URLHelper.getURL('dispatch.php/course/forum/area/edit/' + area_id + '?cid=' + STUDIP.Forum.seminar_id), { type: 'POST', data: name, success: function(data) { @@ -315,7 +315,7 @@ const Forum = { // remember current textarea value textarea.data('reset', textarea.val()); - jQuery.ajax(STUDIP.URLHelper.getURL('plugins.php/coreforum/index/update_entry/' + topic_id + '?cid=' + STUDIP.Forum.seminar_id), { + jQuery.ajax(STUDIP.URLHelper.getURL('dispatch.php/course/forum/index/update_entry/' + topic_id + '?cid=' + STUDIP.Forum.seminar_id), { type: 'POST', data: jQuery('form[data-topicid='+ topic_id +']').serializeObject(), @@ -489,7 +489,7 @@ const Forum = { + nl + nl + $gettext('Link zum Beitrag: ') + nl - + STUDIP.URLHelper.getURL('plugins.php/coreforum/index/index/' + + STUDIP.URLHelper.getURL('dispatch.php/course/forum/index/index/' + topic_id + '?cid=' + STUDIP.Forum.seminar_id + '&again=yes#' + topic_id) + nl + nl + content @@ -543,7 +543,7 @@ const Forum = { posting.posting = STUDIP.wysiwyg.markAsHtml(posting.posting); } - jQuery.ajax(STUDIP.URLHelper.getURL('plugins.php/coreforum/index/preview?cid=' + STUDIP.Forum.seminar_id), { + jQuery.ajax(STUDIP.URLHelper.getURL('dispatch.php/course/forum/index/preview?cid=' + STUDIP.Forum.seminar_id), { type: 'POST', data: posting, success: function (html) { @@ -556,7 +556,7 @@ const Forum = { }, loadAction: function(element, action) { - jQuery(element).load(STUDIP.URLHelper.getURL('plugins.php/coreforum/index/' + jQuery(element).load(STUDIP.URLHelper.getURL('dispatch.php/course/forum/index/' + action + '?cid=' + STUDIP.Forum.seminar_id)) }, @@ -578,14 +578,14 @@ const Forum = { }, setFavorite: function(topic_id) { - jQuery('#favorite_' + topic_id).load(STUDIP.URLHelper.getURL('plugins.php/coreforum/index/set_favorite/' + jQuery('#favorite_' + topic_id).load(STUDIP.URLHelper.getURL('dispatch.php/course/forum/index/set_favorite/' + topic_id + '?cid=' + STUDIP.Forum.seminar_id)); jQuery('a.marked[data-topic-id=' + topic_id +']').show(); return false; }, unsetFavorite: function(topic_id) { - jQuery('#favorite_' + topic_id).load(STUDIP.URLHelper.getURL('plugins.php/coreforum/index/unset_favorite/' + jQuery('#favorite_' + topic_id).load(STUDIP.URLHelper.getURL('dispatch.php/course/forum/index/unset_favorite/' + topic_id + '?cid=' + STUDIP.Forum.seminar_id)); jQuery('a.marked[data-topic-id=' + topic_id +']').hide(); return false; @@ -601,7 +601,7 @@ const Forum = { // jQuery('li[data-id=' + topic_id + '] > a.tooltip2').showAjaxNotification(); // load children from server and show them - jQuery.ajax(STUDIP.URLHelper.getURL('plugins.php/coreforum/admin/childs/' + topic_id), { + jQuery.ajax(STUDIP.URLHelper.getURL('dispatch.php/course/forum/admin/childs/' + topic_id), { dataType: 'html', success: function(response) { jQuery('li[data-id=' + topic_id + ']').append(response); @@ -664,7 +664,7 @@ const Forum = { paste: function(topic_id) { // jQuery('li[data-id=' + topic_id + '] > a.tooltip2').showAjaxNotification(); - jQuery.ajax(STUDIP.URLHelper.getURL('plugins.php/coreforum/admin/move/' + topic_id), { + jQuery.ajax(STUDIP.URLHelper.getURL('dispatch.php/course/forum/admin/move/' + topic_id), { data : { 'topics' : STUDIP.Forum.clipboard }, @@ -742,7 +742,7 @@ const Forum = { openThread: function(topic_id, redirect, page, showSuccessMessage) { jQuery.ajax({ type: 'GET', - url: STUDIP.URLHelper.getURL('plugins.php/coreforum/index/open_thread/' + topic_id + '/' + redirect + '/' + page), + url: STUDIP.URLHelper.getURL('dispatch.php/course/forum/index/open_thread/' + topic_id + '/' + redirect + '/' + page), success: function(data) { if (showSuccessMessage == true) { jQuery('#message_area').html(data); @@ -786,7 +786,7 @@ const Forum = { jQuery.ajax({ type: 'GET', - url: STUDIP.URLHelper.getURL('plugins.php/coreforum/index/close_thread/' + topic_id + '/' + redirect + '/' + page), + url: STUDIP.URLHelper.getURL('dispatch.php/course/forum/index/close_thread/' + topic_id + '/' + redirect + '/' + page), success: function(data) { if (showSuccessMessage == true) { jQuery('#message_area').html(data); @@ -800,7 +800,7 @@ const Forum = { makeThreadStickyFromThread: function(topic_id) { jQuery.ajax({ type: 'GET', - url: STUDIP.URLHelper.getURL('plugins.php/coreforum/index/make_sticky/' + topic_id + '/' + topic_id + '/0'), + url: STUDIP.URLHelper.getURL('dispatch.php/course/forum/index/make_sticky/' + topic_id + '/' + topic_id + '/0'), success: function(data) { jQuery('#message_area').html(data); var linkText = $gettext('Hervorhebung aufheben'); @@ -815,7 +815,7 @@ const Forum = { makeThreadUnstickyFromThread: function(topic_id) { jQuery.ajax({ type: 'GET', - url: STUDIP.URLHelper.getURL('plugins.php/coreforum/index/make_unsticky/' + topic_id + '/' + topic_id + '/0'), + url: STUDIP.URLHelper.getURL('dispatch.php/course/forum/index/make_unsticky/' + topic_id + '/' + topic_id + '/0'), success: function(data) { jQuery('#message_area').html(data); var linkText = $gettext('Thema hervorheben'); diff --git a/resources/assets/javascripts/lib/quick_selection.js b/resources/assets/javascripts/lib/quick_selection.js new file mode 100644 index 0000000..7fd0399 --- /dev/null +++ b/resources/assets/javascripts/lib/quick_selection.js @@ -0,0 +1,7 @@ +const QuickSelection = { + update: function (html) { + jQuery('#quickSelectionWrap').replaceWith(html); + } +}; + +export default QuickSelection; diff --git a/resources/assets/stylesheets/less/activityfeed.less b/resources/assets/stylesheets/less/activityfeed.less new file mode 100644 index 0000000..75fe917 --- /dev/null +++ b/resources/assets/stylesheets/less/activityfeed.less @@ -0,0 +1,195 @@ +#stream-container { + padding: 5px; + margin: 5px; + max-height: 600px; + overflow-y: auto; + + .activity { + border: 1px solid #d0d7e3; + width: 95%; + margin: 30px auto; + + } + + .activity header { + color: #28497c; + padding: 5px; + display: flex; + position: relative; + flex-direction: column; + + .provider_circle { + border-radius: 50%; + width: 42px; + height: 42px; + background: @base-color; + position: absolute; + top: -20px; + border: 3px solid white; + cursor: pointer; + + &.left { + left: -15px; + } + + &.right { + right: -15px; + } + + img { + padding: 12px; + .square(18px); + } + } + + .activity-heading { + padding: 5px; + margin: 0 0 0 25px; + flex-grow: 1; + img { + display: inline; + } + h3 { + display: inline; + color: #28497c; + border-bottom: none; + font-size: 14px; + vertical-align: 6px; + + } + } + .activity-avatar { + max-width: 25px; + } + .activity-date{ + color: gray; + font-size: 0.75em ; + margin: 0 0 0 30px; + display: block; + flex-grow: 1; + + } + } + + .activity-content{ + padding: 5px; + background-color: white; + margin-left: 30px; + display: none; + } + + .clear { + clear: both; + } + + .activity-details { + display: block; + } + + + footer { + padding: 5px; + display: flex; + flex-direction: row; + flex-wrap: nowrap; + justify-content: space-between; + align-content: space-around; + align-items: center; + margin-left: 30px; + + .activity-object-link { + order: 0; + flex: 0 1 auto; + align-self: auto; + } + } + + + .activity-object-link ul { + padding: 0px; + } + + .activity-object-link ul li { + list-style-type: none; + display: inline; + padding-right: 15px; + } + + + .activity-day { + color: gray; + font-size: 0.75em ; + margin: 0 45%; + } + + /* Loading animation */ + .loading-indicator { + text-align: center; + padding: 1em 0; + } + + .loading-indicator span { + background-color: #CCCCDD; + border-radius: 50%; + height: 10px; + position: relative; + width: 10px; + display: inline-block; + } + + .loading-indicator span.load-1 { + animation: loading-animation-1 1s linear 20; + } + + .loading-indicator span.load-2 { + animation: loading-animation-2 1s linear 20; + } + + .loading-indicator span.load-3 { + animation: loading-animation-3 1s linear 20; + } +} + +@keyframes loading-animation-1 { + 0% { transform: scale(1); } + 16% { transform: scale(1.3); } + 33% { transform: scale(1); } + 100% { transform: scale(1); } +} + +@keyframes loading-animation-2 { + 0% { transform: scale(1); } + 33% { transform: scale(1); } + 49% { transform: scale(1.3); } + 65% { transform: scale(1); } + 100% { transform: scale(1); } +} + +@keyframes loading-animation-3 { + 0% { transform: scale(1); } + 66% { transform: scale(1); } + 81% { transform: scale(1.3); } + 100% { transform: scale(1); } +} + +// Mixin that replaces the navigation icon tags with css images +.navigation-icon(@default-icon-name, @toggled-icon-name) { + img { display: none; } + + display: inline-block; + + .background-icon(@default-icon-name, 'clickable'); + .hide-text(); + .square(16px); + + &.toggled { + .background-icon(@toggled-icon-name, 'clickable'); + } +} + +#toggle-all-activities { + .navigation-icon('no-activity', 'activity'); +} +#toggle-user-activities { + .navigation-icon('visibility-invisible/headache', 'visibility-visible/headache'); +} diff --git a/resources/assets/stylesheets/studip.less b/resources/assets/stylesheets/studip.less index a88e730..03f3f29 100644 --- a/resources/assets/stylesheets/studip.less +++ b/resources/assets/stylesheets/studip.less @@ -66,6 +66,7 @@ @import "less/profile.less"; @import "less/consultation.less"; +@import "less/activityfeed.less"; @import "less/mobile.less"; @import "less/pagination.less"; @import "less/enrolment.less"; diff --git a/templates/mail/forum_notification.php b/templates/mail/forum_notification.php new file mode 100644 index 0000000..dc44649 --- /dev/null +++ b/templates/mail/forum_notification.php @@ -0,0 +1,31 @@ + sprintf( + _('Im Forum der Veranstaltung **%s** gibt es einen neuen Beitrag unter **%s** von **%s**'), + $seminar['name'], + implode(' > ', array_map(function ($p) { return $p['name']; }, $path)), + $topic['anonymous'] ? _('Anonym') : $topic['author'] + ), + 'title' => $topic['name'] ? '**' . $topic['name'] ."** \n\n" : '', + 'content' => $topic['content'], + 'url' => _('Beitrag im Forum ansehen:') .' '. URLHelper::getURL( + $GLOBALS['ABSOLUTE_URI_STUDIP'] + . 'dispatch.php/course/forum/index/index/' + . $topic['topic_id'] + .'?cid=' + . $topic['seminar_id'] + .'&again=yes#' + . $topic['topic_id'] + ) +]; + +// since we've possibly got a mixup of HTML and Stud.IP markup, +// create a pure HTML message step by step +$htmlMessage = Studip\Markup::markAsHtml( + implode('

', array_map('formatReady', $message)) +); + +echo $htmlMessage; diff --git a/templates/online/user.php b/templates/online/user.php index 82d8213..931aaab 100644 --- a/templates/online/user.php +++ b/templates/online/user.php @@ -35,7 +35,7 @@ - + _("Blubber diesen Nutzer an"), 'class' => 'text-bottom'])->asImg() ?> diff --git a/templates/start/_jstemplates.php b/templates/start/_jstemplates.php new file mode 100644 index 0000000..7bccfe3 --- /dev/null +++ b/templates/start/_jstemplates.php @@ -0,0 +1,100 @@ + + + + + + + diff --git a/templates/start/activityfeed.php b/templates/start/activityfeed.php new file mode 100644 index 0000000..6d86cf1 --- /dev/null +++ b/templates/start/activityfeed.php @@ -0,0 +1,19 @@ +
+ +
+
+ + + +
+
+ +render_partial('start/_jstemplates'); ?> +
diff --git a/templates/start/contents.php b/templates/start/contents.php new file mode 100644 index 0000000..b03df2a --- /dev/null +++ b/templates/start/contents.php @@ -0,0 +1,28 @@ + diff --git a/templates/start/quickselection.php b/templates/start/quickselection.php new file mode 100644 index 0000000..e465d8d --- /dev/null +++ b/templates/start/quickselection.php @@ -0,0 +1,28 @@ +
+ + isVisible()) : ?> + + + +
diff --git a/tests/jsonapi/ForumEntriesShowTest.php b/tests/jsonapi/ForumEntriesShowTest.php index 2443fa6..e823842 100644 --- a/tests/jsonapi/ForumEntriesShowTest.php +++ b/tests/jsonapi/ForumEntriesShowTest.php @@ -1,6 +1,5 @@