From b1bc66f361a4dda92aba22fdd42843f619797a6c Mon Sep 17 00:00:00 2001 From: Jan-Hendrik Willms Date: Thu, 7 Aug 2025 10:13:09 +0200 Subject: implement performance optimizations for my courses, fixes #4693 Closes #4693 Merge request studip/studip!3724 --- app/controllers/public_courses.php | 4 - lib/classes/ForumEntry.php | 0 lib/classes/ForumVisit.php | 0 lib/classes/MyRealmModel.php | 115 ++++++++++++++++--- lib/models/Forum/Posting.php | 38 ++++--- lib/modules/Blubber.php | 131 ++++++++++++--------- lib/modules/ConsultationModule.php | 15 ++- lib/modules/CoreAdmin.php | 14 +-- lib/modules/CoreCalendar.php | 21 ++-- lib/modules/CoreDocuments.php | 71 +++++++----- lib/modules/CoreForum.php | 55 +++++---- lib/modules/CoreOverview.php | 60 +++++++++- lib/modules/CoreParticipants.php | 177 ++++++++++++++++------------- lib/modules/CorePersonal.php | 8 +- lib/modules/CoreSchedule.php | 95 ++++++++-------- lib/modules/CoreScm.php | 102 ++++++++--------- lib/modules/CoreStudygroupAdmin.php | 20 ++-- lib/modules/CoreStudygroupParticipants.php | 40 +++++-- lib/modules/CoreWiki.php | 130 +++++++++------------ lib/modules/CoursewareModule.php | 78 +++++++------ lib/modules/FeedbackModule.php | 11 +- lib/modules/GradebookModule.php | 81 +++++++------ lib/modules/IconNavigationTrait.php | 19 ++++ lib/modules/IliasInterfaceModule.php | 116 ++++++++++--------- lib/modules/StudipModuleExtended.php | 27 +++++ 25 files changed, 851 insertions(+), 577 deletions(-) create mode 100644 lib/classes/ForumEntry.php create mode 100644 lib/classes/ForumVisit.php create mode 100644 lib/modules/IconNavigationTrait.php create mode 100644 lib/modules/StudipModuleExtended.php diff --git a/app/controllers/public_courses.php b/app/controllers/public_courses.php index 3ab5125..aa5d22d 100644 --- a/app/controllers/public_courses.php +++ b/app/controllers/public_courses.php @@ -85,10 +85,6 @@ class PublicCoursesController extends AuthenticatedController continue; } - if (!Config::get()->VOTE_ENABLE && $plugin_id === 'vote') { - continue; - } - if ($plugin === 'vote') { $seminar['navigations'][$plugin] = false; } else if ($tool = $seminar['tools']->findOneBy('plugin_id', $plugin_id)) { diff --git a/lib/classes/ForumEntry.php b/lib/classes/ForumEntry.php new file mode 100644 index 0000000..e69de29 diff --git a/lib/classes/ForumVisit.php b/lib/classes/ForumVisit.php new file mode 100644 index 0000000..e69de29 diff --git a/lib/classes/MyRealmModel.php b/lib/classes/MyRealmModel.php index 283ff75..138451e 100644 --- a/lib/classes/MyRealmModel.php +++ b/lib/classes/MyRealmModel.php @@ -297,8 +297,6 @@ class MyRealmModel $_course['sem_class'] = $course->getSemClass(); $_course['obj_type'] = 'sem'; - $visits = get_objects_visits([$course->id], 0, null, null, $course->tools->pluck('plugin_id')); - if ($group_field === 'sem_tree_id') { $_course['sem_tree'] = $course->study_areas->toArray(); } @@ -320,8 +318,6 @@ class MyRealmModel }); } - $_course['last_visitdate'] = $visits[$course->id][0]['last_visitdate']; - $_course['visitdate'] = $visits[$course->id][0]['visitdate']; $_course['user_status'] = $user_status; $_course['gruppe'] = !$is_deputy ? $member_ships[$course->id]['gruppe'] ?? null : ($deputy ? $deputy->gruppe : null); $_course['sem_number_end'] = $course->isOpenEnded() ? $max_sem_key : Semester::getIndexById($course->end_semester->id); @@ -336,15 +332,8 @@ class MyRealmModel } $_course['parent_course'] = $course->parent_course ?? null; $_course['is_group'] = $course->getSemClass()->isGroup(); - $_course['navigation'] = self::getAdditionalNavigations( - $_course['seminar_id'], - $_course, - $_course['sem_class'], - $GLOBALS['user']->id, - $visits[$course->id] - ); - // add the the course to the correct semester + // add the course to the correct semester if (empty($_course['parent_course']) && !$course->isStudygroup()) { if ($course->isOpenEnded()) { @@ -380,6 +369,18 @@ class MyRealmModel $sem_courses[$semester_assign[$parent]][$parent]['children'] = $kids; } + $navs = self::getManyAdditionalNavigations( + $sem_courses, + User::findCurrent()->id + ); + foreach ($sem_courses as &$courses) { + foreach ($courses as $c_id => &$course) { + if (!empty($navs[$c_id])) { + $course = array_merge($course, $navs[$c_id]); + } + } + } + if (!empty($params['main_navigation'])) { return $sem_courses; } @@ -449,10 +450,6 @@ class MyRealmModel continue; } - if (!Config::get()->VOTE_ENABLE && $plugin_id === 'vote') { - continue; - } - if ($plugin === 'vote') { $navigation[$plugin_id] = self::checkVote($my_obj_values, $user_id, $object_id); } else if ($tool = $my_obj_values['tools']->findOneBy('plugin_id', $plugin_id)) { @@ -480,6 +477,88 @@ class MyRealmModel return $navigation; } + private static function getManyAdditionalNavigations(array $sem_courses, $user_id): array + { + // -- 0. Extract all courses: semester is irrelevant and flatten for children + $all_courses = []; + foreach ($sem_courses as $courses) { + foreach ($courses as $c_id => $course) { + $all_courses[$c_id] = $course; + if (!empty($course['children'])) { + foreach ($course['children'] as $child_course) { + $all_courses[$child_course['seminar_id']] = $child_course; + } + } + } + } + + // -- 1. Calculate all the relevant courses for each StudipModule + $navigation = []; + $activated_tools = []; + $default_modules = self::getDefaultModules(); + foreach ($all_courses as $course_id => $course) { + // add every default module with null, so there will be blank spaces in the nav + $navigation[$course_id] = array_fill_keys( + array_keys($default_modules), + null + ); + + foreach ($course['tools'] as $tool) { + $studip_module = $tool->getStudipModule(); + if ( + !$studip_module + || $studip_module instanceof CoreAdmin + || $studip_module instanceof CoreStudygroupAdmin + ) { + continue; + } + if (Seminar_Perm::get()->have_studip_perm($tool->getVisibilityPermission(), $course_id, $user_id)) { + $activated_tools[$tool['plugin_id']]['studip_module'] = $studip_module; + $activated_tools[$tool['plugin_id']]['courses'][$course_id] = $course_id; + } + } + } + // -- 2. Fetch the Navigation per StudipModule + $all_course_ids = array_keys($all_courses); + $visits = get_objects_visits($all_course_ids, 0, null, null, array_keys($activated_tools)); + foreach ($activated_tools as $plugin_id => $plugin_data) { + $c_ids = $plugin_data['courses']; + if ($c_ids) { + if ($plugin_id === -1) { + foreach ($c_ids as $c_id) { + // TODO testing vote need to be done + $navigation[$c_id][$plugin_id] = self::checkVote($all_courses[$c_id], $user_id, $c_id); + } + } elseif ($plugin_data['studip_module'] instanceof StudipModuleExtended) { + $fetched_navs = $plugin_data['studip_module']->getManyIconNavigation($c_ids, $user_id); + foreach ($fetched_navs as $fetched_c_id => $fetched_nav) { + $navigation[$fetched_c_id][$plugin_id] = $fetched_nav; + } + } else { + foreach ($c_ids as $c_id) { + $navigation[$c_id][$plugin_id] = $plugin_data['studip_module']->getIconNavigation( + $c_id, + $visits[$c_id][$plugin_id]['visitdate'], + $user_id + ); + } + } + } + } + + // -- 3. Set each nav and visitdate by course + $result = []; + foreach ($navigation as $cid => $nav) { + $result[$cid] = [ + 'navigation' => $nav, + 'visitdate' => $visits[$cid][0]['visitdate'] ?? null, + 'last_visitdate' => $visits[$cid][0]['last_visitdate'] ?? null, + ]; + } + return $result; + } + + /** * This function reset all visits on every available modules * @param $object @@ -910,7 +989,9 @@ class MyRealmModel } $default_modules[$id] = $plugin; } - $default_modules[-1] = 'vote'; + if (Config::get()->VOTE_ENABLE) { + $default_modules[-1] = 'vote'; + } return $default_modules; } } diff --git a/lib/models/Forum/Posting.php b/lib/models/Forum/Posting.php index b0ae0e8..f8fb579 100644 --- a/lib/models/Forum/Posting.php +++ b/lib/models/Forum/Posting.php @@ -73,32 +73,38 @@ class Posting extends SimpleORMap return null; } - public static function getRecentPosts($range_id, int $last_visit = 0): array + public static function getRecentPosts(array|string $range_ids): array { - $query = [ + $single = is_string($range_ids); + if ($single) { + $range_ids = [$range_ids]; + } + $query = "SELECT + forum_topics.range_id, forum_discussions.*, COUNT(DISTINCT forum_postings.posting_id) AS 'posts' FROM forum_topics JOIN forum_discussions USING(topic_id) JOIN forum_postings USING(discussion_id) - WHERE forum_topics.range_id = :range_id AND forum_postings.user_id != :user_id - ", - [ - 'range_id' => $range_id, - 'user_id' => User::findCurrent()->user_id - ] + LEFT JOIN forum_posting_reads AS fp_reads + ON fp_reads.discussion_id = forum_discussions.discussion_id + AND fp_reads.user_id = :user_id + WHERE forum_topics.range_id IN (:range_ids) + AND forum_postings.user_id != :user_id + AND forum_postings.mkdate > IFNULL(fp_reads.read_index, 0) + GROUP BY forum_topics.range_id, forum_discussions.discussion_id"; + $params = [ + ':range_ids' => $range_ids, + ':user_id' => User::findCurrent()->id, ]; - if ($last_visit) { - $query[0] .= " AND forum_postings.mkdate > :last_visit"; - $query[1]["last_visit"] = $last_visit; + $res = \DBManager::get()->fetchAll($query, $params); + $by_course = []; + foreach ($res as $row) { + $by_course[$row['range_id']][] = $row; } - - return \DBManager::get()->fetchAll( - $query[0]." GROUP BY discussion_id ORDER BY forum_postings.mkdate DESC", - $query[1] - ); + return $single ? (array_pop($by_course) ?? []) : $by_course; } public function getOpenGraphURLs(): array diff --git a/lib/modules/Blubber.php b/lib/modules/Blubber.php index c40c090..ecddda5 100644 --- a/lib/modules/Blubber.php +++ b/lib/modules/Blubber.php @@ -12,8 +12,10 @@ * Class Blubber - the Blubber-plugin * This is only used to manage blubber within a course. */ -class Blubber extends CorePlugin implements StudipModule +class Blubber extends CorePlugin implements StudipModuleExtended { + use IconNavigationTrait; + /** * Returns a navigation for the tab displayed in the course. * @param string $course_id of the course @@ -29,76 +31,93 @@ class Blubber extends CorePlugin implements StudipModule 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) + public function getManyIconNavigation(array $course_ids, ?string $user_id = null): array { - $user_id || $user_id = $GLOBALS['user']->id; - $icon = new Navigation( - _('Blubber'), - 'dispatch.php/course/messenger/course' - ); - $icon->setImage(Icon::create('blubber')); - $icon->setLinkAttributes(['title' => _('Blubber-Messenger')]); + $user_id = $user_id ?? User::findCurrent()->id; + $threshold = object_get_visit_threshold(); - $condition = "INNER JOIN blubber_threads USING (thread_id) + // check if there are comments newer than the last visit of blubber + $condition = "JOIN blubber_threads USING (thread_id) + LEFT JOIN object_user_visits AS ouv + ON ouv.object_id = blubber_threads.context_id + AND ouv.user_id = :me + AND ouv.plugin_id = :plugin_id WHERE blubber_threads.context_type = 'course' - AND blubber_threads.context_id = :course_id - AND blubber_comments.mkdate >= :last_visit + AND blubber_threads.context_id IN (:course_ids) + AND blubber_comments.mkdate >= IF(ouv.visitdate > :threshold, ouv.visitdate, :threshold) 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() > $comment->thread->getLastVisit() - ) { - $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; + AND blubber_threads.visible_in_stream = 1"; + $params = [ + ':course_ids' => $course_ids, + ':threshold' => $threshold, + ':me' => $user_id, + ':plugin_id' => 0, // module doesnt write directly into ouv + ]; + $threads = []; + BlubberComment::findEachBySQL( + function ($comment) use (&$threads) { + $threads[$comment->thread_id][] = $comment; + }, + $condition, + $params + ); + + $navs = []; + foreach ($threads as $comments) { + $thread = $comments[0]->thread; + if (isset($navs[$thread->context_id])) { + continue; + } + // check if there are comments that are newer thant the last visit of the blubber thread(!) + if ($thread->isReadable() && $thread->getLatestActivity() > $thread->getLastVisit()) { + $nav = new Navigation(_('Blubber'), 'dispatch.php/course/messenger/course', ['thread' => 'new']); + $nav->setImage(Icon::create('blubber', Icon::ROLE_ATTENTION)); + $nav->setLinkAttributes(['title' => _('Es gibt neue Blubber')]); + $nav->setBadgeNumber(count($comments)); + $navs[$thread->context_id] = $nav; } } - $condition = "context_type = 'course' - AND context_id = :course_id - AND mkdate >= :last_visit - AND user_id != :me - AND visible_in_stream = 1 + // Check for the remaining Courses, if new threads were created + $remaining_courses = array_diff($course_ids, array_keys($navs)); + $condition = "LEFT JOIN object_user_visits AS ouv + ON ouv.object_id = blubber_threads.context_id + AND ouv.user_id = :me + AND ouv.plugin_id = :plugin_id + WHERE blubber_threads.context_type = 'course' + AND blubber_threads.context_id IN (:course_ids) + AND blubber_threads.mkdate >= IF(ouv.visitdate > :threshold, ouv.visitdate, :threshold) + AND blubber_threads.user_id != :me + AND blubber_threads.visible_in_stream = 1 AND ( blubber_threads.display_class IS NOT NULL - OR blubber_threads.`content` 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, + ':course_ids' => $remaining_courses, + ':threshold' => $threshold, + ':me' => $user_id, + ':plugin_id' => 0, // module doesnt write directly into ouv ]); foreach ($threads as $thread) { - if ( - $thread->isVisibleInStream() - && $thread->isReadable() - && $thread->mkdate > $thread->getLastVisit() - ) { - $icon->setImage(Icon::create('blubber', Icon::ROLE_ATTENTION, ['title' => _('Es gibt neue Blubber')])); - $icon->setTitle(_('Es gibt neue Blubber')); - break; + if ($thread->isReadable()) { + $nav = new Navigation(_('Blubber'), 'dispatch.php/course/messenger/course'); + $nav->setImage(Icon::create('blubber', Icon::ROLE_ATTENTION)); + $nav->setLinkAttributes(['title' => _('Es gibt neue Blubber')]); + $nav->setTitle(_('Es gibt neue Blubber')); + $navs[$thread->context_id] = $nav; + } + } + + $default_navigation = new Navigation(_('Blubber'), 'dispatch.php/course/messenger/course'); + $default_navigation->setImage(Icon::create('blubber')); + $default_navigation->setLinkAttributes(['title' => _('Blubber-Messenger')]); + foreach ($course_ids as $course_id) { + if (!isset($navs[$course_id])) { + $navs[$course_id] = $default_navigation; } } - return $icon; + return $navs; } /** diff --git a/lib/modules/ConsultationModule.php b/lib/modules/ConsultationModule.php index 1de4fa3..03ee93f 100644 --- a/lib/modules/ConsultationModule.php +++ b/lib/modules/ConsultationModule.php @@ -3,8 +3,14 @@ * @author Jan-Hendrik Willms * @license GPL2 or any later version */ -class ConsultationModule extends CorePlugin implements StudipModule, SystemPlugin, PrivacyPlugin, HomepagePlugin +class ConsultationModule extends CorePlugin implements + StudipModuleExtended, + SystemPlugin, + PrivacyPlugin, + HomepagePlugin { + use IconNavigationTrait; + public function __construct() { parent::__construct(); @@ -88,14 +94,11 @@ class ConsultationModule extends CorePlugin implements StudipModule, SystemPlugi return true; } - /** - * {@inheritdoc} - */ - public function getIconNavigation($course_id, $last_visit, $user_id) + public function getManyIconNavigation(array $course_ids, ?string $user_id = null): array { // TODO - return null; + return []; } /** diff --git a/lib/modules/CoreAdmin.php b/lib/modules/CoreAdmin.php index aded3ae..bddf732 100644 --- a/lib/modules/CoreAdmin.php +++ b/lib/modules/CoreAdmin.php @@ -7,22 +7,18 @@ * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. */ -class CoreAdmin extends CorePlugin implements StudipModule +class CoreAdmin extends CorePlugin implements StudipModuleExtended { - /** - * {@inheritdoc} - */ - public function getIconNavigation($course_id, $last_visit, $user_id) + use IconNavigationTrait; + + public function getManyIconNavigation(array $course_ids, ?string $user_id = null): array { $navigation = new Navigation(_('Verwaltung'), 'dispatch.php/course/management'); $navigation->setImage(Icon::create('admin')); $navigation->setLinkAttributes(['title' => _('Verwaltung')]); - return $navigation; + return array_fill_keys($course_ids, $navigation); } - /** - * {@inheritdoc} - */ public function getTabNavigation($course_id) { $range = RangeFactory::find($course_id); diff --git a/lib/modules/CoreCalendar.php b/lib/modules/CoreCalendar.php index bc20bdb..fbfc36e 100644 --- a/lib/modules/CoreCalendar.php +++ b/lib/modules/CoreCalendar.php @@ -9,20 +9,23 @@ * the License, or (at your option) any later version. */ -class CoreCalendar extends CorePlugin implements StudipModule +class CoreCalendar extends CorePlugin implements StudipModuleExtended { - /** - * {@inheritdoc} - */ - public function getIconNavigation($course_id, $last_visit, $user_id) + use IconNavigationTrait; + + public function getManyIconNavigation(array $course_ids, ?string $user_id = null): array { if (!Config::get()->CALENDAR_GROUP_ENABLE) { - return null; + return []; } - $navigation = new Navigation(_('Kalender'), URLHelper::getURL('dispatch.php/calendar/calendar/course/' . $course_id)); - $navigation->setImage(Icon::create('schedule')); - return $navigation; + $navs = []; + foreach ($course_ids as $course_id) { + $navigation = new Navigation(_('Kalender'), URLHelper::getURL('dispatch.php/calendar/calendar/course/' . $course_id)); + $navigation->setImage(Icon::create('schedule')); + $navs[$course_id] = $navigation; + } + return $navs; } /** diff --git a/lib/modules/CoreDocuments.php b/lib/modules/CoreDocuments.php index 5eb352c..6d2dea2 100644 --- a/lib/modules/CoreDocuments.php +++ b/lib/modules/CoreDocuments.php @@ -9,8 +9,9 @@ * the License, or (at your option) any later version. */ -class CoreDocuments extends CorePlugin implements StudipModule, OERModule +class CoreDocuments extends CorePlugin implements StudipModuleExtended, OERModule { + use IconNavigationTrait; /** * {@inheritdoc} @@ -102,43 +103,51 @@ class CoreDocuments extends CorePlugin implements StudipModule, OERModule } } - - /** - * {@inheritdoc} - */ - public function getIconNavigation($course_id, $last_visit, $user_id) + public function getManyIconNavigation(array $course_ids, ?string $user_id = null): array { - $range_type = get_object_type($course_id, ['sem', 'inst']) === 'sem' ? 'course' : 'institute'; - $navigation = new Navigation( - _('Dateibereich'), - "dispatch.php/{$range_type}/files" - ); - $navigation->setImage(Icon::create('files')); - $navigation->setLinkAttributes(['title' => _('Dateien')]); - - $condition = "INNER JOIN folders ON (folders.id = file_refs.folder_id) + // Assume that either courses or institutes will be fetched, but not a mix of them + $c_ids_copy = array_reverse($course_ids); + $range_type = get_object_type(array_pop($c_ids_copy), ['sem', 'inst']) === 'sem' ? 'course' : 'institute'; + $condition = "SELECT folders.range_id, file_refs.id + FROM file_refs + JOIN folders ON (folders.id = file_refs.folder_id) + LEFT JOIN object_user_visits AS ouv + ON ouv.object_id = folders.range_id + AND ouv.user_id = :me + AND ouv.plugin_id = :plugin_id WHERE folders.range_type = :range_type - AND folders.range_id = :context_id - AND GREATEST(file_refs.mkdate, file_refs.chdate) >= :last_visit + AND folders.range_id IN (:context_ids) + AND file_refs.chdate >= IF(ouv.visitdate > :threshold, ouv.visitdate, :threshold) AND file_refs.user_id != :me"; - $file_refs = FileRef::findBySQL($condition, [ - 'me' => $user_id, - 'last_visit' => $last_visit, - 'context_id' => $course_id, - 'range_type' => $range_type + $file_refs_by_range = DBManager::get()->fetchGroupedPairs($condition, [ + ':me' => $user_id, + ':plugin_id' => $this->getPluginId(), + ':threshold' => object_get_visit_threshold(), + ':context_ids' => $course_ids, + ':range_type' => $range_type ]); - foreach ($file_refs as $fileref) { - $foldertype = $fileref->folder->getTypedFolder(); - if ($foldertype->isFileDownloadable($fileref->getId(), $user_id)) { - $navigation->setImage(Icon::create('files', Icon::ROLE_ATTENTION), [ - 'title' => _('Es gibt neue Dateien.'), - ]); - $navigation->setURL("dispatch.php/{$range_type}/files/flat", ['select' => 'new']); - break; + + $navs = []; + foreach ($file_refs_by_range as $range_id => $file_refs) { + $file_ref_objs = FileRef::findMany($file_refs); + foreach ($file_ref_objs as $file_ref_obj) { + $foldertype = $file_ref_obj->folder->getTypedFolder(); + $nav = new Navigation(_('Dateibereich'), "dispatch.php/{$range_type}/files"); + if ($foldertype->isFileDownloadable($file_ref_obj->getId(), $user_id)) { + $nav->setImage(Icon::create('files', Icon::ROLE_ATTENTION)); + $nav->setLinkAttributes(['title' => _('Es gibt neue Dateien.')]); + $nav->setURL("dispatch.php/{$range_type}/files/flat", ['select' => 'new']); + $navs[$range_id] = $nav; + break; + } } } - return $navigation; + $default_navigation = new Navigation(_('Dateibereich'), "dispatch.php/{$range_type}/files"); + $default_navigation->setImage(Icon::create('files')); + $default_navigation->setLinkAttributes(['title' => _('Dateien')]); + $remaining_courses = array_diff($course_ids, array_keys($navs)); + return array_merge($navs, array_fill_keys($remaining_courses, $default_navigation)); } /** diff --git a/lib/modules/CoreForum.php b/lib/modules/CoreForum.php index 838362b..a5b293a 100644 --- a/lib/modules/CoreForum.php +++ b/lib/modules/CoreForum.php @@ -10,8 +10,10 @@ use Forum\Posting; -class CoreForum extends CorePlugin implements StudipModule +class CoreForum extends CorePlugin implements StudipModuleExtended { + use IconNavigationTrait; + public function getTabNavigation($course_id) { $navigation = new Navigation(_('Forum'), 'dispatch.php/course/forum/topics'); @@ -43,36 +45,39 @@ class CoreForum extends CorePlugin implements StudipModule return ['forum' => $navigation]; } - public function getIconNavigation($course_id, $last_visit, $user_id) + public function getManyIconNavigation(array $course_ids, ?string $user_id = null): array { - $recent_posts_count = 0; - $navigation_title = _('Forum'); - - if ($GLOBALS['perm']->have_studip_perm('user', $course_id)) { - $recent_posts = Posting::getRecentPosts($course_id, $last_visit); - $recent_posts_count = array_sum(array_column($recent_posts, 'posts')); + $navs = []; + $posts = Posting::getRecentPosts($course_ids); + foreach ($course_ids as $course_id) { + $recent_posts_count = 0; + $navigation_title = _('Forum'); + + if ($GLOBALS['perm']->have_studip_perm('user', $course_id)) { + $recent_posts_count = !empty($posts[$course_id]) + ? array_sum(array_column($posts[$course_id], 'posts')) + : 0; + + if ($recent_posts_count > 0) { + $navigation_title = sprintf(_('%s neue Beiträge seit Ihrem letzten Besuch.'), $recent_posts_count); + } else { + $navigation_title = _('Keine neuen Beiträge seit Ihrem letzten Besuch.'); + } + } + $navigation = new Navigation(_('Forum')); + $navigation->setBadgeNumber($recent_posts_count); + $navigation->setLinkAttributes(['title' => $navigation_title]); if ($recent_posts_count > 0) { - $navigation_title = sprintf(_('%s neue Beiträge seit Ihrem letzten Besuch.'), $recent_posts_count); + $navigation->setImage(Icon::create('forum', Icon::ROLE_ATTENTION)); + $navigation->setURL('dispatch.php/course/forum/recent'); } else { - $navigation_title = _('Keine neuen Beiträge seit Ihrem letzten Besuch.'); + $navigation->setImage(Icon::create('forum')); + $navigation->setURL('dispatch.php/course/forum/topics'); } + $navs[$course_id] = $navigation; } - - $navigation = new Navigation(_("Forum")); - $navigation->setBadgeNumber($recent_posts_count); - - $navigation->setLinkAttributes(['title' => $navigation_title]); - - if ($recent_posts_count > 0) { - $navigation->setImage(Icon::create('forum', Icon::ROLE_ATTENTION)); - $navigation->setURL('dispatch.php/course/forum/recent', ['last_visit' => $last_visit]); - } else { - $navigation->setImage(Icon::create('forum')); - $navigation->setURL('dispatch.php/course/forum/topics'); - } - - return $navigation; + return $navs; } public function getInfoTemplate($course_id) diff --git a/lib/modules/CoreOverview.php b/lib/modules/CoreOverview.php index 7a73be7..5550ed3 100644 --- a/lib/modules/CoreOverview.php +++ b/lib/modules/CoreOverview.php @@ -9,7 +9,7 @@ * the License, or (at your option) any later version. */ -class CoreOverview extends CorePlugin implements StudipModule +class CoreOverview extends CorePlugin implements StudipModuleExtended { /** * {@inheritdoc} @@ -72,6 +72,64 @@ class CoreOverview extends CorePlugin implements StudipModule return $nav; } + public function getManyIconNavigation(array $course_ids, ?string $user_id = null): array + { + $sql = "SELECT news_r.range_id, + COUNT(news.news_id) AS count, + COUNT(IF((news.chdate > IFNULL(b.visitdate, :threshold) AND news.user_id !=:user_id), news.news_id, NULL)) AS neue + FROM news_range AS news_r + JOIN news + ON news_r.news_id = news.news_id + AND UNIX_TIMESTAMP() BETWEEN date AND date + expire + LEFT JOIN object_user_visits AS b + ON b.object_id = news_r.news_id + AND b.user_id = :user_id + AND b.plugin_id = :plugin_id + WHERE news_r.range_id IN (:course_ids) + GROUP BY news_r.range_id"; + $results = DBManager::get()->fetchAll($sql, [ + ':user_id' => $user_id, + ':course_ids' => $course_ids, + ':threshold' => object_get_visit_threshold(), + ':plugin_id' => $this->getPluginId(), + ]); + + $navs = []; + foreach ($results as $result) { + $nav = new Navigation(_('Ankündigungen'), ''); + if ($result['neue']) { + $nav->setURL('?new_news=true'); + $nav->setImage(Icon::create('news', Icon::ROLE_ATTENTION)); + $nav->setLinkAttributes([ + 'title' => sprintf( + ngettext( + '%1$d Ankündigung, %2$d neue', + '%1$d Ankündigungen, %2$d neue', + $result['count'] + ), + $result['count'], + $result['neue'] + ) + ]); + $nav->setBadgeNumber($result['neue']); + } elseif ($result['count']) { + $nav->setImage(Icon::create('news')); + $nav->setLinkAttributes([ + 'title' => sprintf( + ngettext( + '%d Ankündigung', + '%d Ankündigungen', + $result['count'] + ), + $result['count'] + ) + ]); + } + $navs[$result['range_id']] = $nav; + } + return $navs; + } + /** * {@inheritdoc} */ diff --git a/lib/modules/CoreParticipants.php b/lib/modules/CoreParticipants.php index d74d604..a208850 100644 --- a/lib/modules/CoreParticipants.php +++ b/lib/modules/CoreParticipants.php @@ -9,103 +9,126 @@ * the License, or (at your option) any later version. */ -class CoreParticipants extends CorePlugin implements StudipModule +class CoreParticipants extends CorePlugin implements StudipModuleExtended { - /** - * {@inheritdoc} - */ - public function getIconNavigation($course_id, $last_visit, $user_id) + use IconNavigationTrait; + + public function getManyIconNavigation(array $course_ids, ?string $user_id = null): array { + $navs = array_fill_keys($course_ids, null); if ($user_id === 'nobody') { - return null; + return $navs; } - $auto_insert_perm = Config::get()->AUTO_INSERT_SEM_PARTICIPANTS_VIEW_PERM; - // show the participants-icon only if the course is not an auto-insert-sem - if ( - AutoInsert::checkSeminar($course_id) - && ( - ($GLOBALS['perm']->have_perm('admin', $user_id) && !$GLOBALS['perm']->have_perm($auto_insert_perm, $user_id)) - || !$GLOBALS['perm']->have_studip_perm($auto_insert_perm, $course_id, $user_id) - ) - ) { - return null; + // Filter courses that are auto-insert-seminars, to not show any icon + $course_ids = array_filter($course_ids, function ($course_id) use ($user_id) { + $auto_insert_perm = Config::get()->AUTO_INSERT_SEM_PARTICIPANTS_VIEW_PERM; + $is_auto_insert = + AutoInsert::checkSeminar($course_id) + && ( + ($GLOBALS['perm']->have_perm('admin', $user_id) && !$GLOBALS['perm']->have_perm($auto_insert_perm, $user_id)) + || !$GLOBALS['perm']->have_studip_perm($auto_insert_perm, $course_id, $user_id) + ); + return !$is_auto_insert; + }); + + $courses = Course::findMany($course_ids); + $urls = []; + foreach ($courses as $course) { + $is_student = !$GLOBALS['perm']->have_studip_perm('tutor', $course->seminar_id, $user_id); + + // Determine url to redirect to + if (!$course->getSemClass()->isGroup()) { + $urls[$course->seminar_id] = 'dispatch.php/course/members/index'; + } elseif ($is_student) { + $navs[$course->seminar_id] = 0; + continue; + } else { + $urls[$course->seminar_id] = 'dispatch.php/course/grouping/members'; + } + + $navigation = new Navigation(_('Teilnehmende'), $urls[$course->seminar_id]); + $navigation->setImage(Icon::create('persons', Icon::ROLE_CLICKABLE)); + + // Check permission, show no indicator if not at least tutor + if ($is_student) { + $navs[$course->seminar_id] = $navigation; + } } - $course = Course::find($course_id); - - // Determine url to redirect to - if (!$course->getSemClass()->isGroup()) { - $url = 'dispatch.php/course/members/index'; - } elseif (!$GLOBALS['perm']->have_studip_perm('tutor', $course_id, $user_id)) { - return null; - } else { - $url = 'dispatch.php/course/grouping/members'; - } + // For the remaining courses, show if there are new users + $remaining_course_ids = array_filter( + $course_ids, + fn($c_id) => $navs[$c_id] === null + ); - $navigation = new Navigation(_('Teilnehmende'), $url); - $navigation->setImage(Icon::create('persons2')); - - // Check permission, show no indicator if not at least tutor - if (!$GLOBALS['perm']->have_studip_perm('tutor', $course_id, $user_id)) { - return $navigation; - } - - $query = "SELECT COUNT(tmp.user_id) as count, - COUNT(IF((tmp.mkdate > IFNULL(b.visitdate, :threshold) AND tmp.user_id != :user_id), tmp.user_id, NULL)) AS neue + $query = "SELECT seminar_users.seminar_id as seminar_id, + COUNT(seminar_users.user_id) as count, + COUNT(IF((seminar_users.mkdate > IFNULL(b.visitdate, :threshold) AND seminar_users.user_id != :user_id), seminar_users.user_id, NULL)) AS neue FROM ( - SELECT user_id, mkdate + SELECT user_id, seminar_id, mkdate FROM admission_seminar_user - WHERE seminar_id = :course_id + WHERE seminar_id IN (:course_ids) UNION ALL - SELECT user_id, mkdate + SELECT user_id, seminar_id, mkdate FROM seminar_user - WHERE seminar_id = :course_id - ) AS tmp + WHERE seminar_id IN (:course_ids) + ) AS seminar_users LEFT JOIN object_user_visits AS b - ON b.object_id = :course_id + ON b.object_id = seminar_users.seminar_id AND b.user_id = :user_id - AND b.plugin_id = :plugin_id"; - $statement = DBManager::get()->prepare($query); - $statement->bindValue(':user_id', $user_id); - $statement->bindValue(':course_id', $course_id); - $statement->bindValue(':threshold', $last_visit); - $statement->bindValue(':plugin_id', $this->getPluginId()); - - $statement->execute(); - $result = $statement->fetch(PDO::FETCH_ASSOC); - - if ($result['neue']) { - $navigation->setImage(Icon::create('persons', Icon::ROLE_ATTENTION, [ - 'title' => sprintf( - ngettext( - '%1$d Teilnehmende/r, %2$d neue/r', - '%1$d Teilnehmende, %2$d neue', - $result['count'] - ), - $result['count'], - $result['neue'] - ) - ])); - $navigation->setBadgeNumber($result['neue']); - } elseif ($result['count']) { - $navigation->setLinkAttributes([ - 'title' => sprintf( - ngettext( - '%d Teilnehmende/r', - '%d Teilnehmende', + AND b.plugin_id = :plugin_id + GROUP BY seminar_users.seminar_id"; + $users_per_course = DBManager::get()->fetchAll($query, [ + ':course_ids' => $remaining_course_ids, + ':user_id' => $user_id, + ':threshold' => object_get_visit_threshold(), + ':plugin_id' => $this->getPluginId(), + ]); + + foreach ($users_per_course as $result) { + $navigation = new Navigation(_('Teilnehmende'), $urls[$result['seminar_id']]); + + if ($result['neue']) { + $navigation->setImage(Icon::create('persons', Icon::ROLE_ATTENTION)); + $navigation->setLinkAttributes([ + 'title' => sprintf( + ngettext( + '%1$d Teilnehmende/r, %2$d neue/r', + '%1$d Teilnehmende, %2$d neue', + $result['count'] + ), + $result['count'], + $result['neue'] + ) + ]); + $navigation->setBadgeNumber($result['neue']); + } elseif ($result['count']) { + $navigation->setImage(Icon::create('persons')); + $navigation->setLinkAttributes([ + 'title' => sprintf( + ngettext( + '%d Teilnehmende/r', + '%d Teilnehmende', + $result['count'] + ), $result['count'] - ), - $result['count'] - ) - ]); - } + ) + ]); + } - return $navigation; + $navs[$result['seminar_id']] = $navigation; + } + // map the zeros to null; + return array_map( + fn ($nav) => $nav ?: null, + $navs + ); } + /** * {@inheritdoc} */ diff --git a/lib/modules/CorePersonal.php b/lib/modules/CorePersonal.php index 48aa5b2..9972d6f 100644 --- a/lib/modules/CorePersonal.php +++ b/lib/modules/CorePersonal.php @@ -8,11 +8,13 @@ * the License, or (at your option) any later version. */ -class CorePersonal extends CorePlugin implements StudipModule +class CorePersonal extends CorePlugin implements StudipModuleExtended { - public function getIconNavigation($course_id, $last_visit, $user_id) + use IconNavigationTrait; + + public function getManyIconNavigation(array $course_ids, ?string $user_id = null): array { - return null; + return []; } public function getTabNavigation($course_id) diff --git a/lib/modules/CoreSchedule.php b/lib/modules/CoreSchedule.php index 39b3e1f..d5a86af 100644 --- a/lib/modules/CoreSchedule.php +++ b/lib/modules/CoreSchedule.php @@ -9,61 +9,64 @@ * the License, or (at your option) any later version. */ -class CoreSchedule extends CorePlugin implements StudipModule +class CoreSchedule extends CorePlugin implements StudipModuleExtended { - /** - * {@inheritdoc} - */ - public function getIconNavigation($course_id, $last_visit, $user_id) + use IconNavigationTrait; + + public function getManyIconNavigation(array $course_ids, ?string $user_id = null): array { - $query = "SELECT COUNT(termin_id) AS count, - COUNT(IF((chdate > IFNULL(ouv.visitdate, :threshold) AND autor_id != :user_id), termin_id, NULL)) AS neue + $query = "SELECT termine.range_id, + COUNT(termin_id) AS count, + COUNT(IF((chdate > IFNULL(ouv.visitdate, :threshold) AND autor_id != :user_id), termin_id, NULL)) AS neue FROM termine LEFT JOIN object_user_visits AS ouv - ON ouv.object_id = range_id + ON ouv.object_id = termine.range_id AND ouv.user_id = :user_id AND ouv.plugin_id = :plugin_id - WHERE range_id = :course_id"; - $statement = DBManager::get()->prepare($query); - $statement->bindValue(':user_id', $user_id); - $statement->bindValue(':course_id', $course_id); - $statement->bindValue(':threshold', $last_visit); - $statement->bindValue(':plugin_id', $this->getPluginId()); - $statement->execute(); - $result = $statement->fetch(PDO::FETCH_ASSOC); + WHERE termine.range_id IN (:course_ids) + GROUP BY termine.range_id "; + $results = DBManager::get()->fetchAll($query, [ + ':user_id' => $user_id, + ':course_ids' => $course_ids, + ':threshold' => object_get_visit_threshold(), + ':plugin_id' => $this->getPluginId(), + ],PDO::FETCH_ASSOC); - if (!$result || (!$result['neue'] && !$result['count'])) { - return null; - } - - $nav = new Navigation(_('Ablaufplan'), 'dispatch.php/course/dates'); - if ($result['neue']) { - $nav->setImage(Icon::create('schedule', Icon::ROLE_ATTENTION, [ - 'title' => sprintf( - ngettext( - '%1$d Termin, %2$d neuer', - '%1$d Termine, %2$d neue', - $result['count'] - ), - $result['count'], - $result['neue'] - ) - ])); - $nav->setBadgeNumber($result['neue']); - } else { - $nav->setImage(Icon::create('schedule')); - $nav->setLinkAttributes([ - 'title' => sprintf( - ngettext( - '%d Termin', - '%d Termine', + $navs = array_fill_keys($course_ids, null); + foreach ($results as $result) { + $nav = new Navigation(_('Ablaufplan'), 'dispatch.php/course/dates'); + if ($result['neue']) { + $nav->setBadgeNumber($result['neue']); + $nav->setLinkAttributes([ + 'title' => sprintf( + ngettext( + '%1$d Termin, %2$d neuer', + '%1$d Termine, %2$d neue', + $result['count'] + ), + $result['count'], + $result['neue'] + ) + ]); + $nav->setImage(Icon::create('schedule', Icon::ROLE_ATTENTION)); + } else { + $nav->setLinkAttributes([ + 'title' => sprintf( + ngettext( + '%d Termin', + '%d Termine', + $result['count'] + ), $result['count'] - ), - $result['count'] - ) - ]); + ) + ]); + $nav->setImage(Icon::create('schedule')); + } + + $navs[$result['range_id']] = $nav; } - return $nav; + + return $navs; } /** diff --git a/lib/modules/CoreScm.php b/lib/modules/CoreScm.php index 66f3853..3f2c6ed 100644 --- a/lib/modules/CoreScm.php +++ b/lib/modules/CoreScm.php @@ -9,62 +9,52 @@ * the License, or (at your option) any later version. */ -class CoreScm extends CorePlugin implements StudipModule +class CoreScm extends CorePlugin implements StudipModuleExtended { - /** - * {@inheritdoc} - */ - public function getIconNavigation($course_id, $last_visit, $user_id) + use IconNavigationTrait; + + public function getManyIconNavigation(array $course_ids, ?string $user_id = null): array { if (!Config::get()->SCM_ENABLE) { - return null; + return []; } - - $sql = "SELECT scm_id, - SUM(IF(content != '', 1, 0)) AS count, - SUM(IF((chdate > IFNULL(ouv.visitdate, :threshold) AND scm.user_id !=:user_id), IF(content != '', 1, 0), NULL)) AS neue - FROM scm - LEFT JOIN object_user_visits AS ouv - ON ouv.object_id = scm.range_id - AND ouv.user_id = :user_id - AND ouv.plugin_id = :plugin_id - WHERE scm.range_id = :course_id - GROUP BY scm.range_id"; - - $statement = DBManager::get()->prepare($sql); - $statement->bindValue(':user_id', $user_id); - $statement->bindValue(':course_id', $course_id); - $statement->bindValue(':threshold', $last_visit); - $statement->bindValue(':plugin_id', $this->getPluginId()); - $statement->execute(); - $result = $statement->fetch(PDO::FETCH_ASSOC); - - if (!$result) { - return null; - } - - $scm = StudipScmEntry::find($result['scm_id']); - - $nav = new Navigation((string) $scm->tab_name, 'dispatch.php/course/scm'); - - if ($result['count']) { - if ($result['neue']) { - $nav->setImage(Icon::create('infopage', Icon::ROLE_NEW)); - $nav->setBadgeNumber($result['neue']); - if ($result['count'] == 1) { - $title = $scm->tab_name . _(' (geändert)'); - } else { - $title = sprintf( - _('%1$d Einträge insgesamt, %2$d neue'), - $result['count'], - $result['neue'] - ); - } - } else { - $nav->setImage(Icon::create('infopage')); - if ($result['count'] == 1) { - $title = $scm->tab_name; - } else { + $navs = array_fill_keys($course_ids, null); + $sql = "SELECT range_id, tab_name, + SUM(IF(content != '', 1, 0)) AS count, + SUM(IF((chdate > IFNULL(ouv.visitdate, :threshold) AND scm.user_id !=:user_id), IF(content != '', 1, 0), NULL)) AS neue + FROM scm + LEFT JOIN object_user_visits AS ouv + ON ouv.object_id = scm.range_id + AND ouv.user_id = :user_id + AND ouv.plugin_id = :plugin_id + WHERE scm.range_id IN (:course_ids) + GROUP BY scm.range_id"; + $results = DBManager::get()->fetchAll($sql, [ + ':user_id' => $user_id, + ':course_ids' => $course_ids, + ':threshold' => object_get_visit_threshold(), + ':plugin_id' => $this->getPluginId(), + ]); + + foreach ($results as $result) { + $title = $result['tab_name']; + $image = Icon::create('info'); + $badge = 0; + + if ($result['count']) { + if ($result['neue']) { + $badge = $result['neue']; + if ($result['count'] == 1) { + $title .= _(' (geändert)'); + } else { + $title = sprintf( + _('%1$d Einträge insgesamt, %2$d neue'), + $result['count'], + $result['neue'] + ); + } + $image = Icon::create('info', Icon::ROLE_ATTENTION); + } else if ($result['count'] > 1) { $title = sprintf( ngettext( '%d Eintrag', @@ -73,11 +63,17 @@ class CoreScm extends CorePlugin implements StudipModule ), $result['count'] ); + $image = Icon::create('info'); } } + $nav = new Navigation($title, 'dispatch.php/course/scm'); + $nav->setBadgeNumber($badge); + $nav->setImage($image); $nav->setLinkAttributes(['title' => $title]); + $navs[$result['range_id']] = $nav; } - return $nav; + + return $navs; } /** diff --git a/lib/modules/CoreStudygroupAdmin.php b/lib/modules/CoreStudygroupAdmin.php index 5d0f947..869b0cc 100644 --- a/lib/modules/CoreStudygroupAdmin.php +++ b/lib/modules/CoreStudygroupAdmin.php @@ -9,18 +9,20 @@ * the License, or (at your option) any later version. */ -class CoreStudygroupAdmin extends CorePlugin implements StudipModule +class CoreStudygroupAdmin extends CorePlugin implements StudipModuleExtended { + use IconNavigationTrait; - /** - * {@inheritdoc} - */ - public function getIconNavigation($course_id, $last_visit, $user_id) + public function getManyIconNavigation(array $course_ids, ?string $user_id = null): array { - $navigation = new Navigation(_('Verwaltung'), "dispatch.php/course/studygroup/edit/?cid={$course_id}"); - $navigation->setImage(Icon::create('admin')); - $navigation->setLinkAttributes(['title' => _('Verwaltung')]); - return $navigation; + $navs = []; + foreach ($course_ids as $course_id) { + $navigation = new Navigation(_('Verwaltung'), "dispatch.php/course/studygroup/edit/?cid={$course_id}"); + $navigation->setImage(Icon::create('admin')); + $navigation->setLinkAttributes(['title' => _('Verwaltung')]); + $navs[$course_id] = $navigation; + } + return $navs; } /** diff --git a/lib/modules/CoreStudygroupParticipants.php b/lib/modules/CoreStudygroupParticipants.php index 5f8ac87..e77721f 100644 --- a/lib/modules/CoreStudygroupParticipants.php +++ b/lib/modules/CoreStudygroupParticipants.php @@ -9,19 +9,39 @@ * the License, or (at your option) any later version. */ -class CoreStudygroupParticipants extends CorePlugin implements StudipModule +class CoreStudygroupParticipants extends CorePlugin implements StudipModuleExtended { - /** - * {@inheritdoc} - */ - public function getIconNavigation($course_id, $last_visit, $user_id) + use IconNavigationTrait; + + public function getManyIconNavigation(array $course_ids, ?string $user_id = null): array { - $navigation = new Navigation(_('Teilnehmende'), "dispatch.php/course/studygroup/members/{$course_id}"); - $navigation->setImage(Icon::create('persons')); - if ($last_visit && CourseMember::countBySQL("seminar_id = :course_id AND mkdate >= :last_visit", ['last_visit' => $last_visit, 'course_id' => $course_id]) > 0) { - $navigation->setImage(Icon::create('persons', Icon::ROLE_ATTENTION)); + $results = DBManager::get()->fetchAll( + "SELECT seminar_user.Seminar_id, COUNT(seminar_user.user_id) as neue + FROM seminar_user + LEFT JOIN object_user_visits AS ouv + ON ouv.object_id = seminar_user.Seminar_id + AND ouv.user_id = :user_id + AND ouv.plugin_id = :plugin_id + WHERE seminar_user.Seminar_id IN (:course_ids) + AND seminar_user.mkdate > IFNULL(ouv.visitdate, :threshold) + GROUP BY seminar_user.Seminar_id", + [ + ':course_ids' => $course_ids, + ':user_id' => $user_id, + ':plugin_id' => $this->getPluginId(), + 'threshold' => object_get_visit_threshold() + ], + ); + $navs = []; + foreach ($course_ids as $course_id) { + $navigation = new Navigation(_('Teilnehmende'), "dispatch.php/course/studygroup/members/{$course_id}"); + $navigation->setImage(Icon::create('persons')); + if (isset($results[$course_id]) && !empty($results[$course_id]['neue'])) { + $navigation->setImage(Icon::create('persons', Icon::ROLE_ATTENTION)); + } + $navs[$course_id] = $navigation; } - return $navigation; + return $navs; } /** diff --git a/lib/modules/CoreWiki.php b/lib/modules/CoreWiki.php index 76a1f8f..9fd03ab 100644 --- a/lib/modules/CoreWiki.php +++ b/lib/modules/CoreWiki.php @@ -9,98 +9,79 @@ * the License, or (at your option) any later version. */ -class CoreWiki extends CorePlugin implements StudipModule +class CoreWiki extends CorePlugin implements StudipModuleExtended { - /** - * {@inheritdoc} - */ - public function getIconNavigation($range_id, $last_visit, $user_id) + use IconNavigationTrait; + + public function getManyIconNavigation(array $course_ids, ?string $user_id = null): array { if (!Config::get()->WIKI_ENABLE) { - return null; + return []; } $perm = $GLOBALS['perm']->get_perm($user_id); if (in_array($perm, ['admin', 'root'])) { $perm = 'dozent'; } - $statement = DBManager::get()->prepare(" - SELECT `wiki_pages`.`page_id` - FROM `wiki_pages` - LEFT JOIN `statusgruppe_user` ON (`statusgruppe_user`.`statusgruppe_id` = `wiki_pages`.`read_permission`) - WHERE `wiki_pages`.`range_id` = :range_id - AND ( - `wiki_pages`.`read_permission` = 'all' - OR `statusgruppe_user`.`user_id` = :user_id - OR `wiki_pages`.`read_permission` = :perm - OR (`wiki_pages`.`read_permission` = 'tutor' AND :perm = 'dozent') - ) - "); - - $statement->execute([ - 'range_id' => $range_id, - 'user_id' => $user_id, - 'perm' => $perm - ]); - $wiki_page_ids = $statement->fetchAll(PDO::FETCH_COLUMN); - if (count($wiki_page_ids) === 0) { - return null; - } - - $visit_date = object_get_visit($range_id, $this->getPluginId(), 'visitdate') ?? $last_visit; - - $statement = DBManager::get()->prepare(" - SELECT COUNT(*) AS `neue` - FROM `wiki_pages` - WHERE `wiki_pages`.`page_id` IN (:page_ids) - AND `wiki_pages`.`chdate` > :threshold - AND `wiki_pages`.`user_id` != :user_id - "); - $statement->execute([ - 'page_ids' => $wiki_page_ids, - 'threshold' => $visit_date, - 'user_id' => $user_id, + $query = "SELECT wiki_pages.range_id, + COUNT(page_id) AS count, + COUNT(IF((wiki_pages.chdate > IFNULL(ouv.visitdate, :threshold) AND wiki_pages.user_id != :user_id), page_id, NULL)) AS neue + FROM wiki_pages + LEFT JOIN statusgruppe_user ON (statusgruppe_user.statusgruppe_id = wiki_pages.read_permission) + LEFT JOIN object_user_visits AS ouv + ON ouv.object_id = wiki_pages.range_id + AND ouv.user_id = :user_id + AND ouv.plugin_id = :plugin_id + WHERE wiki_pages.range_id IN (:range_ids) + AND ( + wiki_pages.read_permission = 'all' + OR statusgruppe_user.user_id = :user_id + OR wiki_pages.read_permission = :perm + OR (wiki_pages.read_permission = 'tutor' AND :perm = 'dozent') + ) + GROUP BY wiki_pages.range_id;"; + $results = DBManager::get()->fetchAll($query, [ + ':range_ids' => $course_ids, + ':user_id' => $user_id, + ':perm' => $perm, + ':plugin_id' => $this->getPluginId(), + ':threshold' => object_get_visit_threshold(), ]); - $new_pages = $statement->fetch(PDO::FETCH_COLUMN, 0); - $nav = new Navigation(_('Wiki')); - if ($new_pages > 0) { - $nav->setURL('dispatch.php/course/wiki/newpages'); - $nav->setImage(Icon::create('wiki', Icon::ROLE_ATTENTION, [ + $navs = array_fill_keys($course_ids, null); + foreach ($results as $result) { + $nav = new Navigation(_('Wiki')); + $params = [ 'title' => sprintf( ngettext( '%d Wiki-Seite', '%d Wiki-Seiten', - count($wiki_page_ids) + $result['count'] ), - count($wiki_page_ids) + $result['count'] ) - . ', ' - . sprintf( - ngettext( - '%d Änderung', - '%d Änderungen', - $new_pages - ), - $new_pages - ) - ])); - $nav->setBadgeNumber($new_pages); - } else { - $nav->setURL('dispatch.php/course/wiki/page'); - $nav->setImage(Icon::create('wiki')); - $nav->setLinkAttributes([ - 'title' => sprintf( - ngettext( - '%d Wiki-Seite', - '%d Wiki-Seiten', - count($wiki_page_ids) - ), - count($wiki_page_ids) - ) - ]); + ]; + if ($result['neue']) { + $nav->setURL('dispatch.php/course/wiki/newpages'); + $nav->setImage(Icon::create('wiki', Icon::ROLE_ATTENTION)); + $params['title'] .= ', ' . sprintf( + ngettext( + '%d Änderung', + '%d Änderungen', + $result['neue'] + ), + $result['neue'] + ); + $nav->setBadgeNumber($result['neue']); + } else { + $nav->setURL('dispatch.php/course/wiki/page'); + $nav->setImage(Icon::create('wiki')); + + } + $nav->setLinkAttributes($params); + $navs[$result['range_id']] = $nav; } - return $nav; + return $navs; } /** @@ -227,5 +208,4 @@ class CoreWiki extends CorePlugin implements StudipModule { return (bool) Config::get()->getValue('WIKI_ENABLE'); } - } diff --git a/lib/modules/CoursewareModule.php b/lib/modules/CoursewareModule.php index c081e6a..5b12cd6 100644 --- a/lib/modules/CoursewareModule.php +++ b/lib/modules/CoursewareModule.php @@ -3,8 +3,10 @@ use Courseware\Instance; use Courseware\StructuralElement; -class CoursewareModule extends CorePlugin implements SystemPlugin, StudipModule +class CoursewareModule extends CorePlugin implements SystemPlugin, StudipModuleExtended { + use IconNavigationTrait; + /** * {@inheritdoc} */ @@ -79,54 +81,58 @@ class CoursewareModule extends CorePlugin implements SystemPlugin, StudipModule return ['courseware' => $navigation]; } - /** - * {@inheritdoc} - */ - public function getIconNavigation($courseId, $last_visit, $user_id) + public function getManyIconNavigation(array $course_ids, ?string $user_id = null): array { if ($user_id === 'nobody') { - return null; + return []; } - $statement = DBManager::get()->prepare(" - SELECT COUNT(DISTINCT elem.id) + $results = DBManager::get()->fetchGrouped( + "SELECT elem.range_id, + COUNT(IF((blocks.chdate > IFNULL(ouv.visitdate, :threshold) AND blocks.editor_id != :user_id), elem.id, NULL)) AS neue FROM `cw_structural_elements` AS elem INNER JOIN `cw_containers` as container ON (elem.id = container.structural_element_id) INNER JOIN `cw_blocks` as blocks ON (container.id = blocks.container_id) + LEFT JOIN object_user_visits AS ouv + ON ouv.object_id = elem.range_id + AND ouv.user_id = :user_id + AND ouv.plugin_id = :plugin_id WHERE elem.range_type = 'course' - AND elem.range_id = :range_id + AND elem.range_id IN (:range_ids) AND blocks.payload != '' - AND blocks.chdate > :last_visit AND blocks.editor_id != :user_id - "); - - $statement->execute([ - 'range_id' => $courseId, - 'last_visit' => $last_visit, - 'user_id' => $user_id - ]); - - $new = $statement->fetchColumn(); + GROUP BY elem.range_id", + [ + 'user_id' => $user_id, + 'range_ids' => $course_ids, + 'threshold' => object_get_visit_threshold(), + 'plugin_id' => $this->getPluginId(), + ] + ); - $nav = new Navigation(_('Courseware'), 'dispatch.php/course/courseware'); - $nav->setImage(Icon::create('courseware')); - $nav->setLinkAttributes(['title' => _('Courseware')]); + $navs = []; + foreach ($course_ids as $course_id) { + $nav = new Navigation(_('Courseware'), 'dispatch.php/course/courseware'); + $nav->setImage(Icon::create('courseware')); + $nav->setLinkAttributes(['title' => _('Courseware'),]); + + if (!empty($results[$course_id]['neue'])) { + $text = sprintf( + ngettext( + '%u neue Seite', + '%u neue Seiten', + $results[$course_id]['neue'] + ), + $results[$course_id]['neue'] + ); + $nav->setImage(Icon::create('courseware', Icon::ROLE_ATTENTION)); + $nav->setLinkAttributes(['title' => $text]); + $nav->setBadgeNumber($results[$course_id]['neue']); + } - if ($new > 0) { - $text = sprintf( - ngettext( - '%u neue Seite', - '%u neue Seiten', - $new - ), - $new - ); - $nav->setImage(Icon::create('courseware', Icon::ROLE_ATTENTION)); - $nav->setLinkAttributes(['title' => $text]); - $nav->setBadgeNumber($new); + $navs[$course_id] = $nav; } - - return $nav; + return $navs; } /** diff --git a/lib/modules/FeedbackModule.php b/lib/modules/FeedbackModule.php index 34a1ff1..f59ed4b 100644 --- a/lib/modules/FeedbackModule.php +++ b/lib/modules/FeedbackModule.php @@ -11,8 +11,10 @@ * @author Nils Gehrke * @license https://www.gnu.org/licenses/gpl-2.0.html GPL version 2 */ -class FeedbackModule extends CorePlugin implements StudipModule, SystemPlugin +class FeedbackModule extends CorePlugin implements StudipModuleExtended, SystemPlugin { + use IconNavigationTrait; + /** * {@inheritdoc} */ @@ -21,12 +23,9 @@ class FeedbackModule extends CorePlugin implements StudipModule, SystemPlugin return null; } - /** - * {@inheritdoc} - */ - public function getIconNavigation($course_id, $last_visit, $user_id) + public function getManyIconNavigation(array $course_ids, ?string $user_id = null): array { - return null; + return []; } /** diff --git a/lib/modules/GradebookModule.php b/lib/modules/GradebookModule.php index 2a3be5c..34bf835 100644 --- a/lib/modules/GradebookModule.php +++ b/lib/modules/GradebookModule.php @@ -14,8 +14,10 @@ use Grading\Instance; * @author * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 */ -class GradebookModule extends CorePlugin implements SystemPlugin, StudipModule +class GradebookModule extends CorePlugin implements SystemPlugin, StudipModuleExtended { + use IconNavigationTrait; + public function __construct() { parent::__construct(); @@ -38,41 +40,56 @@ class GradebookModule extends CorePlugin implements SystemPlugin, StudipModule return null; } - /** - * {@inheritdoc} - * - * @SuppressWarnings(PHPMD.Superglobals) - */ - public function getIconNavigation($courseId, $lastVisit, $userId) + public function getManyIconNavigation(array $course_ids, ?string $user_id = null): array { - if ($userId === 'nobody') { - return null; + if ($user_id === 'nobody') { + return []; } + // split courses in student-perms and tutor-perms + $tutor_c_ids = []; + foreach ($course_ids as $course_id) { + if ($GLOBALS['perm']->have_studip_perm('tutor', $course_id, $user_id)) { + $tutor_c_ids[$course_id] = $course_id; + } + } + $results = DBManager::get()->fetchGrouped( + "SELECT gd.course_id, gi.user_id + FROM grading_instances gi + JOIN grading_definitions gd ON(gd.id = definition_id) + LEFT JOIN object_user_visits AS ouv + ON ouv.object_id = gd.course_id + AND ouv.user_id = :user_id + AND ouv.plugin_id = :plugin_id + WHERE gd.course_id IN (:course_ids) AND gi.chdate > IFNULL(ouv.visitdate, :threshold)", + [ + ':user_id' => $user_id, + ':plugin_id' => 0, // module doesnt write directly into ouv + ':course_ids' => $course_ids, + ':threshold' => object_get_visit_threshold(), + ] + ); $title = _('Gradebook'); - if ($GLOBALS['perm']->have_studip_perm('tutor', $courseId, $userId)) { - $changed = Instance::countBySQL( - 'INNER JOIN grading_definitions gd ON(gd.id = definition_id) '. - 'WHERE gd.course_id = ? AND grading_instances.chdate > ?', - [$courseId, $lastVisit] - ); - } else { - $changed = Instance::countBySQL( - 'INNER JOIN grading_definitions gd ON(gd.id = definition_id) '. - 'WHERE gd.course_id = ? AND grading_instances.chdate > ? AND user_id = ?', - [$courseId, $lastVisit, $userId] - ); + $navs = []; + foreach ($course_ids as $course_id) { + if (empty($results[$course_id])) { + $changed = false; + } elseif (isset($tutor_c_ids[$course_id])) { + $changed = count($results[$course_id]); + } else { + $filtered_results = array_filter($results[$course_id], fn ($fetched_user_id) => $fetched_user_id === $user_id); + $changed = !empty($filtered_results) ? count($filtered_results) : 0; + } + $icon = $changed + ? Icon::create('assessment', Icon::ROLE_ATTENTION) + : Icon::create('assessment'); + $navigation = new Navigation($title, 'dispatch.php/course/gradebook/overview'); + $navigation->setImage($icon); + $navigation->setLinkAttributes(['title' => $title]); + $navs[$course_id] = $navigation; } - $icon = $changed - ? Icon::create('gradebook', Icon::ROLE_NEW) - : Icon::create('gradebook'); - - $navigation = new Navigation($title, 'dispatch.php/course/gradebook/overview'); - $navigation->setImage($icon); - $navigation->setLinkAttributes(['title' => $title]); - - return $navigation; + return $navs; } /** @@ -157,8 +174,8 @@ class GradebookModule extends CorePlugin implements SystemPlugin, StudipModule 'description' => _('Dieses Modul ermöglicht die manuelle und automatische Erfassung von Noten und Leistungen.'), 'category' => _('Lehr- und Lernorganisation'), 'keywords' => _('automatische und manuelle Erfassung von gewichteten Leistungen;Export von Leistungen;persönliche Fortschrittskontrolle'), - 'icon' => Icon::create('gradebook', Icon::ROLE_INFO), - 'icon_clickable' => Icon::create('gradebook'), + 'icon' => Icon::create('assessment', Icon::ROLE_INFO), + 'icon_clickable' => Icon::create('assessment'), 'screenshots' => [ 'path' => 'assets/images/plus/screenshots/Gradebook', 'pictures' => [ diff --git a/lib/modules/IconNavigationTrait.php b/lib/modules/IconNavigationTrait.php new file mode 100644 index 0000000..739d645 --- /dev/null +++ b/lib/modules/IconNavigationTrait.php @@ -0,0 +1,19 @@ +getManyIconNavigation([$course_id], $user_id); + self::$nav_cache[$course_id] = $navs[$course_id] ?? null; + } + + return self::$nav_cache[$course_id]; + } + +} diff --git a/lib/modules/IliasInterfaceModule.php b/lib/modules/IliasInterfaceModule.php index fdda3e3..6f9eca8 100644 --- a/lib/modules/IliasInterfaceModule.php +++ b/lib/modules/IliasInterfaceModule.php @@ -8,8 +8,10 @@ * @since 4.3 */ -class IliasInterfaceModule extends CorePlugin implements StudipModule, SystemPlugin +class IliasInterfaceModule extends CorePlugin implements StudipModuleExtended, SystemPlugin { + use IconNavigationTrait; + public function __construct() { parent::__construct(); @@ -48,13 +50,16 @@ class IliasInterfaceModule extends CorePlugin implements StudipModule, SystemPlu return null; } - public function getIconNavigation($course_id, $last_visit, $user_id) + public function getManyIconNavigation(array $course_ids, ?string $user_id = null): array { + // TODO Test this function if (!Config::get()->ILIAS_INTERFACE_ENABLE) { - return; + return []; } - $sql = "SELECT COUNT(IF(a.module_type != 'crs', module_id, NULL)) AS count_modules, + $results = DBManager::get()->fetchAll( + "SELECT a.object_id, + COUNT(IF(a.module_type != 'crs', module_id, NULL)) AS count_modules, COUNT(IF(a.module_type = 'crs', module_id, NULL)) AS count_courses, COUNT(IF((chdate > IFNULL(b.visitdate, :threshold) AND a.module_type != 'crs'), module_id, NULL)) AS neue FROM object_contentmodules AS a @@ -62,62 +67,61 @@ class IliasInterfaceModule extends CorePlugin implements StudipModule, SystemPlu ON b.object_id = a.object_id AND b.user_id = :user_id AND b.plugin_id = :plugin_id - WHERE a.object_id = :course_id - GROUP BY a.object_id"; - - $statement = DBManager::get()->prepare($sql); - $statement->bindValue(':user_id', $user_id); - $statement->bindValue(':course_id', $course_id); - $statement->bindValue(':threshold', $last_visit); - $statement->bindValue(':plugin_id', $this->getPluginId()); - $statement->execute(); - $result = $statement->fetch(PDO::FETCH_ASSOC); + WHERE a.object_id IN (:course_ids) + GROUP BY a.object_id", + [ + ':user_id' => $user_id, + ':course_ids' => $course_ids, + ':threshold' => object_get_visit_threshold(), + ':plugin_id' => $this->getPluginId(), + ] + ); - if (!$result) { - return null; - } - - $title = CourseConfig::get($course_id)->getValue('ILIAS_INTERFACE_MODULETITLE'); - $nav = new Navigation($title, 'dispatch.php/course/ilias_interface/index'); - if ($result['neue']) { - $nav->setImage(Icon::create('learnmodule', Icon::ROLE_ATTENTION)); - $nav->setLinkAttributes([ - 'title' => sprintf( - ngettext( - '%1$d Lernobjekt, %2$d neues', - '%1$d Lernobjekte, %2$d neue', - $result['count_modules'] - ), - $result['count_modules'], - $result['neue'] - ) - ]); - } elseif ($result['count_modules']) { - $nav->setImage(Icon::create('learnmodule')); - $nav->setLinkAttributes([ - 'title' => sprintf( - ngettext( - '%d Lernobjekt', - '%d Lernobjekte', + $navs = []; + foreach ($results as $result) { + $title = CourseConfig::get($result['object_id'])->getValue('ILIAS_INTERFACE_MODULETITLE'); + $nav = new Navigation($title, 'dispatch.php/course/ilias_interface/index'); + if ($result['neue']) { + $nav->setImage(Icon::create('learnmodule', Icon::ROLE_ATTENTION)); + $nav->setLinkAttributes([ + 'title' => sprintf( + ngettext( + '%1$d Lernobjekt, %2$d neues', + '%1$d Lernobjekte, %2$d neue', + $result['count_modules'] + ), + $result['count_modules'], + $result['neue'] + ) + ]); + } elseif ($result['count_modules']) { + $nav->setImage(Icon::create('learnmodule')); + $nav->setLinkAttributes([ + 'title' => sprintf( + ngettext( + '%d Lernobjekt', + '%d Lernobjekte', + $result['count_modules'] + ), $result['count_modules'] - ), - $result['count_modules'] - ) - ]); - } elseif ($result['count_courses']) { - $nav->setImage(Icon::create('learnmodule')); - $nav->setLinkAttributes([ - 'title' => sprintf( - ngettext( - '%d ILIAS-Kurs', - '%d ILIAS-Kurse', + ) + ]); + } elseif ($result['count_courses']) { + $nav->setImage(Icon::create('learnmodule')); + $nav->setLinkAttributes([ + 'title' => sprintf( + ngettext( + '%d ILIAS-Kurs', + '%d ILIAS-Kurse', + $result['count_courses'] + ), $result['count_courses'] - ), - $result['count_courses'] - ) - ]); + ) + ]); + } + $navs[$result['object_id']] = $nav; } - return $nav; + return $navs; } public function getTabNavigation($course_id) diff --git a/lib/modules/StudipModuleExtended.php b/lib/modules/StudipModuleExtended.php new file mode 100644 index 0000000..c80af15 --- /dev/null +++ b/lib/modules/StudipModuleExtended.php @@ -0,0 +1,27 @@ + $nav1, 'course_id_2' => $nav2, 'course_id_3' => null, ...]. + * + */ + public function getManyIconNavigation(array $course_ids, ?string $user_id = null): array; + +} -- cgit v1.0