* @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 or later
* @category Stud.IP
* @since 3.1
*/
require_once 'lib/object.inc.php';
require_once 'lib/archiv.inc.php'; //for lastActivity in getCourses() method
class Admin_CoursesController extends AuthenticatedController
{
/**
* This method returns the appropriate widget for the given datafield.
*
* @param DataField datafield The datafield whose widget is requested.
*
* @return SidebarWidget|null Returns a SidebarWidget derivative or null in case of an error.
*/
private function getDatafieldWidget(DataField $datafield)
{
if ($datafield->accessAllowed()) {
//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') {
//bool fields just need a checkbox for the states TRUE and FALSE
$checkboxWidget = new OptionsWidget($datafield->name);
$checkboxWidget->addCheckbox(
_('Feld gesetzt'),
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') {
$options = array_map('trim', explode("\n", DBManager::get()->fetchColumn(
'SELECT typeparam FROM datafields WHERE datafield_id = ?',
[$datafield->id]
)));
if ($options) {
$selectWidget = new SelectWidget(
$datafield->name,
'?',
'df_' . $datafield->id
);
$selectWidget->addElement(
new SelectElement(
'',
'(' . _('Keine Auswahl') . ')'
)
);
foreach ($options as $option) {
$selectWidget->addElement(
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;
} else {
//all other fields get a text field
$textWidget = new SearchWidget();
$textWidget->setTitle($datafield->name);
$textWidget->addNeedle(
'',
'df_'.$datafield->id,
false,
null,
null,
$datafields_filters[$datafield->id] ?? null
);
$textWidget->setOnSubmitHandler("STUDIP.AdminCourses.App.changeFilter({'df_".$datafield->id."': $(this).find('input').val()}); return false;");
return $textWidget;
}
}
}
/**
* This method is responsible for building the sidebar.
*
* Depending on the sidebar elements the user has selected some of those
* elements are shown or not. To find out what elements
* the user has selected the user configuration is accessed.
*
* @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()
{
/*
Depending on the elements the user has selected
some of the following elements may not be presented
in the sidebar.
*/
$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();
/*
Order of elements:
* Navigation
* actions
* selected filters (configurable)
* selected actions widget
* view filter (configurable)
* export
*/
if ($GLOBALS['perm']->have_perm($this->sem_create_perm)) {
$actions = new ActionsWidget();
$actions->addLink(
_('Neue Veranstaltung anlegen'),
URLHelper::getURL('dispatch.php/course/wizard'),
Icon::create('add')
)->asDialog('size=50%');
$actions->addLink(
_('Diese Seitenleiste konfigurieren'),
URLHelper::getURL('dispatch.php/admin/courses/sidebar'),
Icon::create('admin')
)->asDialog();
$sidebar->addWidget($actions, 'links');
}
$filter = new OptionsWidget(_('Filter'));
$filter->setId('admin-filter-widget');
/*
Now draw the configurable elements according
to the values inside the visibleElements array.
*/
$institute_id = null;
if (!empty($visibleElements['search'])) {
$this->setSearchWiget();
}
if (!empty($visibleElements['institute'])) {
$inst_selector = $filter->addElement($this->getInstSelector());
if (count($inst_selector->getOptions()) === 1) {
$institute_id = $this->insts[0]['Institut_id'] ?? null;
}
}
if (!empty($visibleElements['semester'])) {
$filter->addElement($this->getSemesterSelector());
}
if (!empty($visibleElements['stgteil'])) {
$filter->addElement($this->getStgteilSelector());
}
if (!empty($visibleElements['courseType'])) {
$filter->addElement($this->getCourseTypeWidget());
}
if (!empty($visibleElements['teacher'])) {
$filter->addElement($this->getTeacherWidget($institute_id));
}
$sidebar->addWidget($filter, 'filter');
//if there are datafields in the list, draw their input fields, too:
if (!empty($visibleElements['datafields'])) {
//The datafields entry contains an array with datafield-IDs.
//We must fetch them from the database and show an appropriate widget
//for each datafield.
$visibleDatafieldIds = $visibleElements['datafields'];
$datafields = DataField::getDataFields('sem');
if ($datafields) {
foreach ($datafields as $datafield) {
if (in_array($datafield->id, $visibleDatafieldIds)) {
$widget = $this->getDatafieldWidget($datafield);
if ($widget) {
$sidebar->addWidget($widget);
}
}
}
}
}
//this shall be visible in every case:
$this->setActionsWidget();
//"export as Excel" is always visible:
if ($this->sem_create_perm) {
$params = [];
if ($GLOBALS['user']->cfg->ADMIN_COURSES_SEARCHTEXT) {
$params['search'] = $GLOBALS['user']->cfg->ADMIN_COURSES_SEARCHTEXT;
}
$export = new ExportWidget();
$export->addLink(
_('Als CSV-Datei exportieren'),
URLHelper::getURL('dispatch.php/admin/courses/export_csv', $params),
Icon::create('file-excel')
)->asDialog('size=auto');
$sidebar->addWidget($export);
}
foreach (PluginEngine::getPlugins(AdminCourseWidgetPlugin::class) as $plugin) {
foreach ($plugin->getWidgets() as $name => $widget) {
$position = $widget->getPositionInSidebar();
if ($position) {
$sidebar->insertWidget($widget, $position, $name);
} else {
$sidebar->addWidget($widget, $name);
}
}
}
}
/**
* Common tasks for all actions
*
* @param String $action Called action
* @param Array $args Possible arguments
*/
public function before_filter(&$action, &$args)
{
parent::before_filter($action, $args);
if ($GLOBALS['perm']->have_perm('admin')) {
Navigation::activateItem('/browse/my_courses/list');
} else {
Navigation::activateItem('/browse/admincourses');
}
// we are defintely not in an lecture or institute
closeObject();
//delete all temporary permission changes
if (is_array($_SESSION)) {
foreach (array_keys($_SESSION) as $key) {
if (strpos($key, 'seminar_change_view_') === 0) {
unset($_SESSION[$key]);
}
}
}
$this->insts = Institute::getMyInstitutes($GLOBALS['user']->id);
if (empty($this->insts) && !$GLOBALS['perm']->have_perm('root')) {
PageLayout::postError(_('Sie wurden noch keiner Einrichtung zugeordnet'));
}
// Semester selection
if ($GLOBALS['user']->cfg->MY_COURSES_SELECTED_CYCLE) {
$this->semester = Semester::find($GLOBALS['user']->cfg->MY_COURSES_SELECTED_CYCLE);
}
if (Request::get('reset-search')) {
$GLOBALS['user']->cfg->delete('ADMIN_COURSES_SEARCHTEXT');
}
PageLayout::setHelpKeyword('Basis.Veranstaltungen');
PageLayout::setTitle(_('Verwaltung von Veranstaltungen und Einrichtungen'));
// Add admission functions.
$this->max_show_courses = Config::get()->MAX_SHOW_ADMIN_COURSES;
}
/**
* Show all courses with more options
*/
public function index_action()
{
$this->fields = $this->getViewFilters();
$this->sortby = $GLOBALS['user']->cfg->MEINE_SEMINARE_SORT ?? (Config::get()->IMPORTANT_SEMNUMBER ? 'number' : 'name');
$this->sortflag = $GLOBALS['user']->cfg->MEINE_SEMINARE_SORT_FLAG ?? 'ASC';
$this->store_data = $this->getStoreData();
$this->buildSidebar();
}
private function getStoreData(): array
{
$configuration = User::findCurrent()->getConfiguration();
$institut_id = $configuration->MY_INSTITUTES_DEFAULT && $configuration->MY_INSTITUTES_DEFAULT !== 'all'
? $configuration->MY_INSTITUTES_DEFAULT
: null;
if ($configuration->MY_INSTITUTES_INCLUDE_CHILDREN) {
$institut_id .= '_withinst';
}
$filters = array_merge(
array_merge(...PluginEngine::sendMessage(AdminCourseWidgetPlugin::class, 'getFilters')),
$this->getDatafieldFilters(),
[
'institut_id' => $institut_id,
'search' => $configuration->ADMIN_COURSES_SEARCHTEXT,
'semester_id' => $configuration->MY_COURSES_SELECTED_CYCLE,
'course_type' => $configuration->MY_COURSES_TYPE_FILTER,
'stgteil' => $configuration->MY_COURSES_SELECTED_STGTEIL,
'teacher_filter' => $configuration->ADMIN_COURSES_TEACHERFILTER,
]
);
return [
'setActivatedFields' => $this->getFilterConfig(),
'setActionArea' => $configuration->MY_COURSES_ACTION_AREA ?? '1',
'setFilter' => array_filter($filters),
];
}
private function getDatafieldFilters(): array
{
$visibleElements = $this->getActiveElements();
if (empty($visibleElements['datafields'])) {
return [];
}
$datafields = DataField::getDataFields('sem');
$config = $GLOBALS['user']->cfg->ADMIN_COURSES_DATAFIELDS_FILTERS;
$datafields = array_filter($datafields, function (Datafield $datafield) use ($visibleElements, $config) {
return in_array($datafield->id, $visibleElements['datafields'])
&& isset($config[$datafield->id]);
});
$result = [];
foreach ($datafields as $datafield) {
$result["df_{$datafield->id}"] = $config[$datafield->id];
}
return $result;
}
public function search_action()
{
$this->processFilters();
$filter = AdminCourseFilter::get();
if (Request::option('course_id')) { //we have only one course and want to see if that course is part of the result set
$filter->query->where('course_id', 'seminare.Seminar_id = :course_id', ['course_id' => Request::option('course_id')]);
}
PluginEngine::sendMessage(AdminCourseWidgetPlugin::class, 'applyFilters', $filter);
try {
$courses = $filter->fetchCourses(
Request::bool('without_limit') ? null: $this->max_show_courses
);
} catch (OverflowException $e) {
$this->render_json(['count' => (int) $e->getMessage()]);
return;
}
$data = [
'data' => []
];
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'));
$course_ids = [];
foreach ($courses as $course) {
if ($course->parent && !Request::option('course_id')) {
if (!in_array($course->parent->id, $course_ids)) {
$data['data'][] = $this->getCourseData($course->parent, $activated_fields);
$course_ids[] = $course->parent->id;
}
if (!in_array($course->id, $course_ids)) {
$data['data'][] = $this->getCourseData($course, $activated_fields);
$course_ids[] = $course->id;
}
} else {
if (!in_array($course->id, $course_ids)) {
$data['data'][] = $this->getCourseData($course, $activated_fields);
$course_ids[] = $course->id;
}
foreach ($course->children as $childcourse) {
if (!in_array($childcourse->id, $course_ids)) {
$data['data'][] = $this->getCourseData($childcourse, $activated_fields);
$course_ids[] = $childcourse->id;
}
}
}
}
$tf = new Flexi\Factory($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->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'] = '';
$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->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'] = '';
$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'] = '';
$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;
case 21: //Mehrfachzuordnung Studienbereiche
$data['buttons_top'] = '';
$data['buttons_bottom'] = (string) \Studip\Button::createAccept(
_('Mehrfachzuordnung von Studienbereichen'), 'batch_assign_semtree',
[
'formaction' => URLHelper::getURL('dispatch.php/admin/tree/batch_assign_semtree'),
'data-dialog' => 'size=big'
]);
break;
case 22: //Mehrfachzuordnung Studienbereiche
$data['buttons_top'] = '';
$data['buttons_bottom'] = (string) \Studip\Button::createAccept(
_('Teilnehmendenexport'), 'batch_export_members',
[
'formaction' => URLHelper::getURL('dispatch.php/admin/courses/batch_export_members'),
'data-dialog' => 'size=big'
]);
break;
case 23: // Mass mail to selected courses
$data['buttons_top'] = '';
$data['buttons_bottom'] = (string) \Studip\Button::createAccept(
_('Nachricht an ausgewählte Veranstaltungen'), 'massmail',
[
'formaction' => URLHelper::getURL('dispatch.php/massmail/quick/courses'),
'data-dialog' => 'size=big'
]);
break;
default:
foreach (PluginManager::getInstance()->getPlugins(AdminCourseAction::class) as $plugin) {
if ($GLOBALS['user']->cfg->MY_COURSES_ACTION_AREA === get_class($plugin)) {
$multimode = $plugin->useMultimode();
if ($multimode) {
$data['buttons_top'] = '';
if ($multimode instanceof Flexi\Template) {
$data['buttons_bottom'] = $multimode->render();
} elseif ($multimode instanceof \Studip\Button) {
$data['buttons_bottom'] = (string) $multimode;
} 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);
}
private function processFilters(): void
{
$filters = Request::getArray('filters');
$config = User::findCurrent()->getConfiguration();
// Simple filters
$mapping = [
'search' => 'ADMIN_COURSES_SEARCHTEXT',
'semester_id' => 'MY_COURSES_SELECTED_CYCLE',
'stgteil' => 'MY_COURSES_SELECTED_STGTEIL',
'teacher_filter' => 'ADMIN_COURSES_TEACHERFILTER',
'course_type' => 'MY_COURSES_TYPE_FILTER',
'institut_id' => 'MY_INSTITUTES_DEFAULT',
];
if (!empty($filters['semester_id'])) {
$this->semester = Semester::find($filters['semester_id']);
} else {
$this->semester = null;
}
if (!empty($filters['institut_id'])) {
$config->store(
'MY_INSTITUTES_INCLUDE_CHILDREN',
str_contains($filters['institut_id'], '_') ? 1 : 0
);
if ($config->MY_INSTITUTES_INCLUDE_CHILDREN) {
$filters['institut_id'] = substr($filters['institut_id'], 0, strpos($filters['institut_id'], '_'));
}
}
foreach ($mapping as $key => $field) {
if (isset($filters[$key])) {
$config->store($field, $filters[$key]);
}
unset($filters[$key]);
}
if ($config->ADMIN_COURSES_TEACHERFILTER) {
if (!$config->MY_INSTITUTES_DEFAULT) {
$config->delete('ADMIN_COURSES_TEACHERFILTER');
} else {
$include_children = $GLOBALS['user']->cfg->MY_INSTITUTES_INCLUDE_CHILDREN ? ' OR Institute.fakultaets_id = :institut_id ' : '';
$exists = InstituteMember::countBySQL("INNER JOIN `Institute` USING (`Institut_id`) WHERE `user_inst`.`user_id` = :user_id AND (`Institute`.`Institut_id` = :institut_id $include_children) AND `user_inst`.`inst_perms` = 'dozent' ", [
'user_id' => $config->ADMIN_COURSES_TEACHERFILTER,
'institut_id' => $config->MY_INSTITUTES_DEFAULT
]) > 0;
if (!$exists) {
$config->delete('ADMIN_COURSES_TEACHERFILTER');
}
}
}
if ($config->MY_COURSES_SELECTED_STGTEIL) {
if (!$config->MY_INSTITUTES_DEFAULT) {
$config->delete('MY_COURSES_SELECTED_STGTEIL');
} else {
$statement = DBManager::get()->prepare("
SELECT 1
FROM `mvv_stg_stgteil`
INNER JOIN `mvv_studiengang` ON (`mvv_stg_stgteil`.`studiengang_id` = `mvv_studiengang`.`studiengang_id`)
WHERE `mvv_studiengang`.`institut_id` = :institut_id
AND `mvv_stg_stgteil`.`stgteil_id` = :stgteil_id
");
$statement->execute([
'institut_id' => $config->MY_INSTITUTES_DEFAULT,
'stgteil_id' => $config->MY_COURSES_SELECTED_STGTEIL
]);
$exists = (bool) $statement->fetch(PDO::FETCH_COLUMN);
if (!$exists) {
$config->delete('MY_COURSES_SELECTED_STGTEIL');
}
}
}
// Datafield filters
$activeSidebarElements = $this->getActiveElements();
$datafields_filters = $GLOBALS['user']->cfg->ADMIN_COURSES_DATAFIELDS_FILTERS;
foreach (DataField::getDataFields('sem') as $datafield) {
$key = "df_{$datafield->id}";
if (
!empty($filters[$key])
&& in_array($datafield->id, $activeSidebarElements['datafields'])
) {
$datafields_filters[$datafield->id] = $filters[$key];
} else {
unset($datafields_filters[$datafield->id]);
}
}
$config->store('ADMIN_COURSES_DATAFIELDS_FILTERS', $datafields_filters);
// Plugin filters
foreach (PluginEngine::getPlugins(AdminCourseWidgetPlugin::class) as $plugin) {
$plugin_filters = array_intersect_key(
$filters,
$plugin->getFilters()
);
$plugin->setFilters($plugin_filters);
}
}
protected function getCourseData(Course $course, $activated_fields)
{
$d = [
'id' => $course->id,
'parent_course' => $course->parent_course
];
if (in_array('name', $activated_fields)) {
$params = tooltip2(_('Veranstaltungsdetails anzeigen'));
$params['style'] = 'cursor: pointer';
$d['name'] = ''
. htmlReady($course->name)
.' '
.' '
.(!$course->visible ? _('(versteckt)') : '');
}
if (in_array('number', $activated_fields)) {
$d['number'] = ''
. htmlReady($course->veranstaltungsnummer)
.'';
}
if (in_array('avatar', $activated_fields)) {
$d['avatar'] = ''
.CourseAvatar::getAvatar($course->getId())->getImageTag(Avatar::SMALL, ['title' => $course->name])
."";
}
if (in_array('type', $activated_fields)) {
$semtype = $course->getSemType();
$d['type'] = htmlReady($semtype['name']);
}
if (in_array('room_time', $activated_fields)) {
$strings = $course->getAllDatesInSemester($this->semester)->toStringArray();
$d['room_time'] = implode('
', $strings) ?: _('nicht angegeben');
}
if (in_array('semester', $activated_fields)) {
$d['semester'] = htmlReady($course->semester_text);
$d['semester_sort'] = $course->start_semester ? $course->start_semester->beginn : 0;
}
if (in_array('institute', $activated_fields)) {
$d['institute'] = htmlReady($course->home_institut ? $course->home_institut->name : $course->institute);
}
if (in_array('requests', $activated_fields)) {
$d['requests'] = ''.count($course->room_requests)."";
}
if (in_array('teachers', $activated_fields)) {
$teachers = $this->getTeacher($course->id);
$teachers = array_map(function ($teacher) {
return ''. htmlReady($teacher['fullname']).'';
}, $teachers);
$d['teachers'] = implode(', ', $teachers);
}
if (in_array('members', $activated_fields)) {
$d['members'] = ''
.$course->countMembersWithStatus('user autor')
.'';
}
if (in_array('waiting', $activated_fields)) {
$d['waiting'] = ''
.$course->getNumWaiting()
.'';
}
if (in_array('preliminary', $activated_fields)) {
$d['preliminary'] = ''
.$course->getNumPrelimParticipants()
.'';
}
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'] = '