diff --git a/application/config/Events.php b/application/config/Events.php index f9f8d5b3d..eb4baf114 100644 --- a/application/config/Events.php +++ b/application/config/Events.php @@ -23,6 +23,15 @@ Events::on('loadRenderers', function ($renderers) { ); }); +Events::on('loadRenderers', function ($renderers) { + $fhc_core_renderers =& $renderers(); + $fhc_core_renderers["slot_room"] = array( + 'modalTitle' => APP_ROOT.'public/js/components/Cis/Renderer/Slot/roomModalTitle.js', + 'modalContent' => APP_ROOT.'public/js/components/Cis/Renderer/Slot/roomModalContent.js', + 'calendarEventStyles' => APP_ROOT.'public/css/Cis4/CoreCalendarEvents.css' + ); +}); + Events::on('loadRenderers', function ($renderers) { $fhc_core_renderers =& $renderers(); $fhc_core_renderers["ferien"] = array( diff --git a/application/config/noten.php b/application/config/noten.php new file mode 100644 index 000000000..81f467a13 --- /dev/null +++ b/application/config/noten.php @@ -0,0 +1,64 @@ + 1]; // über alle Antritte hinweg nur ein entschuldigt + +// tbl_note pk of the 'entschuldigt' note. An entschuldigt Termin is preserved as its own dated +// entry when a new pruefung of the same type is created (instead of being overwritten). +$config['NOTE_ENTSCHULDIGT'] = 17; + +// The maximum number of attempts that count, the first attempt and the kommissionelle attempt +// included. null derives the number from the old flags: +// 1 (the original assessment) + TERMIN2 + TERMIN3 + KOMMPRUEF +// The examination rules permit three attempts. With TERMIN2 and KOMMPRUEF the formula gives 3. +// An installation with TERMIN3 gets 4, which is one more attempt in the same chain. +$config['CIS_GESAMTNOTE_MAX_ANTRITTE'] = null; + +// The exam types that take place before a commission. +$config['PRUEFUNG_KOMMISSIONELL_TYPEN'] = ['kommPruef', 'zusKommPruef']; + +// The type that the tool writes for the last attempt. The last attempt is always kommissionell. +$config['PRUEFUNG_TYP_KOMMISSIONELL'] = 'kommPruef'; + +// Exam types that never use an attempt. A zusKommPruef repeats a kommissionelle Prüfung that had +// a procedural fault. It stands outside the attempt chain, and the student administration enters +// it. The examination rules do not describe it. +$config['PRUEFUNG_TYPEN_OHNE_ANTRITT'] = ['zusKommPruef']; + +// Spaltenaufteilung der Prüfungen in der Notentabelle: +// 'antritt' - eine Spalte je Antrittsnummer, Datum steht in der Zelle (robust bei Einzelterminen) +// 'datum' - eine Spalte je Prüfungsdatum (kompakt, wenn ganze Jahrgänge am selben Tag antreten) +// Nur die Vorgabe; im Tool umschaltbar und pro Benutzer gespeichert. +$config['CIS_GESAMTNOTE_PRUEFUNGSSPALTEN'] = 'antritt'; + +// Bei der Notenübernahme durch die Assistenz den ersten Antritt als eigene Prüfung anlegen. +$config['CIS_GESAMTNOTE_ERSTANTRITT_BEI_UEBERNAHME'] = true; + +// availability of the two Benotungstool import flows. When both are true they are shown as +// separate buttons/dialogs. +$config['CIS_GESAMTNOTE_PRUEFUNGSIMPORT'] = true; // dated import that creates a pruefung per row +$config['CIS_GESAMTNOTE_NOTENIMPORT'] = false; // classic note-only import (uid + note, no date) + +// Noteneintragungsfrist (Prüfungsordnung §1): grade/pruefung entry is only permitted up to this +// deadline. The month/day below is applied to the studiensemester's year: +// Sommersemester (SSyyyy) -> deadline in the SAME calendar year (default 15th November) +// Wintersemester (WSyyyy) -> deadline in the FOLLOWING calendar year (default 15th May) +$config['CIS_GESAMTNOTE_NOTENEINTRAGUNGSFRIST'] = false; // switch to use the window enforcement +$config['NOTENEINTRAGUNGSFRIST_SS'] = ['month' => 11, 'day' => 15]; // Sommersemester deadline (same year) +$config['NOTENEINTRAGUNGSFRIST_WS'] = ['month' => 5, 'day' => 15]; // Wintersemester deadline (following year) \ No newline at end of file diff --git a/application/config/routes.php b/application/config/routes.php index 79e15edd9..b94935f4f 100644 --- a/application/config/routes.php +++ b/application/config/routes.php @@ -61,12 +61,29 @@ $route['api/v1/organisation/[O|o]rganisationseinheit/(:any)'] = 'api/v1/organisa $route['api/v1/ressource/[B|b]etriebsmittelperson/(:any)'] = 'api/v1/ressource/betriebsmittelperson2/$1'; $route['api/v1/system/[S|s]prache/(:any)'] = 'api/v1/system/sprache2/$1'; +// Legacy CMS news routes +$route['api/frontend/v1/Cms/getNewsItem/(:num)'] = 'api/frontend/v1/NewsAdministrationAPI/getNewsItem/$1'; +$route['api/frontend/v1/Cms/storeNewsItem'] = 'api/frontend/v1/NewsAdministrationAPI/storeNewsItem'; +$route['api/frontend/v1/Cms/updateNewsItem/(:num)'] = 'api/frontend/v1/NewsAdministrationAPI/updateNewsItem/$1'; +$route['api/frontend/v1/Cms/deleteNewsItem/(:num)'] = 'api/frontend/v1/NewsAdministrationAPI/deleteNewsItem/$1'; + +// Keep legacy uppercase URLs refreshable while using the canonical controller method casing +$route['CisVue/Cms/NewsAdministration'] = 'CisVue/Cms/newsAdministration'; + $route['Cis/LvPlan/.*'] = 'Cis/LvPlan/index/$1'; $route['Cis/MyLvPlan/.*'] = 'Cis/MyLvPlan/index/$1'; $route['Cis/MyLv/.*'] = 'Cis/MyLv/index/$1'; $route['Cis/OtherLvPlan/.*'] = 'Cis/OtherLvPlan/index/$1'; //Route for LV Plan Stg/Semester/Verband/Gruppe $route['Cis/StgOrgLvPlan/.*'] = 'Cis/StgOrgLvPlan/index/$1'; +$route['Cis/Benotungstool/.*'] = 'Cis/Benotungstool/index/$1'; + +$route['Cis/MyReservation'] = 'Cis/MyReservation/index/$1'; + +//Route for Zeitsperrenma +$route['Cis/Zeitsperrenma/.*'] = 'Cis/Zeitsperrenma/index/$1'; +//Route für Zeitsperren +$route['Cis/Zeitsperren/.*'] = 'Cis/Zeitsperren/index/$1'; $route['Abgabetool/Assistenz'] = 'Cis/Abgabetool/Assistenz'; $route['Abgabetool/Assistenz/(:any)'] = 'Cis/Abgabetool/Assistenz/$1'; diff --git a/application/controllers/Cis/Benotungstool.php b/application/controllers/Cis/Benotungstool.php new file mode 100644 index 000000000..fe01f3595 --- /dev/null +++ b/application/controllers/Cis/Benotungstool.php @@ -0,0 +1,37 @@ + array('lehre/benotungstool:rw', 'lehre/benotungstool_assistenz:rw') + ]); + + $this->_ci =& get_instance(); + } + + // ----------------------------------------------------------------------------------------------------------------- + // Public methods + + /** + * @return void + */ + public function index() + { + $viewData = array( + 'uid'=>getAuthUID(), + ); + + $this->load->view('CisRouterView/CisRouterView.php', ['viewData' => $viewData, 'route' => 'Benotungstool']); + } + +} \ No newline at end of file diff --git a/application/controllers/Cis/MyReservation.php b/application/controllers/Cis/MyReservation.php new file mode 100644 index 000000000..e4045cf90 --- /dev/null +++ b/application/controllers/Cis/MyReservation.php @@ -0,0 +1,33 @@ + ['basis/cis:r'] + ]); + + // Load Config + $this->load->config('calendar'); + } + + // ----------------------------------------------------------------------------------------------------------------- + // Public methods + + /** + * @return void + */ + public function index() + { + $this->load->view('CisRouterView/CisRouterView.php', ['route' => 'MyReservation']); + } +} diff --git a/application/controllers/Cis/ProjektabgabeUebersicht.php b/application/controllers/Cis/ProjektabgabeUebersicht.php new file mode 100644 index 000000000..e7ce692f0 --- /dev/null +++ b/application/controllers/Cis/ProjektabgabeUebersicht.php @@ -0,0 +1,30 @@ + ['basis/cis:r'] + ]); + } + + // ----------------------------------------------------------------------------------------------------------------- + // Public methods + + /** + * @return void + */ + public function index() + { + $this->load->view('CisRouterView/CisRouterView.php', ['route' => 'ProjektabgabeUebersicht']); + } +} diff --git a/application/controllers/Cis/Zeitsperren.php b/application/controllers/Cis/Zeitsperren.php new file mode 100644 index 000000000..caee13c48 --- /dev/null +++ b/application/controllers/Cis/Zeitsperren.php @@ -0,0 +1,30 @@ + ['cis/eigene_zeitsperren:r'], + ]); + + // Load Libraries + $this->load->library('VariableLib', ['uid' => getAuthUID()]); + } + + /** + * index loads the view Zeitsperren + * @access public + * @return void + */ + public function index() + { + $viewData = array( + 'uid'=>getAuthUID(), + ); + + $this->load->view('CisRouterView/CisRouterView.php', ['viewData' => $viewData, 'route' => 'zeitsperren']); + } +} diff --git a/application/controllers/Cis/Zeitsperrenma.php b/application/controllers/Cis/Zeitsperrenma.php new file mode 100644 index 000000000..851291622 --- /dev/null +++ b/application/controllers/Cis/Zeitsperrenma.php @@ -0,0 +1,31 @@ + ['cis/andere_zeitsperren:r'] + ]); + + } + + // ----------------------------------------------------------------------------------------------------------------- + // Public methods + + /** + * @return void + */ + public function index() + { + $this->load->view('CisRouterView/CisRouterView.php', ['route' => 'Zeitsperrenma']); + } +} diff --git a/application/controllers/CisVue/Cms.php b/application/controllers/CisVue/Cms.php index aa07b919f..8f70e91c8 100644 --- a/application/controllers/CisVue/Cms.php +++ b/application/controllers/CisVue/Cms.php @@ -17,14 +17,15 @@ class Cms extends Auth_Controller array( 'content' => 'basis/cis:r', 'getNews' => 'basis/cis:r', - 'getNewsRowCount' => 'basis/cis:r', 'getRoomInformation' => 'basis/cis:r', - 'news' => 'basis/cis:r' + 'news' => 'basis/cis:r', + 'newsAdministration' => ['basis/news:r'], ) ); // Loads Libraries $this->load->library('CmsLib'); + $this->load->library('PermissionLib'); // Loads phrases system $this->loadPhrases([ @@ -83,10 +84,18 @@ class Cms extends Auth_Controller */ public function news($infoscreen = false, $studiengang_kz = null, $semester = null, $mischen = true, $titel = '', $edit = false, $sichtbar = true) { - $viewData = array(); - $this->load->view('CisRouterView/CisRouterView.php', ['viewData'=>$viewData, 'route' => 'News']); + $this->load->view('CisRouterView/CisRouterView.php', [ + 'route' => 'News', + ]); } + public function newsAdministration() + { + $this->load->view('CisRouterView/CisRouterView.php', [ + 'route' => 'NewsAdministration', + ]); + } + public function getRoomInformation($ort_kurzbz) { // Load Config diff --git a/application/controllers/api/frontend/v1/Abgabe.php b/application/controllers/api/frontend/v1/Abgabe.php index 759c32d58..62d29f029 100644 --- a/application/controllers/api/frontend/v1/Abgabe.php +++ b/application/controllers/api/frontend/v1/Abgabe.php @@ -1916,7 +1916,7 @@ class Abgabe extends FHCAPI_Controller if (!$mailres) { - $this->terminateWithError($this->p->t('abgabetool', 'c4fehlerMailBegutachterv2'), 'general'); + $this->terminateWithError($this->p->t('abgabetool', 'c4fehlerMailZweitBegutachterv2'), 'general'); } } diff --git a/application/controllers/api/frontend/v1/Benutzer.php b/application/controllers/api/frontend/v1/Benutzer.php new file mode 100644 index 000000000..f5dea1c7f --- /dev/null +++ b/application/controllers/api/frontend/v1/Benutzer.php @@ -0,0 +1,48 @@ +. + */ + +if (!defined('BASEPATH')) exit('No direct script access allowed'); + +class Benutzer extends FHCAPI_Controller +{ + + /** + * Object initialization + */ + public function __construct() + { + parent::__construct([ + 'getUserData' => self::PERM_LOGGED, + ]); + + $this->load->model('person/Benutzer_model', 'BenutzerModel'); + } + + //------------------------------------------------------------------------------------------------------------------ + // Public methods + + public function getUserData() + { + $uid = $this->input->get("uid") ?? getAuthUID(); + $this->BenutzerModel->addSelect("uid, person_id, insertamum"); + $userResult = $this->BenutzerModel->load(["uid" => $uid]); + $userData = $this->getDataOrTerminateWithError($userResult); + $this->terminateWithSuccess($userData); + } +} + diff --git a/application/controllers/api/frontend/v1/Cms.php b/application/controllers/api/frontend/v1/Cms.php index b8937a1f4..06b88c454 100644 --- a/application/controllers/api/frontend/v1/Cms.php +++ b/application/controllers/api/frontend/v1/Cms.php @@ -36,14 +36,15 @@ class Cms extends FHCAPI_Controller 'ContentID' => self::PERM_LOGGED, 'getOrtKurzbzContent' => self::PERM_LOGGED, 'content' => self::PERM_LOGGED, - 'news' => self::PERM_LOGGED, - 'getNewsRowCount' => self::PERM_LOGGED, + 'newsForWidget' => self::PERM_LOGGED, 'getNews' => self::PERM_LOGGED, ]); $this->load->model('content/News_model', 'NewsModel'); - + $this->load->model('crm/Student_model', 'StudentModel'); + $this->load->model('crm/Prestudent_model', 'PrestudentModel'); + // setting up the papgination_size $this->page_size = 10; @@ -59,7 +60,6 @@ class Cms extends FHCAPI_Controller //------------------------------------------------------------------------------------------------------------------ // Private methods - //------------------------------------------------------------------------------------------------------------------ // Public methods @@ -106,7 +106,7 @@ class Cms extends FHCAPI_Controller $this->terminateWithSuccess($content_id); } - public function news() + public function newsForWidget() { // form validation @@ -118,7 +118,7 @@ class Cms extends FHCAPI_Controller $this->load->model('content/news_model', 'NewsModel'); $limit = $this->input->get('limit',TRUE); - + //query the news $news = $this->NewsModel->getAll($limit); @@ -157,23 +157,7 @@ class Cms extends FHCAPI_Controller } - public function getNewsRowCount($infoscreen = false, $studiengang_kz = null, $semester = null, $mischen = true, $titel = '', $fachbereich_kurzbz = null, $maxalter = 0, $edit = false, $sichtbar = true, $page = 1, $page_size = 10) - { - list($studiengang_kz, $semester) = $this->cmslib->getStgAndSem($studiengang_kz, $semester); - $all = $edit; - - $this->load->model('content/News_model','NewsModel'); - - $num_rows = $this->NewsModel->countNewsWithContent(getSprache(), $studiengang_kz, $semester, $fachbereich_kurzbz, $sichtbar, $maxalter, $page, $page_size, $all, $mischen); - - $num_rows = $this->getDataOrTerminateWithError($num_rows); - - $this->terminateWithSuccess($num_rows); - - } - - - public function getNews($infoscreen = false, $studiengang_kz = null, $semester = null, $mischen = true, $titel = '', $edit = false, $sichtbar = true) + public function getNews($infoscreen = false, $studiengang_kz = null, $semester = null, $mischen = false, $titel = '', $edit = false, $sichtbar = true) { //form validation $this->load->library('form_validation'); @@ -193,15 +177,45 @@ class Cms extends FHCAPI_Controller // default value for the page_size is 10 $page_size = $page_size ?? 10; + + $passedSichtbar = $this->input->get('published', true); + if($passedSichtbar !== null) + { + $sichtbar = $passedSichtbar; + } - $news = $this->cmslib->getNews($infoscreen, $studiengang_kz, $semester, $mischen, $titel, $edit, $sichtbar, $page, $page_size, $sprache); + $filterForDegreePrograms = false; + $entitledDegreePrograms = []; + $student = $this->StudentModel->loadWhere(['student_uid' => getAuthUID()]); + if(isError($student)) + { + $this->terminateWithError(getError($student)); + } + if(hasData($student)) + { + $student = current(getData($student)); + + $degreeProgramSemesterDataResult = $this->PrestudentModel->getStudiengaengAndAusbildungssemesterByStudentUid(getAuthUID()); + $degreeProgramSemesterData = $this->getDataOrTerminateWithError($degreeProgramSemesterDataResult)[0]; + if ($degreeProgramSemesterData) { + $studiengang_kz = isset($degreeProgramSemesterData->studiengang_kz) ? $degreeProgramSemesterData->studiengang_kz : null; + $semester = isset($degreeProgramSemesterData->ausbildungssemester) ? $degreeProgramSemesterData->ausbildungssemester : null; + } + + $mischen = true; + } else { + $filterForDegreePrograms = true; + $entitledDegreePrograms = $this->permissionlib->getSTG_isEntitledFor("basis/news"); + array_push($entitledDegreePrograms, 0); + } + + $news = $this->cmslib->getNews($infoscreen, $studiengang_kz, $semester, $mischen, $titel, $edit, $sichtbar, $page, $page_size, $sprache, $filterForDegreePrograms, $entitledDegreePrograms, true); $news = $this->getDataOrTerminateWithError($news); - $this->addMeta('phrases', json_decode($this->p->getJson())); - $this->terminateWithSuccess($news); + $this->addMeta('row_count', $news["row_count"] ?? 0); + $this->terminateWithSuccess($news["content"]); } } - diff --git a/application/controllers/api/frontend/v1/Lehre.php b/application/controllers/api/frontend/v1/Lehre.php index c2ebeb10b..fae2f2f3b 100644 --- a/application/controllers/api/frontend/v1/Lehre.php +++ b/application/controllers/api/frontend/v1/Lehre.php @@ -18,18 +18,9 @@ if (! defined('BASEPATH')) exit('No direct script access allowed'); -//require_once('../../../include/studiengang.class.php'); -//require_once('../../../include/student.class.php'); -//require_once('../../../include/datum.class.php'); -//require_once('../../../include/mail.class.php'); -//require_once('../../../include/benutzerberechtigung.class.php'); -//require_once('../../../include/phrasen.class.php'); -//require_once('../../../include/projektarbeit.class.php'); -//require_once('../../../include/projektbetreuer.class.php'); - class Lehre extends FHCAPI_Controller { - + /** * Object initialization */ @@ -40,38 +31,56 @@ class Lehre extends FHCAPI_Controller 'LV' => self::PERM_LOGGED, 'Pruefungen' => self::PERM_LOGGED, 'semesterAverageGrade' => self::PERM_LOGGED, + 'getZugewieseneLv' => self::PERM_LOGGED, + 'getLeForLv' => self::PERM_LOGGED ]); - + + $this->load->library('PhrasesLib'); + + $this->loadPhrases( + array( + 'global', + 'ui', + 'abgabetool' + ) + ); + + $this->load->helper('hlp_sancho_helper'); + + require_once(FHCPATH . 'include/studiengang.class.php'); + require_once(FHCPATH . 'include/student.class.php'); + require_once(FHCPATH . 'include/projektarbeit.class.php'); + require_once(FHCPATH . 'include/projektbetreuer.class.php'); } //------------------------------------------------------------------------------------------------------------------ // Public methods - /** + /** * constructs the emails of the groups from a lehrveranstaltung */ - public function lvStudentenMail() + public function lvStudentenMail() { - $lehreinheit_id = $this->input->get("lehreinheit_id",TRUE); - - // return early if the required parameter is missing - if(!isset($lehreinheit_id)) - { - $this->terminateWithError('Missing required parameter', self::ERROR_TYPE_GENERAL); - } + $lehreinheit_id = $this->input->get("lehreinheit_id",TRUE); - $this->load->model('education/Lehreinheit_model', 'LehreinheitModel'); - - $studentenMails = $this->LehreinheitModel->getStudentenMail($lehreinheit_id); + // return early if the required parameter is missing + if(!isset($lehreinheit_id)) + { + $this->terminateWithError('Missing required parameter', self::ERROR_TYPE_GENERAL); + } - $studentenMails = $this->getDataOrTerminateWithError($studentenMails); + $this->load->model('education/Lehreinheit_model', 'LehreinheitModel'); + + $studentenMails = $this->LehreinheitModel->getStudentenMail($lehreinheit_id); + + $studentenMails = $this->getDataOrTerminateWithError($studentenMails); //convert array of objects into array of strings $studentenMails = array_map(function($element){ return $element->mail; }, $studentenMails); - $this->terminateWithSuccess($studentenMails); + $this->terminateWithSuccess($studentenMails); } public function LV($studiensemester_kurzbz, $lehrveranstaltung_id) @@ -81,13 +90,13 @@ class Lehre extends FHCAPI_Controller $result = $this->LehrveranstaltungModel->getLvsByStudentWithGrades(getAuthUID(), $studiensemester_kurzbz, getUserLanguage(), $lehrveranstaltung_id); $result = current($this->getDataOrTerminateWithError($result)); - + $this->terminateWithSuccess($result); } /** * fetches all Pruefungen of a student for a specific lehrveranstaltung - * if the student passed the Pruefung on the first attempt, no information about the Pruefungen is stored in the database + * if the student passed the Pruefung on the first attempt, no information about the Pruefungen is stored in the database * @param mixed $lehrveranstaltung_id * @return void */ @@ -145,5 +154,46 @@ class Lehre extends FHCAPI_Controller $this->terminateWithSuccess(['average_grade' => $averageGrade, 'weighted_average_grade' => $weightedAverageGrade]); } -} + /** + * fetches all assigned lehrveranstaltungen of a mitarbeiter for a given semester + * @param mixed $uid + * @param mixed $sem_kurzbz + * @return void + */ + public function getZugewieseneLv() { + $uid = $this->input->get("uid",TRUE); + $sem_kurzbz = $this->input->get("sem_kurzbz",TRUE); + + // TODO: error messages + + if(!isset($sem_kurzbz) || isEmptyString($sem_kurzbz)) + $this->terminateWithError($this->p->t('global', 'wrongParameters'), 'general'); + + if (!isset($uid) || isEmptyString($uid)) + $uid = getAuthUID(); + + // querying other ma_uids data requires admin permission + if($uid !== getAuthUID()) { + $this->load->library('PermissionLib'); + $isAdmin = $this->permissionlib->isBerechtigt('admin'); + if(!$isAdmin) $this->terminateWithError($this->p->t('ui', 'keineBerechtigung'), 'general'); + } + + $this->load->model('education/Lehrveranstaltung_model', 'LehrveranstaltungModel'); + $result = $this->LehrveranstaltungModel->getLvForLektorInSemester($sem_kurzbz, $uid); + $data = $this->getDataOrTerminateWithError($result); + $this->terminateWithSuccess($data); + } + + public function getLeForLv() { + $lv_id = $this->input->get("lv_id",TRUE); + $sem_kurzbz = $this->input->get("sem_kurzbz",TRUE); + + $this->load->model('education/Lehreinheit_model', 'LehreinheitModel'); + +// $this->terminateWithSuccess($this->LehreinheitModel->getLesForLv($lv_id, $sem_kurzbz)); + $this->terminateWithSuccess($this->LehreinheitModel->getAllLehreinheitenForLvaAndMaUid($lv_id, getAuthUID(), $sem_kurzbz)); + } + +} diff --git a/application/controllers/api/frontend/v1/LvMenu.php b/application/controllers/api/frontend/v1/LvMenu.php index 45936d9f5..311edb1d9 100644 --- a/application/controllers/api/frontend/v1/LvMenu.php +++ b/application/controllers/api/frontend/v1/LvMenu.php @@ -36,7 +36,8 @@ class LvMenu extends FHCAPI_Controller public function __construct() { parent::__construct([ - 'getLvMenu' => self::PERM_LOGGED + 'getLvMenu' => self::PERM_LOGGED, + 'getMultipleLvMenu' => self::PERM_LOGGED ]); $this->load->model("ressource/Mitarbeiter_model"); @@ -61,24 +62,23 @@ class LvMenu extends FHCAPI_Controller /** * alternative function to get multiple lvMenus with a single http request + * not yet working as intended as the menu_lv.inc.php scripts called by the + * lvMenuBuild event have logic coupled to require_once import which results in + * a wrong logic after the first invocation -> faulty results for lvinfo, moodle + * and several others */ - public function getMultipleLvMenu($lvMenuOptionList){ + public function getMultipleLvMenu(){ + $lvMenuOptionList = $this->input->post('lvMenuOptionList', true); $result =[]; foreach($lvMenuOptionList as $lvMenuOptions){ - $lvMenu = $this->getLvMenu($lvMenuOptions['lvid'],$lvMenuOptions['studiensemester_kurzbz']); - if(isError($lvMenu)){ - // TODO: some lvMenu threw an error, handle error here - } + $lvMenu = $this->getLvMenuInternal($lvMenuOptions['lvid'],$lvMenuOptions['studiensemester_kurzbz']); + $result[$lvMenuOptions['lvid']]=$lvMenu; } $this->terminateWithSuccess($result); } - - /** - * - */ - public function getLvMenu($lvid, $studiensemester_kurzbz) - { + + private function getLvMenuInternal($lvid, $studiensemester_kurzbz) { // return early if parameters are missing if(!isset($lvid) || !isset($studiensemester_kurzbz)) @@ -89,14 +89,14 @@ class LvMenu extends FHCAPI_Controller // get the user if (!$user=getAuthUID()) - $this->terminateWithError($this->p->t('global', 'nichtAngemeldet')); + $this->terminateWithError($this->p->t('global', 'nichtAngemeldet')); // check if is_lector $is_lector = false; $mares = $this->Mitarbeiter_model->isMitarbeiter($user); if(hasData($mares)) { - $is_lector = getData($mares); + $is_lector = getData($mares); } // definition of user_is_allowed_to_upload @@ -105,7 +105,7 @@ class LvMenu extends FHCAPI_Controller // load lehrveranstaltung $lvres = $this->Lehrveranstaltung_model->load($lvid); - if(!hasData($lvres)) + if(!hasData($lvres)) { $this->terminateWithError('LV ' . $lvid . ' not found.'); } @@ -124,7 +124,7 @@ class LvMenu extends FHCAPI_Controller $stgres = $this->Studiengang_model->load(strval($studiengang_kz)); if(!hasData($stgres)) { - $this->terminateWithError('Stg ' . $lv->studiengang_kz . ' not found.'); + $this->terminateWithError('Stg ' . $lv->studiengang_kz . ' not found.'); } $stg = (getData($stgres))[0]; $kurzbz = strtoupper($stg->typ . $stg->kurzbz); @@ -139,7 +139,7 @@ class LvMenu extends FHCAPI_Controller $angemeldet = false; $lesres = $this->Lehreinheit_model->getLehreinheitenForStudentAndStudienSemester( - $lvid, $user, $angezeigtes_stsem + $lvid, $user, $angezeigtes_stsem ); if(hasData($lesres) && count(getData($lesres)) > 0) @@ -148,7 +148,7 @@ class LvMenu extends FHCAPI_Controller // lehrfach $lehrfach_id=''; - + if(defined('CIS_LEHRVERANSTALTUNG_LEHRFACH_ANZEIGEN') && CIS_LEHRVERANSTALTUNG_LEHRFACH_ANZEIGEN) { // Wenn der eingeloggte User zu einer der Lehreinheiten zugeteilt ist @@ -211,8 +211,8 @@ class LvMenu extends FHCAPI_Controller foreach($fbs as $row) { $lehrfach_oe_kurzbz_arr[] = $row->oe_kurzbz; - if($this->PermissionLib->isBerechtigt('lehre', null, $row->oe_kurzbz) - || $this->PermissionLib->isBerechtigt('assistenz', null, $stg->oe_kurzbz)) + if($this->PermissionLib->isBerechtigt('lehre', null, $row->oe_kurzbz) + || $this->PermissionLib->isBerechtigt('assistenz', null, $stg->oe_kurzbz)) { $user_is_allowed_to_upload=true; } @@ -224,21 +224,21 @@ class LvMenu extends FHCAPI_Controller $menu = array(); $this->fhc_menu_lvinfo($menu, $lvid, $studiengang_kz, $lektor_der_lv, $is_lector, $lehrfach_oe_kurzbz_arr); - + $this->fhc_menu_feedback($menu, $angemeldet, $lvid); - + $this->fhc_menu_gesamtnote($menu, $angemeldet, $lvid, $lv, $is_lector, $angezeigtes_stsem); - + $this->fhc_menu_emailStudierende($menu, $user, $angemeldet, $lvid, $angezeigtes_stsem); - + $this->fhc_menu_abmeldung($menu, $user, $is_lector, $lvid, $angezeigtes_stsem); - + $this->fhc_menu_lehretools($menu, $lvid, $angezeigtes_stsem, $sprache); - + $this->fhc_menu_anrechnungStudent($menu, $lvid, $angezeigtes_stsem); - + $this->fhc_menu_anrechnungLector($menu, $angezeigtes_stsem); - + // Addons Menu Logic // ########################################################################################## @@ -272,18 +272,18 @@ class LvMenu extends FHCAPI_Controller 'permissionLib' => &$this->PermissionLib, 'phrasesLib' => &$this->PhrasesLib ]; - - Events::trigger('lvMenuBuild', - // passing $menu per reference - function & () use (&$menu) { - return $menu; - }, - $params + + Events::trigger('lvMenuBuild', + // passing $menu per reference + function & () use (&$menu) { + return $menu; + }, + $params ); // Menu sortieren // ########################################################################################## - + foreach ($menu as $key => $row){ // removes menu points that are not needed in the c4 lvUebersicht @@ -291,7 +291,7 @@ class LvMenu extends FHCAPI_Controller unset($menu[$key]); continue; } - + // fills pos array to sort the menu $pos[$key] = $row['position']; @@ -299,11 +299,18 @@ class LvMenu extends FHCAPI_Controller array_multisort($pos, SORT_ASC, SORT_NUMERIC, $menu); - // HTTP response - // ########################################################################################## + + return $menu; + } + + /** + * + */ + public function getLvMenu($lvid, $studiensemester_kurzbz) + { + $menu = $this->getLvMenuInternal($lvid, $studiensemester_kurzbz); $this->terminateWithSuccess($menu); - } private function fhc_menu_lvinfo(&$menu, $lvid, $studiengang_kz, $lektor_der_lv, $is_lector, $lehrfach_oe_kurzbz_arr){ @@ -372,7 +379,9 @@ class LvMenu extends FHCAPI_Controller 'name'=>$this->p->t('lehre', 'gesamtnote'), 'phrase' => 'lehre/gesamtnote', 'c4_icon'=> base_url('skin/images/button_endnote.png'), - 'c4_link'=> base_url('cis/private/lehre/benotungstool/lvgesamtnoteverwalten.php?lvid='.urlencode($lvid).'&stsem='.urlencode($angezeigtes_stsem)) + 'c4_link'=> base_url('cis.php/Cis/Compat/legacy/cis/private/lehre/benotungstool/lvgesamtnoteverwalten.php?lvid='.urlencode($lvid).'&stsem='.urlencode($angezeigtes_stsem)) + //'c4_link'=> site_url('Cis/Benotungstool/'.urlencode($lvid).'/'.urlencode($angezeigtes_stsem)) + //'c4_link'=> base_url('cis/private/lehre/benotungstool/lvgesamtnoteverwalten.php?lvid='.urlencode($lvid).'&stsem='.urlencode($angezeigtes_stsem)) //'c4_link'=> base_url('benotungstool/lvgesamtnoteverwalten.php?lvid='.urlencode($lvid).'&stsem='.urlencode($angezeigtes_stsem)) ); } @@ -541,4 +550,4 @@ class LvMenu extends FHCAPI_Controller ); } } -} \ No newline at end of file +} diff --git a/application/controllers/api/frontend/v1/MaZeitsperren.php b/application/controllers/api/frontend/v1/MaZeitsperren.php new file mode 100644 index 000000000..0045b15e8 --- /dev/null +++ b/application/controllers/api/frontend/v1/MaZeitsperren.php @@ -0,0 +1,167 @@ + ['cis/andere_zeitsperren:r', 'admin:r', 'assistenz:r'], + 'getAllZeitsperrenFixeMa' => ['cis/andere_zeitsperren:r', 'admin:r', 'assistenz:r'], + 'getAllZeitsperrenLector' => ['cis/andere_zeitsperren:r', 'admin:r', 'assistenz:r'], + 'getAllZeitsperrenOes' => ['cis/andere_zeitsperren:r', 'admin:r', 'assistenz:r'], + 'getZeitsperrenAss' => ['cis/andere_zeitsperren:r', 'admin:r', 'assistenz:r'], + 'getStgLectors' => self::PERM_LOGGED, + 'loadZeitsperrenLectorStg' => ['cis/andere_zeitsperren:r', 'admin:r', 'assistenz:r'], + 'loadZeitsperrenMa' => ['cis/andere_zeitsperren:r', 'admin:r', 'assistenz:r'], + 'getDetailsMa' => self::PERM_LOGGED + ]); + + // Load Libraries + $this->load->library('VariableLib', ['uid' => getAuthUID()]); + $this->load->library('form_validation'); + + // Load language phrases + $this->loadPhrases([ + 'ui', + 'person', + 'zeitsperren' + ]); + + // Load models + $this->load->model('ressource/Zeitsperre_model', 'ZeitsperreModel'); + } + + //only user with zeitsperren + public function getAllActiveZeitsperren($days) + { + $result = $this->ZeitsperreModel->getZeitsperrenForNextDays($days); + $data = $this->getDataOrTerminateWithError($result); + + $this->terminateWithSuccess($data); + } + + //TODO: check query: more results after join with hr-table + //all fixe Ma + public function getAllZeitsperrenFixeMa($days) + { + $von = date('Y-m-d'); + $bis = date('Y-m-d', strtotime("+{$days} days")); + $result = $this->ZeitsperreModel->getMitarbeiterWithZeitsperren($von, $bis, true, false, null, false); + $data = $this->getDataOrTerminateWithError($result); + + $this->terminateWithSuccess($data); + } + + public function getAllZeitsperrenLector($days) + { + $von = date('Y-m-d'); + $bis = date('Y-m-d', strtotime("+{$days} days")); + $result = $this->ZeitsperreModel->getMitarbeiterWithZeitsperren($von, $bis, false, true, null, false); + + $data = $this->getDataOrTerminateWithError($result); + + $this->terminateWithSuccess($data); + } + + public function getZeitsperrenAss($days) + { + $von = date('Y-m-d'); + $bis = date('Y-m-d', strtotime("+{$days} days")); + $result = $this->ZeitsperreModel->getMitarbeiterWithZeitsperren($von, $bis, false, false, null, true); + + $data = $this->getDataOrTerminateWithError($result); + + $this->terminateWithSuccess($data); + } + + public function getAllZeitsperrenOes($days, $oe) + { + if(!$oe) + { + return $this->terminateWithError($this->p->t('ui', 'error_missingId', ['id'=> 'OE Kurzbezeichnung']), self::ERROR_TYPE_GENERAL); + } + $von = date('Y-m-d'); + $bis = date('Y-m-d', strtotime("+{$days} days")); + $result = $this->ZeitsperreModel->getMitarbeiterWithZeitsperren($von, $bis, false, false, $oe, false); + + $data = $this->getDataOrTerminateWithError($result); + + $this->terminateWithSuccess($data); + } + + public function loadZeitsperrenLectorStg($days, $stg) + { + if(!$stg) + { + return $this->terminateWithError($this->p->t('ui', 'error_missingId', ['id'=> 'Studiengangskennzahl']), self::ERROR_TYPE_GENERAL); + } + if(!is_numeric($stg)) + { + return $this->terminateWithError($this->p->t('ui', 'error_paramNoNumber', ['param'=> 'Studiengangskennzahl']), self::ERROR_TYPE_GENERAL); + } + + $von = date('Y-m-d'); + $bis = date('Y-m-d', strtotime("+{$days} days")); + $result = $this->ZeitsperreModel->getMitarbeiterWithZeitsperren($von, $bis, false, true, false, false, $stg); + + $data = $this->getDataOrTerminateWithError($result); + + $this->terminateWithSuccess($data); + } + + public function getStgLectors() + { + $sql = " + select + studiengang_kz, + kurzbz, + kurzbzlang, + typ, + bezeichnung, + kurzbzlang || ' (' || bezeichnung || ')' AS label, + aktiv + from + public.tbl_studiengang + where + typ in ('b','m') + order by typ asc, kurzbz Asc + "; + + $this->load->model('organisation/Studiengang_model', 'StudiengangModel'); + $result = $this->StudiengangModel->execReadOnlyQuery($sql); + $data = $this->getDataOrTerminateWithError($result); + + $this->terminateWithSuccess($data); + } + + public function loadZeitsperrenMa($days, $uid) + { + if ($uid === null || $uid === '') + { + return $this->terminateWithError($this->p->t('ui', 'error_missingId', ['id'=> 'Mitarbeiter UID']), self::ERROR_TYPE_GENERAL); + } + $result = $this->ZeitsperreModel->getZeitsperrenForNextDays($days, $uid); + + $data = $this->getDataOrTerminateWithError($result); + + $this->terminateWithSuccess($data); + } + + public function getDetailsMa($uid) + { + if ($uid === null || $uid === '') + { + $this->terminateWithError($this->p->t('ui', 'error_missingId', ['id'=> 'Mitarbeiter UID']), self::ERROR_TYPE_GENERAL); + } + $this->load->model('person/Person_model', 'PersonModel'); + $result = $this->PersonModel->getFullName($uid); + + $data = $this->getDataOrTerminateWithError($result); + + $this->terminateWithSuccess($data); + } + +} + diff --git a/application/controllers/api/frontend/v1/NewsAdministrationAPI.php b/application/controllers/api/frontend/v1/NewsAdministrationAPI.php new file mode 100644 index 000000000..e15e403cd --- /dev/null +++ b/application/controllers/api/frontend/v1/NewsAdministrationAPI.php @@ -0,0 +1,948 @@ +. + */ + +if (!defined('BASEPATH')) exit('No direct script access allowed'); + +class NewsAdministrationAPI extends FHCAPI_Controller +{ + /** + * News API constructor. + */ + public function __construct() + { + parent::__construct([ + 'getNews' => 'basis/news:r', + 'getNewsItem' => 'basis/news:r', + 'storeNewsItem' => 'basis/news:w', + 'updateNewsItem' => 'basis/news:w', + 'deleteNewsItem' => 'basis/news:w' + ]); + + $this->load->model('content/Content_model', 'ContentModel'); + $this->load->model('content/Contentsprache_model', 'ContentspracheModel'); + $this->load->model('content/News_model', 'NewsModel'); + $this->load->model('organisation/Studiengang_model', 'StudiengangModel'); + $this->load->model('ressource/Mitarbeiter_model', 'MitarbeiterModel'); + $this->load->model('system/Sprache_model', 'SpracheModel'); + $this->load->model('person/Benutzerfunktion_model', 'BenutzerfunktionModel'); + + $this->loadPhrases([ + 'global' + ]); + + $this->load->library('CmsLib'); + + $this->load->helper('hlp_sancho_helper'); + } + + //------------------------------------------------------------------------------------------------------------------ + // Public methods + + public function getNews($infoscreen = false, $studiengang_kz = null, $semester = null, $titel = '', $active = true, $sichtbar = true) + { + $this->load->library('form_validation'); + $this->form_validation->set_data($_GET); + $this->form_validation->set_rules('page','Page','required|is_natural'); + $this->form_validation->set_rules('page_size', 'PageSize', 'is_natural'); + if($this->form_validation->run() == FALSE) $this->terminateWithValidationErrors($this->form_validation->error_array()); + + $page = intval($this->input->get('page', true)); + $page_size = intval($this->input->get('page_size', true)); + $sprache = $this->input->get('sprache', true); + if(!$sprache) + { + $sprache = getUserLanguage(); + } + + // default value for the page_size is 10 + $page_size = $page_size ?? 10; + + $passedSichtbar = $this->input->get('published', true); + if($passedSichtbar !== null) + { + $sichtbar = $passedSichtbar; + } + + $passedIsActive = $this->input->get('isActive', true); + if ($passedIsActive !== null) + { + $isActive = filter_var( + $passedIsActive, + FILTER_VALIDATE_BOOLEAN, + FILTER_NULL_ON_FAILURE + ); + if ($isActive !== null) + { + $active = $isActive; + } + } + + $degreeProgramShortCode = $this->input->get('degreeProgramShortCode', true); + if ($degreeProgramShortCode !== null && $degreeProgramShortCode !== '') + { + if (!ctype_digit((string)$degreeProgramShortCode)) + { + $this->terminateWithValidationErrors([ + 'degreeProgramShortCode' => 'A valid degree program is required' + ]); + } + $studiengang_kz = $degreeProgramShortCode; + } + + $passedSemester = $this->input->get('semester', true); + if ($passedSemester !== null && $passedSemester !== '') + { + if ( + !ctype_digit((string)$passedSemester) + || (int)$passedSemester < 1 + || (int)$passedSemester > 8 + ) + { + $this->terminateWithValidationErrors([ + 'semester' => 'A valid semester is required' + ]); + } + $semester = filter_var($passedSemester, FILTER_VALIDATE_INT); + } + + $entitledDegreePrograms = $this->permissionlib->getSTG_isEntitledFor("basis/news"); + + $news = $this->cmslib->getNews($infoscreen, $studiengang_kz, $semester, false, $titel, true, $sichtbar, $page, $page_size, $sprache, true, $entitledDegreePrograms, $active); + $news = $this->getDataOrTerminateWithError($news); + + $this->addMeta('row_count', $news["row_count"] ?? 0); + $this->terminateWithSuccess($news["content"]); + } + + /** + * Returns one news entry with all available language versions for editing. + * + * @return void + */ + public function getNewsItem($newsId) + { + if (!is_numeric($newsId) || (int)$newsId < 1) + { + $this->terminateWithValidationErrors([ + 'newsId' => 'A valid news ID is required' + ]); + } + + $newsId = (int)$newsId; + $newsResult = $this->NewsModel->load($newsId); + + if (isError($newsResult)) + { + $this->terminateWithError(getError($newsResult), self::ERROR_TYPE_DB); + } + + if (!hasData($newsResult)) + { + $this->terminateWithValidationErrors([ + 'newsId' => 'The requested news entry does not exist' + ]); + } + + $news = current(getData($newsResult)); + $entitledDegreePrograms = $this->permissionlib->getSTG_isEntitledFor("basis/news"); + + if (!in_array($news->studiengang_kz, $entitledDegreePrograms)) { + $this->terminateWithError('You are not entitled to access this news entry', self::ERROR_TYPE_PERMISSION); + } + $translationsResult = $this->ContentspracheModel->loadWhere([ + 'content_id' => $news->content_id + ]); + + if (isError($translationsResult)) + { + $this->terminateWithError(getError($translationsResult), self::ERROR_TYPE_DB); + } + + $languages = []; + + foreach ((array)getData($translationsResult) as $translation) + { + $languages[$translation->sprache] = true; + } + + $translations = []; + + foreach (array_keys($languages) as $language) + { + $contentResult = $this->ContentModel->getContent( + $news->content_id, + $language, + null, + null, + false + ); + + if (isError($contentResult)) + { + $this->terminateWithError(getError($contentResult), self::ERROR_TYPE_DB); + } + + $content = getData($contentResult); + $document = new DOMDocument('1.0', 'UTF-8'); + + if ($content->content !== '' && $document->loadXML($content->content) === false) + { + $this->terminateWithError( + 'The stored news content is invalid', + self::ERROR_TYPE_GENERAL + ); + } + + $author = $document->getElementsByTagName('verfasser')->item(0); + $title = $document->getElementsByTagName('betreff')->item(0); + $text = $document->getElementsByTagName('text')->item(0); + + $translations[] = [ + 'language' => $language, + 'author' => $author ? $author->nodeValue : '', + 'title' => $title ? $title->nodeValue : '', + 'text' => $text ? str_replace('dms.php', APP_ROOT . 'cms/dms.php', $text->nodeValue) : '', + 'isPublished' => (bool)$content->sichtbar + ]; + } + + $this->terminateWithSuccess([ + 'newsId' => (int)$news->news_id, + 'visibleFrom' => $news->datum, + 'visibleTo' => $news->datum_bis, + 'degreeProgramShortCode' => $news->studiengang_kz, + 'semester' => $news->semester, + 'translations' => $translations + ]); + } + + /** + * Creates a news entry and all supplied translations. + * + * @return void + */ + public function storeNewsItem() + { + $this->load->library('form_validation'); + $postData = $this->input->post(null, true); + $this->form_validation->set_data(is_array($postData) ? $postData : []); + $this->form_validation->set_rules('visibleFrom', 'Visible from', 'required|is_valid_date'); + $this->form_validation->set_rules('visibleTo', 'Visible to', 'is_valid_date'); + $this->form_validation->set_rules( + 'degreeProgramShortCode', + 'Degree program', + 'required|is_natural' + ); + $this->form_validation->set_rules( + 'semester', + 'Semester', + 'is_natural_no_zero|less_than_equal_to[8]' + ); + + $errors = $this->form_validation->run() === false + ? $this->form_validation->error_array() + : []; + $visibleFrom = $this->input->post('visibleFrom', true); + $visibleTo = $this->input->post('visibleTo', true); + $translations = $this->input->post('translations', true); + + if ( + is_string($visibleFrom) + && is_string($visibleTo) + && $visibleTo !== '' + && is_valid_date($visibleFrom) + && is_valid_date($visibleTo) + && new DateTime($visibleTo) < new DateTime($visibleFrom) + ) + { + $errors['visibleTo'] = 'Visible to must not be before Visible from'; + } + + if (!is_array($translations) || count($translations) === 0) + { + $errors['translations'] = 'At least one translation is required'; + } + else + { + foreach ($translations as $index => $translation) + { + if (!is_array($translation)) + { + $errors['translations.' . $index] = 'Each translation must be an object'; + } + } + } + + if (count($errors) > 0) + { + $this->terminateWithValidationErrors($errors); + } + + + $this->_ensureUserIsEntitledForDegreeProgram($this->input->post('degreeProgramShortCode', true)); + + $degreeProgramShortCode = $this->input->post('degreeProgramShortCode', true); + $semester = $this->input->post('semester', true); + $semester = $semester === '' ? null : $semester; + $visibleTo = $visibleTo === '' ? null : $visibleTo; + + $studiengang = $this->_getStudiengang($degreeProgramShortCode); + + $data = [ + 'studiengang_kz' => $degreeProgramShortCode, + 'semester' => $semester, + 'datum' => $visibleFrom, + 'datum_bis' => $visibleTo, + 'translations' => [] + ]; + + foreach ($translations as $index => $translation) + { + $data['translations'][$index] = [ + 'sprache' => $translation['language'] ?? '', + 'verfasser' => $translation['author'] ?? '', + 'betreff' => $translation['title'] ?? '', + 'text' => $translation['text'] ?? '', + 'sichtbar' => isset($translation['isPublished']) ? (bool)$translation['isPublished'] : false + ]; + } + + $this->_validateLanguages($data['translations']); + + foreach ($data['translations'] as $index => $translation) + { + $data['translations'][$index]['content'] = $this->_buildNewsXml($translation); + } + + $uid = getAuthUID(); + $now = date('Y-m-d H:i:s'); + + if ($this->db->trans_begin() === false) + { + $this->terminateWithError($this->db->error(), self::ERROR_TYPE_DB); + } + + $contentResult = $this->ContentModel->insert([ + 'template_kurzbz' => 'news', + 'oe_kurzbz' => $studiengang->oe_kurzbz, + 'aktiv' => true, + 'menu_open' => false, + 'insertamum' => $now, + 'insertvon' => $uid + ]); + $contentId = (int)$this->_getInsertedIdOrRollback($contentResult); + + $newsResult = $this->NewsModel->insert([ + 'uid' => $uid, + 'studiengang_kz' => $data['studiengang_kz'], + 'semester' => $data['semester'], + 'datum' => $data['datum'], + 'datum_bis' => $data['datum_bis'], + 'content_id' => $contentId, + 'insertamum' => $now, + 'insertvon' => $uid, + 'updateamum' => $now, + 'updatevon' => $uid + ]); + $newsId = (int)$this->_getInsertedIdOrRollback($newsResult); + + foreach ($data['translations'] as $translation) + { + $translation['content'] = $this->formatContentURLsToRelative($translation['content']); + + $translationResult = $this->ContentspracheModel->insert([ + 'sprache' => $translation['sprache'], + 'content_id' => $contentId, + 'version' => 1, + 'sichtbar' => $translation['sichtbar'], + 'content' => $translation['content'], + 'titel' => $translation['betreff'], + 'insertamum' => $now, + 'insertvon' => $uid, + 'updateamum' => $now, + 'updatevon' => $uid + ]); + + $this->_getInsertedIdOrRollback($translationResult); + } + + $newsItemResult = $this->NewsModel->load($newsId); + $newsItem = $this->getDataOrTerminateWithError($newsItemResult); + $newsItem = $newsItem[0]; + + $this->_sendNewsItemTranslationRequiredMail( + $newsItem + ); + + if ($this->db->trans_status() === false) + { + $error = $this->db->error(); + $this->db->trans_rollback(); + $this->terminateWithError($error, self::ERROR_TYPE_DB); + } + + if ($this->db->trans_commit() === false) + { + $error = $this->db->error(); + $this->db->trans_rollback(); + $this->terminateWithError($error, self::ERROR_TYPE_DB); + } + + $this->terminateWithSuccess($newsId); + } + + /** + * Updates a news entry and its supplied translations. + * + * @return void + */ + public function updateNewsItem($newsId) + { + if (!is_numeric($newsId) || (int)$newsId < 1) + { + $this->terminateWithValidationErrors([ + 'newsId' => 'A valid news ID is required' + ]); + } + + $newsId = (int)$newsId; + $existingNewsResult = $this->NewsModel->load($newsId); + + if (isError($existingNewsResult)) + { + $this->terminateWithError(getError($existingNewsResult), self::ERROR_TYPE_DB); + } + + if (!hasData($existingNewsResult)) + { + $this->terminateWithValidationErrors([ + 'newsId' => 'The requested news entry does not exist' + ]); + } + + $existingNews = current(getData($existingNewsResult)); + + $this->_ensureUserIsEntitledForDegreeProgram($existingNews->studiengang_kz); + + $this->load->library('form_validation'); + $postData = $this->input->post(null, true); + $this->form_validation->set_data(is_array($postData) ? $postData : []); + $this->form_validation->set_rules('visibleFrom', 'Visible from', 'required|is_valid_date'); + $this->form_validation->set_rules('visibleTo', 'Visible to', 'is_valid_date'); + $this->form_validation->set_rules( + 'degreeProgramShortCode', + 'Degree program', + 'required|is_natural' + ); + $this->form_validation->set_rules( + 'semester', + 'Semester', + 'is_natural_no_zero|less_than_equal_to[8]' + ); + + $errors = $this->form_validation->run() === false + ? $this->form_validation->error_array() + : []; + $visibleFrom = $this->input->post('visibleFrom', true); + $visibleTo = $this->input->post('visibleTo', true); + $translations = $this->input->post('translations', true); + + if ( + is_string($visibleFrom) + && is_string($visibleTo) + && $visibleTo !== '' + && is_valid_date($visibleFrom) + && is_valid_date($visibleTo) + && new DateTime($visibleTo) < new DateTime($visibleFrom) + ) + { + $errors['visibleTo'] = 'Visible to must not be before Visible from'; + } + + if (!is_array($translations) || count($translations) === 0) + { + $errors['translations'] = 'At least one translation is required'; + } + else + { + foreach ($translations as $index => $translation) + { + if (!is_array($translation)) + { + $errors['translations.' . $index] = 'Each translation must be an object'; + } + } + } + + if (count($errors) > 0) + { + $this->terminateWithValidationErrors($errors); + } + + $degreeProgramShortCode = $this->input->post('degreeProgramShortCode', true); + $semester = $this->input->post('semester', true); + $semester = $semester === '' ? null : $semester; + $visibleTo = $visibleTo === '' ? null : $visibleTo; + + $studiengang = $this->_getStudiengang($degreeProgramShortCode); + + $data = [ + 'studiengang_kz' => $degreeProgramShortCode, + 'semester' => $semester, + 'datum' => $visibleFrom, + 'datum_bis' => $visibleTo, + 'translations' => [] + ]; + + foreach ($translations as $index => $translation) + { + $data['translations'][$index] = [ + 'sprache' => $translation['language'] ?? '', + 'verfasser' => $translation['author'] ?? '', + 'betreff' => $translation['title'] ?? '', + 'text' => $translation['text'] ?? '', + 'sichtbar' => isset($translation['isPublished']) ? (bool)$translation['isPublished'] : false + ]; + } + + $this->_validateLanguages($data['translations']); + + foreach ($data['translations'] as $index => $translation) + { + $data['translations'][$index]['content'] = $this->_buildNewsXml($translation); + } + + $uid = getAuthUID(); + $now = date('Y-m-d H:i:s'); + + if ($this->db->trans_begin() === false) + { + $this->terminateWithError($this->db->error(), self::ERROR_TYPE_DB); + } + + $contentResult = $this->ContentModel->update($existingNews->content_id, [ + 'oe_kurzbz' => $studiengang->oe_kurzbz, + 'updateamum' => $now, + 'updatevon' => $uid + ]); + + if (isError($contentResult)) + { + $this->db->trans_rollback(); + $this->terminateWithError(getError($contentResult), self::ERROR_TYPE_DB); + } + + $newsResult = $this->NewsModel->update($newsId, [ + 'studiengang_kz' => $data['studiengang_kz'], + 'semester' => $data['semester'], + 'datum' => $data['datum'], + 'datum_bis' => $data['datum_bis'], + 'updateamum' => $now, + 'updatevon' => $uid + ]); + + if (isError($newsResult)) + { + $this->db->trans_rollback(); + $this->terminateWithError(getError($newsResult), self::ERROR_TYPE_DB); + } + + $existingTranslationsResult = $this->ContentspracheModel->loadWhere([ + 'content_id' => $existingNews->content_id + ]); + + if (isError($existingTranslationsResult)) + { + $this->db->trans_rollback(); + $this->terminateWithError(getError($existingTranslationsResult), self::ERROR_TYPE_DB); + } + + $existingTranslations = []; + + foreach ((array)getData($existingTranslationsResult) as $existingTranslation) + { + $language = $existingTranslation->sprache; + + if ( + !isset($existingTranslations[$language]) + || (int)$existingTranslation->version > (int)$existingTranslations[$language]->version + ) + { + $existingTranslations[$language] = $existingTranslation; + } + } + + $submittedLanguages = array_column($data['translations'], 'sprache'); + $this->db->where('content_id', $existingNews->content_id); + $this->db->where_not_in('sprache', $submittedLanguages); + + if ($this->db->delete('campus.tbl_contentsprache') === false) + { + $error = $this->db->error(); + $this->db->trans_rollback(); + $this->terminateWithError($error, self::ERROR_TYPE_DB); + } + + foreach ($data['translations'] as $translation) + { + $translation['content'] = $this->formatContentURLsToRelative($translation['content']); + $translationData = [ + 'sichtbar' => $translation['sichtbar'], + 'content' => $translation['content'], + 'titel' => $translation['betreff'], + 'updateamum' => $now, + 'updatevon' => $uid + ]; + + $existingTranslation = $existingTranslations[$translation['sprache']] ?? null; + + if ($existingTranslation) + { + $translationResult = $this->ContentspracheModel->update( + $existingTranslation->contentsprache_id, + $translationData + ); + } + else + { + $translationResult = $this->ContentspracheModel->insert([ + 'sprache' => $translation['sprache'], + 'content_id' => $existingNews->content_id, + 'version' => 1, + 'insertamum' => $now, + 'insertvon' => $uid + ] + $translationData); + } + + $this->_getInsertedIdOrRollback($translationResult); + } + + if ($this->db->trans_status() === false) + { + $error = $this->db->error(); + $this->db->trans_rollback(); + $this->terminateWithError($error, self::ERROR_TYPE_DB); + } + + if ($this->db->trans_commit() === false) + { + $error = $this->db->error(); + $this->db->trans_rollback(); + $this->terminateWithError($error, self::ERROR_TYPE_DB); + } + + $this->terminateWithSuccess($newsId); + } + + /** + * Deletes one news entry and its linked content. + * + * @return void + */ + public function deleteNewsItem($newsId) + { + if (!is_numeric($newsId) || (int)$newsId < 1) + { + $this->terminateWithValidationErrors([ + 'newsId' => 'A valid news ID is required' + ]); + } + + $newsId = (int)$newsId; + $newsResult = $this->NewsModel->load($newsId); + + if (isError($newsResult)) + { + $this->terminateWithError(getError($newsResult), self::ERROR_TYPE_DB); + } + + if (!hasData($newsResult)) + { + $this->terminateWithValidationErrors([ + 'newsId' => 'The requested news entry does not exist' + ]); + } + + $news = current(getData($newsResult)); + + + $this->_ensureUserIsEntitledForDegreeProgram($news->studiengang_kz); + + if ($this->db->trans_begin() === false) + { + $this->terminateWithError($this->db->error(), self::ERROR_TYPE_DB); + } + + $newsDeleteResult = $this->NewsModel->delete($newsId); + + if (isError($newsDeleteResult)) + { + $this->db->trans_rollback(); + $this->terminateWithError(getError($newsDeleteResult), self::ERROR_TYPE_DB); + } + + $this->db->where('content_id', $news->content_id); + + if ($this->db->delete('campus.tbl_contentsprache') === false) + { + $error = $this->db->error(); + $this->db->trans_rollback(); + $this->terminateWithError($error, self::ERROR_TYPE_DB); + } + + $contentDeleteResult = $this->ContentModel->delete($news->content_id); + + if (isError($contentDeleteResult)) + { + $this->db->trans_rollback(); + $this->terminateWithError(getError($contentDeleteResult), self::ERROR_TYPE_DB); + } + + if ($this->db->trans_status() === false) + { + $error = $this->db->error(); + $this->db->trans_rollback(); + $this->terminateWithError($error, self::ERROR_TYPE_DB); + } + + if ($this->db->trans_commit() === false) + { + $error = $this->db->error(); + $this->db->trans_rollback(); + $this->terminateWithError($error, self::ERROR_TYPE_DB); + } + + $this->terminateWithSuccess($newsId); + } + + //------------------------------------------------------------------------------------------------------------------ + // Private methods + + /** + * Loads the requested Studiengang or reports a validation error. + * + * @param integer $studiengangKz + * @return stdClass + */ + private function _getStudiengang($studiengangKz) + { + $result = $this->StudiengangModel->load($studiengangKz); + + if (isError($result)) + { + $this->terminateWithError(getError($result), self::ERROR_TYPE_DB); + } + + if (!hasData($result)) + { + $this->terminateWithValidationErrors([ + 'studiengang_kz' => 'The requested Studiengang does not exist' + ]); + } + + $studiengaenge = getData($result); + + return current($studiengaenge); + } + + /** + * Ensures that all supplied languages exist in tbl_sprache. + * + * @param array $translations + * @return void + */ + private function _validateLanguages($translations) + { + $languages = []; + + foreach ($translations as $translation) + { + $languages[] = $translation['sprache']; + } + + $result = $this->SpracheModel->loadMultiple($languages); + + if (isError($result)) + { + $this->terminateWithError(getError($result), self::ERROR_TYPE_DB); + } + + $existingLanguages = []; + + foreach ((array)getData($result) as $language) + { + $existingLanguages[$language->sprache] = true; + } + + $errors = []; + + foreach ($translations as $index => $translation) + { + if (!isset($existingLanguages[$translation['sprache']])) + { + $errors['translations.' . $index . '.sprache'] = 'The requested language does not exist'; + } + } + + if (count($errors) > 0) + { + $this->terminateWithValidationErrors($errors); + } + } + + private function _ensureUserIsEntitledForDegreeProgram($degreeProgramShortCode) + { + $entitledDegreePrograms = $this->permissionlib->getSTG_isEntitledFor("basis/news:w"); + if (!is_array($entitledDegreePrograms)) + { + $this->terminateWithError('Failed to retrieve entitled degree programs for the user', self::ERROR_TYPE_GENERAL); + } + + if (!in_array($degreeProgramShortCode, $entitledDegreePrograms)) { + $this->terminateWithError("User is not entitled for the specified degree program"); + } + } + /** + * Returns an inserted identifier or rolls the transaction back on error. + * + * @param stdClass $result + * @return mixed + */ + private function _getInsertedIdOrRollback($result) + { + if (isError($result)) + { + $this->db->trans_rollback(); + $this->terminateWithError(getError($result), self::ERROR_TYPE_DB); + } + + return getData($result); + } + + /** + * Builds the XML format consumed by existing news readers. + * + * @param array $translation + * @return string + */ + private function _buildNewsXml($translation) + { + $document = new DOMDocument('1.0', 'UTF-8'); + $news = $document->createElement('news'); + $document->appendChild($news); + + $this->_appendCdataElement($document, $news, 'verfasser', $translation['verfasser']); + $this->_appendCdataElement($document, $news, 'betreff', $translation['betreff']); + $this->_appendCdataElement($document, $news, 'text', $translation['text']); + + return $document->saveXML($news); + } + + /** + * Appends a CDATA element and safely preserves CDATA terminators. + * + * @param DOMDocument $document + * @param DOMElement $parent + * @param string $name + * @param string $value + * @return void + */ + private function _appendCdataElement($document, $parent, $name, $value) + { + $element = $document->createElement($name); + $parts = explode(']]>', $value); + $lastIndex = count($parts) - 1; + + foreach ($parts as $index => $part) + { + if ($index > 0) + { + $part = '>' . $part; + } + + if ($index < $lastIndex) + { + $part .= ']]'; + } + + $element->appendChild($document->createCDATASection($part)); + } + + $parent->appendChild($element); + } + + private function _sendNewsItemTranslationRequiredMail($newsItem) + { + $translatorsResult = $this->BenutzerfunktionModel->getBenutzerFunktionen("translate", $newsItem->studiengang_kz); + $translators = $this->getDataOrTerminateWithError($translatorsResult); + + $newsItemContentResult = $this->ContentspracheModel->loadWhere([ + 'content_id' => $newsItem->content_id, + 'sprache' => 'German' + ]); + $newsItemContentEntry = getData($newsItemContentResult)[0] ?? null; + if ($newsItemContentEntry) { + $newsItem->sichtbar = $newsItemContentEntry->sichtbar ?? false; + $newsItem->content = $newsItemContentEntry->content; + } + $content = getData( + $this->cmslib->createNewsContent( + $this->cmslib->createNewsWrapper($newsItem, true), + false, + "" + ) + ); + + foreach ($translators as $translator) { + sendSanchoMail( + "Sancho_Mail_News_Translation_Req", + [ + "newsItemUrl" => APP_ROOT . "cis.php/CisVue/Cms/newsAdministration?newsId=" . $newsItem->news_id, + "newsItemAuthor" => $newsItem->verfasser, + "newsItemSubject" => $newsItem->betreff, + "newsItemContent" => $content + ], + $translator->uid . "@" . DOMAIN, + "Übersetzung erforderlich / Translation required" + ); + } + } + + private function formatContentURLsToRelative($content) + { + $content = preg_replace( + '#https?://[^"\'>\s]*/(dms\.php(?:\?[^"\'>\s]*)?)#i', + '$1', + $content + ); + + $content = preg_replace( + '#(?:\.\./)+[^"\'>\s]*/(dms\.php(?:\?[^"\'>\s]*)?)#i', + '$1', + $content + ); + + $content = preg_replace( + '#/[^"\'>\s]*/(dms\.php(?:\?[^"\'>\s]*)?)#i', + '$1', + $content + ); + + return $content; + } +} diff --git a/application/controllers/api/frontend/v1/Noten.php b/application/controllers/api/frontend/v1/Noten.php new file mode 100644 index 000000000..6a7d0f640 --- /dev/null +++ b/application/controllers/api/frontend/v1/Noten.php @@ -0,0 +1,1782 @@ +. + */ + +if (! defined('BASEPATH')) exit('No direct script access allowed'); + +use CI3_Events as Events; + +class Noten extends FHCAPI_Controller +{ + /** tbl_note by PK, read once per request. @see aktiveNoten() */ + private $aktiveNotenCache = null; + + /** + * Object initialization + */ + public function __construct() + { + $permissions = array('lehre/benotungstool:rw', 'lehre/benotungstool_assistenz:rw'); + parent::__construct([ + 'getStudentenNoten' => $permissions, + 'getNoten' => $permissions, + 'saveStudentenNoten' => $permissions, + 'getNotenvorschlagStudent' => $permissions, + 'saveNotenvorschlag' => $permissions, + 'saveStudentPruefung' => $permissions, + 'createPruefungen' => $permissions, + 'saveNotenvorschlagBulk' => $permissions, + 'savePruefungenBulk' => $permissions, + 'getCisConfig' => $permissions, + 'getNoteByPunkte' => $permissions, + 'getBenotungstoolContext' => $permissions, + 'getLehrendeFuerLehreinheit' => $permissions, + 'getLvForStudiengang' => $permissions + ]); + + $this->load->library('AuthLib', null, 'AuthLib'); + $this->load->library('PhrasesLib'); + $this->load->library('PruefungsverlaufLib', null, 'VerlaufLib'); + + // Loads LogLib with different debug trace levels to get data of the job that extends this class + // It also specify parameters to set database fields + $this->load->library('LogLib', array( + 'classIndex' => 5, + 'functionIndex' => 5, + 'lineIndex' => 4, + 'dbLogType' => 'API', // required + 'dbExecuteUser' => 'RESTful API', + 'requestId' => 'API', + 'requestDataFormatter' => function ($data) { + return json_encode($data); + } + ), 'logLib'); + + // Loads phrases system + $this->loadPhrases([ + 'global', + 'person', + 'benotungstool', + 'lehre', + 'ui', + 'password' + ]); + + $this->load->model('education/LePruefung_model', 'LePruefungModel'); + $this->load->model('education/Lvgesamtnote_model', 'LvgesamtnoteModel'); + $this->load->model('education/Lehrveranstaltung_model', 'LehrveranstaltungModel'); + $this->load->model('education/Notenschluesselaufteilung_model', 'NotenschluesselaufteilungModel'); + $this->load->model('education/Note_model', 'NoteModel'); + $this->load->model('person/Person_model', 'PersonModel'); + $this->load->model('organisation/Studienplan_model', 'StudienplanModel'); + $this->load->model('crm/Student_model', 'StudentModel'); + $this->load->model('codex/Mobilitaet_model', 'MobilitaetModel'); + $this->load->model('organisation/Erhalter_model', 'ErhalterModel'); + + $this->load->config('noten'); + $this->load->helper('hlp_sancho_helper'); + + } + + public function getCisConfig() { + // resolved from tbl_note (Bezeichnung) with config fallback -> single source of truth + $special = $this->VerlaufLib->getSpecialNotes(); + $NOTEN_OHNE_ANTRITT = $special['ohneAntritt']; + $NOTEN_OCCURANCE_LIMIT_MAP = $special['limitMap']; + $NOTE_ENTSCHULDIGT = $special['entschuldigt']; + + $this->terminateWithSuccess( + array( + // show the points during the grade entry + 'CIS_GESAMTNOTE_PUNKTE' => CIS_GESAMTNOTE_PUNKTE, + + // basically on/of toggle for the points/grade col and the arrow button + 'CIS_GESAMTNOTE_UEBERSCHREIBEN' => CIS_GESAMTNOTE_UEBERSCHREIBEN, + + // only relevant in punkte calculation in backend + // 'CIS_GESAMTNOTE_GEWICHTUNG' => CIS_GESAMTNOTE_GEWICHTUNG, + + // The maximum number of attempts that count in this tool. The server derives it from the + // configuration or from the old TERMIN2/TERMIN3 flags. The client does not calculate it. + 'CIS_GESAMTNOTE_MAX_ANTRITTE' => $this->VerlaufLib->getMaxAntritte(), + + // the default column layout ('antritt' or 'datum'); the user can change it in the tool + 'CIS_GESAMTNOTE_PRUEFUNGSSPALTEN' => $this->config->item('CIS_GESAMTNOTE_PRUEFUNGSSPALTEN'), + + // used to toggle availability of kommPruef type pruefungen + 'CIS_GESAMTNOTE_PRUEFUNG_KOMMPRUEF' => CIS_GESAMTNOTE_PRUEFUNG_KOMMPRUEF, + + //technically exists but is never used, could be LE pendant to next flag + // 'CIS_GESAMTNOTE_PRUEFUNG_MOODLE_NOTE' => CIS_GESAMTNOTE_PRUEFUNG_MOODLE_NOTE, + + // basically a toggle for "use teilnoten" and the source is always moodle + // setting this to false breaks legacy tool and if that was fixed it wouldnt render any table at all + // anyway so not sure why this even is a config at all. placebo at best + + // toggles availability of the teilnoten column... existas but do we really need this? + 'CIS_GESAMTNOTE_PRUEFUNG_MOODLE_LE_NOTE' => CIS_GESAMTNOTE_PRUEFUNG_MOODLE_LE_NOTE, + + // availability of the two import flows (application/config/noten.php); when both are + // true they are shown separately + 'CIS_GESAMTNOTE_PRUEFUNGSIMPORT' => $this->config->item('CIS_GESAMTNOTE_PRUEFUNGSIMPORT'), + 'CIS_GESAMTNOTE_NOTENIMPORT' => $this->config->item('CIS_GESAMTNOTE_NOTENIMPORT'), + + // send a mail when approving grades + 'CIS_GESAMTNOTE_FREIGABEMAIL_NOTE' => CIS_GESAMTNOTE_FREIGABEMAIL_NOTE, + + 'NOTEN_OHNE_ANTRITT' => $NOTEN_OHNE_ANTRITT, + + 'NOTEN_OCCURANCE_LIMIT_MAP' => $NOTEN_OCCURANCE_LIMIT_MAP, + + // pk of the 'entschuldigt' note; used to preserve excused Termine on new pruefung creation + 'NOTE_ENTSCHULDIGT' => $NOTE_ENTSCHULDIGT, + + // Noteneintragungsfrist window (enforced server-side; also surfaced so the UI can hint at it) + 'CIS_GESAMTNOTE_NOTENEINTRAGUNGSFRIST' => $this->config->item('CIS_GESAMTNOTE_NOTENEINTRAGUNGSFRIST') + ) + ); + } + + /** + * GET METHOD + * expects (optional) 'sem_kurzbz' + * Single role-determining entry point for the tool: the server decides which flow the current user + * gets and returns exactly the initial dropdown data for it. + * A teacher (holds lehre/benotungstool) gets their assigned + * Lehrveranstaltungen directly. Assistenz (only lehre/benotungstool_assistenz) gets the + * degree programmes they are entitled for, and picks one before a course is loaded. + */ + public function getBenotungstoolContext() { + $sem_kurzbz = $this->input->get("sem_kurzbz", TRUE); + $lv_id = $this->input->get("lv_id", TRUE); // optional: deep-link target, used to preselect + + $this->load->library('PermissionLib'); + + // teachers keep the classic assigned-LV flow; the Studiengang flow is only for Assistenz. + // Role determination mirrors assertLvAccess, which scopes each role's actual data + // access. A teacher sees the own courses, an assistant sees the entitled degree programmes. + $isLektor = $this->permissionlib->isBerechtigt('lehre/benotungstool'); + $entitledStgs = $this->permissionlib->getSTG_isEntitledFor('lehre/benotungstool_assistenz'); + $isAssistenz = !$isLektor && is_array($entitledStgs) && count($entitledStgs) > 0; + + $studiengaenge = array(); + $lehrveranstaltungen = array(); + $preselectStudiengang_kz = null; + + if (isset($sem_kurzbz) && !isEmptyString($sem_kurzbz)) { + if ($isAssistenz) { + $this->load->model('organisation/Studiengang_model', 'StudiengangModel'); + $result = $this->StudiengangModel->getByStgs($entitledStgs, $sem_kurzbz); + if (!isError($result)) $studiengaenge = getData($result) ?? array(); + + // deep-link: resolve the Studiengang of the requested LV so the frontend can preselect + // the Studiengang dropdown (and then its LV) - only if the Assistenz is entitled for it + if (isset($lv_id) && !isEmptyString($lv_id)) { + $res = $this->LehrveranstaltungModel->load($lv_id); + if (!isError($res) && hasData($res)) { + $stg = getData($res)[0]->studiengang_kz; + if (in_array($stg, $entitledStgs)) $preselectStudiengang_kz = $stg; + } + } + } else { + $result = $this->LehrveranstaltungModel->getLvForLektorInSemester($sem_kurzbz, getAuthUID()); + if (!isError($result)) $lehrveranstaltungen = getData($result) ?? array(); + } + } + + $this->terminateWithSuccess(array( + 'isAssistenz' => $isAssistenz, + 'studiengaenge' => $studiengaenge, + 'lehrveranstaltungen' => $lehrveranstaltungen, + 'preselectStudiengang_kz' => $preselectStudiengang_kz + )); + } + + /** + * GET METHOD + * expects 'studiengang_kz', 'sem_kurzbz' + * Returns the Lehrveranstaltungen of a Studiengang in a Studiensemester for the Assistenz flow. + * The caller may load only the degree programmes of lehre/benotungstool_assistenz. + */ + public function getLvForStudiengang() { + $studiengang_kz = $this->input->get("studiengang_kz", TRUE); + $sem_kurzbz = $this->input->get("sem_kurzbz", TRUE); + + if (!isset($studiengang_kz) || isEmptyString($studiengang_kz) + || !isset($sem_kurzbz) || isEmptyString($sem_kurzbz)) { + $this->terminateWithError($this->p->t('global', 'wrongParameters'), 'general'); + } + + $this->load->library('PermissionLib'); + $entitledStgs = $this->permissionlib->getSTG_isEntitledFor('lehre/benotungstool_assistenz'); + $isAdmin = $this->permissionlib->isBerechtigt('admin'); + + if (!$isAdmin && (!is_array($entitledStgs) || !in_array($studiengang_kz, $entitledStgs))) { + $this->terminateWithError($this->p->t('ui', 'keineBerechtigung'), 'general'); + } + + $result = $this->LehrveranstaltungModel->getLvForStudiengangInSemester($sem_kurzbz, $studiengang_kz); + $data = $this->getDataOrTerminateWithError($result); + $this->terminateWithSuccess($data); + } + + /** + * Scopes data access to the Lehrveranstaltungen a user may act on, so a shared/guessed URL cannot be + * used to read or edit foreign grades. A teacher (lehre/benotungstool) may + * only touch LVs they are assigned to teach in the given Studiensemester. Assistenz may + * touch LVs of a Studiengang they are entitled for. + */ + private function assertLvAccess($lv_id, $sem_kurzbz = null) + { + $this->load->library('PermissionLib'); + + // admins keep full access + if ($this->permissionlib->isBerechtigt('admin')) return; + + // teachers: only their own LVs (assigned as lehreinheitmitarbeiter in this semester) + if ($this->permissionlib->isBerechtigt('lehre/benotungstool')) { + $res = $this->LehrveranstaltungModel->getLektorIsTeachingLva($lv_id, getAuthUID(), $sem_kurzbz); + $rows = getData($res); + if (!isError($res) && !empty($rows) && $rows[0]->teaches > 0) return; + // not a teacher of this LV -> fall through (a both-role user may still be entitled as Assistenz) + } + + // (pure or additional) Assistenz: only LVs of an entitled Studiengang + $entitledStgs = $this->permissionlib->getSTG_isEntitledFor('lehre/benotungstool_assistenz'); + $lv = null; + if (is_array($entitledStgs) && count($entitledStgs) > 0) { + $res = $this->LehrveranstaltungModel->load($lv_id); + if (!isError($res) && hasData($res)) { + $lv = getData($res)[0]; + if (in_array($lv->studiengang_kz, $entitledStgs)) return; + } + } + + if ($lv === null) { + $res = $this->LehrveranstaltungModel->load($lv_id); + if (!isError($res) && hasData($res)) $lv = getData($res)[0]; + } + $bezeichnung = $lv !== null ? $lv->bezeichnung : $lv_id; + + $this->terminateWithError( + $this->p->t('benotungstool', 'keineBerechtigungNoten', [$bezeichnung, $sem_kurzbz]), + 'general' + ); + } + + /** + * GET METHOD + * expects 'lv_id', 'sem_kurzbz' + * returns List of all Students of given lehrveranstaltung and semester and fetches their grades. + * Loads LvGesamtnote aswell as Teilnoten from externalSources via getExternalGrades Event. + * Calculates the Notenvorschlag for every student based on averaging their Teilnoten. + * At the end it also fetches all exams of every student for that course and semester. + */ + public function getStudentenNoten() { + $lv_id = $this->input->get("lv_id",TRUE); + $sem_kurzbz = $this->input->get("sem_kurzbz",TRUE); + + if (!isset($lv_id) || isEmptyString($lv_id) + || !isset($sem_kurzbz) || isEmptyString($sem_kurzbz)) + $this->terminateWithError($this->p->t('global', 'wrongParameters'), 'general'); + + $this->assertLvAccess($lv_id, $sem_kurzbz); + + // get studenten for lva & sem with zeugnisnote if available + $studenten = $this->LehrveranstaltungModel->getStudentsByLv($sem_kurzbz, $lv_id); + $studentenData = $this->getDataOrTerminateWithError($studenten); + + if(count($studentenData) == 0) { + $this->terminateWithError($this->p->t('benotungstool', 'c4keineStudentenGefunden')); + } + + $func = function ($value) { + return $value->uid; + }; + + $grades = array(); + $student_uids = array_map($func, $studentenData); + + $funcpre = function ($value) { + return $value->prestudent_id; + }; + + $prestudent_ids = array_map($funcpre, $studentenData); + + if(count($student_uids) > 0) { + $mobres = $this->MobilitaetModel->getMobilityZusatzForUids($student_uids); + $mobData = $this->getDataOrTerminateWithError($mobres); + + $result = $this->ErhalterModel->load(); + $erhalter = getData($result)[0]; + + $erhalter_kz = '9' . sprintf("%03s", $erhalter->erhalter_kz); + foreach($mobData as $mob) { + $grades[$mob->uid]['mobility_zusatz'] = $this->MobilitaetModel->formatZusatz($mob, $erhalter_kz); + } + } + + foreach($student_uids as $uid) { + $grades[$uid]['grades'] = []; + + // Read without the filter. getLvGesamtNoten() gives released grades only. With that getter + // the course grade and the release state are empty after a reload. + $lvgesamtnote = $this->getLvGesamtnoteRow($lv_id, $uid, $sem_kurzbz); + if($lvgesamtnote !== null) { + $grades[$uid]['note_lv'] = $lvgesamtnote->note; + $grades[$uid]['freigabedatum'] = $lvgesamtnote->freigabedatum; + $grades[$uid]['benotungsdatum'] = $lvgesamtnote->benotungsdatum; + $grades[$uid]['punkte_lv'] = $lvgesamtnote->punkte; + } else { + $grades[$uid]['note_lv'] = null; + $grades[$uid]['freigabedatum'] = null; + $grades[$uid]['benotungsdatum'] = null; + $grades[$uid]['punkte_lv'] = null; + } + } + + // send $grades reference to moodle addon + try { + Events::trigger( + 'getExternalGrades', + function & () use (&$grades) + { + return $grades; + }, + [ + 'lvid' => $lv_id, + 'stsem' => $sem_kurzbz + ] + ); + } catch (Throwable $t) { +// $this->addMeta('throwable', $t->getTrace()); + $this->addMeta('getExternalGradesError', $t->getMessage()); + } + + // assign the anw% to the students in the studentData loop + $anwresult = $this->getAnwesenheiten($prestudent_ids, $lv_id, $sem_kurzbz); + + // calculate the grade proposals from the partial grades + foreach($studentenData as $student) { + + // null when the Anwesenheiten addon is absent - the column stays empty in the UI + $student->anwquote = $anwresult[$student->prestudent_id] ?? null; + + $g = $grades[$student->uid]['grades']; + $note_lv = $grades[$student->uid]['note_lv']; + + // overwrite any calculation with lv note once available + if(!is_null($note_lv)) { + $student->note_vorschlag = $note_lv; + } else if(count($g) > 0) { + + $notensumme = 0; + $notensumme_gewichtet = 0; + $gewichtsumme = 0; + $punktesumme = 0; + $punktesumme_gewichtet = 0; + $anzahlnoten = 0; + foreach($g as $teilnote) { + if (is_numeric($teilnote['grade']) || (is_null($teilnote['grade']) && is_numeric($teilnote['points']))) + { + $notensumme += $teilnote['grade']; + $punktesumme += $teilnote['points']; + $notensumme_gewichtet += $teilnote['grade'] * $teilnote['weight']; + $punktesumme_gewichtet += $teilnote['points'] * $teilnote['weight']; + $gewichtsumme += $teilnote['weight']; + $anzahlnoten += 1; + } + } + + // Without a partial grade that counts there is no average. Without this guard the + // division uses zero. PHP 7 gives INF, and INF gives the best grade. PHP 8 stops with + // a fatal error and the full grade table stays empty. + $gewichtet = defined('CIS_GESAMTNOTE_GEWICHTUNG') && CIS_GESAMTNOTE_GEWICHTUNG; + $divisor = $gewichtet ? $gewichtsumme : $anzahlnoten; + + if ($divisor > 0) { + if (CIS_GESAMTNOTE_PUNKTE) { + $punkte_vorschlag = round(($gewichtet ? $punktesumme_gewichtet : $punktesumme) / $divisor, 2); + $note_vorschlag_result = $this->NotenschluesselaufteilungModel->getNote($punkte_vorschlag, $lv_id, $sem_kurzbz); + $note_vorschlag = $this->getDataOrTerminateWithError($note_vorschlag_result); + } else { + $note_vorschlag = round(($gewichtet ? $notensumme_gewichtet : $notensumme) / $divisor); + } + + $student->note_vorschlag = $note_vorschlag; + } + } + } + + // get all exams with grades of that semester and that course + $pruefungen = $this->LePruefungModel->getPruefungenByLvStudiensemester($lv_id, $sem_kurzbz); + $pruefungenData = getData($pruefungen); + + // the server derives the history for each student; the client only reads it + $proStudent = []; + foreach($pruefungenData ?: [] as $p) { + $proStudent[$p->student_uid][] = $p; + } + + // the transcript grades come with the student list (tbl_zeugnisnote.note) + $zeugnisnoten = []; + foreach($studentenData as $s) $zeugnisnoten[$s->uid] = $s->note; + + $pruefungenAbgeleitet = []; + foreach(array_unique(array_merge($student_uids, array_keys($proStudent))) as $uid) { + $lvNote = isset($grades[$uid]) ? ($grades[$uid]['note_lv'] ?? null) : null; + $verlauf = $this->VerlaufLib->buildVerlauf($proStudent[$uid] ?? [], $lvNote, $zeugnisnoten[$uid] ?? null); + + foreach($verlauf->pruefungen as $p) $pruefungenAbgeleitet[] = $p; + + if(isset($grades[$uid])) { + $grades[$uid]['verlauf'] = $this->verlaufSummary( + $verlauf, false, $this->getLvGesamtnoteRow($lv_id, $uid, $sem_kurzbz) !== null + ); + } + } + + $this->terminateWithSuccess(array($studentenData, $pruefungenAbgeleitet, DOMAIN, $grades, $anwresult)); + } + + /** + * GET METHOD + * returns List of all available & active NotenOptions + */ + public function getNoten() { + $this->load->model('education/Note_model', 'NoteModel'); + + $result = $this->NoteModel->getAllActive(); + $noten = $this->getDataOrTerminateWithError($result); + $this->terminateWithSuccess($noten); + } + + /** + * POST METHOD + * expects 'lv_id', 'sem_kurzbz', 'password', 'noten' + * Notenfreigabe method which checks the users password as a security measure. + * Tries to load Lehrveranstaltung, Studiengang and Person via Model in order to validate the coherency of input parameters + * lv_id & sem_kurzbz in relation to the noten array delivered. + * Updates the LvGesamtnote note, aswell as freigabedatum, which is key in the logic of the freigegeben/offen/changed notenStatus + * Along this process builds a html table to be placed in a confirmation email (uid only and full variant depending on config) + * which is being sent to the Lektor, aswell as the assigned Assistenz. + */ + public function saveStudentenNoten() { + $result = $this->getPostJSON(); + + if(!property_exists($result, 'sem_kurzbz') || !property_exists($result, 'lv_id') || + !property_exists($result, 'password') || !property_exists($result, 'noten')) { + $this->terminateWithError($this->p->t('global', 'missingParameters'), 'general'); + } + + if(!$this->AuthLib->checkUserAuthByUsernamePassword(getAuthUID(), $result->password)->retval) { + $this->terminateWithError($this->p->t('password', 'wrongPassword'), 'general'); + } + + $lv_id = $result->lv_id; + $sem_kurzbz = $result->sem_kurzbz; + + $this->assertLvAccess($lv_id, $sem_kurzbz); + + $ret = []; + + $res = $this->LehrveranstaltungModel->load($lv_id); + if(isError($res) || !hasData($res)) { + $this->terminateWithError($this->p->t('benotungstool', 'noValidLvFoundForId', [$lv_id])); + } + + $lv = getData($res)[0]; + + $studiengang_kz = $lv->studiengang_kz; + $res = $this->StudiengangModel->load($studiengang_kz); + if(isError($res) || !hasData($res)) { + $this->terminateWithError($this->p->t('benotungstool', 'noValidStudiengangFoundForId', [$studiengang_kz])); + } + $sg = getData($res)[0]; + $lvaFullName = $sg->kurzbzlang . ' ' . $lv->semester . '.Semester + ' . $lv->bezeichnung . " - " .$lv->lehrform_kurzbz. " " . $lv->orgform_kurzbz . " - " . $sem_kurzbz; + + $emails = explode(', ', $sg->email); + + + $res = $this->PersonModel->load(getAuthPersonId()); + if(isError($res) || !hasData($res)) { + $this->terminateWithError($this->p->t('benotungstool', 'noValidPersonFoundForId', [getAuthPersonId()])); + } + $pers = getData($res)[0]; + $lektorFullName = $pers->anrede.' '.$pers->vorname.' '.$pers->nachname; //.' ('.$pers->kurzbz.')'; + + + $res = $this->StudienplanModel->getStudienplanByLvaSemKurzbz($lv_id, $sem_kurzbz); + $data = getData($res); + $studienplan_bezeichnung = ''; + foreach ($data as $row) { + $studienplan_bezeichnung .= $row->bezeichnung . ' '; + } + $betreff = $this->p->t('benotungstool','notenfreigabe').' ' . $lv->bezeichnung . ' ' . $lv->orgform_kurzbz . ' - ' . $studienplan_bezeichnung; + + $studlist = ""; + + if (defined('CIS_GESAMTNOTE_FREIGABEMAIL_NOTE') && CIS_GESAMTNOTE_FREIGABEMAIL_NOTE) { + $studlist .= "\n + \n + \n + \n"; + if(defined('CIS_GESAMTNOTE_PUNKTE') && CIS_GESAMTNOTE_PUNKTE) { + $studlist .= "\n"; + } + $studlist .= "\n"; + $studlist .= "\n"; + } else { + $studlist .= "\n"; + } + + foreach($result->noten as $note) { + + $resultLVGes = $this->LvgesamtnoteModel->getLvGesamtNoteVorschlag($lv_id, $note->uid, $sem_kurzbz); + if (!isError($resultLVGes) && hasData($resultLVGes)) + { + $lvgesamtnote = getData($resultLVGes)[0]; + + if ($lvgesamtnote->benotungsdatum > $lvgesamtnote->freigabedatum) + { + + $id = $this->LvgesamtnoteModel->update( + [$lvgesamtnote->student_uid, $lvgesamtnote->studiensemester_kurzbz, $lvgesamtnote->lehrveranstaltung_id], + array( + 'note' => $lvgesamtnote->note, + 'freigabevon_uid' => getAuthUID(), + 'freigabedatum' => date("Y-m-d H:i:s"), + 'updateamum' => date("Y-m-d H:i:s"), + 'updatevon' => getAuthUID() + ) + ); + + if($id) { + $res = $this->LvgesamtnoteModel->load($id->retval); + if(hasData($res)) { + $lvgesamtnote = getData($res)[0]; + + // The release makes the grade binding, therefore the first exam starts + // here. A new exam never creates a second exam in addition. + $this->upsertErstantritt( + $lv_id, $lvgesamtnote->student_uid, $sem_kurzbz, + $lvgesamtnote->note, $lvgesamtnote->punkte, $lvgesamtnote->benotungsdatum + ); + + // The verlauf goes back with the answer, so the table shows the new exam + // at once. Without it the row updates only after a reload. + $ret[] = array( + 'uid' => $note->uid, + 'freigabedatum' => $lvgesamtnote->freigabedatum, + 'benotungsdatum' => $lvgesamtnote->benotungsdatum, + 'verlauf' => $this->buildVerlaufSummary($note->uid, $lv_id, $sem_kurzbz) + ); + } + } + + if (defined('CIS_GESAMTNOTE_FREIGABEMAIL_NOTE') && CIS_GESAMTNOTE_FREIGABEMAIL_NOTE) + { + $studlist .= ""; + $studlist .= ""; + $studlist .= ""; + $studlist .= ""; + + if(defined('CIS_GESAMTNOTE_PUNKTE') && CIS_GESAMTNOTE_PUNKTE) { + $studlist .= ""; + } + $studlist .= ""; + + $studlist .= ""; + } else { + $studlist .= "\n"; + } + } + } + } + $studlist .= "
" . $this->p->t('person','personenkennzeichen') . "" . $this->p->t('lehre','studiengang') . "" . $this->p->t('benotungstool','c4nachname') . "" . $this->p->t('benotungstool','c4vorname') . "" . $this->p->t('benotungstool','c4punkte') . "" . $this->p->t('benotungstool','c4grade') . "" . $this->p->t('ui','bearbeitetVon') . "
" . $this->p->t('person','uid') . "
" . trim($note->matrikelnr) . "" . trim($note->kuerzel) . "" . trim($note->nachname) . "" . trim($note->vorname) . "" . trim($lvgesamtnote->punkte) . "" .$note->noteBezeichnung. "" . $lvgesamtnote->mitarbeiter_uid; + if ($lvgesamtnote->updatevon != '') + $studlist .= " (" . $lvgesamtnote->updatevon . ")"; + $studlist .= "
" . trim($note->uid) . "
"; + + $this->logLib->logInfoDB(array('saveStudentenNoten', array( + 'updatevon' => getAuthUID(), + 'updateamum' => date('Y-m-d H:i:s') + ), getAuthUID(), getAuthPersonId(), array($result->noten, $lv_id, $sem_kurzbz))); + + // always send the mail, config toggles data contents + $this->sendFreigabeEmail($lektorFullName, $lvaFullName, count($result->noten), $emails, $studlist, $betreff); + + $this->terminateWithSuccess($ret); + } + + + private function sendFreigabeEmail($lektorFullName, $lvaFullName, $notenCount, $emailAdressen, $studlist, $betreff) + { + $emailAdressen[] = getAuthUID() . "@" . DOMAIN; // also send mail to lektors own adress + $adressen = implode(";", $emailAdressen); + + foreach ($emailAdressen as $email) + { + // Prepare mail content + $body_fields = array( + 'lektor' => $lektorFullName, + 'lvaname' => $lvaFullName, + 'studlist' => $studlist, + 'neuenotencount' => $notenCount, + 'adressen' => $adressen + ); + + // Send mail + sendSanchoMail( + 'Notenfreigabe', + $body_fields, + $email, + $betreff + ); + } + + } + + /** + * GET METHOD + * should return Notenvorschlag for single Students, not used anywhere but required as per + * https://openproject.technikum-wien.at/projects/fh-complete/work_packages/60873/activity + */ + public function getNotenvorschlagStudent() { + $uid = $this->input->get("uid",TRUE); + + // if uid is missing or empty, fall back to getAuthUID() + if ($uid === NULL || trim((string)$uid) === '') { + $uid = getAuthUID(); + } + + $sem_kurzbz = $this->input->get("sem_kurzbz",TRUE); + $lv_id = $this->input->get("lv_id",TRUE); + + if ($uid === NULL || trim((string)$uid) === '' + || $sem_kurzbz === NULL || trim((string)$sem_kurzbz) === '' + || $lv_id === NULL || trim((string)$lv_id) === '') { + $this->terminateWithError($this->p->t('global', 'missingParameters'), 'general'); + } + + $this->assertLvAccess($lv_id, $sem_kurzbz); + + + $result = $this->LvgesamtnoteModel->getLvGesamtNoteVorschlag($lv_id, $uid, $sem_kurzbz); + $data = $this->getDataOrTerminateWithError($result); + + // TODO: moodle teilnote but it seems they only work for a whole course? + + // get anw% of student by prestudent_id +// $anwresult = $this->getAnwesenheiten($prestudent_ids, $lv_id, $sem_kurzbz); + + + + $this->terminateWithSuccess($data); + } + + /** + * POST METHOD + * expects 'datum', 'lva_id', 'student_uid', 'note' + * Inserts or updates a pruefung for lva & student_uid at given datum (YYYY-MM-DD). When creating a new + * exam, and sets the grade of that exam. + * Updates the course grade of the student. It never writes the transcript grade, the student + * administration does that. + */ + public function saveStudentPruefung() { // einzelne pruefung speichern + $result = $this->getPostJSON(); + + if(!property_exists($result, 'datum') || !property_exists($result, 'lva_id') || + !property_exists($result, 'student_uid') || !property_exists($result, 'note')) { + $this->terminateWithError($this->p->t('global', 'missingParameters'), 'general'); + } + + $student_uid = $result->student_uid; + $note = $result->note; + $punkte = $result->punkte; + $datum = $result->datum; + $lva_id = $result->lva_id; + $lehreinheit_id = $result->lehreinheit_id; + // pruefung_id identifies the record being edited; null when a new pruefung is added + $pruefung_id = property_exists($result, 'pruefung_id') ? $result->pruefung_id : null; + + $stsem = $result->sem_kurzbz; + + $this->assertLvAccess($lva_id, $stsem); + + // examination rules: no entry and no change after the grade entry deadline + $this->enforceNoteneintragungsfrist($stsem); + + $jetzt = date("Y-m-d H:i:s"); + + if(CIS_GESAMTNOTE_PUNKTE && isset($punkte) && $punkte >= 0) { + // with a points entry the server checks the grade again and corrects it + $resultNote = $this->NotenschluesselaufteilungModel->getNote($punkte, $lva_id, $stsem); + if(isError($resultNote)) { + $this->terminateWithError(getError($resultNote)); + } else { + $data = getData($resultNote); + if($data != $note) + { + $note = $data; + } + } + + } + + // TODO: more sophisticated empty check + if($note=='') { + $this->load->model('education/Note_model', 'NoteModel'); + $result = $this->NoteModel->getNochNichtEingetragenNote(); + $note = getData($result)[0]->note; + } + + // the dialog sends the teacher when the Lehreinheit has more than one + $mitarbeiter_uid = property_exists($result, 'mitarbeiter_uid') ? $result->mitarbeiter_uid : null; + + $result = $this->savePruefungFuerStudent($pruefung_id, $student_uid, $lva_id, $stsem, $lehreinheit_id, $note, $punkte, $datum, $mitarbeiter_uid); + + // validation errors and write errors come back as a translated message + if(is_string($result)) $this->terminateWithError($result, 'general'); + + $savedPruefung = $result['savedPruefung'] ?? []; + $savedPruefungData = count($savedPruefung) > 0 ? $savedPruefung[0] : null; + $lvgesamtnote = $result['lvgesamtnote'] ?? null; + + $this->terminateWithSuccess(array($savedPruefungData, $lvgesamtnote, $result['verlauf'] ?? null)); + } + + /** + * The core of one exam entry for ONE student: validate, write the course grade, write the exam. + * The dialog and the bulk endpoints both use this method. The transcript grade stays unchanged. + * + * @return array|string ['savedPruefung', 'lvgesamtnote', 'verlauf'] or an error message + */ + private function savePruefungFuerStudent($pruefung_id, $student_uid, $lva_id, $stsem, $lehreinheit_id, $note, $punkte, $datum, $mitarbeiter_uid = null) + { + // §7 and §11: every exam must take place before the grade entry deadline. The kommissionelle + // Prüfung is the important case, because it decides if the student loses a semester. + $fristError = $this->pruefungsdatumNachFrist($stsem, $datum, $student_uid); + if($fristError !== null) return $fristError; + + // validate before any write + $editError = $this->validatePruefungEdit($student_uid, $lva_id, $stsem, $note, $datum, $pruefung_id); + if($editError !== null) return $editError; + + if($pruefung_id === null || $pruefung_id === '') { + $addError = $this->validatePruefungAdd($student_uid, $lva_id, $stsem, $note, $datum); + if($addError !== null) return $addError; + } + + // the client does not always send it; without it the insert fails on the NOT NULL column + if(!$lehreinheit_id) { + $resLe = $this->LehrveranstaltungModel->getLeByStudent($student_uid, $stsem, $lva_id); + if(!isError($resLe) && hasData($resLe)) $lehreinheit_id = current(getData($resLe))->lehreinheit_id; + } + + $jetzt = date("Y-m-d H:i:s"); + + // Read without the filter. A grade that is not released still exists. You must update it. + // A new insert breaks the primary key. + $bestehendeLvNote = $this->getLvGesamtnoteRow($lva_id, $student_uid, $stsem); + + // this decides if the course grade is the implicit first attempt; read it before the update + $origLvNote = $bestehendeLvNote ? $bestehendeLvNote->note : null; + $lvgesamtnote = $bestehendeLvNote; + + if($bestehendeLvNote === null) { + $lvgesamtnote = $this->createLvGesamtnote($lva_id, $student_uid, $stsem, $note, $punkte, $lehreinheit_id, $mitarbeiter_uid); + + $this->logLib->logInfoDB(array('pruefung: lvnote angelegt', $student_uid, $lva_id, $stsem, + $note, $punkte, getAuthUID(), getAuthPersonId())); + } else { + $id = $this->LvgesamtnoteModel->update( + [$bestehendeLvNote->student_uid, $bestehendeLvNote->studiensemester_kurzbz, $bestehendeLvNote->lehrveranstaltung_id], + array( + 'note' => $note, + 'punkte' => $punkte, + 'benotungsdatum' => $jetzt, + 'updateamum' => $jetzt, + 'updatevon' => getAuthUID() + ) + ); + + if($id) { + $res = $this->LvgesamtnoteModel->load($id->retval); + if(hasData($res)) $lvgesamtnote = getData($res)[0]; + } + + $this->logLib->logInfoDB(array('pruefung: lvnote aktualisiert', $student_uid, $lva_id, $stsem, + $note, $punkte, getAuthUID(), getAuthPersonId())); + } + + // save pruefung after updating lvnote, since pruefungspunkte get loaded by lv punkte + $pruefungenChanged = $this->savePruefungstermin($pruefung_id, $student_uid, $lva_id, $stsem, $lehreinheit_id, $note, $punkte, $datum, $origLvNote, $mitarbeiter_uid); + if(is_string($pruefungenChanged)) return $pruefungenChanged; + + $pruefungenChanged['lvgesamtnote'] = $lvgesamtnote; + + return $pruefungenChanged; + } + + /** + * Creates an exam or updates an exam. The exam type has no meaning here: the position comes + * from the history. The code writes pruefungstyp_kurzbz, but it never reads it. + * + * @param int $pruefung_id gesetzt = diesen Datensatz bearbeiten, null = neuer Antritt + * @return array|string the changed exams or a translated error message + */ + private function savePruefungstermin($pruefung_id, $student_uid, $lva_id, $stsem, $lehreinheit_id, $note, $punkte, $datum, $origLvNote = null, $mitarbeiter_uid = null) + { + // no exam without a course grade (a grade that is not released also counts) + if($this->getLvGesamtnoteRow($lva_id, $student_uid, $stsem) === null) { + return $this->p->t('benotungstool', 'c4keineLvNoteEingetragen'); + } + + $pruefungen = $this->VerlaufLib->getPruefungen($student_uid, $lva_id, $stsem); + + $status = []; + + // send $grades reference to moodle addon + Events::trigger( + 'getEntschuldigungsStatusForStudentOnDate', + function & () use (&$status) + { + return $status; + }, + [ + 'student_uid' => $student_uid, + 'datum' => $datum + ] + ); + + if(count($status) > 0 && $status[0] == true) { + $entschuldigtNote = $this->VerlaufLib->getSpecialNotes()['entschuldigt']; + + // the limit was checked on the grade BEFORE the override, therefore check it again + if($entschuldigtNote !== null + && !$this->VerlaufLib->ueberschreitetNotenLimit($pruefungen, $entschuldigtNote, $pruefung_id)) { + $note = $entschuldigtNote; + } + } + + $jetzt = date("Y-m-d H:i:s"); + + $pruefungenChanged = []; + + // edit: only the addressed record, no type change and no new record + if($pruefung_id !== null && $pruefung_id !== '') { + $id = $this->LePruefungModel->update( + $pruefung_id, + array( + 'updateamum' => $jetzt, + 'updatevon' => getAuthUID(), + 'note' => $note, + 'punkte' => $punkte, + 'datum' => $datum, + 'anmerkung' => "" + ) + ); + if($id) { + $res = $this->LePruefungModel->load($id->retval); + if(hasData($res)) $pruefungenChanged['savedPruefung'] = getData($res); + } + + $this->logLib->logInfoDB(array('pruefung updated', $res, getAuthUID(), getAuthPersonId())); + + if(!isset($pruefungenChanged['savedPruefung'])) { + return $this->p->t('benotungstool', 'c4pruefungNichtGespeichert', [$student_uid]); + } + + $pruefungenChanged['verlauf'] = $this->buildVerlaufSummary($student_uid, $lva_id, $stsem); + return $pruefungenChanged; + } + + $verlauf = $this->VerlaufLib->buildVerlauf($pruefungen, $origLvNote); + $rolle = $verlauf->naechsteRolle; + + // one action makes one exam; the release creates the first attempt (upsertErstantritt) + $typ = ($rolle === PruefungsverlaufLib::ROLLE_ERSTANTRITT) + ? $this->VerlaufLib->legacyTypFuerAntritt(1) + : $this->VerlaufLib->legacyTypFuerWiederholung($verlauf); + + $id = $this->LePruefungModel->insert( + array( + 'lehreinheit_id' => $lehreinheit_id, + 'student_uid' => $student_uid, + 'mitarbeiter_uid' => $this->benotenderMitarbeiter($lehreinheit_id, $mitarbeiter_uid), + 'note' => $note, + 'punkte' => $punkte, + 'pruefungstyp_kurzbz' => $typ, + 'datum' => $datum, + 'anmerkung' => "", + 'insertamum' => $jetzt, + 'insertvon' => getAuthUID(), + 'updateamum' => null, + 'updatevon' => null, + 'ext_id' => null + ) + ); + if($id) { + $res = $this->LePruefungModel->load($id->retval); + if(hasData($res)) $pruefungenChanged['savedPruefung'] = getData($res); + } + + $this->logLib->logInfoDB(array('pruefung inserted ('.$rolle.')', $res, getAuthUID(), getAuthPersonId())); + + // savedPruefung is the proof of success. Without this guard a failed insert (for example + // a missing lehreinheit_id) tells the client that the write was successful. + if(!isset($pruefungenChanged['savedPruefung'])) { + return $this->p->t('benotungstool', 'c4pruefungNichtGespeichert', [$student_uid]); + } + + $pruefungenChanged['verlauf'] = $this->buildVerlaufSummary($student_uid, $lva_id, $stsem); + return $pruefungenChanged; + } + + /** The history for the client. Each write answer contains it, so the client calculates nothing. */ + private function buildVerlaufSummary($student_uid, $lva_id, $stsem) + { + $lvNote = $this->getLvGesamtnoteRow($lva_id, $student_uid, $stsem); + $verlauf = $this->VerlaufLib->getVerlauf( + $student_uid, $lva_id, $stsem, + $lvNote ? $lvNote->note : null, + $this->getZeugnisnote($lva_id, $student_uid, $stsem) + ); + + return $this->verlaufSummary($verlauf, true, $lvNote !== null); + } + + /** + * @param bool $withPruefungen add the exams (for the write paths) or send the counters only + * @return array + */ + private function verlaufSummary($verlauf, $withPruefungen = false, $hatLvNote = null) + { + $summary = array( + 'antrittCount' => $verlauf->antrittCount, + 'maxAntritte' => $verlauf->maxAntritte, + 'canAdd' => $verlauf->canAdd, + 'terminal' => $verlauf->terminal, + 'erstantrittMoeglich' => $verlauf->erstantrittMoeglich, + 'naechsteRolle' => $verlauf->naechsteRolle, + // credited: the row is visible, but you cannot select it and it gets no exams + 'angerechnet' => $verlauf->angerechnet, + 'hatLvNote' => $hatLvNote // ungefiltert, also inklusive noch nicht freigegebener + ); + + if($withPruefungen) $summary['pruefungen'] = $verlauf->pruefungen; + + return $summary; + } + + /** + * Guards an EDIT ($pruefung_id is set; if not, validatePruefungAdd applies). The date must stay + * between the two adjacent exams. An exam with a later date locks the grade. + * + * @return string|null lokalisierte Fehlermeldung oder null + */ + private function validatePruefungEdit($student_uid, $lva_id, $stsem, $newNote, $newDatum, $pruefung_id) + { + if($pruefung_id === null || $pruefung_id === '') return null; // add, not an edit + + $zeugnisNote = $this->getZeugnisnote($lva_id, $student_uid, $stsem); + if($this->VerlaufLib->istAnrechnungsnote($zeugnisNote)) { + return $this->p->t('benotungstool', 'c4angerechnetKeinePruefung', [$student_uid]); + } + + $pruefungen = $this->VerlaufLib->getPruefungen($student_uid, $lva_id, $stsem); + if(count($pruefungen) === 0) return null; + + $verlauf = $this->VerlaufLib->buildVerlauf($pruefungen, null, $zeugnisNote); + + // the record being edited + $current = null; + foreach($verlauf->pruefungen as $p) { + if($p->pruefung_id == $pruefung_id) { $current = $p; break; } + } + if($current === null) return null; + + // The student administration owns the exams that use no attempt (zusKommPruef), so this tool + // does not validate them. The kommissionelle Prüfung itself is a normal attempt now. + if(in_array($current->pruefungstyp_kurzbz, $this->VerlaufLib->getTypenOhneAntritt())) return null; + + $currentDate = substr((string)$current->datum, 0, 10); + $new = substr((string)$newDatum, 0, 10); + + // the limits come from the two exams before and after this one + $lower = null; $upper = null; + foreach($verlauf->pruefungen as $p) { + if($p->pruefung_id == $current->pruefung_id) continue; + + $d = substr((string)$p->datum, 0, 10); + if($d === '') continue; + + if($d < $currentDate) { if($lower === null || $d > $lower) $lower = $d; } + elseif($d > $currentDate) { if($upper === null || $d < $upper) $upper = $d; } + } + + // grade is locked once a later attempt exists + if($this->VerlaufLib->hatSpaeterenTermin($verlauf, $current->pruefung_id) && $newNote != $current->note) { + return $this->p->t('benotungstool', 'pruefungNoteLocked', [$student_uid]); + } + + // datum must stay strictly between the neighbouring exam dates + if(($lower !== null && $new <= $lower) || ($upper !== null && $new >= $upper)) { + return $this->p->t('benotungstool', 'pruefungDatumOutOfRange', [$student_uid]); + } + + // an edit may also change the note (e.g. to 'entschuldigt') -> keep the occurrence limit + if($this->VerlaufLib->ueberschreitetNotenLimit($verlauf->pruefungen, $newNote, $current->pruefung_id)) { + return $this->p->t('benotungstool', 'noteOccuranceLimitReached', [$student_uid]); + } + + return null; + } + + /** + * Guards a NEW attempt. Rule A is the attempt limit, and nothing follows a final exam. Rule B + * is the chronological order. Rule C is the occurrence limit of a grade. + * + * @return string|null lokalisierte Fehlermeldung oder null + */ + private function validatePruefungAdd($student_uid, $lva_id, $stsem, $note, $datum) + { + $zeugnisNote = $this->getZeugnisnote($lva_id, $student_uid, $stsem); + if($this->VerlaufLib->istAnrechnungsnote($zeugnisNote)) { + return $this->p->t('benotungstool', 'c4angerechnetKeinePruefung', [$student_uid]); + } + + $pruefungen = $this->VerlaufLib->getPruefungen($student_uid, $lva_id, $stsem); + + // the implicit first attempt; read it before the caller changes it + $lvRow = $this->getLvGesamtnoteRow($lva_id, $student_uid, $stsem); + $lvNote = $lvRow ? $lvRow->note : null; + + $verlauf = $this->VerlaufLib->buildVerlauf($pruefungen, $lvNote, $zeugnisNote); + + // A: the first attempt only materialises the course grade, therefore it adds no attempt + if($verlauf->naechsteRolle !== PruefungsverlaufLib::ROLLE_ERSTANTRITT && !$verlauf->canAdd) { + return $this->p->t('benotungstool', 'maxAntritteReached', [$student_uid, $verlauf->maxAntritte]); + } + + // B: no attempt on the same day as an existing exam, and no attempt before it + $newDate = substr((string)$datum, 0, 10); + foreach($pruefungen as $p) { + $d = substr((string)$p->datum, 0, 10); + if($d !== '' && $d >= $newDate) { + return $this->p->t('benotungstool', 'pruefungDatumBeforeExisting', [$student_uid]); + } + } + + // C: the occurrence limit, for example one 'entschuldigt' only + if($this->VerlaufLib->ueberschreitetNotenLimit($pruefungen, $note, null)) { + return $this->p->t('benotungstool', 'noteOccuranceLimitReached', [$student_uid]); + } + + return null; + } + + /** + * The active grades, keyed by their PK. getNoten() sends the client the same set, so both sides + * judge a grade by the same flags. + * + * @return array note (string) => zeile aus lehre.tbl_note + */ + private function aktiveNoten() + { + if($this->aktiveNotenCache !== null) return $this->aktiveNotenCache; + + $this->aktiveNotenCache = array(); + + $result = $this->NoteModel->getAllActive(); + if(!isError($result) && hasData($result)) { + foreach(getData($result) as $n) $this->aktiveNotenCache[(string)$n->note] = $n; + } + + return $this->aktiveNotenCache; + } + + /** + * Guards a DIRECT api write of the course grade (saveNotenvorschlag and its bulk variant). All + * three rules are also in the cell editor, but the API and the CSV import reach the server + * without it. The order follows the editor: grade, exam, transcript. + * + * @return string|null lokalisierte Fehlermeldung oder null + */ + private function validateNotenvorschlag($lva_id, $student_uid, $stsem, $note) + { + $noten = $this->aktiveNoten(); + $wert = trim((string)$note); + + // the editor offers the lehre grades only; an administrative grade belongs to the transcript + if(!isset($noten[$wert]) || !$noten[$wert]->lehre) { + return $this->p->t('benotungstool', 'c4noteNichtInLehre', [$student_uid]); + } + + // as soon as an exam exists the grade belongs to the attempt history and follows its rules + if(count($this->VerlaufLib->getPruefungen($student_uid, $lva_id, $stsem)) > 0) { + return $this->p->t('benotungstool', 'c4notenvorschlagGesperrt', [$student_uid]); + } + + // a transcript grade can forbid the teacher to overwrite it, for example 'intern angerechnet'. + // An unknown or inactive grade locks nothing, which is what the editor does as well. + $zeugnisnote = trim((string)$this->getZeugnisnote($lva_id, $student_uid, $stsem)); + if(isset($noten[$zeugnisnote]) && !$noten[$zeugnisnote]->lkt_ueberschreibbar) { + return $this->p->t('benotungstool', 'c4zeugnisnoteGesperrt', [$student_uid]); + } + + return null; + } + + /** + * Checks the DATE of the exam against the deadline. enforceNoteneintragungsfrist compares the + * current time, which is a different question: a user can enter an exam today and give it a + * date after the deadline. + * + * @return string|null a translated error message, or null if the date is permitted + */ + private function pruefungsdatumNachFrist($sem_kurzbz, $datum, $student_uid) + { + if(!$this->config->item('CIS_GESAMTNOTE_NOTENEINTRAGUNGSFRIST')) return null; + + $deadline = $this->computeNoteneintragungsfrist($sem_kurzbz); + if($deadline === null) return null; + + $tag = substr((string) $datum, 0, 10); + if($tag === '' || $tag <= $deadline->format('Y-m-d')) return null; + + return $this->p->t('benotungstool', 'pruefungsdatumNachFrist', [$student_uid, $deadline->format('d.m.Y')]); + } + + /** Stops the request if the grade entry deadline of the semester has passed. */ + private function enforceNoteneintragungsfrist($sem_kurzbz) + { + if(!$this->config->item('CIS_GESAMTNOTE_NOTENEINTRAGUNGSFRIST')) return; + + $deadline = $this->computeNoteneintragungsfrist($sem_kurzbz); + if($deadline === null) return; + + if(new DateTime() > $deadline) { + $this->terminateWithError( + $this->p->t('benotungstool', 'noteneintragungsfristVorbei', [$deadline->format('d.m.Y')]), + 'general' + ); + } + } + + /** The deadline from '{SS|WS}yyyy': SS in the same year, WS in the next year. @return DateTime|null */ + private function computeNoteneintragungsfrist($sem_kurzbz) + { + if(!is_string($sem_kurzbz) || strlen($sem_kurzbz) < 6) return null; + + $type = strtoupper(substr($sem_kurzbz, 0, 2)); + $year = (int) substr($sem_kurzbz, 2, 4); + if($year <= 0) return null; + + if($type === 'SS') { + $cfg = $this->config->item('NOTENEINTRAGUNGSFRIST_SS'); + $deadlineYear = $year; + } elseif($type === 'WS') { + $cfg = $this->config->item('NOTENEINTRAGUNGSFRIST_WS'); + $deadlineYear = $year + 1; + } else { + return null; + } + + $month = (is_array($cfg) && isset($cfg['month'])) ? (int)$cfg['month'] : ($type === 'SS' ? 11 : 5); + $day = (is_array($cfg) && isset($cfg['day'])) ? (int)$cfg['day'] : 15; + + $deadline = new DateTime(); + $deadline->setDate($deadlineYear, $month, $day); + $deadline->setTime(23, 59, 59); + return $deadline; + } + + /** + * POST METHOD + * expects 'sem_kurzbz', 'lv_id', 'student_uid', 'note' + * Method that sets lv_note of student in lva and semester from provided Points/Grade Selection. + * Updates the note & benotungsdatum, which is key in the noten state offen/freigegeben/changed + */ + public function saveNotenvorschlag() { + $result = $this->getPostJSON(); + + if(!property_exists($result, 'lv_id') || !property_exists($result, 'sem_kurzbz') || + !property_exists($result, 'student_uid') || !property_exists($result, 'note')) { + $this->terminateWithError($this->p->t('global', 'missingParameters'), 'general'); + } + + $lv_id = $result->lv_id; + $student_uid = $result->student_uid; + $sem_kurzbz = $result->sem_kurzbz; + $note = $result->note; + $punkte = $result->punkte; + + $this->assertLvAccess($lv_id, $sem_kurzbz); + + // examination rules: no entry and no change after the grade entry deadline + $this->enforceNoteneintragungsfrist($sem_kurzbz); + + // In the points mode the grading scale decides, not the grade from the client. If not, you + // get a course grade that contradicts its own points. Without points there is nothing to + // derive (for example a proposal from Moodle partial grades), then the given grade applies. + if(CIS_GESAMTNOTE_PUNKTE && $punkte !== null && $punkte !== '') { + $abgeleitet = $this->noteAusPunkten($punkte, $lv_id, $sem_kurzbz, $student_uid); + if(is_string($abgeleitet)) $this->terminateWithError($abgeleitet, 'general'); + $note = $abgeleitet; + } + + $fehler = $this->validateNotenvorschlag($lv_id, $student_uid, $sem_kurzbz, $note); + if($fehler !== null) $this->terminateWithError($fehler, 'general'); + + $result = $this->LvgesamtnoteModel->getLvGesamtNoteVorschlag($lv_id, $student_uid, $sem_kurzbz); + +// $this->addMeta('LvgesamtnoteModelresult', $result); + + if(!isError($result) && hasData($result)) { + $lvgesamtnote = getData($result)[0]; + + $id = $this->LvgesamtnoteModel->update( + [$lvgesamtnote->student_uid, $lvgesamtnote->studiensemester_kurzbz, $lvgesamtnote->lehrveranstaltung_id], + array( + 'note' => $note, + 'punkte' => $punkte, + 'benotungsdatum' => date("Y-m-d H:i:s"), + 'updateamum' => date("Y-m-d H:i:s"), + 'updatevon' => getAuthUID() + ) + ); + + if($id) { + $res = $this->LvgesamtnoteModel->load($id->retval); + if(hasData($res)) $lvgesamtnote = getData($res)[0]; + } + + $this->logLib->logInfoDB(array('saveNotenvorschlag update lv gesamtnote',$res, getAuthUID(), getAuthPersonId())); + + } else if(!isError($result) && !hasData($result)) { + $id = $this->LvgesamtnoteModel->insert( + array( + 'student_uid' => $student_uid, + 'lehrveranstaltung_id' => $lv_id, + 'studiensemester_kurzbz' => $sem_kurzbz, + 'note' => $note, + 'punkte' => $punkte, + 'mitarbeiter_uid' => $this->benotenderMitarbeiterFuerStudent($lv_id, $student_uid, $sem_kurzbz), + 'benotungsdatum' => date("Y-m-d H:i:s"), + 'freigabedatum' => null, + 'freigabevon_uid' => null, + 'bemerkung' => null, + 'updateamum' => null, + 'updatevon' => null, + 'insertamum' => date("Y-m-d H:i:s"), + 'insertvon' => getAuthUID() + ) + ); + if($id) { + $res = $this->LvgesamtnoteModel->load($id->retval); + if(hasData($res)) $lvgesamtnote = getData($res)[0]; + } + + $this->logLib->logInfoDB(array('saveNotenvorschlag insert lv gesamtnote',$res, getAuthUID(), getAuthPersonId())); + } + + $this->terminateWithSuccess(array($lvgesamtnote)); + } + + /** + * POST METHOD + * expects 'sem_kurzbz', 'lv_id', 'noten' + * Bulk variant of saveNotenvorschlag, used when importing grades from csv. + * The answer uses the uid as the key. Each row holds the course grade or an error message. + */ + public function saveNotenvorschlagBulk() { + $result = $this->getPostJSON(); + + if(!property_exists($result, 'lv_id') || !property_exists($result, 'sem_kurzbz') || + !property_exists($result, 'noten')) { + $this->terminateWithError($this->p->t('global', 'missingParameters'), 'general'); + } + + $lv_id = $result->lv_id; + $sem_kurzbz = $result->sem_kurzbz; + $noten = $result->noten; + + $this->assertLvAccess($lv_id, $sem_kurzbz); + + // examination rules: no entry and no change after the grade entry deadline + $this->enforceNoteneintragungsfrist($sem_kurzbz); + + $retLvNoten = []; + + foreach($noten as $note) + { + + $result = $this->LvgesamtnoteModel->getLvGesamtNoteVorschlag($lv_id, $note->uid, $sem_kurzbz); +// $this->addMeta($note->uid.'$result', $result); + + if(CIS_GESAMTNOTE_PUNKTE) { + $abgeleitet = $this->noteAusPunkten($note->punkte, $lv_id, $sem_kurzbz, $note->uid); + // no grade can be derived: skip the row, but do not stop the full request + if(is_string($abgeleitet)) { $retLvNoten[$note->uid] = $abgeleitet; continue; } + $note->note = $abgeleitet; + } + + // one bad row must not stop the import, so the message goes into this row + $fehler = $this->validateNotenvorschlag($lv_id, $note->uid, $sem_kurzbz, $note->note); + if($fehler !== null) { $retLvNoten[$note->uid] = $fehler; continue; } + + if(!isError($result) && hasData($result)) { + $lvgesamtnote = getData($result)[0]; + + $id = $this->LvgesamtnoteModel->update( + [$lvgesamtnote->student_uid, $lvgesamtnote->studiensemester_kurzbz, $lvgesamtnote->lehrveranstaltung_id], + array( + 'note' => trim($note->note), + 'punkte' => $note->punkte, + 'benotungsdatum' => date("Y-m-d H:i:s"), + 'updateamum' => date("Y-m-d H:i:s"), + 'updatevon' => getAuthUID() + ) + ); + + if($id) { + $res = $this->LvgesamtnoteModel->load($id->retval); + if(hasData($res)) $lvgesamtnote = getData($res)[0]; + } + + $this->logLib->logInfoDB(array('saveNotenvorschlagBulk update lv gesamtnote',$res, getAuthUID(), getAuthPersonId())); + + } else if(!isError($result) && !hasData($result)) { + $id = $this->LvgesamtnoteModel->insert( + array( + 'student_uid' => $note->uid, + 'lehrveranstaltung_id' => $lv_id, + 'studiensemester_kurzbz' => $sem_kurzbz, + 'note' => trim($note->note), + 'punkte' => $note->punkte, + 'mitarbeiter_uid' => $this->benotenderMitarbeiterFuerStudent($lv_id, $note->uid, $sem_kurzbz), + 'benotungsdatum' => date("Y-m-d H:i:s"), + 'freigabedatum' => null, + 'freigabevon_uid' => null, + 'bemerkung' => null, + 'updateamum' => null, + 'updatevon' => null, + 'insertamum' => date("Y-m-d H:i:s"), + 'insertvon' => getAuthUID() + ) + ); + if($id) { + $res = $this->LvgesamtnoteModel->load($id->retval); + if(hasData($res)) $lvgesamtnote = getData($res)[0]; + } + + $this->logLib->logInfoDB(array('saveNotenvorschlagBulk insert lv gesamtnote',$res, getAuthUID(), getAuthPersonId())); + } + + $retLvNoten[$note->uid] = $lvgesamtnote; + } + + $this->terminateWithSuccess($retLvNoten); + } + + /** + * POST METHOD + * expects 'uids', 'datum', optional 'note'/'punkte' + * The bulk variant of saveStudentPruefung. It creates one exam for several students. + * Without a selected grade it writes "noch nicht eingetragen". + */ + public function createPruefungen() { + $payload = $this->getPostJSON(); + + if(!property_exists($payload, 'uids') || !property_exists($payload, 'datum')) { + $this->terminateWithError($this->p->t('global', 'missingParameters'), 'general'); + } + + $uids = $payload->uids; + $datum = $payload->datum; + $lva_id = $payload->lva_id; + + $stsem = $payload->sem_kurzbz; + + $this->assertLvAccess($lva_id, $stsem); + + // examination rules: no entry after the grade entry deadline + $this->enforceNoteneintragungsfrist($stsem); + + $ret = []; + + $note = property_exists($payload, 'note') ? $payload->note : null; + $punkte = property_exists($payload, 'punkte') ? $payload->punkte : null; + + // the points win: the grade then comes from the grading scale + if(CIS_GESAMTNOTE_PUNKTE && $punkte !== null && $punkte !== '' && $punkte >= 0) { + $resNote = $this->NotenschluesselaufteilungModel->getNote($punkte, $lva_id, $stsem); + $note = $this->getDataOrTerminateWithError($resNote); + } + + // without a selection the exam has no grade + if($note === null || $note === '') { + $resNote = $this->NoteModel->getNochNichtEingetragenNote(); + $note = getData($resNote)[0]->note; + $punkte = null; + } + + // the dialog sends the teacher when all selected students share one Lehreinheit + $mitarbeiter_uid = property_exists($payload, 'mitarbeiter_uid') ? $payload->mitarbeiter_uid : null; + + // the same core as the dialog; each row gets its own error message + foreach ($uids as $student) { + $ret[$student->uid] = $this->savePruefungFuerStudent( + null, $student->uid, $lva_id, $stsem, $student->lehreinheit_id, $note, $punkte, $datum, + $mitarbeiter_uid + ); + } + + $this->logLib->logInfoDB(array('createPruefungen',$ret, getAuthUID(), getAuthPersonId())); + + $this->terminateWithSuccess($ret); + } + + /** + * POST METHOD + * expects 'lv_id', 'sem_kurzbz', 'pruefungen' + * Bulk variant of saveStudentPruefung, used when importing pruefungsdata from csv with available noten. + */ + public function savePruefungenBulk() { + $result = $this->getPostJSON(); + + if(!property_exists($result, 'lv_id') || !property_exists($result, 'sem_kurzbz') || + !property_exists($result, 'pruefungen')) { + $this->terminateWithError($this->p->t('global', 'missingParameters'), 'general'); + } + + $lv_id = $result->lv_id; + $sem_kurzbz = $result->sem_kurzbz; + $pruefungen = $result->pruefungen; + + $this->assertLvAccess($lv_id, $sem_kurzbz); + + // examination rules: no entry after the grade entry deadline + $this->enforceNoteneintragungsfrist($sem_kurzbz); + + $ret = []; + + foreach ($pruefungen as $pruefung) { + + if(CIS_GESAMTNOTE_PUNKTE) { + $note = $this->noteAusPunkten($pruefung->punkte, $lv_id, $sem_kurzbz, $pruefung->uid); + // no grade can be derived: skip the row, but do not stop the full request + if(is_string($note)) { $ret[$pruefung->uid] = $note; continue; } + $pruefung->note = $note; + } + + // the same as the dialog in the table, but for each import row + $ret[$pruefung->uid] = $this->savePruefungFuerStudent( + null, $pruefung->uid, $lv_id, $sem_kurzbz, $pruefung->lehreinheit_id, + $pruefung->note, $pruefung->punkte, $pruefung->datum, + property_exists($pruefung, 'mitarbeiter_uid') ? $pruefung->mitarbeiter_uid : null + ); + } + + $this->logLib->logInfoDB(array('savePruefungenBulk',$ret, getAuthUID(), getAuthPersonId())); + + $this->terminateWithSuccess($ret); + } + + /** + * Creates or updates the first attempt. The release calls it. + * + * The date is the benotungsdatum of the course grade, which is the moment the teacher recorded + * the assessment. The examination rules ask for the date of the last performance (§8), but the + * system does not hold that date, because many courses have no exam date at all. The old tool + * used the benotungsdatum for the same row, so the value stays comparable with the old data. + * + * It applies only while one exam or no exam exists. After that the course grade holds the last + * grade, and it must not overwrite attempt 1. A credited grade changes nothing. + */ + /** + * The grade from the points for ONE bulk row. It gives the grade, or a translated error message + * if no grade can be derived. The caller then skips the row and continues with the other rows. + * + * @return mixed|string + */ + private function noteAusPunkten($punkte, $lv_id, $sem_kurzbz, $uid) + { + $result = $this->NotenschluesselaufteilungModel->getNote($punkte, $lv_id, $sem_kurzbz); + if(isError($result)) return getError($result); + + $note = getData($result); + if($note === null || $note === '') { + return $this->p->t('benotungstool', 'c4punkteKeineNoteErmittelt', [$uid]); + } + + return $note; + } + + private function upsertErstantritt($lva_id, $student_uid, $stsem, $note, $punkte, $datum) + { + if($this->VerlaufLib->istAnrechnungsnote($this->getZeugnisnote($lva_id, $student_uid, $stsem))) return; + + $pruefungen = $this->VerlaufLib->getPruefungen($student_uid, $lva_id, $stsem); + if(count($pruefungen) > 1) return; + + $jetzt = date("Y-m-d H:i:s"); + + if(count($pruefungen) === 1) { + $this->LePruefungModel->update( + $pruefungen[0]->pruefung_id, + array( + 'note' => $note, + 'punkte' => $punkte, + 'datum' => $datum, + 'updateamum' => $jetzt, + 'updatevon' => getAuthUID() + ) + ); + + $this->logLib->logInfoDB(array('erstantritt aktualisiert (freigabe)', $student_uid, getAuthUID(), getAuthPersonId())); + return; + } + + // the server finds the lehreinheit_id; it does not use the value from the client + $resLe = $this->LehrveranstaltungModel->getLeByStudent($student_uid, $stsem, $lva_id); + if(isError($resLe) || !hasData($resLe)) return; + $le = current(getData($resLe)); + + $this->LePruefungModel->insert( + array( + 'lehreinheit_id' => $le->lehreinheit_id, + 'student_uid' => $student_uid, + 'mitarbeiter_uid' => $this->benotenderMitarbeiter($le->lehreinheit_id), + 'note' => $note, + 'punkte' => $punkte, + 'pruefungstyp_kurzbz' => $this->VerlaufLib->legacyTypFuerAntritt(1), + 'datum' => $datum, + 'anmerkung' => "", + 'insertamum' => $jetzt, + 'insertvon' => getAuthUID(), + 'updateamum' => null, + 'updatevon' => null, + 'ext_id' => null + ) + ); + + $this->logLib->logInfoDB(array('erstantritt angelegt (freigabe)', $student_uid, getAuthUID(), getAuthPersonId())); + } + + /** + * The teachers of one Lehreinheit, from lehre.tbl_lehreinheitmitarbeiter. + * + * @return array + */ + private function lehrendeDerLehreinheit($lehreinheit_id) + { + if(!$lehreinheit_id) return array(); + + $this->load->model('education/Lehreinheitmitarbeiter_model', 'LehreinheitmitarbeiterModel'); + $result = $this->LehreinheitmitarbeiterModel->getLektorenByLe($lehreinheit_id); + if(isError($result) || !hasData($result)) return array(); + + $lehrende = getData($result); + usort($lehrende, function($a, $b) { + return strcmp($a->mitarbeiter_uid, $b->mitarbeiter_uid); + }); + + return $lehrende; + } + + /** + * The uid of the person who gave the grade. It goes into lehre.tbl_pruefung and into + * campus.tbl_lvgesamtnote. + * + * Historically only teachers used this tool, so the caller was always right. An assistant may + * do the data entry too, but the grade still comes from the teacher. The row must therefore + * carry the teacher of the Lehreinheit, not the person who types. insertvon and updatevon keep + * the caller, because they answer a different question. + * + * Order: a valid selection wins, then the caller if the caller teaches this Lehreinheit, then + * the only teacher. With several teachers and no selection the first one wins, because an + * assistant is never the correct answer. Without any teacher the caller stays. + * + * @return string + */ + private function benotenderMitarbeiter($lehreinheit_id, $gewaehlt = null) + { + $uids = array(); + foreach($this->lehrendeDerLehreinheit($lehreinheit_id) as $lehrend) $uids[] = $lehrend->mitarbeiter_uid; + + if(count($uids) === 0) return getAuthUID(); + if($gewaehlt !== null && $gewaehlt !== '' && in_array($gewaehlt, $uids)) return $gewaehlt; + if(in_array(getAuthUID(), $uids)) return getAuthUID(); + + return $uids[0]; + } + + /** + * The same answer for a course grade, which has no Lehreinheit of its own. The Lehreinheit of + * the student decides. + * + * @return string + */ + private function benotenderMitarbeiterFuerStudent($lva_id, $student_uid, $stsem, $gewaehlt = null) + { + $lehreinheit_id = null; + + $resLe = $this->LehrveranstaltungModel->getLeByStudent($student_uid, $stsem, $lva_id); + if(!isError($resLe) && hasData($resLe)) $lehreinheit_id = current(getData($resLe))->lehreinheit_id; + + return $this->benotenderMitarbeiter($lehreinheit_id, $gewaehlt); + } + + /** The transcript grade or null. Credited grades are there, not in the course grade. @return mixed|null */ + private function getZeugnisnote($lva_id, $student_uid, $stsem) + { + $this->load->model('education/Zeugnisnote_model', 'ZeugnisnoteModel'); + + $res = $this->ZeugnisnoteModel->load([ + 'studiensemester_kurzbz' => $stsem, + 'student_uid' => $student_uid, + 'lehrveranstaltung_id' => $lva_id + ]); + + return (!isError($res) && hasData($res)) ? getData($res)[0]->note : null; + } + + /** + * Reads the course grade WITHOUT the filter. getLvGesamtNoten() uses 'freigabedatum < NOW()' + * and therefore hides a new grade. In this tool this wrapper is always the correct one. + */ + private function getLvGesamtnoteRow($lva_id, $student_uid, $stsem) + { + $res = $this->LvgesamtnoteModel->getLvGesamtNoteVorschlag($lva_id, $student_uid, $stsem); + return (!isError($res) && hasData($res)) ? getData($res)[0] : null; + } + + /** Creates a course grade. The student administration writes it to the transcript. @return stdClass|null */ + private function createLvGesamtnote($lva_id, $student_uid, $stsem, $note, $punkte, $lehreinheit_id = null, $mitarbeiter_uid = null) + { + $jetzt = date("Y-m-d H:i:s"); + + // the caller knows the Lehreinheit here, so no second lookup is needed + $benotender = $lehreinheit_id + ? $this->benotenderMitarbeiter($lehreinheit_id, $mitarbeiter_uid) + : $this->benotenderMitarbeiterFuerStudent($lva_id, $student_uid, $stsem, $mitarbeiter_uid); + + $id = $this->LvgesamtnoteModel->insert( + array( + 'student_uid' => $student_uid, + 'lehrveranstaltung_id' => $lva_id, + 'studiensemester_kurzbz' => $stsem, + 'note' => $note, + 'punkte' => $punkte, + 'mitarbeiter_uid' => $benotender, + 'benotungsdatum' => $jetzt, + 'freigabedatum' => null, + 'freigabevon_uid' => null, + 'bemerkung' => null, + 'updateamum' => null, + 'updatevon' => null, + 'insertamum' => $jetzt, + 'insertvon' => getAuthUID() + ) + ); + if(!$id) return null; + + $res = $this->LvgesamtnoteModel->load($id->retval); + return hasData($res) ? getData($res)[0] : null; + } + + private function getAnwesenheiten($prestudent_ids, $lv_id, $sem_kurzbz) { + + $anwesenheiten = []; + try { + $downloadFunc = function ($anwesenheitenResult) use (&$anwesenheiten) { + // map result rows by prestudent_uid to retrieve them by that key later on + foreach ($anwesenheitenResult as $anw) { + $anwesenheiten[$anw->prestudent_id] = $anw->sum; + } + }; + + Events::trigger( + 'getAnwesenheitenForLvAndSemester', + $prestudent_ids, + $lv_id, + $sem_kurzbz, + $downloadFunc + ); + } catch (Throwable $t) { + $this->addMeta('getAnwesenheitenForLvAndSemester', $t->getMessage()); + } + + return $anwesenheiten; + + } + + /** + * GET METHOD + * expects 'lehreinheit_id', 'lv_id', 'sem_kurzbz' + * The teachers of one Lehreinheit. The dialog needs them when the Lehreinheit has more than one, + * because the exam must carry the teacher and not the person who types. + */ + public function getLehrendeFuerLehreinheit() { + $lehreinheit_id = $this->input->get('lehreinheit_id'); + $lv_id = $this->input->get('lv_id'); + $sem_kurzbz = $this->input->get('sem_kurzbz'); + + if(!$lehreinheit_id || !$lv_id || !$sem_kurzbz) { + $this->terminateWithError($this->p->t('global', 'missingParameters'), 'general'); + } + + $this->assertLvAccess($lv_id, $sem_kurzbz); + + $lehrende = array(); + foreach($this->lehrendeDerLehreinheit($lehreinheit_id) as $l) { + $lehrende[] = array( + 'mitarbeiter_uid' => $l->mitarbeiter_uid, + 'vorname' => $l->vorname, + 'nachname' => $l->nachname + ); + } + + $this->terminateWithSuccess($lehrende); + } + + public function getNoteByPunkte() { + $result = $this->getPostJSON(); + + if(!property_exists($result, 'punkte') + || !property_exists($result, 'lv_id') + || !property_exists($result, 'sem_kurzbz')) { + $this->terminateWithError($this->p->t('global', 'missingParameters'), 'general'); + } + + $punkte = $result->punkte; + $lv_id = $result->lv_id; + $sem_kurzbz = $result->sem_kurzbz; + + $result = $this->NotenschluesselaufteilungModel->getNote($punkte, $lv_id, $sem_kurzbz); + $data = $this->getDataOrTerminateWithError($result); + + $this->terminateWithSuccess($data); + + } + +} + diff --git a/application/controllers/api/frontend/v1/Ort.php b/application/controllers/api/frontend/v1/Ort.php index 0d8074fa9..c921c0c4f 100644 --- a/application/controllers/api/frontend/v1/Ort.php +++ b/application/controllers/api/frontend/v1/Ort.php @@ -25,7 +25,19 @@ if (! defined('BASEPATH')) exit('No direct script access allowed'); */ class Ort extends FHCAPI_Controller { - + // advanced filters of getRooms: tbl_ort column => filter type, the same list as in Raumsuche.js. + // text: contains, case-insensitive; select: equals; range: _min, _max; flag: true; exists: not null + const ADVANCED_FILTERS = [ + 'ort_kurzbz' => 'text', + 'bezeichnung' => 'text', + 'planbezeichnung' => 'text', + 'stockwerk' => 'range', + 'gebteil' => 'select', + 'm2' => 'range', + 'lehre' => 'flag', + 'content_id' => 'exists' + ]; + /** * Object initialization */ @@ -36,7 +48,10 @@ class Ort extends FHCAPI_Controller 'ContentID' => self::PERM_LOGGED, 'getOrtKurzbzContent' => self::PERM_LOGGED, 'getRooms' => self::PERM_LOGGED, - 'getTypes' => self::PERM_LOGGED + 'getTypes' => self::PERM_LOGGED, + 'getRoomInfo' => self::PERM_LOGGED, + 'getStandorte' => self::PERM_LOGGED, + 'getAdvancedFilterOptions' => self::PERM_LOGGED ]); $this->load->model('ressource/Ort_model', 'OrtModel'); @@ -56,6 +71,17 @@ class Ort extends FHCAPI_Controller $this->form_validation->set_rules('datum','Datum','required'); $this->form_validation->set_rules('von','Uhrzeit Von','required|regex_match[/^[0-9]{2}:[0-9]{2}$/]'); $this->form_validation->set_rules('bis','Uhrzeit Bis','required|regex_match[/^[0-9]{2}:[0-9]{2}$/]'); + $this->form_validation->set_rules('standort_id','Standort','regex_match[/^([0-9]+|none)$/]'); + foreach (self::ADVANCED_FILTERS as $column => $type) { + if ($type == 'range') { + $this->form_validation->set_rules($column.'_min', $column, 'numeric'); + $this->form_validation->set_rules($column.'_max', $column, 'numeric'); + } elseif ($type == 'flag' || $type == 'exists') { + $this->form_validation->set_rules($column, $column, 'in_list[true]'); + } else { + $this->form_validation->set_rules($column, $column, 'max_length[255]'); + } + } if($this->form_validation->run() == FALSE) { $this->terminateWithValidationErrors($this->form_validation->error_array()); } @@ -65,6 +91,7 @@ class Ort extends FHCAPI_Controller $bis = $this->input->get('bis', TRUE); $typ = $this->input->get('typ', TRUE); $personenanzahl = $this->input->get('personenanzahl', TRUE); + $standort_id = $this->input->get('standort_id', TRUE); $this->load->model('ressource/Mitarbeiter_model', 'MitarbeiterModel'); @@ -74,22 +101,36 @@ class Ort extends FHCAPI_Controller $vonStunde = getData($this->StundeModel->getStundeForTime($von))[0]->stunde; $bisStunde = getData($this->StundeModel->getStundeForTime($bis))[0]->stunde; + $this->load->model('ressource/Ort_model', 'OrtModel'); + $params = array(); - $qry = "SELECT DISTINCT tbl_ort.* + $qry = "SELECT DISTINCT tbl_ort.*, tbl_standort.bezeichnung AS standort FROM public.tbl_ort JOIN public.tbl_ortraumtyp USING(ort_kurzbz) - WHERE aktiv AND lehre AND ort_kurzbz NOT LIKE '\\\\_%'"; + LEFT JOIN public.tbl_standort USING(standort_id) + WHERE aktiv AND reservieren"; if($typ) { $params[] = $typ; - $qry.= "AND raumtyp_kurzbz = ?"; + $qry.= " AND raumtyp_kurzbz = ?"; } + if($standort_id === 'none') { // rooms without a Standort + $qry.= " AND standort_id IS NULL"; + } elseif($standort_id) { + $params[] = $standort_id; + $qry.= " AND standort_id = ?"; + } else { + $qry.= " AND standort_id IS NOT NULL"; + } + + $qry.= $this->advancedFilterSql($params); + if(!$isMitarbeiter) { // students are only allowed to get a subset defined by config $qry.= ' AND raumtyp_kurzbz IN ?'; $params[] = $this->config->item('roomtypes_student'); $this->addMeta('config', $this->config->item('roomtypes_student')); } - $qry.= "AND (max_person>= ? OR max_person is null)"; + $qry.= " AND (max_person>= ? OR max_person is null)"; $params[] = $personenanzahl; $qry.=" AND ort_kurzbz NOT IN @@ -127,6 +168,41 @@ class Ort extends FHCAPI_Controller $this->terminateWithSuccess(getData($result)); } + /** + * Standorte of the rooms that pass the base filter of getRooms + */ + public function getStandorte() + { + $qry = "SELECT DISTINCT standort_id, tbl_standort.bezeichnung + FROM public.tbl_ort JOIN public.tbl_standort USING(standort_id) + WHERE aktiv AND reservieren + ORDER BY tbl_standort.bezeichnung"; + + $result = $this->OrtModel->execReadOnlyQuery($qry); + + $this->terminateWithSuccess($this->getDataOrTerminateWithError($result)); + } + + /** + * Values of the select filters: column => distinct values of the rooms that pass the base filter of getRooms + */ + public function getAdvancedFilterOptions() + { + $options = []; + foreach (self::ADVANCED_FILTERS as $column => $type) { + if ($type != 'select') + continue; + + $result = $this->OrtModel->execReadOnlyQuery("SELECT DISTINCT ".$column." AS value + FROM public.tbl_ort + WHERE aktiv AND reservieren AND ".$column." <> '' + ORDER BY ".$column); + $options[$column] = array_column($this->getDataOrTerminateWithError($result), 'value'); + } + + $this->terminateWithSuccess($options); + } + /** * Gets a JSON body via HTTP POST and provides the parameters */ @@ -174,5 +250,60 @@ class Ort extends FHCAPI_Controller $this->terminateWithSuccess($content); } + + public function getRoomInfo() + { + $ortKurzbz = $this->input->get("ortKurzbz"); + + $roomResult = $this->OrtModel->getRoomInfo($ortKurzbz); + $roomData = $this->getDataOrTerminateWithError($roomResult); + $room = count($roomData) ? $roomData[0] : null; + + $this->terminateWithSuccess($room); + } + + //------------------------------------------------------------------------------------------------------------------ + // Private methods + + /** + * SQL conditions of the advanced filters in the GET parameters, adds their values to $params + */ + private function advancedFilterSql(&$params) + { + // no xss_clean: the values are bound, and it could change a select value so that it no longer equals the column + $sql = ''; + foreach (self::ADVANCED_FILTERS as $column => $type) { + if ($type == 'range') { + $min = $this->input->get($column.'_min'); + $max = $this->input->get($column.'_max'); + if ($min !== null && $min !== '') { + $sql.= " AND tbl_ort.".$column." >= ?::numeric"; + $params[] = $min; + } + if ($max !== null && $max !== '') { + $sql.= " AND tbl_ort.".$column." <= ?::numeric"; + $params[] = $max; + } + continue; + } + + $value = $this->input->get($column); + if ($value === null || $value === '') + continue; + + if ($type == 'flag') { + $sql.= " AND tbl_ort.".$column; + } elseif ($type == 'exists') { + $sql.= " AND tbl_ort.".$column." IS NOT NULL"; + } elseif ($type == 'select') { + $sql.= " AND tbl_ort.".$column." = ?"; + $params[] = $value; + } else { + $sql.= " AND strpos(lower(tbl_ort.".$column."), lower(?)) > 0"; + $params[] = $value; + } + } + return $sql; + } } diff --git a/application/controllers/api/frontend/v1/OtherLvPlan.php b/application/controllers/api/frontend/v1/OtherLvPlan.php index 1807f0a4a..e6616cc99 100644 --- a/application/controllers/api/frontend/v1/OtherLvPlan.php +++ b/application/controllers/api/frontend/v1/OtherLvPlan.php @@ -1,6 +1,6 @@ . + */ + +if (!defined('BASEPATH')) + exit('No direct script access allowed'); + +class Permission extends FHCAPI_Controller +{ + + /** + * Object initialization + */ + public function __construct() + { + parent::__construct([ + 'getPermissions' => self::PERM_LOGGED, + ]); + } + + //------------------------------------------------------------------------------------------------------------------ + // Public methods + + /** + * retrieves permissions for auth user + * @access public + */ + public function getPermissions() + { + $permissionsParam = $this->input->post("permissions"); + if (!$permissionsParam || !is_array($permissionsParam)) { + $this->terminateWithError("Invalid parameter!"); + } + + $permissions = []; + foreach ($permissionsParam as $permission) { + $permissions[$permission] = $this->permissionlib->isBerechtigt($permission); + } + + $this->terminateWithSuccess($permissions); + } + + // ----------------------------------------------------------------------------------------------------------------- + // Private methods + +} + diff --git a/application/controllers/api/frontend/v1/Reservierung.php b/application/controllers/api/frontend/v1/Reservierung.php new file mode 100644 index 000000000..6ee9b293e --- /dev/null +++ b/application/controllers/api/frontend/v1/Reservierung.php @@ -0,0 +1,67 @@ +. + */ + +if (! defined('BASEPATH')) exit('No direct script access allowed'); + +class Reservierung extends FHCAPI_Controller +{ + + /** + * Object initialization + */ + public function __construct() + { + parent::__construct([ + 'getMyReservation' => 'basis/cis:r', + 'deleteReservation' => 'lehre/reservierung:rw' + ]); + + $this->load->model('ressource/Reservierung_model', 'ReservierungModel'); + + } + + /** + * Retrieves all reservations from lehre.vw_reservation of the logged in user + */ + public function getMyReservation() + { + $result = $this->ReservierungModel->getMyReservation(); + $data = $this->getDataOrTerminateWithError($result); + + $this->terminateWithSuccess($data); + } + + /** + * deletes a reservation by ID + */ + public function deleteReservation() + { + $reservierung_id = $this->input->post('reservierung_id'); + + + if ($reservierung_id === NULL || trim((string)$reservierung_id) === '') { + $this->terminateWithError($this->p->t('global', 'wrongParameters'), 'general'); + } + + $result = $this->ReservierungModel->deleteReservation($reservierung_id); + $data = $this->getDataOrTerminateWithError($result); + + $this->terminateWithSuccess($data); + } +} + diff --git a/application/controllers/api/frontend/v1/Studgang.php b/application/controllers/api/frontend/v1/Studgang.php index 82aebb666..001706c92 100644 --- a/application/controllers/api/frontend/v1/Studgang.php +++ b/application/controllers/api/frontend/v1/Studgang.php @@ -27,7 +27,7 @@ class Studgang extends FHCAPI_Controller { parent::__construct([ 'getStudiengangInfo'=> self::PERM_LOGGED, - + 'getDegreePrograms'=> self::PERM_LOGGED, ]); $this->load->model('organisation/Studiengang_model', 'StudiengangModel'); @@ -43,15 +43,46 @@ class Studgang extends FHCAPI_Controller //------------------------------------------------------------------------------------------------------------------ // Public methods - public function getStudiengangInfo(){ - $isMitarbeiter = $this->MitarbeiterModel->isMitarbeiter(getAuthUID()); - $isMitarbeiter = $this->getDataOrTerminateWithError($isMitarbeiter); - if($isMitarbeiter) { - $this->terminateWithSuccess(null); + public function getDegreePrograms() { + $entitledDegreePrograms = $this->permissionlib->getSTG_isEntitledFor("basis/news"); + if (!$entitledDegreePrograms) { + $this->terminateWithError("User is not entitled for any degree program"); } - + + $activeDegreePrograms = $this->StudiengangModel->getDegreePrograms(); + $activeDegreePrograms= $this->getDataOrTerminateWithError($activeDegreePrograms); + + $activeDegreePrograms = array_values(array_filter($activeDegreePrograms, function($degreeProgram) use ($entitledDegreePrograms) { + return in_array($degreeProgram->studiengang_kz, $entitledDegreePrograms); + })); + + $this->terminateWithSuccess($activeDegreePrograms); + } + + public function getStudiengangInfo(){ + $studiengang_kz = $this->input->get('studiengang_kz', true); + $semester = $this->input->get('semester', true); + + // without a Studiengang the info of the logged in Student is returned + if (!is_numeric($studiengang_kz)) + { + $isMitarbeiter = $this->MitarbeiterModel->isMitarbeiter(getAuthUID()); + $isMitarbeiter = $this->getDataOrTerminateWithError($isMitarbeiter); + if($isMitarbeiter) { + $this->terminateWithSuccess(null); + } + + $studiengang_kz = null; + $semester = null; + } + else + { + $studiengang_kz = (int)$studiengang_kz; + $semester = is_numeric($semester) ? (int)$semester : null; + } + // fetches the Studiengang Information which is used next to the news - $studiengangInfo = $this->StudiengangModel->getStudiengangInfoForNews(); + $studiengangInfo = $this->StudiengangModel->getStudiengangInfoForNews($studiengang_kz, $semester); $studiengangInfo= $this->getDataOrTerminateWithError($studiengangInfo); $this->terminateWithSuccess($studiengangInfo); } diff --git a/application/controllers/api/frontend/v1/Studiensemester.php b/application/controllers/api/frontend/v1/Studiensemester.php new file mode 100644 index 000000000..44f236a3c --- /dev/null +++ b/application/controllers/api/frontend/v1/Studiensemester.php @@ -0,0 +1,67 @@ +. + */ + +if (! defined('BASEPATH')) exit('No direct script access allowed'); +class Studiensemester extends FHCAPI_Controller +{ + + private $_ci; + + /** + * Object initialization + */ + public function __construct() + { + parent::__construct([ + 'getStudiensemester'=> self::PERM_LOGGED, + + ]); + + $this->_ci =& get_instance(); + + $this->_ci->load->model('organisation/Studiensemester_model', 'StudiensemesterModel'); + + + } + + //------------------------------------------------------------------------------------------------------------------ + // Public methods + + /** + * GET METHOD + * returns List of all studiensemester as well as current one + */ + public function getStudiensemester() + { + $this->_ci->StudiensemesterModel->addOrder("start", "DESC"); + $result = $this->_ci->StudiensemesterModel->load(); + + $studiensemester = getData($result); + $result = $this->_ci->StudiensemesterModel->getAkt(); + $aktuell = getData($result); + + $this->terminateWithSuccess(array($studiensemester, $aktuell)); + } + + //------------------------------------------------------------------------------------------------------------------ + // Private methods + + + +} + diff --git a/application/controllers/api/frontend/v1/StudstatusAntraege.php b/application/controllers/api/frontend/v1/StudstatusAntraege.php new file mode 100644 index 000000000..44dc4134d --- /dev/null +++ b/application/controllers/api/frontend/v1/StudstatusAntraege.php @@ -0,0 +1,145 @@ +. + */ + +if (! defined('BASEPATH')) exit('No direct script access allowed'); + +class StudstatusAntraege extends FHCAPI_Controller +{ + + /** + * Object initialization + */ + public function __construct() + { + parent::__construct([ + 'getTodos' => self::PERM_LOGGED + ]); + + //load models + $this->load->model('education/Studierendenantrag_model', 'StudierendenantragModel'); + $this->load->model('person/Benutzerfunktion_model', 'BenutzerfunktionModel'); + + } + + //------------------------------------------------------------------------------------------------------------------ + // Public methods + + /** + * gets all todos of antraegen of the user with stg leitungsfunction + * or list of open antraege for user with assistance function + * @access public + * @return array || [] + */ + public function getTodos() + { + $uid = getAuthUID(); + + //at first get studiengang with leitungsfunktion + $result = $this->BenutzerfunktionModel->getSTGLByUID($uid); + + if(hasData($result)) + { + $funktionen = getData($result); + + $studiengaenge = []; + + foreach ($funktionen as $funktion) { + $studiengaenge[] = $funktion->studiengang_kz; + } + + $dataAntrage = []; + + $result = $this->StudierendenantragModel->getOpenAntraegeForStgl($studiengaenge); + + if (isError($result)) { + $this->terminateWithError(getError($result), self::ERROR_TYPE_GENERAL); + } + $dataAntrage[] = getData($result) ?: []; + $statusRole = 1; + + $this->terminateWithSuccess([count($dataAntrage) > 0 ? $dataAntrage[0] : $dataAntrage, $statusRole]); + + } + + //TODO delete after check if needed or not + //get studiengaenge of assistance (regarding benutzerfunktion) + //$result = $this->BenutzerfunktionModel->getSTGAssByUID($uid); + /* if(hasData($result)) { + $statusRole = 2; + $funktionen = getData($result); + + $studiengaenge = []; + + foreach ($funktionen as $funktion) { + $studiengaenge[] = $funktion->studiengang_kz; + } + + $ci->addMeta('stgAss', $studiengaenge); + $dataAntrage = []; + + foreach ($studiengaenge as $studiengang) + { + $result = $this->StudierendenantragModel->getOpenAntraegeForAss($studiengang); + + if (isError($result)) { + $this->terminateWithError(getError($result), self::ERROR_TYPE_GENERAL); + } + if(hasData($result)) { + $dataAntrage[] = getData($result); + $statusRole = 2; + } + } + } + else + { + $statusRole = 0; + $dataAntrage = []; + //$this->terminateWithSuccess([[], $statusRole]); + }*/ + + //get studiengaenge of assistance (regarding rights) + $stgAss = $this->permissionlib->getSTG_isEntitledFor('student/studierendenantrag'); + if(!is_array($stgAss)) + { + $statusRole = 0; + $this->terminateWithSuccess([[], $statusRole]); + } + else { + $statusRole = 2; + $studiengaenge = []; + + foreach ($stgAss as $stg) { + $studiengaenge[] = (int) $stg; + } + + $dataAntrage = []; + $result = $this->StudierendenantragModel->getOpenAntraegeForAss($studiengaenge); + + if (isError($result)) { + $this->terminateWithError(getError($result), self::ERROR_TYPE_GENERAL); + } + if(hasData($result)) { + $dataAntrage[] = getData($result); + $statusRole = 2; + } + + $this->terminateWithSuccess([count($dataAntrage) > 0 ? $dataAntrage[0] : $dataAntrage, $statusRole]); + + } + } +} \ No newline at end of file diff --git a/application/controllers/api/frontend/v1/Zeitsperren.php b/application/controllers/api/frontend/v1/Zeitsperren.php new file mode 100644 index 000000000..aadc13958 --- /dev/null +++ b/application/controllers/api/frontend/v1/Zeitsperren.php @@ -0,0 +1,469 @@ + ['cis/eigene_zeitsperren:r', 'admin:r', 'assistenz:r'], + 'getTypenZeitsperren' => self::PERM_LOGGED, + 'getTypenErreichbarkeit' => self::PERM_LOGGED, + 'getStunden' => self::PERM_LOGGED, + 'loadZeitsperre' => ['cis/eigene_zeitsperren:r', 'admin:r', 'assistenz:r'], + 'add' => ['cis/eigene_zeitsperren:rw', 'admin:rw', 'assistenz:rw'], + 'update' => ['cis/eigene_zeitsperren:rw', 'admin:rw', 'assistenz:rw'], + 'delete' => ['cis/eigene_zeitsperren:rw', 'admin:rw', 'assistenz:rw'], + ]); + + // Load Libraries + $this->load->library('VariableLib', ['uid' => getAuthUID()]); + $this->load->library('form_validation'); + + // Load language phrases + $this->loadPhrases([ + 'ui', + 'person', + 'zeitsperren' + ]); + + // Load models + $this->load->model('ressource/Zeitsperre_model', 'ZeitsperreModel'); + $this->load->model('ressource/Zeitsperretyp_model', 'ZeitsperretypModel'); + $this->load->model('ressource/Erreichbarkeit_model', 'ErreichbarkeitModel'); + $this->load->model('ressource/Stunde_model', 'StundeModel'); + $this->load->model('ressource/Zeitaufzeichnung_model', 'ZeitaufzeichnungModel'); + } + + public function getZeitsperrenUser($uid) + { + //check if $uid is passedUser + $loggedInUser = getAuthUID(); + if($loggedInUser != $uid) { + $this->load->library('PermissionLib'); + $isAdmin = $this->permissionlib->isBerechtigt('admin'); + if(!$isAdmin) { + $this->terminateWithError($this->p->t('ui', 'noAdmin'), self::ERROR_TYPE_GENERAL); + } + } + + $result = $this->ZeitsperreModel->getZeitsperrenUser($uid); + + if (isError($result)) { + $this->terminateWithError(getError($result), self::ERROR_TYPE_GENERAL); + } + $this->terminateWithSuccess((getData($result) ?: [])); + } + + public function getTypenZeitsperren() + { + $excludedTypes = array('ZVerfueg'); + $result = $this->ZeitsperretypModel->getTypenZeitsperren($excludedTypes); + if (isError($result)) { + $this->terminateWithError(getError($result), self::ERROR_TYPE_GENERAL); + } + $this->terminateWithSuccess((getData($result) ?: [])); + } + + public function getTypenErreichbarkeit() + { + $result = $this->ErreichbarkeitModel->load(); + if (isError($result)) { + $this->terminateWithError(getError($result), self::ERROR_TYPE_GENERAL); + } + $this->terminateWithSuccess((getData($result) ?: [])); + } + + public function getStunden() + { + $this->StundeModel->addOrder('stunde', 'ASC'); + $result = $this->StundeModel->load(); + if (isError($result)) { + $this->terminateWithError(getError($result), self::ERROR_TYPE_GENERAL); + } + $this->terminateWithSuccess((getData($result) ?: [])); + } + + public function loadZeitsperre($zeitsperre_id) + { + $this->ZeitsperreModel->addSelect( + 'campus.tbl_zeitsperre.*, typ.*, + ma.person_id AS ma_person_id, ma.vorname AS ma_vorname, ma.nachname AS ma_nachname, + ma.titelpre AS ma_titelpre, ma.titelpost AS ma_titelpost' + ); + $this->ZeitsperreModel->addJoin('campus.tbl_zeitsperretyp typ', 'ON (typ.zeitsperretyp_kurzbz = campus.tbl_zeitsperre.zeitsperretyp_kurzbz)'); + $this->ZeitsperreModel->addJoin('public.tbl_benutzer ben', 'ON (ben.uid = campus.tbl_zeitsperre.vertretung_uid)', 'LEFT'); + $this->ZeitsperreModel->addJoin('public.tbl_person ma', 'ON (ma.person_id = ben.person_id)', 'LEFT'); + $result = $this->ZeitsperreModel->loadWhere( + array('zeitsperre_id' => $zeitsperre_id) + ); + + if (isError($result)) { + $this->terminateWithError(getError($result), self::ERROR_TYPE_GENERAL); + } + $this->terminateWithSuccess((current(getData($result)) ?: [])); + } + + public function add($mitarbeiter_uid) + { + $loggedInUser = getAuthUID(); + + if($mitarbeiter_uid != $loggedInUser) + $this->terminateWithError($this->p->t('ui', 'noPermission'), self::ERROR_TYPE_GENERAL); + + $this->form_validation->set_rules('zeitsperretyp_kurzbz', 'Grund Zeitsperre', 'required', [ + 'required' => $this->p->t('ui', 'error_fieldRequired', ['field' => 'Grund Zeitsperre']) + ]); + + $this->form_validation->set_rules('vondatum', 'VonDatum', 'required|is_valid_date', [ + 'required' => $this->p->t('ui', 'error_fieldRequired', ['field' => 'VonDatum']), + 'is_valid_date' => $this->p->t('ui', 'error_notValidDate', ['field' => 'VonDatum']) + ]); + + $this->form_validation->set_rules('bisdatum', 'BisDatum', 'required|is_valid_date|callback_check_von_bis_datum|callback_check_diff_intval', [ + 'required' => $this->p->t('ui', 'error_fieldRequired', ['field' => 'BisDatum']), + 'is_valid_date' => $this->p->t('ui', 'error_notValidDate', ['field' => 'BisDatum']), + 'check_von_bis_datum' => $this->p->t('zeitsperre', 'error_VonDatumGroesserAlsBisDatum'), + 'check_diff_intval' => $this->p->t('zeitsperre', 'error_zeitraumAuffallendHoch') + ]); + + if ($this->form_validation->run() == false) + { + $this->terminateWithValidationErrors($this->form_validation->error_array()); + } + + $bezeichnung = $this->input->post('bezeichnung'); + $vondatum = $this->input->post('vondatum'); + $vonstunde = $this->input->post('vonstunde'); + $bisdatum = $this->input->post('bisdatum'); + $bisstunde = $this->input->post('bisstunde'); + //$vonIso = $this->input->post('vonISO'); //Timestamp für Stunde + //$bisIso = $this->input->post('bisISO'); //Timestamp für Stunde + $erreichbarkeit_kurzbz = $this->input->post('erreichbarkeit_kurzbz'); + $vertretung_uid = $this->input->post('vertretung_uid'); + $zeitsperretyp_kurzbz = $this->input->post('zeitsperretyp_kurzbz'); + + //check if existing zeitsperre + $result = $this->ZeitsperreModel->getSperreByDate($mitarbeiter_uid, $vondatum, $vonstunde, true); + $data = $this->getDataOrTerminateWithError($result); + + if(hasData($result)) + { + $this->terminateWithError($this->p->t('zeitsperren', 'error_existingZeitsperre', ['typ'=> current($data)->zeitsperretyp_kurzbz]), self::ERROR_TYPE_GENERAL); + } + + //check if existing zeitaufzeichnung + if(in_array($zeitsperretyp_kurzbz, Zeitsperre_model::BLOCKIERENDE_ZEITSPERREN)) + { + $result = $this->ZeitsperreModel->existsZeitaufzeichnung($mitarbeiter_uid, $vondatum, $bisdatum); + + if(hasData($result)) + $this->terminateWithError($this->p->t('zeitsperren', 'error_existingZeitaufzeichnung'), self::ERROR_TYPE_GENERAL); + } + + $result = $this->ZeitsperreModel->insert( + [ + 'mitarbeiter_uid' => $mitarbeiter_uid, + 'bezeichnung' => $bezeichnung, + 'vondatum' => $vondatum, + 'vonstunde' => $vonstunde, + 'bisdatum' => $bisdatum, + 'bisstunde' => $bisstunde, + 'erreichbarkeit_kurzbz' => $erreichbarkeit_kurzbz, + 'zeitsperretyp_kurzbz' => $zeitsperretyp_kurzbz, + 'vertretung_uid' => $vertretung_uid, + 'insertvon' => $loggedInUser, + 'insertamum' => date('c'), + ] + ); + $data = $this->getDataOrTerminateWithError($result); + + if($zeitsperretyp_kurzbz == 'ZA') + { + $this->sendSanchoMailToVorgesetzten( + 'Sancho_Content_Zeitausgleich', + array( + 'beschreibung' =>$zeitsperretyp_kurzbz, + 'vonDatum' => (new DateTime($vondatum))->format('d.m.Y'), + 'bisDatum' => (new DateTime($bisdatum))->format('d.m.Y'), + 'Saldo'=> '' + ), + $this->p->t('zeitsperren', 'zeitausgleichNeu'), + $mitarbeiter_uid + ); + } + + $this->terminateWithSuccess($data); + } + + public function update($zeitsperre_id) + { + //check if loggedin User is owner of the zeitsperre + $loggedInUser = getAuthUID(); + $result = $this->ZeitsperreModel->load($zeitsperre_id); + $data = $this->getDataOrTerminateWithError($result); + $uid = current($data)->mitarbeiter_uid; + + if($uid != $loggedInUser) + $this->terminateWithError($this->p->t('ui', 'noPermission'), self::ERROR_TYPE_GENERAL); + + if(!$zeitsperre_id) + { + return $this->terminateWithError($this->p->t('ui', 'error_missingId', ['id'=> 'Zeitsperre_id']), self::ERROR_TYPE_GENERAL); + } + //get current params + $array_update = [ + 'bezeichnung', + 'vondatum', + 'vonstunde', + 'bisdatum', + 'bisstunde', + // 'vonISO', //Timestamp für Stunde + // 'bisISO', //Timestamp für Stunde + 'erreichbarkeit_kurzbz', + 'vertretung_uid', + 'zeitsperretyp_kurzbz', + 'mitarbeiter_uid', + ]; + $post = $this->input->post(); + $update = []; + + foreach ($array_update as $prop) + { + if (array_key_exists($prop, $post)) + { + $update[$prop] = $post[$prop]; + } + } + + // Validation + $rulesDefined = false; //necessary, otherwise CI validation will always be triggered, even without rules + foreach ($update as $key => $val) { + switch ($key) { + case 'zeitsperretyp_kurzbz': + $this->form_validation->set_rules( + $key, + 'Grund Zeitsperre', + 'required', + ['required' => $this->p->t('ui', 'error_fieldRequired', ['field'=>'Grund Zeitsperre'])] + ); + $rulesDefined = true; + break; + case 'vondatum': + $this->form_validation->set_rules( + $key, + 'VonDatum', + 'required|is_valid_date', + [ + 'required' => $this->p->t('ui', 'error_fieldRequired', ['field'=>'VonDatum']), + 'is_valid_date' => $this->p->t('ui', 'error_notValidDate', ['field'=>'VonDatum']) + ] + ); + $rulesDefined = true; + break; + case 'bisdatum': + $rules = 'required|is_valid_date'; + if (array_key_exists('vondatum', $update)) { + $rules .= '|callback_check_von_bis_datum|callback_check_diff_intval'; + } + $this->form_validation->set_rules( + $key, + 'BisDatum', + $rules, + [ + 'required' => $this->p->t('ui', 'error_fieldRequired', ['field'=>'BisDatum']), + 'is_valid_date' => $this->p->t('ui', 'error_notValidDate', ['field'=>'BisDatum']), + 'check_von_bis_datum' => $this->p->t('zeitsperre', 'error_VonDatumGroesserAlsBisDatum'), + 'check_diff_intval' => $this->p->t('zeitsperre', 'error_zeitraumAuffallendHoch') + ] + ); + $rulesDefined = true; + break; + } + } + + if ($rulesDefined && $this->form_validation->run() == false) { + $this->terminateWithValidationErrors($this->form_validation->error_array()); + } + + if(array_key_exists('vondatum', $post) || array_key_exists('bisdatum', $post)) + { + $result = $this->ZeitsperreModel->load($zeitsperre_id); + $data = $this->getDataOrTerminateWithError($result); + $data = current($data); + + $mitarbeiter_uid = array_key_exists('mitarbeiter_uid', $post) ? $update['mitarbeiter_uid'] : $data->mitarbeiter_uid; + $vondatum = array_key_exists('vondatum', $post) ? $update['vondatum'] : $data->vondatum; + $bisdatum = array_key_exists('bisdatum', $post) ? $update['bisdatum'] : $data->bisdatum; + $vonstunde = array_key_exists('vonstunde', $post) ? $update['vonstunde'] : $data->vonstunde; + $zeitsperretyp_kurzbz = array_key_exists('zeitsperretyp_kurzbz', $post) ? $update['zeitsperretyp_kurzbz'] : $data->zeitsperretyp_kurzbz; + + $result = $this->ZeitsperreModel->getSperreByDate($mitarbeiter_uid, $vondatum, $vonstunde, true); + $data = (array) $this->getDataOrTerminateWithError($result); + + $data = array_filter($data, function($zeitsperre) use ($zeitsperre_id) { + return $zeitsperre->zeitsperre_id != $zeitsperre_id; + }); + + if(count($data) > 0) + { + $this->terminateWithError($this->p->t('zeitsperren', 'error_existingZeitsperre', ['typ'=> current($data)->zeitsperretyp_kurzbz]), self::ERROR_TYPE_GENERAL); + } + + //check if existing zeitaufzeichnung + if(in_array($zeitsperretyp_kurzbz, Zeitsperre_model::BLOCKIERENDE_ZEITSPERREN)) + { + $result = $this->ZeitsperreModel->existsZeitaufzeichnung($mitarbeiter_uid, $vondatum, $bisdatum); + + if(hasData($result)) + $this->terminateWithError($this->p->t('zeitsperren', 'error_existingZeitaufzeichnung'), self::ERROR_TYPE_GENERAL); + } + } + + if (!empty($update)) { + $update['updatevon'] = $loggedInUser; + $update['updateamum'] = date('c'); + $result = $this->ZeitsperreModel->update($zeitsperre_id, $update); + + $data = $this->getDataOrTerminateWithError($result); + + if($zeitsperretyp_kurzbz == 'ZA') + { + $this->sendSanchoMailToVorgesetzten( + 'Sancho_Content_ZA_Aendern', + array( + 'beschreibung' =>$zeitsperretyp_kurzbz, + 'vonDatum' => (new DateTime($vondatum))->format('d.m.Y'), + 'bisDatum' => (new DateTime($bisdatum))->format('d.m.Y'), + 'Saldo'=> '' + ), + $this->p->t('zeitsperren', 'zeitausgleichGeaendert'), + $mitarbeiter_uid + ); + } + + $this->terminateWithSuccess($data); + } + else + $this->terminateWithSuccess("no update"); + } + + public function delete($zeitsperre_id) + { + + if (!is_numeric($zeitsperre_id) || (int)$zeitsperre_id <= 0) + { + $this->terminateWithError($this->p->t('ui', 'error_missingId', ['id' => 'Zeitsperre_id']), self::ERROR_TYPE_GENERAL); + } + + //check if loggedin User is owner of the zeitsperre + $loggedInUser = getAuthUID(); + $result = $this->ZeitsperreModel->load($zeitsperre_id); + $data = $this->getDataOrTerminateWithError($result); + $zeitsperre = (is_array($data) && count($data) > 0) ? current($data) : null; + $uid = (!is_null($zeitsperre)) ? $zeitsperre->mitarbeiter_uid : ''; + + if($uid != $loggedInUser) + $this->terminateWithError($this->p->t('ui', 'noPermission'), self::ERROR_TYPE_GENERAL); + + $result = $this->ZeitsperreModel->delete( + array('zeitsperre_id' => $zeitsperre_id) + ); + + if (isError($result)) { + $this->terminateWithError(getError($result), self::ERROR_TYPE_GENERAL); + } + + if($zeitsperre->zeitsperretyp_kurzbz == 'ZA') + { + $res = $this->sendSanchoMailToVorgesetzten( + 'Sancho_Mail_ZA_loeschen', + array( + 'beschreibung' =>$zeitsperre->zeitsperretyp_kurzbz, + 'vonDatum' => (new DateTime($zeitsperre->vondatum))->format('d.m.Y'), + 'bisDatum' => (new DateTime($zeitsperre->bisdatum))->format('d.m.Y') + ), + $this->p->t('zeitsperren', 'zeitausgleichGeloescht'), + $zeitsperre->mitarbeiter_uid + ); + } + + $this->terminateWithSuccess((getData($result) ?: [])); + } + + public function check_von_bis_datum($bisdatum) + { + $vondatum = $this->input->post('vondatum'); + + return $vondatum <= $bisdatum; + } + + public function check_diff_intval($bisdatum) + { + $vondatum = $this->input->post('vondatum'); + + // Intervall in days + $vonTs = strtotime($vondatum); + $bisTs = strtotime($bisdatum); + + $tage = ($bisTs - $vonTs) / 86400; + + // if intervall > 14 + return $tage <= 14; + } + + protected function sendSanchoMailToVorgesetzten($sancho_vorlage_kurzbz, + $sancho_vorlage_data, $subject, $mauid) + { + $this->load->helper('hlp_sancho_helper'); + + $this->load->model('ressource/Mitarbeiter_model', 'MitarbeiterModel'); + $this->load->model('person/Person_model', 'PersonModel'); + + $fullname = getData($this->PersonModel->getFullName($mauid)); + $vorgesetzte = (array) getData($this->MitarbeiterModel->getVorgesetzte($mauid)); + + $to = array(); + $vorgesetzte_namen = array(); + $mailsent = false; + $msg = $this->p->t('zeitsperren', 'konnteKeinFreigabemailVersendetWerden'); + + if(count($vorgesetzte) > 0) + { + foreach($vorgesetzte as $vg) + { + $to[] = $vg->vorgesetzter .'@' . DOMAIN; + $vorgesetzter_fullname = getData($this->PersonModel->getFullName($vg->vorgesetzter)); + $vorgesetzte_namen[] = $vorgesetzter_fullname; + } + + $sancho_vorlage_data['vorgesetzter'] = implode(', ', $vorgesetzte_namen); + $sancho_vorlage_data['nameMitarbeiter'] = $fullname; + + $mailsent = sendSanchoMail( + $sancho_vorlage_kurzbz, + $sancho_vorlage_data, + implode(',', $to), + $subject + ); + + if($mailsent) + { + $msg = $this->p->t('zeitsperren', 'VorgesetzteInformiert', + array('vorgesetzte' => $sancho_vorlage_data['vorgesetzter'])); + } + else + { + $msg = $this->p->t('zeitsperren', 'fehlerBeimSendenAufgetreten', + array('vorgesetzte' => $sancho_vorlage_data['vorgesetzter'])); + } + } + + $res = (object) array( + 'mailsent' => $mailsent, + 'msg' => $msg + ); + $this->addMeta('mailstatus', $res); + } +} diff --git a/application/controllers/api/frontend/v1/calendar/RoomPlan.php b/application/controllers/api/frontend/v1/calendar/RoomPlan.php new file mode 100644 index 000000000..522478f35 --- /dev/null +++ b/application/controllers/api/frontend/v1/calendar/RoomPlan.php @@ -0,0 +1,276 @@ +. + */ + +if (!defined('BASEPATH')) + exit('No direct script access allowed'); + +class RoomPlan extends FHCAPI_Controller +{ + + /** + * Object initialization + */ + public function __construct() + { + parent::__construct([ + 'addRoomReservation' => self::PERM_LOGGED, + 'deleteRoomReservation' => self::PERM_LOGGED, + 'getRoomCreationInfo' => self::PERM_LOGGED, + 'getGruppen' => self::PERM_LOGGED, + 'getLektor' => self::PERM_LOGGED, + 'getReservableMap' => self::PERM_LOGGED, + 'getGroupOptions' => self::PERM_LOGGED, + ]); + + $this->load->library('LogLib'); + $this->loglib->setConfigs(array( + 'classIndex' => 5, + 'functionIndex' => 5, + 'lineIndex' => 4, + 'dbLogType' => 'API', + 'dbExecuteUser' => 'RESTful API' + )); + + $this->load->library('form_validation'); + $this->load->library('PermissionLib'); + $this->load->library('StundenplanLib'); + + $this->loadPhrases(['ui']); + } + + //------------------------------------------------------------------------------------------------------------------ + // Public methods + + + + public function addRoomReservation() + { + $this->form_validation->set_rules('selectedStart', "Start", "required"); + $this->form_validation->set_rules('selectedEnd', "End", "required"); + $this->form_validation->set_rules('title', "Title", "required|max_length[10]"); + $this->form_validation->set_rules('beschreibung', "Beschreibung", "required|max_length[32]"); + $this->form_validation->set_rules('ort_kurzbz', "Ort", "required|max_length[16]"); + $this->form_validation->set_rules('studiengang', 'Studiengang', 'numeric'); + $this->form_validation->set_rules('semester', 'Semester', 'integer|greater_than_equal_to[0]'); + $this->form_validation->set_rules('verband', 'Verband', 'trim'); + $this->form_validation->set_rules('gruppe', 'Gruppe', 'trim'); + $this->form_validation->set_rules('spezialgruppe', 'Spezialgruppe', 'max_length[32]'); + $this->form_validation->set_rules('lektoren', 'Lektoren'); + + if (!$this->form_validation->run()) + $this->terminateWithValidationErrors($this->form_validation->error_array()); + + $start = $this->input->post('selectedStart'); + $end = $this->input->post('selectedEnd'); + $title = $this->input->post('title'); + $beschreibung = $this->input->post('beschreibung'); + $ort_kurzbz = $this->input->post('ort_kurzbz'); + + $studiengang_kz = $this->input->post('studiengang'); + $semester = $this->input->post('semester'); + $verband = $this->input->post('verband'); + $gruppe = $this->input->post('gruppe'); + $spezialgruppe = $this->input->post('spezialgruppe'); + $lektoren = $this->input->post('lektoren'); + + + $result = $this->stundenplanlib->addReservation($start, $end, $title, $beschreibung, $ort_kurzbz, $lektoren, $studiengang_kz, $semester, $verband, $gruppe, $spezialgruppe); + + if (isError($result)) + $this->terminateWithError($result); + + $this->terminateWithSuccess($result); + } + + public function deleteRoomReservation() + { + $reservierung_id = $this->input->post('reservierung_id'); + + $result = $this->stundenplanlib->deleteReservation($reservierung_id); + + if (isError($result)) + $this->terminateWithError($result); + + $this->terminateWithSuccess($result); + } + + public function getRoomCreationInfo() + { + $return_array = array('berechtigt' => false, 'studiengaenge' => []); + if (!$this->permissionlib->isBerechtigt('lehre/reservierung')) + $this->terminateWithSuccess($return_array); + + $stg_berechtigungen = $this->permissionlib->getSTG_isEntitledFor('lehre/reservierung'); + if (isEmptyArray($stg_berechtigungen)) + $this->terminateWithSuccess($return_array); + + $this->load->model('organisation/Studiengang_model', 'StudiengangModel'); + $this->StudiengangModel->addSelect('studiengang_kz, UPPER(CONCAT(typ, kurzbz)) as kuerzel, kurzbzlang'); + $this->StudiengangModel->addOrder('typ, kurzbz'); + $this->StudiengangModel->db->where_in('studiengang_kz', $stg_berechtigungen); + $studiengaenge = $this->StudiengangModel->loadWhere(array('aktiv' => true)); + + if (isError($studiengaenge)) + $this->terminateWithError($studiengaenge); + + $return_array['studiengaenge'] = hasData($studiengaenge) ? getData($studiengaenge) : []; + $return_array['berechtigt'] = true; + + $this->terminateWithSuccess($return_array); + } + + public function getGruppen() + { + $query = $this->input->get('query'); + if (is_null($query)) + $this->terminateWithError($this->p->t('ui', 'ungueltigeParameter'), self::ERROR_TYPE_GENERAL); + + $stg_berechtigungen = $this->permissionlib->getSTG_isEntitledFor('lehre/reservierung'); + + if (isEmptyArray($stg_berechtigungen)) + $this->terminateWithSuccess([]); + + $this->load->model('organisation/gruppe_model', 'GruppeModel'); + + $query_words = explode(' ', urldecode($query)); + + $this->GruppeModel->addOrder('gruppe_kurzbz'); + $this->GruppeModel->db->group_start(); + foreach ($query_words as $word) { + $this->GruppeModel->db->group_start(); + $this->GruppeModel->db->where('gruppe_kurzbz ILIKE', "%" . $word . "%"); + $this->GruppeModel->db->or_where('bezeichnung ILIKE', "%" . $word . "%"); + $this->GruppeModel->db->or_where('beschreibung ILIKE', "%" . $word . "%"); + $this->GruppeModel->db->or_where('orgform_kurzbz ILIKE', "%" . $word . "%"); + + if (is_numeric($word)) { + $this->GruppeModel->db->or_where('studiengang_kz', $word); + } + $this->GruppeModel->db->group_end(); + } + $this->GruppeModel->db->group_end(); + $this->GruppeModel->db->where_in('studiengang_kz', $stg_berechtigungen); + $gruppen = $this->GruppeModel->loadWhere(array('sichtbar' => true, 'lehre' => true)); + if (isError($gruppen)) + $this->terminateWithError($gruppen); + + $this->terminateWithSuccess(hasData($gruppen) ? getData($gruppen) : []); + } + + public function getLektor() + { + + $query = $this->input->get('query'); + if (is_null($query)) + $this->terminateWithError($this->p->t('ui', 'ungueltigeParameter'), self::ERROR_TYPE_GENERAL); + + $stg_berechtigungen = $this->permissionlib->getSTG_isEntitledFor('lehre/reservierung'); + + if (isEmptyArray($stg_berechtigungen)) + $this->terminateWithSuccess([]); + + $this->load->model('ressource/Mitarbeiter_model', 'MitarbeiterModel'); + + $query_words = explode(' ', urldecode($query)); + + $this->MitarbeiterModel->addSelect('uid, person_id, vorname, nachname'); + $this->MitarbeiterModel->addJoin('public.tbl_benutzer', 'uid = mitarbeiter_uid'); + $this->MitarbeiterModel->addJoin('public.tbl_person', 'person_id'); + $this->MitarbeiterModel->db->where('public.tbl_benutzer.aktiv', true); + $this->MitarbeiterModel->db->group_start(); + foreach ($query_words as $word) { + $this->MitarbeiterModel->db->group_start(); + $this->MitarbeiterModel->db->where('tbl_person.vorname ILIKE', "%" . $word . "%"); + $this->MitarbeiterModel->db->or_where('tbl_person.nachname ILIKE', "%" . $word . "%"); + $this->MitarbeiterModel->db->or_where('uid ILIKE', "%" . $word . "%"); + $this->MitarbeiterModel->db->group_end(); + } + $this->MitarbeiterModel->db->group_end(); + + $this->MitarbeiterModel->addOrder('nachname'); + $this->MitarbeiterModel->addOrder('vorname'); + $mitarbeiter = $this->MitarbeiterModel->load(); + if (isError($mitarbeiter)) + $this->terminateWithError($mitarbeiter); + + $this->terminateWithSuccess(hasData($mitarbeiter) ? getData($mitarbeiter) : []); + } + + public function getReservableMap($ort_kurzbz = null) + { + $this->form_validation->set_rules('start_date', "StartDate", "required"); + $this->form_validation->set_rules('end_date', "EndDate", "required"); + + if (!$this->form_validation->run()) + $this->terminateWithValidationErrors($this->form_validation->error_array()); + + // storing the post parameter in local variables + $start_date = $this->input->post('start_date', true); + $end_date = $this->input->post('end_date', true); + + $result = $this->stundenplanlib->getReservableMap($ort_kurzbz, $start_date, $end_date); + + $this->terminateWithSuccess(array('reservierbarMap' => hasData($result) ? getData($result) : [])); + } + + public function getGroupOptions() + { + $studiengangKz = $this->input->get('studiengangKz'); + + $this->load->model('organisation/Lehrverband_model', 'LehrverbandModel'); + $this->LehrverbandModel->addSelect('semester, verband, gruppe'); + $this->LehrverbandModel->db->where('aktiv', true); + $this->LehrverbandModel->db->where('studiengang_kz', $studiengangKz); + $lehrverbandResult = $this->LehrverbandModel->load(); + + if (isError($lehrverbandResult)) + $this->terminateWithError($lehrverbandResult); + + $lehrverbanden = hasData($lehrverbandResult) ? getData($lehrverbandResult) : []; + + $grouppedValues = []; + foreach ($lehrverbanden as $lehrverband) { + if (isset($grouppedValues[$lehrverband->semester]) && $lehrverband->verband !== ' ') { + $grouppedValues[$lehrverband->semester][] = $lehrverband; + } else if (!isset($grouppedValues[$lehrverband->semester]) && $lehrverband->verband !== ' ') { + $grouppedValues[$lehrverband->semester] = [$lehrverband]; + } else if (!isset($grouppedValues[$lehrverband->semester])) { + $grouppedValues[$lehrverband->semester] = []; + } + } + $grouppedValues = array_map( + function ($semester) { + $verbandGruppen = []; + foreach ($semester as $lehrverband) { + if (isset($verbandGruppen[$lehrverband->verband]) && $lehrverband->gruppe !== ' ') { + $verbandGruppen[$lehrverband->verband][] = $lehrverband->gruppe; + } else if (!isset($verbandGruppen[$lehrverband->verband]) && $lehrverband->gruppe !== ' ') { + $verbandGruppen[$lehrverband->verband] = [$lehrverband->gruppe]; + } else if (!isset($verbandGruppen[$lehrverband->verband])) { + $verbandGruppen[$lehrverband->verband] = []; + } + } + return $verbandGruppen; + }, + $grouppedValues + ); + + $this->terminateWithSuccess($grouppedValues); + } + +} diff --git a/application/controllers/api/frontend/v1/education/PaabgabeUebersicht.php b/application/controllers/api/frontend/v1/education/PaabgabeUebersicht.php new file mode 100644 index 000000000..f4efe2942 --- /dev/null +++ b/application/controllers/api/frontend/v1/education/PaabgabeUebersicht.php @@ -0,0 +1,218 @@ + self::PERM_LOGGED, + 'getPaAbgaben' => array('lehre/abgabetool:r'), + 'getStudiengaenge' => array('lehre/abgabetool:r'), + 'getTermine' => array('lehre/abgabetool:r'), + 'getPaAbgabetypen' => array('lehre/abgabetool:r'), + 'downloadZip' => array('lehre/abgabetool:r'), + //'downloadProjektarbeit' => array('lehre/abgabetool:r') + ]); + + $this->load->model('education/Paabgabe_model', 'PaabgabeModel'); + + $this->load->library('PermissionLib'); + + // Load Phrases + $this->loadPhrases([ + 'abgabetool' + ]); + } + + public function viewData() + { + $viewData = [ + "uid" => getAuthUID(), + // TODO create permission + "showEdit" => true, + ]; + + $this->terminateWithSuccess($viewData); + } + + /** + * Get Projektabgaben for search criteria. + */ + public function getPaAbgaben() + { + $studiengang_kz = $this->input->get('studiengang_kz'); + $abgabetyp_kurzbz = $this->input->get('abgabetyp_kurzbz'); + $abgabedatum = $this->input->get('abgabedatum'); + $personSearchString = $this->input->get('personSearchString'); + + $result = $this->PaabgabeModel->getPaAbgaben(self::ABGABE_TYPES, $studiengang_kz, $abgabetyp_kurzbz, $abgabedatum, $personSearchString); + + if (isError($result)) $this->terminateWithError(getError($result), self::ERROR_TYPE_DB); + + // check wether Abgabe is in visual library + if (hasData($result)) + { + Events::trigger('in_visual_library', getData($result)); + } + + $this->terminateWithSuccess(getData($result) ?: []); + } + + /** + * Get all Studiengänge for which user is entitled for + */ + public function getStudiengaenge() + { + $studiengang_kz_arr = $this->permissionlib->getSTG_isEntitledFor(self::DOWNLOAD_PERMISSION); + + if (!$studiengang_kz_arr) $this->terminateWithSuccess([]); + + $this->load->model('organisation/Studiengang_model', 'StudiengangModel'); + + $this->StudiengangModel->addSelect('tbl_studiengang.*, UPPER(tbl_studiengang.typ || tbl_studiengang.kurzbz) AS kuerzel', $studiengang_kz_arr); + $this->StudiengangModel->db->where_in('studiengang_kz', $studiengang_kz_arr); + $this->StudiengangModel->addOrder('typ, kurzbz'); + $result = $this->StudiengangModel->load(); + + if (isError($result)) $this->terminateWithError(getError($result), self::ERROR_TYPE_DB); + + $this->terminateWithSuccess((getData($result) ?: [])); + } + + /** + * Get projekt work due dates, depending on search criteria. + */ + public function getTermine() + { + $studiengang_kz = $this->input->get('studiengang_kz'); + $abgabetyp_kurzbz = $this->input->get('abgabetyp_kurzbz'); + + $result = $this->PaabgabeModel->getTermine(self::ABGABE_TYPES, $studiengang_kz, $abgabetyp_kurzbz); + + if (isError($result)) $this->terminateWithError(getError($result), self::ERROR_TYPE_DB); + + $this->terminateWithSuccess((getData($result) ?: [])); + } + + /** + * Get all submission types. + */ + public function getPaAbgabetypen() + { + // Load model PaabgabetypModel + $this->load->model('education/Paabgabetyp_model', 'PaabgabetypModel'); + + $this->PaabgabetypModel->addOrder('bezeichnung'); + $result = $this->PaabgabetypModel->load(); + + if (isError($result)) $this->terminateWithError(getError($result), self::ERROR_TYPE_DB); + + $this->terminateWithSuccess((getData($result) ?: [])); + } + + /** + * Download zip files with project works matching submission search criteria. + */ + public function downloadZip() + { + $studiengang_kz = $this->input->get('studiengang_kz'); + $abgabetyp_kurzbz = $this->input->get('abgabetyp_kurzbz'); + $abgabedatum = $this->input->get('abgabedatum'); + $personSearchString = $this->input->get('personSearchString'); + + if (!isset($studiengang_kz) && !isset($abgabetyp_kurzbz) && !isset($abgabedatum) && !isset($personSearchString)) + $this->terminateWithFileOutput('text/plain', $this->p->t('abgabetool', 'nichtsAusgewaehlt')); + + $this->load->library('zip'); + + $result = $this->PaabgabeModel->getPaAbgaben(self::ABGABE_TYPES, $studiengang_kz, $abgabetyp_kurzbz, $abgabedatum, $personSearchString); + + if (isError($result)) $this->terminateWithFileOutput('text/plain', getError($result)); + + $fileExists = false; + $studiengang_kuerzel = null; + + if (!hasData($result)) $this->terminateWithFileOutput('text/plain', $this->p->t('abgabetool', 'keineDateienVorhanden')); + + $abgaben = getData($result); + + foreach ($abgaben as $abgabe) + { + $path = PAABGABE_PATH.$abgabe->paabgabe_id.'_'.$abgabe->uid.'.pdf'; + if (file_exists($path)) + { + $fileExists = true; + $studiengang_kuerzel = $abgabe->studiengang_kuerzel; + $this->zip->read_file($path); + } + } + + if (!$fileExists) $this->terminateWithFileOutput('text/plain', $this->p->t('abgabetool', 'keineDateienVorhanden')); + + $studiengang_kz = $this->input->get('studiengang_kz'); + $zipFileName = 'Abgabe'.(isset($studiengang_kz) && isset($studiengang_kuerzel) ? '_'.$studiengang_kuerzel : '').'.zip'; + $this->zip->download($zipFileName); + } + + /** + * Download Projektarbeit document. + */ + //~ public function downloadProjektarbeit() + //~ { + //~ $paabgabe_id = $this->input->get('paabgabe_id'); + + //~ if (!is_numeric($paabgabe_id)) + //~ $this->terminateWithError($this->p->t('ui', 'error_missingId', ['id' => 'Abgabe ID']), self::ERROR_TYPE_GENERAL); + + //~ //$abgabeRes = $this->PaabgabeModel->getEndabgabe($projektarbeit_id); + //~ $this->PaabgabeModel->addSelect("paabgabe_id, student_uid, tbl_paabgabe.datum, tbl_paabgabe.abgabedatum, projekttyp_kurzbz, titel, titel_english, + //~ paabgabe_id || '_' || student_uid || '.pdf' AS filename"); + //~ $this->PaabgabeModel->addJoin('lehre.tbl_projektarbeit', 'projektarbeit_id'); + //~ $abgabeRes = $this->PaabgabeModel->load($paabgabe_id); + + //~ if (isError($abgabeRes)) + //~ show_error(getError($abgabeRes)); + + //~ if (hasData($abgabeRes)) + //~ { + //~ $endabgabe = getData($abgabeRes)[0]; + //~ $filepath = PAABGABE_PATH.$endabgabe->filename; + + //~ if (file_exists($filepath)) + //~ { + //~ $this->output + //~ ->set_status_header(200) + //~ ->set_content_type('application/pdf', 'utf-8') + //~ ->set_header('Content-Disposition: attachment; filename="'.$endabgabe->filename.'"') + //~ ->set_output(file_get_contents($filepath)) + //~ ->_display(); + //~ } + //~ else + //~ { + //~ show_error("File does not exist."); + //~ } + //~ } + //~ } +} diff --git a/application/controllers/api/frontend/v1/funktionen/Funktionen.php b/application/controllers/api/frontend/v1/funktionen/Funktionen.php index a3efcdb51..735aa6990 100644 --- a/application/controllers/api/frontend/v1/funktionen/Funktionen.php +++ b/application/controllers/api/frontend/v1/funktionen/Funktionen.php @@ -12,9 +12,9 @@ class Funktionen extends FHCAPI_Controller parent::__construct(array( 'getAllFunctions' => ['admin:r', 'assistenz:r'], 'getAllUserFunctions' => ['admin:r', 'assistenz:r'], - 'getOrgHeads' => ['admin:r', 'assistenz:r'], - 'getOrgetsForCompany' => ['admin:r', 'assistenz:r'], - 'getAllOrgUnits' => ['admin:r', 'assistenz:r'], + 'getOrgHeads' => self::PERM_LOGGED, + 'getOrgetsForCompany' => self::PERM_LOGGED, + 'getAllOrgUnits' => self::PERM_LOGGED, 'loadFunction' => ['admin:r', 'assistenz:r'], 'insertFunction' => ['admin:rw', 'assistenz:rw'], 'updateFunction' => ['admin:rw', 'assistenz:rw'], @@ -32,7 +32,6 @@ class Funktionen extends FHCAPI_Controller ]); // Load models - $this->load->model('extensions/FHC-Core-Personalverwaltung/Api_model', 'ApiModel'); $this->load->model('ressource/Funktion_model', 'FunktionModel'); $this->load->model('person/Benutzerfunktion_model', 'BenutzerfunktionModel'); diff --git a/application/controllers/api/frontend/v1/lv/Betreuungen.php b/application/controllers/api/frontend/v1/lv/Betreuungen.php new file mode 100644 index 000000000..2ca822649 --- /dev/null +++ b/application/controllers/api/frontend/v1/lv/Betreuungen.php @@ -0,0 +1,81 @@ +. + */ + +if (! defined('BASEPATH')) exit('No direct script access allowed'); + +class Betreuungen extends FHCAPI_Controller +{ + + /** + * Object initialization + */ + public function __construct() + { + parent::__construct([ + 'getBetreuungen' => self::PERM_LOGGED, + ]); + + } + + //------------------------------------------------------------------------------------------------------------------ + // Public methods + + public function getBetreuungen() + { + $semester = $this->input->get("semester"); + if (!$semester) $this->terminateWithError("Missing parameters!"); + + $personId = getAuthPersonId(); + + $betreuungenQuery = "SELECT + tbl_lehrveranstaltung.bezeichnung, + tbl_projektarbeit.titel, + (SELECT nachname || ' ' || (CASE WHEN wahlname IS NOT NULL THEN wahlname ELSE vorname END) + FROM public.tbl_benutzer + JOIN public.tbl_person USING(person_id) + WHERE uid=student_uid) + as name, + (SELECT uid + FROM public.tbl_benutzer + WHERE uid=student_uid) + as uid, + tbl_lehrveranstaltung.studiengang_kz, + tbl_lehrveranstaltung.semester, + tbl_studiengang.kurzbzlang as studiengang, + tbl_studiengang.email, + tbl_betreuerart.beschreibung AS beutreuerart_beschreibung, + tbl_projektbetreuer.stunden + FROM + lehre.tbl_lehrveranstaltung, lehre.tbl_projektarbeit, lehre.tbl_projektbetreuer, public.tbl_studiengang, lehre.tbl_betreuerart + WHERE + tbl_lehrveranstaltung.lehrveranstaltung_id=tbl_projektarbeit.lehrveranstaltung_id AND + tbl_projektarbeit.studiensemester_kurzbz = '$semester' AND + tbl_projektarbeit.projektarbeit_id = tbl_projektbetreuer.projektarbeit_id AND + tbl_lehrveranstaltung.studiengang_kz = tbl_studiengang.studiengang_kz AND + tbl_projektbetreuer.betreuerart_kurzbz = tbl_betreuerart.betreuerart_kurzbz AND + tbl_projektbetreuer.person_id = '$personId'"; + $this->addMeta("query", $betreuungenQuery); + + $this->load->model('education/Lehrveranstaltung_model', 'LehrveranstaltungModel'); + $result = $this->LehrveranstaltungModel->execReadOnlyQuery($betreuungenQuery); + $result = $this->getDataOrTerminateWithError($result); + + $this->terminateWithSuccess($result); + } + +} diff --git a/application/controllers/api/frontend/v1/lv/Lehreinheit.php b/application/controllers/api/frontend/v1/lv/Lehreinheit.php index 6329d30ac..71ef771e7 100644 --- a/application/controllers/api/frontend/v1/lv/Lehreinheit.php +++ b/application/controllers/api/frontend/v1/lv/Lehreinheit.php @@ -19,7 +19,8 @@ class Lehreinheit extends FHCAPI_Controller 'getLehrfach' => ['admin:r', 'assistenz:r'], 'getSprache' => ['admin:r', 'assistenz:r'], 'getRaumtyp' => ['admin:r', 'assistenz:r'], - 'getLehrform' => ['admin:r', 'assistenz:r'] + 'getLehrform' => ['admin:r', 'assistenz:r'], + 'getLehreinheitenBySemester' => self::PERM_LOGGED, ]); $this->_ci = &get_instance(); @@ -438,6 +439,27 @@ class Lehreinheit extends FHCAPI_Controller $this->terminateWithSuccess($this->p->t('global', 'gespeichert')); } + public function getLehreinheitenBySemester() + { + $semester = $this->input->get('semester'); + $lehreinheiten = $this->LehreinheitModel->getLehrenheitenBySemester(getAuthUID(), $semester); + $lehreinheiten = $this->getDataOrTerminateWithError($lehreinheiten); + + foreach ($lehreinheiten as $lehreinheit) { + $groups = $this->LehreinheitModel->getGroupsByLehreinheit($lehreinheit->lehreinheit_id); + $groups = $this->getDataOrTerminateWithError($groups); + $stgKurzbz = $lehreinheit->stg_kurzbz; + $lehreinheit->groups = array_map( + function ($group) use ($stgKurzbz) { + return $group->gruppe_kurzbz ?? $stgKurzbz . "-" . $group->semester . $group->verband . $group->gruppe; + }, + $groups + ); + } + + $this->terminateWithSuccess($lehreinheiten); + } + private function checkPermission($lehreinheit_id) { diff --git a/application/controllers/api/frontend/v1/stv/Grades.php b/application/controllers/api/frontend/v1/stv/Grades.php index 128316d2b..abc417bca 100644 --- a/application/controllers/api/frontend/v1/stv/Grades.php +++ b/application/controllers/api/frontend/v1/stv/Grades.php @@ -41,6 +41,9 @@ class Grades extends FHCAPI_Controller // Load Libraries $this->load->library('VariableLib', ['uid' => getAuthUID()]); + $this->load->library('PruefungsverlaufLib', null, 'VerlaufLib'); + + $this->load->config('noten'); // Load Phrases $this->loadPhrases([ @@ -355,23 +358,24 @@ class Grades extends FHCAPI_Controller $data['lehrveranstaltung_id'] = $lehrveranstaltung_id; $data['student_uid'] = $student_uid; $data['studiensemester_kurzbz'] = $studiensemester_kurzbz; - - $this->ZeugnisnoteModel->insert($data); - if (defined('FAS_PRUEFUNG_BEI_NOTENEINGABE_ANLEGEN') - && FAS_PRUEFUNG_BEI_NOTENEINGABE_ANLEGEN) { - $result = $this->addTestsForGrade( - $studiensemester_kurzbz, - $student_uid, - $lehrveranstaltung_id, - $teacherGrade->note, - $teacherGrade->punkte - ); - $this->getDataOrTerminateWithError($result); - } + $this->ZeugnisnoteModel->insert($data); + } + + // Der erste Antritt wird bereits bei der Übernahme als eigene Prüfung angelegt, damit der + // Prüfungsverlauf von Anfang an vollständig ist und das Benotungstool ihn nicht erst beim + // Anlegen einer Wiederholung nachtragen muss. Läuft bewusst in BEIDEN Zweigen (auch beim + // Überschreiben einer bestehenden Zeugnisnote) und ist idempotent. + if ($this->config->item('CIS_GESAMTNOTE_ERSTANTRITT_BEI_UEBERNAHME')) { + $result = $this->addErstantrittForGrade( + $studiensemester_kurzbz, + $student_uid, + $lehrveranstaltung_id, + $teacherGrade + ); + $this->getDataOrTerminateWithError($result); } - $this->terminateWithSuccess(true); } @@ -491,20 +495,32 @@ class Grades extends FHCAPI_Controller } /** - * Helper function that adds tests for a student - * (Entries in lehre.tbl_pruefung) + * Legt bei der Notenübernahme den ersten Prüfungsantritt als eigenen Datensatz an + * (Eintrag in lehre.tbl_pruefung). * - * @param string $studiensemester_kurzbz - * @param string $student_uid - * @param integer $lehrveranstaltung_id - * @param integer $note - * @param numeric $punkte + * Idempotent: existiert für den Studenten in dieser LV bereits ein Termin, passiert nichts - + * die Übernahme darf beliebig oft laufen, ohne den Verlauf zu verfälschen. + * + * Datiert auf das Freigabedatum der LV-Note: das ist der Zeitpunkt, zu dem die + * Leistungsfeststellung verbindlich wurde. Das Benotungsdatum ändert sich bei jeder Korrektur, + * der Übernahmezeitpunkt ist reine Verwaltungsarbeit - beide taugen nicht als Termindatum. + * + * @param string $studiensemester_kurzbz + * @param string $student_uid + * @param integer $lehrveranstaltung_id + * @param stdClass $teacherGrade die übernommene LV-Gesamtnote * * @return stdClass */ - protected function addTestsForGrade($studiensemester_kurzbz, $student_uid, $lehrveranstaltung_id, $note, $punkte) + protected function addErstantrittForGrade($studiensemester_kurzbz, $student_uid, $lehrveranstaltung_id, $teacherGrade) { $this->load->model('education/Lehrveranstaltung_model', 'LehrveranstaltungModel'); + $this->load->model('education/LePruefung_model', 'LePruefungModel'); + + // bereits ein Termin vorhanden -> der Verlauf beginnt schon, nichts nachzutragen + $vorhanden = $this->VerlaufLib->getPruefungen($student_uid, $lehrveranstaltung_id, $studiensemester_kurzbz); + if (count($vorhanden) > 0) + return success(null); // Get Lehreinheit $result = $this->LehrveranstaltungModel->getLeByStudent($student_uid, $studiensemester_kurzbz, $lehrveranstaltung_id); @@ -515,19 +531,24 @@ class Grades extends FHCAPI_Controller return error($this->p->t("stv", "grades_error_lehreinheit_id")); $le = current(getData($result)); + $datum = $teacherGrade->freigabedatum ?: ($teacherGrade->benotungsdatum ?: date('Y-m-d')); + $now = date('Y-m-d H:i:s'); + // Prepare - $this->load->model('education/LePruefung_model', 'LePruefungModel'); $data = [ "student_uid" => $student_uid, "lehreinheit_id" => $le->lehreinheit_id, - "datum" => date('Y-m-d'), - "pruefungstyp_kurzbz" => "Termin1", - "note" => $note + "mitarbeiter_uid" => $teacherGrade->mitarbeiter_uid, + "datum" => $datum, + "pruefungstyp_kurzbz" => $this->VerlaufLib->legacyTypFuerAntritt(1), + "note" => $teacherGrade->note, + "insertamum" => $now, + "insertvon" => getAuthUID() ]; if (defined('CIS_GESAMTNOTE_PUNKTE') && CIS_GESAMTNOTE_PUNKTE) - $data["punkte"] = $punkte; - + $data["punkte"] = $teacherGrade->punkte; + // Get Anwesenheit $this->load->model('education/Anwesenheit_model', 'AnwesenheitModel'); $result = $this->AnwesenheitModel->loadAnwesenheitStudiensemester($studiensemester_kurzbz, $student_uid, $lehrveranstaltung_id); @@ -535,7 +556,8 @@ class Grades extends FHCAPI_Controller return $result; $anwesenheit = getData($result); - if ($anwesenheit && (float)current($anwesenheit)->prozent < FAS_ANWESENHEIT_ROT) { + if (defined('FAS_PRUEFUNG_BEI_NOTENEINGABE_ANLEGEN') && FAS_PRUEFUNG_BEI_NOTENEINGABE_ANLEGEN + && $anwesenheit && (float)current($anwesenheit)->prozent < FAS_ANWESENHEIT_ROT) { // Get Anwesenheitsbefreiung $this->load->model('person/Benutzerfunktion_model', 'BenutzerfunktionModel'); $result = $this->BenutzerfunktionModel->getBenutzerFunktionByUidInStdsem($student_uid, $studiensemester_kurzbz, 'awbefreit'); @@ -545,7 +567,10 @@ class Grades extends FHCAPI_Controller $anwesenheitsbefreit = hasData($result); - // Wenn nicht Anwesenheitsbefreit und Anwesenheit unter einem bestimmten Prozentsatz fällt dann wird ein Pruefungsantritt abgezogen + // Bei zu geringer Anwesenheit ohne Befreiung wird ein zusätzlicher Termin + // 'Nicht beurteilt' vor dem eigentlichen Antritt festgehalten. ACHTUNG: diese Note + // zählt nicht mehr als Antritt (NOTEN_OHNE_ANTRITT_BEZEICHNUNGEN) - der frühere + // "Antritt abziehen"-Effekt entfällt damit, der Eintrag ist reine Dokumentation. if (!$anwesenheitsbefreit) { $data2 = $data; $data2["note"] = 7; @@ -556,8 +581,6 @@ class Grades extends FHCAPI_Controller if (isError($result)) return $result; - - $data["pruefungstyp_kurzbz"] = "Termin2"; } } diff --git a/application/controllers/api/frontend/v1/stv/Kontakt.php b/application/controllers/api/frontend/v1/stv/Kontakt.php index d246a04d9..cd35768f3 100644 --- a/application/controllers/api/frontend/v1/stv/Kontakt.php +++ b/application/controllers/api/frontend/v1/stv/Kontakt.php @@ -574,6 +574,19 @@ class Kontakt extends FHCAPI_Controller $person_id = $this->input->post('person_id'); $standort_id = $this->input->post('standort_id'); + //check person_id: allow editing only of persons who are no mitarbeiter + if($person_id) + { + $this->load->model('person/Benutzer_model', 'BenutzerModel'); + $this->BenutzerModel->addJoin('public.tbl_mitarbeiter ma', 'ON (ma.mitarbeiter_uid = public.tbl_benutzer.uid)'); + + $result = $this->BenutzerModel->loadWhere([ + 'person_id' => $person_id + ]); + if(hasData($result)) + return $this->terminateWithError($this->p->t('ui', 'editingMitarbeiterPVonly'), self::ERROR_TYPE_GENERAL); + } + $result = $this->KontaktModel->update( [ 'kontakt_id' => $kontakt_id diff --git a/application/controllers/components/Cis/Mylv.php b/application/controllers/components/Cis/Mylv.php index 30544b647..c632baa39 100644 --- a/application/controllers/components/Cis/Mylv.php +++ b/application/controllers/components/Cis/Mylv.php @@ -13,6 +13,7 @@ class Mylv extends Auth_Controller */ public function __construct() { + parent::__construct([ 'Student' => self::PERM_LOGGED, 'Studiensemester' => self::PERM_LOGGED, @@ -44,13 +45,27 @@ class Mylv extends Auth_Controller public function Studiensemester() { $this->load->model('organisation/Studiensemester_model', 'StudiensemesterModel'); + $this->load->model('crm/Student_model', 'StudentModel'); + $this->load->model('ressource/Mitarbeiter_model', 'MitarbeiterModel'); - $result = $this->StudiensemesterModel->getWhereStudentHasLvs(getAuthUID()); + $isMitarbeiter = getData($this->MitarbeiterModel->isMitarbeiter(getAuthUID())) ?? false; + if($isMitarbeiter) { + $result = $this->StudiensemesterModel->getWhereMitarbeiterHasLvs(getAuthUID()); - if (isError($result)) - return $this->outputJsonError(getError($result)); + if (isError($result)) + return $this->outputJsonError(getError($result)); - $this->outputJsonSuccess(getData($result)); + $this->outputJsonSuccess(getData($result)); + } else if(getData($this->StudentModel->isStudent(getAuthUID())) ?? false) { // $isStudent + $result = $this->StudiensemesterModel->getWhereStudentHasLvs(getAuthUID()); + + if (isError($result)) + return $this->outputJsonError(getError($result)); + + $this->outputJsonSuccess(getData($result)); + } else { + $this->outputJsonError('neither student or mitarbeiter'); + } } /** @@ -58,13 +73,27 @@ class Mylv extends Auth_Controller public function Lvs($studiensemester_kurzbz) { $this->load->model('education/Lehrveranstaltung_model', 'LehrveranstaltungModel'); + $this->load->model('crm/Student_model', 'StudentModel'); + $this->load->model('ressource/Mitarbeiter_model', 'MitarbeiterModel'); - $result = $this->LehrveranstaltungModel->getLvsByStudentWithGrades(getAuthUID(), $studiensemester_kurzbz, getUserLanguage()); + $isMitarbeiter = getData($this->MitarbeiterModel->isMitarbeiter(getAuthUID())) ?? false; + if($isMitarbeiter) { + $result = $this->LehrveranstaltungModel->getLvsByMitarbeiterInSemester(getAuthUID(), $studiensemester_kurzbz); - if (isError($result)) - return $this->outputJsonError(getError($result)); + if (isError($result)) + return $this->outputJsonError(getError($result)); - $this->outputJsonSuccess(getData($result)); + $this->outputJsonSuccess(getData($result)); + } else if(getData($this->StudentModel->isStudent(getAuthUID())) ?? false) { // $isStudent + $result = $this->LehrveranstaltungModel->getLvsByStudentWithGrades(getAuthUID(), $studiensemester_kurzbz, getUserLanguage()); + + if (isError($result)) + return $this->outputJsonError(getError($result)); + + $this->outputJsonSuccess(getData($result)); + } else { + $this->outputJsonError('neither student or mitarbeiter'); + } } /** diff --git a/application/controllers/lehre/Pruefungsprotokoll.php b/application/controllers/lehre/Pruefungsprotokoll.php index 4d34b08ca..aa3eb4c73 100644 --- a/application/controllers/lehre/Pruefungsprotokoll.php +++ b/application/controllers/lehre/Pruefungsprotokoll.php @@ -215,8 +215,11 @@ class Pruefungsprotokoll extends Auth_Controller if (hasData($abschlusspruefung)) { $abschlusspruefung_data = getData($abschlusspruefung); - if ($this->permissionlib->isBerechtigt('admin') || - (isset($abschlusspruefung_data->studiengang_kz) && $this->permissionlib->isBerechtigt('assistenz', 'suid', $abschlusspruefung_data->studiengang_kz)) + if ($this->permissionlib->isBerechtigt('admin') + || ( + isset($abschlusspruefung_data->studiengang_kz) + && $this->permissionlib->isBerechtigt('assistenz', 'suid', $abschlusspruefung_data->studiengang_kz) + ) || $this->_uid === $abschlusspruefung_data->uid_vorsitz) $result = $abschlusspruefung; else diff --git a/application/helpers/hlp_header_helper.php b/application/helpers/hlp_header_helper.php index eb2cd9f85..41f361bf1 100644 --- a/application/helpers/hlp_header_helper.php +++ b/application/helpers/hlp_header_helper.php @@ -82,6 +82,21 @@ function generateCSSsInclude($CSSs) } } +/** + * Generates tags for the style sheets you want to include, the parameter could by a string or an array of strings + */ +function generatePrintCSS($path) +{ + $cssLink = ''; + + $ci =& get_instance(); + $cachetoken = '?'.$ci->config->item('fhcomplete_build_version'); + + if (isset($path)) { + echo sprintf($cssLink, base_url($path).$cachetoken) . PHP_EOL; + } +} + /** * Generates global JS-Object to pass parms to other javascripts */ diff --git a/application/libraries/CmsLib.php b/application/libraries/CmsLib.php index 355cf7c56..89f06c412 100644 --- a/application/libraries/CmsLib.php +++ b/application/libraries/CmsLib.php @@ -4,6 +4,7 @@ if (!defined('BASEPATH')) exit('No direct script access allowed'); use \DateTime as DateTime; +use \DateInterval as DateInterval; use \DOMDocument as DOMDocument; use \XSLTProcessor as XSLTProcessor; @@ -141,63 +142,6 @@ class CmsLib ]); } - /** - * @param stdClass $stg_obj - * - * @return stdClass - */ - protected function getNewsExtras($stg_obj, $semester) - { - $this->ci->load->model('person/Benutzerfunktion_model', 'BenutzerfunktionModel'); - - $stg_ltg = $this->ci->StudiengangModel->getLeitungDetailed($stg_obj->studiengang_kz); - if (isError($stg_ltg)) - return $stg_ltg; - $stg_ltg = getData($stg_ltg) ?: []; - - $gf_ltg = $this->ci->BenutzerfunktionModel->getBenutzerFunktionenDetailed('gLtg', $stg_obj->oe_kurzbz); - if (isError($gf_ltg)) - return $gf_ltg; - $gf_ltg = getData($gf_ltg) ?: []; - - $stv_ltg = $this->ci->BenutzerfunktionModel->getBenutzerFunktionenDetailed('stvLtg', $stg_obj->oe_kurzbz); - if (isError($stv_ltg)) - return $stv_ltg; - $stv_ltg = getData($stv_ltg) ?: []; - - $ass = $this->ci->BenutzerfunktionModel->getBenutzerFunktionenDetailed('ass', $stg_obj->oe_kurzbz); - if (isError($ass)) - return $ass; - $ass = getData($ass) ?: []; - - $hochschulvertr = $this->ci->BenutzerfunktionModel->getBenutzerFunktionenDetailed('hsv'); - if (isError($hochschulvertr)) - return $hochschulvertr; - $hochschulvertr = getData($hochschulvertr) ?: []; - - $stdv = $this->ci->BenutzerfunktionModel->getBenutzerFunktionenDetailed('stdv', $stg_obj->oe_kurzbz); - if (isError($stdv)) - return $stdv; - $stdv = getData($stdv) ?: []; - - $jahrgangsvertr = $this->ci->BenutzerfunktionModel->getBenutzerFunktionenDetailed('jgv', $stg_obj->oe_kurzbz, $semester); - if (isError($jahrgangsvertr)) - return $jahrgangsvertr; - $jahrgangsvertr = getData($jahrgangsvertr) ?: []; - - return success($this->ci->load->view('Cis/Cms/News/Xml/NewsExtras', [ - 'studiengang' => $stg_obj, - 'semester' => $semester, - 'stg_ltg' => $stg_ltg, - 'gf_ltg' => $gf_ltg, - 'stv_ltg' => $stv_ltg, - 'ass' => $ass, - 'hochschulvertr' => $hochschulvertr, - 'stdv' => $stdv, - 'jahrgangsvertr' => $jahrgangsvertr - ], true)); - } - /** * @param string $studiengang_kz * @param string $semester @@ -233,64 +177,102 @@ class CmsLib * * @return void */ - public function getNews($infoscreen = false, $studiengang_kz = null, $semester = null, $mischen = true, $titel = '', $edit = false, $sichtbar = true, $page = 1, $page_size = 10, $sprache) + public function getNews($infoscreen = false, $studiengang_kz = null, $semester = null, $mischen = true, $titel = '', $edit = false, $sichtbar = true, $page = 1, $page_size = 10, $sprache, $filterForDegreePrograms = false, $allowedDegreePrograms = [], $active = true) + { $this->ci->load->model('organisation/Studiengang_model', 'StudiengangModel'); list($studiengang_kz, $semester) = $this->getStgAndSem($studiengang_kz, $semester); - $all = $edit; - - $xml = ''; $this->ci->load->model('content/News_model', 'NewsModel'); - $news = $this->ci->NewsModel->getNewsWithContent($sprache, $studiengang_kz, $semester, null, $sichtbar, 0, $page, $page_size, $all, $mischen); - - if (isError($news)) - return $news; - - $news = getData($news); + + $news = $this->ci->NewsModel->getNewsWithContent($sprache, $studiengang_kz, $semester, null, $sichtbar, $page, $page_size, $active, $mischen, $filterForDegreePrograms, $allowedDegreePrograms); + $news = getData($news) ?? []; + $newsWrappers = []; + foreach ($news as $newsobj) { if ($studiengang_kz && $edit && !$newsobj->studiengang_kz) continue; - $date = new DateTime($newsobj->datum); - $datum = 'format('d.m.Y') . ']]>'; - $datum .= 'format('Y-m-d H:i') . ']]>'; - $id = $edit ? 'news_id . ']]>' : ''; - $xml .= "" . $newsobj->content . $datum . $id . ""; + + $newsWrappers[] = $this->createNewsWrapper($newsobj, $edit); } - /* if ($studiengang_kz != 0) { - $stg_obj = $this->ci->StudiengangModel->load($studiengang_kz); - if (isError($stg_obj)) - return $stg_obj; - $stg_obj = current(getData($stg_obj) ?: []); + $content = $this->createNewsContent($newsWrappers, $infoscreen, $titel); + if (isError($content)) + return $content; - if ($stg_obj) { - if (!$edit && !$infoscreen) { - $extras = $this->getNewsExtras($stg_obj, $semester); - if (isError($extras)) - return $extras; - $xml .= getData($extras); - } - $xml .= 'bezeichnung . ']]>'; - } - } */ + return success([ + "content" => getData($content), + "row_count" => $news[0]->row_count ?? 0 + ]); + } + + /** + * Creates a newswrapper XML element from a news item. + * + * @param stdClass $newsobj + * @param boolean $edit + * + * @return DOMElement + */ + public function createNewsWrapper($newsobj, $edit) + { + $id = $edit ? 'news_id . ']]>' : ''; + $isPublished = $edit ? 'sichtbar ? 'true' : 'false') . ']]>' : ''; + + $date = new DateTime($newsobj->datum); + $datum = 'format('d.m.Y') . ']]>'; + $datum .= 'format('Y-m-d H:i') . ']]>'; + + $hasVisibleToDate = isset($newsobj->datum_bis) && $newsobj->datum_bis !== ''; + $datumTo = $hasVisibleToDate + ? new DateTime($newsobj->datum_bis) + : (clone $date)->add(new DateInterval('P' . MAXNEWSALTER . 'D')); + if ($hasVisibleToDate) + $datumTo->setTime(23, 59, 59); + + $now = new DateTime(); + $isActive = '= $now ? 'true' : 'false') . ']]>'; + + $newsWrapper = new DOMDocument(); + $newsWrapper->loadXML('' . $newsobj->content . $datum . $id . $isPublished . $isActive . ''); + + return $newsWrapper->documentElement; + } + + /** + * Creates rendered news content from one or more newswrapper XML elements. + * + * @param DOMElement|array $newsWrappers + * @param boolean $infoscreen + * @param string $titel + * + * @return stdClass + */ + public function createNewsContent($newsWrappers, $infoscreen, $titel) + { + if (!is_array($newsWrappers)) + $newsWrappers = [$newsWrappers]; + + $XML = new DOMDocument('1.0', 'UTF-8'); + $contentElement = $XML->createElement('content'); + $XML->appendChild($contentElement); + + foreach ($newsWrappers as $newsWrapper) + $contentElement->appendChild($XML->importNode($newsWrapper, true)); if ($titel != '') { - $xml .= '' . $titel . ''; + $titleElement = $XML->createElement('news_titel'); + $titleElement->appendChild($XML->createTextNode($titel)); + $contentElement->appendChild($titleElement); } - $xml .= ''; - //XSLT Vorlage laden $template = $this->ci->TemplateModel->load($infoscreen ? 'news_infoscreen' : 'news'); if (isError($template)) return $template; $template = current(getData($template)); - $XML = new DOMDocument(); - $XML->loadXML($xml); - $xsltemplate = new DOMDocument(); $xsltemplate->loadXML($template->xslt_xhtml_c4); diff --git a/application/libraries/PruefungsverlaufLib.php b/application/libraries/PruefungsverlaufLib.php new file mode 100644 index 000000000..e0087e527 --- /dev/null +++ b/application/libraries/PruefungsverlaufLib.php @@ -0,0 +1,410 @@ +. + */ + +if (! defined('BASEPATH')) exit('No direct script access allowed'); + +/** + * The exam history of one student in one course: the order of the attempts, the attempt count + * and the limits. This class is the only source of the examination rules. The controller and the + * client only read the result. + */ +class PruefungsverlaufLib +{ + /** The first attempt: the original assessment, which is the transcript grade itself. */ + const ROLLE_ERSTANTRITT = 'erstantritt'; + + /** Each attempt after the first one. */ + const ROLLE_PRUEFUNG = 'pruefung'; + + /** The last attempt. The examination rules require a commission for it. */ + const ROLLE_KOMMISSIONELL = 'kommissionell'; + + /** Maps an attempt number to the old exam type. Kept for backward compatibility. */ + private static $_legacyTypen = [1 => 'Termin1', 2 => 'Termin2', 3 => 'Termin3']; + + private $_ci; + private $_noteCache = []; + private $_specialNotes = null; + private $_typen = null; + + public function __construct() + { + $this->_ci =& get_instance(); + $this->_ci->load->model('education/LePruefung_model', 'LePruefungModel'); + $this->_ci->load->model('education/Note_model', 'NoteModel'); + $this->_ci->load->model('education/Pruefungstyp_model', 'PruefungstypModel'); + $this->_ci->load->config('noten'); + } + + /** + * @param mixed $lvNote counts as the implicit first attempt while no exam counts + * @param mixed $zeugnisNote a credited grade makes all exams impossible + * @return stdClass + */ + public function getVerlauf($student_uid, $lv_id, $sem_kurzbz, $lvNote = null, $zeugnisNote = null) + { + return $this->buildVerlauf($this->getPruefungen($student_uid, $lv_id, $sem_kurzbz), $lvNote, $zeugnisNote); + } + + /** All exams of one student in one course, in chronological order. @return array */ + public function getPruefungen($student_uid, $lv_id, $sem_kurzbz) + { + $result = $this->_ci->LePruefungModel->getPruefungenByUidTypLvStudiensemester($student_uid, null, $lv_id, $sem_kurzbz); + if (isError($result) || !hasData($result)) return []; + + return $this->sortPruefungen(getData($result)); + } + + /** Sort keys: datum, then tbl_pruefungstyp.sort (old rows without a date), then pruefung_id. */ + public function sortPruefungen($pruefungen) + { + $pruefungen = array_values($pruefungen); + $typen = $this->getTypen(); + + usort($pruefungen, function ($a, $b) use ($typen) { + $da = substr((string) $a->datum, 0, 10); + $db = substr((string) $b->datum, 0, 10); + + if ($da !== $db) { + if ($da === '') return -1; + if ($db === '') return 1; + return ($da < $db) ? -1 : 1; + } + + $sa = isset($typen[$a->pruefungstyp_kurzbz]) ? (int) $typen[$a->pruefungstyp_kurzbz]->sort : 0; + $sb = isset($typen[$b->pruefungstyp_kurzbz]) ? (int) $typen[$b->pruefungstyp_kurzbz]->sort : 0; + if ($sa !== $sb) return ($sa < $sb) ? -1 : 1; + + return ((int) $a->pruefung_id < (int) $b->pruefung_id) ? -1 : 1; + }); + + return $pruefungen; + } + + /** + * Builds the history from a list of exams. Each exam gets: position, zaehlt, antritt_nr (null + * if the exam does not count) and terminal. + * + * @return stdClass + */ + public function buildVerlauf($pruefungen, $lvNote = null, $zeugnisNote = null) + { + $pruefungen = $this->sortPruefungen($pruefungen); + + $antrittCount = 0; + $terminal = false; + $eintraege = []; + + foreach ($pruefungen as $i => $p) { + $zaehlt = $this->zaehltAlsAntritt($p->note, $p->pruefungstyp_kurzbz); + if ($zaehlt) $antrittCount++; + + // A kommissionelle exam closes the chain, also if its grade uses no attempt. A student + // with a kommissionelle Prüfung gets no further exam from this tool. + $kommissionell = $this->istKommissionell($p->pruefungstyp_kurzbz); + if ($kommissionell) $terminal = true; + + $eintrag = clone $p; + $eintrag->position = $i + 1; + $eintrag->zaehlt = $zaehlt; + $eintrag->antritt_nr = $zaehlt ? $antrittCount : null; + $eintrag->kommissionell = $kommissionell; + $eintraege[] = $eintrag; + } + + // if no exam counts, the course grade itself is the first attempt + $implizit = ($antrittCount === 0 && $lvNote !== null && $this->zaehltAlsAntritt($lvNote)); + if ($implizit) $antrittCount = 1; + + $maxAntritte = $this->getMaxAntritte(); + + // credited: the row stays visible, but you cannot enter anything + $angerechnet = $this->istAnrechnungsnote($zeugnisNote); + + $verlauf = new stdClass(); + $verlauf->pruefungen = $eintraege; + $verlauf->antrittCount = $antrittCount; + $verlauf->maxAntritte = $maxAntritte; + $verlauf->terminal = $terminal; + $verlauf->angerechnet = $angerechnet; + $verlauf->impliziterErstantritt = $implizit; + $verlauf->canAdd = !$terminal && !$angerechnet && $antrittCount < $maxAntritte; + + // after the first exam each new entry is a repeat attempt + $verlauf->erstantrittMoeglich = !$angerechnet && (count($eintraege) === 0); + + // The last attempt of the chain is kommissionell (§17 Abs 1). The role therefore follows + // from the position, and the user interface offers no type to select. + if (count($eintraege) === 0 && !$implizit) { + $verlauf->naechsteRolle = self::ROLLE_ERSTANTRITT; + } elseif (($antrittCount + 1) >= $maxAntritte) { + $verlauf->naechsteRolle = self::ROLLE_KOMMISSIONELL; + } else { + $verlauf->naechsteRolle = self::ROLLE_PRUEFUNG; + } + + return $verlauf; + } + + /** 'angerechnet' or 'intern angerechnet'. The TRANSCRIPT grade decides. @return bool */ + public function istAnrechnungsnote($note) + { + if ($note === null || $note === '') return false; + return in_array($note, $this->getSpecialNotes()['anrechnung']); + } + + /** + * All attempts that count, the kommissionelle attempt included. The explicit configuration + * wins. Without it the number comes from the old flags: the original assessment, one attempt + * for each active repeat, and the kommissionelle attempt. + * + * @return int + */ + public function getMaxAntritte() + { + $max = $this->_ci->config->item('CIS_GESAMTNOTE_MAX_ANTRITTE'); + if (is_numeric($max) && (int) $max > 0) return (int) $max; + + $max = 1; + if (defined('CIS_GESAMTNOTE_PRUEFUNG_TERMIN2') && CIS_GESAMTNOTE_PRUEFUNG_TERMIN2) $max++; + if (defined('CIS_GESAMTNOTE_PRUEFUNG_TERMIN3') && CIS_GESAMTNOTE_PRUEFUNG_TERMIN3) $max++; + if (defined('CIS_GESAMTNOTE_PRUEFUNG_KOMMPRUEF') && CIS_GESAMTNOTE_PRUEFUNG_KOMMPRUEF) $max++; + return $max; + } + + /** + * The NOTE decides, therefore the same type can occur more than one time. The type decides in + * one case only: a type in PRUEFUNG_TYPEN_OHNE_ANTRITT never uses an attempt. + * + * @return bool + */ + public function zaehltAlsAntritt($note, $typ = null) + { + if ($typ !== null && in_array($typ, $this->getTypenOhneAntritt())) return false; + if ($note === null || $note === '') return false; + if (in_array($note, $this->getSpecialNotes()['ohneAntritt'])) return false; + + // a credited grade is not an assessment, therefore it uses no attempt + if ($this->istAnrechnungsnote($note)) return false; + + $noteRow = $this->getNote($note); + return $noteRow ? (bool) $noteRow->lehre : false; + } + + /** Tells you if an exam takes place before a commission. @return bool */ + public function istKommissionell($typ) + { + $typen = $this->_ci->config->item('PRUEFUNG_KOMMISSIONELL_TYPEN'); + if (!is_array($typen)) $typen = ['kommPruef', 'zusKommPruef']; + return in_array($typ, $typen); + } + + /** The exam types that never use an attempt. @return array */ + public function getTypenOhneAntritt() + { + $typen = $this->_ci->config->item('PRUEFUNG_TYPEN_OHNE_ANTRITT'); + return is_array($typen) ? $typen : ['zusKommPruef']; + } + + /** + * The type for an attempt number. The student administration still reads this column, so the + * value must be correct. The last attempt gets the kommissionelle type, the attempts before it + * get the old Termin types. + * + * @return string + */ + public function legacyTypFuerAntritt($antrittNr) + { + $antrittNr = max(1, (int) $antrittNr); + $typen = $this->getTypen(); + + $maxAntritte = $this->getMaxAntritte(); + $kommissionell = $this->_ci->config->item('PRUEFUNG_TYP_KOMMISSIONELL'); + if (!is_string($kommissionell) || $kommissionell === '') $kommissionell = 'kommPruef'; + + if ($maxAntritte > 1 && $antrittNr >= $maxAntritte && isset($typen[$kommissionell])) { + return $kommissionell; + } + + for ($n = $antrittNr; $n >= 1; $n--) { + if (isset(self::$_legacyTypen[$n]) && isset($typen[self::$_legacyTypen[$n]])) { + return self::$_legacyTypen[$n]; + } + } + + return self::$_legacyTypen[1]; + } + + /** + * The old type for a new repeat attempt. It is never lower than Termin2, also if the first + * exam does not use an attempt. + * + * @return string + */ + public function legacyTypFuerWiederholung($verlauf) + { + return $this->legacyTypFuerAntritt(max(2, $verlauf->antrittCount + 1)); + } + + /** @return array{entschuldigt: mixed, ohneAntritt: array, anrechnung: array, limitMap: array} */ + public function getSpecialNotes() + { + if ($this->_specialNotes !== null) return $this->_specialNotes; + + $cfgEntschuldigt = $this->_ci->config->item('NOTE_ENTSCHULDIGT'); + $cfgOhneAntritt = $this->_ci->config->item('NOTEN_OHNE_ANTRITT'); + $cfgLimitMap = $this->_ci->config->item('NOTEN_OCCURANCE_LIMIT_MAP'); + $bezeichnungen = $this->_ci->config->item('NOTEN_OHNE_ANTRITT_BEZEICHNUNGEN'); + if (!is_array($cfgOhneAntritt)) $cfgOhneAntritt = []; + if (!is_array($cfgLimitMap)) $cfgLimitMap = []; + if (!is_array($bezeichnungen)) $bezeichnungen = []; + + $resEnt = $this->_ci->NoteModel->getEntschuldigtNote(); + $entschuldigt = (!isError($resEnt) && hasData($resEnt)) ? getData($resEnt)[0]->note : $cfgEntschuldigt; + + // The name wins against the configured key. Do NOT merge the two sets: the same key has a + // different meaning in each installation (17 is 'entschuldigt' here, but 'nicht zugelassen' + // in the standard data). + $ohneAntritt = []; + foreach ($bezeichnungen as $bezeichnung) { + $note = $this->getNoteByBezeichnung($bezeichnung); + if ($note !== null && !in_array($note, $ohneAntritt)) $ohneAntritt[] = $note; + } + if (count($ohneAntritt) === 0) $ohneAntritt = $cfgOhneAntritt; + if ($entschuldigt !== null && !in_array($entschuldigt, $ohneAntritt)) $ohneAntritt[] = $entschuldigt; + + // move the configured limit for 'entschuldigt' to the key that was found + $limitMap = []; + foreach ($cfgLimitMap as $k => $v) { + $limitMap[($k == $cfgEntschuldigt) ? $entschuldigt : $k] = $v; + } + + $this->_specialNotes = [ + 'entschuldigt' => $entschuldigt, + 'ohneAntritt' => array_values($ohneAntritt), + 'anrechnung' => $this->resolveNoten('NOTEN_ANRECHNUNG_BEZEICHNUNGEN', 'NOTEN_ANRECHNUNG'), + 'limitMap' => $limitMap + ]; + return $this->_specialNotes; + } + + /** + * The occurrence limit from NOTEN_OCCURANCE_LIMIT_MAP. $excludePruefungId skips the record + * that the user edits at this moment. + * + * @return bool + */ + public function ueberschreitetNotenLimit($pruefungen, $note, $excludePruefungId = null) + { + $limitMap = $this->getSpecialNotes()['limitMap']; + + $limit = null; + foreach ($limitMap as $limitNote => $limitVal) { + if ($limitNote == $note) { $limit = $limitVal; break; } + } + if ($limit === null) return false; + + $count = 0; + foreach ($pruefungen as $p) { + if ($excludePruefungId !== null && $p->pruefung_id == $excludePruefungId) continue; + if ($p->note == $note) $count++; + } + + return ($count + 1) > $limit; + } + + /** An exam with a later date locks the grade. You can still correct the date. @return bool */ + public function hatSpaeterenTermin($verlauf, $pruefung_id) + { + $current = null; + foreach ($verlauf->pruefungen as $p) { + if ($p->pruefung_id == $pruefung_id) { $current = $p; break; } + } + if ($current === null) return false; + + foreach ($verlauf->pruefungen as $p) { + if ($p->pruefung_id == $current->pruefung_id) continue; + if ($p->position > $current->position) return true; + } + + return false; + } + + /** + * tbl_pruefungstyp as a map. It sorts old rows, and it limits the legacy type to the types + * that exist. A type that does not exist breaks the foreign key. Termin3 is absent from the + * standard data. + * + * @return array + */ + private function getTypen() + { + if ($this->_typen !== null) return $this->_typen; + + $this->_typen = []; + $result = $this->_ci->PruefungstypModel->load(); + if (!isError($result) && hasData($result)) { + foreach (getData($result) as $typ) { + $this->_typen[$typ->pruefungstyp_kurzbz] = $typ; + } + } + + return $this->_typen; + } + + /** Finds the grades by their name. The configured keys are the fallback only. @return array */ + private function resolveNoten($bezeichnungKey, $pkKey) + { + $bezeichnungen = $this->_ci->config->item($bezeichnungKey); + if (!is_array($bezeichnungen)) $bezeichnungen = []; + + $noten = []; + foreach ($bezeichnungen as $bezeichnung) { + $note = $this->getNoteByBezeichnung($bezeichnung); + if ($note !== null && !in_array($note, $noten)) $noten[] = $note; + } + + if (count($noten) === 0) { + $fallback = $this->_ci->config->item($pkKey); + if (is_array($fallback)) $noten = $fallback; + } + + return array_values($noten); + } + + /** + * @return stdClass|null + */ + private function getNote($note) + { + if (array_key_exists($note, $this->_noteCache)) return $this->_noteCache[$note]; + + $result = $this->_ci->NoteModel->load($note); + $this->_noteCache[$note] = (!isError($result) && hasData($result)) ? getData($result)[0] : null; + return $this->_noteCache[$note]; + } + + /** + * @return mixed|null + */ + private function getNoteByBezeichnung($bezeichnung) + { + $result = $this->_ci->NoteModel->loadWhere(['bezeichnung' => $bezeichnung]); + return (!isError($result) && hasData($result)) ? getData($result)[0]->note : null; + } +} diff --git a/application/libraries/StundenplanLib.php b/application/libraries/StundenplanLib.php index 2c13a29d9..4cfefc634 100644 --- a/application/libraries/StundenplanLib.php +++ b/application/libraries/StundenplanLib.php @@ -116,6 +116,7 @@ class StundenplanLib return $stundenplan_data; $stundenplan_data = getData($stundenplan_data) ?? []; + $this->_ci->addMeta("stundenplanData", $stundenplan_data); $function_error = $this->expandObjectInformation($stundenplan_data); if ($function_error) return $function_error; @@ -391,6 +392,39 @@ class StundenplanLib $item->gruppe = $gruppe_obj_array; $item->lektor = $lektor_obj_array; + $this->_ci->load->library('PermissionLib'); + $berechtigt_begrenzt = $this->_ci->permissionlib->isBerechtigt('lehre/reservierung:begrenzt', 'sui'); + + $now = time(); + $res_lektor_start = $this->jump_day($now, RES_TAGE_LEKTOR_MIN - 1); + $res_lektor_ende = mktime(0, 0, 0, date('m', $now), date('d', $now) + RES_TAGE_LEKTOR_BIS, date('Y', $now)); + + $start_date = is_numeric($item->beginn) ? $item->beginn : strtotime($item->beginn); + if (!date('w', $start_date)) { + $start_date = $this->jump_day($start_date, 1); + } + + $start_date_str = date('Y-m-d', $start_date); + $res_lektor_start_str = date('Y-m-d', $res_lektor_start); + $res_lektor_ende_str = date('Y-m-d', $res_lektor_ende); + + $show_delete = ( + ( + $berechtigt_begrenzt && + ( + (isset($item->insertvon) && $item->insertvon == getAuthUID()) || + (isset($item->uids) && in_array(getAuthUID(), $item->uids)) + ) + ) && + $start_date_str >= $res_lektor_start_str && + $start_date_str <= $res_lektor_ende_str + ); + + if ($show_delete) + $item->deletable = true; + else + $item->deletable = false; + } } @@ -486,6 +520,212 @@ class StundenplanLib return success($stundenplan_data); } + public function addReservation($start, $end, $title, $beschreibung, $ort_kurzbz, $lektoren = null, $studiengang = null, $semester = null, $verband = null, $gruppe = null, $spezialgruppe = null) + { + $this->_ci =& get_instance(); + $this->_ci->load->model('ressource/Stunde_model', 'StundeModel'); + $this->_ci->load->model('ressource/Reservierung_model', 'ReservierungModel'); + $this->_ci->load->model('ressource/stundenplandev_model', 'StundenplandevModel'); + $this->_ci->load->model('ressource/stundenplan_model', 'StundenplanModel'); + $this->_ci->load->library('PermissionLib'); + + $startTime = new DateTime($start); + $endTime = new DateTime($end); + + $stunden = $this->_ci->StundeModel->loadWhere(array( + 'beginn <' => $endTime->format('H:i:s'), + 'ende >' => $startTime->format('H:i:s') + )); + + if (!hasData($stunden)) + { + return error("Keine Stunden vorhanden"); + } + + $stunden = array_column(getData($stunden), 'stunde'); + + $this->_ci->StundenplandevModel->db->select('1'); + $this->_ci->StundenplandevModel->db->where('datum', $startTime->format('Y-m-d')); + $this->_ci->StundenplandevModel->db->where('ort_kurzbz', $ort_kurzbz); + $this->_ci->StundenplandevModel->db->where_in('stunde', $stunden); + $stundenplandev_belegung = $this->_ci->StundenplandevModel->load(); + + $this->_ci->StundenplanModel->db->select('1'); + $this->_ci->StundenplanModel->db->where('ort_kurzbz', $ort_kurzbz); + $this->_ci->StundenplanModel->db->where('datum', $startTime->format('Y-m-d')); + $this->_ci->StundenplanModel->db->where_in('stunde', $stunden); + $stundenplan_belegung = $this->_ci->StundenplanModel->load(); + + if ((hasData($stundenplandev_belegung) || hasData($stundenplan_belegung)) + && !$this->_ci->permissionlib->isBerechtigt('lehre/reservierungAdvanced')) + return error ('lvplan/bereitsReserviert'); + + $this->_ci->ReservierungModel->addSelect('stunde'); + $reservation_hours = $this->_ci->ReservierungModel->loadWhere(array('datum' => $startTime->format('Y-m-d'), 'ort_kurzbz' => $ort_kurzbz)); + + + if (isError($reservation_hours)) + return $reservation_hours; + + $reservation_hours = hasData($reservation_hours) ? array_column(getData($reservation_hours), 'stunde') : array(); + + if (!empty(array_intersect($stunden, $reservation_hours)) + && !$this->_ci->permissionlib->isBerechtigt('lehre/reservierungAdvanced')) + return error("lvplan/bereitsReserviert"); + + + if (!empty($lektoren)) + { + foreach ($lektoren as $lektor) + { + $insert = array('ort_kurzbz' => $ort_kurzbz, + 'datum' => $startTime->format('Y-m-d'), + 'titel' => $title, + 'studiengang_kz' => is_null($studiengang) ? 0 : $studiengang, + 'beschreibung' => $beschreibung, + 'insertvon' => getAuthUID(), + 'uid' => $lektor, + 'semester' => is_null($semester) ? null : $semester, + 'verband' => is_null($verband) ? null : $verband, + 'gruppe' => is_null($gruppe) ? null : $gruppe, + 'gruppe_kurzbz' => is_null($spezialgruppe) ? null : $spezialgruppe, + ); + + foreach ($stunden as $stunde) + { + $insert['stunde'] = $stunde; + $check_insert = $this->_ci->ReservierungModel->insert($insert); + if (isError($check_insert)) + return $check_insert; + } + } + } + else + { + foreach ($stunden as $stunde) + { + $check_insert = $this->_ci->ReservierungModel->insert(array( + 'ort_kurzbz' => $ort_kurzbz, + 'uid' => getAuthUID(), + 'stunde' => $stunde, + 'datum' => $startTime->format('Y-m-d'), + 'titel' => $title, + 'studiengang_kz' => is_null($studiengang) ? 0 : $studiengang, + 'beschreibung' => $beschreibung, + 'insertvon' => getAuthUID() + )); + if (isError($check_insert)) + return $check_insert; + } + } + + return success("Erfolgreich"); + } + + public function deleteReservation($reservierung_id) + { + $this->_ci =& get_instance(); + $this->_ci->load->model('ressource/Reservierung_model', 'ReservierungModel'); + $this->_ci->load->model('ressource/Stunde_model', 'StundeModel'); + $this->_ci->load->library('PermissionLib'); + + + $this->_ci->ReservierungModel->db->where_in('reservierung_id', $reservierung_id); + $reservation = $this->_ci->ReservierungModel->load(); + if (isError($reservation)) + return $reservation; + + if (!hasData($reservation)) + return error("Reservierungen nicht gefunden"); + + $reservations = getData($reservation); + + $today = new DateTime(); + foreach ($reservations as $reservierung) + { + if ($today->format('Y-m-d') > $reservierung->datum) + return error("Vergangene Reservierungen können nicht gelöscht werden"); + + if (($this->_ci->permissionlib->isBerechtigt('lehre/reservierung:begrenzt')) && ($reservierung->insertvon == getAuthUID() || $reservierung->uid === getAuthUID())) + { + $delete_result = $this->_ci->ReservierungModel->delete($reservierung->reservierung_id); + + if (isError($delete_result)) + return $delete_result; + } + } + return success("Erfolgreich"); + } + + + public function getReservableMap($ort_kurzbz, $start_date, $end_date) + { + $this->_ci =& get_instance(); + $this->_ci->load->model('ressource/Ort_model', 'OrtModel'); + $this->_ci->load->library('PermissionLib'); + + $berechtigt_begrenzt = $this->_ci->permissionlib->isBerechtigt('lehre/reservierung:begrenzt', 'suid'); + $berechtigt_erweitert = $this->_ci->permissionlib->isBerechtigt('lehre/reservierung', 'suid'); + + $ort_data = $this->_ci->OrtModel->load($ort_kurzbz); + if (isError($ort_data) || !hasData($ort_data)) + return []; + + $ort_data = getData($ort_data)[0]; + + if (!$ort_data->reservieren) + return []; + + if (!$berechtigt_begrenzt && !$berechtigt_erweitert) + return []; + + $start_ts = is_numeric($start_date) ? (int)$start_date : strtotime($start_date); + $end_ts = is_numeric($end_date) ? (int)$end_date : strtotime($end_date); + + if (!$start_ts || !$end_ts) + return []; + + if ($end_ts < $start_ts) + { + $tmp = $start_ts; + $start_ts = $end_ts; + $end_ts = $tmp; + } + + $now = time(); + $tage_min = defined('RES_TAGE_LEKTOR_MIN') ? (int)RES_TAGE_LEKTOR_MIN : 0; + $tage_bis = defined('RES_TAGE_LEKTOR_BIS') ? (int)RES_TAGE_LEKTOR_BIS : 0; + + $datum_res_lektor_start = $this->jump_day($now, $tage_min - 1); + $datum_res_lektor_ende = $this->jump_day($now, $tage_bis); + + $start_ymd_allowed = date('Y-m-d', $datum_res_lektor_start); + $end_ymd_allowed = date('Y-m-d', $datum_res_lektor_ende); + + $result = []; + + $current = strtotime(date('Y-m-d', $start_ts) . ' 00:00:00'); + $end_day = strtotime(date('Y-m-d', $end_ts) . ' 00:00:00'); + + while ($current <= $end_day) + { + $ymd = date('Y-m-d', $current); + + $result[$ymd] = ($ymd >= $start_ymd_allowed && $ymd <= $end_ymd_allowed) ? true : false; + + $current = $this->jump_day($current, 1); + } + + return success($result); + } + + private function jump_day($timestamp, $days) + { + $days = (int)$days; + $prefix = ($days >= 0 ? '+' : ''); + return strtotime($prefix . $days . ' days', $timestamp); + } + // start of the private functions ######################################################################################################## // function used to sort an array of studiensemester strings diff --git a/application/models/codex/Mobilitaet_model.php b/application/models/codex/Mobilitaet_model.php index 13f966d50..107c1ebb1 100644 --- a/application/models/codex/Mobilitaet_model.php +++ b/application/models/codex/Mobilitaet_model.php @@ -11,4 +11,73 @@ class Mobilitaet_model extends DB_Model $this->dbTable = 'bis.tbl_mobilitaet'; $this->pk = 'mobilitaet_id'; } + + public function getMobilityZusatzForUids($uids) { + $qry = "SELECT distinct on(nachname, vorname, public.tbl_benutzer.person_id) uid, + tbl_mitarbeiter.mitarbeiter_uid, + tbl_note.lkt_ueberschreibbar, tbl_note.anmerkung, + tbl_mobilitaet.mobilitaetstyp_kurzbz, + (CASE WHEN bis.tbl_mobilitaet.studiensemester_kurzbz = vw_student_lehrveranstaltung.studiensemester_kurzbz THEN 1 ELSE 0 END) as doubledegree, + public.tbl_prestudent.gsstudientyp_kurzbz as ddtype, + (SELECT status_kurzbz FROM public.tbl_prestudentstatus + WHERE prestudent_id=tbl_student.prestudent_id + ORDER BY datum DESC, insertamum DESC, ext_id DESC LIMIT 1) as studienstatus + FROM + campus.vw_student_lehrveranstaltung + JOIN public.tbl_benutzer USING(uid) + JOIN public.tbl_person USING(person_id) + LEFT JOIN public.tbl_student ON(uid=student_uid) + LEFT JOIN public.tbl_mitarbeiter ON(uid=mitarbeiter_uid) + LEFT JOIN public.tbl_studentlehrverband USING(student_uid,studiensemester_kurzbz) + LEFT JOIN lehre.tbl_zeugnisnote on(vw_student_lehrveranstaltung.lehrveranstaltung_id=tbl_zeugnisnote.lehrveranstaltung_id + AND tbl_zeugnisnote.student_uid=tbl_student.student_uid + AND tbl_zeugnisnote.studiensemester_kurzbz=tbl_studentlehrverband.studiensemester_kurzbz) + LEFT JOIN lehre.tbl_note USING (note) + LEFT JOIN bis.tbl_bisio ON(uid=tbl_bisio.student_uid) + LEFT JOIN public.tbl_studiengang ON(tbl_student.studiengang_kz=tbl_studiengang.studiengang_kz) + LEFT JOIN bis.tbl_mobilitaet USING(prestudent_id) + LEFT JOIN public.tbl_prestudent USING(prestudent_id) + WHERE uid IN ?"; + + return $this->execReadOnlyQuery($qry, [$uids]); + } + + public function formatZusatz($entry, $erhalter_kz) { + $zusatz = ''; + + if (isset($entry->studienstatus) && $entry->studienstatus === 'Incoming') { + $zusatz = '(i)'; + } + + if (isset($entry->lkt_ueberschreibbar) && $entry->lkt_ueberschreibbar === false) { + $zusatz .= ' (' . ($entry->anmerkung ?? '') . ')'; + } + + if (isset($entry->mitarbeiter_uid) && $entry->mitarbeiter_uid !== null) { + $zusatz .= ' (ma)'; + } + + if (isset($entry->stg_kz_student) && $entry->stg_kz_student == $erhalter_kz) { + $zusatz .= ' (a.o.)'; + } + + if ( + isset($entry->mobilitaetstyp_kurzbz) && $entry->mobilitaetstyp_kurzbz && + isset($entry->doubledegree) && $entry->doubledegree === 1 + ) { + $zusatz .= ' (d.d.'; + + $ddtype = $entry->ddtype ?? null; + + if ($ddtype == 'Intern') { + $zusatz .= 'i.)'; + } elseif ($ddtype == 'Extern') { + $zusatz .= 'o.)'; + } else { + $zusatz .= ')'; + } + } + + return $zusatz; + } } diff --git a/application/models/content/News_model.php b/application/models/content/News_model.php index ad8ecab99..ee00bf327 100644 --- a/application/models/content/News_model.php +++ b/application/models/content/News_model.php @@ -20,14 +20,16 @@ class News_model extends DB_Model public function getAll($limit = null) { $this->addJoin("campus.tbl_content","content_id"); + return $this->execReadOnlyQuery(' SELECT *, TO_CHAR(campus.tbl_news.datum, ?) as datumformatted FROM campus.tbl_news JOIN campus.tbl_content content ON content.content_id = campus.tbl_news.content_id - WHERE + WHERE --text IS NOT NULL AND - datum <= NOW() AND (datum_bis IS NULL OR datum_bis >= now()::date) - ORDER BY datum DESC + datum <= NOW() AND (datum_bis >= NOW()::date OR datum_bis IS NULL) AND + (NOW()-datum) < interval \'' . MAXNEWSALTER . ' days\' + ORDER BY datum DESC, campus.tbl_news.updateamum DESC LIMIT ' . $this->escape($limit) , ['DD/MM/YYYY']); } @@ -48,31 +50,28 @@ class News_model extends DB_Model * @param integer | null $semester * @param string $fachbereich_kurzbz * @param boolean $sichtbar - * @param integer $maxalter * @param integer $page * @param integer $page_size - * @param boolean $all + * @param boolean $active * @param boolean $mischen * * TODO(chris): this is not a good function -> the params are all over the place * */ - protected function prepareNewsWithContent($sprache, $studiengang_kz, $semester, $fachbereich_kurzbz = null, $sichtbar = true, $maxalter = 0, $page = 1, $page_size = 10, $all = false, $mischen = true) + protected function prepareNewsWithContent($sprache, $studiengang_kz, $semester, $fachbereich_kurzbz = null, $sichtbar = true, $page = 1, $page_size = 10, $active = true, $mischen = true, $filterForDegreePrograms = false, $allowedDegreePrograms = []) { + $this->addSelect(["*", "COUNT(*) OVER() AS row_count"]); $this->addOrder('datum', 'DESC'); + $this->addOrder('tbl_news.updateamum', 'DESC'); $studiengang_kz = trim($studiengang_kz); $fachbereich_kurzbz = trim($fachbereich_kurzbz); $where = []; - if (trim($maxalter) != '0') { - $where[] = "(now()-datum) < interval " . $this->db->escape($maxalter) . " days"; - - } - if (!$all) { - $where[] = "datum <= now()"; - $where[] = "(datum_bis >= now()::date OR datum_bis IS NULL)"; + + if ($active) { + $where[] = "(datum <= NOW()::date AND (datum_bis >= NOW()::date OR (datum_bis IS NULL AND (NOW()-datum) < interval '" . MAXNEWSALTER . " days')))"; } if ($fachbereich_kurzbz != '*') { if ($fachbereich_kurzbz == '') { @@ -82,27 +81,54 @@ class News_model extends DB_Model } } - if ($studiengang_kz == '0') { - $where[] = "studiengang_kz = " . $this->db->escape($studiengang_kz); - if ($semester === NULL) - $where[] = "semester IS NULL"; - elseif ($semester === 0) - $where[] = "semester = 0"; - } elseif ($studiengang_kz != '') { - $add = $mischen === true ? " OR (studiengang_kz = 0 AND semester IS NULL)" : ""; - $where[] = "((studiengang_kz = " . $this->db->escape($studiengang_kz) . " AND semester = " . $this->db->escape($semester) . ") OR (studiengang_kz = " . $this->db->escape($studiengang_kz) . " AND semester = 0) OR (studiengang_kz = 0 AND semester = " . $this->db->escape($semester) . ")" . $add . ")"; + $sgAndSemesterCondition = []; + if ($studiengang_kz !== '') { + $sgAndSemesterCondition[] = 'studiengang_kz = ' . $this->db->escape($studiengang_kz); } + if ($semester !== null && $semester !== 0) { + $sgAndSemesterCondition[] = 'semester = ' . $this->db->escape($semester); + } + + $targetedSgAndSemesterCondition = []; + + if (!empty($sgAndSemesterCondition)) { + $targetedSgAndSemesterCondition[] = '(' . implode(' AND ', $sgAndSemesterCondition) . ')'; + } + if ($mischen === true) { + $targetedSgAndSemesterCondition[] = '(studiengang_kz = 0 AND (semester = 0 OR semester IS NULL))'; + + if ($semester !== null && $semester !== 0) { + $targetedSgAndSemesterCondition[] = '(studiengang_kz = 0 AND semester = ' . $this->db->escape($semester) . ')'; + } + } + + $finalSgAndSemesterCondition = implode(' OR ', $targetedSgAndSemesterCondition); + + if ($finalSgAndSemesterCondition !== '') { + $where[] = "(" . $finalSgAndSemesterCondition . ")"; + } + $this->addJoin('campus.tbl_contentsprache cs', 'content_id'); - $where[] = "cs.sichtbar = " . ($sichtbar ? "true" : "false"); + if (is_bool($sichtbar) || $sichtbar === 'true' || $sichtbar === 'false') { + $isSichtbar = filter_var($sichtbar, FILTER_VALIDATE_BOOLEAN); + $where[] = "cs.sichtbar = " . ($isSichtbar ? "true" : "false"); + } $where[] = "cs.sprache = (CASE WHEN EXISTS(SELECT 1 FROM campus.tbl_contentsprache cs2 WHERE cs2.content_id=" . $this->dbTable . ".content_id AND sprache=" . $this->db->escape($sprache) . ") THEN " . $this->db->escape($sprache) . " ELSE " . $this->db->escape(DEFAULT_LANGUAGE) . " END)"; $where[] = "cs.version = (SELECT MAX(version) FROM campus.tbl_contentsprache cs3 WHERE cs3.content_id=" . $this->dbTable . ".content_id AND cs3.sprache = (CASE WHEN EXISTS(SELECT 1 FROM campus.tbl_contentsprache cs2 WHERE cs2.content_id=" . $this->dbTable . ".content_id AND sprache=" . $this->db->escape($sprache) . ") THEN " . $this->db->escape($sprache) . " ELSE " . $this->db->escape(DEFAULT_LANGUAGE) . " END))"; + if ($filterForDegreePrograms) { + if (empty($allowedDegreePrograms)) { + $where[] = "1=0"; + } else { + $where[] = "studiengang_kz IN (" . implode(',', array_map([$this->db, 'escape'], $allowedDegreePrograms)) . ")"; + } + } $where = implode(" AND ", $where); @@ -110,25 +136,16 @@ class News_model extends DB_Model } - public function getNewsWithContent($sprache, $studiengang_kz, $semester, $fachbereich_kurzbz = null, $sichtbar = true, $maxalter = 0, $page = 1, $page_size = 10, $all = false, $mischen = true) + public function getNewsWithContent($sprache, $studiengang_kz, $semester, $fachbereich_kurzbz = null, $sichtbar = true, $page = 1, $page_size = 10, $active = true, $mischen = true, $filterForDegreePrograms = false, $allowedDegreePrograms = []) { - $this->prepareNewsWithContent($sprache, $studiengang_kz, $semester, $fachbereich_kurzbz, $sichtbar, $maxalter, $page, $page_size, $all, $mischen); - + $this->prepareNewsWithContent($sprache, $studiengang_kz, $semester, $fachbereich_kurzbz, $sichtbar, $page, $page_size, $active, $mischen, $filterForDegreePrograms, $allowedDegreePrograms); // getting the number of rows of the query and adding pagination to the query result $num_rows = $this->getNumRows(true); $this->addPagination($page, $page_size, $num_rows); // preparing the query again because every call to get_compiled_select or cour_all_results will add the from clause to the query - $this->prepareNewsWithContent($sprache, $studiengang_kz, $semester, $fachbereich_kurzbz, $sichtbar, $maxalter, $page, $page_size, $all, $mischen); + $this->prepareNewsWithContent($sprache, $studiengang_kz, $semester, $fachbereich_kurzbz, $sichtbar, $page, $page_size, $active, $mischen, $filterForDegreePrograms, $allowedDegreePrograms); return $this->load(); } - - public function countNewsWithContent($sprache, $studiengang_kz, $semester, $fachbereich_kurzbz = null, $sichtbar = true, $maxalter = 0, $page = 1, $page_size = 10, $all = false, $mischen = true) - { - $this->prepareNewsWithContent($sprache, $studiengang_kz, $semester, $fachbereich_kurzbz, $sichtbar, $maxalter, $page, $page_size, $all, $mischen); - return $this->getNumRows(); - } - - } diff --git a/application/models/crm/Prestudent_model.php b/application/models/crm/Prestudent_model.php index ad5c3e141..9b1778c4e 100644 --- a/application/models/crm/Prestudent_model.php +++ b/application/models/crm/Prestudent_model.php @@ -88,6 +88,30 @@ class Prestudent_model extends DB_Model return $this->execQuery($query, $parametersArray); } + /** + * Gets the Studiengang and current Ausbildungssemester for every + * Prestudent belonging to the same person as the student. + * + * @param string $student_uid + * @return stdClass + */ + public function getStudiengaengAndAusbildungssemesterByStudentUid($student_uid) + { + $query = 'SELECT p.studiengang_kz, + ( + SELECT ps.ausbildungssemester + FROM public.tbl_prestudentstatus ps + WHERE ps.prestudent_id = p.prestudent_id + ORDER BY ps.datum DESC, ps.insertamum DESC, ps.ext_id DESC + LIMIT 1 + ) AS ausbildungssemester + FROM public.tbl_student s + JOIN public.tbl_prestudent p USING (prestudent_id) + WHERE s.student_uid = ?'; + + return $this->execQuery($query, array($student_uid)); + } + /** * updateAufnahmegruppe */ diff --git a/application/models/education/Abschlusspruefung_model.php b/application/models/education/Abschlusspruefung_model.php index 0ba8fd55c..fda196d23 100644 --- a/application/models/education/Abschlusspruefung_model.php +++ b/application/models/education/Abschlusspruefung_model.php @@ -100,12 +100,14 @@ class Abschlusspruefung_model extends DB_Model if (isError($abschlussarbeit)) return $abschlussarbeit; + if (hasData($abschlussarbeit)) { $abschlussarbeit = getData($abschlussarbeit)[0]; $abschlusspruefungdata->projektarbeit_studiengangstyp_name = $abschlussarbeit->projekttyp_kurzbz; $abschlusspruefungdata->abschlussarbeit_titel = $abschlussarbeit->titel; $abschlusspruefungdata->abschlussarbeit_note = $abschlussarbeit->note; + $abschlusspruefungdata->abschlussarbeit_sprache = $abschlussarbeit->sprache_bezeichnung; } } } diff --git a/application/models/education/LePruefung_model.php b/application/models/education/LePruefung_model.php index 6e51f1975..35cedc324 100644 --- a/application/models/education/LePruefung_model.php +++ b/application/models/education/LePruefung_model.php @@ -52,4 +52,53 @@ class LePruefung_model extends DB_Model 'student_uid' => $student_uid ]); } + + public function getPruefungenByLvStudiensemester($lv_id, $sem_kurzbz) { + $qry = "SELECT lehre.tbl_pruefung.*, tbl_lehrveranstaltung.bezeichnung as lehrveranstaltung_bezeichnung, tbl_lehrveranstaltung.lehrveranstaltung_id, + tbl_note.bezeichnung as note_bezeichnung, tbl_pruefungstyp.beschreibung as typ_beschreibung, tbl_lehreinheit.studiensemester_kurzbz as studiensemester_kurzbz + FROM lehre.tbl_pruefung, lehre.tbl_lehreinheit, lehre.tbl_lehrveranstaltung, lehre.tbl_note, lehre.tbl_pruefungstyp + WHERE lehre.tbl_pruefung.lehreinheit_id=tbl_lehreinheit.lehreinheit_id + AND tbl_lehreinheit.lehrveranstaltung_id=tbl_lehrveranstaltung.lehrveranstaltung_id + AND lehre.tbl_pruefung.note = tbl_note.note + AND lehre.tbl_pruefung.pruefungstyp_kurzbz=tbl_pruefungstyp.pruefungstyp_kurzbz + AND tbl_lehrveranstaltung.lehrveranstaltung_id = ? + AND tbl_lehreinheit.studiensemester_kurzbz = ? + ORDER BY datum DESC;"; + + return $this->execReadOnlyQuery($qry, array($lv_id, $sem_kurzbz)); + } + + public function getPruefungenByUidTypLvStudiensemester($uid, $typ = null, $lv_id = null, $sem_kurzbz = null) { + $params = [$uid]; + $qry = "SELECT tbl_pruefung.*, tbl_lehrveranstaltung.bezeichnung as lehrveranstaltung_bezeichnung, tbl_lehrveranstaltung.lehrveranstaltung_id, + tbl_note.bezeichnung as note_bezeichnung, tbl_pruefungstyp.beschreibung as typ_beschreibung, tbl_lehreinheit.studiensemester_kurzbz as studiensemester_kurzbz + FROM lehre.tbl_pruefung, lehre.tbl_lehreinheit, lehre.tbl_lehrveranstaltung, lehre.tbl_note, lehre.tbl_pruefungstyp + WHERE student_uid= ? + AND tbl_pruefung.lehreinheit_id=tbl_lehreinheit.lehreinheit_id + AND tbl_lehreinheit.lehrveranstaltung_id=tbl_lehrveranstaltung.lehrveranstaltung_id + AND tbl_pruefung.note = tbl_note.note + AND tbl_pruefung.pruefungstyp_kurzbz=tbl_pruefungstyp.pruefungstyp_kurzbz"; + if ($typ != null) + { + $qry .= " AND tbl_pruefungstyp.pruefungstyp_kurzbz = ?"; + $params[] = $typ; + } + + if ($lv_id != null) + { + $qry .= " AND tbl_lehrveranstaltung.lehrveranstaltung_id = ?"; + $params[] = $lv_id; + } + + if ($sem_kurzbz != null) + { + $qry .= " AND tbl_lehreinheit.studiensemester_kurzbz = ?"; + $params[] = $sem_kurzbz; + } + + + $qry .= " ORDER BY datum DESC"; + + return $this->execReadOnlyQuery($qry, $params); + } } diff --git a/application/models/education/Lehreinheit_model.php b/application/models/education/Lehreinheit_model.php index 8cc701da2..f446f7c84 100644 --- a/application/models/education/Lehreinheit_model.php +++ b/application/models/education/Lehreinheit_model.php @@ -840,4 +840,69 @@ EOSQL; )"; } + + public function getAllLehreinheitenForLvaAndMaUid($lva_id, $ma_uid, $sem_kurzbz) + { + $query = "SELECT DISTINCT tbl_lehreinheitmitarbeiter.lehreinheit_id, tbl_lehreinheit.lehrveranstaltung_id, tbl_lehreinheit.lehrform_kurzbz, + tbl_lehreinheitmitarbeiter.mitarbeiter_uid, + tbl_lehreinheitgruppe.semester, + tbl_lehreinheitgruppe.verband, + tbl_lehreinheitgruppe.gruppe, + tbl_lehreinheitgruppe.gruppe_kurzbz, + tbl_lehrveranstaltung.kurzbz, + tbl_studiengang.kurzbzlang, + (SELECT COUNT(DISTINCT datum) FROM campus.vw_stundenplan WHERE lehreinheit_id = lehre.tbl_lehreinheit.lehreinheit_id) as termincount, + (SELECT COUNT(*) FROM campus.vw_student_lehrveranstaltung WHERE lehreinheit_id = lehre.tbl_lehreinheit.lehreinheit_id) as studentcount + FROM lehre.tbl_lehreinheit JOIN lehre.tbl_lehreinheitmitarbeiter USING(lehreinheit_id) + JOIN lehre.tbl_lehreinheitgruppe USING(lehreinheit_id) + JOIN lehre.tbl_lehrveranstaltung USING(lehrveranstaltung_id) + JOIN public.tbl_studiengang ON (tbl_lehreinheitgruppe.studiengang_kz = tbl_studiengang.studiengang_kz) + WHERE lehrveranstaltung_id = ? AND studiensemester_kurzbz = ? AND mitarbeiter_uid = ? + ORDER BY tbl_lehreinheitgruppe.gruppe_kurzbz"; + + return $this->execQuery($query, [$lva_id, $sem_kurzbz, $ma_uid]); + } + + public function getLehrenheitenBySemester($uid, $semester) + { + $query = "SELECT + tbl_lehreinheit.lehreinheit_id, + lehrfach.kurzbz as lehrfach, + tbl_lehreinheit.lehrform_kurzbz as lehrform, + tbl_lehrveranstaltung.bezeichnung as lv_bezeichnung, + (SELECT kurzbz FROM public.tbl_mitarbeiter + WHERE mitarbeiter_uid=tbl_lehreinheitmitarbeiter.mitarbeiter_uid) as lektor, + UPPER(tbl_studiengang.typ::varchar(1) || tbl_studiengang.kurzbz) as stg_kurzbz, + tbl_lehrveranstaltung.semester as lv_semester, + tbl_lehreinheit.raumtyp, + tbl_lehreinheit.raumtypalternativ, + tbl_lehreinheit.stundenblockung, + tbl_lehreinheit.wochenrythmus, + tbl_lehreinheitmitarbeiter.semesterstunden as semesterstunden, + tbl_lehreinheit.start_kw, + lvangebot.anmeldefenster_start, + lvangebot.anmeldefenster_ende, + tbl_lehrveranstaltung.lehrveranstaltung_id + FROM + lehre.tbl_lehreinheit JOIN lehre.tbl_lehreinheitmitarbeiter USING(lehreinheit_id) + JOIN lehre.tbl_lehrveranstaltung USING(lehrveranstaltung_id) + JOIN public.tbl_studiengang USING(studiengang_kz) + JOIN lehre.tbl_lehrveranstaltung as lehrfach ON(tbl_lehreinheit.lehrfach_id=lehrfach.lehrveranstaltung_id) + LEFT JOIN lehre.tbl_lvangebot as lvangebot ON(tbl_lehrveranstaltung.lehrveranstaltung_id=lvangebot.lehrveranstaltung_id) + WHERE + tbl_lehreinheit.studiensemester_kurzbz = '$semester' + AND mitarbeiter_uid = '$uid' + ORDER BY stg_kurzbz,lv_semester,lv_bezeichnung"; + + return $this->execReadOnlyQuery($query); + } + + public function getGroupsByLehreinheit($lehreinheitId) + { + $query = "SELECT * + FROM lehre.tbl_lehreinheitgruppe + WHERE lehreinheit_id='$lehreinheitId'"; + + return $this->execReadOnlyQuery($query); + } } diff --git a/application/models/education/Lehrveranstaltung_model.php b/application/models/education/Lehrveranstaltung_model.php index ffa652cbc..1cf50ee4d 100644 --- a/application/models/education/Lehrveranstaltung_model.php +++ b/application/models/education/Lehrveranstaltung_model.php @@ -317,7 +317,7 @@ class Lehrveranstaltung_model extends DB_Model tbl_bisio.bisio_id, tbl_bisio.von, tbl_bisio.bis, tbl_student.studiengang_kz AS stg_kz_student, tbl_zeugnisnote.note, tbl_mitarbeiter.mitarbeiter_uid, tbl_person.matr_nr, tbl_benutzer.uid, UPPER(tbl_studiengang.typ::varchar(1) || tbl_studiengang.kurzbz) as kuerzel, tbl_studiengang.orgform_kurzbz, vw_student_lehrveranstaltung.semester, vw_student_lehrveranstaltung.studiensemester_kurzbz, vw_student_lehrveranstaltung.bezeichnung, - tbl_student.prestudent_id + tbl_student.prestudent_id, campus.vw_student_lehrveranstaltung.lehreinheit_id FROM campus.vw_student_lehrveranstaltung JOIN public.tbl_benutzer USING(uid) @@ -1379,4 +1379,114 @@ class Lehrveranstaltung_model extends DB_Model return $this->execQuery($qry, $params); } + + public function getLvForLektorInSemester($sem_kurzbz, $uid) { + $qry = "SELECT DISTINCT (tbl_lehrveranstaltung.lehrveranstaltung_id), + UPPER(tbl_studiengang.typ::varchar(1) || tbl_studiengang.kurzbz) as stg_kurzbz, + tbl_studiengang.studiengang_kz, + tbl_lehrveranstaltung.semester as lv_semester, + tbl_lehrveranstaltung.bezeichnung as lv_bezeichnung, + (SELECT kurzbz FROM public.tbl_mitarbeiter + WHERE mitarbeiter_uid=tbl_lehreinheitmitarbeiter.mitarbeiter_uid) as lektor, + lehre.tbl_lehrveranstaltung.orgform_kurzbz as orgform + + FROM + lehre.tbl_lehreinheit JOIN lehre.tbl_lehreinheitmitarbeiter USING(lehreinheit_id) + JOIN lehre.tbl_lehrveranstaltung USING(lehrveranstaltung_id) + JOIN public.tbl_studiengang USING(studiengang_kz) + JOIN lehre.tbl_lehrveranstaltung as lehrfach ON(tbl_lehreinheit.lehrfach_id=lehrfach.lehrveranstaltung_id) + WHERE + tbl_lehreinheit.studiensemester_kurzbz = ? + AND mitarbeiter_uid = ? + ORDER BY stg_kurzbz,lv_semester,lv_bezeichnung"; + + return $this->execReadOnlyQuery($qry, array($sem_kurzbz, $uid)); + } + + // used for cis4 mylv mitarbeiter + public function getLvsByMitarbeiterInSemester($mitarbeiter_uid, $sem_kurzbz) { + $qry = "SELECT * FROM ( + SELECT DISTINCT ON (lehre.tbl_lehrveranstaltung.lehrveranstaltung_id) + public.tbl_studiengang.studiengang_kz, + lehre.tbl_lehrveranstaltung.semester, + public.tbl_studiengang.bezeichnung as sg_bezeichnung, + public.tbl_studiengang.english as sg_bezeichnung_eng, + UPPER(tbl_studiengang.typ::varchar(1) || tbl_studiengang.kurzbz) as studiengang_kuerzel, + lehre.tbl_lehrveranstaltung.lehrveranstaltung_id, + lehre.tbl_lehrveranstaltung.bezeichnung, + lehre.tbl_lehrveranstaltung.bezeichnung_english as bezeichnung_eng, + lehre.tbl_lehrveranstaltung.farbe, + lehre.tbl_lehrveranstaltung.lvinfo, + lehre.tbl_lehrveranstaltung.benotung, + lehre.tbl_lehrveranstaltung.orgform_kurzbz, + lehre.tbl_lehrveranstaltung.sprache, + lehre.tbl_lehrveranstaltung.ects, + lehre.tbl_lehrveranstaltung.incoming + FROM + lehre.tbl_lehreinheit JOIN lehre.tbl_lehreinheitmitarbeiter USING(lehreinheit_id) + JOIN lehre.tbl_lehrveranstaltung USING(lehrveranstaltung_id) + JOIN public.tbl_studiengang USING(studiengang_kz) + JOIN lehre.tbl_lehrveranstaltung as lehrfach ON(tbl_lehreinheit.lehrfach_id=lehrfach.lehrveranstaltung_id) + WHERE + tbl_lehreinheit.studiensemester_kurzbz = ? + AND mitarbeiter_uid = ?) as distincted_by_lva_id + JOIN ( + SELECT lehrveranstaltung_id, TRUNC(SUM(lehre.tbl_lehreinheitmitarbeiter.semesterstunden)) as semesterstunden + FROM lehre.tbl_lehreinheit + JOIN lehre.tbl_lehreinheitmitarbeiter USING(lehreinheit_id) + JOIN lehre.tbl_lehrveranstaltung USING(lehrveranstaltung_id) + WHERE tbl_lehreinheit.studiensemester_kurzbz = ? + AND mitarbeiter_uid = ? + GROUP BY lehrveranstaltung_id + ) semesterstundenAggregatedSubquery USING(lehrveranstaltung_id) + ORDER BY studiengang_kuerzel, semester, bezeichnung"; + + return $this->execReadOnlyQuery($qry, [$sem_kurzbz, $mitarbeiter_uid, $sem_kurzbz, $mitarbeiter_uid]); + } + + /** + * All Lehrveranstaltungen of a Studiengang offered in a Studiensemester. + */ + public function getLvForStudiengangInSemester($sem_kurzbz, $studiengang_kz) { + $qry = "SELECT DISTINCT (tbl_lehrveranstaltung.lehrveranstaltung_id), + UPPER(tbl_studiengang.typ::varchar(1) || tbl_studiengang.kurzbz) as stg_kurzbz, + tbl_studiengang.studiengang_kz, + tbl_lehrveranstaltung.semester as lv_semester, + tbl_lehrveranstaltung.bezeichnung as lv_bezeichnung, + tbl_lehrveranstaltung.orgform_kurzbz as orgform + FROM + lehre.tbl_lehrveranstaltung JOIN public.tbl_studiengang USING(studiengang_kz) + WHERE + tbl_lehrveranstaltung.studiengang_kz = ? + AND tbl_lehrveranstaltung.aktiv = TRUE + AND EXISTS ( + SELECT 1 FROM lehre.tbl_lehreinheit le + WHERE le.lehrveranstaltung_id = tbl_lehrveranstaltung.lehrveranstaltung_id + AND le.studiensemester_kurzbz = ? + ) + ORDER BY stg_kurzbz,lv_semester,lv_bezeichnung"; + + return $this->execReadOnlyQuery($qry, array($studiengang_kz, $sem_kurzbz)); + } + + /** + * Number of Lehreinheit assignments the given mitarbeiter has for a Lehrveranstaltung (i.e. whether + * they teach it), optionally restricted to a Studiensemester. Returns a single row with a `teaches` + * count - check `teaches > 0`. Used to scope teacher access in the Benotungstool to their own LVs. + */ + public function getLektorIsTeachingLva($lva_id, $ma_uid, $studiensemester_kurzbz = null) + { + $params = array($lva_id, $ma_uid); + $query = "SELECT COUNT(*) AS teaches + FROM lehre.tbl_lehreinheitmitarbeiter + JOIN lehre.tbl_lehreinheit USING (lehreinheit_id) + JOIN lehre.tbl_lehrveranstaltung USING (lehrveranstaltung_id) + WHERE lehrveranstaltung_id = ? AND mitarbeiter_uid = ?"; + if ($studiensemester_kurzbz !== null && $studiensemester_kurzbz !== '') { + $query .= " AND tbl_lehreinheit.studiensemester_kurzbz = ?"; + $params[] = $studiensemester_kurzbz; + } + + return $this->execReadOnlyQuery($query, $params); + } } diff --git a/application/models/education/Lvgesamtnote_model.php b/application/models/education/Lvgesamtnote_model.php index 0a7638869..00112d39c 100644 --- a/application/models/education/Lvgesamtnote_model.php +++ b/application/models/education/Lvgesamtnote_model.php @@ -14,7 +14,7 @@ class Lvgesamtnote_model extends DB_Model } /** - * Laedt die Noten + * Laedt die Noten - lvgesamtnote (Vorschlag) JOIN tbl.note (zeugnisnote) * * @param integer $lehrveranstaltung_id * @param string $student_uid @@ -50,4 +50,19 @@ class Lvgesamtnote_model extends DB_Model return $this->loadWhere($where); } + + public function getLvGesamtNoteVorschlag($lehrveranstaltung_id, $student_uid, $studiensemester_kurzbz) + { + $qry = "SELECT * FROM campus.tbl_lvgesamtnote + WHERE campus.tbl_lvgesamtnote.student_uid = ? + AND campus.tbl_lvgesamtnote.studiensemester_kurzbz = ?"; + $params = [$student_uid, $studiensemester_kurzbz]; + + if ($lehrveranstaltung_id) { + $qry .= " AND campus.tbl_lvgesamtnote.lehrveranstaltung_id = ?"; + $params[] = $lehrveranstaltung_id; + } + + return $this->execReadOnlyQuery($qry, $params); + } } diff --git a/application/models/education/Note_model.php b/application/models/education/Note_model.php index 87a1501e0..e92bb8452 100644 --- a/application/models/education/Note_model.php +++ b/application/models/education/Note_model.php @@ -11,12 +11,33 @@ class Note_model extends DB_Model $this->dbTable = 'lehre.tbl_note'; $this->pk = 'note'; } - + public function getAllActive() { $qry ="SELECT * FROM lehre.tbl_note WHERE aktiv = true"; + + return $this->execReadOnlyQuery($qry); + } + + // used to determine the primary key of note "entschuldigt" to avoid hardcoded magic numbers + // that might differ in a different installation of fhcomplete + public function getEntschuldigtNote() { + $qry ="SELECT * + FROM lehre.tbl_note + WHERE bezeichnung = 'entschuldigt'"; return $this->execReadOnlyQuery($qry); } + + // used to determine the primary key of note "noch nicht eingetragen" to avoid hardcoded magic numbers + // that might differ in a different installation of fhcomplete + public function getNochNichtEingetragenNote() { + $qry ="SELECT * + FROM lehre.tbl_note + WHERE bezeichnung = 'Noch nicht eingetragen'"; + + return $this->execReadOnlyQuery($qry); + } + } \ No newline at end of file diff --git a/application/models/education/Notenschluesselaufteilung_model.php b/application/models/education/Notenschluesselaufteilung_model.php index d48e16b0b..33c8de26c 100644 --- a/application/models/education/Notenschluesselaufteilung_model.php +++ b/application/models/education/Notenschluesselaufteilung_model.php @@ -13,7 +13,7 @@ class Notenschluesselaufteilung_model extends DB_Model } /** - * Liefert die Note zu Punkten einer Lehrveranstaltung + * Gives the grade for the points of a course. * * @param number $points * @param integer $lehrveranstaltung_id @@ -23,9 +23,18 @@ class Notenschluesselaufteilung_model extends DB_Model */ public function getNote($points, $lehrveranstaltung_id, $studiensemester_kurzbz) { + // Without a valid points value you cannot derive a grade. Without this guard the query + // builder makes a bad statement from "punkte <=" => null ('punkte' < 'IS' 'NULL'). The + // request then fails with HTTP 500 instead of an answer that says "no grade". + if (!is_numeric($points)) + return success(null); + $this->load->model('education/Notenschluesselzuordnung_model', 'NotenschluesselzuordnungModel'); $notenschluessel_kurzbz = $this->NotenschluesselzuordnungModel->getKurzbzForLv($lehrveranstaltung_id, $studiensemester_kurzbz); + if($notenschluessel_kurzbz == null) + return success(null); + $this->addSelect("note"); $this->addOrder("punkte", "DESC"); $this->addLimit(1); diff --git a/application/models/education/Paabgabe_model.php b/application/models/education/Paabgabe_model.php index 064a2507a..3f6159bd6 100644 --- a/application/models/education/Paabgabe_model.php +++ b/application/models/education/Paabgabe_model.php @@ -60,6 +60,172 @@ class Paabgabe_model extends DB_Model return $this->execReadOnlyQuery($qry, array($person_id)); } + /** + * Gets project submissions for search criteria. + * @param array $projekttyp_kurzbz_arr contains all relevant project types (e.g. Bachelor) + * @param int $studiengang_kz study program + * @param string $abgabetyp_kurzbz project submission type (e.g. end upload, intermediate submission) + * @param string $abgabedatum due date for hand-in + * @param string $personSearchString for searching by person, i.e. name, uid, person/prestudent id + * @param int $limit limiting max number of results if search criteria is not precise enough + * @return object + */ + public function getPaAbgaben( + $projekttyp_kurzbz_arr, + $studiengang_kz = null, + $abgabetyp_kurzbz = null, + $abgabedatum = null, + $personSearchString = null, + $limit = 1000 + ) { + $params = []; + + $qry = " + SELECT + stg.bezeichnung AS stgbez, paabg.datum AS termin, + paabg.paabgabe_id, paabg.projektarbeit_id, paabg.paabgabetyp_kurzbz, paabg.abgabedatum, + abgabetyp.bezeichnung AS paabgabetyp_bezeichnung, ben.uid, pers.vorname, pers.nachname, pa.projekttyp_kurzbz, pa.titel, + UPPER(stg.typ || stg.kurzbz) AS studiengang_kuerzel, + ( + /* show all relevant Studiengänge of person and wether it is an employee*/ + SELECT + STRING_AGG(studiengang || ' ' || last_status, ' | ') + || (CASE WHEN EXISTS ( + SELECT 1 FROM public.tbl_mitarbeiter ma + JOIN public.tbl_benutzer ben ON ma.mitarbeiter_uid = ben.uid + WHERE person_id = prestudents.person_id + AND ben.aktiv + ) THEN ' | Mitarbeiter' ELSE '' END) + FROM ( + SELECT + DISTINCT person_id, prestudent_id, UPPER(stg.typ || stg.kurzbz) AS studiengang, + get_rolle_prestudent(ps.prestudent_id, null) AS last_status + FROM + public.tbl_prestudent ps + JOIN public.tbl_studiengang stg USING (studiengang_kz) + WHERE + person_id = pers.person_id + ORDER BY + prestudent_id DESC + ) prestudents + WHERE + last_status IN ('Abgewiesener','Aufgenommener', 'Student', 'Incoming', 'Diplomand', 'Abbrecher', 'Unterbrecher', 'Absolvent') + GROUP BY + person_id + LIMIT 1; + ) AS status + FROM + lehre.tbl_projektarbeit pa + JOIN campus.tbl_paabgabe paabg USING(projektarbeit_id) + JOIN campus.tbl_paabgabetyp abgabetyp USING(paabgabetyp_kurzbz) + LEFT JOIN public.tbl_benutzer ben ON(uid=student_uid) + LEFT JOIN public.tbl_person pers ON(ben.person_id=pers.person_id) + LEFT JOIN lehre.tbl_lehrveranstaltung USING(lehrveranstaltung_id) + LEFT JOIN public.tbl_studiengang stg USING(studiengang_kz) + WHERE + TRUE"; + + if (isset($projekttyp_kurzbz_arr) && !isEmptyArray($projekttyp_kurzbz_arr)) + { + $qry .= " AND projekttyp_kurzbz IN ?"; + $params[] = $projekttyp_kurzbz_arr; + } + + if (isset($studiengang_kz) && is_numeric($studiengang_kz)) + { + $qry .= " AND stg.studiengang_kz=?"; + $params[] = $studiengang_kz; + } + + if (isset($abgabetyp_kurzbz)) + { + $qry .= " AND paabg.paabgabetyp_kurzbz=?"; + $params[] = $abgabetyp_kurzbz; + } + + if (isset($abgabedatum)) + { + $qry .= " AND paabg.datum=?"; + $params[] = $abgabedatum; + } + + if (is_numeric($personSearchString)) + { + $personSearchString = (int) $personSearchString; + $params = array_merge($params, [$personSearchString, $personSearchString]); + $qry .= " AND ( + pers.person_id = ? + OR EXISTS (SELECT 1 FROM public.tbl_prestudent WHERE person_id = pers.person_id AND prestudent_id = ?) + )"; + } + elseif (is_string($personSearchString)) + { + // remove empty spaces and lowercase + $personSearchString = strtolower(str_replace(' ', '', $personSearchString)); + $qry .= " AND ( + LOWER(REPLACE(pers.nachname || pers.vorname || pers.nachname, ' ', '')) LIKE ".$this->db->escape('%'.$personSearchString.'%')." + OR ben.uid LIKE ".$this->db->escape('%'.$personSearchString.'%')." + )"; + } + + $qry .= " ORDER BY nachname"; + + if (isset($limit) && is_numeric($limit) && (!isset($studiengang_kz) || !is_numeric($studiengang_kz)) && !isset($abgabedatum)) + { + $qry .= " LIMIT ?"; + $params[] = $limit; + } + + return $this->execReadOnlyQuery($qry, $params); + } + + /** + * Gets due dates for projekt submission search criteria. + * @param array $projekttyp_kurzbz_arr contains all relevant project types (e.g. Bachelor) + * @param int $studiengang_kz study program + * @param string $abgabetyp_kurzbz project submission type (e.g. end upload, intermediate submission) + * @return object + */ + public function getTermine($projekttyp_kurzbz_arr, $studiengang_kz, $abgabetyp_kurzbz) + { + $params = []; + + $qry = " + SELECT + DISTINCT tbl_paabgabe.datum as termin, to_char(tbl_paabgabe.datum, 'DD.MM.YYYY') as termin_anzeige + FROM + lehre.tbl_projektarbeit + JOIN campus.tbl_paabgabe USING(projektarbeit_id) + LEFT JOIN public.tbl_benutzer ON(uid=student_uid) + LEFT JOIN public.tbl_person ON(tbl_benutzer.person_id=tbl_person.person_id) + LEFT JOIN lehre.tbl_lehrveranstaltung USING(lehrveranstaltung_id) + LEFT JOIN public.tbl_studiengang USING(studiengang_kz) + WHERE + TRUE"; + + if (isset($projekttyp_kurzbz_arr) && !isEmptyArray($projekttyp_kurzbz_arr)) + { + $qry .= " AND projekttyp_kurzbz IN ?"; + $params[] = $projekttyp_kurzbz_arr; + } + + if (isset($studiengang_kz) && is_numeric($studiengang_kz)) + { + $qry .= " AND public.tbl_studiengang.studiengang_kz=?"; + $params[] = $studiengang_kz; + } + + if (isset($abgabetyp_kurzbz)) + { + $qry .= " AND campus.tbl_paabgabe.paabgabetyp_kurzbz=?"; + $params[] = $abgabetyp_kurzbz; + } + + $qry .= " ORDER BY termin DESC"; + + return $this->execReadOnlyQuery($qry, $params); + } + public function findAbgabenNewOrUpdatedSince($interval, $relevantTypes) { diff --git a/application/models/education/Projektarbeit_model.php b/application/models/education/Projektarbeit_model.php index 43d33e4e8..c002349b0 100644 --- a/application/models/education/Projektarbeit_model.php +++ b/application/models/education/Projektarbeit_model.php @@ -23,8 +23,10 @@ class Projektarbeit_model extends DB_Model */ public function getProjektarbeit($student_uid, $studiengang_kz = null, $studiensemester_kurzbz = null, $projekttyp = null, $final = null) { + $sprache_index = "COALESCE((SELECT index FROM public.tbl_sprache WHERE sprache=" . $this->escape(getUserLanguage()) . " LIMIT 1), 1)"; $qry = "SELECT - pa.*, tbl_projekttyp.bezeichnung, + pa.*, tbl_projekttyp.bezeichnung, tbl_lehrveranstaltung.lehrveranstaltung_id, + tbl_sprache.bezeichnung[".$sprache_index."] AS sprache_bezeichnung, pa.studiensemester_kurzbz, pa.lehrveranstaltung_id, tbl_firma.name AS firma_name, ( @@ -43,6 +45,7 @@ class Projektarbeit_model extends DB_Model JOIN lehre.tbl_projekttyp USING (projekttyp_kurzbz) JOIN lehre.tbl_lehrveranstaltung USING (lehrveranstaltung_id) LEFT JOIN public.tbl_firma USING (firma_id) + LEFT JOIN public.tbl_sprache ON pa.sprache = tbl_sprache.sprache WHERE pa.student_uid = ?"; diff --git a/application/models/education/Pruefung_model.php b/application/models/education/Pruefung_model.php index 927d83c82..2f7ba8cf8 100644 --- a/application/models/education/Pruefung_model.php +++ b/application/models/education/Pruefung_model.php @@ -306,4 +306,5 @@ class Pruefung_model extends DB_Model return $this->loadWhereCommitteeExamsFailed(); } + } diff --git a/application/models/education/Studierendenantrag_model.php b/application/models/education/Studierendenantrag_model.php index c18d18053..1fa5b89ef 100644 --- a/application/models/education/Studierendenantrag_model.php +++ b/application/models/education/Studierendenantrag_model.php @@ -515,4 +515,75 @@ class Studierendenantrag_model extends DB_Model return hasData($this->load()); } + + /** + * Loads all todos for a certain studiengangsleiter + * + * @param integer $stg_kz + * + * @return stdClass + */ + public function getOpenAntraegeForStgl($stg_kz) + { + $qry = " + SELECT + studierendenantrag_id, + prestudent_id, + typ, + person.nachname, + person.vorname, + ps.studiengang_kz + FROM campus.tbl_studierendenantrag a + JOIN public.tbl_prestudent ps USING (prestudent_id) + JOIN public.tbl_person person USING (person_id) + WHERE studiengang_kz in ? + AND ( + (typ IN ('Abmeldung', 'AbmeldungStgl', 'Unterbrechung') + AND campus.get_status_studierendenantrag(a.studierendenantrag_id) = 'Erstellt') + OR + (typ = 'Wiederholung' + AND campus.get_status_studierendenantrag(a.studierendenantrag_id) = 'Lvszugewiesen') + ) + ORDER BY typ; + "; + + $params = array($stg_kz); + return $this->execQuery($qry, $params); + } + + /** + * Lists all open antraege for a certain stg + * + * @param Array $stg_kz + * + * @return stdClass + */ + public function getOpenAntraegeForAss($stg_kz) + { + $qry = " + SELECT + UPPER(stg.typ) || UPPER(stg.kurzbz) || ' ' || stg.bezeichnung AS studiengang, + COUNT(*) AS anzahl + FROM campus.tbl_studierendenantrag + JOIN public.tbl_prestudent p USING (prestudent_id) + JOIN public.tbl_studiengang stg ON p.studiengang_kz=stg.studiengang_kz + JOIN campus.tbl_studierendenantrag_status as s ON campus.get_status_id_studierendenantrag(campus.tbl_studierendenantrag.studierendenantrag_id) = studierendenantrag_status_id + JOIN campus.tbl_studierendenantrag_statustyp as st USING (studierendenantrag_statustyp_kurzbz) + WHERE ( + s.studierendenantrag_statustyp_kurzbz NOT IN('Zurueckgezogen', 'Genehmigt', 'Abgelehnt', 'EinspruchAbgelehnt', 'Abgemeldet', 'Pause', 'EmailVersandt') + OR ( + s.studierendenantrag_statustyp_kurzbz = 'Genehmigt' + AND tbl_studierendenantrag.typ = 'AbmeldungStgl' + ) + ) + AND p.studiengang_kz IN ? + GROUP BY studiengang + ORDER BY studiengang + " + ; + + $params = array($stg_kz); + return $this->execQuery($qry, $params); + } + } diff --git a/application/models/organisation/Studiengang_model.php b/application/models/organisation/Studiengang_model.php index db0996319..9a0a992b0 100644 --- a/application/models/organisation/Studiengang_model.php +++ b/application/models/organisation/Studiengang_model.php @@ -653,31 +653,42 @@ class Studiengang_model extends DB_Model } /** + * Gets the Studiengang information. Without parameters it uses the Studiengang + * of the logged in Student. + * + * @param int|null $studiengang_kz + * @param int|null $semester used for the Jahrgangsvertretung * @return stdClass */ - public function getStudiengangInfoForNews() + public function getStudiengangInfoForNews($studiengang_kz = null, $semester = null) { $this->load->model('person/Benutzerfunktion_model', 'BenutzerfunktionModel'); $this->load->model('person/Person_model', 'PersonModel'); $this->load->model('crm/Student_model', 'StudentModel'); - - $student = $this->StudentModel->loadWhere(['student_uid' => getAuthUID()]); - if (isError($student)) - return error($student); - if (getData($student)) { - $student = current(getData($student)); - $studiengang_kz = $student->studiengang_kz; - $semester = $student->semester; + + if (is_null($studiengang_kz)) + { + $student = $this->StudentModel->loadWhere(['student_uid' => getAuthUID()]); + if (isError($student)) + return error($student); + if (getData($student)) { + $student = current(getData($student)); + $studiengang_kz = $student->studiengang_kz; + $semester = $student->semester; + } } - + + // users without a Student record get no information instead of an error + if (is_null($studiengang_kz)) + return success(null); + $stg_obj = $this->load($studiengang_kz); if(isError($stg_obj)) return error($stg_obj); - if(getData($stg_obj)) - { - $stg_obj = current(getData($stg_obj)); - } + if(!hasData($stg_obj)) + return error('Studiengang nicht gefunden'); + $stg_obj = current(getData($stg_obj)); $stg_ltg = $this->getLeitungDetailed($stg_obj->studiengang_kz); if (isError($stg_ltg)) @@ -886,6 +897,14 @@ class Studiengang_model extends DB_Model return success($ass); } + public function getDegreePrograms() { + $query = "SELECT * + FROM public.tbl_studiengang + ORDER BY public.tbl_studiengang.kurzbzlang"; + + return $this->execReadOnlyQuery($query); + } + private function addEmailProperty(&$benutzerfunktionen) { if(count($benutzerfunktionen) && defined('DOMAIN')) { diff --git a/application/models/organisation/Studienplan_model.php b/application/models/organisation/Studienplan_model.php index 4a5f87832..66c23de0a 100644 --- a/application/models/organisation/Studienplan_model.php +++ b/application/models/organisation/Studienplan_model.php @@ -59,6 +59,37 @@ class Studienplan_model extends DB_Model 'tbl_studienplan_lehrveranstaltung.semester' => $semester )); } + + public function getStudienplanByLvaSemKurzbz($lehrveranstaltung_id, $studiensemester_kurzbz) { + $qry= " + SELECT + DISTINCT tbl_studienplan.* + FROM + lehre.tbl_studienplan + JOIN lehre.tbl_studienplan_lehrveranstaltung + USING(studienplan_id) + WHERE + tbl_studienplan_lehrveranstaltung.lehrveranstaltung_id IN ( + SELECT + lv.lehrveranstaltung_id + FROM + lehre.tbl_lehrveranstaltung AS lv + LEFT JOIN lehre.tbl_lehrveranstaltung AS t ON t.lehrveranstaltung_id=lv.lehrveranstaltung_template_id + WHERE + lv.lehrtyp_kurzbz<>'tpl' + AND (lv.lehrveranstaltung_id= ? OR (lv.lehrveranstaltung_template_id= ? AND t.lehrtyp_kurzbz='tpl')) + ) + AND EXISTS ( + SELECT 1 + FROM + lehre.tbl_studienplan_semester + WHERE studienplan_id=tbl_studienplan.studienplan_id + AND studiensemester_kurzbz= ? + AND semester = tbl_studienplan_lehrveranstaltung.semester) + ORDER BY bezeichnung"; + + return $this->execReadOnlyQuery($qry, array($lehrveranstaltung_id, $lehrveranstaltung_id, $studiensemester_kurzbz)); + } public function getStudienplanLehrveranstaltungForPrestudent($studienplan_id, $semester, $prestudent_id) { diff --git a/application/models/organisation/Studiensemester_model.php b/application/models/organisation/Studiensemester_model.php index e53850560..67e2a9e99 100644 --- a/application/models/organisation/Studiensemester_model.php +++ b/application/models/organisation/Studiensemester_model.php @@ -375,6 +375,30 @@ EOSQL; return $this->loadWhere(['uid' => $student_uid, 'v.lehre' => true]); } + public function getWhereMitarbeiterHasLvs($uid) { + // first order by year with last 2 letter from right, + // then order by WS/SS inside the years + // query it asc so the ordering magic in cis4 turns it around again + $qry = "WITH unique_semesters AS ( + SELECT DISTINCT ON (studiensemester_kurzbz) + studiensemester_kurzbz, + start, + ende, + bezeichnung, + studienjahr_kurzbz + FROM lehre.tbl_lehreinheit + JOIN lehre.tbl_lehreinheitmitarbeiter USING(lehreinheit_id) + JOIN public.tbl_studiensemester USING(studiensemester_kurzbz) + WHERE mitarbeiter_uid = ? + ) + SELECT * FROM unique_semesters + ORDER BY + RIGHT(studiensemester_kurzbz, 2) ASC, + LEFT(studiensemester_kurzbz, 2) ASC;"; + + return $this->execReadOnlyQuery($qry, [$uid]); + } + public function getAktAndFutureSemester() { $query = 'SELECT studiensemester_kurzbz diff --git a/application/models/person/Benutzerfunktion_model.php b/application/models/person/Benutzerfunktion_model.php index 52236ba7c..83d106210 100644 --- a/application/models/person/Benutzerfunktion_model.php +++ b/application/models/person/Benutzerfunktion_model.php @@ -319,6 +319,40 @@ class Benutzerfunktion_model extends DB_Model return $record; } + /** + * Get active Assistenz(en) of the user by UID. + * @param $uid + */ + public function getSTGAssByUID($uid) + { + $query = ' + SELECT + uid, + oe_kurzbz, + studiengang_kz, + typ, + tbl_studiengang.bezeichnung + FROM + public.tbl_benutzerfunktion + JOIN public.tbl_studiengang USING (oe_kurzbz) + WHERE + funktion_kurzbz = \'ass\' + AND (datum_von IS NULL OR datum_von <= now()) + AND (datum_bis IS NULL OR datum_bis >= now()) + AND uid = ? + ORDER BY + oe_kurzbz + '; + + $parameters_array = array(); + if (is_string($uid)) + { + $parameters_array[] = $uid; + } + + return $this->execQuery($query, $parameters_array); + } + function updateBenutzerfunktion($funktionJson) { $funktionJson['updatevon'] = getAuthUID(); diff --git a/application/models/ressource/Ort_model.php b/application/models/ressource/Ort_model.php index de0c8e331..9e2f286d0 100644 --- a/application/models/ressource/Ort_model.php +++ b/application/models/ressource/Ort_model.php @@ -12,6 +12,12 @@ class Ort_model extends DB_Model $this->pk = "ort_kurzbz"; } + public function getRoomInfo($ortKurzbz) + { + $this->addSelect("ort_kurzbz, ausstattung"); + return $this->OrtModel->loadWhere(array("ort_kurzbz" => $ortKurzbz)); + } + public function getAll($raumtyp_kurzbz) { $this->addOrder("ort_kurzbz"); diff --git a/application/models/ressource/Reservierung_model.php b/application/models/ressource/Reservierung_model.php index d96fe2f6f..88fa0d2dd 100644 --- a/application/models/ressource/Reservierung_model.php +++ b/application/models/ressource/Reservierung_model.php @@ -24,7 +24,7 @@ class Reservierung_model extends DB_Model $lvplan_reservierungen_query = "SELECT r.* , stund.beginn, stund.ende, CASE WHEN r.gruppe_kurzbz IS NOT NULL THEN r.gruppe_kurzbz - ELSE CONCAT(UPPER(studg.typ),UPPER(studg.kurzbz),'-',COALESCE(CAST(r.semester AS varchar),'/'),COALESCE(CAST(r.verband AS varchar),'/')) + ELSE CONCAT(UPPER(studg.typ),UPPER(studg.kurzbz),'-',COALESCE(CAST(r.semester AS varchar),'/'),COALESCE(CAST(r.verband AS varchar),'/'),COALESCE(CAST(r.gruppe AS varchar),'/')) END as gruppen_kuerzel FROM campus.vw_reservierung r JOIN public.tbl_studiengang studg ON studg.studiengang_kz=r.studiengang_kz @@ -39,7 +39,7 @@ class Reservierung_model extends DB_Model $raum_reservierungen_query = "SELECT res.*, beginn, ende, CASE WHEN res.gruppe_kurzbz IS NOT NULL THEN res.gruppe_kurzbz - ELSE CONCAT(UPPER(studg.typ),UPPER(studg.kurzbz),'-',COALESCE(CAST(res.semester AS varchar),'/'),COALESCE(CAST(res.verband AS varchar),'/')) + ELSE CONCAT(UPPER(studg.typ),UPPER(studg.kurzbz),'-',COALESCE(CAST(res.semester AS varchar),'/'),COALESCE(CAST(res.verband AS varchar),'/'),COALESCE(CAST(res.gruppe AS varchar),'/')) END as gruppen_kuerzel FROM campus.vw_reservierung res JOIN public.tbl_studiengang studg ON studg.studiengang_kz=res.studiengang_kz @@ -50,11 +50,12 @@ class Reservierung_model extends DB_Model $query_result = $this->execReadOnlyQuery(" SELECT - 'reservierung' as type, beginn, ende, datum, + DISTINCT(insertvon), + 'reservierung' as type, beginn, ende, datum, array_agg(DISTINCT reservierung_id) AS reservierung_id, COALESCE(titel, beschreibung) as topic, array_agg(DISTINCT mitarbeiter_kurzbz) as lektor, array_agg(DISTINCT (gruppe,verband,semester,studiengang_kz,gruppen_kuerzel)) as gruppe, - + array_agg(DISTINCT(uid)) as uids, ort_kurzbz, 'FFFFFF' as farbe FROM @@ -62,7 +63,7 @@ class Reservierung_model extends DB_Model " . $subquery . " ) AS subquery - GROUP BY datum, beginn, ende, ort_kurzbz, titel, beschreibung + GROUP BY datum, beginn, ende, ort_kurzbz, titel, beschreibung, insertvon ORDER BY datum, beginn ", is_null($ort_kurzbz) ? [$uid ?? getAuthUID(), $uid ?? getAuthUID(), $start_date, $end_date] : [$ort_kurzbz, $start_date, $end_date]); @@ -82,7 +83,7 @@ class Reservierung_model extends DB_Model $raum_reservierungen_query = "SELECT res.*, beginn, ende, CASE WHEN res.gruppe_kurzbz IS NOT NULL THEN res.gruppe_kurzbz - ELSE CONCAT(UPPER(studg.typ),UPPER(studg.kurzbz),'-',COALESCE(CAST(res.semester AS varchar),'/'),COALESCE(CAST(res.verband AS varchar),'/')) + ELSE CONCAT(UPPER(studg.typ),UPPER(studg.kurzbz),'-',COALESCE(CAST(res.semester AS varchar),'/'),COALESCE(CAST(res.verband AS varchar),'/'),COALESCE(CAST(res.gruppe AS varchar),'/')) END as gruppen_kuerzel FROM campus.vw_reservierung res JOIN public.tbl_studiengang studg ON studg.studiengang_kz=res.studiengang_kz @@ -94,11 +95,12 @@ class Reservierung_model extends DB_Model $query_result = $this->execReadOnlyQuery(" SELECT - 'reservierung' as type, beginn, ende, datum, + DISTINCT(insertvon), + 'reservierung' as type, beginn, ende, datum, array_agg(DISTINCT reservierung_id) AS reservierung_id, COALESCE(titel, beschreibung) as topic, array_agg(DISTINCT mitarbeiter_kurzbz) as lektor, array_agg(DISTINCT (gruppe,verband,semester,studiengang_kz,gruppen_kuerzel)) as gruppe, - + array_agg(DISTINCT(uid)) as uids, ort_kurzbz, 'FFFFFF' as farbe FROM @@ -106,7 +108,7 @@ class Reservierung_model extends DB_Model " . $subquery . " ) AS subquery - GROUP BY datum, beginn, ende, ort_kurzbz, titel, beschreibung + GROUP BY datum, beginn, ende, ort_kurzbz, titel, beschreibung, insertvon ORDER BY datum, beginn ", [$uid ?? getAuthUID(), $start_date, $end_date]); @@ -146,4 +148,40 @@ class Reservierung_model extends DB_Model return $this->execReadOnlyQuery($qry, [$uid, $datum, $stunde]); } + public function deleteReservation($reservierung_id) { + $uid = getAuthUID(); + + $result = $this->load($reservierung_id); + + if (isError($result)) + return $result; + + if (!hasData($result)) + return error('Reservierung nicht gefunden'); + + $row = $result->retval[0]; + + if ($row->uid !== $uid && $row->insertvon !== $uid) + return error('Keine Berechtigung'); + + return $this->delete($reservierung_id); + } + + public function getMyReservation() { + $uid = getAuthUID(); + $date = date("Y-m-d",time()); + + $qry = "SELECT vw_reservierung.*, + trim(COALESCE(vw_mitarbeiter.titelpre,'')||' '||COALESCE(vw_mitarbeiter.vorname,'')||' '||COALESCE(vw_mitarbeiter.nachname,'')||' '||COALESCE(vw_mitarbeiter.titelpost,'')) as reserviert_fuer, + trim(COALESCE(reserviert_von.titelpre,'')||' '||COALESCE(reserviert_von.vorname,'')||' '||COALESCE(reserviert_von.nachname,'')||' '||COALESCE(reserviert_von.titelpost,'')) as reserviert_von + FROM campus.vw_reservierung + JOIN campus.vw_mitarbeiter ON vw_reservierung.uid=vw_mitarbeiter.uid + LEFT JOIN campus.vw_mitarbeiter reserviert_von ON vw_reservierung.insertvon=reserviert_von.uid + WHERE datum >= ? + AND (vw_reservierung.uid= ? OR vw_reservierung.insertvon= ? ) + ORDER BY datum, titel, ort_kurzbz, stunde"; + + return $this->execReadOnlyQuery($qry, [$date, $uid, $uid]); + } + } diff --git a/application/models/ressource/Zeitsperre_model.php b/application/models/ressource/Zeitsperre_model.php index 50ba4cb1d..55fdb5b9d 100644 --- a/application/models/ressource/Zeitsperre_model.php +++ b/application/models/ressource/Zeitsperre_model.php @@ -12,6 +12,8 @@ class Zeitsperre_model extends DB_Model $this->pk = 'zeitsperre_id'; } + const BLOCKIERENDE_ZEITSPERREN = ['Krank','Urlaub','ZA','DienstV','PflegeU','DienstF','CovidSB','CovidKS']; + /** * Save or update Zeitsperre. * @@ -62,6 +64,270 @@ class Zeitsperre_model extends DB_Model return $this->execQuery($qry); } + /** + * get Zeitsperren of a user + * + * @param $uid mitarbeiteruid + * @param $bisgrenze @true show only entries of actual business year (1.9.- 31.8.) + * + * @return array + */ + public function getZeitsperrenUser($uid, $bisgrenze = true) + { + $qry = " + SELECT + tbl_zeitsperre.*, tbl_zeitsperretyp.*, tbl_erreichbarkeit.farbe AS erreichbarkeit_farbe, + tbl_erreichbarkeit.beschreibung AS erreichbarkeit_beschreibung, + CONCAT (ps.vorname, ' ', ps.nachname) as vertretung + FROM (campus.tbl_zeitsperre JOIN campus.tbl_zeitsperretyp USING (zeitsperretyp_kurzbz)) + LEFT JOIN campus.tbl_erreichbarkeit USING (erreichbarkeit_kurzbz) + LEFT JOIN public.tbl_benutzer ON campus.tbl_zeitsperre.vertretung_uid = public.tbl_benutzer.uid + LEFT JOIN public.tbl_person ps USING (person_id) + WHERE mitarbeiter_uid= ? + "; + + if($bisgrenze) + { + $qry.=" + AND ( + (date_part('month',vondatum)>=9 AND date_part('year', vondatum)>='".(date('Y')-1)."') + OR + (date_part('month',vondatum)<9 AND date_part('year', vondatum)>='".(date('Y'))."') + )"; + } + + $qry.= " ORDER BY vondatum DESC"; + + return $this->execQuery($qry, array('mitarbeiter_uid' => $uid)); + } + + /** + * check a date for existing zeitsperre + * + * @param $uid mitarbeiteruid + * @param $datum datum to check + * @param $stunde stunde (default = null) + * @param bool $nurblockierend if only hr relevante zeitsperren have to be checked + * + * @return array + */ + public function getSperreByDate($uid, $datum, $stunde = null, $nurblockierend = false) + { + $parametersArray = [$datum, $datum]; + + $qry = " + SELECT + * + FROM + campus.tbl_zeitsperre + WHERE + vondatum <= ? + AND bisdatum>= ?"; + + if($nurblockierend) + { + $qry .= " AND zeitsperretyp_kurzbz IN ('" + . implode("','", self::BLOCKIERENDE_ZEITSPERREN) + . "')"; + } + + if(!is_null($stunde)) + { + $parametersArray = array_merge( + $parametersArray, + [$datum, $stunde, $datum, $datum, $stunde, $datum] + ); + + $qry.=" AND + ((vondatum= ? AND vonstunde<= ? OR vonstunde is null OR vondatum<> ?) AND + (bisdatum= ? AND bisstunde>= ? OR bisstunde is null OR bisdatum<> ?))"; + } + + array_push($parametersArray, $uid); + + $qry .= "AND mitarbeiter_uid= ? "; + + return $this->execQuery($qry, $parametersArray); + } + + /** + * check a date for existing zeitsperre + * + * @param $uid mitarbeiteruid + * @param $vondatum datum in Format IS0 + * @param $bisdatum datum in Format ISO + * + * @return array + */ + public function existsZeitaufzeichnung($uid, $vonDay, $bisDay) + { + try { + $from = new DateTime($vonDay); + $to = new DateTime($bisDay); + } catch (Exception $e) { + throw new Exception("Invalid date format"); + } + + //remove hour stamps + $from->setTime(0, 0, 0); + $to->setTime(0, 0, 0)->modify('+1 day'); + + $fromSql = $from->format('Y-m-d'); + $toSql = $to->format('Y-m-d'); + $params = [$uid, $fromSql, $toSql]; + + $qry = " + SELECT * + FROM campus.tbl_zeitaufzeichnung + WHERE uid = ? + AND start >= ? + AND ende < ? "; + + $result = $this->execQuery($qry, $params); + + return $result; + } + + /** + * getZeitsperren of Mitarbeiter for the next days + * @ days: count of the intervall of the next days + * @return array + */ + public function getZeitsperrenForNextDays($days, $uid=null) + { + $von = date('Y-m-d'); + $bis = date('Y-m-d', strtotime("+{$days} days")); + + $paramsArray = [$von, $bis, $bis, $von]; + + if($uid) + $paramsArray[] = $uid; + + $qry = "select + nachname, + vorname, + uid, + vondatum, + bisdatum, + vertretung_uid, + erreichbarkeit_kurzbz, + mv.kurzbz + from + campus.vw_mitarbeiter + join campus.tbl_zeitsperre on + (uid = mitarbeiter_uid) + left join public.tbl_mitarbeiter mv on campus.tbl_zeitsperre.vertretung_uid = mv.mitarbeiter_uid + where + ((? <= bisdatum + and ? >= bisdatum) + or (?>= vondatum + and ? <= vondatum))"; + + if($uid) + $qry .= " and uid = ? "; + + $qry .= " + order by nachname + "; + + $result = $this->execQuery($qry, $paramsArray); + + return $result; + } + + public function getMitarbeiterWithZeitsperren($von, $bis, $fix=false, $lector=false, $oe=null, $ass=false, $stg=false) + { + $paramsArray = [$von, $bis, $bis, $von]; + + if($oe) + { + $paramsArray[] = $oe; + } + + if($stg) + { + $paramsArray[] = $stg; + } + + $qry = " + SELECT + m.nachname, + m.vorname, + m.uid, + z.vondatum, + z.bisdatum, + z.vertretung_uid, + z.erreichbarkeit_kurzbz, + m.lektor, + m.fixangestellt, + mv.kurzbz + FROM campus.vw_mitarbeiter m + join public.tbl_benutzer bn on bn.uid = m.uid"; + + if($oe || $ass || $stg) + { + $qry .= " JOIN public.tbl_benutzerfunktion bf on bf.uid = bn.uid"; + } + + if($stg) + { + $qry .= " JOIN public.tbl_studiengang stg using (oe_kurzbz)"; + } + + $qry.= " + LEFT JOIN campus.tbl_zeitsperre z + ON m.uid = z.mitarbeiter_uid + AND ( + (? <= z.bisdatum AND ? >= z.bisdatum) + OR + (? >= z.vondatum AND ? <= z.vondatum) + ) + left join public.tbl_mitarbeiter mv on z.vertretung_uid = mv.mitarbeiter_uid + where bn.aktiv = true + "; + + + if($fix) { + $qry.= " AND m.fixangestellt = true"; + } + if($lector) { + $qry.= " AND m.lektor = true"; + } + + if ($oe) + { + $qry.= " + AND bf.funktion_kurzbz ='oezuordnung' + AND bf.oe_kurzbz= ? + AND (bf.datum_von is null or bf.datum_von <=now()) + AND (bf.datum_bis is null or bf.datum_bis >= now()) + "; + } + + if ($ass) + { + $qry.= " + AND bf.funktion_kurzbz ='ass' + AND (bf.datum_von is null or bf.datum_von <=now()) + AND (bf.datum_bis is null or bf.datum_bis >= now()) + "; + } + + if ($stg) + { + $qry.= " + AND stg.studiengang_kz = ? + "; + } + + $qry .= " order by nachname, vorname, uid"; + + + $result = $this->execQuery($qry, $paramsArray); + + return $result; + } + public function checkIfZeitsperreExists($uid, $datum, $stunde) { $this->db->select("*"); @@ -71,30 +337,29 @@ class Zeitsperre_model extends DB_Model $this->db->group_start(); - $this->db->where('vondatum <', $datum); - $this->db->or_group_start(); - $this->db->where('vondatum', $datum); - $this->db->group_start(); - $this->db->where('vonstunde <=', $stunde); - $this->db->or_where('vonstunde IS NULL', null, false); - $this->db->group_end(); - $this->db->group_end(); + $this->db->where('vondatum <', $datum); + $this->db->or_group_start(); + $this->db->where('vondatum', $datum); + $this->db->group_start(); + $this->db->where('vonstunde <=', $stunde); + $this->db->or_where('vonstunde IS NULL', null, false); + $this->db->group_end(); + $this->db->group_end(); $this->db->group_end(); $this->db->group_start(); - $this->db->where('bisdatum >', $datum); - $this->db->or_group_start(); - $this->db->where('bisdatum', $datum); - $this->db->group_start(); - $this->db->where('bisstunde >=', $stunde); - $this->db->or_where('bisstunde IS NULL', null, false); - $this->db->group_end(); + $this->db->where('bisdatum >', $datum); + $this->db->or_group_start(); + $this->db->where('bisdatum', $datum); + $this->db->group_start(); + $this->db->where('bisstunde >=', $stunde); + $this->db->or_where('bisstunde IS NULL', null, false); + $this->db->group_end(); - $this->db->group_end(); + $this->db->group_end(); $this->db->group_end(); return $this->load(); - } } diff --git a/application/models/ressource/Zeitsperretyp_model.php b/application/models/ressource/Zeitsperretyp_model.php index ef54571e8..b693d189b 100644 --- a/application/models/ressource/Zeitsperretyp_model.php +++ b/application/models/ressource/Zeitsperretyp_model.php @@ -11,4 +11,20 @@ class Zeitsperretyp_model extends DB_Model $this->dbTable = 'campus.tbl_zeitsperretyp'; $this->pk = 'zeitsperretyp_kurzbz'; } + + public function getTypenZeitsperren($excludedTypes = []) + { + $whereNotClause = ""; + if (count($excludedTypes)) { + $excludedTypes = array_map( + function ($type) { + return "'$type'"; + }, + $excludedTypes + ); + $excludedTypes = implode(", ", $excludedTypes); + $whereNotClause = "WHERE NOT zeitsperretyp_kurzbz IN ($excludedTypes)"; + } + return $this->execQuery("SELECT * FROM $this->dbTable $whereNotClause ORDER BY beschreibung ASC"); + } } diff --git a/application/views/CisRouterView/CisRouterView.php b/application/views/CisRouterView/CisRouterView.php index c51ca40c3..529dc1f08 100644 --- a/application/views/CisRouterView/CisRouterView.php +++ b/application/views/CisRouterView/CisRouterView.php @@ -10,6 +10,7 @@ $includesArray = array( 'primevue3' => true, 'skipID' => '#fhccontent', 'vuedatepicker11' => true, + 'tinymce5' => true, 'customCSSs' => array( 'public/css/components/verticalsplit.css', 'public/css/components/searchbar/searchbar.css', @@ -21,9 +22,14 @@ $includesArray = array( 'public/css/components/FilterComponent.css', 'public/css/components/Profil.css', 'public/css/components/FormUnderline.css', + 'public/css/components/inViewTooltip.css', 'public/css/components/abgabetool/abgabe.css', 'public/css/Cis4/Cms.css', - 'public/css/Cis4/Studium.css' + 'public/css/Cis4/Studium.css', + 'public/css/components/vue-datepicker.css', + 'public/css/Cis4/Benotungstool.css', + 'public/css/components/Zeitsperrenma.css', //70747 + 'public/css/Cis4/Zeitsperren.css', //77941 ), 'customJSs' => array( 'vendor/npm-asset/primevue/accordion/accordion.min.js', @@ -37,11 +43,15 @@ $includesArray = array( 'vendor/npm-asset/primevue/timeline/timeline.min.js', 'vendor/npm-asset/primevue/inplace/inplace.min.js', 'vendor/npm-asset/primevue/message/message.min.js', + 'vendor/npm-asset/primevue/divider/divider.min.js', + 'vendor/npm-asset/primevue/password/password.js', + 'vendor/npm-asset/primevue/multiselect/multiselect.js', 'vendor/npm-asset/primevue/tieredmenu/tieredmenu.js', 'vendor/moment/luxonjs/luxon.min.js' ), 'customJSModules' => array( 'public/js/apps/Cis/Cis.js', + 'vendor/olifolkerd/tabulator5/src/js/modules/ColumnCalcs/ColumnCalcs.js' ), ); @@ -49,6 +59,8 @@ $includesArray = array( $this->load->view('templates/CISVUE-Header', $includesArray); ?>
> - +
load->view('templates/CISVUE-Footer', $includesArray); ?> diff --git a/application/views/Vertragsverwaltung.php b/application/views/Vertragsverwaltung.php index 8fa6dff27..390798cd8 100644 --- a/application/views/Vertragsverwaltung.php +++ b/application/views/Vertragsverwaltung.php @@ -12,13 +12,13 @@ $includesArray = array( 'tinymce5' => true, 'phrases' => array( 'global', - 'ui', + 'ui' ), 'customCSSs' => [ 'public/css/components/vue-datepicker.css', 'public/css/components/primevue.css', 'public/css/Vertragsverwaltung.css', - 'public/css/components/Detailheader.css' + 'public/css/components/Detailheader.css', ], 'customJSs' => [ #'vendor/npm-asset/primevue/tree/tree.min.js', diff --git a/application/views/lehre/lehrauftrag/Dashboard.php b/application/views/lehre/lehrauftrag/Dashboard.php index 3da01700c..60faa2649 100644 --- a/application/views/lehre/lehrauftrag/Dashboard.php +++ b/application/views/lehre/lehrauftrag/Dashboard.php @@ -1,7 +1,5 @@ load->view( - 'templates/FHC-Header', - array( +$includesArray = array( 'title' => 'Lehrauftrag bestellen', 'jquery3' => true, 'jqueryui1' => true, @@ -12,8 +10,15 @@ $this->load->view( 'dialoglib' => true, 'navigationwidget' => true, 'addons' => true, - ) ); + + +$this->load->view( + 'templates/FHC-Header', + $includesArray +); + + ?> widgetlib->widget('NavigationWidget'); ?> @@ -33,4 +38,12 @@ $this->load->view( -load->view('templates/FHC-Footer'); ?> +load->view( + 'templates/FHC-Footer', + $includesArray +); + + ?> diff --git a/application/views/lehre/lehrauftrag/LehrendeUebersicht.php b/application/views/lehre/lehrauftrag/LehrendeUebersicht.php index 4f49a2518..a91bac29d 100644 --- a/application/views/lehre/lehrauftrag/LehrendeUebersicht.php +++ b/application/views/lehre/lehrauftrag/LehrendeUebersicht.php @@ -1,7 +1,6 @@ load->view( - 'templates/FHC-Header', - array( + +$includesArray = array( 'title' => 'Lehrauftrag bestellen', 'jquery3' => true, 'bootstrap3' => true, @@ -9,8 +8,13 @@ $this->load->view( 'sbadmintemplate3' => true, 'ajaxlib' => true, 'navigationwidget' => true, - ) ); + +$this->load->view( + 'templates/FHC-Header', + $includesArray +); + ?> widgetlib->widget('NavigationWidget'); ?> @@ -34,4 +38,11 @@ $this->load->view( -load->view('templates/FHC-Footer'); ?> +load->view( + 'templates/FHC-Footer', + $includesArray +); + +?> diff --git a/application/views/lehre/lehrauftrag/acceptLehrauftrag.php b/application/views/lehre/lehrauftrag/acceptLehrauftrag.php index 0e8aa13cb..33d3fe2e0 100644 --- a/application/views/lehre/lehrauftrag/acceptLehrauftrag.php +++ b/application/views/lehre/lehrauftrag/acceptLehrauftrag.php @@ -1,89 +1,101 @@ load->view( - 'templates/FHC-Header', - array( - 'title' => 'Lehrauftrag annehmen', - 'jquery3' => true, - 'jqueryui1' => true, - 'jquerycheckboxes1' => true, - 'bootstrap5' => true, - 'fontawesome6' => true, - 'sbadmintemplate' => false, - 'tabulator5' => true, - 'tabulator5JQuery' => true, - 'cis'=>true, - 'momentjs2' => true, - 'ajaxlib' => true, - 'dialoglib' => true, - 'tablewidget' => true, - 'phrases' => array( - 'global' => array( - 'lehrauftraegeAnnehmen', - 'dokumentePDF', - 'PDFLehrauftraegeFH', - 'PDFLehrauftraegeLehrgaenge' - ), - 'ui' => array( - 'anzeigen', - 'alleAnzeigen', - 'nurBestellteAnzeigen', - 'nurErteilteAnzeigen', - 'nurAngenommeneAnzeigen', - 'nurStornierteAnzeigen', - 'hilfeZuDieserSeite', - 'alleAuswaehlen', - 'alleAbwaehlen', - 'ausgewaehlteZeilen', - 'hilfe', - 'tabelleneinstellungen', - 'keineDatenVorhanden', - 'spaltenEinstellen', - 'bestelltVon', - 'erteiltVon', - 'angenommenVon', - 'storniertVon', - 'lehrauftragInBearbeitung', - 'wartetAufErteilung', - 'wartetAufErneuteErteilung', - 'letzterStatusBestellt', - 'letzterStatusErteilt', - 'letzterStatusAngenommen', - 'vertragWurdeStorniert', - ), - 'password' => array('password'), - 'dms' => array('informationsblattExterneLehrende'), - 'table' => array( - 'spaltenEinAusblenden', - 'spaltenEinAusblendenMitKlickOeffnen', - 'spaltenEinAusblendenAufEinstellungenKlicken', - 'spaltenEinAusblendenMitKlickAktivieren', - 'spaltenEinAusblendenMitKlickSchliessen', - 'spaltenbreiteVeraendern', - 'spaltenbreiteVeraendernText', - 'spaltenbreiteVeraendernInfotext', - 'zeilenAuswaehlen', - 'zeilenAuswaehlenEinzeln', - 'zeilenAuswaehlenBereich', - 'zeilenAuswaehlenAlle' - ), - 'lehre' => array( - 'lehrauftraegeAnnehmen', - 'lehrauftraegeAnnehmenText', - 'lehrauftraegeAnnehmenKlickStatusicon', - 'lehrauftraegeAnnehmenLehrauftraegeWaehlen', - 'lehrauftraegeAnnehmenMitKlickAnnehmen', - 'lehrauftraegeNichtAuswaehlbar', - 'lehrauftraegeNichtAuswaehlbarTextBeiAnnahme', - 'filterAlleBeiAnnahme', - 'filterErteiltBeiAnnahme', - 'filterAngenommen' - ) + +$includesArray = array( + 'title' => 'Lehrauftrag annehmen', + 'jquery3' => true, + 'jqueryui1' => true, + 'jquerycheckboxes1' => true, + 'bootstrap5' => true, + 'fontawesome6' => true, + 'sbadmintemplate' => false, + 'tabulator5' => true, + 'tabulator5JQuery' => true, + 'cis'=>true, + 'momentjs2' => true, + 'ajaxlib' => true, + 'dialoglib' => true, + 'tablewidget' => true, + 'phrases' => array( + 'global' => array( + 'lehrauftraegeAnnehmen', + 'dokumentePDF', + 'PDFLehrauftraegeFH', + 'PDFLehrauftraegeLehrgaenge' ), - 'customJSs' => array( - 'public/js/bootstrapper.js', - 'public/js/lehre/lehrauftrag/acceptLehrauftrag.js') + 'ui' => array( + 'anzeigen', + 'alleAnzeigen', + 'nurBestellteAnzeigen', + 'nurErteilteAnzeigen', + 'nurAngenommeneAnzeigen', + 'nurStornierteAnzeigen', + 'hilfeZuDieserSeite', + 'alleAuswaehlen', + 'alleAbwaehlen', + 'ausgewaehlteZeilen', + 'hilfe', + 'tabelleneinstellungen', + 'keineDatenVorhanden', + 'spaltenEinstellen', + 'bestelltVon', + 'erteiltVon', + 'angenommenVon', + 'storniertVon', + 'lehrauftragInBearbeitung', + 'wartetAufErteilung', + 'wartetAufErneuteErteilung', + 'letzterStatusBestellt', + 'letzterStatusErteilt', + 'letzterStatusAngenommen', + 'vertragWurdeStorniert', + ), + 'password' => array('password'), + 'dms' => array('informationsblattExterneLehrende'), + 'table' => array( + 'spaltenEinAusblenden', + 'spaltenEinAusblendenMitKlickOeffnen', + 'spaltenEinAusblendenAufEinstellungenKlicken', + 'spaltenEinAusblendenMitKlickAktivieren', + 'spaltenEinAusblendenMitKlickSchliessen', + 'spaltenbreiteVeraendern', + 'spaltenbreiteVeraendernText', + 'spaltenbreiteVeraendernInfotext', + 'zeilenAuswaehlen', + 'zeilenAuswaehlenEinzeln', + 'zeilenAuswaehlenBereich', + 'zeilenAuswaehlenAlle' + ), + 'lehre' => array( + 'lehrauftraegeAnnehmen', + 'lehrauftraegeAnnehmenText', + 'lehrauftraegeAnnehmenKlickStatusicon', + 'lehrauftraegeAnnehmenLehrauftraegeWaehlen', + 'lehrauftraegeAnnehmenMitKlickAnnehmen', + 'lehrauftraegeNichtAuswaehlbar', + 'lehrauftraegeNichtAuswaehlbarTextBeiAnnahme', + 'filterAlleBeiAnnahme', + 'filterErteiltBeiAnnahme', + 'filterAngenommen' + ) + ), + 'customJSs' => array( + 'public/js/bootstrapper.js', + 'public/js/lehre/lehrauftrag/acceptLehrauftrag.js' ) ); + +if (defined("CIS4")) { + $this->load->view( + 'templates/CISVUE-Header', + $includesArray + ); +} else { + $this->load->view( + 'templates/FHC-Header', + $includesArray + ); +} + ?> @@ -230,5 +242,17 @@ $this->load->view(
-load->view('templates/FHC-Footer'); ?> +load->view( + 'templates/CISVUE-Footer', + $includesArray + ); +} else { + $this->load->view( + 'templates/FHC-Footer', + $includesArray + ); +} +?> diff --git a/application/views/lehre/lehrauftrag/approveLehrauftrag.php b/application/views/lehre/lehrauftrag/approveLehrauftrag.php index a598be015..822cd396e 100644 --- a/application/views/lehre/lehrauftrag/approveLehrauftrag.php +++ b/application/views/lehre/lehrauftrag/approveLehrauftrag.php @@ -1,98 +1,101 @@ 'Lehrauftrag erteilen', + 'jquery3' => true, + 'jqueryui1' => true, + 'jquerycheckboxes1' => true, + 'bootstrap3' => true, + 'fontawesome6' => true, + 'sbadmintemplate3' => true, + 'tabulator5' => true, + 'tabulator5JQuery' => true, + 'momentjs2' => true, + 'ajaxlib' => true, + 'dialoglib' => true, + 'tablewidget' => true, + 'navigationwidget' => true, + 'phrases' => array( + 'global' => array( + 'lehrauftraegeErteilen', + 'mehrHilfe', + 'weitereInformationenUnter' + ), + 'ui' => array( + 'anzeigen', + 'alleAnzeigen', + 'nurNeueAnzeigen', + 'nurBestellteAnzeigen', + 'nurErteilteAnzeigen', + 'nurAngenommeneAnzeigen', + 'nurGeaenderteAnzeigen', + 'nurDummiesAnzeigen', + 'hilfeZuDieserSeite', + 'alleAuswaehlen', + 'alleAbwaehlen', + 'ausgewaehlteZeilen', + 'hilfe', + 'tabelleneinstellungen', + 'keineDatenVorhanden', + 'spaltenEinstellen', + 'bestelltVon', + 'erteiltVon', + 'angenommenVon', + 'stundenStundensatzGeaendert', + 'neuerLehrauftragOhneLektorVerplant', + 'wartetAufBestellung', + 'wartetAufErneuteBestellung', + 'neuerLehrauftragWartetAufBestellung', + 'letzterStatusBestellt', + 'letzterStatusErteilt', + 'letzterStatusAngenommen', + ), + 'table' => array( + 'spaltenEinAusblenden', + 'spaltenEinAusblendenMitKlickOeffnen', + 'spaltenEinAusblendenAufEinstellungenKlicken', + 'spaltenEinAusblendenMitKlickAktivieren', + 'spaltenEinAusblendenMitKlickSchliessen', + 'spaltenbreiteVeraendern', + 'spaltenbreiteVeraendernText', + 'spaltenbreiteVeraendernInfotext', + 'zeilenAuswaehlen', + 'zeilenAuswaehlenEinzeln', + 'zeilenAuswaehlenBereich', + 'zeilenAuswaehlenAlle' + ), + 'lehre' => array( + 'lehrauftragStandardBestellprozess', + 'lehrauftragStandardBestellprozessBestellen', + 'lehrauftragStandardBestellprozessErteilen', + 'lehrauftragStandardBestellprozessAnnehmen', + 'lehrauftraegeErteilen', + 'lehrauftraegeErteilenText', + 'lehrauftraegeErteilenKlickStatusicon', + 'lehrauftraegeErteilenLehrauftraegeWaehlen', + 'lehrauftraegeErteilenMitKlickErteilen', + 'geaenderteLehrauftraege', + 'geaenderteLehrauftraegeTextBeiErteilung', + 'lehrauftraegeNichtAuswaehlbar', + 'lehrauftraegeNichtAuswaehlbarTextBeiErteilung', + 'filterAlle', + 'filterNeu', + 'filterBestellt', + 'filterErteilt', + 'filterAngenommen', + 'filterGeaendert', + 'filterDummies' + ) + ), + 'customJSs' => array( + 'public/js/bootstrapper.js', + 'public/js/lehre/lehrauftrag/approveLehrauftrag.js' + ) +); + + $this->load->view( - 'templates/FHC-Header', - array( - 'title' => 'Lehrauftrag erteilen', - 'jquery3' => true, - 'jqueryui1' => true, - 'jquerycheckboxes1' => true, - 'bootstrap3' => true, - 'fontawesome6' => true, - 'sbadmintemplate3' => true, - 'tabulator5' => true, - 'tabulator5JQuery' => true, - 'momentjs2' => true, - 'ajaxlib' => true, - 'dialoglib' => true, - 'tablewidget' => true, - 'navigationwidget' => true, - 'phrases' => array( - 'global' => array( - 'lehrauftraegeErteilen', - 'mehrHilfe', - 'weitereInformationenUnter' - ), - 'ui' => array( - 'anzeigen', - 'alleAnzeigen', - 'nurNeueAnzeigen', - 'nurBestellteAnzeigen', - 'nurErteilteAnzeigen', - 'nurAngenommeneAnzeigen', - 'nurGeaenderteAnzeigen', - 'nurDummiesAnzeigen', - 'hilfeZuDieserSeite', - 'alleAuswaehlen', - 'alleAbwaehlen', - 'ausgewaehlteZeilen', - 'hilfe', - 'tabelleneinstellungen', - 'keineDatenVorhanden', - 'spaltenEinstellen', - 'bestelltVon', - 'erteiltVon', - 'angenommenVon', - 'stundenStundensatzGeaendert', - 'neuerLehrauftragOhneLektorVerplant', - 'wartetAufBestellung', - 'wartetAufErneuteBestellung', - 'neuerLehrauftragWartetAufBestellung', - 'letzterStatusBestellt', - 'letzterStatusErteilt', - 'letzterStatusAngenommen', - ), - 'table' => array( - 'spaltenEinAusblenden', - 'spaltenEinAusblendenMitKlickOeffnen', - 'spaltenEinAusblendenAufEinstellungenKlicken', - 'spaltenEinAusblendenMitKlickAktivieren', - 'spaltenEinAusblendenMitKlickSchliessen', - 'spaltenbreiteVeraendern', - 'spaltenbreiteVeraendernText', - 'spaltenbreiteVeraendernInfotext', - 'zeilenAuswaehlen', - 'zeilenAuswaehlenEinzeln', - 'zeilenAuswaehlenBereich', - 'zeilenAuswaehlenAlle' - ), - 'lehre' => array( - 'lehrauftragStandardBestellprozess', - 'lehrauftragStandardBestellprozessBestellen', - 'lehrauftragStandardBestellprozessErteilen', - 'lehrauftragStandardBestellprozessAnnehmen', - 'lehrauftraegeErteilen', - 'lehrauftraegeErteilenText', - 'lehrauftraegeErteilenKlickStatusicon', - 'lehrauftraegeErteilenLehrauftraegeWaehlen', - 'lehrauftraegeErteilenMitKlickErteilen', - 'geaenderteLehrauftraege', - 'geaenderteLehrauftraegeTextBeiErteilung', - 'lehrauftraegeNichtAuswaehlbar', - 'lehrauftraegeNichtAuswaehlbarTextBeiErteilung', - 'filterAlle', - 'filterNeu', - 'filterBestellt', - 'filterErteilt', - 'filterAngenommen', - 'filterGeaendert', - 'filterDummies' - ) - ), - 'customJSs' => array( - 'public/js/bootstrapper.js', - 'public/js/lehre/lehrauftrag/approveLehrauftrag.js' - ) - ) + 'templates/FHC-Header', + $includesArray ); ?> @@ -208,5 +211,13 @@ $this->load->view(
-load->view('templates/FHC-Footer'); ?> +load->view( + 'templates/FHC-Footer', + $includesArray +); + + ?> diff --git a/application/views/lehre/lehrauftrag/orderLehrauftrag.php b/application/views/lehre/lehrauftrag/orderLehrauftrag.php index 6806aa401..107db2871 100644 --- a/application/views/lehre/lehrauftrag/orderLehrauftrag.php +++ b/application/views/lehre/lehrauftrag/orderLehrauftrag.php @@ -1,98 +1,101 @@ 'Lehrauftrag bestellen', + 'jquery3' => true, + 'jqueryui1' => true, + 'jquerycheckboxes1' => true, + 'bootstrap3' => true, + 'fontawesome6' => true, + 'sbadmintemplate3' => true, + 'tabulator5' => true, + 'tabulator5JQuery' => true, + 'momentjs2' => true, + 'ajaxlib' => true, + 'dialoglib' => true, + 'tablewidget' => true, + 'navigationwidget' => true, + 'phrases' => array( + 'global' => array( + 'lehrauftraegeBestellen', + 'mehrHilfe', + 'weitereInformationenUnter' + ), + 'ui' => array( + 'anzeigen', + 'alleAnzeigen', + 'nurNeueAnzeigen', + 'nurBestellteAnzeigen', + 'nurErteilteAnzeigen', + 'nurAngenommeneAnzeigen', + 'nurGeaenderteAnzeigen', + 'nurDummiesAnzeigen', + 'hilfeZuDieserSeite', + 'alleAuswaehlen', + 'alleAbwaehlen', + 'ausgewaehlteZeilen', + 'hilfe', + 'tabelleneinstellungen', + 'keineDatenVorhanden', + 'spaltenEinstellen', + 'bestelltVon', + 'erteiltVon', + 'angenommenVon', + 'neuerLehrauftragOhneLektorVerplant', + 'neuerLehrauftragWartetAufBestellung', + 'letzterStatusBestellt', + 'letzterStatusErteilt', + 'letzterStatusAngenommen', + 'nachAenderungStundensatzStunden', + 'vorAenderungStundensatzStunden' + ), + 'table' => array( + 'spaltenEinAusblenden', + 'spaltenEinAusblendenMitKlickOeffnen', + 'spaltenEinAusblendenAufEinstellungenKlicken', + 'spaltenEinAusblendenMitKlickAktivieren', + 'spaltenEinAusblendenMitKlickSchliessen', + 'spaltenbreiteVeraendern', + 'spaltenbreiteVeraendernText', + 'spaltenbreiteVeraendernInfotext', + 'zeilenAuswaehlen', + 'zeilenAuswaehlenEinzeln', + 'zeilenAuswaehlenBereich', + 'zeilenAuswaehlenAlle' + ), + 'lehre' => array( + 'lehrauftragStandardBestellprozess', + 'lehrauftragStandardBestellprozessBestellen', + 'lehrauftragStandardBestellprozessErteilen', + 'lehrauftragStandardBestellprozessAnnehmen', + 'lehrauftraegeBestellen', + 'lehrauftraegeBestellenText', + 'lehrauftraegeBestellenKlickStatusicon', + 'lehrauftraegeBestellenLehrauftraegeWaehlen', + 'lehrauftraegeBestellenMitKlickBestellen', + 'lehrauftraegeBestellenVertragWirdAngelegt', + 'geaenderteLehrauftraege', + 'geaenderteLehrauftraegeText', + 'lehrauftraegeNichtAuswaehlbar', + 'lehrauftraegeNichtAuswaehlbarText', + 'filterAlle', + 'filterNeu', + 'filterBestellt', + 'filterErteilt', + 'filterAngenommen', + 'filterGeaendert', + 'filterDummies' + ) + ), + 'customJSs' => array( + 'public/js/bootstrapper.js', + 'public/js/lehre/lehrauftrag/orderLehrauftrag.js' + ) +); + + $this->load->view( - 'templates/FHC-Header', - array( - 'title' => 'Lehrauftrag bestellen', - 'jquery3' => true, - 'jqueryui1' => true, - 'jquerycheckboxes1' => true, - 'bootstrap3' => true, - 'fontawesome6' => true, - 'sbadmintemplate3' => true, - 'tabulator5' => true, - 'tabulator5JQuery' => true, - 'momentjs2' => true, - 'ajaxlib' => true, - 'dialoglib' => true, - 'tablewidget' => true, - 'navigationwidget' => true, - 'phrases' => array( - 'global' => array( - 'lehrauftraegeBestellen', - 'mehrHilfe', - 'weitereInformationenUnter' - ), - 'ui' => array( - 'anzeigen', - 'alleAnzeigen', - 'nurNeueAnzeigen', - 'nurBestellteAnzeigen', - 'nurErteilteAnzeigen', - 'nurAngenommeneAnzeigen', - 'nurGeaenderteAnzeigen', - 'nurDummiesAnzeigen', - 'hilfeZuDieserSeite', - 'alleAuswaehlen', - 'alleAbwaehlen', - 'ausgewaehlteZeilen', - 'hilfe', - 'tabelleneinstellungen', - 'keineDatenVorhanden', - 'spaltenEinstellen', - 'bestelltVon', - 'erteiltVon', - 'angenommenVon', - 'neuerLehrauftragOhneLektorVerplant', - 'neuerLehrauftragWartetAufBestellung', - 'letzterStatusBestellt', - 'letzterStatusErteilt', - 'letzterStatusAngenommen', - 'nachAenderungStundensatzStunden', - 'vorAenderungStundensatzStunden' - ), - 'table' => array( - 'spaltenEinAusblenden', - 'spaltenEinAusblendenMitKlickOeffnen', - 'spaltenEinAusblendenAufEinstellungenKlicken', - 'spaltenEinAusblendenMitKlickAktivieren', - 'spaltenEinAusblendenMitKlickSchliessen', - 'spaltenbreiteVeraendern', - 'spaltenbreiteVeraendernText', - 'spaltenbreiteVeraendernInfotext', - 'zeilenAuswaehlen', - 'zeilenAuswaehlenEinzeln', - 'zeilenAuswaehlenBereich', - 'zeilenAuswaehlenAlle' - ), - 'lehre' => array( - 'lehrauftragStandardBestellprozess', - 'lehrauftragStandardBestellprozessBestellen', - 'lehrauftragStandardBestellprozessErteilen', - 'lehrauftragStandardBestellprozessAnnehmen', - 'lehrauftraegeBestellen', - 'lehrauftraegeBestellenText', - 'lehrauftraegeBestellenKlickStatusicon', - 'lehrauftraegeBestellenLehrauftraegeWaehlen', - 'lehrauftraegeBestellenMitKlickBestellen', - 'lehrauftraegeBestellenVertragWirdAngelegt', - 'geaenderteLehrauftraege', - 'geaenderteLehrauftraegeText', - 'lehrauftraegeNichtAuswaehlbar', - 'lehrauftraegeNichtAuswaehlbarText', - 'filterAlle', - 'filterNeu', - 'filterBestellt', - 'filterErteilt', - 'filterAngenommen', - 'filterGeaendert', - 'filterDummies' - ) - ), - 'customJSs' => array( - 'public/js/bootstrapper.js', - 'public/js/lehre/lehrauftrag/orderLehrauftrag.js' - ) - ) + 'templates/FHC-Header', + $includesArray ); ?> @@ -209,5 +212,11 @@ $this->load->view(
-load->view('templates/FHC-Footer'); ?> +load->view( + 'templates/FHC-Footer', + $includesArray +); + ?> diff --git a/application/views/lehre/pruefungsprotokoll.php b/application/views/lehre/pruefungsprotokoll.php index 3ac23da6a..cdc206377 100644 --- a/application/views/lehre/pruefungsprotokoll.php +++ b/application/views/lehre/pruefungsprotokoll.php @@ -9,22 +9,8 @@ $sitesettings = array( 'ajaxlib' => true, 'sbadmintemplate3' => true, 'phrases' => array( - 'abschlusspruefung' => array( - 'freigegebenAm', - 'pruefungGespeichert', - 'pruefungSpeichernFehler', - 'abschlussbeurteilungLeer', - 'beginnzeitLeer', - 'beginnzeitFormatError', - 'endezeitLeer', - 'endezeitFormatError', - 'endezeitBeforeError', - 'verfNotice' - ), - 'ui' => array( - 'stunde', - 'minute' - ) + 'abschlusspruefung', + 'ui' ), 'customCSSs' => array( 'public/css/sbadmin2/admintemplate_contentonly.css', @@ -37,10 +23,18 @@ $sitesettings = array( ) ); -$this->load->view( - 'templates/FHC-Header', - $sitesettings -); +if(defined('CIS4')){ + $this->load->view( + 'templates/CISVUE-Header', + $sitesettings + ); +}else{ + $this->load->view( + 'templates/FHC-Header', + $sitesettings + ); +} + ?>
@@ -161,6 +155,14 @@ $this->load->view( studiengangstyp == 'Bachelor' ? $this->p->t('abschlusspruefung', 'pruefungsgegenstandBachelor') : $this->p->t('abschlusspruefung', 'pruefungsgegenstandMaster')) ?> + + + p->t('abschlusspruefung', 'spracheDerArbeit') ?>  + + + abschlussarbeit_sprache ?? '' ?> + + p->t('global', 'notizen')); ?> @@ -201,7 +203,9 @@ $this->load->view(

freigabedatum); ?> - +

@@ -236,7 +240,14 @@ $this->load->view(
load->view( - 'templates/FHC-Footer', - $sitesettings -); +if (defined('CIS4')) { + $this->load->view( + 'templates/CISVUE-Footer', + $sitesettings + ); +} else { + $this->load->view( + 'templates/FHC-Footer', + $sitesettings + ); +} \ No newline at end of file diff --git a/application/views/lehre/pruefungsprotokollUebersicht.php b/application/views/lehre/pruefungsprotokollUebersicht.php index e75e3c914..e9e61e548 100644 --- a/application/views/lehre/pruefungsprotokollUebersicht.php +++ b/application/views/lehre/pruefungsprotokollUebersicht.php @@ -1,33 +1,45 @@ load->view( - 'templates/FHC-Header', - array( - 'title' => 'Prüfungsprotokoll', - 'jquery3' => true, - 'jqueryui1' => true, - 'jquerycheckboxes1' => true, - 'bootstrap3' => true, - 'fontawesome4' => true, - 'tablesorter2' => true, - 'tabulator5' => true, - 'tabulator5JQuery' => true, - 'cis'=>true, - 'ajaxlib' => true, - 'dialoglib' => true, - 'tablewidget' => true, - 'phrases' => array( - 'ui' => array( - 'keineDatenVorhanden', - 'heute', - 'letzteWoche', - 'alle', - 'zeitraum' - ) - ), - 'customCSSs' => array('public/css/sbadmin2/tablesort_bootstrap.css'), - 'customJSs' => array('public/js/bootstrapper.js') - ) + $includesArray = array( + 'title' => 'Prüfungsprotokoll', + 'jquery3' => true, + 'jqueryui1' => true, + 'jquerycheckboxes1' => true, + 'bootstrap3' => true, + 'fontawesome4' => true, + 'tablesorter2' => true, + 'tabulator5' => true, + 'tabulator5JQuery' => true, + 'cis'=>true, + 'ajaxlib' => true, + 'dialoglib' => true, + 'tablewidget' => true, + 'phrases' => array( + 'ui' => array( + 'keineDatenVorhanden', + 'heute', + 'letzteWoche', + 'alle', + 'zeitraum' + ) + ), + 'customCSSs' => array('public/css/sbadmin2/tablesort_bootstrap.css'), + 'customJSs' => array('public/js/bootstrapper.js') ); + + if(defined("CIS4") && CIS4) + { + $this->load->view( + 'templates/CISVUE-Header', + $includesArray + ); + } + else + { + $this->load->view( + 'templates/FHC-Header', + $includesArray + ); + } ?>
@@ -66,4 +78,15 @@
-load->view('templates/FHC-Footer'); ?> +load->view( + 'templates/CISVUE-Footer', + $includesArray + ); + } else { + $this->load->view( + 'templates/FHC-Footer', + $includesArray + ); + } diff --git a/application/views/templates/CISVUE-Footer.php b/application/views/templates/CISVUE-Footer.php index eae2a94ff..0523a21d3 100644 --- a/application/views/templates/CISVUE-Footer.php +++ b/application/views/templates/CISVUE-Footer.php @@ -5,6 +5,11 @@ $includesArray = array( 'bootstrap5' => true, 'fontawesome6' => true, 'axios027' => true, + 'primevue3' => true, + 'vuedatepicker11' => true, + 'customJSs' => array_merge([ + 'vendor/moment/luxonjs/luxon.min.js' + ], $customJSs ?? []), 'customJSModules' => array_merge([ 'public/js/apps/Cis/Menu.js' ], $customJSModules ?? []), diff --git a/application/views/templates/CISVUE-Header.php b/application/views/templates/CISVUE-Header.php index f28012dd5..1d5b79dce 100644 --- a/application/views/templates/CISVUE-Header.php +++ b/application/views/templates/CISVUE-Header.php @@ -7,6 +7,11 @@ $includesArray = array( 'fontawesome6' => true, 'axios027' => true, 'primevue3' => true, + 'vuedatepicker11' => true, + 'printCssPath' => 'public/css/Cis4/print.css', + 'customJSs' => array_merge([ + 'vendor/moment/luxonjs/luxon.min.js' + ], $customJSs ?? []), 'customJSModules' => array_merge([ 'public/js/apps/Cis/Menu.js' ], $customJSModules ?? []), @@ -26,15 +31,19 @@ $this->load->view('templates/FHC-Header', $includesArray); document.body.classList.add("in-frame"); -