Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • alexander.vorwerk/studip
  • hochschule-wismar/stud-ip
  • tleilax/studip
  • marcus/studip
  • manschwa/studip
  • eberhardt/studip
  • uol/studip
  • pluta/studip
  • thienel/extern-uni-b
  • studip/studip
  • strohm/studip
  • uni-osnabrueck/studip
  • FloB/studip
  • universit-t-rostock/studip
  • Robinyyy/studip
  • jakob.diel/studip
  • HyperSpeeed/studip
  • ann/studip
  • nod3zer0/stud-ip-siple-saml-php-plugin
19 results
Show changes
Commits on Source (184)
Showing
with 734 additions and 224 deletions
......@@ -33,6 +33,7 @@ public/pictures/banner/*.jpg
public/pictures/banner/*.png
public/pictures/course/[0-9a-f]*.png
public/pictures/institute/[0-9a-f]*.png
public/pictures/stock-images/*
public/pictures/user/[0-9a-f]*.png
public/plugins_packages/*
......
This diff is collapsed.
# Stud.IP v5.4
**23.05.23**
**08.11.23**
## Neue Features
## New Features
### System:
- Komplett neu entwickelte Verzeichnisstrukturen
- Unter Administraion -> Standort haben Root die Möglichkeit, einen systemweiten Pool an Bildern anzulegen und Lizenzen anzugeben. Die Bilder können dann von Lehrenden in Courseware genutzt werden.
- Barrierefreiheitserklärung und Meldefunktion sind nun im Footer integriert. Eine Mustererklärung ist enthalten, diese muss aber angepasst werden.
- Neben dem Vollbild-Modus (eingeführt in der Version 5.0), der nur in bestimmten Kontexten gezeigt wird, gibt es nun einen Modus "kompakte Navigation". Der neue Modus wird über das bisherige Icon für den Vollbildmodus aktiviert. Bitte passen Sie ihre Dokumentationen an.
- Root können"Banner" auf der Verwaltungsseite für Werkzeuge in Veranstaltungen schalten und damit z.B. auf neue Funktionen hinweisen.
- Der Hinweistext, der Nutzenden unter Profil->Persönliche Angaben->Grunddaten angezeigt wird, wenn die Grunddaten dort nicht änderbar sind, weil bspw. Shibboleth verwendet wird, lässt sich nun global konfigurieren. Damit kann ein Hinweis hinterlegt werden, an welche Stelle man sich zur Änderung der Grunddaten wenden soll.
- Veranstaltungen auf der "Meine Veranstaltungen"-Seite lassen sich nun auch nach Modulen gruppieren (wenn das Modularisierte Vorlesungsverzeichnis MVV verwendet wird)
### Raumverwaltung:
- Vereinfachte Raumanfragen
- Sammelaktionen
### Veranstaltungen:
- Hauptordner im Dateibereich lässt sich für Uploads von Studierenden/Teilnehmenden sperren.
- Die "Mehr"-Seite zur Verwaltung von Veranstaltungswerkzeugen gibt es nicht mehr als separaten Reiter. Alle Funktionen der "mehr"-Seite sind nun unter "Verwaltung" zu finden - bitte weisen Sie die Lehrenden unbedingt darauf hin und passen Sie ggf. ihre Dokumentation an.
- Verwaltungsseite wurde komplett in vue.js neu programmiert.
### Courseware:
- Jede Courseware ist nun ein einzelnes Lernmaterial und kann kopiert, exportiert, importiert werden und gibt Lernfortschritt an.
- Sammelmappe für Abschnitte und Blöcke
- Übersichtsseite für Feedback und Kommentare
- Neue Blöcke zur Darstellung eines Lebenslaufs
- Funktionen zum Teilen von Seiten an Personen und Gruppen, ermöglicht niedrigschwelliges Peerfeedback
- Übersichtsseite für Lehrende über verteilte Aufgaben mit Bearbeitungsstatus, Feedbackfunktion und Fristverlängerungsanfrage
## Breaking changes
......@@ -17,7 +41,12 @@
## Deprecated Features
- Das Verwenden von LESS-Stylesheets in Plugins wurde deprecated und wird zu Stud.IP 6.0 entfernt werden. Die betroffenen Plugins müssen angepasst und auf SCSS umgestellt werden.
- Die REST-API ist als deprecated markiert und wird perspektivisch entfernt. Neue Entwicklungen sollten nicht darauf aufbauen.
- Evaluationen werden perspektivisch entfernt, wenn die "Fragebögen"-Funktion dem Funktionsumfang der Evaluationen gleich kommt.
## Known Issues
- Der Vollbildmodus funktioniert nicht auf Apple iPads. Der Modus kann zwar initiiert werden, beendet sich aber selbsständig, wenn nach oben gescrollt wird. Dieses Verhalten ist en Fehler innerhalb von iOS/iPadOS und kann seitens Stud.IP nicht umgangen werden. Der Fehler ist bei Apple gemeldet.
## Other
- Mindestanforderung an Node.JS ist nun Version 16
RELEASE 5.4.alpha
RELEASE 5.4
......@@ -91,6 +91,24 @@ class Accessibility_FormsController extends StudipController
)
);
// Add a honeypot value and timestamp
$personal_data_part->addInput(
new \Studip\Forms\TextInput(
'homepage',
_('Homepage'),
'',
[
'aria-hidden' => 'true',
'class' => 'sr-only',
'placeholder' => _('Dieses Feld nicht ausfüllen'),
'title' => _('Dieses Feld nicht ausfüllen'),
]
)
);
$this->form->addInput(
new \Studip\Forms\HiddenInput('time', '', time())
);
$personal_data_part->addText(sprintf('<p>%s</p>',
_('Informationen zum Datenschutz dieses Formulars finden Sie in der Datenschutzerklärung.')));
......@@ -118,6 +136,13 @@ class Accessibility_FormsController extends StudipController
$this->form->setURL($this->report_barrierURL());
$this->form->addStoreCallback(
function ($form, $form_values) {
if (
$form_values['time'] >= time() - 2
|| !empty($form_values['homepage'])
) {
return 0;
}
$recipients = Config::get()->ACCESSIBILITY_RECEIVER_EMAIL;
if (empty($recipients)) {
//Fallback: Use the UNI_CONTACT mail address:
......
......@@ -52,7 +52,7 @@ class Admin_AdditionalController extends AuthenticatedController
}
// purge data
if (Request::submitted('delete')) {
DatafieldEntryModel::deleteBySQL('sec_rage_id = ?', [$this->course->id]);
DatafieldEntryModel::deleteBySQL('sec_range_id = ?', [$this->course->id]);
}
if ($this->course->store()) {
......
......@@ -768,69 +768,7 @@ class Admin_CourseplanningController extends AuthenticatedController
*/
private function getCourses($params = [], $display_all = false): array
{
// Init
if ($GLOBALS['user']->cfg->MY_INSTITUTES_DEFAULT === "all") {
$inst = new SimpleCollection($this->insts);
$inst->filter(function ($a) use (&$inst_ids) {
$inst_ids[] = $a->Institut_id;
});
} else {
//We must check, if the institute ID belongs to a faculty
//and has the string _i appended to it.
//In that case we must display the courses of the faculty
//and all its institutes.
//Otherwise we just display the courses of the faculty.
$inst_id = $GLOBALS['user']->cfg->MY_INSTITUTES_DEFAULT;
$institut = new Institute($inst_id);
if (!$institut->isFaculty() || $GLOBALS['user']->cfg->MY_INSTITUTES_INCLUDE_CHILDREN) {
// If the institute is not a faculty or the child insts are included,
// pick the institute IDs of the faculty/institute and of all sub-institutes.
$inst_ids[] = $inst_id;
if ($institut->isFaculty()) {
foreach ($institut->sub_institutes->pluck("Institut_id") as $institut_id) {
$inst_ids[] = $institut_id;
}
}
} else {
// If the institute is a faculty and the child insts are not included,
// pick only the institute id of the faculty:
$inst_ids[] = $inst_id;
}
}
$active_elements = $this->getActiveElements();
$filter = AdminCourseFilter::get(true);
$filter->where("sem_classes.studygroup_mode = '0'");
if (is_object($this->semester)) {
$filter->filterBySemester($this->semester->getId());
}
if ($params['typeFilter'] && $params['typeFilter'] !== "all") {
list($class_filter,$type_filter) = explode('_', $params['typeFilter']);
if (!$type_filter && !empty($GLOBALS['SEM_CLASS'][$class_filter])) {
$type_filter = array_keys($GLOBALS['SEM_CLASS'][$class_filter]->getSemTypes());
}
$filter->filterByType($type_filter);
}
if ($GLOBALS['user']->cfg->ADMIN_COURSES_TEACHERFILTER && ($GLOBALS['user']->cfg->ADMIN_COURSES_TEACHERFILTER !== "all")) {
$filter->filterByDozent($GLOBALS['user']->cfg->ADMIN_COURSES_TEACHERFILTER);
}
if ($active_elements['institute'] && $GLOBALS['user']->cfg->MY_INSTITUTES_DEFAULT !== "all") {
$filter->filterByInstitute($inst_ids);
}
if ($GLOBALS['user']->cfg->MY_COURSES_SELECTED_STGTEIL && $GLOBALS['user']->cfg->MY_COURSES_SELECTED_STGTEIL !== 'all') {
$filter->filterByStgTeil($GLOBALS['user']->cfg->MY_COURSES_SELECTED_STGTEIL);
}
$filter->storeSettings();
$this->count_courses = $filter->countCourses();
if ($this->count_courses && ($this->count_courses <= $filter->max_show_courses || $display_all)) {
$courses = $filter->getCourses();
......@@ -842,41 +780,42 @@ class Admin_CourseplanningController extends AuthenticatedController
$sem_types = SemType::getTypes();
}
$seminars = array_map('current', $courses);
$seminars = [];
foreach ($courses as $course) {
$seminars[$course->id] = $course->toArray();
foreach ($seminars as $seminar_id => $seminar) {
$seminars[$seminar_id]['seminar_id'] = $seminar_id;
$seminars[$seminar_id]['obj_type'] = 'sem';
$dozenten = $this->getTeacher($seminar_id);
$seminars[$seminar_id]['dozenten'] = $dozenten;
$seminars[$course->id]['seminar_id'] = $course->id;
$seminars[$course->id]['obj_type'] = 'sem';
$dozenten = $this->getTeacher($course->id);
$seminars[$course->id]['dozenten'] = $dozenten;
if (in_array('contents', $params['view_filter'])) {
$tools = new SimpleCollection(ToolActivation::findbyRange_id($seminar_id, "ORDER BY position"));
$visit_data = get_objects_visits([$seminar_id], 0, null, null, $tools->pluck('plugin_id'));
$seminars[$seminar_id]['tools'] = $tools;
$seminars[$seminar_id]['visitdate'] = $visit_data[$seminar_id][0]['visitdate'];
$seminars[$seminar_id]['last_visitdate'] = $visit_data[$seminar_id][0]['last_visitdate'];
$seminars[$seminar_id]['sem_class'] = $sem_types[$seminar['status']]->getClass();
$seminars[$seminar_id]['navigation'] = MyRealmModel::getAdditionalNavigations(
$seminar_id,
$seminars[$seminar_id],
$seminars[$seminar_id]['sem_class'],
$tools = new SimpleCollection(ToolActivation::findbyRange_id($course->id, "ORDER BY position"));
$visit_data = get_objects_visits([$course->id], 0, null, null, $tools->pluck('plugin_id'));
$seminars[$course->id]['tools'] = $tools;
$seminars[$course->id]['visitdate'] = $visit_data[$course->id][0]['visitdate'];
$seminars[$course->id]['last_visitdate'] = $visit_data[$course->id][0]['last_visitdate'];
$seminars[$course->id]['sem_class'] = $sem_types[$course->status]->getClass();
$seminars[$course->id]['navigation'] = MyRealmModel::getAdditionalNavigations(
$course->id,
$seminars[$course->id],
$seminars[$course->id]['sem_class'],
$GLOBALS['user']->id,
$visit_data[$seminar_id]
$visit_data[$course->id]
);
}
//add last activity column:
if (in_array('last_activity', $params['view_filter'])) {
$seminars[$seminar_id]['last_activity'] = lastActivity($seminar_id);
$seminars[$course->id]['last_activity'] = lastActivity($course->id);
}
if ($this->selected_action == 17) {
$seminars[$seminar_id]['admission_locked'] = false;
if ($seminar['course_set']) {
$set = new CourseSet($seminar['course_set']);
$seminars[$course->id]['admission_locked'] = false;
if ($course->course_set) {
$set = new CourseSet($course->course_set);
if (!is_null($set) && $set->hasAdmissionRule('LockedAdmission')) {
$seminars[$seminar_id]['admission_locked'] = 'locked';
$seminars[$course->id]['admission_locked'] = 'locked';
} else {
$seminars[$seminar_id]['admission_locked'] = 'disable';
$seminars[$course->id]['admission_locked'] = 'disable';
}
unset($set);
}
......
......@@ -235,6 +235,17 @@ class Admin_CoursesController extends AuthenticatedController
)->asDialog('size=auto');
$sidebar->addWidget($export);
}
foreach (PluginEngine::getPlugins(AdminCourseWidgetPlugin::class) as $plugin) {
foreach ($plugin->getWidgets() as $name => $widget) {
$position = $widget->getPositionInSidebar();
if ($position) {
$sidebar->insertWidget($widget, $position, $name);
} else {
$sidebar->addWidget($widget, $name);
}
}
}
}
......@@ -315,21 +326,23 @@ class Admin_CoursesController extends AuthenticatedController
? $configuration->MY_INSTITUTES_DEFAULT
: null;
$filters = array_merge(
array_merge(...PluginEngine::sendMessage(AdminCourseWidgetPlugin::class, 'getFilters')),
$this->getDatafieldFilters(),
[
'institut_id' => $institut_id,
'search' => $configuration->ADMIN_COURSES_SEARCHTEXT,
'semester_id' => $configuration->MY_COURSES_SELECTED_CYCLE,
'course_type' => $configuration->MY_COURSES_TYPE_FILTER,
'stgteil' => $configuration->MY_COURSES_SELECTED_STGTEIL,
'teacher_filter' => $configuration->ADMIN_COURSES_TEACHERFILTER,
]
);
return [
'setActivatedFields' => $this->getFilterConfig(),
'setActionArea' => $configuration->MY_COURSES_ACTION_AREA ?? '1',
'setFilter' => array_filter(array_merge(
$this->getDatafieldFilters(),
[
'institut_id' => $institut_id,
'search' => $configuration->ADMIN_COURSES_SEARCHTEXT,
'semester_id' => $configuration->MY_COURSES_SELECTED_CYCLE,
'course_type' => $configuration->MY_COURSES_TYPE_FILTER,
'stgteil' => $configuration->MY_COURSES_SELECTED_STGTEIL,
'teacher_filter' => $configuration->ADMIN_COURSES_TEACHERFILTER,
]
)),
'setFilter' => array_filter($filters),
];
}
......@@ -357,59 +370,14 @@ class Admin_CoursesController extends AuthenticatedController
public function search_action()
{
$activeSidebarElements = $this->getActiveElements();
if (Request::get('search')) {
$GLOBALS['user']->cfg->store('ADMIN_COURSES_SEARCHTEXT', Request::get('search'));
} else {
$GLOBALS['user']->cfg->delete('ADMIN_COURSES_SEARCHTEXT');
}
if (Request::option('institut_id') && Request::option('institut_id') !== 'all') {
$GLOBALS['user']->cfg->store('MY_INSTITUTES_DEFAULT', Request::option('institut_id'));
} else {
$GLOBALS['user']->cfg->delete('MY_INSTITUTES_DEFAULT');
}
if (Request::option('semester_id')) {
$GLOBALS['user']->cfg->store('MY_COURSES_SELECTED_CYCLE', Request::option('semester_id'));
} else {
$GLOBALS['user']->cfg->delete('MY_COURSES_SELECTED_CYCLE');
}
if (Request::option('course_type') && Request::option('course_type') !== 'all') {
$GLOBALS['user']->cfg->store('MY_COURSES_TYPE_FILTER', Request::option('course_type'));
} else {
$GLOBALS['user']->cfg->delete('MY_COURSES_TYPE_FILTER');
}
if (Request::option('stgteil')) {
$GLOBALS['user']->cfg->store('MY_COURSES_SELECTED_STGTEIL', Request::option('stgteil'));
} else {
$GLOBALS['user']->cfg->delete('MY_COURSES_SELECTED_STGTEIL');
}
if (Request::option('teacher_filter')) {
$GLOBALS['user']->cfg->store('ADMIN_COURSES_TEACHERFILTER', Request::option('teacher_filter'));
} else {
$GLOBALS['user']->cfg->delete('ADMIN_COURSES_TEACHERFILTER');
}
$datafields_filters = $GLOBALS['user']->cfg->ADMIN_COURSES_DATAFIELDS_FILTERS;
foreach (DataField::getDataFields('sem') as $datafield) {
if (
Request::get('df_'.$datafield->getId())
&& in_array($datafield->getId(), $activeSidebarElements['datafields'])
) {
$datafields_filters[$datafield->getId()] = Request::get('df_'.$datafield->getId());
} else {
unset($datafields_filters[$datafield->getId()]);
}
}
$GLOBALS['user']->cfg->store('ADMIN_COURSES_DATAFIELDS_FILTERS', $datafields_filters);
$this->processFilters();
$filter = AdminCourseFilter::get();
if (Request::option('course_id')) { //we have only one course and want to see if that course is part of the result set
$filter->query->where('course_id', 'seminare.Seminar_id = :course_id', ['course_id' => Request::option('course_id')]);
}
PluginEngine::sendMessage(AdminCourseWidgetPlugin::class, 'applyFilters', $filter);
$count = $filter->countCourses();
if ($count > $this->max_show_courses && !Request::submitted('without_limit')) {
$this->render_json([
......@@ -526,6 +494,57 @@ class Admin_CoursesController extends AuthenticatedController
$this->render_json($data);
}
private function processFilters(): void
{
$filters = Request::getArray('filters');
$config = User::findCurrent()->getConfiguration();
// Simple filters
$mapping = [
'search' => 'ADMIN_COURSES_SEARCHTEXT',
'semester_id' => 'MY_COURSES_SELECTED_CYCLE',
'stgteil' => 'MY_COURSES_SELECTED_STGTEIL',
'teacher_filter' => 'ADMIN_COURSES_TEACHERFILTER',
'course_type' => 'MY_COURSES_TYPE_FILTER',
'institut_id' => 'MY_INSTITUTES_DEFAULT',
];
foreach ($mapping as $key => $field) {
if (isset($filters[$key])) {
$config->store($field, $filters[$key]);
}
unset($filters[$key]);
}
// Datafield filters
$activeSidebarElements = $this->getActiveElements();
$datafields_filters = $GLOBALS['user']->cfg->ADMIN_COURSES_DATAFIELDS_FILTERS;
foreach (DataField::getDataFields('sem') as $datafield) {
$key = "df_{$datafield->id}";
if (
!empty($filters[$key])
&& in_array($datafield->id, $activeSidebarElements['datafields'])
) {
$datafields_filters[$datafield->id] = $filters[$key];
} else {
unset($datafields_filters[$datafield->id]);
}
}
$config->store('ADMIN_COURSES_DATAFIELDS_FILTERS', $datafields_filters);
// Plugin filters
foreach (PluginEngine::getPlugins(AdminCourseWidgetPlugin::class) as $plugin) {
$plugin_filters = array_intersect_key(
$filters,
$plugin->getFilters()
);
$plugin->setFilters($plugin_filters);
}
}
protected function getCourseData(Course $course, $activated_fields)
{
$d = [
......@@ -861,7 +880,9 @@ class Admin_CoursesController extends AuthenticatedController
$filter_config = Request::getArray('fields');
if (count($filter_config) > 0) {
$courses = AdminCourseFilter::get()->getCourses();
$filter = AdminCourseFilter::get();
PluginEngine::sendMessage(AdminCourseWidgetPlugin::class, 'applyFilters', $filter);
$courses = $filter->getCourses();
$view_filters = $this->getViewFilters();
......
......@@ -354,13 +354,10 @@ class Admin_StatusgroupsController extends AuthenticatedController
*/
private function setType()
{
if (get_object_type(Context::getId(), ['inst', 'fak'])) {
$type = 'inst';
}
$type = Context::isInstitute() ? 'inst' : null;
$types = $this->types();
if (!$type || Request::submitted('type') && $type != Request::get('type')) {
if (!$type || Request::submitted('type') && $type !== Request::get('type')) {
$types[Request::get('type', 'inst')]['redirect']();
} else {
$this->type = $types[$type];
......
......@@ -184,64 +184,78 @@ class Admin_TreeController extends AuthenticatedController
*/
public function batch_assign_semtree_action()
{
$GLOBALS['perm']->check('admin');
if (!$GLOBALS['perm']->have_perm('admin')
&& !RolePersistence::isAssignedRole(User::findCurrent()->id, 'DedicatedAdmin')) {
throw new AccessDeniedException();
}
//set the page title with the area of Stud.IP:
PageLayout::setTitle(_('Veranstaltungszuordnungen bearbeiten'));
Navigation::activateItem('/browse/my_courses/list');
$GLOBALS['perm']->check('admin');
// check the assign_semtree array and extract the relevant course IDs:
$courseIds = Request::optionArray('assign_semtree');
$order = Config::get()->IMPORTANT_SEMNUMBER
? "ORDER BY `start_time` DESC, `VeranstaltungsNummer`, `Name`"
: "ORDER BY `start_time` DESC, `Name`";
$this->courses = Course::findMany($courseIds, $order);
$this->courses = array_filter(
Course::findMany($courseIds, $order),
function (Course $course): bool {
/*
* Check if sem_tree entries are allowed and may be changed and remove all courses
* where this is not the case.
*/
return !LockRules::Check($course->id, 'sem_tree', 'sem')
&& $course->getSemClass()['bereiche'];
}
);
$this->return = Request::get('return');
// check if at least one course was selected (this can only happen from admin courses overview):
if (!$courseIds) {
PageLayout::postWarning('Es wurde keine Veranstaltung gewählt.');
if (count($this->courses) === 0) {
PageLayout::postWarning('Es wurde keine Veranstaltung gewählt oder die Zuordnungen können ' .
'nicht bearbeitet werden.');
$this->relocate('admin/courses');
}
}
public function assign_courses_action($class_id)
{
$GLOBALS['perm']->check('root');
$data = $this->checkClassAndId($class_id);
$GLOBALS['perm']->check('admin');
$this->search = QuickSearch::get('courses[]', new StandardSearch('Seminar_id'))->withButton();
$this->node = $data['id'];
}
/**
* Store (de-)assignments from courses to sem_tree nodes.
* @return void
*/
public function do_batch_assign_action()
{
$GLOBALS['perm']->check('admin');
$astmt = DBManager::get()->prepare("INSERT IGNORE INTO `seminar_sem_tree` VALUES (:course, :node)");
$dstmt = DBManager::get()->prepare(
"DELETE FROM `seminar_sem_tree` WHERE `seminar_id` IN (:courses) AND `sem_tree_id` = :node");
if (!$GLOBALS['perm']->have_perm('admin')
&& !RolePersistence::isAssignedRole(User::findCurrent()->id, 'DedicatedAdmin')) {
throw new AccessDeniedException();
}
CSRFProtection::verifyUnsafeRequest();
$success = true;
// Add course assignments to the specified nodes.
foreach (Request::optionArray('courses') as $course) {
foreach (Request::optionArray('add_assignments') as $a) {
$success = $astmt->execute(['course' => $course, 'node' => $a]);
$courses = Course::findMany(Request::optionArray('courses'));
foreach ($courses as $course) {
if ($GLOBALS['perm']->have_studip_perm('tutor', $course->id)) {
$areas = $course->study_areas->pluck('sem_tree_id');
$newAreas = array_merge($areas, Request::optionArray('add_assignments'));
$delete = Request::optionArray('delete_assignments');
$changed = array_diff($newAreas, $delete);
// Set new areas for course if at least one area remains.
if (count($changed) > 0) {
$course->setStudyAreas($changed);
// Allow to remove all study areas only when there are modules.
} else if ($course->getSemClass()['module'] && count(Lvgruppe::findBySeminar($course->id))) {
$course->setStudyAreas($changed);
} else {
$success = false;
}
} else {
$success = false;
}
}
// Remove course assignments from the specified nodes.
foreach (Request::optionArray('delete_assignments') as $d) {
$success = $dstmt->execute(['courses' => Request::optionArray('courses'), 'node' => $d]);
}
if ($success) {
PageLayout::postSuccess(_('Die Zuordnungen wurden gespeichert.'));
} else {
......
......@@ -273,12 +273,13 @@ class Admin_UserController extends AuthenticatedController
*/
public function delete_action($user_id = null, $parent = '')
{
$delete_documents = (bool) Request::int('documents');
$delete_content_from_course = (bool) Request::int('coursecontent');
$delete_personal_documents = (bool) Request::int('personaldocuments');
$delete_personal_content = (bool) Request::int('personalcontent');
$delete_names = (bool) Request::int('personalnames');
$delete_memberships = (bool) Request::int('memberships');
$delete_documents = Request::bool('documents');
$delete_courseware = Request::bool('courseware');
$delete_content_from_course = Request::bool('coursecontent');
$delete_personal_documents = Request::bool('personaldocuments');
$delete_personal_content = Request::bool('personalcontent');
$delete_names = Request::bool('personalnames');
$delete_memberships = Request::bool('memberships');
//deleting one user
if (!is_null($user_id)) {
......@@ -305,7 +306,7 @@ class Admin_UserController extends AuthenticatedController
$umanager->getFromDatabase($user_id);
//delete
if ($umanager->deleteUser($delete_documents, $delete_content_from_course, $delete_personal_documents, $delete_personal_content, $delete_names, $delete_memberships)) {
if ($umanager->deleteUser($delete_documents, $delete_content_from_course, $delete_personal_documents, $delete_personal_content, $delete_names, $delete_memberships, $delete_courseware)) {
$details = explode('§', str_replace(['msg§', 'info§', 'error§'], '', mb_substr($umanager->msg, 0, -1)));
PageLayout::postSuccess(htmlReady(sprintf(_('"%s (%s)" wurde erfolgreich gelöscht.'), $user->getFullName(), $user->username)), $details);
} else {
......@@ -393,15 +394,18 @@ class Admin_UserController extends AuthenticatedController
if ($user_id === null) {
if (Request::option('user')) {
$user_id = Request::option('user');
} else {
PageLayout::postInfo(_('Sie haben niemanden ausgewählt!'));
//liste wieder anzeigen
$this->redirect('admin/user/');
return;
}
}
$this->user = User::find($user_id);
if (!$this->user) {
PageLayout::postInfo(_('Sie haben niemanden ausgewählt!'));
//liste wieder anzeigen
$this->redirect('admin/user/');
return;
}
$this->user_roles = $this->user->getRoles();
// Änderungen speichern
......@@ -978,6 +982,7 @@ class Admin_UserController extends AuthenticatedController
*/
public function change_password_action($user_id)
{
CSRFProtection::verifyUnsafeRequest();
// mail address did not change, so skip this check
$GLOBALS['MAIL_VALIDATE_BOX'] = false;
$UserManagement = new UserManagement($user_id);
......@@ -989,7 +994,7 @@ class Admin_UserController extends AuthenticatedController
PageLayout::postError(_('Die Änderungen konnten nicht gespeichert werden.'), $details);
}
if (Request::int('from_index')) {
$this->redirect('admin/user');
$this->relocate('admin/user');
} else {
$this->redirect('admin/user/edit/' . $user_id);
}
......@@ -1031,7 +1036,7 @@ class Admin_UserController extends AuthenticatedController
}
if (Request::int('from_index')) {
$this->redirect('admin/user');
$this->relocate('admin/user');
} else {
$this->redirect('admin/user/edit/' . $user_id);
}
......@@ -1044,6 +1049,7 @@ class Admin_UserController extends AuthenticatedController
*/
public function unlock_action($user_id)
{
CSRFProtection::verifyUnsafeRequest();
$user = User::find($user_id);
$user->locked = 0;
......@@ -1063,7 +1069,7 @@ class Admin_UserController extends AuthenticatedController
}
if (Request::int('from_index')) {
$this->redirect('admin/user');
$this->relocate('admin/user');
} else {
$this->redirect('admin/user/edit/' . $user_id);
}
......@@ -1146,6 +1152,7 @@ class Admin_UserController extends AuthenticatedController
*/
public function delete_studycourse_action($user_id, $fach_id, $abschlus_id)
{
CSRFProtection::verifyUnsafeRequest();
$user_stc = UserStudyCourse::find([$user_id, $fach_id, $abschlus_id]);
$deleted = false;
if ($user_stc) {
......@@ -1167,19 +1174,19 @@ class Admin_UserController extends AuthenticatedController
*/
public function delete_institute_action($user_id, $institut_id)
{
CSRFProtection::verifyUnsafeRequest();
if ($GLOBALS['perm']->have_studip_perm("admin", $institut_id)) {
$groups = GetAllStatusgruppen($institut_id);
$group_list = GetRoleNames($groups, 0, '', true);
if (is_array($group_list) && count($group_list) > 0) {
$query = "DELETE FROM statusgruppe_user
WHERE statusgruppe_id IN (?) AND user_id = ?";
$statement = DBManager::get()->prepare($query);
$statement->execute([array_keys($group_list), $user_id]);
StatusgruppeUser::deleteBySQL(
"`statusgruppe_id` IN (?) AND `user_id` = ?",
[array_keys($group_list), $user_id]
);
}
$db = DBManager::get()->prepare("DELETE FROM user_inst WHERE user_id = ? AND Institut_id = ?");
$db->execute([$user_id, $institut_id]);
if ($db->rowCount() == 1) {
$count = InstituteMember::deleteBySQL("`user_id` = ? AND `Institut_id` = ?", [$user_id, $institut_id]);
if ($count === 1) {
StudipLog::log('INST_USER_DEL', $institut_id, $user_id);
NotificationCenter::postNotification('UserInstitutionDidDelete', $institut_id, $user_id);
InstituteMember::ensureDefaultInstituteForUser($user_id);
......@@ -1203,6 +1210,7 @@ class Admin_UserController extends AuthenticatedController
*/
public function delete_userdomain_action($user_id)
{
CSRFProtection::verifyUnsafeRequest();
$domain_id = Request::get('domain_id');
UserDomain::find($domain_id)->removeUser($user_id);
$result = AutoInsert::instance()->saveUser($user_id);
......@@ -1221,11 +1229,12 @@ class Admin_UserController extends AuthenticatedController
}
/**
* Reset notfication for user
* Reset notification for user
* @param $user_id
*/
public function reset_notification_action($user_id)
{
CSRFProtection::verifyUnsafeRequest();
$resetted = CourseMemberNotification::deleteBySQL("user_id = ?", [$user_id]);
PageLayout::postSuccess(sprintf(_('Die Benachrichtigungseinstellungen für %s Veranstaltungen wurden zurück gesetzt.'), $resetted));
$this->redirect('admin/user/edit/' . $user_id);
......@@ -1237,6 +1246,7 @@ class Admin_UserController extends AuthenticatedController
*/
public function reset_tfa_action($user_id)
{
CSRFProtection::verifyUnsafeRequest();
if (TFASecret::deleteByUser_id($user_id)) {
PageLayout::postSuccess(_('Die Zwei-Faktor-Authentifizierung wurde für diese Person deaktiviert.'));
}
......@@ -1688,7 +1698,7 @@ class Admin_UserController extends AuthenticatedController
_('Personenaccount entsperren'),
$this->url_for("admin/user/unlock/{$this->user->id}"),
Icon::create('lock-unlocked')
);
)->asButton();
} else {
$user_actions->addLink(
_('Personenaccount sperren'),
......@@ -1703,7 +1713,7 @@ class Admin_UserController extends AuthenticatedController
_('Passwortlink zusenden'),
$this->url_for("admin/user/change_password/{$this->user->id}"),
Icon::create('key')
);
)->asButton();
}
$user_actions->addLink(
_('Person löschen'),
......@@ -1716,7 +1726,7 @@ class Admin_UserController extends AuthenticatedController
_('Benachrichtigungen zurücksetzen'),
$this->url_for("admin/user/reset_notification/{$this->user->id}"),
Icon::create('refresh')
);
)->asButton();
}
if ($this->action === 'activities') {
......@@ -1724,7 +1734,7 @@ class Admin_UserController extends AuthenticatedController
_('Alle Dateien des Nutzers aus Veranstaltungen und Einrichtungen als ZIP herunterladen'),
$this->url_for("admin/user/download_user_files/{$this->user->user_id}"),
Icon::create('folder-full')
);
)->asButton();
}
if ($this->user->id !== $GLOBALS['user']->id && TFASecret::exists($this->user->id)) {
......@@ -1732,7 +1742,7 @@ class Admin_UserController extends AuthenticatedController
_('Zwei-Faktor-Authentifizierung deaktivieren'),
$this->url_for("admin/user/reset_tfa/{$this->user->id}"),
Icon::create('code-qr')
);
)->asButton();
}
$sidebar->insertWidget($user_actions, 'actions', 'user_actions');
......
......@@ -351,7 +351,7 @@ class BlubberController extends AuthenticatedController
if (mb_strpos($file['type'], 'video') !== false) {
$type = 'video';
}
if (mb_strpos($file['type'], 'audio') !== false || mb_strpos($file_ref['name'], '.ogg') !== false) {
if (mb_strpos($file['type'], 'audio') !== false || mb_strpos($file['name'], '.ogg') !== false) {
$type = 'audio';
}
if ($type) {
......@@ -414,11 +414,7 @@ class BlubberController extends AuthenticatedController
$statement = DBManager::get()->prepare($query);
$statement->execute([$this->thread->id]);
foreach ($statement->fetchFirst() as $user_id) {
$member = new CourseMember();
$member['user_id'] = $user_id;
$member['seminar_id'] = $course->getId();
$member['status'] = $user_id === $this->thread['user_id'] ? 'dozent' : 'tutor';
$member->store();
CourseMember::insertCourseMember($course->getId(), $user_id, $user_id === $this->thread['user_id'] ? 'dozent' : 'tutor');
}
$this->thread['context_type'] = 'course';
......
......@@ -243,7 +243,11 @@ class Calendar_ScheduleController extends AuthenticatedController
$this->render_template('calendar/schedule/_entry_course');
} else if ($id) {
$entry_columns = CalendarScheduleModel::getScheduleEntries($GLOBALS['user']->id, 0, 0, $id);
$entries = array_pop($entry_columns)->getEntries();
$entries = [];
$entry_columns = array_pop($entry_columns);
if ($entry_columns) {
$entries = $entry_columns->getEntries();
}
$this->show_entry = array_pop($entries);
$this->render_template('calendar/schedule/_entry_schedule');
}
......
......@@ -16,6 +16,10 @@ class Consultation_AdminController extends ConsultationController
{
parent::before_filter($action, $args);
if (!$this->range || $action === 'not_found') {
return;
}
if (!$this->range->isEditableByUser()) {
throw new AccessDeniedException();
}
......
......@@ -12,14 +12,21 @@ abstract class ConsultationController extends AuthenticatedController
{
parent::before_filter($action, $args);
$type = 'person';
if (Request::submitted('username')) {
$this->range = User::findByUsername(Request::username('username'));
} elseif (Request::submitted('cid')) {
$this->range = Context::get();
$type = 'object';
} else {
$this->range = $GLOBALS['user']->getAuthenticatedUser();
}
if (!$this->range) {
$this->redirect($this->not_foundURL($type));
return;
}
if ($this->range instanceof User) {
URLHelper::addLinkParam('username', $this->range->username);
} elseif ($this->range instanceof Course || $this->range instanceof Institute) {
......@@ -47,6 +54,12 @@ abstract class ConsultationController extends AuthenticatedController
};
}
public function not_found_action(string $type): void
{
$this->type = $type;
$this->render_template('consultation/not_found', $this->layout);
}
protected function activateNavigation($path)
{
$path = ltrim($path, '/');
......
......@@ -14,6 +14,10 @@ class Consultation_OverviewController extends ConsultationController
{
parent::before_filter($action, $args);
if (!$this->range) {
return;
}
if ($this->range->isEditableByUser()) {
$this->redirect('consultation/admin');
}
......
......@@ -121,6 +121,7 @@ class Course_AdmissionController extends AuthenticatedController
CSRFProtection::verifyUnsafeRequest();
PageLayout::setTitle(_('Anmeldemodus ändern'));
$request = null;
$question = null;
if (Request::submitted('change_admission_prelim')) {
$request = Request::extract('admission_prelim int, admission_binding submitted, admission_prelim_txt');
$request = array_diff_key($request, array_filter($this->is_locked));
......@@ -278,7 +279,7 @@ class Course_AdmissionController extends AuthenticatedController
$limit = $this->course->getNumWaiting() - $this->course->admission_waitlist_max;
$removed_applicants = $this->course->admission_applicants->findBy('status', 'awaiting')->orderBy('position desc', SORT_NUMERIC)->limit($limit);
}
if ($removed_applicants) {
if (!empty($removed_applicants)) {
$num_moved = 0;
foreach ($removed_applicants as $applicant) {
setTempLanguage($applicant->user_id);
......
......@@ -490,7 +490,7 @@ class Course_BasicdataController extends AuthenticatedController
} else {
// format of input element name is "course_xxx"
$varname = mb_substr($field['name'], 7);
if ($field['i18n']) {
if (!empty($field['i18n'])) {
$req_value = Request::i18n($field['name']);
} else {
$req_value = Request::get($field['name']);
......@@ -528,7 +528,12 @@ class Course_BasicdataController extends AuthenticatedController
$after = array_diff_assoc($sem->getSettings(), $old_settings);
//update admission, if turnout was raised
if($after['admission_turnout'] > $before['admission_turnout'] && $sem->isAdmissionEnabled()) {
if (
!empty($after['admission_turnout'])
&& !empty($before['admission_turnout'])
&& $after['admission_turnout'] > $before['admission_turnout']
&& $sem->isAdmissionEnabled()
) {
AdmissionApplication::addMembers($sem->getId());
}
......
......@@ -20,6 +20,9 @@ class Course_CoursewareController extends CoursewareController
{
parent::before_filter($action, $args);
if (!Context::get()) {
throw new CheckObjectException(_('Sie haben kein Objekt gewählt.'));
}
PageLayout::setTitle(Context::get()->getFullname() . ' - ' . _('Courseware'));
PageLayout::setHelpKeyword('Basis.Courseware');
......@@ -44,7 +47,7 @@ class Course_CoursewareController extends CoursewareController
public function courseware_action($unit_id = null): void
{
global $user;
Navigation::activateItem('course/courseware/unit');
if ($this->unitsNotFound) {
PageLayout::postMessage(MessageBox::info(_('Es wurde kein Lernmaterial gefunden.')));
......
......@@ -67,11 +67,11 @@ class Course_DetailsController extends AuthenticatedController
public function index_action()
{
$this->prelim_discussion = vorbesprechung($this->course->id);
$this->title = $this->course->getFullname();
$this->course_domains = UserDomain::getUserDomainsForSeminar($this->course->id);
$this->sem = new Seminar($this->course);
$this->links = [];
//public folders
$folders = Folder::findBySQL("range_type='course' AND range_id = ? AND folder_type = 'CoursePublicFolder'", [$this->course->id]);
......@@ -111,7 +111,7 @@ class Course_DetailsController extends AuthenticatedController
return false;
}
$modul_start = Semester::find($modul->start)->beginn ?: 0;
$modul_end = Semester::find($modul->end)->ende ?: PHP_INT_MAX;
$modul_end = $modul->end ? Semester::find($modul->end)->ende : PHP_INT_MAX;
return ($modul_start <= $course_end && $modul_end >= $course_start);
});
......@@ -233,6 +233,12 @@ class Course_DetailsController extends AuthenticatedController
['data-dialog' => 'size=big']
);
$this->links[] = [
'label' => $abo_msg,
'url' => $this->url_for("course/enrolment/apply/{$this->course->id}"),
'attributes' => ['data-dialog' => 'size=big'],
];
}
if (Config::get()->SCHEDULE_ENABLE
......@@ -253,6 +259,12 @@ class Course_DetailsController extends AuthenticatedController
$this->url_for("calendar/schedule/addvirtual/{$this->course->id}"),
Icon::create('info')
);
$this->links[] = [
'label' => _('Nur im Stundenplan vormerken'),
'url' => $this->url_for("calendar/schedule/addvirtual/{$this->course->id}"),
'attributes' => [],
];
}
}
......