diff options
| author | Rasmus Fuhse <fuhse@data-quest.de> | 2025-01-10 12:13:24 +0000 |
|---|---|---|
| committer | Rasmus Fuhse <fuhse@data-quest.de> | 2025-01-10 12:13:24 +0000 |
| commit | 39745c9aa8bb099e8bda1f4d775ed229dbe97be4 (patch) | |
| tree | a2a96deecf48e62d995507c5c8b39eea9ae9293d /app/controllers | |
| parent | 03e8e1d8f9100cf9bfcb111b63ac4077f510a026 (diff) | |
Resolve "Studiengruppen erweitern" - Hauptbronch
Closes #3616
Merge request studip/studip!2509
Diffstat (limited to 'app/controllers')
| -rw-r--r-- | app/controllers/admin/tags.php | 67 | ||||
| -rw-r--r-- | app/controllers/course/connectedcourses.php | 234 | ||||
| -rw-r--r-- | app/controllers/course/connectedstudygroups.php | 315 | ||||
| -rw-r--r-- | app/controllers/course/overview.php | 11 | ||||
| -rw-r--r-- | app/controllers/course/studygroup.php | 209 | ||||
| -rw-r--r-- | app/controllers/course/wizard.php | 11 | ||||
| -rw-r--r-- | app/controllers/my_courses.php | 34 | ||||
| -rw-r--r-- | app/controllers/my_studygroups.php | 100 | ||||
| -rw-r--r-- | app/controllers/search/angebot.php | 12 | ||||
| -rw-r--r-- | app/controllers/search/globalsearch.php | 28 | ||||
| -rw-r--r-- | app/controllers/search/studiengaenge.php | 14 |
11 files changed, 975 insertions, 60 deletions
diff --git a/app/controllers/admin/tags.php b/app/controllers/admin/tags.php new file mode 100644 index 0000000..03819bd --- /dev/null +++ b/app/controllers/admin/tags.php @@ -0,0 +1,67 @@ +<?php + +class Admin_TagsController extends AuthenticatedController +{ + /** + * Common tasks for all actions. + */ + public function before_filter(&$action, &$args) + { + parent::before_filter($action, $args); + + $GLOBALS['perm']->check('root'); + Navigation::activateItem('/admin/locations/tags'); + PageLayout::setTitle(_('Schlagwortverwaltung')); + } + + public function index_action() + { + Tag::deleteBySQL('LEFT JOIN `tags_relations` ON (`tags`.`id` = `tags_relations`.`tag_id`) WHERE `tags_relations`.`range_id` IS NULL'); + $this->page = Request::int('page', 0); + $this->tags = Tag::findBySQL('1 ORDER BY `name` ASC LIMIT :offset, :limit', [ + 'offset' => $this->page * Config::get()->ENTRIES_PER_PAGE, + 'limit' => Config::get()->ENTRIES_PER_PAGE + ]); + $this->all_tags = Tag::countBySql('1'); + } + + public function edit_action(Tag $tag) + { + PageLayout::setTitle(sprintf(_('Schlagwort „%s“ bearbeiten'), $tag->name)); + $form = \Studip\Forms\Form::fromSORM( + $tag, + [ + 'legend' => _('Grunddaten'), + 'fields' => [ + 'name' => [ + 'label' =>_('Name'), + 'validate' => function ($value) use ($tag) { + $output = ''; + if ($value !== mb_strtolower($value)) { + $output .= _('Schlagwörter sollen keine Großbuchstaben entahlten').' '; + } + foreach (['\n', '#', '|', ' '] as $forbidden) { + if (str_contains($value, $forbidden)) { + $output .= _('Schlagwörter dürfen keine Zeilenumbrüche, Leerzeichen, Doppelkreuze (#) oder Pipe-Zeichen (|) enthalten.').' '; + break; + } + } + if (Tag::findOneByName($value) && $value !== $tag->name) { + $output .= _('Dieses Schlagwort ist schon vergeben.').' '; + } + return $output !== '' ? $output : true; + } + ], + 'active' => _('Aktiv') + ] + ] + )->autoStore()->setURL($this->indexURL()); + $this->render_form($form); + } + + public function view_objects_action(Tag $tag) + { + $this->tag = $tag; + PageLayout::setTitle(sprintf(_("Verknüpfte Objekte mit Schlagwort „%s“"), $tag->name)); + } +} diff --git a/app/controllers/course/connectedcourses.php b/app/controllers/course/connectedcourses.php new file mode 100644 index 0000000..dcbc3ae --- /dev/null +++ b/app/controllers/course/connectedcourses.php @@ -0,0 +1,234 @@ +<?php + +class Course_ConnectedcoursesController extends AuthenticatedController +{ + + public function before_filter(&$action, &$args) + { + parent::before_filter($action, $args); + + if (!$GLOBALS['perm']->have_studip_perm('tutor', Context::getId())) { + throw new AccessDeniedException(); + } + } + + public function index_action() + { + Navigation::activateItem('/course/admin/connectedcourses'); + $this->connected = StudygroupCourse::findBySQL( + 'INNER JOIN seminare ON (seminare.Seminar_id = studygroup_courses.course_id) WHERE studygroup_courses.studygroup_id = ? ORDER BY seminare.name ASC', + [ + Context::getId() + ] + ); + $this->proposals = StudygroupCourseProposal::findBySQL( + 'INNER JOIN seminare ON (seminare.Seminar_id = studygroup_courses_proposals.studygroup_id) WHERE studygroup_courses_proposals.studygroup_id = ? ORDER BY seminare.name ASC', + [ + Context::getId() + ] + ); + $this->buildSidebar(); + + } + + public function connect_action($course_id = null) + { + + Navigation::activateItem('/course/admin/connectedcourses'); + PageLayout::setTitle(_('Veranstaltung suchen und zur Verknüpfung vorschlagen')); + + if (Request::isPost() && (Request::option('course_id') || $course_id)) { + CSRFProtection::verifySecurityToken(); + $course_id = $course_id ?? Request::option('course_id'); + $status = StudygroupModel::proposeAsStudygroupTo(Context::get(), $course_id); + if ($status === 'connected') { + PageLayout::postSuccess(_('Veranstaltung wurde verknüpft.')); + } + if ($status === 'proposed') { + PageLayout::postSuccess(_('Vorschlag wurde eingereicht.')); + } + $this->redirect('course/connectedcourses/index'); + return; + } + + $connected = StudygroupCourse::findBySQL( + 'INNER JOIN seminare ON (seminare.Seminar_id = studygroup_courses.course_id) WHERE studygroup_courses.studygroup_id = ? ORDER BY seminare.name ASC', + [ + Context::getId() + ] + ); + $proposals = StudygroupCourseProposal::findBySQL( + 'INNER JOIN seminare ON (seminare.Seminar_id = studygroup_courses_proposals.studygroup_id) WHERE studygroup_courses_proposals.studygroup_id = ? ORDER BY seminare.name ASC', + [ + Context::getId() + ] + ); + $already_covered = array_map(function($c) { return $c->course_id; }, $connected); + $already_covered = $already_covered + array_map(function($c) { return $c->course_id; }, $proposals); + + + + + $studygroup_ids = []; + foreach (SemClass::getClasses() as $sem_class) { + if ($sem_class['studygroup_mode'] > 0) { + foreach ($sem_class->getSemTypes() as $sem_type) { + $studygroup_ids[] = $sem_type['id']; + } + } + } + $this->my_courses = []; + if (!$GLOBALS['perm']->have_perm('admin')) { + $this->my_courses = Course::findBySQL('INNER JOIN `seminar_user` USING (`Seminar_id`) + LEFT JOIN `semester_courses` ON (`seminare`.`Seminar_id` = `semester_courses`.`course_id`) + WHERE `seminar_user`.`user_id` = :user_id + AND `seminare`.`status` NOT IN (:studygroup_sem_types) + AND (`semester_courses`.`semester_id` IS NULL OR `semester_courses`.`semester_id` = :semester_id) + AND `seminare`.`Seminar_id` NOT IN (:ignore) + ORDER BY `seminare`.`name` ASC ', + [ + 'user_id' => User::findCurrent()->id, + 'studygroup_sem_types' => $studygroup_ids, + 'semester_id' => Request::get('semester_id') ?? Semester::findCurrent()->id, + 'ignore' => count($already_covered) ? $already_covered : '' + ]); + foreach ($this->my_courses as $my_course) { + $already_covered[] = $my_course->id; + } + } + + if (Request::get('search') && Request::get('search') != 1) { + //do the search: + $query = SQLQuery::table('seminare') + ->where('search', + '`name` LIKE :search OR `VeranstaltungsNummer` LIKE :search', + ['search' => '%' . Request::get('search') . '%'] + ) + ->where( + 'studygroups', + '`seminare`.`status` NOT IN (:sem_type_ids)', + ['sem_type_ids' => $studygroup_ids] + ) + ->groupBy('`seminare`.`Seminar_id`'); + if (count($already_covered) > 0) { + $query->where( + 'ignore', + '`seminare`.`Seminar_id` NOT IN (:ignore)', + ['ignore' => $already_covered] + ); + } + if (!empty(Request::get('semester_id'))) { + $query->join( + 'semester_courses', + 'semester_courses', + '`semester_courses`.`course_id` = `seminare`.`Seminar_id`', + 'LEFT JOIN' + ); + $query->where( + 'semester_id', + 'semester_courses.semester_id = :semester_id OR semester_courses.semester_id IS NULL', + ['semester_id' => Request::get('semester_id')] + ); + } + $this->searchresults = $query->fetchAll(Course::class); + } else { + //get up to 10 courses with a lot of members of the current studygroup: + $statement = DBManager::get()->prepare(" + SELECT `seminare`.* + FROM `seminar_user` + INNER JOIN `seminare` ON (`seminare`.`Seminar_id` = `seminar_user`.`Seminar_id`) + INNER JOIN `seminar_user` AS `su2` ON (`su2`.`user_id` = `seminar_user`.`user_id` AND `su2`.`Seminar_id` = :course_id) + LEFT JOIN `studygroup_courses` ON (`studygroup_courses`.`course_id` = `seminare`.`Seminar_id` AND `studygroup_courses`.`studygroup_id` = `su2`.`Seminar_id`) + WHERE `seminare`.`status` NOT IN (:studygroup_sem_types) + AND `studygroup_courses`.`id` IS NULL + AND `seminare`.`Seminar_id` NOT IN (:ignore) + GROUP BY `seminare`.`Seminar_id` + HAVING COUNT(`seminar_user`.`user_id`) > 1 + ORDER BY COUNT(`seminar_user`.`user_id`) DESC + LIMIT 20 + "); + $statement->execute([ + 'course_id' => Context::getId(), + 'studygroup_sem_types' => $studygroup_ids, + 'ignore' => count($already_covered) ? $already_covered : '' + ]); + $suggestions = $statement->fetchAll(PDO::FETCH_ASSOC); + $this->suggestions = array_map(function ($d) { + return Course::buildExisting($d); + }, $suggestions); + } + + + } + + public function remove_action($course_id) + { + if (Request::isPost() && $course_id) { + CSRFProtection::verifySecurityToken(); + StudygroupCourse::deleteBySQL('course_id = ? AND studygroup_id = ?', [ + $course_id, + Context::getId() + ]); + PageLayout::postSuccess(_('Verknüpfung zu der Veranstaltung wurde aufgehoben.')); + } + $this->redirect('course/connectedcourses/index'); + } + + public function decline_action(StudygroupCourseProposal $proposal) + { + if (Request::isPost()) { + CSRFProtection::verifySecurityToken(); + if ($GLOBALS['perm']->have_studip_perm('tutor', $proposal['course_id']) || $GLOBALS['perm']->have_studip_perm('tutor', $proposal['studygroup_id'])) { + if ($proposal['proposed_from'] === 'course') { + PageLayout::postSuccess(_('Vorschlag wurde abgewiesen.')); + $statement = DBManager::get()->prepare(" + SELECT `username`, `user_id` + FROM `auth_user_md5` + INNER JOIN `seminar_user` USING (`user_id`) + WHERE `seminar_user`.`Seminar_id` = ? AND `seminar_user`.`status` IN ('tutor', 'dozent') + "); + $statement->execute([$proposal['course_id']]); + $messaging = new messaging(); + + foreach ($statement->fetchAll(PDO::FETCH_ASSOC) as $user_data) { + setTempLanguage($user_data['user_id']); + $messaging->insert_message( + sprintf( + _('Ihr Vorschlag, die Studiengruppe „%1$s“ mit der Veranstaltung „%2$s“ zu verknüpfen, wurde leider abgewiesen.'), + Context::get()->getFullname(), + $proposal->studygroup->getFullname() + ), + $user_data['username'], + '____%system%____', + '', + '', + '', + '', + _('Verknüpfungsvorschlag abgewiesen'), + '', + 'normal', + ['Studiengruppe'] + ); + restoreLanguage(); + } + } else { + PageLayout::postSuccess(_('Vorschlag wurde zurückgezogen.')); + } + $proposal->delete(); + } + } + $this->redirect('course/connectedcourses/index'); + } + + protected function buildSidebar() + { + $actions = new ActionsWidget(); + $actions->addLink( + _('Verknüpfung vorschlagen'), + $this->url_for('course/connectedcourses/connect'), + Icon::create('add'), + ['data-dialog' => 1] + ); + Sidebar::Get()->addWidget($actions); + } +} diff --git a/app/controllers/course/connectedstudygroups.php b/app/controllers/course/connectedstudygroups.php new file mode 100644 index 0000000..9ef6b92 --- /dev/null +++ b/app/controllers/course/connectedstudygroups.php @@ -0,0 +1,315 @@ +<?php + +class Course_ConnectedstudygroupsController extends AuthenticatedController +{ + + public function before_filter(&$action, &$args) + { + parent::before_filter($action, $args); + + if (!$GLOBALS['perm']->have_studip_perm('tutor', Context::getId())) { + throw new AccessDeniedException(); + } + } + + public function index_action() + { + Navigation::activateItem('/course/admin/connectedstudygroups'); + $this->connected = StudygroupCourse::findBySQL( + 'INNER JOIN seminare ON (seminare.Seminar_id = studygroup_courses.studygroup_id) WHERE studygroup_courses.course_id = ? ORDER BY seminare.name ASC', + [ + Context::getId() + ] + ); + $this->proposals = StudygroupCourseProposal::findBySQL( + 'INNER JOIN seminare ON (seminare.Seminar_id = studygroup_courses_proposals.studygroup_id) WHERE studygroup_courses_proposals.course_id = ? ORDER BY seminare.name ASC', + [ + Context::getId() + ] + ); + $this->buildSidebar(); + + } + + public function connect_action($course_id = null) + { + Navigation::activateItem('/course/admin/connectedstudygroups'); + PageLayout::setTitle(_('Studiengruppe suchen und verknüpfen')); + if (Request::isPost() && (Request::option('course_id') || $course_id)) { + CSRFProtection::verifySecurityToken(); + $course_id = $course_id ?? Request::option('course_id'); + $proposal = StudygroupCourseProposal::findOneBySQL('course_id = ? AND studygroup_id = ?', [ + Context::getId(), + $course_id + ]); + if ($GLOBALS['perm']->have_studip_perm('tutor', $course_id) || $proposal['proposed_from'] === 'studygroup') { + $connection = StudygroupCourse::findOneBySQL('course_id = ? AND studygroup_id = ?', [ + Context::getId(), + $course_id + ]); + if (!$connection) { + $connection = new StudygroupCourse(); + $connection['course_id'] = Context::getId(); + $connection['studygroup_id'] = $course_id; + $connection->store(); + } + if ($proposal) { + if ($proposal['proposed_from'] === 'studygroup') { + $statement = DBManager::get()->prepare(" + SELECT `username`, `user_id` + FROM `auth_user_md5` + INNER JOIN `seminar_user` USING (`user_id`) + WHERE `seminar_user`.`Seminar_id` = ? AND `seminar_user`.`status` IN ('tutor', 'dozent') + "); + $statement->execute([$course_id]); + $messaging = new messaging(); + + foreach ($statement->fetchAll(PDO::FETCH_ASSOC) as $user_data) { + setTempLanguage($user_data['user_id']); + $messaging->insert_message( + sprintf( + _('Ihr Vorschlag, die Studiengruppe „%1$s“ mit der Veranstaltung „%2$s“ zu verknüpfen, wurde angenommen.'), + Context::get()->getFullname(), + Course::find($course_id)->getFullname() + ), + $user_data['username'], + '____%system%____', + '', + '', + '', + '', + _('Verknüpfungsvorschlag angenommen'), + '', + 'normal', + ['Studiengruppe'] + ); + restoreLanguage(); + } + } + $proposal->delete(); + } + PageLayout::postSuccess(_('Veranstaltung wurde verknüpft.')); + } else { + //send message: + if (!$proposal) { + $proposal = new StudygroupCourseProposal(); + $proposal['course_id'] = Context::getId(); + $proposal['studygroup_id'] = $course_id; + $proposal['proposed_from'] = 'course'; + $proposal['user_id'] = User::findCurrent()->id; + $proposal->store(); + + $statement = DBManager::get()->prepare(" + SELECT `username`, `user_id` + FROM `auth_user_md5` + INNER JOIN `seminar_user` USING (`user_id`) + WHERE `seminar_user`.`Seminar_id` = ? AND `seminar_user`.`status` IN ('tutor', 'dozent') + "); + $statement->execute([$course_id]); + $messaging = new messaging(); + $oldbase = URLHelper::setBaseURL($GLOBALS['ABSOLUTE_URI_STUDIP']); + + foreach ($statement->fetchAll(PDO::FETCH_ASSOC) as $user_data) { + setTempLanguage($user_data['user_id']); + $messaging->insert_message( + sprintf( + _('Es wurde vorgeschlagen, die Veranstaltung „%1$s“ mit Ihrer Studiengruppe „%2$s“ zu verknüpfen. Sie können den Vorschlag unter folgendem Link annehmen oder ablehnen:'), + Context::get()->getFullname(), + Course::find($course_id)->getFullname() + )."\n\n".URLHelper::getURL('dispatch.php/course/connectedcourses/index', ['cid' => $course_id]), + $user_data['username'], + '____%system%____', + '', + '', + '', + '', + _('Verknüpfung Ihrer Studiengruppe zu einer Veranstaltung'), + '', + 'normal', + ['Studiengruppe'] + ); + restoreLanguage(); + } + + URLHelper::setBaseURL($oldbase); + } + PageLayout::postSuccess(_('Antrag wurde gestellt.')); + } + $this->redirect('course/connectedstudygroups/index'); + return; + } + + $connected = StudygroupCourse::findBySQL( + 'INNER JOIN seminare ON (seminare.Seminar_id = studygroup_courses.studygroup_id) WHERE studygroup_courses.course_id = ? ORDER BY seminare.name ASC', + [ + Context::getId() + ] + ); + $proposals = StudygroupCourseProposal::findBySQL( + 'INNER JOIN seminare ON (seminare.Seminar_id = studygroup_courses_proposals.course_id) WHERE studygroup_courses_proposals.course_id = ? ORDER BY seminare.name ASC', + [ + Context::getId() + ] + ); + $already_covered = array_map(function ($c) { + return $c->course_id; + }, $connected); + $already_covered = $already_covered + array_map(function ($c) { + return $c->course_id; + }, $proposals); + + $studygroup_ids = []; + foreach (SemClass::getClasses() as $sem_class) { + if ($sem_class['studygroup_mode'] > 0) { + foreach ($sem_class->getSemTypes() as $sem_type) { + $studygroup_ids[] = $sem_type['id']; + } + } + } + + if (Request::get('search') && Request::get('search') != 1) { + $query = SQLQuery::table('seminare') + ->where('search', '`name` LIKE :search', ['search' => '%' . Request::get('search') . '%']) + ->where( + 'studygroups', + '`seminare`.`status` IN (:sem_type_ids)', + ['sem_type_ids' => $studygroup_ids] + ) + ->groupBy('`seminare`.`Seminar_id`'); + if (count($already_covered) > 0) { + $query->where( + 'ignore', + '`seminare`.`Seminar_id` NOT IN (:ignore)', + ['ignore' => $already_covered] + ); + } + if (!empty(Request::get('semester_id'))) { + $query->join( + 'semester_courses', + 'semester_courses', + '`semester_courses`.`course_id` = `seminare`.`Seminar_id`', + 'LEFT JOIN' + ); + $query->where( + 'semester_id', + 'semester_courses.semester_id = :semester_id OR semester_courses.semester_id IS NULL', + ['semester_id' => Request::get('semester_id')] + ); + } + $this->searchresults = $query->fetchAll(Course::class); + } else { + + $this->my_studygroups = []; + if (!$GLOBALS['perm']->have_perm('admin')) { + $this->my_studygroups = Course::findBySQL('INNER JOIN `seminar_user` USING (`Seminar_id`) + WHERE `seminar_user`.`user_id` = :user_id + AND `seminare`.`status` IN (:studygroup_sem_types) + AND `seminare`.`Seminar_id` NOT IN (:ignore) + ORDER BY `seminare`.`name` ASC ', + [ + 'user_id' => User::findCurrent()->id, + 'studygroup_sem_types' => $studygroup_ids, + 'ignore' => count($already_covered) ? $already_covered : '' + ]); + foreach ($this->my_studygroups as $my_studygroup) { + $already_covered[] = $my_studygroup->id; + } + } + + //get all studygroups with a lot of members in the current course: + $statement = DBManager::get()->prepare(" + SELECT `seminare`.* + FROM `seminar_user` + INNER JOIN `seminare` ON (`seminare`.`Seminar_id` = `seminar_user`.`Seminar_id`) + LEFT JOIN `seminar_user` AS `su2` ON (`su2`.`user_id` = `seminar_user`.`user_id` AND `su2`.`Seminar_id` = :course_id) + LEFT JOIN `studygroup_courses` ON (`studygroup_courses`.`studygroup_id` = `seminare`.`Seminar_id` AND `studygroup_courses`.`course_id` = `su2`.`Seminar_id`) + WHERE `seminare`.`status` IN (:studygroup_sem_types) + AND `studygroup_courses`.`id` IS NULL + GROUP BY `seminare`.`Seminar_id` + HAVING COUNT(`seminar_user`.`user_id`) > 1 + ORDER BY COUNT(`seminar_user`.`user_id`) DESC + LIMIT 20 + "); + $statement->execute([ + 'course_id' => Context::getId(), + 'studygroup_sem_types' => $studygroup_ids + ]); + $this->suggestions = $statement->fetchAll(PDO::FETCH_ASSOC); + $this->suggestions = array_map(function ($d) { + return Course::buildExisting($d); + }, $this->suggestions); + } + } + + public function remove_action($course_id) + { + if (Request::isPost() && $course_id) { + CSRFProtection::verifySecurityToken(); + $connection = StudygroupCourse::deleteBySQL('course_id = ? AND studygroup_id = ?', [ + Context::getId(), + $course_id + ]); + PageLayout::postSuccess(_('Verknüpfung zu der Studiengruppe wurde aufgehoben.')); + } + $this->redirect('course/connectedstudygroups/index'); + } + + public function decline_action(StudygroupCourseProposal $proposal) + { + if (Request::isPost()) { + CSRFProtection::verifySecurityToken(); + if ($GLOBALS['perm']->have_studip_perm('tutor', $proposal['course_id']) || $GLOBALS['perm']->have_studip_perm('tutor', $proposal['studygroup_id'])) { + if ($proposal['proposed_from'] === 'studygroup') { + PageLayout::postSuccess(_('Vorschlag wurde abgewiesen.')); + $statement = DBManager::get()->prepare(" + SELECT `username`, `user_id` + FROM `auth_user_md5` + INNER JOIN `seminar_user` USING (`user_id`) + WHERE `seminar_user`.`Seminar_id` = ? AND `seminar_user`.`status` IN ('tutor', 'dozent') + "); + $statement->execute([$proposal['studygroup_id']]); + $messaging = new messaging(); + + foreach ($statement->fetchAll(PDO::FETCH_ASSOC) as $user_data) { + setTempLanguage($user_data['user_id']); + $messaging->insert_message( + sprintf( + _('Ihr Vorschlag, die Studiengruppe „%1$s“ mit der Veranstaltung „%2$s“ zu verknüpfen, wurde leider abgewiesen.'), + $proposal->studygroup->getFullname(), + Context::get()->getFullname() + ), + $user_data['username'], + '____%system%____', + '', + '', + '', + '', + _('Verknüpfungsvorschlag abgewiesen'), + '', + 'normal', + ['Studiengruppe'] + ); + + restoreLanguage(); + } + } else { + PageLayout::postSuccess(_('Vorschlag wurde zurückgezogen.')); + } + $proposal->delete(); + } + } + $this->redirect('course/connectedstudygroups/index'); + } + + protected function buildSidebar() + { + $actions = new ActionsWidget(); + $actions->addLink( + _('Studiengruppe verknüpfen'), + $this->url_for('course/connectedstudygroups/connect'), + Icon::create('add'), + ['data-dialog' => 1] + ); + Sidebar::Get()->addWidget($actions); + } +} diff --git a/app/controllers/course/overview.php b/app/controllers/course/overview.php index e1da00e..4313cdc 100644 --- a/app/controllers/course/overview.php +++ b/app/controllers/course/overview.php @@ -108,6 +108,17 @@ class Course_OverviewController extends AuthenticatedController $this->avatar = StudygroupAvatar::getAvatar($this->course_id); } + $connections = StudygroupCourse::countBySql( + "`studygroup_id` = :cid OR `course_id` = :cid", + [ + 'cid' => $this->course_id + ] + ); + if ($connections > 0) { + $response = $this->relay('course/studygroup/widget/' . $this->course_id); + $this->connectedstudygroups = $response->body; + } + $this->plugins = PluginEngine::getPlugins(StandardPlugin::class, $this->course_id); $sidebar = Sidebar::get(); diff --git a/app/controllers/course/studygroup.php b/app/controllers/course/studygroup.php index 981a152..c95c20e 100644 --- a/app/controllers/course/studygroup.php +++ b/app/controllers/course/studygroup.php @@ -170,43 +170,168 @@ class Course_StudygroupController extends AuthenticatedController */ public function edit_action() { - global $perm; - - $id = Context::getId(); - + PageLayout::setTitle(Context::getHeaderLine() . ' - ' . _('Studiengruppe bearbeiten')); + Navigation::activateItem('/course/admin/main'); PageLayout::setHelpKeyword('Basis.StudiengruppenBearbeiten'); - // if we are permitted to edit the studygroup get some data... - if ($id && $perm->have_studip_perm('dozent', $id)) { - $this->course = Course::find($id); + $course = Context::get(); - PageLayout::setTitle(Context::getHeaderLine() . ' - ' . _('Studiengruppe bearbeiten')); - Navigation::activateItem('/course/admin/main'); + $expiration_date = CourseConfig::get($course->id)->STUDYGROUP_EXPIRATION_DATE; + if (!$expiration_date) { + $expiration_date = _('Unbegrenzt'); + } - $this->course_id = $id; - $this->sem_class = $GLOBALS['SEM_CLASS'][$GLOBALS['SEM_TYPE'][$this->course->status]['class']]; - $this->tutors = CourseMember::findByCourseAndStatus($this->course->id, 'tutor'); - $this->founders = StudygroupModel::getFounders($id); + $zugang_options = [ + 'all' => _('Offen für alle'), + 'invite' => _('Auf Anfrage'), + 'connectedcourse' => _('Für Mitglieder der zugehörigen Lehrveranstaltung') + ]; + if (Config::get()->STUDYGROUPS_INVISIBLE_ALLOWED) { + $zugang_options['invisible'] = _('Unsichtbar'); + } - $actions = new ActionsWidget(); + $form = \Studip\Forms\Form::fromSORM(Context::get(), [ + 'legend' => _('Grunddaten'), + 'fields' => [ + 'name' => [ + 'label' => _('Name'), + 'required' => true + ], + 'beschreibung' => _('Beschreibung'), + 'zugang' => [ + 'label' => _('Zugang'), + 'type' => 'select', + 'options' => $zugang_options, + 'value' => function () use ($course) { + $courseset = CourseSet::getSetForCourse($course->id); + if ($courseset && $courseset->getId() === CourseSet::getConnectedcourseAdmissionSetId()) { + return 'connectedcourse'; + } elseif (!$course->visible) { + return 'invisible'; + } else { + return $course->admission_prelim > 0 ? 'invite' : 'all'; + } + }, + 'store' => function ($value, $input) { + $course = $input->getContextObject(); + switch ($value) { + case 'connectedcourse': + CourseSet::addCourseToSet(CourseSet::getConnectedcourseAdmissionSetId(), $course->id); + $course->visible = 1; + break; + case 'invisible': + CourseSet::removeCourseFromSet(CourseSet::getConnectedcourseAdmissionSetId(), $course->id); + if (Config::get()->STUDYGROUPS_INVISIBLE_ALLOWED) { + $course->visible = 0; + break; + } + case 'invite': + CourseSet::removeCourseFromSet(CourseSet::getConnectedcourseAdmissionSetId(), $course->id); + $course->visible = 1; + $course->admission_prelim = 1; + $course->admission_prelim_txt = _('Die Moderator:innen der Studiengruppe können Ihren Aufnahmewunsch bestätigen oder ablehnen. Erst nach Bestätigung erhalten Sie vollen Zugriff auf die Gruppe.'); + break; + case 'all': + CourseSet::removeCourseFromSet(CourseSet::getConnectedcourseAdmissionSetId(), $course->id); + $course->visible = 1; + $course->admission_prelim = 0; + break; + } + $course->store(); + } + ] + ] + ])->addSORM( + Context::get(), [ + 'legend' => _('Erweiterte Einstellungen'), + 'fields' => [ + 'ablaufdatum' => [ + 'label' => _('Ablaufdatum / Löschdatum'), + 'type' => 'datetimepicker', + 'value' => $expiration_date, + 'store' => function ($value) { + CourseConfig::get(Context::getId())->store('STUDYGROUP_EXPIRATION_DATE', $value); + } + ], + 'tags' => [ + 'label' => _('Schlagwörter'), + 'type' => 'multiquicksearch', + 'addlabel' => _('Schlagwort hinzufügen'), + 'value' => function () { + $course = Context::get(); + $tags = Tag::getByRange($course->id, 'course'); + return array_map(function ($t) { return $t->name; }, $tags); + }, + 'searchtype' => (string) SQLSearch::get('SELECT `name`, `name` FROM `tags` WHERE `active` = 1 AND `name` LIKE :input', _('Schlagwort suchen')), + 'autocomplete' => true, + 'mapper' => function ($value, $obj) { + $tags = []; + foreach ($value as $name) { + if ($name) { + if ($tag = Tag::findOneByName($name)) { + if ($tag->active) { + $tags[] = $tag; + } + } else { + $tag = new Tag(); + $tag->name = $name; + $tag->store(); + $tags[] = $tag; + } + } + } + return $tags; + }, + 'store' => function ($tags, $input) { + $course = $input->getContextObject(); + $tag_ids = []; + foreach ($tags as $tag) { + $tag_ids[] = $tag->id; + $relation = TagRelation::findOneBySQL( + "`range_id` = :course_id AND `range_type` = 'course' AND `tag_id` = :tag_id", + [ + 'tag_id' => $tag->id, + 'course_id' => $course->id + ] + ); + if (!$relation) { + $relation = TagRelation::create([ + 'range_id' => $course->id, + 'range_type' => 'course', + 'tag_id' => $tag->id, + ]); + } + } + TagRelation::deleteBySQL( + "`range_id` = :course_id AND `range_type` = 'course' AND `tag_id` NOT IN (:ids)", + [ + 'ids' => $tag_ids, + 'course_id' => $course->id + ] + ); + } + ] + ] + ] + )->setURL($this->editURL()) + ->autoStore(); - $actions->addLink( - _('Neue Studiengruppe anlegen'), - $this->url_for('course/wizard?studygroup=1'), - Icon::create('add') - ); + $actions = new ActionsWidget(); - $actions->addLink( - _('Diese Studiengruppe löschen'), - $this->deleteURL(), - Icon::create('trash') - ); + $actions->addLink( + _('Neue Studiengruppe anlegen'), + $this->url_for('course/wizard?studygroup=1'), + Icon::create('add') + ); + $actions->addLink( + _('Diese Studiengruppe löschen'), + $this->deleteURL(), + Icon::create('trash') + ); - Sidebar::get()->addWidget($actions); - } // ... otherwise redirect us to the seminar - else { - $this->redirect(URLHelper::getURL('dispatch.php/course/go?to=' . $id)); - } + Sidebar::get()->addWidget($actions); + + $this->render_form($form); } /** @@ -255,14 +380,20 @@ class Course_StudygroupController extends AuthenticatedController $course->schreibzugriff = 1; $course->visible = 1; - if (Request::get('groupaccess') == 'all') { + $cs_id = CourseSet::getConnectedcourseAdmissionSetId(); + if (Request::get('groupaccess') === 'all') { $course->admission_prelim = 0; + CourseSet::removeCourseFromSet($cs_id, $id); + } elseif(Request::get('groupaccess') === 'top-course') { + CourseSet::addCourseToSet($cs_id, $id); } else { $course->admission_prelim = 1; - if (Config::get()->STUDYGROUPS_INVISIBLE_ALLOWED && Request::get('groupaccess') == 'invisible') { + if (Config::get()->STUDYGROUPS_INVISIBLE_ALLOWED && Request::get('groupaccess') === 'invisible') { $course->visible = 0; } $course->admission_prelim_txt = _('Die für die Moderation zuständigen Personen der Studiengruppe können Ihren Aufnahmewunsch bestätigen oder ablehnen. Erst nach Bestätigung erhalten Sie vollen Zugriff auf die Gruppe.'); + + CourseSet::removeCourseFromSet($cs_id, $id); } $course->store(); } @@ -783,4 +914,20 @@ class Course_StudygroupController extends AuthenticatedController $this->avatar_url = $avatar->getURL(Avatar::NORMAL); } + public function widget_action($range_id) + { + if (get_class($this->parent_controller) === __CLASS__) { + throw new RuntimeException('widget_action must be relayed'); + } + $this->course = Course::find($range_id); + + if ($this->course->isStudygroup()) { + $sql = "INNER JOIN `seminare` ON (`seminare`.`Seminar_id` = `studygroup_courses`.`course_id`) WHERE `studygroup_id` = ? ORDER BY `seminare`.`name` ASC"; + } else { + $sql = "INNER JOIN `seminare` ON (`seminare`.`Seminar_id` = `studygroup_courses`.`studygroup_id`) WHERE `course_id` = ? ORDER BY `seminare`.`name` ASC "; + } + $this->connections = StudygroupCourse::findBySQL($sql, [$range_id]); + } + + } diff --git a/app/controllers/course/wizard.php b/app/controllers/course/wizard.php index b4244b6..ad289ed 100644 --- a/app/controllers/course/wizard.php +++ b/app/controllers/course/wizard.php @@ -64,7 +64,7 @@ class Course_WizardController extends AuthenticatedController */ public function index_action() { - $this->redirect('course/wizard/step/0' . ($this->studygroup ? '?studygroup=1' : '')); + $this->redirect('course/wizard/step/0' . ($this->studygroup ? '?studygroup=1&stgteil_id='.Request::option('stgteil_id') : '')); } /** @@ -93,7 +93,10 @@ class Course_WizardController extends AuthenticatedController // Add special studygroup flag to set values. $this->setStepValues( get_class($step), - array_merge($this->getValues(get_class($step)), ['studygroup' => 1]) + array_merge($this->getValues(get_class($step)), [ + 'studygroup' => 1, + 'stgteil_id' => Request::option('stgteil_id') + ]) ); } $this->values = $this->getValues(); @@ -193,11 +196,13 @@ class Course_WizardController extends AuthenticatedController } // A studygroup has been created. if (in_array($this->course->status, studygroup_sem_types())) { + $message = MessageBox::success(sprintf( - _('Die Studien-/Arbeitsgruppe "%s" wurde angelegt. ' + _('Die Studien-/Arbeitsgruppe „%s“ wurde angelegt. ' . 'Sie können sie direkt hier weiter verwalten.'), htmlReady($this->course->name) )); + $target = $this->url_for('course/studygroup/edit', ['cid' => $this->course->id]); // "Normal" course. diff --git a/app/controllers/my_courses.php b/app/controllers/my_courses.php index c7a6771..a8d9797 100644 --- a/app/controllers/my_courses.php +++ b/app/controllers/my_courses.php @@ -704,17 +704,19 @@ class MyCoursesController extends AuthenticatedController } } - $groups[] = [ - 'id' => $_outer_index, - 'name' => (string) $sem_data[$_outer_index]['name'], - 'data' => [ - [ - 'id' => md5($_outer_index), - 'label' => false, - 'ids' => array_keys($_courses), + if ($_outer_index) { + $groups[] = [ + 'id' => $_outer_index, + 'name' => (string)$sem_data[$_outer_index]['name'], + 'data' => [ + [ + 'id' => md5($_outer_index), + 'label' => false, + 'ids' => array_keys($_courses), + ], ], - ], - ]; + ]; + } $temp_courses = array_merge($temp_courses, $_courses); } else { $count = 1; @@ -747,11 +749,13 @@ class MyCoursesController extends AuthenticatedController $temp_courses = array_merge($temp_courses, $_courses); } - $groups[] = [ - 'id' => $_outer_index, - 'name' => (string) $sem_data[$_outer_index]['name'], - 'data' => $_groups, - ]; + if ($_outer_index) { + $groups[] = [ + 'id' => $_outer_index, + 'name' => (string)$sem_data[$_outer_index]['name'], + 'data' => $_groups, + ]; + } } } } diff --git a/app/controllers/my_studygroups.php b/app/controllers/my_studygroups.php index 579d47e..ac55cf4 100644 --- a/app/controllers/my_studygroups.php +++ b/app/controllers/my_studygroups.php @@ -10,15 +10,28 @@ class MyStudygroupsController extends AuthenticatedController } } - public function index_action() + public function index_action($is_widget = false) { PageLayout::setHelpKeyword('Basis.MeineStudiengruppen'); PageLayout::setTitle(_('Meine Studiengruppen')); URLHelper::removeLinkParam('cid'); - $this->studygroups = MyRealmModel::getStudygroups(); + $this->is_widget = (bool)$is_widget; + $this->studygroups = StudygroupModel::getStudygroups(); $this->nav_elements = MyRealmModel::calc_single_navigation($this->studygroups); - $this->set_sidebar(); + + // do not render sidebar if this is the widget + if (!$this->is_widget) { + $this->set_sidebar(); + } + } + + public function proposals_action() + { + PageLayout::setHelpKeyword('Basis.MeineStudiengruppen'); + PageLayout::setTitle(_('Meine Studiengruppen')); + URLHelper::removeLinkParam('cid'); + $this->proposed_studygroups = $this->proposeStudygroups(); } public function set_sidebar() @@ -44,4 +57,85 @@ class MyStudygroupsController extends AuthenticatedController } $sidebar->addWidget($actions); } + + public function proposeStudygroups($user_id = null, $amount = 4) + { + $user_id ??= User::findCurrent()->id; + $cache_id = 'core/studygroups/proposals/' . $user_id; + $cache = \Studip\Cache\Factory::getCache(); + $studygroup_ids = $cache->read($cache_id); + if ($studygroup_ids !== false) { + return Course::findMany($studygroup_ids); + } + + // Vorgeschlagen werden sollen Studiengruppen, + // a) in denen Personen sitzen, die auch in anderen Veranstaltungen sitzen, in denen der aktive Nutzer Mitglied ist + // b) die zu dem Studienbereich des Studierenden gehören + // c) die einfach neu sind + // und die zudem aktiv sind. Es wird eine Liste von 36 Studiengruppen gebaut, wovon drei alle 15 Minuten im Widget + // angezeigt werden. + + $studygroup_sem_types = array_filter( + array_keys($GLOBALS['SEM_TYPE']), + function ($sem_type_id) { + return (bool) $GLOBALS['SEM_CLASS'][$GLOBALS['SEM_TYPE'][$sem_type_id]['class']]['studygroup_mode']; + } + ); + + $statement = DBManager::get()->prepare(" + SELECT `Seminar_id` FROM ( + SELECT `seminare`.`Seminar_id`, COUNT(`seminar_user`.`user_id`) AS `count_colleages` + FROM `seminar_user` AS `my_courses` + LEFT JOIN `seminar_user` AS `my_colleages` ON (`my_colleages`.`Seminar_id` = `my_courses`.`Seminar_id`) + LEFT JOIN `seminar_user` ON (`my_colleages`.`user_id` = `seminar_user`.`user_id`) + LEFT JOIN `seminar_user` AS `am_i_connected` ON (`seminar_user`.`Seminar_id` = `am_i_connected`.`Seminar_id` AND `am_i_connected`.`user_id` = :me) + LEFT JOIN `seminare` ON (`seminare`.`Seminar_id` = `seminar_user`.`Seminar_id`) + WHERE `seminare`.`status` IN (:studygroup_types) + AND `am_i_connected`.`user_id` IS NULL + AND `my_courses`.`user_id` = :me + GROUP BY `seminare`.`seminar_id` + ORDER BY `count_colleages` DESC + LIMIT 12 + ) AS `colleages_groups` + + UNION SELECT `Seminar_id` FROM ( + SELECT `seminare`.`Seminar_id` + FROM `seminare` + LEFT JOIN `seminar_user` AS `am_i_connected` ON (`am_i_connected`.`Seminar_id` = `seminare`.`Seminar_id` AND `am_i_connected`.`user_id` = :me) + INNER JOIN `studygroup_stgteil` ON (`studygroup_stgteil`.`studygroup_id` = `seminare`.`Seminar_id`) + INNER JOIN `mvv_stgteil` ON (`studygroup_stgteil`.`stgteil_id` = `mvv_stgteil`.`stgteil_id`) + INNER JOIN `user_studiengang` ON (`user_studiengang`.`fach_id` = `mvv_stgteil`.`fach_id`) + INNER JOIN `mvv_stg_stgteil` ON (`mvv_stg_stgteil`.`stgteil_id` = `mvv_stgteil`.`stgteil_id`) + INNER JOIN `mvv_studiengang` ON (`mvv_studiengang`.`studiengang_id` = `mvv_stg_stgteil`.`studiengang_id` + AND `mvv_studiengang`.`abschluss_id` = `user_studiengang`.`abschluss_id`) + WHERE `am_i_connected`.`user_id` IS NULL + AND `seminare`.`status` IN (:studygroup_types) + AND `user_studiengang`.`user_id` = :me + ORDER BY rand() + LIMIT 12 + ) AS `same_studyarea_groups` + + UNION SELECT `Seminar_id` FROM ( + SELECT `seminare`.`Seminar_id` + FROM `seminare` + LEFT JOIN `seminar_user` AS `am_i_connected` ON (`am_i_connected`.`Seminar_id` = `seminare`.`Seminar_id` AND `am_i_connected`.`user_id` = :me) + WHERE `am_i_connected`.`user_id` IS NULL + AND `seminare`.`status` IN (:studygroup_types) + ORDER BY `seminare`.`mkdate` DESC + LIMIT 12 + ) AS `new_groups` + + GROUP BY `Seminar_id` + ORDER BY rand() + LIMIT :amount + "); + $statement->execute([ + 'studygroup_types' => $studygroup_sem_types, + 'me' => $user_id, + 'amount' => $amount + ]); + $group_ids = $statement->fetchAll(PDO::FETCH_COLUMN); + $cache->write($cache_id, $group_ids, 15 * 60); + return Course::findMany($group_ids); + } } diff --git a/app/controllers/search/angebot.php b/app/controllers/search/angebot.php index 0c0f402..4f749d4 100644 --- a/app/controllers/search/angebot.php +++ b/app/controllers/search/angebot.php @@ -179,4 +179,16 @@ class Search_AngebotController extends MVVController $this->content = $response->body; $this->render_template('shared/content', $this->layout); } + + public function remove_studygroup_action($course_id, $stgteil_id) + { + CSRFProtection::verifyUnsafeRequest(); + if (!$GLOBALS['perm']->have_studip_perm('tutor', $course_id) && !$GLOBALS['perm']->have_perm('admin')) { + throw new AccessDeniedException(); + } + StudygroupStgteil::deleteBySQL('`studygroup_id` = ? AND `stgteil_id` = ?', [$course_id, $stgteil_id]); + PageLayout::postSuccess(_('Zuordnung wurde aufgehoben.')); + $stgteil = StudiengangTeil::find($stgteil_id); + $this->redirect('search/angebot/studiengang/'.$stgteil->studiengang[0]->id); + } } diff --git a/app/controllers/search/globalsearch.php b/app/controllers/search/globalsearch.php index b31663f..fd82103 100644 --- a/app/controllers/search/globalsearch.php +++ b/app/controllers/search/globalsearch.php @@ -107,6 +107,17 @@ class Search_GlobalsearchController extends AuthenticatedController ), 'institute_filter' ); + + $filter_widget->addElement( + new SelectListElement( + _('Studiengang'), + 'study_course', + $this->getStudyCourses(), + '', + ['id' => 'study_course_select'] + ), + 'study_course_filter' + ); } /** @@ -171,6 +182,23 @@ class Search_GlobalsearchController extends AuthenticatedController } /** + * @return array + */ + private function getStudyCourses() + { + + $this->user = User::findCurrent(); + $study_courses = []; + + foreach ($this->user->studycourses as $usc) + { + $study_courses[] = $usc->studycourse->name; + } + + return $study_courses; + } + + /** * Add some information on how to use the search. */ private function addInfoText() diff --git a/app/controllers/search/studiengaenge.php b/app/controllers/search/studiengaenge.php index 8f08c32..1745ba6 100644 --- a/app/controllers/search/studiengaenge.php +++ b/app/controllers/search/studiengaenge.php @@ -200,12 +200,12 @@ class Search_StudiengaengeController extends MVVController $this->with_courses = Request::option('with_courses', $_SESSION['MVV_SEARCH_SEQUENCE_WITH_COURSES'] ?? null); $_SESSION['MVV_SEARCH_SEQUENCE_WITH_COURSES'] = $this->with_courses; - $studiengangTeil = StudiengangTeil::find($stgteil_id); + $this->studiengangTeil = StudiengangTeil::find($stgteil_id); $versionen = StgteilVersion::findByStgteil($stgteil_id, 'start', 'DESC')->filter(function ($version) { $public = $GLOBALS['MVV_STGTEILVERSION']['STATUS']['values'][$version->stat]['public']; return (bool) $public; }); - if (!$studiengangTeil || count($versionen) === 0) { + if (!$this->studiengangTeil || count($versionen) === 0) { PageLayout::postInfo(_('Kein Verlaufsplan im gewählten Bereich verfügbar.')); } else { $version_id = Request::option('version', $this->sessGet('selected_version')); @@ -307,26 +307,24 @@ class Search_StudiengaengeController extends MVVController if ($studiengang_id) { if ($stgteil_bez_id) { $this->stgTeilBez = StgteilBezeichnung::get($stgteil_bez_id); - $this->breadcrumb->append([$this->stgTeilBez, $studiengangTeil], 'verlauf'); + $this->breadcrumb->append([$this->stgTeilBez, $this->studiengangTeil], 'verlauf'); } else { - $this->breadcrumb->append($studiengangTeil, 'verlauf'); + $this->breadcrumb->append($this->studiengangTeil, 'verlauf'); } $this->studiengang = Studiengang::get($studiengang_id); } $this->setVersionSelectWidget( $versionen, - $this->action_url('verlauf', $studiengangTeil->id, $stgteil_bez_id, $studiengang_id) + $this->action_url('verlauf', $this->studiengangTeil->id, $stgteil_bez_id, $studiengang_id) ); ksort($fachsemesterData); $this->fachsemesterData = $fachsemesterData; $this->abschnitteData = $abschnitteData; $this->versionen = $versionen; - // Augsburg // Ausgabe des Namens ohne Fach (dieses ist im Zusatz bereits enthalten) - // $this->studiengangTeilName = $studiengangTeil->getDisplayName(0); - $this->studiengangTeilName = $studiengangTeil->getDisplayName(); + $this->studiengangTeilName = $this->studiengangTeil->getDisplayName(); // add option widget to show only modules with courses in the // selected semester |
