aboutsummaryrefslogtreecommitdiff
path: root/app/controllers/course/statusgroups.php
diff options
context:
space:
mode:
authorJan-Hendrik Willms <tleilax+github@gmail.com>2021-07-22 16:07:19 +0200
committerJan-Hendrik Willms <tleilax+github@gmail.com>2021-07-22 16:19:12 +0200
commita3da1483a9e689846179159355badfec8073dbec (patch)
tree770dcca6bdf5f6f2a11b0e7fcbbeda6919a3fc52 /app/controllers/course/statusgroups.php
current code from svn, revision 62608
Diffstat (limited to 'app/controllers/course/statusgroups.php')
-rw-r--r--app/controllers/course/statusgroups.php1425
1 files changed, 1425 insertions, 0 deletions
diff --git a/app/controllers/course/statusgroups.php b/app/controllers/course/statusgroups.php
new file mode 100644
index 0000000..f8378bc
--- /dev/null
+++ b/app/controllers/course/statusgroups.php
@@ -0,0 +1,1425 @@
+<?php
+/**
+ * StatusgroupController
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * @author Thomas Hackl <thomas.hackl@uni-passau.de>
+ * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2
+ * @category Stud.IP
+ * @since 3.5
+ */
+
+require_once 'lib/messaging.inc.php'; //Funktionen des Nachrichtensystems
+require_once 'lib/export/export_studipdata_func.inc.php'; // Funktionen für den Export
+require_once 'lib/export/export_linking_func.inc.php';
+
+class Course_StatusgroupsController extends AuthenticatedController
+{
+ public function before_filter(&$action, &$args)
+ {
+ parent::before_filter($action, $args);
+
+ global $perm;
+
+ checkObject();
+ checkObjectModule("participants");
+
+ $course = Course::findCurrent();
+ $this->course_id = $course->id;
+ $this->course_title = $course->getFullname();
+ $this->config = CourseConfig::get($this->course_id);
+
+ // Check perms
+ $this->is_dozent = $perm->have_studip_perm('dozent', $this->course_id);
+ $this->is_tutor = $perm->have_studip_perm('tutor', $this->course_id);
+ $this->is_autor = $perm->have_studip_perm('autor', $this->course_id);
+
+ // Hide groups page?
+ if (!$this->is_tutor && $this->config->COURSE_MEMBERGROUPS_HIDE) {
+ throw new AccessDeniedException();
+ }
+
+ // Check lock rules
+ $this->is_locked = LockRules::Check($this->course_id, 'groups');
+ $this->is_participants_locked = LockRules::Check($this->course_id, 'participants');
+
+ PageLayout::setTitle(sprintf('%s - %s', Course::findCurrent()->getFullname(), _('Gruppen')));
+ PageLayout::addStyleSheet('studip-statusgroups.css');
+ PageLayout::addScript('studip-statusgroups.js');
+ }
+
+ /**
+ * Lists all available statusgroups.
+ */
+ public function index_action()
+ {
+ Navigation::activateItem('/course/members/statusgroups');
+
+ if ($this->is_locked && $this->is_tutor) {
+ $lockdata = LockRules::getObjectRule($this->course_id);
+ if ($lockdata['description']) {
+ PageLayout::postInfo(formatLinks($lockdata['description']));
+ }
+ }
+
+ // Sorting as given by Request parameters
+ $this->sort_by = Request::option('sortby', 'nachname');
+ $this->order = Request::option('order', 'desc');
+ $this->sort_group = Request::get('sort_group', '');
+ $this->open_groups = Request::get('open_groups');
+
+ // Get all course members (needed for mkdate).
+ $this->allmembers = SimpleCollection::createFromArray(
+ CourseMember::findByCourse($this->course_id));
+
+ // Find all statusgroups for this course.
+ $groups = Statusgruppen::findBySeminar_id($this->course_id);
+
+ /*
+ * Check if the current user may join any group at all. This is needed
+ * for deciding if a Sidebar action for joining a group will be
+ * displayed.
+ */
+ $joinable = false;
+
+ // Fetch membercounts (all at once for performance)
+ $membercounts = array_column(DBManager::get()->fetchAll(
+ "SELECT u.`statusgruppe_id`, COUNT(u.`user_id`) as membercount
+ FROM `statusgruppen` s
+ JOIN `statusgruppe_user` u USING (`statusgruppe_id`)
+ WHERE s.`range_id` = ?
+ GROUP BY `statusgruppe_id`
+ ORDER BY s.`position` ASC, s.`name` ASC",
+ [$this->course_id]),
+ 'membercount',
+ 'statusgruppe_id'
+ );
+
+
+ // Now build actual groups.
+ $this->groups = [];
+ foreach ($groups as $g) {
+ $groupdata = [
+ 'group' => $g,
+ 'members' => [],
+ 'membercount' => $membercounts[$g->id] ?: 0,
+ 'invisible_users' => 0
+ ];
+
+ /*
+ * We only need to load members for a group that shall be sorted
+ * explicitly, as this group will be loaded at once and not via AJAX.
+ */
+ if ($g->id == $this->sort_group || $this->open_groups) {
+ if ($this->sort_group == $g->id) {
+ $sorted = $this->sortMembers($g->members, $this->sort_by, $this->order);
+ } else {
+ $sorted = $sorted = $this->sortMembers($g->members);
+ }
+
+ foreach ($sorted as $member) {
+ //Note: $member is a StatusgruppeUser object.
+ //We must get the CourseMember object to correctly
+ //determine the visibility of the user.
+ $course_member = CourseMember::findOneBySql(
+ 'user_id = :user_id AND seminar_id = :course_id',
+ ['user_id' => $member->user_id, 'course_id' => $member->group->range_id]
+ );
+ if ($this->is_tutor || ($member->user_id == $GLOBALS['user']->id) ||
+ ($course_member->visible != 'no')) {
+ $groupdata['members'][] = $member;
+ } else {
+ $groupdata['invisible_users']++;
+ }
+ }
+ $groupdata['load'] = true;
+ }
+
+ if (!$this->is_tutor && $g->userMayJoin($GLOBALS['user']->id)) {
+ $groupdata['joinable'] = true;
+ $joinable = true;
+ }
+
+ $this->groups[] = $groupdata;
+ }
+
+ /*
+ * Get number of users that are in no group, this is needed
+ * for displaying in group header.
+ */
+ $ungrouped_count = DBManager::get()->fetchFirst(
+ "SELECT COUNT(s.`user_id`) FROM `seminar_user` s WHERE s.`Seminar_id` = :course AND NOT EXISTS (
+ SELECT u.`user_id` FROM `statusgruppe_user` u
+ WHERE u.`statusgruppe_id` IN (:groups) AND u.`user_id` = s.`user_id`)",
+ [
+ 'course' => $this->course_id,
+ 'groups' => DBManager::get()->fetchFirst(
+ "SELECT `statusgruppe_id` FROM `statusgruppen` WHERE `range_id` = ?",
+ [$this->course_id])
+ ]);
+ $ungrouped_count = $ungrouped_count[0];
+ if ($ungrouped_count > 0) {
+ // Create dummy entry for "no group" users.
+ $no_group = new StdClass();
+ $no_group->id = 'nogroup';
+ $no_group->name = _('keiner Gruppe zugeordnet');
+ $no_group->size = 0;
+ $no_group->selfassign = 0;
+
+ $groupdata = [
+ 'group' => $no_group,
+ 'membercount' => $ungrouped_count,
+ 'joinable' => false,
+ 'invisible_users' => 0,
+ 'members' => []
+ ];
+
+ $nogroupmembers = DBManager::get()->fetchFirst("SELECT user_id
+ FROM seminar_user
+ WHERE `Seminar_id` = :course AND NOT EXISTS (
+ SELECT `user_id` FROM `statusgruppe_user`
+ WHERE `statusgruppe_id` IN (:groups) AND `user_id` = seminar_user.`user_id`)",
+ [
+ 'course' => $this->course_id,
+ 'groups' => array_map(function ($g) { return $g->id; }, $groups)
+ ]);
+
+
+ $this->nogroupmembers = $nogroupmembers;
+
+ if ($this->sort_group == 'nogroup') {
+ $members = $this->allmembers->findby('user_id', $nogroupmembers);
+ $members = $this->sortMembers($members, $this->sort_by, $this->order);
+ $groupdata['load'] = true;
+ } else {
+ $members = $this->allmembers->findby(
+ 'user_id',
+ $this->nogroupmembers
+ );
+ }
+
+ if(!empty($members)) {
+ foreach ($members as $member) {
+ //Note: $member is a CourseMember object here.
+ if ($this->is_tutor || ($member->user_id == $GLOBALS['user']->id) ||
+ ($member->visible != 'no')) {
+ $groupdata['members'][] = $member;
+ } else {
+ $groupdata['invisible_users']++;
+ }
+ }
+ }
+ $this->groups[] = $groupdata;
+ }
+
+ // Prepare search object for MultiPersonSearch.
+ $this->memberSearch = new PermissionSearch(
+ 'user',
+ _('Personen suchen'),
+ 'user_id',
+ [
+ 'permission' => ['user', 'autor', 'tutor', 'dozent'],
+ 'exclude_user' => []
+ ]
+ );
+
+ /*
+ * Setup sidebar.
+ */
+ $sidebar = Sidebar::get();
+
+ $actions = new ActionsWidget();
+ if ($this->is_tutor) {
+ if (!$this->is_locked) {
+ $actions->addLink(
+ _('Neue Gruppe anlegen'),
+ $this->url_for('course/statusgroups/edit'),
+ Icon::create('add')
+ )->asDialog('size=auto');
+ $actions->addLink(
+ _('Mehrere Gruppen anlegen'),
+ $this->url_for('course/statusgroups/create_groups'),
+ Icon::create('group2+add')
+ )->asDialog('size=auto');
+ }
+ if (Config::get()->EXPORT_ENABLE) {
+ $export = new ExportWidget();
+ // create csv-export link
+ $csvExport = export_link($this->course_id, 'person',
+ sprintf('%s %s', _('Gruppenliste'), htmlReady($this->course_title)),
+ 'csv', 'csv-gruppen', 'status',
+ _('Gruppen als CSV-Dokument exportieren'),
+ 'passthrough');
+ $element = LinkElement::fromHTML($csvExport, Icon::create('file-office'));
+ $export->addElement($element);
+
+ // create rtf-export link
+ $rtfExport = export_link($this->course_id, 'person',
+ sprintf('%s %s', _('Gruppenliste'), htmlReady($this->course_title)),
+ 'rtf', 'rtf-gruppen', 'status',
+ _('Gruppen als RTF-Dokument exportieren'),
+ 'passthrough');
+ $element = LinkElement::fromHTML($rtfExport, Icon::create('file-text'));
+ $export->addElement($element);
+
+ $sidebar->addWidget($export);
+ }
+ // Current user may join at least one group => show sidebar action.
+ } else if ($joinable) {
+ $actions->addLink(
+ _('In eine Gruppe eintragen'),
+ $this->url_for('course/statusgroups/joinables'),
+ Icon::create('door-enter')
+ )->asDialog('size=auto');
+ }
+
+ if ($this->open_groups) {
+ $actions->addLink(
+ _('Alle Gruppen zuklappen'),
+ $this->url_for('course/statusgroups'),
+ Icon::create('arr_2up')
+ );
+ } else {
+ $actions->addLink(
+ _('Alle Gruppen aufklappen'),
+ $this->url_for('course/statusgroups', ['open_groups' => '1']),
+ Icon::create('arr_2down')
+ );
+ }
+ $sidebar->addWidget($actions);
+
+ if ($this->is_tutor) {
+ $options = $sidebar->addWidget(new OptionsWidget());
+ $options->addCheckbox(
+ _('Diese Seite für Studierende verbergen'),
+ $this->config->COURSE_MEMBERGROUPS_HIDE,
+ $this->url_for('course/statusgroups/course_groups_hide/1'),
+ $this->url_for('course/statusgroups/course_groups_hide/0'),
+ ['title' => _('Über diese Option können Sie die Teilnehmendengruppenliste für Studierende der Veranstaltung unsichtbar machen')]
+ );
+
+ }
+ }
+
+ /**
+ * Fetches the members of the given group.
+ *
+ * @param String $group_id the statusgroup to get members for.
+ */
+ public function getgroup_action($group_id)
+ {
+ if ($group_id != 'nogroup') {
+ $this->group = Statusgruppen::find($group_id);
+ $this->members = [];
+ $this->invisible = 0;
+ if (count($this->group->members) > 0) {
+ //Note: $members consists of StatusgruppeUser objects here.
+ $members = $this->sortMembers($this->group->members);
+ foreach ($members as $member) {
+ //Get the course member object to check the visibility
+ //in the course.
+ $course_member = CourseMember::findOneBySql(
+ 'user_id = :user_id AND seminar_id = :course_id',
+ ['user_id' => $member->user_id, 'course_id' => $member->group->range_id]
+ );
+ if ($this->is_tutor || ($member->user_id == $GLOBALS['user']->id)
+ || ($course_member->visible != 'no')) {
+ $this->members[] = $member;
+ } else {
+ $this->invisible++;
+ }
+ }
+ }
+ } else {
+ // Create dummy entry for "no group" users.
+ $no_group = new StdClass();
+ $no_group->id = 'nogroup';
+ $no_group->name = _('keiner Gruppe zugeordnet');
+ $no_group->size = 0;
+ $no_group->selfassign = 0;
+
+ $members = DBManager::get()->fetchAll("SELECT seminar_user.*,
+ aum.vorname,aum.nachname,aum.email,
+ aum.username,ui.title_front,ui.title_rear
+ FROM seminar_user
+ LEFT JOIN auth_user_md5 aum USING (user_id)
+ LEFT JOIN user_info ui USING (user_id)
+ WHERE `Seminar_id` = :course AND NOT EXISTS (
+ SELECT `user_id` FROM `statusgruppe_user`
+ WHERE `statusgruppe_id` IN (:groups) AND `user_id` = seminar_user.`user_id`)",
+ [
+ 'course' => $this->course_id,
+ 'groups' => DBManager::get()->fetchFirst(
+ "SELECT `statusgruppe_id` FROM `statusgruppen` WHERE `range_id` = ?",
+ [$this->course_id])
+ ], 'CourseMember::buildExisting');
+
+ $members = new SimpleCollection($members);
+ //Note: $members consists of CourseMember objects here.
+ $members = $this->sortMembers($members);
+ $this->invisible = 0;
+ $this->members = [];
+ foreach ($members as $member) {
+ if ($this->is_tutor || ($member->user_id == $GLOBALS['user']->id)
+ || ($member->visible != 'no')) {
+ $this->members[] = $member;
+ } else {
+ $this->invisible++;
+ }
+ }
+
+ $this->group = $no_group;
+ }
+ }
+
+ /**
+ * Provides extended info about a status group, like maximum number of
+ * participants, selfassign, exclusive entry, selfassign start and end
+ * times.
+ *
+ * @param String $group_id The group to show info for.
+ */
+ public function groupinfo_action($group_id)
+ {
+ $this->group = Statusgruppen::find($group_id);
+
+ // Topics can be implicitly assigned via course dates.
+ $this->topics = $this->group->findTopics();
+
+ // Lecturers can be implicitly assigned via course dates.
+ $this->lecturers = $this->group->findLecturers();
+ }
+
+ /**
+ * Shows a list of all groups that can be joined by current user
+ * and allows the user to select one.
+ */
+ public function joinables_action()
+ {
+ $this->joinables = SimpleCollection::createFromArray(
+ Statusgruppen::findJoinableGroups($this->course_id, $GLOBALS['user']->id))
+ ->orderBy('position asc, name asc');
+ }
+
+ /**
+ * Adds selected persons to given group. user_ids to add come from a
+ * MultiPersonSearch object which was triggered in group actions.
+ *
+ * @param String $group_id
+ */
+ public function add_member_action($group_id)
+ {
+ $g = Statusgruppen::find($group_id);
+
+ // Get selected persons.
+ $mp = MultiPersonSearch::load('add_statusgroup_member' . $group_id);
+
+ $success = 0;
+ $fail = 0;
+
+ foreach ($mp->getAddedUsers() as $a) {
+
+ if (!CourseMember::exists([$this->course_id, $a])) {
+ $m = new CourseMember();
+ $m->seminar_id = $this->course_id;
+ $m->user_id = $a;
+ $m->status = User::find($a)->perms == 'user' ? 'user' : 'autor';
+ $m->store();
+ }
+
+ $s = new StatusgruppeUser();
+ $s->statusgruppe_id = $group_id;
+ $s->user_id = $a;
+ if ($s->store() !== false) {
+ $success++;
+ } else {
+ $fail++;
+ }
+ }
+
+ if ($success > 0 && $fail == 0) {
+ PageLayout::postSuccess(sprintf(ngettext(
+ '%u Person wurde zu %s hinzugefügt.',
+ '%u Personen wurden zu %s hinzugefügt.',
+ $success), $success, htmlReady($g->name)
+ ));
+ } else if ($success > 0 && $fail > 0) {
+ $successMsg = sprintf(ngettext(
+ '%u Person wurde zu %s hinzugefügt.',
+ '%u Personen wurden zu %s hinzugefügt.',
+ $success), $success, htmlReady($g->name)
+ );
+ $failMsg = sprintf(ngettext(
+ '%u Person konnte nicht zu %s hinzugefügt werden.',
+ '%u Personen konnten nicht zu %s hinzugefügt werden.',
+ $fail), $fail, htmlReady($g->name)
+ );
+ PageLayout::postWarning($successMsg . ' ' . $failMsg);
+ } else if ($success == 0 && $fail > 0) {
+ PageLayout::postError(sprintf(ngettext(
+ '%u Person konnte nicht zu %s hinzugefügt werden.',
+ '%u Personen konnten nicht zu %s hinzugefügt werden.',
+ $success), $success, htmlReady($g->name)
+ ));
+ }
+
+ $this->relocate('course/statusgroups');
+
+ }
+
+ /**
+ * Allows editing of a given statusgroup or creating a new one.
+ * @param String $group_id ID of the group to edit
+ * @throws AccessDeniedException if access not allowed with current permission level.
+ */
+ public function edit_action($group_id = '')
+ {
+ if (!$this->is_tutor) {
+ throw new AccessDeniedException();
+ }
+
+ // Fetch group with given ID or create a new one.
+ $this->group = new Statusgruppen($group_id);
+
+ // Check if course has regular times.
+ $this->cycles = SeminarCycleDate::findBySeminar_id($this->course_id);
+
+ // Check if course has single dates, not belonging to a regular cycle.
+ $dates = CourseDate::findBySeminar_id($this->course_id);
+ $this->singledates = array_filter($dates, function ($d) {
+ return !((bool) $d->metadate_id);
+ });
+ }
+
+ /**
+ * Saves changes to given statusgroup or creates a new entry.
+ *
+ * @param String $group_id ID of the group to edit
+ * @throws AccessDeniedException if access not allowed with current permission level.
+ */
+ public function save_action($group_id = '')
+ {
+ if (!$this->is_tutor) {
+ throw new AccessDeniedException();
+ }
+
+ CSRFProtection::verifyUnsafeRequest();
+ /*
+ * Check if a valid end time was given.
+ */
+ if (Request::int('selfassign', 0)) {
+ $endtime = strtotime(Request::get('selfassign_end', 'now'));
+ $starttime = strtotime(Request::get('selfassign_start', 'now'));
+ if ($endtime <= $starttime) {
+ $endtime = 0;
+ }
+ }
+ $position = Statusgruppen::find($group_id)->position;
+ $group = Statusgruppen::createOrUpdate(
+ $group_id,
+ Request::get('name'),
+ $position,
+ $this->course_id, Request::int('size', 0),
+ Request::int('selfassign', 0) + Request::int('exclusive', 0),
+ strtotime(Request::get('selfassign_start', 'now')),
+ Request::get('selfassign_end') ? strtotime(Request::get('selfassign_end')) : 0,
+ Request::int('makefolder', 0),
+ Request::getArray('dates')
+ );
+
+ if (!$group_id) {
+ PageLayout::postSuccess(sprintf(
+ _('Die Gruppe "%s" wurde angelegt.'),
+ htmlReady($group->name)
+ ));
+ } else {
+ PageLayout::postSuccess(sprintf(
+ _('Die Daten der Gruppe "%s" wurden gespeichert.'),
+ htmlReady($group->name)
+ ));
+ }
+
+ $thread = BlubberStatusgruppeThread::findByStatusgruppe_id($group->id);
+ if (Request::get("blubber") && !$thread) {
+ $thread = new BlubberStatusgruppeThread();
+ $thread['context_type'] = "course";
+ $thread['context_id'] = $this->course_id;
+ $thread['user_id'] = $GLOBALS['user']->id;
+ $thread['external_contact'] = 0;
+ $thread['visible_in_stream'] = 1;
+ $thread['display_class'] = "BlubberStatusgruppeThread";
+ $thread['commentable'] = 1;
+ $thread['metadata'] = ['statusgruppe_id' => $group->id];
+ $thread->store();
+ } elseif(!Request::get("blubber") && $thread) {
+ $thread->delete();
+ }
+
+ $this->relocate('course/statusgroups');
+ }
+
+ /**
+ * Deletes the given statusgroup.
+ *
+ * @param String $group_id ID of the group to delete
+ * @throws AccessDeniedException if access not allowed with current permission level.
+ */
+ public function delete_action($group_id)
+ {
+ if (!$this->is_tutor) {
+ throw new AccessDeniedException();
+ }
+
+ $group = Statusgruppen::find($group_id);
+ $groupname = $group->name;
+ $group->delete();
+ PageLayout::postSuccess(sprintf(
+ _('Die Gruppe "%s" wurde gelöscht.'),
+ htmlReady($groupname)
+ ));
+ $this->relocate('course/statusgroups');
+ }
+
+ /**
+ * Removes the given user from the given statusgroup.
+ *
+ * @param String $user_id user to remove
+ * @param String $group_id affected group
+ */
+ public function delete_member_action($user_id, $group_id)
+ {
+ $g = Statusgruppen::find($group_id);
+ if (!$this->is_tutor && ($user_id !== $GLOBALS['user']->id || !$g->userMayLeave($user_id))) {
+ throw new AccessDeniedException();
+ }
+
+ $s = StatusgruppeUser::find([$group_id, $user_id]);
+ $name = $s->user->getFullname();
+ if ($s->delete()) {
+ if ($user_id == $GLOBALS['user']->id) {
+ PageLayout::postSuccess(sprintf(
+ _('Sie wurden aus der Gruppe %s ausgetragen.'),
+ htmlReady($g->name)
+ ));
+ } else {
+ PageLayout::postSuccess(sprintf(
+ _('%s wurde aus der Gruppe %s ausgetragen.'),
+ htmlReady($name),
+ htmlReady($g->name)
+ ));
+ }
+ } else {
+ if ($user_id == $GLOBALS['user']->id) {
+ PageLayout::postError(sprintf(
+ _('Sie konnten nicht aus der Gruppe %s ausgetragen werden.'),
+ htmlReady($g->name)
+ ));
+ } else {
+ PageLayout::postError(sprintf(
+ _('%s konnte nicht aus der Gruppe %s ausgetragen werden.'),
+ htmlReady($name),
+ htmlReady($g->name)
+ ));
+ }
+ }
+ $this->relocate('course/statusgroups');
+ }
+
+ public function move_member_action($user_id, $group_id)
+ {
+ if (!$this->is_tutor) {
+ throw new AccessDeniedException();
+ }
+
+ $this->source_group = $group_id;
+
+ $this->members = [$user_id];
+
+ // Find possible target groups.
+ $this->target_groups = SimpleCollection::createFromArray(
+ Statusgruppen::findByRange_id($this->course_id))
+ ->orderBy('position, name')
+ ->filter(function ($g) use ($group_id) { return $g->id != $group_id; });
+ }
+
+ /**
+ * Provides the possibility to batch create several groups at once.
+ *
+ * @throws AccessDeniedException if access not allowed with current permission level.
+ */
+ public function create_groups_action()
+ {
+ if (!$this->is_tutor) {
+ throw new AccessDeniedException();
+ }
+
+ // Check if course has regular times.
+ $this->has_cycles = count(SeminarCycleDate::findBySeminar_id($this->course_id)) > 0;
+
+ // Check if course has single dates, not belonging to a regular cycle.
+ $dates = CourseDate::findBySeminar_id($this->course_id);
+ $this->has_singledates = count(array_filter($dates, function ($d) {
+ return !((bool) $d->metadate_id);
+ })) > 0;
+
+ // Check if course has topics.
+ $topics = CourseTopic::findBySeminar_id($this->course_id);
+ $paper_topics = array_filter($topics, function ($topic) {
+ return $topic->paper_related;
+ });
+
+ $this->has_topics = count($topics) > 0;
+ $this->has_paper_related_topics = count($paper_topics) > 0;
+ }
+
+ /**
+ * Adds the current user to the given group.
+ *
+ * @throws AccessDeniedException if current user may not join the given group.
+ */
+ public function join_action($group_id = '')
+ {
+
+ // group_id can also be given per request.
+ if (!$group_id) {
+ CSRFProtection::verifyUnsafeRequest();
+ $group_id = Request::option('target_group');
+
+ // Safety check if no group_id at all.
+ if (!$group_id) {
+ throw new Trails_Exception(400);
+ }
+ }
+
+ $g = Statusgruppen::find($group_id);
+
+ if (!$g->userMayJoin($GLOBALS['user']->id)) {
+ throw new AccessDeniedException();
+ }
+
+ $s = new StatusgruppeUser();
+ $s->user_id = $GLOBALS['user']->id;
+ $s->statusgruppe_id = $group_id;
+ if ($s->store()) {
+ PageLayout::postSuccess(sprintf(
+ _('Sie wurden als Mitglied der Gruppe %s eingetragen.'),
+ htmlReady($g->name)
+ ));
+ } else {
+ PageLayout::postError(sprintf(
+ _('Sie konnten nicht als Mitglied der Gruppe %s eingetragen werden.'),
+ htmlReady($g->name)
+ ));
+ }
+
+ $this->relocate('course/statusgroups');
+ }
+
+ /**
+ * Removes the current user from the given group.
+ *
+ * @throws AccessDeniedException if current user may not join the given group.
+ */
+ public function leave_action($group_id)
+ {
+ $g = Statusgruppen::find($group_id);
+
+ if (!$g->userMayLeave($GLOBALS['user']->id)) {
+ throw new AccessDeniedException();
+ }
+
+ $s = StatusgruppeUser::find([$group_id, $GLOBALS['user']->id]);
+ if ($s->delete()) {
+ PageLayout::postSuccess(sprintf(
+ _('Sie wurden aus der Gruppe %s ausgetragen.'),
+ htmlReady($g->name)
+ ));
+ } else {
+ PageLayout::postError(sprintf(
+ _('Sie konnten nicht aus der Gruppe %s ausgetragen werden.'),
+ htmlReady($g->name)
+ ));
+ }
+
+ $this->relocate('course/statusgroups');
+ }
+
+ /**
+ * Batch creation of statusgroups according to given settings.
+ *
+ * @throws AccessDeniedException if access not allowed with current permission level.
+ */
+ public function batch_create_action()
+ {
+ if (!$this->is_tutor) {
+ throw new AccessDeniedException();
+ }
+
+ CSRFProtection::verifyUnsafeRequest();
+
+ $counter = 0;
+
+ // Create a number of groups, sequentially named.
+ if (Request::option('mode') == 'numbering') {
+ if (Request::get('numbering_type') == 2) {
+ $numbering = 'A';
+ } else {
+ $numbering = Request::int('startnumber', 1);
+ }
+ for ($i = 0 ; $i < Request::int('number') ; $i++) {
+ $group = Statusgruppen::createOrUpdate('', Request::get('prefix').' '.
+ $numbering++,
+ null, $this->course_id, Request::int('size', 0),
+ Request::int('selfassign', 0) + Request::int('exclusive', 0),
+ strtotime(Request::get('selfassign_start', 'now')),
+ strtotime(Request::get('selfassign_end', 0)),
+ Request::int('makefolder', 0));
+ $counter++;
+ }
+
+ // Create groups by course metadata, like topics, dates or lecturers.
+ } else if (Request::option('mode') == 'coursedata') {
+ $mode = Request::option('createmode');
+ switch ($mode) {
+
+ // Create groups per topic.
+ case 'topics':
+ case 'paper_related':
+ $topics = SimpleCollection::createFromArray(
+ CourseTopic::findBySeminar_id($this->course_id)
+ )->filter(function ($topic) use ($mode) {
+ return $mode !== 'paper_related'
+ || $topic->paper_related;
+ })->orderBy('priority');
+
+ foreach ($topics as $t) {
+ $group = Statusgruppen::createOrUpdate('', _('Thema:') . ' ' . $t->title,
+ null, $this->course_id, Request::int('size', 0),
+ Request::int('selfassign', 0) + Request::int('exclusive', 0),
+ strtotime(Request::get('selfassign_start', 'now')),
+ strtotime(Request::get('selfassign_end', 0)),
+ Request::int('makefolder', 0)
+ );
+
+ // Connect group to dates that are assigned to the given topic.
+ $dates = CourseDate::findByIssue_id($t->id);
+ foreach ($dates as $d) {
+ $d->statusgruppen->append($group);
+ $d->store();
+ }
+
+ $counter++;
+ }
+
+ break;
+
+ // Create groups per (regular and irregular) dates.
+ case 'dates':
+
+ // Find regular cycles first and create corresponding groups.
+ $cycles = SimpleCollection::createFromArray(
+ SeminarCycleDate::findBySeminar_id($this->course_id));
+
+ foreach ($cycles as $c) {
+ $cd = new CycleData($c);
+
+ $name = $c->toString();
+
+ // Append description to group title if applicable.
+ if ($c->description) {
+ $name .= ' ' . mila($c->description, 30);
+ }
+
+ // Get name of most used room and append to group title.
+ if ($rooms = $cd->getPredominantRoom()) {
+ $room_keys = array_keys($rooms);
+ $room_name = DBManager::get()->fetchOne(
+ "SELECT `name` FROM `resources` WHERE `id` = ?",
+ [array_pop($room_keys)]);
+ $name .= ' (' . $room_name['name'] . ')';
+ } else {
+ $free_text_predominant_rooms = array_keys($cd->getFreeTextPredominantRoom());
+ $room = trim(array_pop($free_text_predominant_rooms));
+ if ($room) {
+ $name .= ' (' . $room . ')';
+ }
+ }
+
+ $group = Statusgruppen::createOrUpdate('', $name,
+ null, $this->course_id, Request::int('size', 0),
+ Request::int('selfassign', 0) + Request::int('exclusive', 0),
+ strtotime(Request::get('selfassign_start', 'now')),
+ strtotime(Request::get('selfassign_end', 0)),
+ Request::int('makefolder', 0));
+
+ // Connect group to dates that are assigned to the given cycle.
+ foreach ($c->dates as $d) {
+ $d->statusgruppen->append($group);
+ $d->store();
+ }
+
+ $counter++;
+ }
+
+ // Now find irregular dates and create groups.
+ $dates = CourseDate::findBySeminar_id($this->course_id);
+ $singledates = array_filter($dates, function ($d) { return !((bool) $d->metadate_id); });
+ foreach ($singledates as $d) {
+ $name = $d->toString();
+
+ // Append description to group title if applicable.
+ if ($d->description) {
+ $name .= ' ' . mila($d->description, 30);
+ }
+
+ // Get room name and append to group title.
+ if ($room = $d->getRoomName()) {
+ $name .= ' (' . $room . ')';
+ }
+
+ $group = Statusgruppen::createOrUpdate('', $name,
+ $counter + 1, $this->course_id, Request::int('size', 0),
+ Request::int('selfassign', 0) + Request::int('exclusive', 0),
+ strtotime(Request::get('selfassign_start', 'now')),
+ strtotime(Request::get('selfassign_end', 0)),
+ Request::int('makefolder', 0));
+
+ $d->statusgruppen->append($group);
+ $d->store();
+
+ $counter++;
+ }
+
+ break;
+
+
+ // Create groups per lecturer.
+ case 'lecturers':
+ $lecturers = SimpleCollection::createFromArray(
+ CourseMember::findByCourseAndStatus($this->course_id, 'dozent'))->orderBy('position');
+
+ foreach ($lecturers as $l) {
+ Statusgruppen::createOrUpdate('', $l->getUserFullname('full'),
+ null, $this->course_id, Request::int('size', 0),
+ Request::int('selfassign', 0) + Request::int('exclusive', 0),
+ strtotime(Request::get('selfassign_start', 'now')),
+ strtotime(Request::get('selfassign_end', 0)),
+ Request::int('makefolder', 0));
+ $counter++;
+ }
+
+ break;
+ }
+
+ }
+
+ if ($counter > 0) {
+ PageLayout::postSuccess(sprintf(
+ ngettext('Eine Gruppe wurde angelegt.', '%u Gruppen wurden angelegt.', $counter),
+ $counter)
+ );
+ }
+
+ $this->relocate('course/statusgroups');
+ }
+
+ /**
+ * Batch action for several groups or group members at once.
+ *
+ * @throws AccessDeniedException if access not allowed with current permission level.
+ */
+ public function batch_action_action()
+ {
+ // Non-tutors may not access this.
+ if (!$this->is_tutor) {
+ throw new AccessDeniedException();
+ }
+
+ // Actions for selected groups.
+ if (Request::submitted('batch_groups')) {
+ if ($groups = Request::getArray('groups')) {
+ $this->groups = SimpleCollection::createFromArray(
+ Statusgruppen::findMany($groups))->orderBy('position, name');
+ switch (Request::option('groups_action')) {
+ case 'edit_size':
+ PageLayout::setTitle(_('Gruppengröße bearbeiten'));
+ $this->edit_size = true;
+
+ $sizes = [];
+
+ // Check for diverging values on all groups.
+ foreach ($this->groups as $group) {
+ $sizes[$group->size] = true;
+ }
+
+ // Get default group size
+ $this->size = max(array_keys($sizes));
+
+ break;
+ case 'edit_selfassign':
+ PageLayout::setTitle(_('Selbsteintrag bearbeiten'));
+ $this->edit_selfassign = true;
+
+ $selfassign = 0;
+ $exclusive = 0;
+ $selfassign_start = [];
+ $selfassign_end = [];
+
+ // Check for diverging values on all groups.
+ foreach ($this->groups as $group) {
+ if ((int)$group->selfassign == 1) {
+ $selfassign++;
+ }
+ if ((int)$group->selfassign === 2) {
+ $selfassign++;
+ $exclusive++;
+ }
+ $selfassign_start[$group->selfassign_start] = true;
+ $selfassign_end[$group->selfassign_end] = true;
+ }
+
+ if ($selfassign > 0) {
+ $this->selfassign = true;
+ }
+
+ if ($exclusive > 0) {
+ $this->exclusive = true;
+ }
+
+ // Selfassign start time set for all selected groups?
+ if (count($selfassign_start) <= 1) {
+ // Just one entry, take it as value for all.
+ $selfassign_starts = array_keys($selfassign_start);
+ $start = array_pop($selfassign_starts);
+ $this->selfassign_start = $start ? date('d.m.Y H:i', $start) : '';
+ } else {
+ // Different entries, mark this.
+ $this->selfassign_start = -1;
+ }
+
+ // Selfassign end time set for all selected groups?
+ if (count($selfassign_end) <= 1) {
+ // Just one entry, take it as value for all.
+ $selfassign_ends = array_keys($selfassign_end);
+ $end = array_pop($selfassign_ends);
+ $this->selfassign_end = $end ? date('d.m.Y H:i', $end) : '';
+ } else {
+ // Different entries, mark this.
+ $this->selfassign_end = -1;
+ }
+
+ break;
+ case 'write_message':
+ //Send the group-IDs of the selected groups
+ //to messages/write:
+ $this->redirect(
+ URLHelper::getURL(
+ 'dispatch.php/messages/write',
+ [
+ 'default_subject' => $this->course_title,
+ 'group_ids' => $groups
+ ]
+ )
+ );
+ break;
+ case 'delete':
+ PageLayout::setTitle(_('Gruppe(n) löschen?'));
+ $this->askdelete = true;
+ break;
+ default:
+ $this->relocate('course/statusgroups');
+ }
+ } else {
+ PageLayout::postError(_('Sie haben keine Gruppe ausgewählt.'));
+ }
+ // Actions for selected group members.
+ } else if (Request::submitted('batch_members')) {
+ // Which group is selected?
+ $group_id = key(Request::getArray('batch_members'));
+
+ // Get selected group members.
+ $group = Request::getArray('group');
+ $this->members = array_keys($group[$group_id]);
+
+ // Get selected action for group members.
+ $actions = Request::getArray('members_action');
+ $action = $actions[$group_id];
+
+ switch ($action) {
+ case 'move':
+ case 'copy':
+ if ($action === 'move') {
+ PageLayout::setTitle(_('Gruppenmitglieder verschieben'));
+ $this->movemembers = true;
+ } else {
+ PageLayout::setTitle(_('Gruppenmitglieder kopieren'));
+ $this->copymembers = true;
+ }
+ $this->source_group = $group_id;
+ // Find possible target groups.
+ $this->target_groups = SimpleCollection::createFromArray(
+ Statusgruppen::findByRange_id($this->course_id))
+ ->orderBy('position, name')
+ ->filter(function ($g) use ($group_id) { return $g->id != $group_id; });
+ break;
+ case 'delete':
+ PageLayout::setTitle(_('Gruppenmitglieder entfernen'));
+ $this->deletemembers = true;
+ $this->source_group = Statusgruppen::find($group_id);
+ break;
+ case 'cancel':
+ PageLayout::setTitle(_('Personen aus Veranstaltung austragen'));
+ $this->cancelmembers = true;
+ $this->source_group = Statusgruppen::find($group_id);
+ break;
+ }
+
+ }
+ }
+
+ /**
+ * Deletes several groups at once.
+ *
+ * @throws AccessDeniedException if access not allowed with current permission level.
+ */
+ public function batch_delete_groups_action()
+ {
+ CSRFProtection::verifyUnsafeRequest();
+
+ if (!$this->is_tutor) {
+ throw new AccessDeniedException();
+ }
+
+ Statusgruppen::findEachMany(
+ function ($group) {
+ $group->delete();
+ },
+ Request::optionArray('groups')
+ );
+ Statusgruppen::reorderPositionsForRange($this->course_id);
+
+ PageLayout::postSuccess(_('Die ausgewählten Gruppen wurden gelöscht.'));
+ $this->relocate('course/statusgroups');
+ }
+
+ /**
+ * Sets data for several groups at once.
+ *
+ * @throws AccessDeniedException if access not allowed with current permission level.
+ */
+ public function batch_save_groups_size_action()
+ {
+ if (!$this->is_tutor) {
+ throw new AccessDeniedException();
+ }
+
+ CSRFProtection::verifyUnsafeRequest();
+ $groups = Statusgruppen::findMany(Request::getArray('groups'));
+
+ foreach ($groups as $g) {
+ Statusgruppen::createOrUpdate($g->id, $g->name,
+ $g->position, $this->course_id,
+ Request::int('size', 0),
+ $g->selfassign, $g->selfassign_start, $g->selfassign_end,
+ false);
+ }
+ PageLayout::postSuccess(_('Die Einstellungen der ausgewählten Gruppen wurden gespeichert.'));
+ $this->relocate('course/statusgroups');
+ }
+
+ /**
+ * Sets data for several groups at once.
+ *
+ * @throws AccessDeniedException if access not allowed with current permission level.
+ */
+ public function batch_save_groups_selfassign_action()
+ {
+ if (!$this->is_tutor) {
+ throw new AccessDeniedException();
+ }
+
+ CSRFProtection::verifyUnsafeRequest();
+ $groups = Statusgruppen::findMany(Request::getArray('groups'));
+
+ $selfassign = Request::int('selfassign', 0);
+ $selfassign_start = 0;
+ $selfassign_end = 0;
+ if ($selfassign) {
+ $selfassign += Request::int('exclusive', 0);
+ $selfassign_start = strtotime(Request::get('selfassign_start', 'now'));
+ $selfassign_end = strtotime(Request::get('selfassign_end', 0));
+ }
+
+ foreach ($groups as $g) {
+ Statusgruppen::createOrUpdate($g->id, $g->name,
+ $g->position, $this->course_id, $g->size,
+ $selfassign, $selfassign_start, $selfassign_end,
+ false);
+ }
+ PageLayout::postSuccess(_('Die Einstellungen der ausgewählten Gruppen wurden gespeichert.'));
+ $this->relocate('course/statusgroups');
+ }
+
+ /**
+ * Moves selected group members to another group.
+ *
+ * @throws AccessDeniedException if access not allowed with current permission level.
+ */
+ public function batch_move_members_action()
+ {
+ if (!$this->is_tutor) {
+ throw new AccessDeniedException();
+ }
+
+ CSRFProtection::verifyUnsafeRequest();
+ $success = 0;
+ $error = 0;
+ $members = Request::getArray('members');
+ foreach ($members as $m) {
+
+ $stored = false;
+
+ // Add user to target statusgroup (if not already in there).
+ if (!StatusgruppeUser::exists([Request::option('target_group'), $m])) {
+ $s = new StatusgruppeUser();
+ $s->user_id = $m;
+ $s->statusgruppe_id = Request::option('target_group');
+ if ($s->store()) {
+ $stored = true;
+ }
+ }
+
+ // Delete old group membership.
+ $source = Request::option('source');
+ if ($stored) {
+ if ($source != 'nogroup') {
+ $old = StatusgruppeUser::find([$source, $m]);
+ if ($old->delete()) {
+ $success++;
+ } else {
+ $error++;
+ }
+ } else {
+ $success++;
+ }
+ }
+ }
+ $groupname = Statusgruppen::find(Request::option('target_group'))->name;
+
+ // Everything completed successfully => success message.
+ if ($success && !$error) {
+ PageLayout::postSuccess(sprintf(ngettext('%u Person wurde in die Gruppe %s verschoben.',
+ '%u Personen wurden in die Gruppe %s verschoben.',
+ $success), $success, htmlReady($groupname)));
+
+ // Some entries worked, some didn't => warning message.
+ } else if ($success && $error) {
+ PageLayout::postWarning(
+ sprintf(ngettext('%u Person wurde in die Gruppe %s verschoben.',
+ '%u Personen wurden in die Gruppe %s verschoben.',
+ $success), $success, htmlReady($groupname)) . '<br>' .
+ sprintf(ngettext('%u Person konnte nicht in die Gruppe %s verschoben werden.',
+ '%u Personen konnten nicht in die Gruppe %s verschoben werden.',
+ $error), $error, htmlReady($groupname))
+ );
+
+ // All is lost => error message.
+ } else if ($error) {
+ PageLayout::postError(sprintf(ngettext('%u Person konnte nicht in die Gruppe %s verschoben werden.',
+ '%u Personen konnten nicht in die Gruppe %s verschoben werden.',
+ $error), $error, htmlReady($groupname)));
+ }
+
+ $this->relocate('course/statusgroups');
+ }
+
+ /**
+ * Copies selected group members to another group.
+ *
+ * @throws AccessDeniedException if access not allowed with current permission level.
+ */
+ public function batch_copy_members_action()
+ {
+ if (!$this->is_tutor) {
+ throw new AccessDeniedException();
+ }
+
+ CSRFProtection::verifyUnsafeRequest();
+ $success = 0;
+ $members = Request::optionArray('members');
+ foreach ($members as $m) {
+
+ // Add user to target statusgroup (if not already in there).
+ if (!StatusgruppeUser::exists(array(Request::option('target_group'), $m))) {
+ $s = new StatusgruppeUser();
+ $s->user_id = $m;
+ $s->statusgruppe_id = Request::option('target_group');
+ if ($s->store()) {
+ $success++;
+ }
+ }
+ }
+ $groupname = Statusgruppen::find(Request::option('target_group'))->name;
+
+ // Everything completed successfully => success message.
+ if ($success) {
+ PageLayout::postSuccess(sprintf(ngettext('%u Person wurde in die Gruppe %s kopiert.',
+ '%u Personen wurden in die Gruppe %s kopiert.',
+ $success), $success, htmlReady($groupname)));
+ }
+
+ $this->relocate('course/statusgroups');
+ }
+
+ /**
+ * Removes selected group members from given group.
+ *
+ * @param String $group_id group to remove members from.
+ * @throws AccessDeniedException if access not allowed with current permission level.
+ */
+ public function batch_delete_members_action($group_id)
+ {
+ if (!$this->is_tutor) {
+ throw new AccessDeniedException();
+ }
+
+ CSRFProtection::verifyUnsafeRequest();
+ $success = 0;
+ $error = 0;
+ $members = Request::getArray('members');
+ foreach ($members as $m) {
+
+ // Remove user from target statusgroup.
+ $s = StatusgruppeUser::find([$group_id, $m]);
+ if ($s->delete()) {
+ $success++;
+ } else {
+ $error++;
+ }
+
+ }
+ $groupname = Statusgruppen::find($group_id)->name;
+
+ // Everything completed successfully => success message.
+ if ($success && !$error) {
+ PageLayout::postSuccess(sprintf(ngettext('%u Person wurde aus der Gruppe %s entfernt.',
+ '%u Personen wurden aus der Gruppe %s entfernt.',
+ $success), $success, htmlReady($groupname)));
+
+ // Some entries worked, some didn't => warning message.
+ } else if ($success && $error) {
+ PageLayout::postWarning(
+ sprintf(ngettext('%u Person wurde aus der Gruppe %s entfernt.',
+ '%u Personen wurden aus der Gruppe %s entfernt.',
+ $success), $success, htmlReady($groupname)) . '<br>' .
+ sprintf(ngettext('%u Person konnte nicht aus der Gruppe %s entfernt werden.',
+ '%u Personen konnten nicht aus der Gruppe %s entfernt werden.',
+ $error), $error, htmlReady($groupname))
+ );
+
+ // All is lost => error message.
+ } else if ($error) {
+ PageLayout::postError(sprintf(ngettext('%u Person konnte nicht aus der Gruppe %s entfernt werden.',
+ '%u Personen konnten nicht aus der Gruppe %s entfernt werden.',
+ $error), $error, htmlReady($groupname)));
+ }
+
+ $this->relocate('course/statusgroups');
+ }
+
+ /**
+ * Removes selected group members from the course (cancels their admission).
+ *
+ * @throws AccessDeniedException if access not allowed with current permission level.
+ */
+ public function batch_cancel_members_action()
+ {
+ if (!$this->is_tutor) {
+ throw new AccessDeniedException();
+ }
+
+ CSRFProtection::verifyUnsafeRequest();
+
+ $members = Request::getArray('members');
+ $model = new MembersModel($this->course_id, $this->course_title);
+
+ $removed_names = $model->cancelSubscription($members);
+
+ PageLayout::postSuccess(
+ _('Die folgenden Personen wurden aus der Veranstaltung ausgetragen'),
+ $removed_names
+ );
+
+ $this->relocate('course/statusgroups');
+ }
+
+ public function order_action()
+ {
+ if (!Request::isPost()) {
+ throw new MethodNotAllowedException();
+ }
+
+ if (!$this->is_tutor || $this->is_locked) {
+ throw new AccessDeniedException();
+ }
+
+ $id = Request::option('id');
+ $index = Request::int('index');
+
+ $group = Statusgruppen::find($id);
+ if (!$group || $group->range_id !== $this->course_id) {
+ throw new Exception('Invalid group or group does not belong to course');
+ }
+
+ if ($group->position == $index) {
+ return;
+ }
+
+ if ($group->position < $index) {
+ $range = [$group->position, $index];
+ $adjustment = -1;
+ } else {
+ $range = [$index, $group->position];
+ $adjustment = 1;
+ }
+
+ Statusgruppen::findEachBySQL(
+ function ($g) use ($adjustment) {
+ $g->position = $g->position + $adjustment;
+ $g->store();
+ },
+ 'range_id = ? AND statusgruppe_id != ? AND position BETWEEN ? AND ?',
+ [$this->course_id, $id, $range[0], $range[1]]
+ );
+
+ $group->position = $index;
+ $group->store();
+
+ $this->render_nothing();
+ }
+
+ public function course_groups_hide_action($state)
+ {
+ if (!$this->is_tutor) {
+ throw new AccessDeniedException();
+ }
+
+ $this->config->store('COURSE_MEMBERGROUPS_HIDE', $state);
+
+ $this->redirect('course/statusgroups');
+ }
+
+ private function sortMembers(SimpleCollection $members, $sort_by = null, $sort_dir = null)
+ {
+ $order = "nachname asc, vorname asc";
+ if ($sort_by && $sort_dir) {
+ $order = "{$sort_by} {$sort_dir}, {$order}";
+ }
+ return $members->orderBy($order);
+
+ }
+}