aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRasmus Fuhse <fuhse@data-quest.de>2023-07-11 13:49:47 +0000
committerRasmus Fuhse <fuhse@data-quest.de>2023-07-11 13:49:47 +0000
commite234779f2cf3195b2a14a4179f05c31038e478be (patch)
tree6eed77119dd36103f8164556123e7198b15aef93
parent3beb9a1c6a6d5df15ac0fc3d856b44c9da2faeec (diff)
Resolve "Umstellung der Adminseite für Veranstaltungen auf vue.js"
Closes #1965 Merge request studip/studip!1280
-rw-r--r--app/controllers/admin/courses.php866
-rw-r--r--app/controllers/course/basicdata.php9
-rw-r--r--app/controllers/course/lvgselector.php9
-rw-r--r--app/controllers/course/study_areas.php8
-rw-r--r--app/controllers/course/timesrooms.php6
-rw-r--r--app/controllers/resources/room_request.php2
-rw-r--r--app/views/admin/courses/_course.php227
-rw-r--r--app/views/admin/courses/aux-select.php4
-rw-r--r--app/views/admin/courses/aux_preselect.php2
-rw-r--r--app/views/admin/courses/get_subcourses.php12
-rw-r--r--app/views/admin/courses/index.php47
-rw-r--r--app/views/admin/courses/lock.php4
-rw-r--r--app/views/admin/courses/lock_preselect.php6
-rw-r--r--app/views/admin/courses/sidebar.php26
-rw-r--r--app/views/course/lvgselector/index.php4
-rw-r--r--app/views/course/study_areas/show.php4
-rw-r--r--db/migrations/5.4.9_add_datafields_filter_config.php26
-rw-r--r--lib/classes/AdminCourseFilter.class.php448
-rw-r--r--lib/classes/SQLQuery.php10
-rw-r--r--lib/classes/sidebar/OptionsWidget.php3
-rw-r--r--lib/classes/sidebar/SearchWidget.php7
-rw-r--r--lib/classes/sidebar/SelectWidget.php9
-rw-r--r--lib/models/ConfigValue.php2
-rw-r--r--lib/models/Course.class.php10
-rw-r--r--resources/assets/javascripts/bootstrap/admin-courses.js29
-rw-r--r--resources/assets/javascripts/bootstrap/application.js18
-rw-r--r--resources/assets/javascripts/bootstrap/forms.js1
-rw-r--r--resources/assets/javascripts/init.js4
-rw-r--r--resources/assets/javascripts/lib/admin-courses.js20
-rw-r--r--resources/assets/javascripts/lib/screenreader.js8
-rw-r--r--resources/assets/stylesheets/less/tables.less20
-rw-r--r--resources/assets/stylesheets/scss/admin.scss3
-rw-r--r--resources/vue/components/AdminCourses.vue265
-rw-r--r--resources/vue/store/AdminCoursesStore.js170
-rw-r--r--templates/layouts/base.php1
-rw-r--r--templates/sidebar/search-widget.php6
-rw-r--r--templates/sidebar/select-widget.php14
-rw-r--r--templates/sidebar/selector-widget.php4
38 files changed, 1368 insertions, 946 deletions
diff --git a/app/controllers/admin/courses.php b/app/controllers/admin/courses.php
index edf529b..19a577f 100644
--- a/app/controllers/admin/courses.php
+++ b/app/controllers/admin/courses.php
@@ -30,43 +30,6 @@ class Admin_CoursesController extends AuthenticatedController
{
/**
- * This helper method retrieves the values of datafields when
- * the user started a search for courses matching a specific value of
- * one or more datafields.
- * This method also checks if a datafield is activated by the user
- * and will reject any value for datafields that aren't activated by the user.
- *
- * @return Array Associative array, consisting of datafield names
- * (as array keys) and values for those datafields.
- */
- private function getDatafieldFilters()
- {
- //first get the active datafields of the user:
- $userSelectedElements = $this->getActiveElements();
- $activeDatafields = $userSelectedElements['datafields'] ?? [];
-
- if (!$activeDatafields) {
- return [];
- }
-
- //Ok, we have a list of active datafields whose value may be searched for.
- //We must check for the request parameters (df_$DATAFIELD_ID)
- //and return their IDs with a value.
-
- $searchedDatafields = [];
-
- foreach ($activeDatafields as $activeField) {
- $requestParamValue = Request::get('df_'.$activeField);
- if ($requestParamValue) {
- $searchedDatafields[$activeField] = $requestParamValue;
- }
- }
-
- return $searchedDatafields;
- }
-
-
- /**
* This method returns the appropriate widget for the given datafield.
*
* @param DataField datafield The datafield whose widget is requested.
@@ -79,6 +42,8 @@ class Admin_CoursesController extends AuthenticatedController
//The current user is allowed to see this datafield.
//Now we must distinguish between the different types of data fields:
+ $datafields_filters = $GLOBALS['user']->cfg->ADMIN_COURSES_DATAFIELDS_FILTERS;
+
$type = $datafield->type;
if ($type == 'bool') {
@@ -86,14 +51,15 @@ class Admin_CoursesController extends AuthenticatedController
$checkboxWidget = new OptionsWidget($datafield->name);
$checkboxWidget->addCheckbox(
_('Feld gesetzt'),
- Request::bool('df_'.$datafield->id, false),
+ Request::bool('df_'.$datafield->id, $datafields_filters[$datafield->id] ?? false),
URLHelper::getURL(
'dispatch.php/admin/courses/index',
['df_'.$datafield->id => '1']
),
URLHelper::getURL(
'dispatch.php/admin/courses/index'
- )
+ ),
+ ['onclick' => "$(this).toggleClass(['options-checked', 'options-unchecked']); STUDIP.AdminCourses.App.changeFilter({'df_".$datafield->id."': $(this).hasClass('options-checked') ? 1 : 0}); return false;"]
);
return $checkboxWidget;
} elseif ($type == 'selectbox' || $type == 'radio' || $type == 'selectboxmultiple') {
@@ -103,21 +69,27 @@ class Admin_CoursesController extends AuthenticatedController
)));
if ($options) {
- $options = array_merge(
- [' ' => '(' . _('keine Auswahl') . ')'],
- $options
- );
-
$selectWidget = new SelectWidget(
$datafield->name,
'?',
'df_' . $datafield->id
);
- foreach($options as $option) {
+ $selectWidget->addElement(
+ new SelectElement(
+ '',
+ '(' . _('keine Auswahl') . ')'
+ )
+ );
+ foreach ($options as $option) {
$selectWidget->addElement(
- new SelectElement($option, $option, Request::get('df_'.$datafield->id) == $option)
+ new SelectElement(
+ $option,
+ $option,
+ Request::get('df_'.$datafield->id, $datafields_filters[$datafield->id] ?? null) === $option
+ )
);
}
+ $selectWidget->setOnSubmitHandler("STUDIP.AdminCourses.App.changeFilter({'df_".$datafield->id."': $(this).find('select').val()}); return false;");
return $selectWidget;
}
return null;
@@ -127,8 +99,13 @@ class Admin_CoursesController extends AuthenticatedController
$textWidget->setTitle($datafield->name);
$textWidget->addNeedle(
'',
- 'df_'.$datafield->id
+ 'df_'.$datafield->id,
+ false,
+ null,
+ null,
+ $datafields_filters[$datafield->id]
);
+ $textWidget->setOnSubmitHandler("STUDIP.AdminCourses.App.changeFilter({'df_".$datafield->id."': $(this).find('input').val()}); return false;");
return $textWidget;
}
}
@@ -144,7 +121,7 @@ class Admin_CoursesController extends AuthenticatedController
* @param string courseTypeFilterConfig The selected value for the course type filter field, defaults to null.
* @return null This method does not return any value.
*/
- private function buildSidebar($courseTypeFilterConfig = null)
+ private function buildSidebar()
{
/*
Depending on the elements the user has selected
@@ -153,6 +130,10 @@ class Admin_CoursesController extends AuthenticatedController
*/
$visibleElements = $this->getActiveElements();
+ $this->sem_create_perm = in_array(Config::get()->SEM_CREATE_PERM, ['root', 'admin', 'dozent'])
+ ? Config::get()->SEM_CREATE_PERM
+ : 'dozent';
+
$sidebar = Sidebar::get();
/*
@@ -179,13 +160,13 @@ class Admin_CoursesController extends AuthenticatedController
$this->setSemesterSelector();
}
if (!empty($visibleElements['stgteil'])) {
- $this->setStgteilSelector();
+ Sidebar::Get()->addWidget($this->getStgteilSelector(), 'filter_stgteil');
}
if (!empty($visibleElements['courseType'])) {
- $this->setCourseTypeWidget($courseTypeFilterConfig);
+ $this->setCourseTypeWidget();
}
if (!empty($visibleElements['teacher'])) {
- $this->setTeacherWidget();
+ Sidebar::Get()->addWidget($this->getTeacherWidget(), 'filter_teacher');
}
//if there are datafields in the list, draw their input fields, too:
@@ -213,7 +194,7 @@ class Admin_CoursesController extends AuthenticatedController
//this shall be visible in every case:
- $this->setActionsWidget($this->selected_action);
+ $this->setActionsWidget();
//actions: always visible, too
@@ -296,20 +277,15 @@ class Admin_CoursesController extends AuthenticatedController
$this->semester = Semester::find($GLOBALS['user']->cfg->MY_COURSES_SELECTED_CYCLE);
}
- if (Request::submitted('search')) {
- $GLOBALS['user']->cfg->store('ADMIN_COURSES_SEARCHTEXT', Request::get('search'));
- }
if (Request::get('reset-search')) {
$GLOBALS['user']->cfg->delete('ADMIN_COURSES_SEARCHTEXT');
}
- if (Request::submitted('teacher_filter')) {
- $GLOBALS['user']->cfg->store('ADMIN_COURSES_TEACHERFILTER', Request::option('teacher_filter'));
- }
PageLayout::setHelpKeyword('Basis.Veranstaltungen');
PageLayout::setTitle(_('Verwaltung von Veranstaltungen und Einrichtungen'));
// Add admission functions.
PageLayout::addScript('studip-admission.js');
+ $this->max_show_courses = 500;
}
/**
@@ -317,67 +293,457 @@ class Admin_CoursesController extends AuthenticatedController
*/
public function index_action()
{
- $this->sem_create_perm = in_array(Config::get()->SEM_CREATE_PERM, ['root', 'admin', 'dozent'])
- ? Config::get()->SEM_CREATE_PERM
- : 'dozent';
+ $this->fields = $this->getViewFilters();
+ $this->sortby = $GLOBALS['user']->cfg->MEINE_SEMINARE_SORT ?? 'name';
+ $this->sortflag = $GLOBALS['user']->cfg->MEINE_SEMINARE_SORT_FLAG ?? 'ASC';
+
+ $this->buildSidebar();
+
+ PageLayout::addHeadElement('script', [
+ 'type' => 'text/javascript',
+ ], sprintf(
+ 'window.AdminCoursesStoreData = %s;',
+ json_encode($this->getStoreData())
+ ));
+ }
+
+ private function getStoreData(): array
+ {
+ $configuration = User::findCurrent()->getConfiguration();
+
+ $institut_id = $configuration->MY_INSTITUTES_DEFAULT && $configuration->MY_INSTITUTES_DEFAULT !== 'all'
+ ? $configuration->MY_INSTITUTES_DEFAULT
+ : null;
+
+
+ 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,
+ ]
+ )),
+ ];
+ }
+
+ private function getDatafieldFilters(): array
+ {
+ $visibleElements = $this->getActiveElements();
+ if (empty($visibleElements['datafields'])) {
+ return [];
+ }
+
+ $datafields = DataField::getDataFields('sem');
+ $config = $GLOBALS['user']->cfg->ADMIN_COURSES_DATAFIELDS_FILTERS;
- // get courses only if institutes available
- $this->actions = $this->getActions();
+ $datafields = array_filter($datafields, function (Datafield $datafield) use ($visibleElements, $config) {
+ return in_array($datafield->id, $visibleElements['datafields'])
+ && isset($config[$datafield->id]);
+ });
- $config_my_course_type_filter = $GLOBALS['user']->cfg->MY_COURSES_TYPE_FILTER;
+ $result = [];
+ foreach ($datafields as $datafield) {
+ $result["df_{$datafield->id}"] = $config[$datafield->id];
+ }
+ return $result;
+ }
- // Get the view filter
- $this->view_filter = $this->getFilterConfig();
+ 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::get('sortFlag')) {
- $GLOBALS['user']->cfg->store('MEINE_SEMINARE_SORT_FLAG', Request::get('sortFlag') === 'asc' ? 'DESC' : 'ASC');
+ 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('sortby')) {
- $GLOBALS['user']->cfg->store('MEINE_SEMINARE_SORT', Request::option('sortby'));
+
+ 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');
}
- $this->selected_action = $GLOBALS['user']->cfg->MY_COURSES_ACTION_AREA;
- if (is_null($this->selected_action) || (!is_numeric($this->selected_action) && !class_exists($this->selected_action))) {
- $this->selected_action = 1;
+ if (Request::option('stgteil')) {
+ $GLOBALS['user']->cfg->store('MY_COURSES_SELECTED_STGTEIL', Request::option('stgteil'));
+ } else {
+ $GLOBALS['user']->cfg->delete('MY_COURSES_SELECTED_STGTEIL');
}
- $this->sortby = $GLOBALS['user']->cfg->MEINE_SEMINARE_SORT;
- $this->sortFlag = $GLOBALS['user']->cfg->MEINE_SEMINARE_SORT_FLAG ?: 'ASC';
+ if (Request::option('teacher_filter')) {
+ $GLOBALS['user']->cfg->store('ADMIN_COURSES_TEACHERFILTER', Request::option('teacher_filter'));
+ } else {
+ $GLOBALS['user']->cfg->delete('ADMIN_COURSES_TEACHERFILTER');
+ }
- $this->courses = $this->getCourses([
- 'sortby' => $this->sortby,
- 'sortFlag' => $this->sortFlag,
- 'view_filter' => $this->view_filter,
- 'typeFilter' => $config_my_course_type_filter,
- 'datafields' => $this->getDatafieldFilters()
- ], Request::get('display') === 'all');
+ $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);
- if (in_array('contents', $this->view_filter)) {
- $this->nav_elements = MyRealmModel::calc_nav_elements([$this->courses]);
+ $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')]);
}
- // get all available teacher for infobox-filter
- // filter by selected teacher
- $_SESSION['MY_COURSES_LIST'] = array_map(function ($c, $id) {
- return [
- 'Name' => $c['Name'],
- 'Seminar_id' => $id
+ $count = $filter->countCourses();
+ if ($count > $this->max_show_courses && !Request::submitted('without_limit')) {
+ $this->render_json([
+ 'count' => $count
+ ]);
+ return;
+ }
+ $courses = AdminCourseFilter::get()->getCourses();
+
+ $data = [
+ 'data' => []
];
- }, array_values($this->courses), array_keys($this->courses));
+ if (Request::submitted('activated_fields')) {
+ $GLOBALS['user']->cfg->store('MY_COURSES_ADMIN_VIEW_FILTER_ARGS', json_encode(Request::getArray('activated_fields')));
+ }
+ $activated_fields = $this->getFilterConfig();
+
+ $GLOBALS['user']->cfg->store('MY_COURSES_ACTION_AREA', Request::option('action'));
+ foreach ($courses as $course) {
+ if ($course->parent_course && !Request::option('course_id')) {
+ continue;
+ }
+ $data['data'][] = $this->getCourseData($course, $activated_fields);
+ foreach ($course->children as $childcourse) {
+ $data['data'][] = $this->getCourseData($childcourse, $activated_fields);
+ }
+ }
+ $tf = new Flexi_TemplateFactory($GLOBALS['STUDIP_BASE_PATH'] . '/app/views');
+ switch ($GLOBALS['user']->cfg->MY_COURSES_ACTION_AREA) {
+ case 1:
+ case 2:
+ case 3:
+ case 4:
+ break;
+ case 8: //Sperrebenen
+ $template = $tf->open('admin/courses/lock_preselect');
+ $template->course = $course;
+ $template->all_lock_rules = new SimpleCollection(array_merge(
+ [[
+ 'name' => '--' . _('keine Sperrebene') . '--',
+ 'lock_id' => 'none'
+ ]],
+ LockRule::findAllByType('sem')
+ ));
+ $data['buttons_top'] = $template->render();
+ $data['buttons_bottom'] = (string) \Studip\Button::createAccept(_('Sperrebenen'), 'locking_button', ['formaction' => URLHelper::getURL('dispatch.php/admin/courses/set_lockrule')]);
+ break;
+ case 9: //Sichtbarkeit
+ $data['buttons_top'] = '<label>'._('Alle auswählen').'<input type="checkbox" data-proxyfor=".course-admin td:last-child :checkbox"></label>';
+ $data['buttons_bottom'] = (string) \Studip\Button::createAccept(_('Sichtbarkeit'), 'visibility_button', ['formaction' => URLHelper::getURL('dispatch.php/admin/courses/set_visibility')]);
+ break;
+ case 10: //Zusatzangaben
+ $template = $tf->open('admin/courses/aux_preselect');
+ $template->course = $course;
+ $template->aux_lock_rules = AuxLockRule::findBySQL('1 ORDER BY name ASC');
+ $data['buttons_top'] = $template->render();
+ $data['buttons_bottom'] = (string) \Studip\Button::createAccept(_('Zusatzangaben'), 'aux_button', ['formaction' => URLHelper::getURL('dispatch.php/admin/courses/set_aux_lockrule')]);
+ break;
+ case 11: //Veranstaltung kopieren
+ break;
+ case 14: //Zugangsberechtigungen
+ break;
+ case 16: //Löschen
+ $data['buttons_top'] = '<label>'._('Alle auswählen').'<input type="checkbox" data-proxyfor=".course-admin td:last-child :checkbox"></label>';
+ $data['buttons_bottom'] = (string) \Studip\Button::createAccept(_('Löschen'), 'deleting_button', ['formaction' => URLHelper::getURL('dispatch.php/course/archive/confirm')]);
+ break;
+ case 17: //Gesperrte Veranstaltungen
+ $data['buttons_top'] = '<label>'._('Alle auswählen').'<input type="checkbox" data-proxyfor=".course-admin td:last-child :checkbox"></label>';
+ $data['buttons_bottom'] = (string) \Studip\Button::createAccept(_('Einstellungen speichern'), 'locking_button', ['formaction' => URLHelper::getURL('dispatch.php/admin/courses/set_locked')]);
+ break;
+ case 18: //Startsemester
+ break;
+ case 19: //LV-Gruppen
+ break;
+ case 20: //Notiz
+ break;
+ default:
+ foreach (PluginManager::getInstance()->getPlugins('AdminCourseAction') as $plugin) {
+ if ($GLOBALS['user']->cfg->MY_COURSES_ACTION_AREA === get_class($plugin)) {
+ $multimode = $plugin->useMultimode();
+ if ($multimode) {
+ $data['buttons_top'] = '<label>'._('Alle auswählen').'<input type="checkbox" data-proxyfor=".course-admin td:last-child :checkbox"></label>';
+ if ($multimode instanceof Flexi_Template) {
+ $data['buttons_bottom'] = $multimode->render();
+ } elseif (is_string($multimode)) {
+ $data['buttons_bottom'] = (string) \Studip\Button::create($multimode, '', ['formaction' => $plugin->getAdminActionURL()]);
+ } else {
+ $data['buttons_bottom'] = (string) \Studip\Button::create(_('Speichern'), '', ['formaction' => $plugin->getAdminActionURL()]);
+ }
+ }
+ }
+ break;
+ }
+ }
+ if (!isset($data['buttons_top'])) {
+ $data['buttons_top'] = '';
+ }
+ if (!isset($data['buttons_bottom'])) {
+ $data['buttons_bottom'] = '';
+ }
+ $this->render_json($data);
+ }
- $this->all_lock_rules = new SimpleCollection(array_merge(
- [[
- 'name' => '--' . _("keine Sperrebene") . '--',
- 'lock_id' => 'none'
- ]],
- LockRule::findAllByType('sem')
- ));
- $this->aux_lock_rules = AuxLockRule::findBySQL('1 ORDER BY name');
+ protected function getCourseData(Course $course, $activated_fields)
+ {
+ $d = [
+ 'id' => $course->id,
+ 'parent_course' => $course->parent_course
+ ];
+ if (in_array('name', $activated_fields)) {
+ $params = tooltip2(_('Veranstaltungsdetails anzeigen'));
+ $params['style'] = 'cursor: pointer';
+ $d['name'] = '<a href="'.URLHelper::getLink('seminar_main.php', ['auswahl' => $course->id]).'">'
+ . htmlReady($course->name)
+ .'</a> '
+ .'<a href="'.URLHelper::getLink('dispatch.php/course/details/index/'. $course->id).'" data-dialog><button class="undecorated">'.Icon::create('info-circle', Icon::ROLE_INACTIVE)->asImg($params).'</button></a> '
+ .(!$course->visible ? _('(versteckt)') : '');
+ }
+ if (in_array('number', $activated_fields)) {
+ $d['number'] = '<a href="'.URLHelper::getLink('seminar_main.php', ['auswahl' => $course->id]).'">'
+ .$course->veranstaltungsnummer
+ .'</a>';
+ }
+ if (in_array('avatar', $activated_fields)) {
+ $d['avatar'] = '<a href="'.URLHelper::getLink('seminar_main.php', ['auswahl' => $course->id]).'">'
+ .CourseAvatar::getAvatar($course->getId())->getImageTag(Avatar::SMALL, ['title' => $course->name])
+ ."</a>";
+ }
+ if (in_array('type', $activated_fields)) {
+ $semtype = $course->getSemType();
+ $d['type'] = $semtype['name'];
+ }
+ if (in_array('room_time', $activated_fields)) {
+ $d['room_time'] = Seminar::GetInstance($course->id)->getDatesHTML([
+ 'show_room' => true,
+ ]) ?: _('nicht angegeben');
+ }
+ if (in_array('semester', $activated_fields)) {
+ $d['semester'] = $course->semester_text;
+ }
+ if (in_array('institute', $activated_fields)) {
+ $d['institute'] = $course->home_institut ? $course->home_institut->name : $course->institute;
+ }
+ if (in_array('requests', $activated_fields)) {
+ $d['requests'] = '<a href="'.URLHelper::getLink('dispatch.php/course/room_requests', ['cid' => $course->id]).'">'.count($course->room_requests)."</a>";
+ }
+ if (in_array('teachers', $activated_fields)) {
+ $teachers = $this->getTeacher($course->id);
+ $teachers = array_map(function ($teacher) {
+ return '<a href="'.URLHelper::getLink('dispatch.php/profile', ['username' => $teacher['username']]) .'">'. htmlReady($teacher['fullname']).'</a>';
+ }, $teachers);
+ $d['teachers'] = implode(', ', $teachers);
+ }
+ if (in_array('members', $activated_fields)) {
+ $d['members'] = '<a href="'.URLHelper::getLink('dispatch.php/course/members', ['cid' => $course->id]).'">'
+ .$course->getNumParticipants()
+ .'</a>';
+ }
+ if (in_array('waiting', $activated_fields)) {
+ $d['waiting'] = '<a href="'.URLHelper::getLink('dispatch.php/course/members', ['cid' => $course->id]).'">'
+ .$course->getNumWaiting()
+ .'</a>';
+ }
+ if (in_array('preliminary', $activated_fields)) {
+ $d['preliminary'] = '<a href="'.URLHelper::getLink('dispatch.php/course/members', ['cid' => $course->id]).'">'
+ .$course->getNumPrelimParticipants()
+ .'</a>';
+ }
+ if (in_array('contents', $activated_fields)) {
+ $icons = [];
+ foreach ($course->tools as $tool) {
+ $module = $tool->getStudipModule();
+ if ($module) {
+ $last_visit = object_get_visit($course->id, $module->getPluginId());
+ $nav = $module->getIconNavigation($course->id, $last_visit, $GLOBALS['user']->id);
+ if (isset($nav) && $nav->isVisible(true)) {
+ $icons[] = $nav;
+ }
+ }
+ }
+ $d['contents'] = '<div class="icons">
+ <ul class="my-courses-navigation">';
+
+ foreach ($icons as $icon) {
+ $d['contents'] .= '<li class="my-courses-navigation-item '. ($icon->getImage()->signalsAttention() ? 'my-courses-navigation-important' : '').'">
+ <a href="'. URLHelper::getLink('seminar_main.php', ['auswahl' => $course->id, 'redirect_to' => $icon->getURL()]).'"'. ($icon->getTitle() ? ' title="'.htmlReady($icon->getTitle()).'"' : '') .'>
+ '. $icon->getImage()->asImg(20) .'
+ </a>
+ </li>';
+ }
+ $d['contents'] .= '</ul></div>';
+ }
+ if (in_array('last_activity', $activated_fields)) {
+ $d['last_activity'] = date('%x', lastActivity($course->id));
+ }
+ foreach (PluginManager::getInstance()->getPlugins('AdminCourseContents') as $plugin) {
+ foreach ($plugin->adminAvailableContents() as $index => $label) {
+ if (in_array($plugin->getPluginId() . '_' . $index, $activated_fields)) {
+ $content = $plugin->adminAreaGetCourseContent($course, $index);
+ $d[$plugin->getPluginId()."_".$index] = $content instanceof Flexi_Template ? $content->render() : $content;
+ }
+ }
+ }
+ $tf = new Flexi_TemplateFactory($GLOBALS['STUDIP_BASE_PATH'].'/app/views');
- //build the sidebar:
- $this->buildSidebar($config_my_course_type_filter);
+ switch ($GLOBALS['user']->cfg->MY_COURSES_ACTION_AREA) {
+ case 1:
+ $d['action'] = (string) \Studip\LinkButton::create(
+ _('Grunddaten'),
+ URLHelper::getURL('dispatch.php/course/basicdata/view', ['cid' => $course->id]),
+ ['data-dialog' => '', 'role' => 'button']
+ );
+ break;
+ case 2:
+ $d['action'] = (string) \Studip\LinkButton::create(
+ _('Studienbereiche'),
+ URLHelper::getURL('dispatch.php/course/study_areas/show', ['cid' => $course->id, 'from' => 'admin/courses']),
+ ['data-dialog' => '', 'role' => 'button']
+ );
+ break;
+ case 3:
+ $d['action'] = (string) \Studip\LinkButton::create(
+ _('Zeiten/Räume'),
+ URLHelper::getURL('dispatch.php/course/timesrooms/index', ['cid' => $course->id, 'cmd' => 'applyFilter']),
+ ['data-dialog' => '', 'role' => 'button']
+ );
+ break;
+ case 4:
+ $d['action'] = (string) \Studip\LinkButton::create(
+ _('Raumanfragen'),
+ URLHelper::getURL('dispatch.php/course/room_requests/index', ['cid' => $course->id, 'origin' => 'admin_courses']),
+ ['data-dialog' => '', 'role' => 'button']
+ );
+ break;
+ case 8: //Sperrebenen
+ $template = $tf->open('admin/courses/lock');
+ $template->course = $course;
+ $template->aux_lock_rules = AuxLockRule::findBySQL('1 ORDER BY name ASC');
+ $template->all_lock_rules = new SimpleCollection(array_merge(
+ [[
+ 'name' => '--' . _('keine Sperrebene') . '--',
+ 'lock_id' => 'none'
+ ]],
+ LockRule::findAllByType('sem')
+ ));
+ $d['action'] = $template->render();
+ break;
+ case 9: //Sichtbarkeit
+ $d['action'] = '<input type="hidden" name="all_sem[]" value="'.htmlReady($course->id).'"><input type="checkbox" name="visibility['.$course->id.']" '.($course->visible ? ' checked ' : '').'value="1">';
+ break;
+ case 10: //Zusatzangaben
+ $template = $tf->open('admin/courses/aux-select');
+ $template->course = $course;
+ $template->aux_lock_rules = AuxLockRule::findBySQL('1 ORDER BY name ASC');
+ $d['action'] = $template->render();
+ break;
+ case 11: //Veranstaltung kopieren
+ $d['action'] = (string) \Studip\LinkButton::create(
+ _('Kopieren'),
+ URLHelper::getURL('dispatch.php/course/wizard/copy/' . $course->id),
+ ['data-dialog' => '', 'role' => 'button']
+ );
+ break;
+ case 14: //Zugangsberechtigungen
+ $d['action'] = (string) \Studip\LinkButton::create(
+ _('Zugangsberechtigungen'),
+ URLHelper::getURL('dispatch.php/course/admission', ['cid' => $course->id]),
+ ['data-dialog' => '', 'role' => 'button']
+ );
+ break;
+ case 16: //Löschen
+ $d['action'] = '<input type="checkbox" name="archiv_sem[]" value="'.htmlReady($course->id).'" aria-label="'.htmlReady(sprintf(_('Veranstaltung %s löschen'), $course->getFullName())).'">';
+ break;
+ case 17: //Gesperrte Veranstaltungen
+ $cs = CourseSet::getSetForCourse($course->id);
+ if ($cs) {
+ $locked = $cs->getId() === CourseSet::getGlobalLockedAdmissionSetId();
+ } else {
+ $locked = false;
+ }
+ $d['action'] = '<input type="hidden" name="all_sem[]" value="'.htmlReady($course->id).'"><input type="checkbox" name="admission_locked['.$course->getId().']" '.($locked ? 'checked' : '').' value="1" aria-label="'.htmlReady(sprintf(_('Veranstaltung %s sperren'), $course->getFullName())).'">';
+ break;
+ case 18: //Startsemester
+ $d['action'] = (string) \Studip\LinkButton::create(
+ _('Startsemester'),
+ URLHelper::getURL('dispatch.php/course/timesrooms/editSemester', ['cid' => $course->id, 'origin' => 'admin_courses']),
+ ['data-dialog' => '', 'role' => 'button']
+ );
+ break;
+ case 19: //LV-Gruppen
+ $d['action'] = (string) \Studip\LinkButton::create(
+ _('LV-Gruppen'),
+ URLHelper::getURL('dispatch.php/course/lvgselector', ['cid' => $course->id, 'from' => 'admin/courses']),
+ ['data-dialog' => '', 'role' => 'button']
+ );
+ break;
+ case 20: //Notiz
+ $method = $course->config->COURSE_ADMIN_NOTICE ? 'createHasNotice' : 'createHasNoNotice';
+ $d['action'] = (string) \Studip\LinkButton::$method(
+ _('Notiz'),
+ URLHelper::getURL('dispatch.php/admin/courses/notice/'.$course->id),
+ [
+ 'data-dialog' => 'size=auto',
+ 'title' => $course->config->COURSE_ADMIN_NOTICE,
+ 'role' => 'button'
+ ]
+ );
+ break;
+ default:
+ foreach (PluginManager::getInstance()->getPlugins('AdminCourseAction') as $plugin) {
+ if ($GLOBALS['user']->cfg->MY_COURSES_ACTION_AREA === get_class($plugin)) {
+ $output = $plugin->getAdminCourseActionTemplate($course->getId());
+ $d['action'] = $output instanceof Flexi_Template ? $output->render() : (string) $output;
+ }
+ break;
+ }
+ }
+ $d['completion'] = $course->completion;
+ return $d;
+ }
+ /**
+ * This action just stores the new settings for sorting the table of courses.
+ * @return void
+ */
+ public function sort_action()
+ {
+ if (Request::isPost()) {
+ $GLOBALS['user']->cfg->store('MEINE_SEMINARE_SORT', Request::get('sortby'));
+ $GLOBALS['user']->cfg->store('MEINE_SEMINARE_SORT_FLAG', Request::get('sortflag'));
+ }
+ $this->render_nothing();
}
@@ -454,6 +820,18 @@ class Admin_CoursesController extends AuthenticatedController
}
}
+ public function get_stdgangteil_selector_action($institut_id)
+ {
+ $selector = $this->getStgteilSelector($institut_id);
+ $this->render_text($selector->render(['base_class' => 'sidebar']));
+ }
+
+ public function get_teacher_selector_action($institut_id)
+ {
+ $selector = $this->getTeacherWidget($institut_id);
+ $this->render_text($selector->render(['base_class' => 'sidebar']));
+ }
+
/**
* Export action
@@ -463,15 +841,7 @@ class Admin_CoursesController extends AuthenticatedController
$filter_config = Request::getArray('fields');
if (count($filter_config) > 0) {
- $sortby = $GLOBALS['user']->cfg->getValue('MEINE_SEMINARE_SORT');
- $config_my_course_type_filter = $GLOBALS['user']->cfg->getValue('MY_COURSES_TYPE_FILTER');
-
- $courses = $this->getCourses([
- 'sortby' => $sortby,
- 'sortFlag' => 'asc',
- 'typeFilter' => $config_my_course_type_filter,
- 'view_filter' => $filter_config,
- ], true);
+ $courses = AdminCourseFilter::get()->getCourses();
$view_filters = $this->getViewFilters();
@@ -506,37 +876,40 @@ class Admin_CoursesController extends AuthenticatedController
}
if (in_array('requests', $filter_config)) {
- $row['requests'] = $course['requests'];
+ $row['requests'] = $course['room_requests'];
}
if (in_array('teachers', $filter_config)) {
- $row['teachers'] = implode(', ', array_map(function ($d) {
- return $d['fullname'];
- }, $course['dozenten']));
+ $row['teachers'] = implode(
+ ', ',
+ $course->teachers->map(function ($d) {
+ return $d->user->getFullName();
+ })
+ );
}
if (in_array('members', $filter_config)) {
- $row['members'] = $course['teilnehmer'];
+ $row['members'] = $course->getNumParticipants();
}
if (in_array('waiting', $filter_config)) {
- $row['waiting'] = $course['waiting'];
+ $row['waiting'] = $course->getNumWaiting();
}
if (in_array('preliminary', $filter_config)) {
- $row['preliminary'] = $course['prelim'];
+ $row['preliminary'] = $course->getNumPrelimParticipants();
}
if (in_array('last_activity', $filter_config)) {
- $row['last_activity'] = strftime('%x', $course['last_activity']);
+ $row['last_activity'] = strftime('%x', lastActivity($course->id));
}
if (in_array('semester', $filter_config)) {
- $row['semester'] = $course_model->getTextualSemester();
+ $row['semester'] = $course->getTextualSemester();
}
if (in_array('institute', $filter_config)) {
- $row['institute'] = $course_model->home_institut ? (string) $course_model->home_institut['name'] : $course_model['institut_id'];
+ $row['institute'] = $course->home_institut ? (string) $course->home_institut['name'] : $course['institut_id'];
}
foreach (PluginManager::getInstance()->getPlugins('AdminCourseContents') as $plugin) {
@@ -551,7 +924,7 @@ class Admin_CoursesController extends AuthenticatedController
}
}
- $data[$course_id] = $row;
+ $data[$course->id] = $row;
}
$captions = [];
@@ -581,51 +954,6 @@ class Admin_CoursesController extends AuthenticatedController
}
}
- /**
- * Set the selected institute or semester
- */
- public function set_selection_action()
- {
- if (Request::option('institute')) {
- $GLOBALS['user']->cfg->store('ADMIN_COURSES_TEACHERFILTER', null);
- $GLOBALS['user']->cfg->store('MY_COURSES_SELECTED_STGTEIL', null);
- $inst = explode('_', Request::option('institute'));
- $GLOBALS['user']->cfg->store('MY_INSTITUTES_DEFAULT', $inst[0]);
-
- if (isset($inst[1]) && $inst[1] === 'withinst') {
- $GLOBALS['user']->cfg->store('MY_INSTITUTES_INCLUDE_CHILDREN', 1);
- } else {
- $GLOBALS['user']->cfg->store('MY_INSTITUTES_INCLUDE_CHILDREN', 0);
- }
-
- PageLayout::postSuccess(_('Die gewünschte Einrichtung wurde ausgewählt!'));
- }
-
- if (Request::option('sem_select')) {
- $GLOBALS['user']->cfg->store('MY_COURSES_SELECTED_CYCLE', Request::option('sem_select'));
- if (Request::option('sem_select') !== "all") {
- $sem_name = Semester::find(Request::option('sem_select'))->name;
- PageLayout::postSuccess(sprintf(_('Das %s wurde ausgewählt'), htmlReady($sem_name)));
- } else {
- PageLayout::postSuccess(_('Semesterfilter abgewählt'));
- }
- }
-
- if (Request::option('stgteil_select')) {
- $GLOBALS['user']->cfg->store('MY_COURSES_SELECTED_STGTEIL', Request::option('stgteil_select'));
- if (Request::option('stgteil_select') !== "all") {
- PageLayout::postSuccess(sprintf(
- _('Der Studiengangteil %s wurde ausgewählt'),
- htmlReady(StudiengangTeil::find(Request::option('stgteil_select'))->getDisplayName())
- ));
- } else {
- PageLayout::postSuccess(_('Studiengangteilfilter abgewählt'));
- }
- }
-
- $this->redirect('admin/courses/index');
- }
-
/**
* Set the lockrules of courses
@@ -785,9 +1113,9 @@ class Admin_CoursesController extends AuthenticatedController
// force to pre selection
if (Request::submitted('all')) {
$value = Request::get('lock_sem_all');
- $value_forced = Request::int('aux_all_forced');
+ $value_forced = Request::int('aux_all_forced', 0);
} else {
- $value_forced = $lock_sem_forced[$course_id];
+ $value_forced = $lock_sem_forced[$course_id] ?? 0;
}
$course = Course::find($course_id);
@@ -823,53 +1151,6 @@ class Admin_CoursesController extends AuthenticatedController
/**
- * Set the selected view filter and store the selection in configuration
- */
- public function set_view_filter_action($filter = null, $state = true)
- {
- // store view filter in configuration
- if (!is_null($filter)) {
- $filters = $this->getFilterConfig();
-
- if ($state) {
- $filters = array_diff($filters, [$filter]);
- } else {
- $filters[] = $filter;
- }
-
- $this->setFilterConfig($filters);
- }
-
- $this->redirect('admin/courses/index');
- }
-
- /**
- * Set the selected action type and store the selection in configuration
- */
- public function set_action_type_action()
- {
- // select the action area
- if (Request::option('action_area')) {
- $GLOBALS['user']->cfg->store('MY_COURSES_ACTION_AREA', Request::option('action_area'));
- PageLayout::postSuccess(_('Der Aktionsbereich wurde erfolgreich übernommen!'));
- }
-
- $this->redirect('admin/courses/index');
- }
-
- /**
- * Set the selected course type filter and store the selection in configuration
- */
- public function set_course_type_action()
- {
- if (Request::option('course_type')) {
- $GLOBALS['user']->cfg->store('MY_COURSES_TYPE_FILTER', Request::option('course_type'));
- PageLayout::postSuccess(_('Der gewünschte Veranstaltungstyp wurde übernommen!'));
- }
- $this->redirect('admin/courses/index');
- }
-
- /**
* Marks a course as complete/incomplete.
*
* @param String $course_id Id of the course
@@ -904,11 +1185,9 @@ class Admin_CoursesController extends AuthenticatedController
$course->config->store('COURSE_ADMIN_NOTICE', trim(Request::get('notice')));
if (Request::isXhr()) {
- $this->response->add_header('X-Dialog-Notice', json_encode([
- 'id' => $course->id,
- 'notice' => $course->config->COURSE_ADMIN_NOTICE,
- ]));
- $this->render_nothing();
+ $this->response->add_header('X-Dialog-Execute', 'STUDIP.AdminCourses.App.reloadCourse');
+ $this->response->add_header('X-Dialog-Close', '1');
+ $this->render_text($course->id);
} else {
$this->redirect($this->indexURL("#course-{$course->id}"));
}
@@ -919,30 +1198,6 @@ class Admin_CoursesController extends AuthenticatedController
$this->notice = $course->config->COURSE_ADMIN_NOTICE;
}
- public function get_subcourses_action($course_id)
- {
- // get courses only if institutes available
- $this->actions = $this->getActions();
-
- // Get the view filter
- $this->view_filter = $this->getFilterConfig();
-
- $this->selected_action = $GLOBALS['user']->cfg->MY_COURSES_ACTION_AREA;
- if (is_null($this->selected_action) || (!is_numeric($this->selected_action) && !class_exists($this->selected_action))) {
- $this->selected_action = 1;
- }
-
- $this->courses = $this->getCourses([
- 'sortby' => $this->sortby,
- 'sortFlag' => $this->sortFlag,
- 'view_filter' => $this->view_filter,
- 'datafields' => $this->getDatafieldFilters(),
- 'parent_course' => $course_id
- ]);
-
- $this->parent = $course_id;
-
- }
/**
* Return a specifically action or all available actions
@@ -1090,6 +1345,7 @@ class Admin_CoursesController extends AuthenticatedController
private function getViewFilters()
{
$views = [
+ 'avatar' => _('Avatar'),
'number' => _('Nr.'),
'name' => _('Name'),
'type' => _('Veranstaltungstyp'),
@@ -1301,9 +1557,9 @@ class Admin_CoursesController extends AuthenticatedController
* Adds view filter to the sidebar
* @param array $configs
*/
- private function setViewWidget($configs = [])
+ private function setViewWidget()
{
- $configs = $configs ?: [];
+ $configs = $this->getFilterConfig();
$checkbox_widget = new OptionsWidget();
$checkbox_widget->setTitle(_('Darstellungsfilter'));
@@ -1312,7 +1568,9 @@ class Admin_CoursesController extends AuthenticatedController
$checkbox_widget->addCheckbox(
$label,
$state,
- $this->url_for('admin/courses/set_view_filter/' . $index . '/' . $state)
+ $this->url_for('admin/courses/set_view_filter/' . $index . '/' . $state),
+ null,
+ ['onclick' => "$(this).toggleClass(['options-checked', 'options-unchecked']); $(this).attr('aria-checked', $(this).hasClass('options-checked') ? 'true' : 'false'); STUDIP.AdminCourses.App.toggleActiveField('".$index."'); return false;"]
);
}
Sidebar::get()->addWidget($checkbox_widget, 'views');
@@ -1333,9 +1591,9 @@ class Admin_CoursesController extends AuthenticatedController
if ($GLOBALS['perm']->have_perm('root') || (count($this->insts) > 1)) {
$list->addElement(new SelectElement(
- 'all',
+ '',
$GLOBALS['perm']->have_perm('root') ? _('Alle') : _('Alle meine Einrichtungen'),
- $GLOBALS['user']->cfg->MY_INSTITUTES_DEFAULT === 'all'),
+ !$GLOBALS['user']->cfg->MY_INSTITUTES_DEFAULT),
'select-all'
);
}
@@ -1366,6 +1624,7 @@ class Admin_CoursesController extends AuthenticatedController
);
}
}
+ $list->setOnSubmitHandler("STUDIP.AdminCourses.changeFiltersDependendOnInstitute($(this).find('select').val()); return false;");
$sidebar->addWidget($list, 'filter_institute');
}
@@ -1378,7 +1637,7 @@ class Admin_CoursesController extends AuthenticatedController
$semesters = array_reverse(Semester::getAll());
$sidebar = Sidebar::Get();
$list = new SelectWidget(_('Semester'), $this->url_for('admin/courses/set_selection'), 'sem_select');
- $list->addElement(new SelectElement('all', _('Alle')), 'sem_select-all');
+ $list->addElement(new SelectElement('', _('Alle')), 'sem_select-all');
foreach ($semesters as $semester) {
$list->addElement(new SelectElement(
$semester->id,
@@ -1386,6 +1645,7 @@ class Admin_CoursesController extends AuthenticatedController
$semester->id === $GLOBALS['user']->cfg->MY_COURSES_SELECTED_CYCLE
), 'sem_select-' . $semester->id);
}
+ $list->setOnSubmitHandler("STUDIP.AdminCourses.App.changeFilter({semester_id: $(this).find('select').val()}); return false;");
$sidebar->addWidget($list, 'filter_semester');
}
@@ -1393,12 +1653,18 @@ class Admin_CoursesController extends AuthenticatedController
/**
* Adds the studiengangteil selector to the sidebar
*/
- private function setStgteilSelector()
+ private function getStgteilSelector($institut_id = null)
{
- $stgteile = StudiengangTeil::getAllEnriched('fach_name','ASC', ['mvv_fach_inst.institut_id' => $GLOBALS['user']->cfg->MY_INSTITUTES_DEFAULT]);
- $sidebar = Sidebar::Get();
+ $institut_id = $institut_id ?: $GLOBALS['user']->cfg->MY_INSTITUTES_DEFAULT;
+ $stgteile = StudiengangTeil::getAllEnriched('fach_name', 'ASC', ['mvv_fach_inst.institut_id' => $institut_id]);
$list = new SelectWidget(_('Studiengangteil'), $this->url_for('admin/courses/set_selection'), 'stgteil_select');
- $list->addElement(new SelectElement('all', _('Alle')), 'stgteil_select-all');
+ if (!$institut_id || $institut_id === 'all') {
+ $list->addElement(new SelectElement('', _('Wählen Sie eine Einrichtung') ), 'stgteil_select-all');
+ } elseif (count($stgteile) === 0) {
+ $list->addElement(new SelectElement('', _('Keine Studiengangteile zu der gewählten Einrichtung') ), 'stgteil_select-all');
+ } else {
+ $list->addElement(new SelectElement('', _('Alle')), 'stgteil_select-all');
+ }
foreach ($stgteile as $stgteil) {
$list->addElement(new SelectElement(
$stgteil->id,
@@ -1406,8 +1672,8 @@ class Admin_CoursesController extends AuthenticatedController
$stgteil->id === $GLOBALS['user']->cfg->MY_COURSES_SELECTED_STGTEIL
), 'stgteil_select-' . $stgteil->id);
}
-
- $sidebar->addWidget($list, 'filter_stgteil');
+ $list->setOnSubmitHandler("STUDIP.AdminCourses.App.changeFilter({stgteil: $(this).find('select').val()}); return false;");
+ return $list;
}
@@ -1415,15 +1681,21 @@ class Admin_CoursesController extends AuthenticatedController
* Adds HTML-Selector to the sidebar
* @param null $selected_action
*/
- private function setActionsWidget($selected_action = null)
+ private function setActionsWidget()
{
$actions = $this->getActions();
$sidebar = Sidebar::Get();
$list = new SelectWidget(_('Aktionsbereichauswahl'), $this->url_for('admin/courses/set_action_type'), 'action_area');
foreach ($actions as $index => $action) {
- $list->addElement(new SelectElement($index, $action['name'], $selected_action == $index), 'action-aria-' . $index);
+ $list->addElement(new SelectElement(
+ $index,
+ $action['name'],
+ $GLOBALS['user']->cfg->MY_COURSES_ACTION_AREA == $index),
+ 'action-aria-' . $index
+ );
}
+ $list->setOnSubmitHandler("STUDIP.AdminCourses.App.changeActionArea($(this).find('select').val()); return false;");
$sidebar->addWidget($list, 'editmode');
}
@@ -1433,12 +1705,12 @@ class Admin_CoursesController extends AuthenticatedController
* @param string $selected
* @param array $params
*/
- private function setCourseTypeWidget($selected = 'all')
+ private function setCourseTypeWidget()
{
$sidebar = Sidebar::get();
$this->url = $this->url_for('admin/courses/set_course_type');
$this->types = [];
- $this->selected = $selected;
+ $this->selected = $GLOBALS['user']->cfg->MY_COURSES_TYPE_FILTER;
$list = new SelectWidget(
_('Veranstaltungstypfilter'),
@@ -1446,7 +1718,7 @@ class Admin_CoursesController extends AuthenticatedController
'course_type'
);
$list->addElement(new SelectElement(
- 'all', _('Alle'), $selected === 'all'
+ '', _('Alle'), !$GLOBALS['user']->cfg->MY_COURSES_TYPE_FILTER
), 'course-type-all');
foreach ($GLOBALS['SEM_CLASS'] as $class_id => $class) {
if ($class['studygroup_mode']) {
@@ -1456,7 +1728,7 @@ class Admin_CoursesController extends AuthenticatedController
$element = new SelectElement(
$class_id,
$class['name'],
- $selected === (string)$class_id
+ $GLOBALS['user']->cfg->MY_COURSES_TYPE_FILTER === (string)$class_id
);
$list->addElement(
$element->setAsHeader(),
@@ -1467,7 +1739,7 @@ class Admin_CoursesController extends AuthenticatedController
$element = new SelectElement(
$class_id . '_' . $id,
$result['name'],
- $selected === $class_id . '_' . $id
+ $GLOBALS['user']->cfg->MY_COURSES_TYPE_FILTER === $class_id . '_' . $id
);
$list->addElement(
$element->setIndentLevel(1),
@@ -1475,6 +1747,7 @@ class Admin_CoursesController extends AuthenticatedController
);
}
}
+ $list->setOnSubmitHandler("STUDIP.AdminCourses.App.changeFilter({course_type: $(this).find('select').val()}); return false;");
$sidebar->addWidget($list, 'filter-course-type');
}
@@ -1482,35 +1755,38 @@ class Admin_CoursesController extends AuthenticatedController
* Returns a widget to selected a specific teacher
* @param array $teachers
*/
- private function setTeacherWidget()
+ private function getTeacherWidget($institut_id = null)
{
- if (!$GLOBALS['user']->cfg->MY_INSTITUTES_DEFAULT || $GLOBALS['user']->cfg->MY_INSTITUTES_DEFAULT === "all") {
- return;
- }
+ $institut_id = $institut_id ?: $GLOBALS['user']->cfg->MY_INSTITUTES_DEFAULT;
$teachers = DBManager::get()->fetchAll("
- SELECT auth_user_md5.*, user_info.*
- FROM auth_user_md5
- LEFT JOIN user_info ON (auth_user_md5.user_id = user_info.user_id)
- INNER JOIN user_inst ON (user_inst.user_id = auth_user_md5.user_id)
- INNER JOIN Institute ON (Institute.Institut_id = user_inst.Institut_id)
- WHERE (Institute.Institut_id = :institut_id OR Institute.fakultaets_id = :institut_id)
- AND auth_user_md5.perms = 'dozent'
- ORDER BY auth_user_md5.Nachname ASC, auth_user_md5.Vorname ASC
- ", [
- 'institut_id' => $GLOBALS['user']->cfg->MY_INSTITUTES_DEFAULT
- ],
- function ($data) {
- $ret['user_id'] = $data['user_id'];
- unset($data['user_id']);
- $ret['fullname'] = User::build($data)->getFullName("full_rev");
- return $ret;
- }
+ SELECT auth_user_md5.*, user_info.*
+ FROM auth_user_md5
+ LEFT JOIN user_info ON (auth_user_md5.user_id = user_info.user_id)
+ INNER JOIN user_inst ON (user_inst.user_id = auth_user_md5.user_id)
+ INNER JOIN Institute ON (Institute.Institut_id = user_inst.Institut_id)
+ WHERE (Institute.Institut_id = :institut_id OR Institute.fakultaets_id = :institut_id)
+ AND auth_user_md5.perms = 'dozent'
+ ORDER BY auth_user_md5.Nachname ASC, auth_user_md5.Vorname ASC
+ ", [
+ 'institut_id' => $institut_id
+ ],
+ function ($data) {
+ $ret['user_id'] = $data['user_id'];
+ unset($data['user_id']);
+ $ret['fullname'] = User::build($data)->getFullName("full_rev");
+ return $ret;
+ }
);
- $sidebar = Sidebar::Get();
$list = new SelectWidget(_('Lehrendenfilter'), $this->url_for('admin/courses/index'), 'teacher_filter');
- $list->addElement(new SelectElement('all', _('alle'), Request::get('teacher_filter') == 'all'), 'teacher_filter-all');
+ if (!$institut_id || $institut_id === 'all') {
+ $list->addElement(new SelectElement('', _('Wählen Sie eine Einrichtung') ), 'teacher_filter-all');
+ } elseif (count($teachers) === 0) {
+ $list->addElement(new SelectElement('', _('Keine Lehrenden in der gewählten Einrichtung') ), 'teacher_filter-all');
+ } else {
+ $list->addElement(new SelectElement('', _('Alle')), 'teacher_filter-all');
+ }
foreach ($teachers as $teacher) {
$list->addElement(new SelectElement(
@@ -1519,8 +1795,8 @@ class Admin_CoursesController extends AuthenticatedController
$GLOBALS['user']->cfg->ADMIN_COURSES_TEACHERFILTER === $teacher['user_id']
), 'teacher_filter-' . $teacher['user_id']);
}
-
- $sidebar->addWidget($list, 'filter_teacher');
+ $list->setOnSubmitHandler("STUDIP.AdminCourses.App.changeFilter({teacher_filter: $(this).find('select').val()}); return false;");
+ return $list;
}
/**
@@ -1530,7 +1806,15 @@ class Admin_CoursesController extends AuthenticatedController
{
$sidebar = Sidebar::Get();
$search = new SearchWidget(URLHelper::getURL('dispatch.php/admin/courses'));
- $search->addNeedle(_('Freie Suche'), 'search', true, null, null, $GLOBALS['user']->cfg->ADMIN_COURSES_SEARCHTEXT);
+ $search->addNeedle(
+ _('Freie Suche'),
+ 'search',
+ true,
+ null,
+ '',
+ $GLOBALS['user']->cfg->ADMIN_COURSES_SEARCHTEXT
+ );
+ $search->setOnSubmitHandler("STUDIP.AdminCourses.App.changeFilter({search: $(this).find('input').val()}); return false;");
$sidebar->addWidget($search, 'filter_search');
}
@@ -1556,7 +1840,9 @@ class Admin_CoursesController extends AuthenticatedController
}
if (!$config) {
- $config = $this->setFilterConfig($available_filters);
+ $config = $this->setFilterConfig([
+ 'number', 'name', 'semester', 'institute', 'teachers'
+ ]);
}
return $config;
diff --git a/app/controllers/course/basicdata.php b/app/controllers/course/basicdata.php
index baa817a..3bf10f7 100644
--- a/app/controllers/course/basicdata.php
+++ b/app/controllers/course/basicdata.php
@@ -524,7 +524,14 @@ class Course_BasicdataController extends AuthenticatedController
}
$this->flash['msg'] = $this->msg;
$this->flash['open'] = Request::get("open");
- $this->redirect($this->url_for('course/basicdata/view/' . $sem->getId()));
+ if (Request::isDialog()) {
+ $this->response->add_header('X-Dialog-Close', 1);
+ $this->response->add_header('X-Dialog-Execute', 'STUDIP.AdminCourses.App.reloadCourse');
+ $this->render_text($course_id);
+ } else {
+ $this->redirect($this->url_for('course/basicdata/view/' . $sem->getId()));
+ }
+
}
public function add_member_action($course_id, $status = 'dozent')
diff --git a/app/controllers/course/lvgselector.php b/app/controllers/course/lvgselector.php
index 101bee0..60b090e 100644
--- a/app/controllers/course/lvgselector.php
+++ b/app/controllers/course/lvgselector.php
@@ -196,8 +196,13 @@ class Course_LvgselectorController extends AuthenticatedController
$url = $this->action_url('index');
}
-
- $this->redirect($url);
+ if (Request::isDialog()) {
+ $this->response->add_header('X-Dialog-Close', 1);
+ $this->response->add_header('X-Dialog-Execute', 'STUDIP.AdminCourses.App.reloadCourse');
+ $this->render_text($this->course_id);
+ } else {
+ $this->redirect($url);
+ }
}
/**
diff --git a/app/controllers/course/study_areas.php b/app/controllers/course/study_areas.php
index 3643309..2993f56 100644
--- a/app/controllers/course/study_areas.php
+++ b/app/controllers/course/study_areas.php
@@ -149,7 +149,13 @@ class Course_StudyAreasController extends AuthenticatedController
} else {
PageLayout::postError($msg);
}
- $this->redirect($url);
+ if (Request::isDialog()) {
+ $this->response->add_header('X-Dialog-Close', 1);
+ $this->response->add_header('X-Dialog-Execute', 'STUDIP.AdminCourses.App.reloadCourse');
+ $this->render_text($this->course->id);
+ } else {
+ $this->redirect($url);
+ }
}
public function unassign()
diff --git a/app/controllers/course/timesrooms.php b/app/controllers/course/timesrooms.php
index 7e3d459..7168bd7 100644
--- a/app/controllers/course/timesrooms.php
+++ b/app/controllers/course/timesrooms.php
@@ -245,6 +245,9 @@ class Course_TimesroomsController extends AuthenticatedController
$this->checked_dates = $_SESSION['_checked_dates'];
unset($_SESSION['_checked_dates']);
}
+ if (Request::isDialog()) {
+ $this->response->add_header('X-Dialog-Execute', '{"func": "STUDIP.AdminCourses.App.reloadCourse", "payload": "'.$this->course->getId().'"}');
+ }
}
/**
@@ -314,6 +317,9 @@ class Course_TimesroomsController extends AuthenticatedController
}
$this->relocate(str_replace('_', '/', Request::option('origin')));
}
+ if (Request::isDialog()) {
+ $this->response->add_header('X-Dialog-Execute', '{"func": "STUDIP.AdminCourses.App.reloadCourse", "payload": "'.$this->course->getId().'"}');
+ }
}
}
diff --git a/app/controllers/resources/room_request.php b/app/controllers/resources/room_request.php
index e2b4766..03c6516 100644
--- a/app/controllers/resources/room_request.php
+++ b/app/controllers/resources/room_request.php
@@ -1997,6 +1997,8 @@ class Resources_RoomRequestController extends AuthenticatedController
if ($save_only) {
// redirect to reload all infos and showing the most current ones
$this->redirect('resources/room_request/resolve/' . $request_id);
+ } elseif (Request::isDialog()) {
+ $this->response->add_header('X-Dialog-Execute', '{"func": "STUDIP.AdminCourses.App.reloadCourse", "payload": "'.Context::get()->id.'"}');
}
}
diff --git a/app/views/admin/courses/_course.php b/app/views/admin/courses/_course.php
deleted file mode 100644
index e12d31a..0000000
--- a/app/views/admin/courses/_course.php
+++ /dev/null
@@ -1,227 +0,0 @@
-<?php
-/**
- * Show course only if it has no parent course or the parent course is not
- * part of the current view. Otherwise the current course will be listed
- * as subcourse under its parent.
- *
- * @var array $values
- * @var array $courses
- * @var string $semid
- * @var string $parent
- * @var Admin_CoursesController $controller
- * @var array $view_filter
- * @var Semester $semester
- * @var string $selected_action
- */
-if (!$values['parent_course'] || !in_array($values['parent_course'], array_keys($courses))) : ?>
- <?php
- $course = Course::find($semid);
- $children = [];
- if ($GLOBALS['SEM_CLASS'][$GLOBALS['SEM_TYPE'][$values['status']]['class']]['is_group']) {
- $children = Course::findbyParent_Course($semid);
- }
- ?>
- <tr id="course-<?= $semid ?>"<?= $parent ? ' class="subcourses subcourse-' . $parent . '"' : '' ?> data-course-id="<?= $semid ?>">
- <td>
- <? if (Config::get()->ADMIN_COURSES_SHOW_COMPLETE): ?>
- <? if ($GLOBALS['perm']->have_studip_perm('tutor', $semid)) : ?>
- <a href="<?= $controller->toggle_complete($course) ?>"
- class="course-completion"
- data-course-completion="<?= $values['completion'] ?>"
- title="<?= htmlReady($course->getCompetionLabel()) ?>"
- aria-label="<?= _('Bearbeitungsstatus ändern') ?>">
- <?= _('Bearbeitungsstatus ändern') ?>
- </a>
- <? else : ?>
- <?= $course->getCompletionIcon()->asImg(['title' => _('Bearbeitungsstatus kann nicht von Ihnen geändert werden.')]) ?>
- <? endif ?>
- <? else: ?>
- <?= CourseAvatar::getAvatar($semid)->getImageTag(Avatar::SMALL, ['title' => trim($values['Name'])]) ?>
- <? endif; ?>
- </td>
- <? if (in_array('number', $view_filter)) : ?>
- <td>
- <? if ($GLOBALS['perm']->have_studip_perm('autor', $semid)) : ?>
- <a href="<?= URLHelper::getLink('seminar_main.php', ['auswahl' => $semid]) ?>">
- <? endif ?>
- <?= htmlReady($values["VeranstaltungsNummer"]) ?>
- <? if ($GLOBALS['perm']->have_studip_perm('autor', $semid)) : ?>
- </a>
- <? endif ?>
- </td>
- <? endif ?>
- <? if (in_array('name', $view_filter)) : ?>
- <td>
- <? if ($GLOBALS['perm']->have_studip_perm("autor", $semid)) : ?>
- <a href="<?= URLHelper::getLink('seminar_main.php', ['auswahl' => $semid]) ?>">
- <? endif ?>
- <?= htmlReady($course->name) ?>
- <? if ($GLOBALS['perm']->have_studip_perm("autor", $semid)) : ?>
- </a>
- <? endif ?>
- <a data-dialog="buttons=false" href="<?= $controller->url_for(sprintf('course/details/index/%s', $semid)) ?>">
- <? $params = tooltip2(_("Veranstaltungsdetails anzeigen")); ?>
- <? $params['style'] = 'cursor: pointer'; ?>
- <?= Icon::create('info-circle', 'inactive')->asImg($params) ?>
- </a>
- <? if ($values["visible"] == 0) : ?>
- <?= _("(versteckt)") ?>
- <? endif ?>
- <?php if (count($children) > 0) : ?>
- <br>
- <a href="" class="toggle-subcourses" data-get-subcourses-url="<?= $controller->url_for('admin/courses/get_subcourses', $semid) ?>">
- <?= Icon::create('add', 'clickable')->asImg(12) ?>
- <?= Icon::create('remove', 'clickable', ['class' => 'hidden-js'])->asImg(12) ?>
- <?= sprintf(
- ngettext('%u Unterveranstaltung', '%u Unterveranstaltungen',
- count($children)),
- count($children)) ?>
- </a>
- <?php endif ?>
- </td>
- <? endif ?>
- <? if (in_array('type', $view_filter)) : ?>
- <td>
- <?= htmlReady($GLOBALS['SEM_CLASS'][$GLOBALS['SEM_TYPE'][$values["status"]]["class"]]['name']) ?>:
- <strong><?= htmlReady($GLOBALS['SEM_TYPE'][$values["status"]]["name"]) ?></strong>
- </td>
- <? endif ?>
- <? if (in_array('room_time', $view_filter)) : ?>
- <td class="raumzeit">
- <?= Seminar::GetInstance($semid)->getDatesHTML([
- 'semester_id' => $semester ? $semester->id : null,
- 'show_room' => true,
- ]) ?: _('nicht angegeben') ?>
- </td>
- <? endif ?>
- <? if (in_array('semester', $view_filter)) : ?>
- <td>
- <?= htmlReady($course->semester_text) ?>
- </td>
- <? endif?>
- <? if (in_array('institute', $view_filter)) : ?>
- <td>
- <?= htmlReady($course->home_institut ? $course->home_institut['name'] : $course['institute']) ?>
- </td>
- <? endif?>
- <? if (in_array('requests', $view_filter)) : ?>
- <td style="text-align: center;">
- <a title="<?=_('Raumanfragen')?>" href="<?= URLHelper::getLink('dispatch.php/course/room_requests', ['cid' => $semid])?>">
- <?= $values['requests'] ?>
- </a>
- </td>
- <? endif ?>
- <? if (in_array('teachers', $view_filter)) : ?>
- <td>
- <?= $this->render_partial_collection('my_courses/_dozent', $values['dozenten']) ?>
-
- </td>
- <? endif ?>
- <? if (in_array('members', $view_filter)) : ?>
- <td style="text-align: center;">
- <a title="<?=_('Teilnehmende')?>" href="<?= URLHelper::getLink(count($children) > 0 ? 'dispatch.php/course/grouping/members' : 'dispatch.php/course/members', ['cid' => $semid]) ?>">
- <?= $values["teilnehmer"] ?>
- </a>
- </td>
- <? endif ?>
- <? if (in_array('waiting', $view_filter)) : ?>
- <td style="text-align: center;">
- <a title="<?=_('Teilnehmende auf der Warteliste')?>" href="<?= URLHelper::getLink('dispatch.php/course/members', ['cid' => $semid])?>">
- <?= $values["waiting"] ?>
- </a>
- </td>
- <? endif ?>
- <? if (in_array('preliminary', $view_filter)) : ?>
- <td style="text-align: center;">
- <a title="<?=_('Vorläufige Anmeldungen') ?>" href="<?= URLHelper::getLink('dispatch.php/course/members', ['cid' => $semid])?>">
- <?= $values['prelim'] ?>
- </a>
- </td>
- <? endif ?>
- <? if (in_array('contents', $view_filter)) : ?>
- <td style="text-align: left; white-space: nowrap;">
- <? if (!empty($values['navigation'])) : ?>
- <ul class="my-courses-navigation" style="flex-wrap: nowrap">
- <? foreach (MyRealmModel::array_rtrim($values['navigation']) as $key => $nav) : ?>
- <? if ($nav instanceof Navigation && $nav->isVisible(true)) : ?>
- <li class="my-courses-navigation-item <? if ($nav->getImage()->signalsAttention()) echo 'my-courses-navigation-important'; ?>">
- <a href="<?=
- URLHelper::getLink('seminar_main.php',
- ['auswahl' => $semid,
- 'redirect_to' => $nav->getURL()]) ?>" <?= $nav->hasBadgeNumber() ? 'class="badge" data-badge-number="' . intval($nav->getBadgeNumber()) . '"' : '' ?>>
- <?= $nav->getImage()->asImg(20, $nav->getLinkAttributes()) ?>
- </a>
- </li>
- <? elseif (is_string($key)) : ?>
- <li class="my-courses-navigation-item">
- <span class="empty-slot" style="width: 20px"></span>
- </li>
- <? endif ?>
- <? endforeach ?>
- </ul>
- <? endif ?>
- </td>
- <? endif ?>
- <? if (in_array('last_activity', $view_filter)) : ?>
- <td style="text-align: center;">
- <span title="<?=_('Datum der letzten Aktivität in dieser Veranstaltung')?>">
- <?= htmlReady(date('d.m.Y', $values['last_activity'])); ?>
- </span>
- </td>
- <? endif ?>
- <? foreach (PluginManager::getInstance()->getPlugins("AdminCourseContents") as $plugin) : ?>
- <? foreach ($plugin->adminAvailableContents() as $index => $label) : ?>
- <? if (in_array($plugin->getPluginId()."_".$index, $view_filter)) : ?>
- <td style="text-align: center;">
- <? $content = $plugin->adminAreaGetCourseContent($course, $index) ?>
- <?= is_a($content, "Flexi_Template") ? $content->render() : $content ?>
- </td>
- <? endif ?>
- <? endforeach ?>
- <? endforeach ?>
- <td class="actions">
- <? if (isset($actions[$selected_action]['partial']) && is_numeric($selected_action) && $GLOBALS['perm']->have_studip_perm('tutor', $semid)) : ?>
- <?= $this->render_partial("admin/courses/{$actions[$selected_action]['partial']}", [
- 'course' => $course,
- 'values' => $values,
- 'action' => $actions[$selected_action],
- ]) ?>
- <? elseif (!is_numeric($selected_action)) : ?>
- <? $plugin = PluginManager::getInstance()->getPlugin($selected_action) ?>
- <? $template = $plugin->getAdminCourseActionTemplate($semid, $values) ?>
- <? if ($template) : ?>
- <?= $template->render() ?>
- <? elseif ($GLOBALS['perm']->have_studip_perm('tutor', $semid)) : ?>
- <?=
- \Studip\LinkButton::create(
- $actions[$selected_action]['title'],
- URLHelper::getURL(sprintf($actions[$selected_action]['url'], $semid),
- ($actions[$selected_action]['params'] ? $actions[$selected_action]['params'] : [])),
- ($actions[$selected_action]['attributes'] ? $actions[$selected_action]['attributes'] : [])
- ) ?>
- <? endif ?>
- <? elseif ($GLOBALS['perm']->have_studip_perm('tutor', $semid)) : ?>
- <? $lockrules = [
- '2' => "sem_tree",
- '3' => "room_time",
- '11' => "seminar_copy",
- '14' => "admission_type",
- '16' => "seminar_archive",
- '17' => "admission_type",
- '18' => 'room_time'
- ] ?>
- <? if ($GLOBALS['perm']->have_studip_perm("admin", $semid) || !isset($lockrules[$selected_action]) || !LockRules::Check($semid, $lockrules[$selected_action])) : ?>
- <?=
- \Studip\LinkButton::create(
- $actions[$selected_action]['title'],
- URLHelper::getURL(
- sprintf($actions[$selected_action]['url'], $semid),
- $actions[$selected_action]['params'] ?? []
- ),
- $actions[$selected_action]['attributes'] ?? []
- ) ?>
- <? endif ?>
- <? endif ?>
- </td>
- </tr>
-<?php endif ?>
diff --git a/app/views/admin/courses/aux-select.php b/app/views/admin/courses/aux-select.php
index 950120e..0b87029 100644
--- a/app/views/admin/courses/aux-select.php
+++ b/app/views/admin/courses/aux-select.php
@@ -10,7 +10,7 @@
--<?= _('keine Zusatzangaben') ?>--
</option>
<? foreach ($aux_lock_rules as $rule) : ?>
- <option value="<?= htmlReady($rule->id) ?>" <? if ($values['aux_lock_rule'] === $rule->id) echo 'selected'; ?>>
+ <option value="<?= htmlReady($rule->id) ?>" <? if ($course->aux_lock_rule === $rule->id) echo 'selected'; ?>>
<?= htmlReady($rule->name) ?>
</option>
<? endforeach ?>
@@ -18,6 +18,6 @@
<br>
<label>
<input type="checkbox" value="1" name="lock_sem_forced[<?= htmlReady($course->id) ?>]"
- <?= $values['aux_lock_rule_forced'] ? 'checked' : '' ?>>
+ <?= $course->aux_lock_rule_forced ? 'checked' : '' ?>>
<?=_('Erzwungen')?>
</label>
diff --git a/app/views/admin/courses/aux_preselect.php b/app/views/admin/courses/aux_preselect.php
index 73a2036..d7fd801 100644
--- a/app/views/admin/courses/aux_preselect.php
+++ b/app/views/admin/courses/aux_preselect.php
@@ -21,4 +21,4 @@
<input type="checkbox" value="1" name="aux_all_forced">
<?=_('Erzwungen')?>
</label>
-<?= \Studip\Button::createAccept(_('Speichern'), 'all'); ?>
+<?= \Studip\Button::createAccept(_('Speichern'), 'all', ['formaction' => URLHelper::getURL('dispatch.php/admin/courses/set_aux_lockrule')]); ?>
diff --git a/app/views/admin/courses/get_subcourses.php b/app/views/admin/courses/get_subcourses.php
deleted file mode 100644
index b2857ae..0000000
--- a/app/views/admin/courses/get_subcourses.php
+++ /dev/null
@@ -1,12 +0,0 @@
-<?php
-/**
- * @var array $courses
- * @var array $view_filter
- * @var array $actions
- * @var string $selected_action
- * @var string $parent
- */
-?>
-<?php foreach ($courses as $semid => $values) : ?>
- <?= $this->render_partial('admin/courses/_course', compact('semid', 'values', 'view_filter', 'actions', 'selected_action', 'parent', 'courses')) ?>
-<?php endforeach;
diff --git a/app/views/admin/courses/index.php b/app/views/admin/courses/index.php
index 7145332..812790c 100644
--- a/app/views/admin/courses/index.php
+++ b/app/views/admin/courses/index.php
@@ -2,19 +2,44 @@
/**
* @var Admin_CoursesController $controller
* @var int $count_courses
+ * @var Semester $semester
+ * @var array $fields
+ * @var array $activated_fields
+ * @var string $sortby
+ * @var string $sortflag
+ * @var array $activeSidebarElements
+ * @var int $max_show_courses
*/
+
+$unsortable_fields = [
+ 'avatar',
+ 'room_time',
+ 'contents'
+];
?>
+
<? if (empty($insts)): ?>
<?= MessageBox::info(sprintf(_('Sie wurden noch keinen Einrichtungen zugeordnet. Bitte wenden Sie sich an einen der zuständigen %sAdministratoren%s.'), '<a href="' . URLHelper::getLink('dispatch.php/siteinfo/show') . '">', '</a>')) ?>
-<? elseif (!empty($courses)): ?>
- <?= $this->render_partial('admin/courses/courses.php', compact('courses')) ?>
-<? elseif ($count_courses): ?>
- <?= MessageBox::info(sprintf(
- _('Es wurden %u Veranstaltungen gefunden. Grenzen Sie das Suchergebnis mit den Filtermöglichkeiten weiter ein, oder %slassen Sie sich alle Veranstaltungen anzeigen%s.'),
- $count_courses,
- '<a href="' . $controller->url_for('admin/courses', ['display' => 'all']) . '">',
- '</a>'
- )) ?>
-<? else: ?>
- <?= MessageBox::info(_('Ihre Suche ergab keine Treffer')) ?>
+<? else :
+
+ $attributes = [
+ ':show-complete' => json_encode((bool) Config::get()->ADMIN_COURSES_SHOW_COMPLETE),
+ ':fields' => json_encode($fields),
+ ':unsortable-fields' => json_encode($unsortable_fields),
+ ':max-courses' => (int) $max_show_courses,
+ 'sort-by' => $sortby,
+ 'sort-flag' => $sortflag,
+ ];
+?>
+ <form method="post">
+ <?= CSRFProtection::tokenTag() ?>
+
+ <div class="admin-courses-vue-app course-admin"
+ is="AdminCourses"
+ v-cloak
+ ref="app"
+ <?= arrayToHtmlAttributes($attributes) ?>
+ ></div>
+ </form>
+
<? endif; ?>
diff --git a/app/views/admin/courses/lock.php b/app/views/admin/courses/lock.php
index 459b8f7..d364f89 100644
--- a/app/views/admin/courses/lock.php
+++ b/app/views/admin/courses/lock.php
@@ -5,13 +5,13 @@
* @var Course $course
*/
?>
-<? $current_lock_rule = $all_lock_rules->findOneBy('lock_id', $values['lock_rule']); ?>
+<? $current_lock_rule = $all_lock_rules->findOneBy('lock_id', $course->lock_rule); ?>
<? if (!$GLOBALS['perm']->have_perm('root') && ($current_lock_rule['permission'] == 'admin' || $current_lock_rule['permission'] == 'root')) : ?>
<?= htmlReady($current_lock_rule['name'])?>
<? else : ?>
<select name="lock_sem[<?= htmlReady($course->id) ?>]" style="max-width: 200px">
<? foreach ($all_lock_rules as $lock_rule): ?>
- <option value="<?= $lock_rule['lock_id'] ?>" <?= $lock_rule['lock_id'] == $values['lock_rule'] ? 'selected' : '' ?>>
+ <option value="<?= $lock_rule['lock_id'] ?>" <?= $lock_rule['lock_id'] === $course->lock_rule ? 'selected' : '' ?>>
<?= htmlReady($lock_rule['name']) ?>
</option>
<? endforeach; ?>
diff --git a/app/views/admin/courses/lock_preselect.php b/app/views/admin/courses/lock_preselect.php
index b9bfcef..433230e 100644
--- a/app/views/admin/courses/lock_preselect.php
+++ b/app/views/admin/courses/lock_preselect.php
@@ -1,6 +1,6 @@
<?php
/**
- * @var array $values
+ * @var Course $course
* @var SimpleCollection $all_lock_rules
*/
?>
@@ -8,11 +8,11 @@
<select name="lock_sem_all" style="max-width: 200px">
<? for ($i = 0; $i < count($all_lock_rules); $i++) : ?>
<option value="<?= $all_lock_rules[$i]["lock_id"] ?>"
- <?= ($all_lock_rules[$i]["lock_id"] == $values['lock_rule']) ? 'selected' : '' ?>>
+ <?= $all_lock_rules[$i]['lock_id'] === $course->lock_rule ? 'selected' : '' ?>>
<?= htmlReady($all_lock_rules[$i]["name"]) ?>
</option>
<? endfor ?>
</select>
</label>
-<?= \Studip\Button::createAccept(_('Zuweisen'), 'all'); ?>
+<?= \Studip\Button::createAccept(_('Zuweisen'), 'all', ['formaction' => URLHelper::getURL('dispatch.php/admin/courses/set_lockrule')]); ?>
diff --git a/app/views/admin/courses/sidebar.php b/app/views/admin/courses/sidebar.php
index 92263f1..6b243ca 100644
--- a/app/views/admin/courses/sidebar.php
+++ b/app/views/admin/courses/sidebar.php
@@ -17,16 +17,16 @@
</label>
<label>
- <input name="instituteActive" type="checkbox" value="1"
- <?= (!empty($userSelectedElements['institute'])) ? 'checked' : '' ?>
+ <input name="semesterActive" type="checkbox" value="1"
+ <?= !empty($userSelectedElements['semester']) ? 'checked' : '' ?>
>
- <?= _('Einrichtung'); ?>
+ <?= _('Semester'); ?>
</label>
<label>
- <input name="semesterActive" type="checkbox" value="1"
- <?= (!empty($userSelectedElements['semester'])) ? 'checked' : '' ?>
+ <input name="instituteActive" type="checkbox" value="1"
+ <?= !empty($userSelectedElements['institute']) ? 'checked' : '' ?>
>
- <?= _('Semester'); ?>
+ <?= _('Einrichtung'); ?>
</label>
<label>
<input name="stgteilActive" type="checkbox" value="1"
@@ -35,18 +35,18 @@
<?= _('Studiengangteil'); ?>
</label>
<label>
- <input name="courseTypeActive" type="checkbox" value="1"
- <?= (!empty($userSelectedElements['courseType'])) ? 'checked' : '' ?>
- >
- <?= _('Veranstaltungstypfilter'); ?>
- </label>
- <label>
<input name="teacherActive" type="checkbox" value="1"
<?= (!empty($userSelectedElements['teacher'])) ? 'checked' : '' ?>
- >
+ >
<?= _('Lehrperson'); ?>
</label>
<label>
+ <input name="courseTypeActive" type="checkbox" value="1"
+ <?= !empty($userSelectedElements['courseType']) ? 'checked' : '' ?>
+ >
+ <?= _('Veranstaltungstypfilter'); ?>
+ </label>
+ <label>
<input name="viewFilterActive" type="checkbox" value="1"
<?= (!empty($userSelectedElements['viewFilter'])) ? 'checked' : '' ?>
>
diff --git a/app/views/course/lvgselector/index.php b/app/views/course/lvgselector/index.php
index ca9b46f..925c1c4 100644
--- a/app/views/course/lvgselector/index.php
+++ b/app/views/course/lvgselector/index.php
@@ -1,5 +1,7 @@
<? if (!$locked) : ?>
- <form action="<?= $controller->link_for('course/lvgselector/index/' . $course_id, $url_params ?? []) ?>" method="post">
+ <form action="<?= $controller->link_for('course/lvgselector/index/' . $course_id, $url_params ?? []) ?>"
+ <?= Request::isDialog() ? 'data-dialog' : '' ?>
+ method="post">
<? endif ?>
<h1><?= _('Lehrveranstaltungsgruppen') ?></h1>
<div id="assigned" data-ajax-url="<?= $ajax_url ?>" data-forward-url="<?= $no_js_url ?>">
diff --git a/app/views/course/study_areas/show.php b/app/views/course/study_areas/show.php
index efec1b4..5bf7a5a 100644
--- a/app/views/course/study_areas/show.php
+++ b/app/views/course/study_areas/show.php
@@ -1,5 +1,7 @@
<? if (!$locked) : ?>
- <form action="<?= $controller->url_for('course/study_areas/save/' . $course->id, $url_params) ?>" method="post">
+ <form action="<?= $controller->link_for('course/study_areas/save/' . $course->id, $url_params) ?>"
+ <?= Request::isDialog() ? 'data-dialog' : '' ?>
+ method="post">
<? endif?>
<?= $tree ?>
<div style="text-align: center;">
diff --git a/db/migrations/5.4.9_add_datafields_filter_config.php b/db/migrations/5.4.9_add_datafields_filter_config.php
new file mode 100644
index 0000000..b44d108
--- /dev/null
+++ b/db/migrations/5.4.9_add_datafields_filter_config.php
@@ -0,0 +1,26 @@
+<?php
+
+class AddDatafieldsFilterConfig extends Migration
+{
+ protected function up()
+ {
+ $query = "INSERT IGNORE INTO `config` (`field`, `value`, `type`, `range`, `mkdate`, `chdate`, `description`)
+ VALUES (:name, :value, :type, :range, UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), :description)";
+
+ $statement = DBManager::get()->prepare($query);
+ $statement->execute([
+ ':name' => 'ADMIN_COURSES_DATAFIELDS_FILTERS',
+ ':description' => 'Für Admins, Roots und DedicatedAdmins können hier die Datenfelder gespeichert werden, nach denen die Veranstaltungen gefiltert werden sollen.',
+ ':range' => 'user',
+ ':type' => 'array',
+ ':value' => '[]'
+ ]);
+ }
+
+ protected function down()
+ {
+ DBManager::get()->prepare('
+ DELETE FROM `config` WHERE `field` = "ADMIN_COURSES_DATAFIELDS_FILTERS"
+ ');
+ }
+}
diff --git a/lib/classes/AdminCourseFilter.class.php b/lib/classes/AdminCourseFilter.class.php
index 7ec1c7b..68b96d2 100644
--- a/lib/classes/AdminCourseFilter.class.php
+++ b/lib/classes/AdminCourseFilter.class.php
@@ -18,39 +18,17 @@
* public function addLectureshipFilter($event, $filter)
* {
* if ($GLOBALS['user']->cfg->getValue("LECTURESHIP_FILTER")) {
- * $filter->settings['query']['joins']['lehrauftrag'] = array(
- * 'join' => "INNER JOIN",
- * 'on' => "seminare.Seminar_id = lehrauftrag.seminar_id"
- * );
+ * $filter->query->join('lehrauftrag', 'seminare.Seminar_id = lehrauftrag.seminar_id');
* }
* }
*
- * Within this method you alter the public $filter->settings array, because this array
- * describes entirely the big query for the admin-search. In our example above
- * we simple add an INNER JOIN to filter for the course having an entry in
- * the lehrauftrag table.
- *
- * Description of this array is as follows:
- *
- * $filter->settings['query'] : The main sql query as a prepared statement.
- * $filter->settings['query']['select'] : An assoc array. $filter->settings['query']['select']['Number_of_teachers'] = "COUNT(DISTINCT dozenten.user_id)"
- * will select the result of COUNT as the variable Number_of_teachers.
- * $filter->settings['query']['joins'] : Example $filter->settings['query']['joins']['dozenten'] = array(
- * 'join' => "INNER JOIN", //default value, else use "LEFT JOIN"
- * 'table' => "seminar_user", //can me omitted if you don't want to use a table-alias
- * 'on' => "dozenten.Seminar_id = seminare.Seminar_id AND dozenten.status = 'dozent'"
- * )
- * if 'table' differs from the index, the index will be the alias of the table.
- * So normally you don't need to name a table if you don't want it to be aliased.
- * $filter->settings['query']['where'] : You might want to use the method $filter->where($sql, $parameter) instead.
- * $filter->settings['query']['orderby'] : You might want to use $filter->orderBy($attribute, $flag = "ASC") instead.
- * $filter->settings['parameter'] : An assoc array of parameter that will be passed to
- * the prepared statement.
+ * Within this method you alter the public $filter->query object. That query object is of type SQLQuery.
*
*/
class AdminCourseFilter
{
static protected $instance = null;
+ public $query = null;
public $max_show_courses = 500;
public $settings = [];
@@ -68,288 +46,131 @@ class AdminCourseFilter
}
/**
- * Constructor of the singleton-object. The settings might come from the session
- * if $reset_settings is false.
- * @param bool $reset_settings : should the session settings of the singleton be reset?
- */
- public function __construct($reset_settings = false)
- {
- $this->initSettings();
-
- if ($reset_settings) {
- $this->resetSettings();
- } else {
- $this->restoreSettings();
- }
- }
-
- /**
- * store settings in session
- */
- public function storeSettings()
- {
- $_SESSION['AdminCourseFilter_settings'] = $this->settings;
- }
-
- /**
- * restore settings from session
- */
- public function restoreSettings()
- {
- if ($_SESSION['AdminCourseFilter_settings']) {
- $this->settings = $_SESSION['AdminCourseFilter_settings'];
- }
- }
-
- /**
- * reset settings
+ * Constructor of the singleton-object.
*/
- public function resetSettings()
+ public function __construct()
{
$this->initSettings();
- unset($_SESSION['AdminCourseFilter_settings']);
}
- /**
- * initialize settings
- */
- public function initSettings()
- {
- $this->settings = [];
-
- $this->settings['query']['select'] = [
- 'Institut' => "Institute.Name",
- 'teilnehmer' => "(SELECT COUNT(seminar_id)
- FROM seminar_user
- WHERE seminar_id = seminare.Seminar_id AND status != 'dozent' AND status != 'tutor')",
- 'prelim' => "(SELECT COUNT(seminar_id)
- FROM admission_seminar_user
- WHERE seminar_id = seminare.Seminar_id AND status = 'accepted')",
- 'waiting' => "(SELECT COUNT(seminar_id)
- FROM admission_seminar_user
- WHERE seminar_id = seminare.Seminar_id AND status = 'awaiting')",
- 'requests' => "(SELECT COUNT(id)
- FROM resource_requests
- WHERE course_id = seminare.Seminar_id)",
- 'course_set' => "(SELECT set_id FROM seminar_courseset WHERE seminar_id = seminare.Seminar_id LIMIT 1)"
- ];
- $this->settings['query']['joins'] = [
- 'seminar_inst' => [
- 'join' => "INNER JOIN",
- 'on' => "seminare.Seminar_id = seminar_inst.seminar_id"
- ],
- 'Institute' => [
- 'join' => "INNER JOIN",
- 'on' => "seminar_inst.institut_id = Institute.Institut_id"
- ],
- 'sem_types' => [
- 'join' => "LEFT JOIN",
- 'on' => "sem_types.id = seminare.status"
- ],
- 'sem_classes' => [
- 'join' => "LEFT JOIN",
- 'on' => "sem_classes.id = sem_types.class"
- ]
- ];
- $this->settings['query']['where'] = [];
- $this->settings['query']['orderby'] = Config::get()->IMPORTANT_SEMNUMBER ? "seminare.veranstaltungsnummer, seminare.name" : "seminare.name";
- }
-
- /**
- * Adds a filter for all courses of the given semester.
- * @param string $semester_id : ID of the given semester.
- * @return AdminCourseFilter
- * @throws Exception if semester_id does not exist
- */
- public function filterBySemester($semester_id)
- {
- $semester = Semester::find($semester_id);
- if (!$semester) {
- throw new Exception("Das ausgewählte Semester scheint nicht zu existieren.");
- }
- $this->settings['query']['joins']['semester_courses'] = [
- 'join' => "LEFT JOIN",
- 'on' => "semester_courses.course_id = seminare.Seminar_id"
- ];
- $this->settings['query']['where']['semester'] = "(semester_courses.semester_id IS NULL OR semester_courses.semester_id = :semester_id)";
- $this->settings['parameter']['semester_beginn'] = $semester['beginn'];
- $this->settings['parameter']['semester_id'] = $semester['id'];
- return $this;
- }
-
- /**
- * Adds a filter for a sem_type or many sem_types if the parameter is an array.
- * @param array|integer $type : id or ids of sem_types
- * @return AdminCourseFilter
- */
- public function filterByType($type)
- {
- if (is_array($type)) {
- $this->settings['query']['where']['status'] = "seminare.status IN (:types)";
- $this->settings['parameter']['types'] = $type;
+ protected function initSettings()
+ {
+ $this->query = SQLQuery::table('seminare');
+ $this->query->join('sem_types', 'sem_types', 'sem_types.id = seminare.status');
+ $this->query->join('sem_classes', 'sem_classes', 'sem_classes.id = sem_types.class');
+ $this->query->where("sem_classes.studygroup_mode = '0'");
+ $this->query->groupBy('seminare.Seminar_id');
+
+ if ($GLOBALS['user']->cfg->ADMIN_COURSES_SEARCHTEXT) {
+ $this->query->join('teachers_su', 'seminar_user', "teachers_su.Seminar_id = seminare.Seminar_id AND teachers_su.status = 'dozent'");
+ $this->query->join('teachers', 'auth_user_md5', 'teachers.user_id = teachers_su.user_id');
+ $this->query->where(
+ 'search',
+ "(seminare.name LIKE :search OR seminare.VeranstaltungsNummer LIKE :search OR seminare.untertitel LIKE :search OR CONCAT(teachers.Vorname, ' ', teachers.Nachname) LIKE :search)",
+ ['search' => '%'.$GLOBALS['user']->cfg->ADMIN_COURSES_SEARCHTEXT.'%']
+ );
+ }
+ if (Request::option('course_id')) {
+ $this->query->where('course_id', 'seminare.Seminar_id = :course_id', ['course_id' => Request::option('course_id')]);
+ }
+ $inst_ids = [];
+
+ if (
+ !$GLOBALS['user']->cfg->MY_INSTITUTES_DEFAULT
+ || $GLOBALS['user']->cfg->MY_INSTITUTES_DEFAULT === 'all'
+ ) {
+ $inst = new SimpleCollection(Institute::getMyInstitutes($GLOBALS['user']->id));
+ $inst_ids = $inst->map(function ($a) {
+ return $a['Institut_id'];
+ });
} else {
- $this->settings['query']['where']['status'] = "seminare.status = :type";
- $this->settings['parameter']['type'] = (int) $type;
+ //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.
+
+ $include_children = false;
+ $inst_id = $GLOBALS['user']->cfg->MY_INSTITUTES_DEFAULT;
+ if (str_contains($inst_id, '_')) {
+ $inst_id = substr($inst_id, 0, strpos($inst_id, '_'));
+ $include_children = true;
+ }
+ $inst_ids[] = $inst_id;
+
+ if ($include_children) {
+ $inst = Institute::find($inst_id);
+ if ($inst && $inst->isFaculty()) {
+ foreach ($inst->sub_institutes->pluck('Institut_id') as $institut_id) {
+ $inst_ids[] = $institut_id;
+ }
+ }
+ }
}
- return $this;
- }
- /**
- * Adds a filter for an institut_id or many institut_ids if the parameter is an array.
- * @param array|integer $institut_ids : id or ids of institutes
- * @return AdminCourseFilter
- */
- public function filterByInstitute($institut_ids)
- {
if (Config::get()->ALLOW_ADMIN_RELATED_INST) {
$sem_inst = 'seminar_inst';
+ $this->query->join('seminar_inst', 'seminar_inst', 'seminar_inst.seminar_id = seminare.Seminar_id');
} else {
$sem_inst = 'seminare';
}
- if (is_array($institut_ids)) {
- $this->settings['query']['where']['institute'] = "$sem_inst.institut_id IN (:institut_ids)";
- $this->settings['parameter']['institut_ids'] = $institut_ids;
- } else {
- $this->settings['query']['where']['status'] = "$sem_inst.institut_id = :institut_id";
- $this->settings['parameter']['institut_id'] = (string) $institut_ids;
+ $this->query->where('seminar_inst', "$sem_inst.institut_id IN (:institut_ids)");
+ $this->query->parameter('institut_ids', $inst_ids);
+
+ if ($GLOBALS['user']->cfg->MY_COURSES_SELECTED_CYCLE) {
+ $this->query->join('semester_courses', 'semester_courses.course_id = seminare.Seminar_id');
+ $this->query->where('semester_id', '(semester_courses.semester_id = :semester_id OR semester_courses.semester_id IS NULL)', [
+ 'semester_id' => $GLOBALS['user']->cfg->MY_COURSES_SELECTED_CYCLE
+ ]);
}
- return $this;
- }
- /**
- * Adds a filter for an stgteil_id or many stgteil_ids if the parameter is an array.
- * @param array|integer $stgteil_ids : id or ids of stgteile
- * @return AdminCourseFilter
- */
- public function filterByStgTeil($stgteil_ids)
- {
- $this->settings['query']['joins']['mvv_lvgruppe_seminar'] = [
- 'join' => "LEFT JOIN",
- 'table' => "mvv_lvgruppe_seminar",
- 'on' => "mvv_lvgruppe_seminar.seminar_id = seminare.Seminar_id"
- ];
- $this->settings['query']['joins']['mvv_lvgruppe_modulteil'] = [
- 'join' => "LEFT JOIN",
- 'table' => "mvv_lvgruppe_modulteil",
- 'on' => "mvv_lvgruppe_modulteil.lvgruppe_id = mvv_lvgruppe_seminar.lvgruppe_id"
- ];
- $this->settings['query']['joins']['mvv_modulteil'] = [
- 'join' => "LEFT JOIN",
- 'table' => "mvv_modulteil",
- 'on' => "mvv_modulteil.modulteil_id = mvv_lvgruppe_modulteil.modulteil_id"
- ];
- $this->settings['query']['joins']['mvv_stgteilabschnitt_modul'] = [
- 'join' => "LEFT JOIN",
- 'table' => "mvv_stgteilabschnitt_modul",
- 'on' => "mvv_stgteilabschnitt_modul.modul_id = mvv_modulteil.modul_id"
- ];
- $this->settings['query']['joins']['mvv_stgteilabschnitt'] = [
- 'join' => "LEFT JOIN",
- 'table' => "mvv_stgteilabschnitt",
- 'on' => "mvv_stgteilabschnitt.abschnitt_id = mvv_stgteilabschnitt_modul.abschnitt_id"
- ];
- $this->settings['query']['joins']['mvv_stgteilversion'] = [
- 'join' => "LEFT JOIN",
- 'table' => "mvv_stgteilversion",
- 'on' => "mvv_stgteilversion.version_id = mvv_stgteilabschnitt.version_id"
- ];
+ if ($GLOBALS['user']->cfg->MY_COURSES_TYPE_FILTER && $GLOBALS['user']->cfg->MY_COURSES_TYPE_FILTER !== 'all') {
+ if (str_contains(Request::option('course_type'), '_')) {
+ list($sem_class_id, $sem_type_id) = explode('_', $GLOBALS['user']->cfg->MY_COURSES_TYPE_FILTER);
+ $this->query->where('course_type', 'seminare.status = :course_type', ['course_type' => $sem_type_id]);
+ } else {
+ //sem class
+ $this->query->where('course_class', 'sem_types.class = :course_class', [
+ 'course_class' => $GLOBALS['user']->cfg->MY_COURSES_TYPE_FILTER
+ ]);
+ }
- if (is_array($stgteil_ids)) {
- $this->settings['query']['where']['mvv_stgteilversion'] = "mvv_stgteilversion.stgteil_id IN (:stgteil_ids)";
- $this->settings['parameter']['stgteil_ids'] = $stgteil_ids;
- } else {
- $this->settings['query']['where']['mvv_stgteilversion'] = "mvv_stgteilversion.stgteil_id = :stgteil_id";
- $this->settings['parameter']['stgteil_id'] = (string) $stgteil_ids;
}
- return $this;
- }
- /**
- * @param array|string $user_ids
- * @return AdminCourseFilter
- */
- public function filterByDozent($user_ids)
- {
- $this->settings['query']['joins']['dozenten'] = [
- 'join' => "INNER JOIN",
- 'table' => "seminar_user",
- 'on' => "dozenten.Seminar_id = seminare.Seminar_id AND dozenten.status = 'dozent'"
- ];
- if (is_array($user_ids)) {
- $this->settings['query']['where']['dozenten'] = "dozenten.user_id IN (:dozenten_ids)";
- $this->settings['parameter']['dozenten_ids'] = $user_ids;
- } else {
- $this->settings['query']['where']['dozenten'] = "dozenten.user_id = :dozenten_id";
- $this->settings['parameter']['dozenten_id'] = (string) $user_ids;
+ if ($GLOBALS['user']->cfg->MY_COURSES_SELECTED_STGTEIL) {
+ $this->query->join('mvv_lvgruppe_seminar', '`mvv_lvgruppe_seminar`.`seminar_id` = `seminare`.`Seminar_id`');
+ $this->query->join('mvv_lvgruppe_modulteil', '`mvv_lvgruppe_modulteil`.`lvgruppe_id` = `mvv_lvgruppe_seminar`.`lvgruppe_id`');
+ $this->query->join('mvv_modulteil', '`mvv_modulteil`.`modulteil_id` = `mvv_lvgruppe_modulteil`.`modulteil_id`');
+ $this->query->join('mvv_modul', '`mvv_modul`.`modul_id` = `mvv_modulteil`.`modul_id`');
+ $this->query->join('mvv_stgteilabschnitt_modul', '`mvv_stgteilabschnitt_modul`.`modul_id` = `mvv_modul`.`modul_id`');
+ $this->query->join('mvv_stgteilabschnitt', '`mvv_stgteilabschnitt`.`abschnitt_id` = `mvv_stgteilabschnitt_modul`.`abschnitt_id`');
+ $this->query->join('mvv_stgteilversion', '`mvv_stgteilversion`.`version_id` = `mvv_stgteilabschnitt`.`version_id`');
+ $this->query->where('stgteil', 'mvv_stgteilversion.stgteil_id = :stgteil_id', [
+ 'stgteil_id' => $GLOBALS['user']->cfg->MY_COURSES_SELECTED_STGTEIL
+ ]);
}
- return $this;
- }
- /**
- * Adds a filter for a textstring, that can be the coursenumber, the name of the course
- * or the last name of one of the dozenten.
- * @param string $text the searchstring
- * @return AdminCourseFilter
- */
- public function filterBySearchstring($text)
- {
- $this->settings['query']['joins']['dozenten'] = [
- 'join' => "INNER JOIN",
- 'table' => "seminar_user",
- 'on' => "dozenten.Seminar_id = seminare.Seminar_id AND dozenten.status = 'dozent'"
- ];
- $this->settings['query']['joins']['dozentendata'] = [
- 'join' => "INNER JOIN",
- 'table' => "auth_user_md5",
- 'on' => "dozenten.user_id = dozentendata.user_id"
- ];
- $this->settings['query']['where']['search'] = "(CONCAT_WS(' ', seminare.VeranstaltungsNummer, seminare.name, seminare.Untertitel, dozentendata.Nachname) LIKE :search
- OR CONCAT(dozentendata.Nachname, ', ', dozentendata.Vorname) LIKE :search
- OR CONCAT_WS(' ', dozentendata.Vorname, dozentendata.Nachname) LIKE :search
- OR dozentendata.Vorname LIKE :search
- OR dozentendata.Nachname LIKE :search
- )";
- $this->settings['parameter']['search'] = "%".$text."%";
+ if ($GLOBALS['user']->cfg->ADMIN_COURSES_TEACHERFILTER) {
+ $this->query->join('teachers_su', 'seminar_user', "teachers_su.Seminar_id = seminare.Seminar_id AND teachers_su.status = 'dozent'");
+ $this->query->where(
+ 'teacher_filter',
+ "teachers_su.user_id = :teacher_id",
+ ['teacher_id' => $GLOBALS['user']->cfg->ADMIN_COURSES_TEACHERFILTER]
+ );
+ }
- return $this;
- }
- /**
- * @param string $attribute : column, name of the column, yb whcih we should order the results
- * @param string $flag : "ASC" or "DESC for ascending order or descending order,
- * @return AdminCourseFilter
- * @throws Exception if $flag does not exist
- */
- public function orderBy($attribute, $flag = 'ASC')
- {
- $flag = mb_strtoupper($flag);
- if (!in_array($flag, words('ASC DESC'))) {
- throw new Exception("Sortierreihenfolge undefiniert.");
- }
- if (in_array($attribute, words('VeranstaltungsNummer Name status teilnehmer waiting prelim requests completion start_time Institute.Name'))) {
- $this->settings['query']['orderby'] = $attribute . ' ' . $flag;
- }
- return $this;
- }
- /**
- * Adds a where filter.
- * @param string $where any where condition like "sem_classes.overview = 'CoreOverview'"
- * @param array $parameter an array of parameter that appear in the $where query.
- * @param null|string $id an id of the where-query. Use this to possibly
- * avoid double where conditions or allow deleting the condition
- * by plugins if necessary. Can be omitted.
- * @return AdminCourseFilter
- */
- public function where($where, $parameter = [], $id = null)
- {
- if (!$id) {
- $id = md5($where);
+ $datafields_filters = $GLOBALS['user']->cfg->ADMIN_COURSES_DATAFIELDS_FILTERS;
+ foreach ($datafields_filters as $datafield_id => $value) {
+ $this->query->join('de_'.$datafield_id, 'datafields_entries', 'de_'.$datafield_id.'.range_id = seminare.Seminar_id AND `de_'.$datafield_id.'`.datafield_id = :de_'.$datafield_id.'_id');
+ $this->query->where('de_' . $datafield_id . '_contents', 'de_' . $datafield_id . '.`content` LIKE :de_' . $datafield_id . '_content',
+ [
+ 'de_' . $datafield_id . '_id' => $datafield_id,
+ 'de_' . $datafield_id . '_content' => '%' . $value . '%'
+ ]);
}
- $this->settings['query']['where'][$id] = $where;
- $this->settings['parameter'] = array_merge((array)($this->settings['parameter'] ?? []), $parameter);
- return $this;
}
/**
@@ -359,16 +180,10 @@ class AdminCourseFilter
* Plugins may register at this event to fully alter this AdminCourseFilter-object and so the resultset.
* @return array associative array with seminar_ids as keys and seminar-data-arrays as values.
*/
- public function getCourses($grouped = true)
+ public function getCourses()
{
NotificationCenter::postNotification("AdminCourseFilterWillQuery", $this);
- if (empty($this->settings['query']['where'])) {
- return [];
- }
- $statement = DBManager::get()->prepare($this->createQuery());
- $statement->execute($this->settings['parameter']);
- $_SESSION['AdminCourseFilter_settings'] = $this->settings;
- return $statement->fetchAll($grouped ? (PDO::FETCH_GROUP | PDO::FETCH_ASSOC) : PDO::FETCH_ASSOC);
+ return $this->query->fetchAll(Course::class);
}
/**
@@ -377,10 +192,7 @@ class AdminCourseFilter
public function countCourses()
{
NotificationCenter::postNotification("AdminCourseFilterWillQuery", $this);
- if (empty($this->settings['query']['where'])) {
- return 0;
- }
- return (int)DBManager::get()->fetchColumn($this->createQuery(true), $this->settings['parameter']);
+ return $this->query->count();
}
/**
@@ -400,54 +212,10 @@ class AdminCourseFilter
$order = 'seminare.veranstaltungsnummer, seminare.name';
}
if ($count_courses && $count_courses <= $this->max_show_courses) {
- $settings = $this->settings;
- $this->settings['query']['select'] = [];
- $this->settings['query']['orderby'] = $order;
- $ret = $this->getCourses(false);
- $this->settings = $settings;
- return $ret;
+ $this->query->orderBy($order);
+ return $this->getCourses();
}
return [];
}
- /**
- * Creates the sql-query from the $this->settings['query']
- * @param boolean $only_count : boolean
- * @return string the big query
- */
- public function createQuery($only_count = false)
- {
- if ($only_count) {
- $select_query = "COUNT(DISTINCT seminare.Seminar_id) ";
- } else {
- $select_query = "seminare.* ";
- foreach ((array) $this->settings['query']['select'] as $alias => $select) {
- $select_query .= ", ".$select." AS ".$alias." ";
- }
- }
-
- $join_query = "";
- foreach ((array) $this->settings['query']['joins'] as $alias => $joininfo) {
- $table = isset($joininfo['table']) ? $joininfo['table']." AS ".$alias : $alias;
- $on = isset($joininfo['on']) ? " ON (".$joininfo['on'].")" : "";
- $join_query .= " ".(isset($joininfo['join']) ? $joininfo['join'] : "INNER JOIN")." ".$table.$on." ";
- }
-
- $where_query = "";
- if (count($this->settings['query']['where']) > 0) {
- $where_query .= implode(" AND ", $this->settings['query']['where']);
- }
-
- $query = "
- SELECT ".$select_query."
- FROM seminare
- ".$join_query."
- ".($where_query ? "WHERE ".$where_query : "");
- if (!$only_count) {
- $query .= " GROUP BY seminare.Seminar_id ORDER BY ".$this->settings['query']['orderby'].($this->settings['query']['orderby'] !== "seminare.name" ? ", seminare.name" : "");
- }
-
- return $query;
- }
-
}
diff --git a/lib/classes/SQLQuery.php b/lib/classes/SQLQuery.php
index 3a09fd9..0442604 100644
--- a/lib/classes/SQLQuery.php
+++ b/lib/classes/SQLQuery.php
@@ -283,20 +283,20 @@ class SQLQuery
$on = isset($joindata['on']) ? " ON ({$joindata['on']})" : '';
$sql .= " " . (isset($joindata['join']) ? $joindata['join'] : 'INNER JOIN') . " {$table}{$on} ";
}
- if ($this->settings['where']) {
+ if (!empty($this->settings['where'])) {
$sql .= "WHERE (" . implode(") AND (", $this->settings['where']) . ") ";
}
- if ($this->settings['groupby']) {
+ if (!empty($this->settings['groupby'])) {
$sql .= "GROUP BY {$this->settings['groupby']} ";
}
- if ($this->settings['having']) {
+ if (!empty($this->settings['having'])) {
$sql .= "HAVING (" . implode(") AND (", $this->settings['having']) . ") ";
}
- if ($this->settings['order']) {
+ if (!empty($this->settings['order'])) {
$sql .= "ORDER BY {$this->settings['order']} ";
}
- if ($this->settings['limit']) {
+ if (!empty($this->settings['limit'])) {
$sql .= "LIMIT ". (int) $this->settings['limit'][0] . ", " . (int) $this->settings['limit'][1] . " ";
}
return $sql;
diff --git a/lib/classes/sidebar/OptionsWidget.php b/lib/classes/sidebar/OptionsWidget.php
index 0cb805c..a7931ce 100644
--- a/lib/classes/sidebar/OptionsWidget.php
+++ b/lib/classes/sidebar/OptionsWidget.php
@@ -34,8 +34,9 @@ class OptionsWidget extends ListWidget
$toggle_url_off = isset($toggle_url_off) ? html_entity_decode($toggle_url_off) : null;
$content = sprintf(
- '<a href="%s" class="options-checkbox options-%s" %s>%s</a>',
+ '<a href="%s" role="checkbox" aria-checked="%s" class="options-checkbox options-%s" %s>%s</a>',
htmlReady($state && $toggle_url_off !== null ? $toggle_url_off : $toggle_url),
+ $state ? 'true' : 'false',
$state ? 'checked' : 'unchecked',
arrayToHtmlAttributes($attributes),
htmlReady($label)
diff --git a/lib/classes/sidebar/SearchWidget.php b/lib/classes/sidebar/SearchWidget.php
index 82aa26b..16d577b 100644
--- a/lib/classes/sidebar/SearchWidget.php
+++ b/lib/classes/sidebar/SearchWidget.php
@@ -16,6 +16,7 @@ class SearchWidget extends SidebarWidget
protected $filters = [];
protected $method = 'get';
protected $id = null;
+ protected $onsubmit = null;
/**
* Constructor for the widget.
@@ -105,6 +106,11 @@ class SearchWidget extends SidebarWidget
return count($this->elements) + count($this->needles) + count($this->filters) > 0;
}
+ public function setOnSubmitHandler($onsubmit)
+ {
+ $this->onsubmit = $onsubmit;
+ }
+
/**
* Renders the widget.
*
@@ -167,6 +173,7 @@ class SearchWidget extends SidebarWidget
$this->template_variables['filters'] = $this->filters;
$this->template_variables['has_data'] = $this->hasData();
+ $this->template_variables['onsubmit'] = $this->onsubmit;
return parent::render($variables);
}
diff --git a/lib/classes/sidebar/SelectWidget.php b/lib/classes/sidebar/SelectWidget.php
index 7397175..772004c 100644
--- a/lib/classes/sidebar/SelectWidget.php
+++ b/lib/classes/sidebar/SelectWidget.php
@@ -8,6 +8,9 @@
*/
class SelectWidget extends SidebarWidget
{
+
+ protected $onsubmit = null;
+
/**
* Constructs the widget by defining a special template.
*
@@ -125,6 +128,11 @@ class SelectWidget extends SidebarWidget
}
}
+ public function setOnSubmitHandler($onsubmit)
+ {
+ $this->onsubmit = $onsubmit;
+ }
+
/**
* Renders the select widget
* @param array $variables Additional vaiarbles
@@ -141,6 +149,7 @@ class SelectWidget extends SidebarWidget
}
$this->template_variables['class'] .= ' submit-upon-select';
}
+ $this->template_variables['onsubmit'] = $this->onsubmit;
return parent::render($variables);
}
diff --git a/lib/models/ConfigValue.php b/lib/models/ConfigValue.php
index 829567d..f5410c9 100644
--- a/lib/models/ConfigValue.php
+++ b/lib/models/ConfigValue.php
@@ -43,7 +43,7 @@ class ConfigValue extends SimpleORMap
];
$config['registered_callbacks']['after_delete'][] = function (ConfigValue $value) {
- if ($value->entry->type === 'i18n') {
+ if (isset($value->entry) && $value->entry->type === 'i18n') {
$value->getTypedValue()->removeTranslations();
}
};
diff --git a/lib/models/Course.class.php b/lib/models/Course.class.php
index 86e9a47..e16df06 100644
--- a/lib/models/Course.class.php
+++ b/lib/models/Course.class.php
@@ -224,6 +224,9 @@ class Course extends SimpleORMap implements Range, PrivacyObject, StudipItem, Fe
$config['default_values']['schreibzugriff'] = 1;
$config['default_values']['duration_time'] = 0;
+ $config['additional_fields']['teachers'] = [
+ 'get' => 'getTeachers'
+ ];
$config['additional_fields']['end_time'] = true;
$config['additional_fields']['start_semester'] = [
@@ -456,6 +459,13 @@ class Course extends SimpleORMap implements Range, PrivacyObject, StudipItem, Fe
}
}
+ public function getTeachers()
+ {
+ return $this->members->filter(function ($m) {
+ return $m['status'] === 'dozent';
+ });
+ }
+
public function getFreeSeats()
{
$free_seats = $this->admission_turnout - $this->getNumParticipants();
diff --git a/resources/assets/javascripts/bootstrap/admin-courses.js b/resources/assets/javascripts/bootstrap/admin-courses.js
index 3fa0511..06c4621 100644
--- a/resources/assets/javascripts/bootstrap/admin-courses.js
+++ b/resources/assets/javascripts/bootstrap/admin-courses.js
@@ -1,12 +1,25 @@
-STUDIP.Dialog.registerHeaderHandler('X-Dialog-Notice', json => {
- json = JSON.parse(json);
+STUDIP.domReady(() => {
+ const node = document.querySelector('.admin-courses-vue-app');
+ if (!node) {
+ return;
+ }
- $(`#course-${json.id} td.actions .button`)
- .removeClass('has-notice has-no-notice')
- .addClass(json.notice.length > 0 ? 'has-notice' : 'has-no-notice')
- .attr('title', json.notice);
+ Promise.all([
+ STUDIP.Vue.load(),
+ import('../../../vue/store/AdminCoursesStore.js').then((config) => config.default),
+ import('../../../vue/components/AdminCourses.vue').then((component) => component.default),
+ ]).then(([{ createApp, store }, storeConfig, AdminCourses]) => {
+ store.registerModule('admincourses', storeConfig);
- STUDIP.Dialog.close();
+ Object.entries(window.AdminCoursesStoreData ?? {}).forEach(([key, value]) => {
+ store.commit(`admincourses/${key}`, value);
+ })
- return false;
+ const vm = createApp({
+ components: { AdminCourses },
+ });
+ vm.$mount(node);
+
+ STUDIP.AdminCourses.App = vm.$refs.app;
+ });
});
diff --git a/resources/assets/javascripts/bootstrap/application.js b/resources/assets/javascripts/bootstrap/application.js
index d673455..c6ffe35 100644
--- a/resources/assets/javascripts/bootstrap/application.js
+++ b/resources/assets/javascripts/bootstrap/application.js
@@ -331,24 +331,6 @@ STUDIP.domReady(function () {
}
});
-jQuery(document).on('click', '.course-admin td .course-completion', function () {
- var href = $(this).attr('href'),
- timeout = window.setTimeout(function () {
- $(this).addClass('ajaxing');
- }.bind(this), 300);
-
- $.getJSON(href).done(function (response) {
- clearTimeout(timeout);
-
- $(this).removeClass('ajaxing').attr({
- 'data-course-completion': response.state,
- title: response.label
- });
- }.bind(this));
-
- return false;
-});
-
// Global handler:
// Toggle a table element. The url of the link will be called, an ajax
// indicator will be shown instead of the element and the whole table row
diff --git a/resources/assets/javascripts/bootstrap/forms.js b/resources/assets/javascripts/bootstrap/forms.js
index 161b6d6..3d9be08 100644
--- a/resources/assets/javascripts/bootstrap/forms.js
+++ b/resources/assets/javascripts/bootstrap/forms.js
@@ -108,6 +108,7 @@ $(document)
// select was opened by click
if (!is_default && shouldSubmit) {
$(this.form).submit();
+ $('option', this).prop('defaultSelected', false).filter(':selected').prop('defaultSelected', true);
return false;
}
});
diff --git a/resources/assets/javascripts/init.js b/resources/assets/javascripts/init.js
index a7d6f05..5527602 100644
--- a/resources/assets/javascripts/init.js
+++ b/resources/assets/javascripts/init.js
@@ -4,6 +4,7 @@ import Vue from './lib/studip-vue.js';
import ActionMenu from './lib/actionmenu.js';
import ActivityFeed from './lib/activityfeed.js';
import admin_sem_class from './lib/admin_sem_class.js';
+import AdminCourses from './lib/admin-courses.js';
import Admission from './lib/admission.js';
import Arbeitsgruppen from './lib/arbeitsgruppen.js';
import Archive from './lib/archive.js';
@@ -66,6 +67,7 @@ import Resources from './lib/resources.js';
import Responsive from './lib/responsive.js';
import RESTAPI, { api } from './lib/restapi.js';
import Schedule from './lib/schedule.js';
+import Screenreader from './lib/screenreader.js';
import Scroll from './lib/scroll.js';
import Search from './lib/search.js';
import Sidebar from './lib/sidebar.js';
@@ -90,6 +92,7 @@ window.STUDIP = _.assign(window.STUDIP || {}, {
ActionMenu,
ActivityFeed,
admin_sem_class,
+ AdminCourses,
Admission,
api,
Arbeitsgruppen,
@@ -154,6 +157,7 @@ window.STUDIP = _.assign(window.STUDIP || {}, {
RESTAPI,
Schedule,
Scroll,
+ Screenreader,
Search,
Sidebar,
SkipLinks,
diff --git a/resources/assets/javascripts/lib/admin-courses.js b/resources/assets/javascripts/lib/admin-courses.js
new file mode 100644
index 0000000..e6a3523
--- /dev/null
+++ b/resources/assets/javascripts/lib/admin-courses.js
@@ -0,0 +1,20 @@
+const AdminCourses = {
+ App: null,
+ changeFiltersDependendOnInstitute(institut_id) {
+ STUDIP.AdminCourses.App.changeFilter({ institut_id });
+ //change Studiengangteil filter
+ $.get(
+ STUDIP.URLHelper.getURL('dispatch.php/admin/courses/get_stdgangteil_selector/' + institut_id)
+ ).done((widget) => {
+ $('select[name=stgteil_select]').closest('.sidebar-widget').replaceWith(widget);
+ });
+
+ //change Dozenten-Filter
+ $.get(
+ STUDIP.URLHelper.getURL('dispatch.php/admin/courses/get_teacher_selector/' + institut_id)
+ ).done((widget) => {
+ $('select[name=teacher_filter]').closest('.sidebar-widget').replaceWith(widget);
+ });
+ }
+};
+export default AdminCourses;
diff --git a/resources/assets/javascripts/lib/screenreader.js b/resources/assets/javascripts/lib/screenreader.js
new file mode 100644
index 0000000..b8b9d09
--- /dev/null
+++ b/resources/assets/javascripts/lib/screenreader.js
@@ -0,0 +1,8 @@
+class Screenreader
+{
+ static notify(text) {
+ $('#notes_for_screenreader').text(text);
+ }
+}
+
+export default Screenreader;
diff --git a/resources/assets/stylesheets/less/tables.less b/resources/assets/stylesheets/less/tables.less
index c7b1f95..eb576dd 100644
--- a/resources/assets/stylesheets/less/tables.less
+++ b/resources/assets/stylesheets/less/tables.less
@@ -438,6 +438,23 @@ table.default {
}
}
}
+ > tbody > tr.selected > td {
+ background-color: var(--yellow-20);
+ &:first-child {
+ position: relative;
+ &::before {
+ display: block;
+ content: '';
+ position: absolute;
+
+ top: 0;
+ bottom: 0;
+ left: 0;
+ width: 2px;
+ background-color: var(--light-gray-color);
+ }
+ }
+ }
> tbody > tr.new > td {
font-weight: bold;
&:first-child {
@@ -465,6 +482,9 @@ table.default {
&:not(.nohover) > tbody:not(.nohover) > tr:not(.nohover):hover > td:not(.nohover) {
background-color: fadeout(@light-gray-color, 80%);
}
+ &:not(.nohover) > tbody:not(.nohover) > tr.selected:not(.nohover):hover > td:not(.nohover) {
+ background-color: var(--yellow-40);
+ }
> tfoot {
> tr > td {
background-color: @content-color-20;
diff --git a/resources/assets/stylesheets/scss/admin.scss b/resources/assets/stylesheets/scss/admin.scss
index a2e5843..d8ac4d3 100644
--- a/resources/assets/stylesheets/scss/admin.scss
+++ b/resources/assets/stylesheets/scss/admin.scss
@@ -151,4 +151,7 @@ fieldset.attribute_table {
background-image: url("#{$image-path}/loading-indicator.svg");
}
}
+ > tbody.loading > tr > td {
+ opacity: 0.5;
+ }
}
diff --git a/resources/vue/components/AdminCourses.vue b/resources/vue/components/AdminCourses.vue
new file mode 100644
index 0000000..8e8c5c8
--- /dev/null
+++ b/resources/vue/components/AdminCourses.vue
@@ -0,0 +1,265 @@
+<template>
+ <table class="default">
+ <caption>
+ {{ $gettext('Veranstaltungen') }}
+ <span class="actions" v-if="isLoading">
+ <img :src="loadingIndicator" width="20" height="20" :title="$gettext('Daten werden geladen')">
+ </span>
+ <span class="actions" v-else-if="coursesCount > 0">
+ {{ coursesCount + ' ' + $gettext('Veranstaltungen') }}
+ </span>
+ </caption>
+ <thead>
+ <tr class="sortable">
+ <th v-if="showComplete">
+ <a
+ @click.prevent="changeSort('completion')"
+ class="course-completion"
+ :title="$gettext('Bearbeitungsstatus')"
+ >
+ {{ $gettext('Bearbeitungsstatus') }}
+ </a>
+ <studip-icon :shape="sort.direction === 'ASC' ? 'arr_1down' : 'arr_1up'"
+ v-if="sort.by === 'completion'"
+ class="text-bottom"></studip-icon>
+ </th>
+ <th v-for="activeField in sortedActivatedFields" :key="`field-${activeField}`">
+ <a href="#"
+ @click.prevent="changeSort(activeField)"
+ :title="sort.by === activeField && sort.direction === 'ASC' ? $gettextInterpolate('Sortiert aufsteigend nach %{field}', {field: fields[activeField]}) : (sort.by === activeField && sort.direction === 'DESC' ? $gettextInterpolate('Sortiert absteigend nach %{ field } ', { field: fields[activeField]}) : $gettextInterpolate('Sortieren nach %{ field }', { field: fields[activeField]}))"
+ v-if="!unsortableFields.includes(activeField)"
+ >
+ {{ fields[activeField] }}
+ <studip-icon :shape="sort.direction === 'ASC' ? 'arr_1down' : 'arr_1up'"
+ v-if="sort.by === activeField"
+ class="text-bottom"></studip-icon>
+ </a>
+ <template v-else>
+ {{ fields[activeField] }}
+ </template>
+ </th>
+ <th class="actions" style="text-align: center;">
+ {{ $gettext('Aktion') }}
+ </th>
+ </tr>
+ <tr v-if="buttons.top">
+ <th v-html="buttons.top" style="text-align: right" :colspan="colspan"></th>
+ </tr>
+ </thead>
+ <tbody :class="{ loading: isLoading }">
+ <tr v-for="course in sortedCourses"
+ :key="course.id"
+ :class="course.id === currentLine ? 'selected' : ''"
+ @click="currentLine = course.id">
+ <td v-if="showComplete">
+ <button :href="getURL('dispatch.php/admin/courses/toggle_complete/' + course.id)"
+ class="course-completion undecorated"
+ :data-course-completion="course.completion"
+ :title="(course.completion > 0 ? (course.completion == 1 ? $gettext('Veranstaltung in Bearbeitung.') : $gettext('Veranstaltung komplett.')) : $gettext('Veranstaltung neu.')) + ' ' + $gettext('Klicken zum Ändern des Status.')"
+ @click.prevent="toggleCompletionState(course.id)">
+ {{ $gettext('Bearbeitungsstatus ändern') }}
+ </button>
+ </td>
+ <td v-for="active_field in sortedActivatedFields" :key="active_field">
+ <div v-html="course[active_field]"></div>
+ <a v-if="active_field === 'name' && getChildren(course).length > 0"
+ @click.prevent="toggleOpenChildren(course.id)"
+ href="">
+ <studip-icon :shape="open_children.indexOf(course.id) === -1 ? 'add' : 'remove'" class="text-bottom"></studip-icon>
+ {{ $gettextInterpolate(
+ $gettext('%{ n } Unterveranstaltungen'),
+ { n: getChildren(course).length }
+ ) }}
+ </a>
+ </td>
+ <td class="actions" v-html="course.action">
+ </td>
+ </tr>
+ <tr v-if="sortedCourses.length === 0 && coursesCount <= 500 && displayLimitedLines && coursesLoaded">
+ <td :colspan="colspan">
+ {{ $gettext('Keine Ergebnisse') }}
+ </td>
+ </tr>
+ <tr v-if="coursesCount > maxCourses && displayLimitedLines">
+ <td :colspan="colspan">
+ {{
+ $gettextInterpolate(
+ $gettext(`%{ n } Veranstaltungen entsprechen Ihrem Filter. Schränken Sie nach Möglichkeit die Filter weiter ein.`),
+ { n: coursesCount }
+ )
+ }}
+ <a href="" @click.prevent="loadCourses({withoutLimit: true}); displayLimitedLines = false;">
+ {{ $gettext('Alle anzeigen') }}
+ </a>
+ </td>
+ </tr>
+ <tr v-if="!coursesLoaded">
+ <td :colspan="colspan">
+ {{ $gettext('Daten werden geladen ...') }}
+ </td>
+ </tr>
+ </tbody>
+ <tfoot v-if="buttons.bottom">
+ <tr>
+ <td v-html="buttons.bottom" style="text-align: right" :colspan="colspan"></td>
+ </tr>
+ </tfoot>
+ </table>
+
+</template>
+<script>
+import { mapActions, mapGetters, mapState } from 'vuex';
+
+export default {
+ name: 'AdminCourses',
+ props: {
+ maxCourses: Number,
+ showComplete: {
+ type: Boolean,
+ default: false,
+ },
+ fields: Object,
+ unsortableFields: Array,
+ sortBy: String,
+ sortFlag: String,
+ },
+ data() {
+ return {
+ sort: {
+ by: this.sortBy,
+ direction: this.sortFlag,
+ },
+ currentLine: null,
+ displayLimitedLines: true,
+ open_children: [],
+ };
+ },
+ created() {
+ this.loadCourses();
+ },
+ computed: {
+ ...mapState('admincourses', [
+ 'activatedFields',
+ 'buttons',
+ 'courses',
+ 'coursesCount',
+ 'coursesLoaded',
+ 'filters',
+ ]),
+ ...mapGetters('admincourses', [
+ 'isLoading',
+ ]),
+ colspan () {
+ let colspan = this.activatedFields.length + 1;
+ if (this.showComplete) {
+ colspan += 1;
+ }
+ return colspan;
+ },
+ sortedCourses() {
+ let maincourses = this.courses.filter(c => !c.parent_course);
+ maincourses = this.sortArray(maincourses);
+
+ let sorted_courses = [];
+ let children = [];
+ for (let i in maincourses) {
+ sorted_courses.push(maincourses[i]);
+ if (this.open_children.indexOf(maincourses[i].id) !== -1) {
+ children = this.getChildren(maincourses[i]);
+ children = this.sortArray(children);
+ for (let k in children) {
+ sorted_courses.push(children[k]);
+ }
+ }
+ }
+ return sorted_courses;
+ },
+ sortedActivatedFields() {
+ return Object.keys(this.fields).filter(f => this.activatedFields.includes(f));
+ },
+ loadingIndicator() {
+ return STUDIP.ASSETS_URL + 'images/loading-indicator.svg';
+ }
+ },
+ methods: {
+ ...mapActions('admincourses', [
+ 'changeActionArea',
+ 'changeFilter',
+ 'loadCourses',
+ 'loadCourse',
+ 'toggleActiveField',
+ 'toggleCompletionState',
+ ]),
+ getChildren(course) {
+ return this.courses.filter(c => c.parent_course === course.id);
+ },
+ toggleOpenChildren(course_id) {
+ if (!this.open_children.includes(course_id)) {
+ this.open_children.push(course_id);
+ } else {
+ this.open_children = this.open_children.filter(cid => cid !== course_id);
+ }
+ },
+ changeSort(column) {
+ if (this.sort.by === column) {
+ this.sort.direction = this.sort.direction === 'ASC' ? 'DESC' : 'ASC';
+ } else {
+ this.currentLine = null;
+ this.sort.direction = 'ASC';
+ }
+ this.sort.by = column;
+
+ $.post(STUDIP.URLHelper.getURL('dispatch.php/admin/courses/sort'), {
+ sortby: column,
+ sortflag: this.sort.direction,
+ });
+ },
+ sortArray (array) {
+ if (!array.length) {
+ return [];
+ }
+ let sortby = this.sort.by;
+ if (!this.activatedFields.includes(sortby) && sortby !== 'completion') {
+ return array;
+ }
+
+ const striptags = function (text) {
+ if (typeof text === "string") {
+ return text.replace(/(<([^>]+)>)/gi, "");
+ } else {
+ return text;
+ }
+ };
+
+ // Define sort direction by this factor
+ const directionFactor = this.sort.direction === 'ASC' ? 1 : -1;
+
+ // Default sort function by string comparison of field
+ const collator = new Intl.Collator(String.locale, {numeric: true, sensitivity: 'base'});
+ let sortFunction = function (a, b) {
+ return collator.compare(striptags(a[sortby]), striptags(b[sortby]));
+ };
+
+ let is_numeric = true;
+ for (let i in array) {
+ if (striptags(array[i][sortby]) && isNaN(striptags(array[i][sortby]))) {
+ is_numeric = false;
+ break;
+ }
+ }
+ if (is_numeric) {
+ sortFunction = function (a, b) {
+ return (striptags(a[sortby]) ? parseInt(striptags(a[sortby]), 10) : 0)
+ - (striptags(b[sortby]) ? parseInt(striptags(b[sortby]), 10) : 0);
+ };
+ }
+
+ // Actual sort on copy of array
+ return array.concat().sort((a, b) => directionFactor * sortFunction(a, b));
+ },
+ getURL(url, params = {}) {
+ return STUDIP.URLHelper.getURL(url, params);
+ },
+ }
+};
+</script>
diff --git a/resources/vue/store/AdminCoursesStore.js b/resources/vue/store/AdminCoursesStore.js
new file mode 100644
index 0000000..e3df44e
--- /dev/null
+++ b/resources/vue/store/AdminCoursesStore.js
@@ -0,0 +1,170 @@
+import Screenreader from '../../assets/javascripts/lib/screenreader.js';
+import { $gettext } from '../../assets/javascripts/lib/gettext.js';
+
+export default {
+ namespaced: true,
+
+ state: () => ({
+ actionArea: 1,
+ activatedFields: [],
+ buttons: {
+ top: '',
+ bottom: '',
+ },
+ courses: [],
+ coursesCount: null,
+ coursesLoaded: false,
+ filters: {},
+ loadingXhr: false,
+ }),
+
+ getters: {
+ getCourseById: (state) => (courseId) => {
+ return state.courses.find((course) => course.id === courseId);
+ },
+ isLoading(state) {
+ return state.loadingXhr !== null;
+ }
+ },
+
+ mutations: {
+ setActionArea(state, area) {
+ state.actionArea = area;
+ },
+ setActivatedFields(state, fields) {
+ state.activatedFields = fields;
+ },
+ setButtons(state, { top, bottom }) {
+ state.buttons.top = top ?? state.buttons.top;
+ state.buttons.bottom = bottom ?? state.buttons.bottom;
+ },
+ setCourse(state, payload) {
+ state.courses = state.courses.filter(c => c.id !== payload.courseId);
+ if (payload.data) {
+ state.courses.push(payload.data);
+ }
+ },
+ setCourses(state, courses, count = null) {
+ state.courses = courses;
+ state.coursesCount = count ?? courses.length;
+ },
+ setCoursesLoaded(state, loaded = true) {
+ state.coursesLoaded = loaded;
+ },
+ setFilter(state, filters) {
+ state.filters = {
+ ...state.filters,
+ ...filters,
+ };
+ },
+ },
+
+ actions: {
+ loadCourse({ commit, state }, courseId) {
+ $.getJSON(STUDIP.URLHelper.getURL('dispatch.php/admin/courses/search'), {
+ ...state.filters,
+ course_id: courseId,
+ action: state.actionArea,
+ }).done((response) => {
+ commit('setCourse', {
+ courseId,
+ data: response.data[0] ?? null
+ });
+ });
+
+ },
+ loadCourses({ commit, state }, {withoutLimit = false, withoutScreenreaderNotice = false} = {}) {
+ if (state.loadingXhr) {
+ state.loadingXhr.abort();
+ }
+
+ Screenreader.notify('');
+
+ let params = {
+ ...state.filters,
+ action: state.actionArea,
+ activated_fields: state.activatedFields,
+ without_limit: withoutLimit ? 1 : null,
+ };
+
+ // Remove empty items from params
+ params = Object.keys(params)
+ .filter((k) => params[k])
+ .reduce((a, k) => ({ ...a, [k]: params[k] }), {});
+
+ let timeout = null;
+ if (!withoutScreenreaderNotice && !state.coursesLoaded) {
+ timeout = setTimeout(() => {
+ STUDIP.Screenreader.notify($gettext('Suche läuft.'));
+ }, 800);
+ }
+
+ const xhr = $.ajax({
+ type: 'GET',
+ url: STUDIP.URLHelper.getURL('dispatch.php/admin/courses/search'),
+ dataType: 'json',
+ data: params,
+ });
+ xhr.done((response) => {
+ commit('setCoursesLoaded');
+
+ if (response.data === undefined) {
+ commit('setCourses', [], response.count);
+ } else {
+ commit('setCourses', response.data);
+ }
+
+ commit('setButtons', {
+ top: response.buttons_top ?? null,
+ bottom: response.buttons_bottom ?? null,
+ });
+ }).always(() => {
+ clearTimeout(timeout);
+ state.loadingXhr = null;
+ });
+
+ state.loadingXhr = xhr;
+ },
+ changeActionArea({ commit, state, dispatch }, area) {
+ if (state.actionArea !== area) {
+ commit('setActionArea', area);
+ dispatch('loadCourses');
+ }
+ },
+ changeFilter({ commit, state, dispatch }, filters) {
+ const changed = Object.entries(filters).some(([key, value]) => {
+ return state.filters[key] === undefined || state.filters[key] !== value;
+ });
+ if (changed) {
+ commit('setFilter', filters);
+ dispatch('loadCourses');
+ }
+ },
+ toggleActiveField({ commit, state, dispatch }, field) {
+ let fields = state.activatedFields;
+ if (fields.includes(field)) {
+ fields = fields.filter(f => f !== field);
+ } else {
+ fields.push(field);
+ }
+
+ commit('setActivatedFields', fields);
+ dispatch('loadCourses');
+ },
+ toggleCompletionState({ commit, getters }, courseId) {
+ $.get(
+ STUDIP.URLHelper.getURL('dispatch.php/admin/courses/toggle_complete/' + courseId)
+ ).done((response) => {
+ const course = getters.getCourseById(courseId);
+ commit('setCourse', {
+ courseId,
+ data: {
+ ...course,
+ completion: response.state,
+ }
+ });
+ });
+
+ }
+ }
+}
diff --git a/templates/layouts/base.php b/templates/layouts/base.php
index 2869270..67bb2b9 100644
--- a/templates/layouts/base.php
+++ b/templates/layouts/base.php
@@ -102,6 +102,7 @@ $lang_attr = str_replace('_', '-', $_SESSION['_language']);
<?= $this->render_partial('footer', ['link_params' => $header_template->link_params]); ?>
<?= SkipLinks::getHTML() ?>
+ <section class="sr-only" id="notes_for_screenreader" aria-live="polite"></section>
</body>
</html>
<?php NotificationCenter::postNotification('PageDidRender', PageLayout::getBodyElementId());
diff --git a/templates/sidebar/search-widget.php b/templates/sidebar/search-widget.php
index 13d6bed..6d870ba 100644
--- a/templates/sidebar/search-widget.php
+++ b/templates/sidebar/search-widget.php
@@ -1,4 +1,8 @@
-<form action="<?= URLHelper::getLink($url) ?>" method="<?= $method ?>" <? if (isset($id)) printf('id="%s"', htmlReady($id)); ?> class="sidebar-search">
+<form action="<?= URLHelper::getLink($url) ?>"
+ method="<?= $method ?>"
+ <? if (isset($id)) printf('id="%s"', htmlReady($id)); ?>
+ <?= $onsubmit ? 'onsubmit="'.htmlReady($onsubmit).'"' : '' ?>
+ class="sidebar-search">
<? foreach ($url_params as $key => $value): ?>
<?=addHiddenFields($key,$value)?>
<? endforeach; ?>
diff --git a/templates/sidebar/select-widget.php b/templates/sidebar/select-widget.php
index 7c7b039..b7aa596 100644
--- a/templates/sidebar/select-widget.php
+++ b/templates/sidebar/select-widget.php
@@ -1,9 +1,15 @@
-<form action="<?= URLHelper::getLink($url) ?>" method="<?= $method ?>">
+<form action="<?= URLHelper::getLink($url) ?>"
+ <?= $onsubmit ? 'onsubmit="'.htmlReady($onsubmit).'"' : '' ?>
+ method="<?= $method ?>">
<?= \SelectWidget::arrayToHiddenInput($params) ?>
<?= (strtolower($method) == 'post') ? CSRFProtection::tokenTag() : ''; ?>
- <select class="sidebar-selectlist <?= $class ?> <? if ($__is_nested): ?>nested-select<? endif; ?>" <? !empty($size) ? printf('size="%u"', $size) : '' ?> <?= !empty($attributes) ? arrayToHtmlAttributes($attributes) : '' ?>
- name="<?= sprintf('%s%s', htmlReady($name), $multiple ? '[]' : '') ?>" <? if ($multiple) echo 'multiple'; ?>
- aria-label="<?= htmlReady($title) ?>" <?= $dropdownAutoWidth ? 'data-dropdown-auto-width="1"' : '' ?>>
+ <select class="sidebar-selectlist <?= $class ?> <? if ($__is_nested): ?>nested-select<? endif; ?>"
+ <? !empty($size) ? printf('size="%u"', $size) : '' ?>
+ <?= !empty($attributes) ? arrayToHtmlAttributes($attributes) : '' ?>
+ name="<?= sprintf('%s%s', htmlReady($name), $multiple ? '[]' : '') ?>"
+ <? if ($multiple) echo 'multiple'; ?>
+ aria-label="<?= htmlReady($title) ?>"
+ <?= $dropdownAutoWidth ? 'data-dropdown-auto-width="1"' : '' ?>>
<? foreach ($elements as $element): ?>
<? if ($element instanceof SelectGroupElement && count($element->getElements()) > 0): ?>
diff --git a/templates/sidebar/selector-widget.php b/templates/sidebar/selector-widget.php
index 62f384c..f96e6b3 100644
--- a/templates/sidebar/selector-widget.php
+++ b/templates/sidebar/selector-widget.php
@@ -1,4 +1,6 @@
-<form action="<?= URLHelper::getLink($url, [], true) ?>" method="<?= $method?>" class="selector-widget">
+<form action="<?= URLHelper::getLink($url, [], true) ?>"
+ method="<?= $method?>"
+ class="selector-widget">
<?= ($method == 'post' ? CSRFProtection::tokenTag() : '') ?>
<select name="<?=htmlReady($name)?>" class="sidebar-selectlist submit-upon-select text-top" size="<?= (int) $size ?: 8 ?>" aria-label="<?= _("Wählen Sie ein Objekt aus. Sie gelangen dann zur neuen Seite.") ?>">
<? foreach ($elements as $element): ?>