aboutsummaryrefslogtreecommitdiff
path: root/lib/classes/coursewizardsteps
diff options
context:
space:
mode:
authorJan-Hendrik Willms <tleilax+github@gmail.com>2021-07-22 16:07:19 +0200
committerJan-Hendrik Willms <tleilax+github@gmail.com>2021-07-22 16:19:12 +0200
commita3da1483a9e689846179159355badfec8073dbec (patch)
tree770dcca6bdf5f6f2a11b0e7fcbbeda6919a3fc52 /lib/classes/coursewizardsteps
current code from svn, revision 62608
Diffstat (limited to 'lib/classes/coursewizardsteps')
-rw-r--r--lib/classes/coursewizardsteps/AdvancedBasicDataWizardStep.php144
-rw-r--r--lib/classes/coursewizardsteps/BasicDataWizardStep.php612
-rw-r--r--lib/classes/coursewizardsteps/LVGroupsWizardStep.php527
-rw-r--r--lib/classes/coursewizardsteps/StudyAreasLVGroupsCombinedWizardStep.php103
-rw-r--r--lib/classes/coursewizardsteps/StudyAreasWizardStep.php289
5 files changed, 1675 insertions, 0 deletions
diff --git a/lib/classes/coursewizardsteps/AdvancedBasicDataWizardStep.php b/lib/classes/coursewizardsteps/AdvancedBasicDataWizardStep.php
new file mode 100644
index 0000000..1843084
--- /dev/null
+++ b/lib/classes/coursewizardsteps/AdvancedBasicDataWizardStep.php
@@ -0,0 +1,144 @@
+<?php
+/**
+ * AdvancedBasicDataWizardStep.php
+ * Course wizard step for getting the basic course data.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * @author Thomas Hackl <thomas.hackl@uni-passau.de>
+ * @author Stefan Osterloh <s.osterloh@uni-oldenburg.de>
+ * @copyright 2015 Stud.IP Core-Group
+ * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2
+ * @category Stud.IP
+ */
+
+class AdvancedBasicDataWizardStep extends BasicDataWizardStep
+{
+ /**
+ * Returns the Flexi template for entering the necessary values
+ * for this step.
+ *
+ * @param Array $values Pre-set values
+ * @param int $stepnumber which number has the current step in the wizard?
+ * @param String $temp_id temporary ID for wizard workflow
+ * @return String a Flexi template for getting needed data.
+ */
+ public function getStepTemplate($values, $stepnumber, $temp_id)
+ {
+ $values = $this->adjustValues($values);
+
+ // We only need our own stored values here.
+ if ($values[__CLASS__]['studygroup']) {
+ return parent::getStepTemplate($values, $stepnumber, $temp_id);
+ }
+
+ // Load template from step template directory.
+ $factory = new Flexi_TemplateFactory($GLOBALS['STUDIP_BASE_PATH'].'/app/views/course/wizard/steps');
+ $template = $factory->open('advancedbasicdata/index');
+
+ if ($template = $this->setupTemplateAttributes($template, $values, $stepnumber, $temp_id)) {
+ $values = $this->makeI18N($values[__CLASS__], ['name', 'description', 'subtitle', 'kind']);
+
+ $template->set_attribute('values', array_merge($template->values, $values));
+
+ return $template->render();
+ }
+ }
+
+ /**
+ * Validates if given values are sufficient for completing the current
+ * course wizard step and switch to another one. If not, all errors are
+ * collected and shown via PageLayout::postMessage.
+ *
+ * @param mixed $values Array of stored values
+ * @return bool Everything ok?
+ */
+ public function validate($values)
+ {
+ $values = $this->adjustValues($values);
+ return parent::validate($values);
+ }
+
+ /**
+ * Stores the given values to the given course.
+ *
+ * @param Course $course the course to store values for
+ * @param Array $values values to set
+ * @return Course The course object with updated values.
+ */
+ public function storeValues($course, $values)
+ {
+ $values = $this->adjustValues($values);
+ $course = parent::storeValues($course, $values);
+
+ // There probably was an error upon storing
+ if (!$course) {
+ return false;
+ }
+
+ // Studygroup? -> nothing to do here
+ if ($values[__CLASS__]['studygroup']) {
+ return $course;
+ }
+
+ // Add advanced data
+ $course->untertitel = new I18NString($values[__CLASS__]['subtitle'], $values[__CLASS__]['subtitle_i18n']);
+ $course->art = new I18NString($values[__CLASS__]['kind'], $values[__CLASS__]['kind_i18n']);
+ $course->ects = $values[__CLASS__]['ects'];
+ $course->admission_turnout = $values[__CLASS__]['maxmembers'];
+ if ($course->store() === false) {
+ PageLayout::postError(sprintf(_('Es ist ein Fehler beim Speichern der erweiterten Einstellungen für %s aufgetreten. Kontrollieren Sie bitte:')
+ , htmlReady($course->name)),
+ [_('Untertitel der Veranstalung'),
+ _('Art der Veranstaltung'),
+ _('ECTS-Punkte der Veranstaltung'),
+ _('Max. Teilnehmendenzahl der Veranstaltung')]);
+ }
+ return $course;
+ }
+
+ /**
+ * This method will adjust the given values from parent class
+ * or use previously set values from this class.
+ *
+ * @param array $values Array of values
+ * @return array of adjusted values
+ */
+ private function adjustValues($values)
+ {
+ $parent_class = get_parent_class($this);
+
+ if (!isset($values[__CLASS__]) && isset($values[$parent_class])) {
+ $values[__CLASS__] = $values[$parent_class];
+ } else {
+ $values[$parent_class] = $values[__CLASS__];
+ }
+
+ return $values;
+ }
+
+ /**
+ * Copy values for basic data wizard step from given course.
+ * @param Course $course
+ * @param Array $values
+ */
+ public function copy($course, $values)
+ {
+ $values = parent::copy($course, $values);
+ $values = $this->adjustValues($values);
+
+ $values[__CLASS__] = array_merge($values[__CLASS__], [
+ 'subtitle' => $course->untertitel,
+ 'subtitle_i18n' => $course->untertitel->toArray(),
+ 'kind' => $course->art,
+ 'kind_i18n' => $course->art->toArray(),
+ 'ects' => $course->ects,
+ 'maxmembers' => $course->admission_turnout,
+ ]);
+
+ return $values;
+ }
+}
diff --git a/lib/classes/coursewizardsteps/BasicDataWizardStep.php b/lib/classes/coursewizardsteps/BasicDataWizardStep.php
new file mode 100644
index 0000000..225b8ff
--- /dev/null
+++ b/lib/classes/coursewizardsteps/BasicDataWizardStep.php
@@ -0,0 +1,612 @@
+<?php
+/**
+ * BasicDataWizardStep.php
+ * Course wizard step for getting the basic course data.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * @author Thomas Hackl <thomas.hackl@uni-passau.de>
+ * @copyright 2015 Stud.IP Core-Group
+ * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2
+ * @category Stud.IP
+ */
+
+class BasicDataWizardStep implements CourseWizardStep
+{
+ /**
+ * Returns the Flexi template for entering the necessary values
+ * for this step.
+ *
+ * @param Array $values Pre-set values
+ * @param int $stepnumber which number has the current step in the wizard?
+ * @param String $temp_id temporary ID for wizard workflow
+ * @return String a Flexi template for getting needed data.
+ */
+ public function getStepTemplate($values, $stepnumber, $temp_id)
+ {
+ // Load template from step template directory.
+ $factory = new Flexi_TemplateFactory($GLOBALS['STUDIP_BASE_PATH'] . '/app/views/course/wizard/steps');
+ if ($values[__CLASS__]['studygroup']) {
+ $tpl = $factory->open('basicdata/index_studygroup');
+ $values[__CLASS__]['lecturers'][$GLOBALS['user']->id] = 1;
+ } else {
+ $tpl = $factory->open('basicdata/index');
+ }
+ if ($this->setupTemplateAttributes($tpl, $values, $stepnumber, $temp_id)) {
+ return $tpl->render();
+ }
+ }
+
+ protected function setupTemplateAttributes($tpl, $values, $stepnumber, $temp_id)
+ {
+ // We only need our own stored values here.
+ $values = $values[__CLASS__];
+ // Get all available course types and their categories.
+ $typestruct = [];
+ foreach (SemType::getTypes() as $type) {
+ $class = $type->getClass();
+ // Creates a studygroup.
+ if ($values['studygroup']) {
+ // Get all studygroup types.
+ if ($class['studygroup_mode']) {
+ $typestruct[$class['name']][] = $type;
+ }
+ // Pre-set institute for studygroup assignment.
+ $values['institute'] = Config::get()->STUDYGROUP_DEFAULT_INST;
+ // Normal course.
+ } else {
+ if (!$class['course_creation_forbidden'] && !$class['studygroup_mode']) {
+ $typestruct[$class['name']][] = $type;
+ }
+ }
+ }
+ $tpl->set_attribute('types', $typestruct);
+ // Select a default type if none is given.
+ if (!$values['coursetype']) {
+ if ($GLOBALS['user']->cfg->MY_COURSES_TYPE_FILTER && Request::isXhr()) {
+ $values['coursetype'] = $GLOBALS['user']->cfg->MY_COURSES_TYPE_FILTER;
+ } else {
+ $values['coursetype'] = 1;
+ }
+ }
+
+ // Semester selection.
+ $semesters = [];
+ $now = time();
+ // Allow only current or future semesters for selection.
+ foreach (Semester::getAll() as $s) {
+ if ($s->ende >= $now) {
+ if ($GLOBALS['perm']->have_perm("admin")) {
+ if ($s->id == $GLOBALS['user']->cfg->MY_COURSES_SELECTED_CYCLE &&
+ !$values['start_time'] && Request::isXhr()) {
+ $values['start_time'] = $s->beginn;
+ }
+ } else {
+ if ((time() >= $s->beginn - Config::get()->SEMESTER_TIME_SWITCH * 86400 * 7)
+ && (time() < $s->ende - Config::get()->SEMESTER_TIME_SWITCH * 86400 * 7)) {
+ $values['start_time'] = $s->beginn;
+ }
+ }
+ $semesters[] = $s;
+ }
+ }
+ if ($values['studygroup'] && (!count($typestruct) || !$values['institute']) ) {
+ $message = sprintf(_('Die Konfiguration der Studiengruppen ist unvollständig. ' .
+ 'Bitte wenden Sie sich an [die Stud.IP-Administration]%s .'),
+ URLHelper::getLink('dispatch.php/siteinfo/show')
+ );
+ PageLayout::postError(formatReady($message));
+ return false;
+ }
+ if (count($semesters) > 0) {
+ $tpl->set_attribute('semesters', array_reverse($semesters));
+ // If no semester is set, use current as selected default.
+ if (!$values['start_time']) {
+ $values['start_time'] = Semester::findCurrent()->beginn;
+ }
+ } else {
+ $message = sprintf(_('Veranstaltungen können nur ' .
+ 'im aktuellen oder in zukünftigen Semestern angelegt werden. ' .
+ 'Leider wurde kein passendes Semester gefunden. Bitte wenden ' .
+ 'Sie sich an [die Stud.IP-Administration]%s .'),
+ URLHelper::getLink('dispatch.php/siteinfo/show')
+ );
+ PageLayout::postError(formatReady($message));
+ return false;
+ }
+
+ // Create a I18NString for course name and description.
+ $values = $this->makeI18N($values, ['name', 'description']);
+
+ // Get all allowed home institutes (my own).
+ $institutes = Institute::getMyInstitutes();
+ if ($values['studygroup'] || count($institutes) > 0) {
+ $tpl->set_attribute('institutes', $institutes);
+ if (!$values['institute']) {
+ if ($GLOBALS['user']->cfg->MY_INSTITUTES_DEFAULT && Request::isXhr()) {
+ $values['institute'] = $GLOBALS['user']->cfg->MY_INSTITUTES_DEFAULT;
+ } else {
+ $values['institute'] = InstituteMember::getDefaultInstituteIdForUser($GLOBALS['user']->id);
+
+ // if for some reason no default institute is set, use the first one listed
+ if (!$values['institute']) {
+ $values['institute'] = $institutes[0]['Institut_id'];
+ }
+ }
+ }
+ } else {
+ $message = sprintf(_('Um Veranstaltungen ' .
+ 'anlegen zu können, muss Ihr Account der Einrichtung, ' .
+ 'für die Sie eine Veranstaltung anlegen möchten, zugeordnet ' .
+ 'werden. Bitte wenden Sie sich an [die ' .
+ 'Stud.IP-Administration]%s .'),
+ URLHelper::getLink('dispatch.php/siteinfo/show')
+ );
+ PageLayout::postError(formatReady($message));
+ return false;
+ }
+
+ // QuickSearch for participating institutes.
+ // No JS: Keep search value and results for displaying in search select box.
+ if ($values['part_inst_id']) {
+ Request::getInstance()->offsetSet('part_inst_id', $values['part_inst_id']);
+ }
+ if ($values['part_inst_id_parameter']) {
+ Request::getInstance()->offsetSet('part_inst_id_parameter', $values['part_inst_id_parameter']);
+ }
+ $instsearch = new StandardSearch('Institut_id',
+ _('Beteiligte Einrichtung hinzufügen'),
+ 'part_inst_id'
+ );
+ $tpl->set_attribute('instsearch', QuickSearch::get('part_inst_id', $instsearch)
+ ->withButton(['search_button_name' => 'search_part_inst', 'reset_button_name' => 'reset_instsearch'])
+ ->fireJSFunctionOnSelect('STUDIP.CourseWizard.addParticipatingInst')
+ ->render());
+ if (!$values['participating']) {
+ $values['participating'] = [];
+ }
+
+ // Quicksearch for lecturers.
+ // No JS: Keep search value and results for displaying in search select box.
+ if ($values['lecturer_id']) {
+ Request::getInstance()->offsetSet('lecturer_id', $values['lecturer_id']);
+ }
+ if ($values['lecturer_id_parameter']) {
+ Request::getInstance()->offsetSet('lecturer_id_parameter', $values['lecturer_id_parameter']);
+ }
+
+ // Check for deputies.
+ $deputies = Config::get()->DEPUTIES_ENABLE;
+ /*
+ * No lecturers set, add yourself so that at least one lecturer is
+ * present. But this can only be done if your own permission level
+ * is 'dozent'.
+ */
+ if (!$values['lecturers'] && $GLOBALS['perm']->have_perm('dozent') && !$GLOBALS['perm']->have_perm('admin')) {
+ $values['lecturers'][$GLOBALS['user']->id] = true;
+ // Remove from deputies if set.
+ if ($deputies && $values['deputies'][$GLOBALS['user']->id]) {
+ unset($values['deputies'][$GLOBALS['user']->id]);
+ }
+ // Add your own default deputies if applicable.
+ if ($deputies && Config::get()->DEPUTIES_DEFAULTENTRY_ENABLE) {
+ $values['deputies'] = array_merge($values['deputies'] ?: [],
+ array_flip(Deputy::findDeputies($GLOBALS['user']->id)->pluck('user_id')));
+ }
+ }
+ // Add lecturer from my courses filter.
+ if ($GLOBALS['user']->cfg->ADMIN_COURSES_TEACHERFILTER && !$values['lecturers'] && Request::isXhr()) {
+ $values['lecturers'][$GLOBALS['user']->cfg->ADMIN_COURSES_TEACHERFILTER] = true;
+ // Add this lecturer's default deputies if applicable.
+ if ($deputies && Config::get()->DEPUTIES_DEFAULTENTRY_ENABLE) {
+ $values['deputies'] = array_merge($values['deputies'] ?: [],
+ array_flip(Deputy::findDeputies($GLOBALS['user']->cfg->ADMIN_COURSES_TEACHERFILTER)->pluck('user_id')));
+ }
+ }
+ if (!$values['lecturers']) {
+ $values['lecturers'] = [];
+ }
+ if ($deputies && !$values['deputies']) {
+ $values['deputies'] = [];
+ }
+
+
+
+ // Quicksearch for deputies if applicable.
+ if ($deputies) {
+ // No JS: Keep search value and results for displaying in search select box.
+ if ($values['deputy_id']) {
+ Request::getInstance()->offsetSet('deputy_id', $values['deputy_id']);
+ }
+ if ($values['deputy_id_parameter']) {
+ Request::getInstance()->offsetSet('deputy_id_parameter', $values['deputy_id_parameter']);
+ }
+ $deputysearch = new PermissionSearch('user',
+ _('Vertretung hinzufügen'),
+ 'user_id',
+ ['permission' => 'dozent',
+ 'exclude_user' => array_keys($values['deputies'])]
+ );
+ $tpl->set_attribute('dsearch', QuickSearch::get('deputy_id', $deputysearch)
+ ->withButton(['search_button_name' => 'search_deputy', 'reset_button_name' => 'reset_dsearch'])
+ ->fireJSFunctionOnSelect('STUDIP.CourseWizard.addDeputy')
+ ->render());
+ }
+
+ if (!$values['tutors']) {
+ $values['tutors'] = [];
+ }
+
+ list($lsearch, $tsearch) = array_values($this->getSearch($values['coursetype'],
+ array_merge([$values['institute']], array_keys($values['participating'])),
+ array_keys($values['lecturers']), array_keys($values['tutors'])));
+ // Quicksearch for lecturers.
+ $tpl->set_attribute('lsearch', $lsearch);
+ $tpl->set_attribute('tsearch', $tsearch);
+ $tpl->set_attribute('values', $values);
+ // AJAX URL needed for default deputy checking.
+ $tpl->set_attribute('ajax_url', $values['ajax_url'] ?: URLHelper::getLink('dispatch.php/course/wizard/ajax'));
+ $tpl->set_attribute('default_deputies_enabled',
+ ($deputies && Config::get()->DEPUTIES_DEFAULTENTRY_ENABLE) ? 1 : 0);
+
+ return $tpl;
+ }
+
+ /**
+ * The function only needs to handle person adding and removing
+ * as other actions are handled by normal request processing.
+ * @param Array $values currently set values for the wizard.
+ * @return bool
+ */
+ public function alterValues($values)
+ {
+ // We only need our own stored values here.
+ $values = $values[__CLASS__];
+
+ // Add a participating institute.
+ if (Request::submitted('add_part_inst') && Request::option('part_inst_id')) {
+ $values['participating'][Request::option('part_inst_id')] = true;
+ unset($values['part_inst_id']);
+ unset($values['part_inst_id_parameter']);
+ }
+ // Remove a participating institute.
+ if ($remove = array_keys(Request::getArray('remove_participating'))) {
+ $remove = $remove[0];
+ unset($values['participating'][$remove]);
+ }
+ // Add a lecturer.
+ if (Request::submitted('add_lecturer') && Request::option('lecturer_id')) {
+ $values['lecturers'][Request::option('lecturer_id')] = true;
+ unset($values['lecturer_id']);
+ unset($values['lecturer_id_parameter']);
+ // Add default deputies if applicable.
+ if (Config::get()->DEPUTIES_ENABLE && Config::get()->DEPUTIES_DEFAULTENTRY_ENABLE) {
+ $values['deputies'] = array_merge($values['deputies'] ?: [],
+ array_flip(array_keys(Request::option('lecturer_id'))));
+ }
+ }
+ // Remove a lecturer.
+ if ($remove = array_keys(Request::getArray('remove_lecturer'))) {
+ $remove = $remove[0];
+ unset($values['lecturers'][$remove]);
+ }
+ // Add a deputy.
+ if (Request::submitted('add_deputy')) {
+ $values['deputies'][Request::option('deputy_id')] = true;
+ unset($values['deputy_id']);
+ unset($values['deputy_id_parameter']);
+ }
+ // Remove a deputy.
+ if ($remove = array_keys(Request::getArray('remove_deputy'))) {
+ $remove = $remove[0];
+ unset($values['deputies'][$remove]);
+ }
+ // Add a tutor.
+ if (Request::submitted('add_tutor') && Request::option('tutor_id')) {
+ $values['tutors'][Request::option('tutor_id')] = true;
+ unset($values['tutor_id']);
+ unset($values['tutor_id_parameter']);
+ }
+ // Remove a tutor.
+ if ($remove = array_keys(Request::getArray('remove_tutor'))) {
+ $remove = $remove[0];
+ unset($values['tutors'][$remove]);
+ }
+ return $values;
+ }
+
+ /**
+ * Validates if given values are sufficient for completing the current
+ * course wizard step and switch to another one. If not, all errors are
+ * collected and shown via PageLayout::postMessage.
+ *
+ * @param mixed $values Array of stored values
+ * @return bool Everything ok?
+ */
+ public function validate($values)
+ {
+ // We only need our own stored values here.
+ $values = $values[__CLASS__];
+ $ok = true;
+ $errors = [];
+ if (!trim($values['name'])) {
+ $errors[] = _('Bitte geben Sie den Namen der Veranstaltung an.');
+ }
+ if ($values['number'] != '') {
+ $course_number_format = Config::get()->COURSE_NUMBER_FORMAT;
+ if ($course_number_format && !preg_match('/^' . $course_number_format . '$/', $values['number'])) {
+ $errors[] = _('Die Veranstaltungsnummer hat ein ungültiges Format.');
+ }
+ }
+ if (!$values['lecturers']) {
+ $errors[] = sprintf(
+ _('Bitte tragen Sie mindestens eine Person als %s ein.'),
+ htmlReady(get_title_for_status('dozent', 1, $values['coursetype']))
+ );
+ }
+ if (!$values['lecturers'][$GLOBALS['user']->id] && !$GLOBALS['perm']->have_perm('admin')) {
+ if (Config::get()->DEPUTIES_ENABLE) {
+ if (!$values['deputies'][$GLOBALS['user']->id]) {
+ $errors[] = sprintf(
+ _('Sie selbst müssen entweder als %s oder als Vertretung eingetragen sein.'),
+ htmlReady(get_title_for_status('dozent', 1, $values['coursetype']))
+ );
+ }
+ } else {
+ $errors[] = sprintf(
+ _('Sie müssen selbst als %s eingetragen sein.'),
+ htmlReady(get_title_for_status('dozent', 1, $values['coursetype']))
+ );
+ }
+ }
+ if (in_array($values['coursetype'], studygroup_sem_types())) {
+ if (!$values['accept']) {
+ $errors[] = _('Sie müssen die Nutzungsbedingungen akzeptieren.');
+ }
+ }
+ if ($errors) {
+ $ok = false;
+ PageLayout::postError(_('Bitte beheben Sie erst folgende Fehler, bevor Sie fortfahren:'), $errors);
+ }
+ return $ok;
+ }
+
+ /**
+ * Stores the given values to the given course.
+ *
+ * @param Course $course the course to store values for
+ * @param Array $values values to set
+ * @return Course The course object with updated values.
+ */
+ public function storeValues($course, $values)
+ {
+ // We only need our own stored values here.
+ if (@$values['copy_basic_data'] === true) {
+ $source = Course::find($values['source_id']);
+ }
+ $values = $values[__CLASS__];
+ $seminar = new Seminar($course);
+
+ if (isset($source)) {
+ $course->setData($source->toArray('untertitel ort sonstiges art teilnehmer vorrausetzungen lernorga leistungsnachweis ects admission_turnout modules'));
+ foreach ($source->datafields as $one) {
+ $df = $one->getTypedDatafield();
+ if ($df->isEditable()) {
+ $course->datafields->findOneBy('datafield_id', $one->datafield_id)->content = $one->content;
+ }
+ }
+ }
+
+ $course->status = $values['coursetype'];
+ $course->start_time = $values['start_time'];
+ $course->duration_time = 0;
+ $course->name = new I18NString($values['name'], $values['name_i18n']);
+ $course->veranstaltungsnummer = $values['number'];
+ $course->beschreibung = new I18NString($values['description'], $values['description_i18n']);
+ $course->institut_id = $values['institute'];
+
+ $semclass = $seminar->getSemClass();
+ $course->visible = $semclass['visible'];
+ $course->admission_prelim = $semclass['admission_prelim_default'];
+ $course->lesezugriff = $semclass['default_read_level'] ?: 1;
+ $course->schreibzugriff = $semclass['default_write_level'] ?: 1;
+
+ // Studygroups: access and description.
+ if (in_array($values['coursetype'], studygroup_sem_types())) {
+ $course->visible = 1;
+ $course->duration_time = -1;
+ switch ($values['access']) {
+ case 'all':
+ $course->admission_prelim = 0;
+ break;
+ case 'invisible':
+ if (!Config::get()->STUDYGROUPS_INVISIBLE_ALLOWED) {
+ $course->visible = 0;
+ }
+ case 'invite':
+ $course->admission_prelim = 1;
+ $course->admission_prelim_txt = Config::get()->STUDYGROUP_ACCEPTANCE_TEXT;
+ break;
+ }
+ }
+ if ($course->store()) {
+ StudipLog::log('SEM_CREATE', $course->id, null, 'Veranstaltung mit Assistent angelegt');
+ $institutes = [$values['institute']];
+ if (isset($values['participating']) && is_array($values['participating'])) {
+ $institutes = array_merge($institutes, array_keys($values['participating']));
+ }
+ $seminar->setInstitutes($institutes);
+ $course->setSemesters([
+ Semester::findByTimestamp($values['start_time'])
+ ]);
+ if (isset($values['lecturers']) && is_array($values['lecturers'])) {
+ foreach (array_keys($values['lecturers']) as $user_id) {
+ $seminar->addMember($user_id, 'dozent');
+ }
+ }
+ if (isset($values['tutors']) && is_array($values['tutors'])) {
+ foreach (array_keys($values['tutors']) as $user_id) {
+ $seminar->addMember($user_id, 'tutor');
+ }
+ }
+ if (Config::get()->DEPUTIES_ENABLE && isset($values['deputies']) && is_array($values['deputies'])) {
+ foreach ($values['deputies'] as $d => $assigned) {
+ Deputy::addDeputy($d, $course->id);
+ }
+ }
+ if ($semclass['admission_type_default'] == 3) {
+ $course_set_id = CourseSet::getGlobalLockedAdmissionSetId();
+ CourseSet::addCourseToSet($course_set_id, $course->id);
+ }
+ return $course;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Checks if the current step needs to be executed according
+ * to already given values. A good example are study areas which
+ * are only needed for certain sem_classes.
+ *
+ * @param Array $values values specified from previous steps
+ * @return bool Is the current step required for a new course?
+ */
+ public function isRequired($values)
+ {
+ return true;
+ }
+
+ /**
+ * Copy values for basic data wizard step from given course.
+ * @param Course $course
+ * @param Array $values
+ */
+ public function copy($course, $values)
+ {
+ $data = [
+ 'coursetype' => $course->status,
+ 'start_time' => $course->start_time,
+ 'name' => $course->name,
+ 'name_i18n' => $course->name->toArray(),
+ 'number' => $course->veranstaltungsnummer,
+ 'institute' => $course->institut_id,
+ 'description' => $course->beschreibung,
+ 'description_i18n' => $course->beschreibung->toArray()
+ ];
+ $lecturers = $course->members->findBy('status', 'dozent')->pluck('user_id');
+ $data['lecturers'] = array_flip($lecturers);
+ $tutors = $course->members->findBy('status', 'tutor')->pluck('user_id');
+ $data['tutors'] = array_flip($tutors);
+ $participating = $course->institutes->pluck('institut_id');
+ $data['participating'] = array_flip($participating);
+ unset($data['participating'][$course->institut_id]);
+ if (Config::get()->DEPUTIES_ENABLE) {
+ $data['deputies'] = array_flip(Deputy::findDeputies($course->id)->pluck('user_id'));
+ }
+ $values[__CLASS__] = $data;
+ return $values;
+ }
+
+ /**
+ * Fetches the default deputies for a given person if the necessary
+ * config options are set.
+ * @param $user_id user whose default deputies to get
+ * @return Array Default deputy user_ids.
+ */
+ public function getDefaultDeputies($user_id)
+ {
+ if (Config::get()->DEPUTIES_ENABLE && Config::get()->DEPUTIES_DEFAULTENTRY_ENABLE) {
+ return Deputy::findDeputies($user_id)->map(function($deputy) {
+ return ['id' => $deputy->user_id, 'name' => $deputy->getDeputyFullname()];
+ });
+ } else {
+ return [];
+ }
+ }
+
+ public function getSearch($course_type, $institute_ids, $exclude_lecturers = [],$exclude_tutors = [])
+ {
+ if (SeminarCategories::getByTypeId($course_type)->only_inst_user) {
+ $search = 'user_inst';
+ } else {
+ $search = 'user';
+ }
+ $psearch = new PermissionSearch($search,
+ sprintf(_("%s hinzufügen"), get_title_for_status('dozent', 1, $course_type)),
+ 'user_id',
+ __CLASS__ . '::lsearchHelper'
+ );
+ $lsearch = QuickSearch::get('lecturer_id', $psearch)
+ ->withButton(['search_button_name' => 'search_lecturer', 'reset_button_name' => 'reset_lsearch'])
+ ->fireJSFunctionOnSelect('STUDIP.CourseWizard.addLecturer')
+ ->render();
+
+ $tutor_psearch = new PermissionSearch($search,
+ sprintf(_("%s hinzufügen"), get_title_for_status('tutor', 1, $course_type)),
+ 'user_id',
+ __CLASS__ . '::tsearchHelper'
+ );
+ $tsearch = QuickSearch::get('tutor_id', $tutor_psearch)
+ ->withButton(['search_button_name' => 'search_tutor', 'reset_button_name' => 'reset_tsearch'])
+ ->fireJSFunctionOnSelect('STUDIP.CourseWizard.addTutor')
+ ->render();
+
+ return compact('lsearch', 'tsearch');
+ }
+
+ public static function tsearchHelper($psearch, $context)
+ {
+ $ret['permission'] = ['tutor', 'dozent'];
+ $ret['exclude_user'] = array_keys((array)$context['tutors']);
+ $ret['institute'] = array_merge([$context['institute']], array_keys((array)$context['participating']));
+ return $ret;
+ }
+
+ public static function lsearchHelper($psearch, $context)
+ {
+ $ret['permission'] = 'dozent';
+ $ret['exclude_user'] = array_keys((array)$context['lecturers']);
+ $ret['institute'] = array_merge([$context['institute']], array_keys((array)$context['participating']));
+ return $ret;
+ }
+
+ /**
+ * Creates I18N strings from the given values at the given indices.
+ *
+ * @param array $values this step's set values
+ * @param array $indices the values to convert to I18NStrings
+ *
+ * @return array modified values
+ */
+ protected function makeI18N($values, $indices)
+ {
+ /**
+ * Create array for configured content languages
+ */
+ $translations = array_combine(
+ array_keys($GLOBALS['CONTENT_LANGUAGES']),
+ array_fill(0, count($GLOBALS['CONTENT_LANGUAGES']), '')
+ );
+
+ foreach ($indices as $index) {
+ // There are values given => create an I18NString
+ if ($values[$index]) {
+
+ $values[$index] = new I18NString($values[$index], $values[$index . '_i18n']);
+
+ // Current index is not set (yet), create an empty I18NString
+ } else {
+
+ $values[$index] = new I18NString('', $translations);
+
+ }
+ }
+
+ return $values;
+ }
+
+}
diff --git a/lib/classes/coursewizardsteps/LVGroupsWizardStep.php b/lib/classes/coursewizardsteps/LVGroupsWizardStep.php
new file mode 100644
index 0000000..6d5f20c
--- /dev/null
+++ b/lib/classes/coursewizardsteps/LVGroupsWizardStep.php
@@ -0,0 +1,527 @@
+<?php
+/**
+ * LVGroupsWizardStep.php
+ * Course wizard step for assigning LV Groups.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * @author Timo Hartge <hartge@data-quest.de>
+ * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2
+ * @category Stud.IP
+ */
+
+require_once dirname(__FILE__) . '/../StudipLvgruppeSelection.class.php';
+
+class LVGroupsWizardStep implements CourseWizardStep
+{
+ /**
+ * Returns the Flexi template for entering the necessary values
+ * for this step.
+ *
+ * @param Array $values Pre-set values
+ * @param int $stepnumber which number has the current step in the wizard?
+ * @param String $temp_id temporary ID for wizard workflow
+ * @return String a Flexi template for getting needed data.
+ */
+ public function getStepTemplate($values, $stepnumber, $temp_id)
+ {
+ // retrieve class of step 1 from step registry
+ $step_one_class = CourseWizardStepRegistry::findOneBySQL('number = 1 AND enabled = 1')
+ ->classname;
+
+ // store start time of semester selected in first step
+ $course_start_time = $values[$step_one_class]['start_time'];
+
+ // We only need our own stored values here.
+ $values = $values[__CLASS__];
+
+ // Load template from step template directory.
+ $factory = new Flexi_TemplateFactory($GLOBALS['STUDIP_BASE_PATH'] . '/app/views/course/wizard/steps');
+ $tpl = $factory->open('lvgroups/index');
+ $tpl->set_attribute('values', $values);
+
+ $lvgtree = new StudipLvgruppeSelection();
+
+ $selection = self::get_selection($temp_id);
+ if (!empty($values['lvgruppe_selection']['areas'])) {
+ foreach ($values['lvgruppe_selection']['areas'] as $area_id) {
+ $lvgroup = Lvgruppe::find($area_id);
+ $selection->add($lvgroup);
+ }
+ }
+
+ $selection_details = $values['lvgruppe_selection']['area_details'];
+
+ if ($_SESSION[__CLASS__]['course_start_time'] != $course_start_time) {
+ // don't store previously opened nodes
+ // because we get in trouble if the semester has changed
+ $open_nodes = [];
+ } else {
+ $open_nodes = !empty($values['open_lvg_nodes']) ? $values['open_lvg_nodes'] : [];
+ }
+
+ $_SESSION[__CLASS__]['course_start_time'] = $course_start_time;
+
+ $tpl->set_attribute('open_lvg_nodes', $open_nodes);
+ $tpl->set_attribute('selection', $selection);
+ $tpl->set_attribute('selection_details', $selection_details);
+ $tpl->set_attribute('tree', $lvgtree->getRootItem()->getChildren());
+
+ $tpl->set_attribute('ajax_url', $values['ajax_url'] ?: URLHelper::getLink('dispatch.php/course/wizard/ajax'));
+ $tpl->set_attribute('no_js_url', $values['no_js_url'] ?: 'dispatch.php/course/wizard/forward/'.$stepnumber.'/'.$temp_id);
+ $tpl->set_attribute('stepnumber', $stepnumber);
+ $tpl->set_attribute('temp_id', $temp_id);
+ return $tpl->render();
+ }
+
+ /**
+ * Returns a LvGruppen-Selection object for a given course ID.
+ *
+ * @param string either the MD5ish ID of a course or something falsy to
+ * indicate a course that is currently being created
+ *
+ * @return mixed LvGruppen-Selection object representing
+ * the selection form
+ */
+ public function get_selection($course_id)
+ {
+ if (self::isCourseId($course_id)) {
+ $selection = new StudipLvgruppeSelection($course_id);
+ } else {
+ $lvgruppen = [];
+ if (isset($GLOBALS['sem_create_data'])
+ && isset($GLOBALS['sem_create_data']['sem_lvgruppen'])) {
+ $lvgruppen = $GLOBALS['sem_create_data']['sem_lvgruppen'];
+ }
+ $selection = new StudipLvgruppeSelection();
+ $selection->setLvgruppen($lvgruppen);
+ }
+ return $selection;
+ }
+
+ /**
+ * Every (non-empty) string is a valid course ID except the string '-'
+ *
+ * @param mixed the value to check
+ * @return bool TRUE if it is courseID-ish, FALSE otherwise
+ */
+ public static function isCourseId($id)
+ {
+ return is_string($id) && $id !== '' && $id !== '-';
+ }
+
+ public function getLVGroupTreeLevel($parentId, $parentClass)
+ {
+ $level = [];
+ $children = [];
+ $searchtree = [];
+
+ $course = Course::findCurrent();
+ if ($course) {
+ $course_start = $course->start_time;
+ $course_end = ($course->end_time < 0 || is_null($course->end_time)) ? PHP_INT_MAX : $course->end_time;
+ } else {
+ $semester = Semester::findByTimestamp($_SESSION[__CLASS__]['course_start_time']);
+ $course_start = $semester->beginn;
+ $course_end = $semester->ende;
+ }
+
+ $mvvid = explode('-', $parentId);
+ $mvvobj = $parentClass::find($mvvid[0]);
+ $children = $mvvobj->getChildren();
+
+ $i = 1;
+ foreach ($children as $c) {
+
+ if (isset($c->stat)) {
+ if ($c->stat != 'genehmigt') {
+ continue;
+ } elseif (isset($c->start) || isset($c->end)) {
+ $mvv_start = Semester::find($c->start);
+ $mvv_start = $mvv_start ? $mvv_start->beginn : 0;
+ $mvv_end = Semester::find($c->end);
+ $mvv_end = $mvv_end ? $mvv_end->ende : PHP_INT_MAX;
+
+ if ($course_end < $mvv_start || $course_start > $mvv_end) {
+ continue;
+ }
+ }
+ }
+
+ // name of module maybe differs from original module title if it
+ // is assigned to a Studiengangteilabschnitt
+ if (is_a($c, 'Modul')) {
+ $stgteilabschnitt_modul = StgteilabschnittModul::findOneBySql(
+ '`abschnitt_id` = ? AND `modul_id` = ?', [$mvvid[0], $c->id]);
+ $name = $stgteilabschnitt_modul->getDisplayName();
+ } else {
+ $name = $c->getDisplayName();
+ }
+
+ $level[] = [
+ 'id' => $c->id . '-' . $mvvid[1] . $i++,
+ 'name' => $name,
+ 'has_children' => $c->hasChildren(),
+ 'parent' => $c->getTrailParentId(),
+ 'assignable' => $c->isAssignable(),
+ 'mvvclass' => get_class($c)
+ ];
+ }
+
+ if (Request::isXhr()) {
+ return json_encode($level);
+ } else {
+ return $level;
+ }
+ }
+
+ public function searchLVGroupTree($searchterm)
+ {
+ $result = [];
+ $selection = self::get_selection(Request::get('cid'));
+ $selectedlvg = [];
+ if (!empty($selection)) {
+ $selectedlvg = $selection->getLvGruppenIDs();
+ }
+
+ $course = Course::findCurrent();
+ if ($course) {
+ $course_start = $course->start_time;
+ $course_end = ($course->end_time < 0 || is_null($course->end_time)) ? PHP_INT_MAX : $course->end_time;
+ } else {
+ $semester = Semester::findByTimestamp($_SESSION[__CLASS__]['course_start_time']);
+ $course_start = $semester->beginn;
+ $course_end = $semester->ende;
+ }
+
+ $status_modul = [];
+ foreach ($GLOBALS['MVV_MODUL']['STATUS']['values'] as $name => $status) {
+ if ($status['public'] && $status['visible']) {
+ $status_modul[] = $name;
+ }
+ }
+
+ $status_version = [];
+ foreach ($GLOBALS['MVV_STGTEILVERSION']['STATUS']['values'] as $name => $status) {
+ if ($status['public'] && $status['visible']) {
+ $status_version[] = $name;
+ }
+ }
+
+ $filter = [
+ 'mvv_modul.stat' => $status_modul,
+ 'mvv_stgteilversion.stat' => $status_version,
+ 'start_sem.beginn' => $course_end,
+ 'end_sem.ende' => $course_start
+ ];
+
+ foreach (Lvgruppe::findBySearchTerm($searchterm, $filter) as $area) {
+ if (in_array($area->id, $selectedlvg)) {
+ continue;
+ }
+
+ $factory = new Flexi_TemplateFactory($GLOBALS['STUDIP_BASE_PATH'] . '/app/views');
+ $html = $factory->render('course/wizard/steps/lvgroups/lvgroup_searchentry', compact('area', 'inlist'));
+ $data = [
+ 'id' => $area->id,
+ 'html_string' => $html
+ ];
+ $result[] = $data;
+ }
+ return json_encode($result);
+ }
+
+ public function getLVGroupDetails($id) {
+ $mvvid = explode('-', $id);
+ $area = Lvgruppe::find($mvvid[0]);
+
+ $course = Course::findCurrent();
+ if ($course) {
+ $course_start = $course->start_time;
+ $course_end = ($course->end_time < 0 || is_null($course->end_time)) ? PHP_INT_MAX : $course->end_time;
+ } else {
+ $semester = Semester::findByTimestamp($_SESSION[__CLASS__]['course_start_time']);
+ $course_start = $semester->beginn;
+ $course_end = $semester->ende;
+ }
+
+ $status_modul = [];
+ foreach ($GLOBALS['MVV_MODUL']['STATUS']['values'] as $name => $status) {
+ if ($status['public'] && $status['visible']) {
+ $status_modul[] = $name;
+ }
+ }
+
+ ModuleManagementModelTreeItem::setObjectFilter('Modul',
+ function ($modul) use ($course_start, $course_end, $status_modul) {
+ if (!in_array($modul->stat, $status_modul)) {
+ return false;
+ }
+ $modul_start = Semester::find($modul->start)->beginn ?: 0;
+ $modul_end = Semester::find($modul->end)->ende ?: PHP_INT_MAX;
+ return ($modul_start <= $course_end && $modul_end >= $course_start);
+ });
+
+ $status_version = [];
+ foreach ($GLOBALS['MVV_STGTEILVERSION']['STATUS']['values'] as $name => $status) {
+ if ($status['public'] && $status['visible']) {
+ $status_version[] = $name;
+ }
+ }
+
+ ModuleManagementModelTreeItem::setObjectFilter('StgteilVersion',
+ function ($version) use ($status_version) {
+ return in_array($version->stat, $status_version);
+ });
+
+ $trails = $area->getTrails([
+ 'Modulteil',
+ 'StgteilabschnittModul',
+ 'StgteilAbschnitt',
+ 'StgteilVersion',
+ 'Studiengang']);
+ $pathes = ModuleManagementModelTreeItem::getPathes($trails);
+
+ $factory = new Flexi_TemplateFactory($GLOBALS['STUDIP_BASE_PATH'] . '/app/views');
+ $html = $factory->render('course/lvgselector/entry_trails',
+ compact('area', 'pathes'));
+
+ $data = [
+ 'id' => $area->id,
+ 'html_string' => $html
+ ];
+ if (Request::isXhr()) {
+ return json_encode($data);
+ } else {
+ return $data;
+ }
+ }
+
+ public function getAncestorTree($id)
+ {
+ $mvvid = explode('-', $id);
+ $area = Lvgruppe::find($mvvid[0]);
+
+ $factory = new Flexi_TemplateFactory($GLOBALS['STUDIP_BASE_PATH'] . '/app/views');
+ $html = $factory->render('course/wizard/steps/lvgroups/lvgroup_entry', compact('area'));
+
+ $data = [
+ 'id' => $area->id,
+ 'html_string' => $html
+ ];
+ return json_encode($data);
+ }
+
+
+ /**
+ * Catch form submits other than "previous" and "next" and handle the
+ * given values. This is only important for no-JS situations.
+ * @param Array $values currently set values for the wizard.
+ * @return bool
+ */
+ public function alterValues($values)
+ {
+ // We only need our own stored values here.
+ $values = $values[__CLASS__];
+
+ if (Request::submitted('open_nodes')) {
+ $already_open_nodes = unserialize(Request::get('open_nodes'));
+ foreach ($already_open_nodes as $open_lvgnode) {
+ $values['open_lvg_nodes'][] = $open_lvgnode;
+ }
+ }
+
+ if (Request::option('open_node')) {
+ $node_to_open = Request::get('open_node');
+ if (!in_array($node_to_open, $values['open_lvg_nodes'])) {
+ $values['open_lvg_nodes'][] = $node_to_open;
+ } else {
+ $k = array_search($node_to_open, $values['open_lvg_nodes']);
+ unset($values['open_lvg_nodes'][$k]);
+ }
+ }
+
+ if (Request::submitted('lvgruppe_selection')) {
+
+ $lvgruppe_selection = Request::getArray('lvgruppe_selection');
+
+ if (isset($lvgruppe_selection['details'])) {
+ foreach (array_keys($lvgruppe_selection['details']) as $lvgid) {
+ $detail = $this->getLVGroupDetails($lvgid);
+ $values['lvgruppe_selection']['area_details'][$detail['id']] = $detail['html_string'];
+ }
+ }
+
+ if (isset($lvgruppe_selection['remove'])) {
+ $new_areas = [];
+ foreach ($lvgruppe_selection['areas'] as $area) {
+ if(!key_exists($area, $lvgruppe_selection['remove'])) {
+ $new_areas[] = $area;
+ }
+ }
+ $values['lvgruppe_selection']['areas'] = $new_areas;
+ }
+ }
+
+ if ($assign = array_keys(Request::getArray('assign'))) {
+ $values['lvgruppe_selection']['areas'][] = $assign[0];
+ }
+
+ return $values;
+ }
+
+ /**
+ * Validates if given values are sufficient for completing the current
+ * course wizard step and switch to another one. If not, all errors are
+ * collected and shown via PageLayout::postMessage.
+ *
+ * @param mixed $values Array of stored values
+ * @return bool Everything ok?
+ */
+ public function validate($values)
+ {
+ $ok = true;
+ $errors = [];
+
+ $coursetype = 1;
+ foreach ($values as $class) {
+ if ($class['coursetype']) {
+ $coursetype = $class['coursetype'];
+ break;
+ }
+ }
+ $category = SeminarCategories::GetByTypeId($coursetype);
+
+ if ($category->bereiche) {
+ if (isset($values['StudyAreasLVGroupsCombinedWizardStep'])
+ && !count($values['StudyAreasLVGroupsCombinedWizardStep']['studyareas'])
+ && !count($values[__CLASS__]['lvgruppe_selection']['areas'])
+ && $values[__CLASS__]['step'] > $values['StudyAreasLVGroupsCombinedWizardStep']['step']) {
+ $ok = false;
+ $errors[] = _('Die Veranstaltung muss mindestens einem Studienbereich oder einer LV-Gruppe zugeordnet sein.');
+ }
+ } else {
+ // optional step if study areas step is activated and at least one area is assigned
+ if (!count($values[__CLASS__]['lvgruppe_selection']['areas'])) {
+ $ok = false;
+ $errors[] = _('Der Veranstaltung muss mindestens eine Lehrveranstaltungsgruppe zugeordnet sein.');
+ }
+ }
+ if ($errors) {
+ PageLayout::postError(
+ _('Bitte beheben Sie erst folgende Fehler, bevor Sie fortfahren:'),
+ $errors
+ );
+ }
+ return $ok;
+ }
+
+ /**
+ * Stores the given values to the given course.
+ *
+ * @param Course $course the course to store values for
+ * @param Array $values values to set
+ * @return Course The course object with updated values.
+ */
+ public function storeValues($course, $values)
+ {
+ if ($this->is_locked($values)) {
+ throw new AccessDeniedException();
+ }
+
+ // Leave early if no values are set
+ if (!isset($values[__CLASS__]['lvgruppe_selection']['areas'])
+ || !is_array($values[__CLASS__]['lvgruppe_selection']['areas']))
+ {
+ return $course;
+ }
+
+ $selection = new StudipLvgruppeSelection($course->id);
+ foreach ($values[__CLASS__]['lvgruppe_selection']['areas'] as $lvg_id) {
+ $area = Lvgruppe::find($lvg_id);
+ $selection->add($area);
+ }
+ LvGruppe::setLvgruppen($course->id, $selection->getLvgruppenIDs());
+
+ return $course;
+ }
+
+ /**
+ * Checks if the current step needs to be executed according
+ * to already given values.
+ *
+ * @param Array $values values specified from previous steps
+ * @return bool Is the current step required for a new course?
+ */
+ public function isRequired($values)
+ {
+ // is locked?
+ // Set global state in MVV_ACCESS_ASSIGN_LVGRUPPEN
+ $locked = $this->is_locked($values);
+
+ $coursetype = 1;
+ foreach ($values as $class)
+ {
+ if ($class['coursetype'])
+ {
+ $coursetype = $class['coursetype'];
+ break;
+ }
+ }
+ $category = SeminarCategories::GetByTypeId($coursetype);
+
+ return (!$locked && $category->module);
+ }
+
+ public function is_locked($values)
+ {
+ global $perm;
+
+ // Has user access to this function? Access state is configured in global config.
+ $access_right = Config::get()->MVV_ACCESS_ASSIGN_LVGRUPPEN;
+
+ // the id of the home institute
+ // get the institute from the first step (normally "BasicDataWizardStep")
+ $inst_id = reset($values)['institute'];
+ if ($access_right == 'fakadmin') {
+ // is fakadmin at faculty of given home institute
+ $db = DBManager::get();
+ $st = $db->prepare("SELECT a.Institut_id FROM user_inst a
+ LEFT JOIN Institute b ON (a.Institut_id = b.Institut_id AND b.Institut_id = b.fakultaets_id)
+ LEFT JOIN Institute c ON (c.Institut_id = b.Institut_id)
+ WHERE a.user_id = ? AND a.inst_perms='admin' AND NOT ISNULL(b.Institut_id)
+ AND c.Institut_id = ? LIMIT 1");
+ $st->execute([$GLOBALS['user']->id, $inst_id]);
+ return !((bool) $st->fetchColumn());
+ }
+ return !$perm->have_studip_perm($access_right, $inst_id);
+
+ }
+
+ /**
+ * Copy values for study areas wizard step from given course.
+ * @param Course $course
+ * @param Array $values
+ */
+ public function copy($course, $values)
+ {
+ $data = [];
+ $selection = new StudipLvgruppeSelection($course->id);
+ foreach ($selection->getAreas() as $a) {
+ /*
+ * Check if areas assigned to given course are
+ * still assignable.
+ */
+ if ($a->isAssignable()) {
+ $data[] = $a->id;
+ }
+ }
+
+ $values[__CLASS__]['lvgruppe_selection']['areas'] = $data;
+ return $values;
+ }
+
+}
diff --git a/lib/classes/coursewizardsteps/StudyAreasLVGroupsCombinedWizardStep.php b/lib/classes/coursewizardsteps/StudyAreasLVGroupsCombinedWizardStep.php
new file mode 100644
index 0000000..3bb02f5
--- /dev/null
+++ b/lib/classes/coursewizardsteps/StudyAreasLVGroupsCombinedWizardStep.php
@@ -0,0 +1,103 @@
+<?php
+/**
+ * StudyAreasWizardStep.php
+ * Course wizard step for assigning study areas.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * @author Thomas Hackl <thomas.hackl@uni-passau.de>
+ * @author Peter Thienel <thienel@data-quest.de>
+ * @copyright 2015 Stud.IP Core-Group
+ * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2
+ * @category Stud.IP
+ */
+
+class StudyAreasLVGroupsCombinedWizardStep extends StudyAreasWizardStep
+{
+
+ /**
+ * Validates if given values are sufficient for completing the current
+ * course wizard step and switch to another one. If not, all errors are
+ * collected and shown via PageLayout::postMessage.
+ *
+ * @param mixed $values Array of stored values
+ * @return bool Everything ok?
+ */
+ public function validate($values)
+ {
+ $ok = true;
+ $errors = [];
+
+ $coursetype = 1;
+ foreach ($values[get_class($this)] as $class) {
+ if (!empty($class['coursetype'])) {
+ $coursetype = $class['coursetype'];
+ break;
+ }
+ }
+ $category = SeminarCategories::GetByTypeId($coursetype);
+ if ($category->module) {
+ if (isset($values['LVGroupsWizardStep'])
+ && !count($values['LVGroupsWizardStep']['lvgruppe_selection']['areas'])
+ && !count($values[get_class($this)]['studyareas'])
+ && $values[get_class($this)]['step'] > $values['LVGroupsWizardStep']['step']) {
+ $ok = false;
+ $errors[] = _('Die Veranstaltung muss mindestens einem Studienbereich oder einer LV-Gruppe zugeordnet sein.');
+ }
+ } else {
+ if (!$values[get_class($this)]['studyareas']) {
+ $ok = false;
+ $errors[] = _('Die Veranstaltung muss mindestens einem Studienbereich zugeordnet sein.');
+ }
+ }
+ if ($errors) {
+ PageLayout::postMessage(MessageBox::error(
+ _('Bitte beheben Sie erst folgende Fehler, bevor Sie fortfahren:'), $errors));
+ }
+ return $ok;
+ }
+
+ /**
+ * Stores the given values to the given course.
+ *
+ * @param Course $course the course to store values for
+ * @param Array $values values to set
+ * @return Course The course object with updated values.
+ */
+ public function storeValues($course, $values)
+ {
+ // get courstype configuration to check
+ // whether assignment of lv-gruppen is possible
+ $coursetype = 1;
+ foreach ($values as $class) {
+ if (!empty($class['coursetype'])) {
+ $coursetype = $class['coursetype'];
+ break;
+ }
+ }
+ $category = SeminarCategories::GetByTypeId($coursetype);
+ $areas_required = true;
+ if ($category->module
+ && $values['LVGroupsWizardStep']['lvgruppe_selection']['areas'])
+ {
+ $areas_required = false;
+ }
+ if ($areas_required
+ || ($values[get_class($this)]['studyareas'] && is_array($values[__CLASS__]['studyareas'])))
+ {
+ $course->study_areas = SimpleORMapCollection::createFromArray(
+ StudipStudyArea::findMany($values[get_class($this)]['studyareas'])
+ );
+ if ($course->store()) {
+ return $course;
+ } else {
+ return false;
+ }
+ }
+ return $course;
+ }
+
+}
diff --git a/lib/classes/coursewizardsteps/StudyAreasWizardStep.php b/lib/classes/coursewizardsteps/StudyAreasWizardStep.php
new file mode 100644
index 0000000..054f638
--- /dev/null
+++ b/lib/classes/coursewizardsteps/StudyAreasWizardStep.php
@@ -0,0 +1,289 @@
+<?php
+/**
+ * StudyAreasWizardStep.php
+ * Course wizard step for assigning study areas.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * @author Thomas Hackl <thomas.hackl@uni-passau.de>
+ * @copyright 2015 Stud.IP Core-Group
+ * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2
+ * @category Stud.IP
+ */
+
+class StudyAreasWizardStep implements CourseWizardStep
+{
+ /**
+ * Returns the Flexi template for entering the necessary values
+ * for this step.
+ *
+ * @param Array $values Pre-set values
+ * @param int $stepnumber which number has the current step in the wizard?
+ * @param String $temp_id temporary ID for wizard workflow
+ * @return String a Flexi template for getting needed data.
+ */
+ public function getStepTemplate($values, $stepnumber, $temp_id)
+ {
+ // We only need our own stored values here.
+ $values = $values[get_class($this)];
+ // Load template from step template directory.
+ $factory = new Flexi_TemplateFactory($GLOBALS['STUDIP_BASE_PATH'].'/app/views/course/wizard/steps');
+ $tpl = $factory->open('studyareas/index');
+ if ($values['studyareas'])
+ {
+ $tree = $this->buildPartialSemTree(StudipStudyArea::backwards(StudipStudyArea::findMany($values['studyareas'])));
+ $tpl->set_attribute('assigned', $tree);
+ } else {
+ $tpl->set_attribute('assigned', []);
+ }
+ $tpl->set_attribute('values', $values);
+
+ // First tree level is always shown.
+ $tree = StudipStudyArea::findByParent(StudipStudyArea::ROOT);
+
+ if (count($tree) == 0) {
+ PageLayout::postError(formatReady(_('Das Anlegen einer ' .
+ 'Veranstaltung ist nicht möglich, da keine Studienbereiche ' .
+ 'existieren. Bitte wenden Sie sich an [die ' .
+ 'Stud.IP-Administration]' .
+ URLHelper::getLink('dispatch.php/siteinfo/show') . ' .')));
+ return false;
+ }
+
+ /*
+ * Someone works without JS activated, load all ancestors and
+ * children of open node.
+ */
+ if ($values['open_node']) {
+ $tpl->set_attribute('open_nodes',
+ $this->buildPartialSemTree(
+ StudipStudyArea::backwards(
+ StudipStudyArea::findByParent(
+ $values['open_node'])), true));
+ }
+ /*
+ * Someone works without JS and has entered a search term:
+ * build the partial tree with search results.
+ */
+ if ($values['searchterm']) {
+ $search = $this->searchSemTree($values['searchterm'], true);
+ if ($search) {
+ $tpl->set_attribute('open_nodes', $search);
+ $tpl->set_attribute('search_result', $search);
+ unset($values['open_node']);
+ } else {
+ PageLayout::postMessage(MessageBox::info(_('Es wurde kein Suchergebnis gefunden.')));
+ unset($values['searchterm']);
+ }
+ }
+ $tpl->set_attribute('tree', $tree);
+ $tpl->set_attribute('ajax_url', $values['ajax_url'] ?: URLHelper::getLink('dispatch.php/course/wizard/ajax'));
+ $tpl->set_attribute('no_js_url', $values['no_js_url'] ? : 'dispatch.php/course/wizard/forward/'.$stepnumber.'/'.$temp_id);
+ $tpl->set_attribute('stepnumber', $stepnumber);
+ $tpl->set_attribute('temp_id', $temp_id);
+ return $tpl->render();
+ }
+
+ /**
+ * Catch form submits other than "previous" and "next" and handle the
+ * given values. This is only important for no-JS situations.
+ * @param Array $values currently set values for the wizard.
+ * @return bool
+ */
+ public function alterValues($values)
+ {
+ // We only need our own stored values here.
+ $values = $values[get_class($this)];
+ // A node has been clicked in order to open the subtree.
+ if (Request::option('open_node')) {
+ $values['open_node'] = Request::get('open_node');
+ }
+ // Assign a node to the course.
+ if ($assign = array_keys(Request::getArray('assign'))) {
+ if ($values['studyareas']) {
+ $values['studyareas'][] = $assign[0];
+ } else {
+ $values['studyareas'] = $assign;
+ }
+ }
+ // Unassign an assigned node.
+ if ($unassign = array_keys(Request::getArray('unassign'))) {
+ $unassign = $unassign[0];
+ // Use array_filter to remove the given entry from assigned nodes.
+ $values['studyareas'] = array_filter(
+ $values['studyareas'],
+ function ($e) use ($unassign) { return $e != $unassign; }
+ );
+ }
+ // Search for a given term in the semtree.
+ if (Request::submitted('start_search')) {
+ $values['searchterm'] = Request::get('search');
+ unset($values['open_node']);
+ }
+ // Reset search -> normal semtree view.
+ if (Request::submitted('reset_search')) {
+ unset($values['searchterm']);
+ }
+ return $values;
+ }
+
+ /**
+ * Validates if given values are sufficient for completing the current
+ * course wizard step and switch to another one. If not, all errors are
+ * collected and shown via PageLayout::postMessage.
+ *
+ * @param mixed $values Array of stored values
+ * @return bool Everything ok?
+ */
+ public function validate($values)
+ {
+ // We only need our own stored values here.
+ $values = $values[get_class($this)];
+ $ok = true;
+ $errors = [];
+ if (!$values['studyareas']) {
+ $ok = false;
+ $errors[] = _('Die Veranstaltung muss mindestens einem Studienbereich zugeordnet sein.');
+ }
+ if ($errors) {
+ PageLayout::postMessage(MessageBox::error(
+ _('Bitte beheben Sie erst folgende Fehler, bevor Sie fortfahren:'), $errors));
+ }
+ return $ok;
+ }
+
+ /**
+ * Stores the given values to the given course.
+ *
+ * @param Course $course the course to store values for
+ * @param Array $values values to set
+ * @return Course The course object with updated values.
+ */
+ public function storeValues($course, $values)
+ {
+ // We only need our own stored values here.
+ $values = $values[get_class($this)];
+ $course->study_areas = SimpleORMapCollection::createFromArray(
+ StudipStudyArea::findMany($values['studyareas']));
+ if ($course->store()) {
+ return $course;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Checks if the current step needs to be executed according
+ * to already given values. A good example are study areas which
+ * are only needed for certain sem_classes.
+ *
+ * @param Array $values values specified from previous steps
+ * @return bool Is the current step required for a new course?
+ */
+ public function isRequired($values)
+ {
+ $coursetype = 1;
+ foreach ($values as $class)
+ {
+ if ($class['coursetype'])
+ {
+ $coursetype = $class['coursetype'];
+ break;
+ }
+ }
+ $category = SeminarCategories::GetByTypeId($coursetype);
+ return $category->bereiche;
+ }
+
+ /**
+ * Copy values for study areas wizard step from given course.
+ * @param Course $course
+ * @param Array $values
+ */
+ public function copy($course, $values)
+ {
+ $data = [];
+ foreach ($course->study_areas as $a) {
+ /*
+ * Check if areas assigned to given course are
+ * still assignable.
+ */
+ if ($a->isAssignable()) {
+ $data['studyareas'][] = $a->id;
+ }
+ }
+ $values[get_class($this)] = $data;
+ return $values;
+ }
+
+ public function getSemTreeLevel($parentId)
+ {
+ $level = [];
+ $children = StudipStudyArea::findByParent($parentId);
+ foreach ($children as $c) {
+ if (!$c->isHidden()) {
+ $level[] = [
+ 'id' => $c->sem_tree_id,
+ 'name' => (string) $c->getName(),
+ 'has_children' => $c->hasChildren(),
+ 'parent' => $parentId,
+ 'assignable' => $c->isAssignable()
+ ];
+ }
+ }
+ if (Request::isXhr()) {
+ return json_encode($level);
+ } else {
+ return $level;
+ }
+ }
+
+ public function searchSemTree($searchterm, $id_only=false)
+ {
+ $result = [];
+ $search = StudipStudyArea::search($searchterm);
+ $search = array_filter($search, function($a) { return !$a->isHidden(); });
+ $root = StudipStudyArea::backwards($search);
+ $result = $this->buildPartialSemTree($root, $id_only);
+ if ($id_only) {
+ return $result;
+ } else {
+ return json_encode($result);
+ }
+ }
+
+ public function getAncestorTree($id)
+ {
+ $result = [];
+ $node = StudipStudyArea::find($id);
+ $root = StudipStudyArea::backwards([$node]);
+ $result = $this->buildPartialSemTree($root);
+ return json_encode($result);
+ }
+
+ protected function buildPartialSemTree($node, $id_only=false) {
+ $children = [];
+ foreach ($node->required_children as $c)
+ {
+ if ($id_only) {
+ $children[] = $c->id;
+ $children = array_merge($children, $this->buildPartialSemTree($c, $id_only));
+ } else {
+ $data = [
+ 'id' => $c->id,
+ 'name' => (string)$c->name,
+ 'has_children' => $c->hasChildren(),
+ 'parent' => $node->id,
+ 'assignable' => $c->isAssignable(),
+ 'children' => $this->buildPartialSemTree($c)
+ ];
+ $children[] = $data;
+ }
+ }
+ return $children;
+ }
+
+}