diff --git a/application/controllers/Documents.php b/application/controllers/Documents.php new file mode 100644 index 000000000..47aae7ed1 --- /dev/null +++ b/application/controllers/Documents.php @@ -0,0 +1,294 @@ +. + */ + +if (! defined('BASEPATH')) exit('No direct script access allowed'); + +/** + * This controller handles output and access to documents. + * It creates a XML file, transforms it with the XSL-FO Vorlage from the + * database and generates a PDF file with unoconv or docsbox. + * This file is then outputted as download. + * + * It is the CodeIgniter version of content/pdfExport.php when not using the + * get paremeters: "archivdokument" and "archive". + * Use exportSigned() instead of providing the "sign" get parameter and + * export() otherwise. + */ +class Documents extends Auth_Controller +{ + public function __construct() + { + parent::__construct([ + 'export' => self::PERM_LOGGED, + 'exportSigned' => self::PERM_LOGGED + ]); + + // Load Phrases + $this->loadPhrases([ + 'stv' + ]); + } + + /** + * Download a not signed document. + * + * @param string $xml + * @param string $xsl + * + * @return void + */ + public function export($xml, $xsl) + { + return $this->_export($xml, $xsl); + } + + /** + * Download a signed document. + * + * @param string $xml + * @param string $xsl + * + * @return void + */ + public function exportSigned($xml, $xsl) + { + return $this->_export($xml, $xsl, getAuthUID()); + } + + /** + * Helper function for export() and exportSigned() + * + * @param string $xml + * @param string $xsl + * @param string $sign_user (optional) + * + * @return void + */ + protected function _export($xml, $xsl, $sign_user = null) + { + $xsl_oe_kurzbz = null; + $version = $this->input->post_get('version') ?: null; + + // Get the OE or STG of the document + $xsl_oe_kurzbz = $this->input->post_get('xsl_oe_kurzbz') + ?: $this->input->post_get('xsl_stg_kz') + ?: $this->input->post_get('stg_kz'); + if (is_null($xsl_oe_kurzbz)) { + $uid = $this->input->post_get('uid'); + if ($uid) { + $uid = current(explode(';', $uid)); + $this->load->model('crm/Student_model', 'StudentModel'); + $result = $this->StudentModel->load([$uid]); + if (!isError($result) && hasData($result)) + $xsl_oe_kurzbz = current(getData($result))->studiengang_kz; + } + } + if (is_null($xsl_oe_kurzbz)) { + $prestudent_id = $this->input->post_get('prestudent_id'); + if ($prestudent_id) { + $prestudent_id = current(explode(';', $prestudent_id)); + $this->load->model('crm/Prestudent_model', 'PrestudentModel'); + $result = $this->PrestudentModel->load($prestudent_id); + if (!isError($result) && hasData($result)) + $xsl_oe_kurzbz = current(getData($result))->studiengang_kz; + } + } + if (is_null($xsl_oe_kurzbz)) + $xsl_oe_kurzbz = 0; + + // Access rights + if ($xsl == 'AccountInfo') { + $this->load->model('resource/Mitarbeiter_model', 'MitarbeiterModel'); + $this->load->model('crm/Student_model', 'StudentModel'); + $uids = $this->input->post_get('uid'); + if ($uids) { + $uids = explode(';', $uids); + foreach ($uids as $uid) { + $result = $this->MitarbeiterModel->load($uid); + if (!isError($result) && hasData($result)) { + if (!$this->permissionlib->isBerechtigt('admin', 'suid', 0) + && !$this->permissionlib->isBerechtigt('mitarbeiter', 'suid', 0)) + return $this->_outputAuthError([$this->router->method => ['admin:rw', 'mitarbeiter:rw']]); + } else { + $result = $this->StudentModel->load([$uid]); + if (!isError($result) && hasData($result)) { + $student = current(getData($result)); + if (!$this->permissionlib->isBerechtigt('admin', 'suid', $student->studiengang_kz) + && !$this->permissionlib->isBerechtigt('admin', 'suid', 0) + && !$this->permissionlib->isBerechtigt('assistenz', 'suid', $student->studiengang_kz) + && !$this->permissionlib->isBerechtigt('assistenz', 'suid', 0) + && !$this->permissionlib->isBerechtigt('support', 'suid', 0)) + return $this->_outputAuthError([$this->router->method => ['admin:rw', 'assistenz:rw', 'support:rw']]); + } + } + } + } + } else { + $this->load->model('system/Vorlagestudiengang_model', 'VorlagestudiengangModel'); + + $result = $this->VorlagestudiengangModel->getCurrent($xsl, $xsl_oe_kurzbz, $version); + if (isError($result)) + return show_error(getError($result)); + if (!hasData($result)) + return show_404(); + + $access_rights = current(getData($result))->berechtigung; + if (!$access_rights) + return show_404(); + $allowed = false; + foreach ($access_rights as $access_right) { + if ($this->permissionlib->isBerechtigt($access_right)) { + $allowed = true; + break; + } + } + if (!$allowed) + return $this->_outputAuthError([$this->router->method => $access_rights]); + } + + // Output format + $outputformat = $this->input->post_get('output') ?: 'pdf'; + if ($outputformat != 'pdf' + // An der FHTW darf das Studienblatt und das Prüfungsprotokoll auch in anderen Formaten exportiert werden + && !(CAMPUS_NAME == 'FH Technikum Wien' + && ($xsl == 'Studienblatt' + || $xsl == 'StudienblattEng' + || $xsl == 'PrProtBA' + || $xsl == 'PrProtBAEng' + || $xsl == 'PrProtMA' + || $xsl == 'PrProtMAEng' + ) + ) + && !$this->permissionlib->isBerechtigt('system/change_outputformat', null, $xsl_oe_kurzbz) + ) { + $outputformat = 'pdf'; + } + + // XML Params + $params = 'xmlformat=xml'; + foreach ([ + 'uid', + 'stg_kz', + 'person_id', + 'id', + 'prestudent_id', + 'buchungsnummern', + 'ss', + 'abschlusspruefung_id', + 'typ', + 'all', + 'preoutgoing_id', + 'lvid', + 'projekt_kurzbz', + 'von', + 'bis', + 'stundevon', + 'stundebis', + 'sem', + 'lehreinheit', + 'mitarbeiter_uid', + 'studienordnung_id', + 'fixangestellt', + 'standort', + 'abrechnungsmonat', + 'form', + 'projektarbeit_id', + 'betreuerart_kurzbz', + 'studiensemester_kurzbz' + ] as $key) { + $value = $this->input->post_get($key); + if ($value !== null) + $params .= '&' . $key . '=' . urlencode($value); + } + $value = $this->input->post_get('vertrag_id'); + if ($value !== null) { + foreach ($value as $id) + $params .= '&vertrag_id[]=' . urlencode($id); + } + + $this->load->library('DocumentExportLib'); + $this->load->model('system/Vorlage_model', 'VorlageModel'); + + $result = $this->VorlageModel->load($xsl); + if (isError($result)) + return show_error(getError($result)); + if (!hasData($result)) + show_404(); + + $vorlage = current(getData($result)); + if ($sign_user && !$vorlage->signierbar) + return show_error($this->p->t("stv", "grades_error_sign")); + + + // Filename + $filename = ($vorlage->bezeichnung ?: $vorlage->vorlage_kurzbz); + switch ($xsl) { + case 'LV_Informationen': + $this->load->model('organisation/Studiengang_model', 'StudiengangModel'); + $result = $this->StudiengangModel->load($this->input->post_get('stg_kz')); + if (!isError($result) && hasData($result)) + $filename .= '_' . sanitizeProblemChars(current(getData($result))->kurzbzlang); + + $this->load->model('organisation/Studiensemester_model', 'StudiensemesterModel'); + $result = $this->StudiensemesterModel->load($this->input->post_get('ss')); + if (!isError($result) && hasData($result)) + $filename .= '_' . sanitizeProblemChars(current(getData($result))->studiensemester_kurzbz); + break; + case 'Honorarvertrag': + $uid = str_replace(';', '', $this->input->post_get('uid') ?: ''); + $this->load->model('person/Benutzer_model', 'BenutzerModel'); + $this->BenutzerModel->addJoin('public.tbl_person', 'person_id', 'LEFT'); + $result = $this->BenutzerModel->load([$uid]); + if (!isError($result) && hasData($result)) { + $user = current(getData($result)); + $filename .= '_' . sanitizeProblemChars($user->nachname) . '_' . sanitizeProblemChars($user->vorname); + } + break; + case 'Studienordnung': + $filename = 'Studienordnung-Studienplan-'; + + $this->load->model('organisation/Studienordnung_model', 'StudienordnungModel'); + $result = $this->StudienordnungModel->load($this->input->post_get('studienordnung_id')); + if (!isError($result) && hasData($result)) { + $so = current(getData($result)); + $filename .= sprintf("%'.04d", $so->studiengang_kz) . '-' . $so->studiengangkurzbzlang; + } + break; + default: + $uid = str_replace(';', '', $this->input->post_get('uid') ?: ''); + $this->load->model('person/Benutzer_model', 'BenutzerModel'); + $this->BenutzerModel->addJoin('public.tbl_person', 'person_id', 'LEFT'); + $result = $this->BenutzerModel->load([$uid]); + if (!isError($result) && hasData($result)) { + $user = current(getData($result)); + $filename .= '_' . sanitizeProblemChars($user->nachname); + } + break; + } + + // XML Data + $result = $this->documentexportlib->getDataURL($xml, $params); + if (isError($result)) + return show_error(getError($result)); + + $data = getData($result); + + // Output + $this->documentexportlib->showContent($filename, $vorlage, $data, $xsl_oe_kurzbz, $version, $outputformat, $sign_user); + } +} diff --git a/application/controllers/api/frontend/v1/Documents.php b/application/controllers/api/frontend/v1/Documents.php new file mode 100644 index 000000000..60010e14d --- /dev/null +++ b/application/controllers/api/frontend/v1/Documents.php @@ -0,0 +1,422 @@ +. + */ + +if (! defined('BASEPATH')) exit('No direct script access allowed'); + +/** + * This controller operates between (interface) the JS (GUI) and the back-end + * Provides data to the ajax get calls about documents + * Listens to ajax post calls to change the documents data + * This controller works with JSON calls on the HTTP GET or POST and the output is always JSON + * + * This controller handles output and access to documents. + * It checks permissions to render documents in an alternative format + * or it creates a XML file, transforms it with the XSL-FO Vorlage from the + * database and generates a PDF file with unoconv or docsbox. + * This file is then archivated in the database. + * + * The last part is the CodeIgniter version of content/pdfExport.php when not + * using the get paremeter: "archivdokument" but using the get parameter: + * "archive". + * Use archiveSigned() instead of providing the "sign" get parameter and + * archive() otherwise. + */ +class Documents extends FHCAPI_Controller +{ + public function __construct() + { + parent::__construct([ + 'permissionAlternativeFormat' => self::PERM_LOGGED, + 'archive' => ['admin:rw', 'assistenz:rw'], + 'archiveSigned' => ['admin:rw', 'assistenz:rw'] + ]); + + // Load Phrases + $this->loadPhrases([ + 'stv' + ]); + } + + /** + * Checks if the current user has permission to render documents in an + * alternative format. + * + * @param string $oe_kurzbz Or studiengang_kz + * + * @return void + */ + public function permissionAlternativeFormat($oe_kurzbz) + { + $this->terminateWithSuccess($this->permissionlib->isBerechtigt('system/change_outputformat', null, $oe_kurzbz)); + } + + /** + * Download a not signed document. + * + * @param string $xml (optional) + * @param string $xsl (optional) + * + * @return void + */ + public function archive($xml = null, $xsl = null) + { + return $this->_archive($xml, $xsl); + } + + /** + * Download a signed document. + * + * @param string $xml (optional) + * @param string $xsl (optional) + * + * @return void + */ + public function archiveSigned($xml = null, $xsl = null) + { + return $this->_archive($xml, $xsl, getAuthUID()); + } + + /** + * Helper function for archive() and archiveSigned() + * + * @param string $xml + * @param string $xsl + * @param string $sign_user (optional) + * + * @return void + */ + public function _archive($xml, $xsl, $sign_user = null) + { + if (!$xml || !$xsl) { + $this->load->library('form_validation'); + if (!$xml) { + $xml = $this->input->post_get('xml'); + $this->form_validation->set_rules('xml', 'xml', 'required'); + } + if (!$xsl) { + $xsl = $this->input->post_get('xsl'); + $this->form_validation->set_rules('xsl', 'xsl', 'required'); + } + + if (!$this->form_validation->run()) + $this->terminateWithValidationErrors($this->form_validation->error_array()); + } + + $xsl_oe_kurzbz = null; + $version = $this->input->post_get('version') ?: null; + + // Get the OE or STG of the document + $xsl_oe_kurzbz = $this->input->post_get('xsl_oe_kurzbz') + ?: $this->input->post_get('xsl_stg_kz') + ?: $this->input->post_get('stg_kz'); + if (is_null($xsl_oe_kurzbz)) { + $uid = $this->input->post_get('uid'); + if ($uid) { + $uid = current(explode(';', $uid)); + $this->load->model('crm/Student_model', 'StudentModel'); + $result = $this->StudentModel->load([$uid]); + if (!isError($result) && hasData($result)) + $xsl_oe_kurzbz = current(getData($result))->studiengang_kz; + } + } + if (is_null($xsl_oe_kurzbz)) { + $prestudent_id = $this->input->post_get('prestudent_id'); + if ($prestudent_id) { + $prestudent_id = current(explode(';', $prestudent_id)); + $this->load->model('crm/Prestudent_model', 'PrestudentModel'); + $result = $this->PrestudentModel->load($prestudent_id); + if (!isError($result) && hasData($result)) + $xsl_oe_kurzbz = current(getData($result))->studiengang_kz; + } + } + if (is_null($xsl_oe_kurzbz)) + $xsl_oe_kurzbz = 0; + + // Vorlage + $this->load->model('system/Vorlage_model', 'VorlageModel'); + + $result = $this->VorlageModel->load($xsl); + $vorlage = current($this->getDataOrTerminateWithError($result)); + if (!$vorlage) + show_404(); + + // Akte Data + $akteData = [ + 'dokument_kurzbz' => $vorlage->dokument_kurzbz ?: 'Zeugnis', + 'mimetype' => 'application/pdf', + 'erstelltam' => date('Y-m-d'), + 'gedruckt' => true, + 'insertamum' => date('c'), + 'insertvon' => getAuthUID(), + 'uid' => $this->input->post_get('uid') ?: '', + 'archiv' => true, + 'signiert' => !!$sign_user, + 'stud_selfservice' => $vorlage->stud_selfservice + ]; + $studiengang_kz = null; + if ($akteData['uid']) { + $this->load->model('crm/Student_model', 'StudentModel'); + $this->StudentModel->addJoin('public.tbl_studiengang', 'studiengang_kz', 'LEFT'); + $result = $this->StudentModel->load([$akteData['uid']]); + $student = current($this->getDataOrTerminateWithError($result)); + + $ss = $this->input->post_get('ss'); + + if ($ss !== null) { + $this->load->model('crm/prestudentstatus_model', 'PrestudentstatusModel'); + $result = $this->PrestudentstatusModel->getLastStatus($student->prestudent_id, $ss); + $status = current($this->getDataOrTerminateWithError($result)); + if (!$status) + $this->terminateWithError($this->p->t("stv", "grades_error_prestudentstatus")); + $semester = $status->ausbildungssemester; + + $this->load->model('education/Studentlehrverband_model', 'StudentlehrverbandModel'); + $this->StudentlehrverbandModel->addJoin('public.tbl_benutzer', 'uid = student_uid'); + $this->StudentlehrverbandModel->addJoin('public.tbl_studiengang', 'studiengang_kz'); + $result = $this->StudentlehrverbandModel->load([ + 'studiensemester_kurzbz' => $ss, + 'student_uid' => $akteData['uid'] + ]); + $res = current($this->getDataOrTerminateWithError($result)); + + $studiengang_kz = $res->studiengang_kz; + $akteData['person_id'] = $res->person_id; + switch ($xsl) { + case 'Ausbildungsver': + case 'AusbVerEng': + $akteData['titel'] = mb_substr($xsl . + "_" . + strtoupper($res->typ) . + strtoupper($res->kurzbz) . + "_" . + $semester . + "_" . + $ss, 0, 64); + $akteData['bezeichnung'] = mb_substr($vorlage->bezeichnung . " " . $student->kuerzel, 0, 64); + break; + case 'LVZeugnisEng': + case 'LVZeugnis': + case 'Zertifikat': + $this->load->model('education/Lehrveranstaltung_model', 'LehrveranstaltungModel'); + $result = $this->LehrveranstaltungModel->load($this->input->post_get('lvid')); + $lv = current($this->getDataOrTerminateWithError($result)); + $akteData['dokument_kurzbz'] = $xsl; + $akteData['titel'] = mb_substr($xsl . + "_" . + strtoupper($res->typ) . + strtoupper($res->kurzbz) . + "_" . + $semester . + '_' . + $ss . + '_' . + str_replace(' ', '_', $lv->bezeichnung), 0, 60); + $akteData['bezeichnung'] = mb_substr($xsl . + " " . + strtoupper($res->typ) . + strtoupper($res->kurzbz) . + " " . + $semester . + ". Semester" . + ' ' . + $ss . + ' ' . + $lv->bezeichnung, 0, 64); + break; + case 'SZeugnis': + $akteData['titel'] = mb_substr($vorlage->bezeichnung . " " . $student->kuerzel, 0, 64); + $akteData['bezeichnung'] = mb_substr($vorlage->bezeichnung . " " . $student->kuerzel, 0, 64); + break; + default: + $akteData['titel'] = mb_substr($xsl . + "_" . + strtoupper($res->typ) . + strtoupper($res->kurzbz) . + "_" . + $semester . + "_" . + $ss, 0, 64); + $akteData['bezeichnung'] = mb_substr($xsl . + " " . + strtoupper($res->typ) . + strtoupper($res->kurzbz) . + " " . + $semester . + ". Semester" . + ' ' . + $ss, 0, 64); + break; + } + } else { + $studiengang_kz = $student->studiengang_kz; + $akteData['person_id'] = $student->person_id; + $akteData['titel'] = $vorlage->bezeichnung . '_' . $student->kuerzel; + $akteData['bezeichnung'] = mb_substr($vorlage->bezeichnung . " " . $student->kuerzel, 0, 64); + } + } else { + $prestudent_id = $this->input->post_get('prestudent_id'); + if ($prestudent_id) { + $this->load->model('crm/prestudent_model', 'PrestudentModel'); + $this->PrestudentModel->addJoin('public.tbl_studiengang', 'studiengang_kz', 'LEFT'); + $result = $this->PrestudentModel->load($prestudent_id); + $prestudent = current($this->getDataOrTerminateWithError($result)); + + $studiengang_kz = $prestudent->studiengang_kz; + $akteData['person_id'] = $prestudent->person_id; + $akteData['titel'] = mb_substr($xsl . "_" . $prestudent->kuerzel, 0, 64); + $akteData['bezeichnung'] = mb_substr($vorlage->bezeichnung . " " . $prestudent->kuerzel, 0, 64); + } + } + + // Access rights + if (!$this->permissionlib->isBerechtigt('admin', 'suid', $studiengang_kz) + && !$this->permissionlib->isBerechtigt('assistenz', 'suid', $studiengang_kz)) + return $this->_outputAuthError([$this->router->method => ['admin:rw', 'assistenz:rw']]); + if ($xsl == 'AccountInfo') { + $this->load->model('resource/Mitarbeiter_model', 'MitarbeiterModel'); + $this->load->model('crm/Student_model', 'StudentModel'); + $uids = $this->input->post_get('uid'); + if ($uids) { + $uids = explode(';', $uids); + foreach ($uids as $uid) { + $result = $this->MitarbeiterModel->load($uid); + if (!isError($result) && hasData($result)) { + if (!$this->permissionlib->isBerechtigt('admin', 'suid', 0) + && !$this->permissionlib->isBerechtigt('mitarbeiter', 'suid', 0)) + return $this->_outputAuthError([$this->router->method => ['admin:rw', 'mitarbeiter:rw']]); + } else { + $result = $this->StudentModel->load([$uid]); + if (!isError($result) && hasData($result)) { + $student = current(getData($result)); + if (!$this->permissionlib->isBerechtigt('admin', 'suid', $student->studiengang_kz) + && !$this->permissionlib->isBerechtigt('admin', 'suid', 0) + && !$this->permissionlib->isBerechtigt('assistenz', 'suid', $student->studiengang_kz) + && !$this->permissionlib->isBerechtigt('assistenz', 'suid', 0) + && !$this->permissionlib->isBerechtigt('support', 'suid', 0)) + return $this->_outputAuthError([$this->router->method => ['admin:rw', 'assistenz:rw', 'support:rw']]); + } + } + } + } + } else { + $this->load->model('system/Vorlagestudiengang_model', 'VorlagestudiengangModel'); + + $result = $this->VorlagestudiengangModel->getCurrent($xsl, $xsl_oe_kurzbz, $version); + $access_rights = current($this->getDataOrTerminateWithError($result)); + if (!$access_rights || !$access_rights->berechtigung) + return show_404(); + + $allowed = false; + foreach ($access_rights->berechtigung as $access_right) { + if ($this->permissionlib->isBerechtigt($access_right)) { + $allowed = true; + break; + } + } + if (!$allowed) + return $this->_outputAuthError([$this->router->method => $access_rights]); + } + + // Output format + $outputformat = $this->input->post_get('output') ?: 'pdf'; + if ($outputformat != 'pdf' + // An der FHTW darf das Studienblatt und das Prüfungsprotokoll auch in anderen Formaten exportiert werden + && !(CAMPUS_NAME == 'FH Technikum Wien' + && ($xsl == 'Studienblatt' + || $xsl == 'StudienblattEng' + || $xsl == 'PrProtBA' + || $xsl == 'PrProtBAEng' + || $xsl == 'PrProtMA' + || $xsl == 'PrProtMAEng' + ) + ) + && !$this->permissionlib->isBerechtigt('system/change_outputformat', null, $xsl_oe_kurzbz) + ) { + $outputformat = 'pdf'; + } + + // XML Params + $params = 'xmlformat=xml'; + foreach ([ + 'uid', + 'stg_kz', + 'person_id', + 'id', + 'prestudent_id', + 'buchungsnummern', + 'ss', + 'abschlusspruefung_id', + 'typ', + 'all', + 'preoutgoing_id', + 'lvid', + 'projekt_kurzbz', + 'von', + 'bis', + 'stundevon', + 'stundebis', + 'sem', + 'lehreinheit', + 'mitarbeiter_uid', + 'studienordnung_id', + 'fixangestellt', + 'standort', + 'abrechnungsmonat', + 'form', + 'projektarbeit_id', + 'betreuerart_kurzbz', + 'studiensemester_kurzbz' + ] as $key) { + $value = $this->input->post_get($key); + if ($value !== null) + $params .= '&' . $key . '=' . urlencode($value); + } + $value = $this->input->post_get('vertrag_id'); + if ($value !== null) { + foreach ($value as $id) + $params .= '&vertrag_id[]=' . urlencode($id); + } + + if (!$vorlage->archivierbar) + $this->terminateWithError($this->p->t("stv", "grades_error_archive")); + + if ($sign_user && !$vorlage->signierbar) + $this->terminateWithError($this->p->t("stv", "grades_error_sign")); + + + $this->load->library('DocumentExportLib'); + + // XML Data + $result = $this->documentexportlib->getDataURL($xml, $params); + $data = $this->getDataOrTerminateWithError($result); + $this->documentexportlib->addArchiveToData($data); + + // Output + $result = $this->documentexportlib->getContent($vorlage, $data, $xsl_oe_kurzbz, $version, $outputformat, $sign_user); + + $content = $this->getDataOrTerminateWithError($result); + $akteData['titel'] .= '.pdf'; + $akteData['inhalt'] = base64_encode($content); + + $this->load->model('crm/Akte_model', 'AkteModel'); + $result = $this->AkteModel->insert($akteData); + $this->getDataOrTerminateWithError($result); + + $this->terminateWithSuccess(true); + } +} diff --git a/application/controllers/api/frontend/v1/stv/Config.php b/application/controllers/api/frontend/v1/stv/Config.php index 9010bcc0b..c3aeda49d 100644 --- a/application/controllers/api/frontend/v1/stv/Config.php +++ b/application/controllers/api/frontend/v1/stv/Config.php @@ -99,12 +99,19 @@ class Config extends FHCAPI_Controller 'title' => $this->p->t('stv', 'tab_resources'), 'component' => './Stv/Studentenverwaltung/Details/Betriebsmittel.js' ]; - /* TODO(chris): Ausgeblendet für Testing $result['grades'] = [ 'title' => $this->p->t('stv', 'tab_grades'), - 'component' => './Stv/Studentenverwaltung/Details/Noten.js' + 'component' => './Stv/Studentenverwaltung/Details/Noten.js', + 'showOnlyWithUid' => true, + 'config' => [ + 'usePoints' => defined('CIS_GESAMTNOTE_PUNKTE') && CIS_GESAMTNOTE_PUNKTE, + 'edit' => 'both', // Possible values: both|header|inline + 'delete' => 'both', // Possible values: both|header|inline + 'documents' => 'both', // Possible values: both|header|inline + 'documentslist' => $this->gradesDocumentsList() + ] ]; - */ + $result['exam'] = [ 'title' => $this->p->t('stv', 'tab_exam'), 'component' => './Stv/Studentenverwaltung/Details/Pruefung.js' @@ -254,4 +261,186 @@ class Config extends FHCAPI_Controller ] ] + $this->kontoColumns(); } + + /** + * Helper function to generate the default documentslist config for the + * grades tab. + * + * The resulting array consists of elements which are associative arrays + * that can have the following entries: + * title (required) on the first level this can be HTML code. + * permissioncheck (optional) an URL to an FHCAPI endpoint which returns + * true or false. + * link (optional) an URL that will be called if "action" and + * "children" are not defined. + * action (optional) an associative array that describes an + * POST action that will be called if "children" is + * not defined. + * It can have the following entries: + * - url (required) an URL to an FHCAPI endpoint. + * - post (optional) an associative array with the POST data to + * be sent. + * - response (optional) a string that will be displayed on success. + * children (optional) an array of child elements + * + * All strings that start with { and end with } in the URLs and the + * actions post parameter will be replaced with the corresponding + * attribute of the current dataset (e.G: {uid} will be replaced with the + * uid of the current dataset) + * + * @return array + */ + protected function gradesDocumentsList() + { + $permissioncheck = site_url("api/frontend/v1/documents/permissionAlternativeFormat/{studiengang_kz}"); + + $title_ger = $this->p->t("global", "deutsch"); + $title_eng = $this->p->t("global", "englisch"); + $title_ff = $this->p->t("stv", "document_certificate"); + $title_lv = $this->p->t("stv", "document_coursecertificate"); + + $link_ff = "documents/export/" . + "zertifikat.rdf.php/" . + "Zertifikat" . + "?stg_kz={studiengang_kz_lv}" . + "&uid={uid}" . + "&ss={studiensemester_kurzbz}" . + "&lvid={lehrveranstaltung_id}"; + $link_lv_ger = "documents/export/" . + "lehrveranstaltungszeugnis.rdf.php/" . + "LVZeugnis" . + "?stg_kz={studiengang_kz}" . + "&uid={uid}" . + "&ss={studiensemester_kurzbz}" . + "&lvid={lehrveranstaltung_id}"; + $link_lv_eng = "documents/export/" . + "lehrveranstaltungszeugnis.rdf.php/" . + "LVZeugnisEng" . + "?stg_kz={studiengang_kz}" . + "&uid={uid}" . + "&ss={studiensemester_kurzbz}" . + "&lvid={lehrveranstaltung_id}"; + + $archive_url = "api/frontend/v1/documents/archiveSigned"; + $archive_response = $this->p->t("stv", "document_signed_and_archived"); + $archive_post_ff = [ + "xml" => "zertifikat.rdf.php", + "xsl" => "Zertifikat", + "stg_kz" => "{studiengang_kz_lv}", + "uid" => "{uid}", + "ss" => "{studiensemester_kurzbz}", + "lvid" => "{lehrveranstaltung_id}" + ]; + $archive_post_lv_ger = [ + "xml" => "lehrveranstaltungszeugnis.rdf.php", + "xsl" => "LVZeugnis", + "stg_kz" => "{studiengang_kz}", + "uid" => "{uid}", + "ss" => "{studiensemester_kurzbz}", + "lvid" => "{lehrveranstaltung_id}" + ]; + $archive_post_lv_eng = [ + "xml" => "lehrveranstaltungszeugnis.rdf.php", + "xsl" => "LVZeugnisEng", + "stg_kz" => "{studiengang_kz}", + "uid" => "{uid}", + "ss" => "{studiensemester_kurzbz}", + "lvid" => "{lehrveranstaltung_id}" + ]; + + $list = [ + [ + 'title' => '', + 'children' => [ + [ + 'title' => $title_ff, + 'link' => site_url($link_ff) + ], + [ + 'title' => $title_lv, + 'children' => [ + [ + 'title' => $title_ger, + 'link' => site_url($link_lv_ger), + 'children' => [ + [ + 'title' => 'PDF', + 'permissioncheck' => $permissioncheck, + 'link' => site_url($link_lv_ger) + ], + [ + 'title' => 'DOC', + 'permissioncheck' => $permissioncheck, + 'link' => site_url($link_lv_ger . "&output=doc") + ], + [ + 'title' => 'ODT', + 'permissioncheck' => $permissioncheck, + 'link' => site_url($link_lv_ger . "&output=odt") + ] + ] + ], + [ + 'title' => $title_eng, + 'link' => site_url($link_lv_eng), + 'children' => [ + [ + 'title' => 'PDF', + 'permissioncheck' => $permissioncheck, + 'link' => site_url($link_lv_eng) + ], + [ + 'title' => 'DOC', + 'permissioncheck' => $permissioncheck, + 'link' => site_url($link_lv_eng . "&output=doc") + ], + [ + 'title' => 'ODT', + 'permissioncheck' => $permissioncheck, + 'link' => site_url($link_lv_eng . "&output=odt") + ] + ] + ] + ] + ] + ] + ], + [ + 'title' => '', + 'children' => [ + [ + 'title' => $title_ff, + 'action' => [ + 'url' => site_url($archive_url), + 'post' => $archive_post_ff, + 'response' => $archive_response + ] + ], + [ + 'title' => $title_lv, + 'children' => [ + [ + 'title' => $title_ger, + 'action' => [ + 'url' => site_url($archive_url), + 'post' => $archive_post_lv_ger, + 'response' => $archive_response + ] + ], + [ + 'title' => $title_eng, + 'action' => [ + 'url' => site_url($archive_url), + 'post' => $archive_post_lv_eng, + 'response' => $archive_response + ] + ] + ] + ] + ] + ] + ]; + + return $list; + } } diff --git a/application/controllers/api/frontend/v1/stv/Grades.php b/application/controllers/api/frontend/v1/stv/Grades.php new file mode 100644 index 000000000..61d797495 --- /dev/null +++ b/application/controllers/api/frontend/v1/stv/Grades.php @@ -0,0 +1,685 @@ +. + */ + +if (! defined('BASEPATH')) exit('No direct script access allowed'); + +/** + * This controller operates between (interface) the JS (GUI) and the back-end + * Provides data to the ajax get calls about grades + * This controller works with JSON calls on the HTTP GET or POST and the output is always JSON + */ +class Grades extends FHCAPI_Controller +{ + public function __construct() + { + parent::__construct([ + 'list' => 'student/noten:r', + 'getCertificate' => 'student/noten:r', + 'getTeacherProposal' => 'student/noten:r', + 'getRepeaterGrades' => 'student/noten:r', + 'updateCertificate' => ['admin:w', 'assistenz:w'], + 'deleteCertificate' => ['admin:w', 'assistenz:w'], + 'copyTeacherProposalToCertificate' => 'student/noten:w', + 'copyRepeaterGradeToCertificate' => 'student/noten:w', + 'getGradeFromPoints' => 'student/noten:r' + ]); + + // Load Libraries + $this->load->library('VariableLib', ['uid' => getAuthUID()]); + + // Load Phrases + $this->loadPhrases([ + 'stv', + 'person', + 'lehre' + ]); + } + + /** + * List all possible grades + * (Entries in lehre.tbl_note) + * + * @return void + */ + public function list() + { + $this->load->model('codex/Note_model', 'NoteModel'); + + $this->NoteModel->addOrder('note'); + + $result = $this->NoteModel->load(); + + $grades = $this->getDataOrTerminateWithError($result); + + $this->terminateWithSuccess($grades); + } + + /** + * List grades for the certificate of a prestudent. + * (Entries in lehre.tbl_zeugnisnote) + * + * @param string $prestudent_id + * @param string|null $all (optional) If null only the current semesters grades will be loaded, otherwise all semesters grades will be loaded. + * + * @return void + */ + public function getCertificate($prestudent_id, $all = null) + { + $this->load->model('crm/Student_model', 'StudentModel'); + $this->load->model('education/Zeugnisnote_model', 'ZeugnisnoteModel'); + + $result = $this->StudentModel->loadWhere([ + 'prestudent_id' => $prestudent_id + ]); + + $student = $this->getDataOrTerminateWithError($result); + if (!$student) + $this->terminateWithSuccess([]); + + + $student_uid = current($student)->student_uid; + + $studiensemester_kurzbz = ($all === null) ? $this->variablelib->getVar('semester_aktuell') : null; + + + $result = $this->ZeugnisnoteModel->getZeugnisnoten($student_uid, $studiensemester_kurzbz); + + $grades = $this->getDataOrTerminateWithError($result); + + $this->terminateWithSuccess($grades); + } + + /** + * List grades of a prestudent that teachers gave. + * (Entries in campus.tbl_lvgesamtnote) + * + * @param string $prestudent_id + * @param string|null $all (optional) If null only the current semesters grades will be loaded, otherwise all semesters grades will be loaded. + * + * @return void + */ + public function getTeacherProposal($prestudent_id, $all = null) + { + $this->load->model('crm/Student_model', 'StudentModel'); + $this->load->model('education/Lvgesamtnote_model', 'LvgesamtnoteModel'); + + $result = $this->StudentModel->loadWhere([ + 'prestudent_id' => $prestudent_id + ]); + + $student = $this->getDataOrTerminateWithError($result); + if (!$student) + $this->terminateWithSuccess([]); + + + $student_uid = current($student)->student_uid; + + $studiensemester_kurzbz = ($all === null) ? $this->variablelib->getVar('semester_aktuell') : null; + + + $result = $this->LvgesamtnoteModel->getLvGesamtNoten(null, $student_uid, $studiensemester_kurzbz); + + $grades = $this->getDataOrTerminateWithError($result); + + $this->terminateWithSuccess($grades); + } + + /** + * List grades of a prestudent that an assistant marked as already done + * or as not allowed because of the repeating of a semester. + * + * @param string $prestudent_id + * @param string|null $all (optional) If null only the current semesters grades will be loaded, otherwise all semesters grades will be loaded. + * + * @return void + */ + public function getRepeaterGrades($prestudent_id, $all = null) + { + $this->load->library('AntragLib'); + + $studiensemester_kurzbz = ($all === null) ? $this->variablelib->getVar('semester_aktuell') : false; + + + $result = $this->antraglib->getLvsForPrestudent($prestudent_id, $studiensemester_kurzbz); + + $grades = $this->getDataOrTerminateWithError($result); + + $this->terminateWithSuccess($grades); + } + + /** + * Update or Insert a grade for the certificate of a prestudent. + * (Entry in lehre.tbl_zeugnisnote) + * + * @return void + */ + public function updateCertificate() + { + $this->load->library('form_validation'); + + $this->form_validation->set_rules("lehrveranstaltung_id", $this->p->t('lehre', 'lehrveranstaltung'), "required|integer"); + $this->form_validation->set_rules("student_uid", $this->p->t('person', 'student'), "required"); + $this->form_validation->set_rules("studiensemester_kurzbz", $this->p->t('lehre', 'studiensemester'), "required"); + $this->form_validation->set_rules('note', $this->p->t('lehre', 'note'), 'required|numeric'); + + if (!$this->form_validation->run()) + $this->terminateWithValidationErrors($this->form_validation->error_array()); + + + $studiensemester_kurzbz = $this->input->post('studiensemester_kurzbz'); + $student_uid = $this->input->post('student_uid'); + $lehrveranstaltung_id = $this->input->post('lehrveranstaltung_id'); + $note = $this->input->post('note'); + $authUID = getAuthUID(); + $now = date('c'); + + // NOTE(chris): Stg Permissions + if (!$this->hasPermissionUpdate($lehrveranstaltung_id, $student_uid)) + return $this->_outputAuthError([$this->router->method => ['admin', 'assistenz']]); + + $this->load->model('education/Zeugnisnote_model', 'ZeugnisnoteModel'); + + $result = $this->ZeugnisnoteModel->load([ + 'studiensemester_kurzbz' => $studiensemester_kurzbz, + 'student_uid' => $student_uid, + 'lehrveranstaltung_id' => $lehrveranstaltung_id + ]); + $current = $this->getDataOrTerminateWithError($result); + + if ($current) { + $result = $this->ZeugnisnoteModel->update([ + 'studiensemester_kurzbz' => $studiensemester_kurzbz, + 'student_uid' => $student_uid, + 'lehrveranstaltung_id' => $lehrveranstaltung_id + ], [ + 'note' => $note, + 'benotungsdatum' => $now, + 'updateamum' => $now, + 'updatevon' => $authUID + ]); + } else { + $result = $this->ZeugnisnoteModel->insert([ + 'studiensemester_kurzbz' => $studiensemester_kurzbz, + 'student_uid' => $student_uid, + 'lehrveranstaltung_id' => $lehrveranstaltung_id, + 'note' => $note, + 'benotungsdatum' => $now, + 'insertamum' => $now, + 'insertvon' => $authUID + ]); + } + $this->getDataOrTerminateWithError($result); + + $this->terminateWithSuccess(true); + } + + /** + * Delete a grade from the certificate of a prestudent. + * (Entry in lehre.tbl_zeugnisnote) + * + * @return void + */ + public function deleteCertificate() + { + $this->load->library('form_validation'); + + $this->form_validation->set_rules("lehrveranstaltung_id", $this->p->t('lehre', 'lehrveranstaltung'), "required|integer"); + $this->form_validation->set_rules("student_uid", $this->p->t('person', 'student'), "required"); + $this->form_validation->set_rules("studiensemester_kurzbz", $this->p->t('lehre', 'studiensemester'), "required"); + + if (!$this->form_validation->run()) + $this->terminateWithValidationErrors($this->form_validation->error_array()); + + + $studiensemester_kurzbz = $this->input->post('studiensemester_kurzbz'); + $student_uid = $this->input->post('student_uid'); + $lehrveranstaltung_id = $this->input->post('lehrveranstaltung_id'); + + // NOTE(chris): Stg Permissions + if (!$this->hasPermissionDelete($lehrveranstaltung_id, $student_uid)) + return $this->_outputAuthError([$this->router->method => ['admin', 'assistenz']]); + + $this->load->model('education/Zeugnisnote_model', 'ZeugnisnoteModel'); + + $result = $this->ZeugnisnoteModel->delete([ + 'studiensemester_kurzbz' => $studiensemester_kurzbz, + 'student_uid' => $student_uid, + 'lehrveranstaltung_id' => $lehrveranstaltung_id + ]); + $this->getDataOrTerminateWithError($result); + + $this->terminateWithSuccess(true); + } + + /** + * Copy a grade that teachers gave to the certificate of a prestudent. + * (Entry in campus.tbl_lvgesamtnote to an entry in lehre.tbl_zeugnisnote) + * + * @return void + */ + public function copyTeacherProposalToCertificate() + { + $this->load->library('form_validation'); + + $this->form_validation->set_rules("lehrveranstaltung_id", $this->p->t('lehre', 'lehrveranstaltung'), "required|integer"); + $this->form_validation->set_rules("student_uid", $this->p->t('person', 'student'), "required"); + $this->form_validation->set_rules("studiensemester_kurzbz", $this->p->t('lehre', 'studiensemester'), "required"); + + if (!$this->form_validation->run()) + $this->terminateWithValidationErrors($this->form_validation->error_array()); + + $lehrveranstaltung_id = $this->input->post('lehrveranstaltung_id'); + $student_uid = $this->input->post('student_uid'); + $studiensemester_kurzbz = $this->input->post('studiensemester_kurzbz'); + $authUID = getAuthUID(); + + // NOTE(chris): Stg Permissions + if (!$this->hasPermissionCopy($lehrveranstaltung_id, $student_uid)) + return $this->_outputAuthError([$this->router->method => 'student/noten']); + + $this->load->model('education/Lvgesamtnote_model', 'LvgesamtnoteModel'); + + $result = $this->LvgesamtnoteModel->load([ + 'student_uid' => $student_uid, + 'studiensemester_kurzbz' => $studiensemester_kurzbz, + 'lehrveranstaltung_id' => $lehrveranstaltung_id + ]); + $teacherGrade = $this->getDataOrTerminateWithError($result); + + if (!$teacherGrade) + show_404(); + + $teacherGrade = current($teacherGrade); + + $data = [ + 'note' => $teacherGrade->note, + 'punkte' => $teacherGrade->punkte, + 'uebernahmedatum' => date('c'), + 'benotungsdatum' => $teacherGrade->benotungsdatum, + 'bemerkung' => $teacherGrade->bemerkung + ]; + + $this->load->model('education/Zeugnisnote_model', 'ZeugnisnoteModel'); + + $this->ZeugnisnoteModel->addJoin('lehre.tbl_note n', 'note'); + $result = $this->ZeugnisnoteModel->load([ + 'studiensemester_kurzbz' => $studiensemester_kurzbz, + 'student_uid' => $student_uid, + 'lehrveranstaltung_id' => $lehrveranstaltung_id + ]); + $certificateGrade = $this->getDataOrTerminateWithError($result); + + if ($certificateGrade) { + $certificateGrade = current($certificateGrade); + + if (!$certificateGrade->lkt_ueberschreibbar) + $this->terminateWithError($this->p->t("stv", "grades_error_overwrite")); + + // NOTE(chris): update + $data['updateamum'] = $data['uebernahmedatum']; + $data['updatevon'] = $authUID; + + $this->ZeugnisnoteModel->update([ + 'studiensemester_kurzbz' => $studiensemester_kurzbz, + 'student_uid' => $student_uid, + 'lehrveranstaltung_id' => $lehrveranstaltung_id + ], $data); + } else { + // NOTE(chris): insert + $data['insertamum'] = $data['uebernahmedatum']; + $data['insertvon'] = $authUID; + $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->terminateWithSuccess(true); + } + + /** + * Copy a grade that was marked by an assistant as already done or not + * allowed because of the repeating of a semester to the certificate of a + * prestudent. + * + * @return void + */ + public function copyRepeaterGradeToCertificate() + { + $this->load->library('form_validation'); + + $this->form_validation->set_rules("studierendenantrag_lehrveranstaltung_id", "studierendenantrag_lehrveranstaltung_id", "required|integer"); + + if (!$this->form_validation->run()) + $this->terminateWithValidationErrors($this->form_validation->error_array()); + + $id = $this->input->post('studierendenantrag_lehrveranstaltung_id'); + $authUID = getAuthUID(); + + $this->load->model('education/Studierendenantraglehrveranstaltung_model', 'StudierendenantraglehrveranstaltungModel'); + + $this->StudierendenantraglehrveranstaltungModel->addSelect("tbl_studierendenantrag_lehrveranstaltung.*"); + $this->StudierendenantraglehrveranstaltungModel->addSelect("student_uid"); + $this->StudierendenantraglehrveranstaltungModel->addJoin("campus.tbl_studierendenantrag", "studierendenantrag_id"); + $this->StudierendenantraglehrveranstaltungModel->addJoin("public.tbl_student", "prestudent_id", "LEFT"); + + $result = $this->StudierendenantraglehrveranstaltungModel->load($id); + $repeaterGrade = $this->getDataOrTerminateWithError($result); + + if (!$repeaterGrade) + show_404(); + + $repeaterGrade = current($repeaterGrade); + + // NOTE(chris): Stg Permissions + if (!$this->hasPermissionCopy($repeaterGrade->lehrveranstaltung_id, $repeaterGrade->student_uid)) + return $this->_outputAuthError([$this->router->method => 'student/noten']); + + $data = [ + 'note' => $repeaterGrade->note, + 'uebernahmedatum' => date('c'), + 'benotungsdatum' => $repeaterGrade->insertamum, + 'bemerkung' => $repeaterGrade->anmerkung + ]; + + $this->load->model('education/Zeugnisnote_model', 'ZeugnisnoteModel'); + + $result = $this->ZeugnisnoteModel->load([ + $repeaterGrade->studiensemester_kurzbz, + $repeaterGrade->student_uid, + $repeaterGrade->lehrveranstaltung_id + ]); + $certificateGrade = $this->getDataOrTerminateWithError($result); + + if ($certificateGrade) { + // NOTE(chris): update + $data['updateamum'] = $data['uebernahmedatum']; + $data['updatevon'] = $authUID; + + $this->ZeugnisnoteModel->update([ + $repeaterGrade->studiensemester_kurzbz, + $repeaterGrade->student_uid, + $repeaterGrade->lehrveranstaltung_id + ], $data); + } else { + // NOTE(chris): insert + $data['insertamum'] = $data['uebernahmedatum']; + $data['insertvon'] = $authUID; + $data['lehrveranstaltung_id'] = $repeaterGrade->lehrveranstaltung_id; + $data['student_uid'] = $repeaterGrade->student_uid; + $data['studiensemester_kurzbz'] = $repeaterGrade->studiensemester_kurzbz; + + $this->ZeugnisnoteModel->insert($data); + } + + + $this->terminateWithSuccess(true); + } + + /** + * Loads the grade from the points using the gradingkey + * + * @return void + */ + public function getGradeFromPoints() + { + $this->load->library('form_validation'); + + $this->form_validation->set_rules("lehrveranstaltung_id", $this->p->t('lehre', 'lehrveranstaltung'), "required|integer"); + $this->form_validation->set_rules("points", $this->p->t("stv", "grades_points"), "required|numeric"); + + if (!$this->form_validation->run()) + $this->terminateWithValidationErrors($this->form_validation->error_array()); + + $this->load->model('education/Notenschluesselaufteilung_model', 'NotenschluesselaufteilungModel'); + + $studiensemester_kurzbz = $this->variablelib->getVar('semester_aktuell'); + + $result = $this->NotenschluesselaufteilungModel->getNote( + $this->input->post('points'), + $this->input->post('lehrveranstaltung_id'), + $studiensemester_kurzbz + ); + + $note = $this->getDataOrTerminateWithError($result); + + $this->terminateWithSuccess($note); + } + + /** + * Helper function that adds tests for a student + * (Entries in lehre.tbl_pruefung) + * + * @param string $studiensemester_kurzbz + * @param string $student_uid + * @param integer $lehrveranstaltung_id + * @param integer $note + * @param numeric $punkte + * + * @return stdClass + */ + protected function addTestsForGrade($studiensemester_kurzbz, $student_uid, $lehrveranstaltung_id, $note, $punkte) + { + $this->load->model('education/Lehrveranstaltung_model', 'LehrveranstaltungModel'); + + // Get Lehreinheit + $result = $this->LehrveranstaltungModel->getLeByStudent($student_uid, $studiensemester_kurzbz, $lehrveranstaltung_id); + + if (isError($result)) + return $result; + if (!hasData($result)) + return error($this->p->t("stv", "grades_error_lehreinheit_id")); + $le = current(getData($result)); + + // 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 + ]; + + if (defined('CIS_GESAMTNOTE_PUNKTE') && CIS_GESAMTNOTE_PUNKTE) + $data["punkte"] = $punkte; + + // Get Anwesenheit + $this->load->model('education/Anwesenheit_model', 'AnwesenheitModel'); + $result = $this->AnwesenheitModel->loadAnwesenheitStudiensemester($studiensemester_kurzbz, $student_uid, $lehrveranstaltung_id); + if (isError($result)) + return $result; + $anwesenheit = getData($result); + + if ($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'); + + if (isError($result)) + return $result; + + $anwesenheitsbefreit = hasData($result); + + // Wenn nicht Anwesenheitsbefreit und Anwesenheit unter einem bestimmten Prozentsatz fällt dann wird ein Pruefungsantritt abgezogen + if (!$anwesenheitsbefreit) { + $data2 = $data; + $data2["note"] = 7; + if (isset($data2["punkte"])) + unset($data2["punkte"]); + + $result = $this->LePruefungModel->insert($data2); + + if (isError($result)) + return $result; + + $data["pruefungstyp_kurzbz"] = "Termin2"; + } + } + + return $this->LePruefungModel->insert($data); + } + + /** + * Helper function to check permissions for updateCertificate() + * + * @param integer $lehrveranstaltung_id + * @param string $student_uid + * + * @return boolean + */ + protected function hasPermissionUpdate($lehrveranstaltung_id, $student_uid) + { + if ($lehrveranstaltung_id === null || $student_uid === null) + return true; + + $this->load->model('crm/Student_model', 'StudentModel'); + + $result = $this->StudentModel->load([$student_uid]); + if (isError($result) || !hasData($result)) + return false; + + $student = current(getData($result)); + + if ($this->permissionlib->isBerechtigt('admin', 'suid', $student->studiengang_kz)) + return true; + if ($this->permissionlib->isBerechtigt('assistenz', 'suid', $student->studiengang_kz)) + return true; + + $this->load->model('organisation/Studienplan_model', 'StudienplanModel'); + + $result = $this->StudienplanModel->getAllOesForLv($lehrveranstaltung_id); + if (isError($result)) + return false; + + $oes = getData($result) ?: []; + + $this->load->model('education/Lehrveranstaltung_model', 'LehrveranstaltungModel'); + + $result = $this->LehrveranstaltungModel->getStg($lehrveranstaltung_id); + if (isError($result)) + return false; + + if (hasData($result)) + $oes[] = current(getData($result)); + + foreach ($oes as $oe) { + if ($this->permissionlib->isBerechtigt('admin', 'suid', $oe->oe_kurzbz)) + return true; + if ($this->permissionlib->isBerechtigt('assistenz', 'suid', $oe->oe_kurzbz)) + return true; + } + + return false; + } + + /** + * Helper function to check permissions for deleteCertificate() + * + * @param integer $lehrveranstaltung_id + * @param string $student_uid + * + * @return boolean + */ + protected function hasPermissionDelete($lehrveranstaltung_id, $student_uid) + { + if ($lehrveranstaltung_id === null || $student_uid === null) + return true; + + $this->load->model('crm/Student_model', 'StudentModel'); + + $result = $this->StudentModel->load([$student_uid]); + if (isError($result) || !hasData($result)) + return false; + + $student = current(getData($result)); + + if ($this->permissionlib->isBerechtigt('admin', 'suid', $student->studiengang_kz)) + return true; + if ($this->permissionlib->isBerechtigt('assistenz', 'suid', $student->studiengang_kz)) + return true; + + $this->load->model('education/Lehrveranstaltung_model', 'LehrveranstaltungModel'); + + $result = $this->LehrveranstaltungModel->load($lehrveranstaltung_id); + if (isError($result) || !hasData($result)) + return false; + + $oe = current(getData($result)); + + if ($this->permissionlib->isBerechtigt('admin', 'suid', $oe->oe_kurzbz)) + return true; + if ($this->permissionlib->isBerechtigt('assistenz', 'suid', $oe->oe_kurzbz)) + return true; + + return false; + } + + /** + * Helper function to check permissions for + * copyTeacherProposalToCertificate() and copyRepeaterGradeToCertificate() + * + * @param integer $lehrveranstaltung_id + * @param string $student_uid + * + * @return boolean + */ + protected function hasPermissionCopy($lehrveranstaltung_id, $student_uid) + { + if ($lehrveranstaltung_id === null || $student_uid === null) + return true; + + $this->load->model('crm/Student_model', 'StudentModel'); + + $result = $this->StudentModel->load([$student_uid]); + if (isError($result) || !hasData($result)) + return false; + + $student = current(getData($result)); + + if ($this->permissionlib->isBerechtigt('student/noten', 'suid', $student->studiengang_kz)) + return true; + + $this->load->model('education/Lehrveranstaltung_model', 'LehrveranstaltungModel'); + + $result = $this->LehrveranstaltungModel->load($lehrveranstaltung_id); + if (isError($result) || !hasData($result)) + return false; + + $oe = current(getData($result)); + + if ($this->permissionlib->isBerechtigt('student/noten', 'suid', $oe->oe_kurzbz)) + return true; + + return false; + } +} diff --git a/application/controllers/components/stv/Noten.php b/application/controllers/components/stv/Noten.php deleted file mode 100644 index fb61de065..000000000 --- a/application/controllers/components/stv/Noten.php +++ /dev/null @@ -1,168 +0,0 @@ - 'student/noten:r', - 'getZeugnis' => 'student/noten:r', - 'update' => ['admin:w', 'assistenz:w'] - ]); - - // Load Libraries - $this->load->library('VariableLib', ['uid' => getAuthUID()]); - } - - public function get() - { - $this->load->model('codex/Note_model', 'NoteModel'); - - $result = $this->NoteModel->addOrder('note'); - - $result = $this->NoteModel->load(); - if (isError($result)) { - $this->output->set_status_header(REST_Controller::HTTP_INTERNAL_SERVER_ERROR); - } - - return $this->outputJson($result); - } - - public function getZeugnis($prestudent_id, $all = null) - { - $this->load->model('crm/Student_model', 'StudentModel'); - $this->load->model('education/Zeugnisnote_model', 'ZeugnisnoteModel'); - - $result = $this->StudentModel->loadWhere([ - 'prestudent_id' => $prestudent_id - ]); - if (isError($result)) { - $this->output->set_status_header(REST_Controller::HTTP_INTERNAL_SERVER_ERROR); - return $this->outputJson($result); - } - if (!hasData($result)) - return $this->outputJsonSuccess(null); - - $student_uid = current(getData($result))->student_uid; - - $studiensemester_kurzbz = ($all === null) ? $this->variablelib->getVar('semester_aktuell') : null; - - $result = $this->ZeugnisnoteModel->getZeugnisnoten($student_uid, $studiensemester_kurzbz); - if (isError($result)) { - $this->output->set_status_header(REST_Controller::HTTP_INTERNAL_SERVER_ERROR); - } - - return $this->outputJson($result); - } - - public function update() - { - $this->load->model('crm/Student_model', 'StudentModel'); - $this->load->model('organisation/Studienplan_model', 'StudienplanModel'); - $this->load->model('education/Lehrveranstaltung_model', 'LehrveranstaltungModel'); - $this->load->model('education/Zeugnisnote_model', 'ZeugnisnoteModel'); - - $this->load->library('form_validation'); - - $_POST = json_decode(utf8_encode($this->input->raw_input_stream), true); - - if (empty($_POST) || !is_array(current($_POST))) { - $result = $this->hasPermissionUpdate($this->input->post('lehrveranstaltung_id'), $this->input->post('student_uid')); - if (isError($result)) { - $this->output->set_status_header(REST_Controller::HTTP_FORBIDDEN); - return $this->outputJson($result); - } - - $this->form_validation->set_rules('lehrveranstaltung_id', 'Lehrverantaltung ID', 'required|numeric'); - $this->form_validation->set_rules('student_uid', 'Student UID', 'required'); - $this->form_validation->set_rules('studiensemester_kurzbz', 'Studiensemester Kurzbezeichnung', 'required'); - $this->form_validation->set_rules('note', 'Note', 'required|numeric'); - $post = [$_POST]; - } else { - foreach ($_POST as $i => $data) { - $lvid = isset($data['lehrveranstaltung_id']) ? $data['lehrveranstaltung_id'] : null; - $uid = isset($data['student_uid']) ? $data['student_uid'] : null; - $result = $this->hasPermissionUpdate($lvid, $uid); - if (isError($result)) { - $this->output->set_status_header(REST_Controller::HTTP_FORBIDDEN); - return $this->outputJson($result); - } - - $this->form_validation->set_rules($i . '[lehrveranstaltung_id]', '#' . $i . ' Lehrverantaltung ID', 'required|numeric'); - $this->form_validation->set_rules($i . '[student_uid]', '#' . $i . ' Student UID', 'required'); - $this->form_validation->set_rules($i . '[studiensemester_kurzbz]', '#' . $i . ' Studiensemester Kurzbezeichnung', 'required'); - $this->form_validation->set_rules($i . '[note]', '#' . $i . ' Note', 'required|numeric'); - } - $post = $_POST; - } - if ($this->form_validation->run() == false) { - $this->output->set_status_header(REST_Controller::HTTP_BAD_REQUEST); - return $this->outputJsonError($this->form_validation->error_array()); - } - - $final_result = success(); - $this->ZeugnisnoteModel->db->trans_start(); - - foreach ($post as $i => $data) { - $note = $data['note']; - unset($data['note']); - $result = $this->ZeugnisnoteModel->update($data, [ - 'note' => $note, - 'benotungsdatum' => date('c'), - 'updateamum' => date('c'), - 'updatevon' => getAuthUID() - ]); - if (isError($result)) { - $final_result = $result; - break; - } - } - - $this->ZeugnisnoteModel->db->trans_complete(); - - if (isError($final_result)) { - $this->output->set_status_header(REST_Controller::HTTP_INTERNAL_SERVER_ERROR); - } - - $this->outputJson($final_result); - } - - protected function hasPermissionUpdate($lehrveranstaltung_id, $student_uid) - { - // TODO(chris): error phrases! - if ($lehrveranstaltung_id === null || $student_uid === null) - return success(); - $result = $this->StudentModel->load([$student_uid]); - if (isError($result)) - return $result; - if (!hasData($result)) - return error('Fehler beim Ermitteln des Studenten'); - $student = current(getData($result)); - - if ($this->permissionlib->isBerechtigt('admin', 'suid', $student->studiengang_kz)) - return success(); - if ($this->permissionlib->isBerechtigt('assistenz', 'suid', $student->studiengang_kz)) - return success(); - - $result = $this->StudienplanModel->getAllOesForLv($lehrveranstaltung_id); - if (isError($result)) - return $result; - $oes = getData($result) ?: []; - $result = $this->LehrveranstaltungModel->getStg($lehrveranstaltung_id); - if (isError($result)) - return $result; - if (hasData($result)) - $oes[] = current(getData($result)); - - foreach ($oes as $oe) { - if ($this->permissionlib->isBerechtigt('admin', 'suid', $oe->oe_kurzbz)) - return success(); - if ($this->permissionlib->isBerechtigt('assistenz', 'suid', $oe->oe_kurzbz)) - return success(); - } - - return error('Forbidden'); - } -} diff --git a/application/libraries/DocumentExportLib.php b/application/libraries/DocumentExportLib.php new file mode 100644 index 000000000..595ac461a --- /dev/null +++ b/application/libraries/DocumentExportLib.php @@ -0,0 +1,714 @@ +vorlage_kurzbz, $oe_kurzbz, $version); + * $doc->setFilename($filename); + * $doc->addDataXML($data); + * $doc->addImage($imagepath, $imagename, $imagecontenttype); + * $doc->create($outputformat); + * $doc->output(true); + * $doc->close(); + * + * New: + * $xml_data = $this->documentexportlib->getDataXML($data); + * $images = [[ + * 'path' => $imagepath, + * 'name' => $imagename, + * 'contenttype' => $imagecontenttype + * ]]; + * $this->documentexportlib->showContent( + * $filename, + * $vorlage, + * $xml_data, + * $oe_kurzbz, + * $version, + * $outputformat, + * null, + * null, + * $images + * ); + */ +class DocumentExportLib +{ + private $unoconv_version; + + /** + * Constructor + */ + public function __construct() + { + // Gets CI instance + $this->ci =& get_instance(); + + // Load Phrases + $this->ci->load->library('PhrasesLib', ['document_export', null], 'documentExportPhrases'); + + // Which document converter has to be used + if (defined('DOCSBOX_ENABLED') && DOCSBOX_ENABLED === true) + { + // Use docsbox!! + } + else + { + exec('unoconv --version', $ret_arr); + + if(isset($ret_arr[0])) + { + $hlp = explode(' ', $ret_arr[0]); + if(isset($hlp[1])) + { + $this->unoconv_version = $hlp[1]; + } + else + show_error($this->ci->documentExportPhrases->t("document_export", "error_unoconv_version")); + } + else + show_error($this->ci->documentExportPhrases->t("document_export", "error_unoconv")); + } + } + + /** + * Laedt die XML Daten fuer die XSL Transformation anhand eines Arrays + * + * @param array $data Array mit Daten + * @param string $root Bezeichnung des Root Nodes + * + * @return DOMDocument + */ + public function getDataArray($data, $root) + { + $xml_data = new DOMDocument(); + $xml_data->loadXML($this->convertArrayToXML($data, $root)); + return $xml_data; + } + + /** + * XML Daten fuer die XSL Transformation + * + * @param string $xml + * + * @return DOMDocument + */ + public function getDataXML($xml) + { + $xml_data = new DOMDocument(); + $xml_data->loadXML($xml); + return $xml_data; + } + + /** + * URL zu XML Datei die fuer XSLTransformation verwendet werden soll + * + * @param string $xml URL to XML + * @param string $params GET parameter + * + * @return stdClass + */ + public function getDataURL($xml, $params) + { + $xml_found = false; + + $aktive_addons = array_filter(array_map('trim', explode(";", ACTIVE_ADDONS))); + foreach($aktive_addons as $addon) { + $xmlfile = DOC_ROOT . 'addons/' . $addon . '/rdf/' . $xml; + if (file_exists($xmlfile)) { + $xml_found = true; + $xml_url = XML_ROOT . '../addons/' . $addon . '/rdf/' . $xml . '?' . $params; + break; + } + } + if (!$xml_found) + $xml_url = XML_ROOT . $xml . '?' . $params; + + + // Load the XML source + $xml_data = new DOMDocument; + + if (!$xml_data->load($xml_url)) + return error($this->ci->documentExportPhrases->t("document_export", "error_xml_load", [ + "url" => $xml_url, + "xml" => $xml, + "params" => $params + ])); + + return success($xml_data); + } + + /** + * Adds a XML Tag for signatur to the document + * + * @param DomDocument $xml_data + * + * @return void + */ + protected function addSignToData($xml_data) + { + $signblock = $xml_data->createElement("signed", "true"); + $xml_data->documentElement->appendChild($signblock); + } + + /** + * Adds a XML Tag for archive to the document + * + * @param DomDocument $xml_data + * + * @return void + */ + public function addArchiveToData($xml_data) + { + $archiv = $xml_data->createElement("archivierbar", "true"); + $xml_data->documentElement->appendChild($archiv); + } + + /** + * Get the contents of a Document + * + * @param stdClass $vorlage A db entry from tbl_vorlage + * @param DomDocument $xml_data + * @param string $oe_kurzbz + * @param integer|null $version (optional) + * @param string $outputformat (optional) + * @param string $sign_user (optional) Must be a valid uid + * @param string $sign_profile (optional) Signatureprofile for signing + * @param array $images (optional) Each element should have a property path, name & contenttype which are all strings + * + * @return stdClass + */ + public function getContent( + $vorlage, + $xml_data, + $oe_kurzbz, + $version = null, + $outputformat = null, + $sign_user = null, + $sign_profile = null, + $images = [] + ) { + $source_folder = getcwd(); + $temp_folder = sys_get_temp_dir() . '/fhcunoconv-' . uniqid(); + + $outputformat = $this->getDefaultOutputFormat($outputformat, $vorlage->mimetype); + + $result = $this->createAndSignContent( + $temp_folder, + $outputformat, + $vorlage, + $oe_kurzbz, + $version, + $xml_data, + $images, + $sign_user, + $sign_profile + ); + if (isError($result)) { + $this->close($temp_folder, $source_folder); + return $result; + } + $temp_filename = getData($result); + + $fsize = filesize($temp_filename); + $handle = fopen($temp_filename, 'r'); + if (!$handle) + return error($this->ci->documentExportPhrases->t("document_export", "error_file_load")); + $result = fread($handle, $fsize); + fclose($handle); + + $this->close($temp_folder, $source_folder); + + return success($result); + } + + /** + * Sets the headers and displays the Document. + * On failure the exit() function will be called + * + * @param string $filename + * @param stdClass $vorlage A db entry from tbl_vorlage + * @param DomDocument $xml_data + * @param string $oe_kurzbz + * @param integer|null $version (optional) + * @param string $outputformat (optional) + * @param string $sign_user (optional) Must be a valid uid + * @param string $sign_profile (optional) Signatureprofile for signing + * @param array $images (optional) Each element should have a property path, name & contenttype which are all strings + * + * @return void + */ + public function showContent( + $filename, + $vorlage, + $xml_data, + $oe_kurzbz, + $version = null, + $outputformat = null, + $sign_user = null, + $sign_profile = null, + $images = [] + ) { + $source_folder = getcwd(); + $temp_folder = sys_get_temp_dir() . '/fhcunoconv-' . uniqid(); + + $outputformat = $this->getDefaultOutputFormat($outputformat, $vorlage->mimetype); + + $result = $this->createAndSignContent( + $temp_folder, + $outputformat, + $vorlage, + $oe_kurzbz, + $version, + $xml_data, + $images, + $sign_user, + $sign_profile + ); + if (isError($result)) { + $this->close($temp_folder, $source_folder); + exit(getError($result)); + } + $temp_filename = getData($result); + + $fsize = filesize($temp_filename); + $handle = fopen($temp_filename, 'r'); + if (!$handle) { + $this->close($temp_folder, $source_folder); + exit($this->ci->documentExportPhrases->t("document_export", "error_file_load")); + } + + if (headers_sent()) { + $this->close($temp_folder, $source_folder); + exit($this->ci->documentExportPhrases->t("document_export", "error_headers")); + } + + switch ($outputformat) { + case 'pdf': + header('Content-type: application/pdf'); + header('Content-Disposition: attachment; filename="' . $filename . '.pdf"'); + header('Content-Length: ' . $fsize); + break; + + case 'doc': + header('Content-type: application/vnd.ms-word'); + header('Content-Disposition: attachment; filename="' . $filename . '.doc"'); + header('Content-Length: ' . $fsize); + break; + + case 'odt': + header('Content-type: application/vnd.oasis.opendocument.text'); + header('Content-Disposition: attachment; filename="' . $filename . '.odt"'); + header('Content-Length: ' . $fsize); + break; + default: + $this->close($temp_folder, $source_folder); + exit($this->ci->documentExportPhrases->t("document_export", "error_outputformat_missing")); + } + + while (!feof($handle)) { + echo fread($handle, 8192); + } + fclose($handle); + + $this->close($temp_folder, $source_folder); + } + + /** + * Helper function for getContent and showContent. + * Creates the temp folder and calls create and sign functions. + * + * @param string $temp_folder + * @param string $outputformat + * @param stdClass $vorlage + * @param string $oe_kurzbz + * @param integer $version + * @param DomDocument $xml_data + * @param array $images Each element should have a property path, name and contenttype which are all strings + * @param string $sign_user Must be a valid uid + * @param string $sign_profile Signatureprofile for signing + * + * @return stdClass + */ + protected function createAndSignContent( + $temp_folder, + $outputformat, + $vorlage, + $oe_kurzbz, + $version, + $xml_data, + $images, + $sign_user, + $sign_profile + ) { + mkdir($temp_folder); + chdir($temp_folder); + + $this->ci->load->model('system/Vorlagestudiengang_model', 'VorlagestudiengangModel'); + + $result = $this->ci->VorlagestudiengangModel->getCurrent($vorlage->vorlage_kurzbz, $oe_kurzbz, $version); + if (isError($result)) + return $result; + if (!hasData($result)) + return error($this->ci->documentExportPhrases->t("document_export", "error_template_missing")); + $vorlage_stg = current(getData($result)); + foreach ($vorlage_stg as $k => $v) + $vorlage->$k = $v; + + $result = $this->create($temp_folder, $outputformat, $vorlage, $xml_data, $images); + if (isError($result)) + return $result; + + $temp_filename = getData($result); + + if ($sign_user) { + $this->addSignToData($xml_data); + + $result = $this->sign($temp_folder, $temp_filename, $outputformat, $sign_user, $sign_profile); + if (isError($result)) + return $result; + + $temp_filename = getData($result); + } + + return success($temp_filename); + } + + /** + * Helper function for createAndSignContent. + * Creates the files in the temp folder. + * + * @param string $temp_folder + * @param string $outputformat + * @param stdClass $vorlage + * @param DomDocument $xml_data + * @param array $images Each element should have a property path, name and contenttype which are all strings + * + * @return stdClass + */ + protected function create($temp_folder, $outputformat, $vorlage, $xml_data, $images) + { + $content_xsl = new DOMDocument(); + if (!$content_xsl->loadXML($vorlage->text)) + return error($this->ci->documentExportPhrases->t("document_export", "error_xsl_load")); + + $proc = new XSLTProcessor(); + $proc->importStyleSheet($content_xsl); + + $contentbuffer = $proc->transformToXml($xml_data); + + file_put_contents($temp_folder . '/content.xml', $contentbuffer); + + if ($xml_data->firstChild->tagName == 'error') + return error($xml_data->firstChild->textContent); + + // styles.xml erstellen + if ($vorlage->style) { + $styles_xsl = new DOMDocument(); + if (!$styles_xsl->loadXML($vorlage->style)) + return error($this->ci->documentExportPhrases->t("document_export", "error_styles_load")); + $style_proc = new XSLTProcessor(); + $style_proc->importStyleSheet($styles_xsl); + + $stylesbuffer = $style_proc->transformToXml($xml_data); + + file_put_contents($temp_folder . '/styles.xml', $stylesbuffer); + } + + // Template holen + $vorlage_found = false; + $vorlage_filename = $vorlage->vorlage_kurzbz . ($vorlage->mimetype == 'application/vnd.oasis.opendocument.spreadsheet' ? '.ods' : '.odt'); + + $aktive_addons = array_filter(array_map('trim', explode(";", ACTIVE_ADDONS))); + foreach($aktive_addons as $addon) { + $zipfile = DOC_ROOT . 'addons/' . $addon . '/system/vorlage_zip/' . $vorlage_filename; + + if (file_exists($zipfile)) { + $vorlage_found = true; + break; + } + } + if (!$vorlage_found) + $zipfile = DOC_ROOT . 'system/vorlage_zip/' . $vorlage_filename; + + $tempname_zip = $temp_folder . '/out.zip'; + + if (!copy($zipfile, $tempname_zip)) + return error($this->ci->documentExportPhrases->t("document_export", "error_file_copy")); + + exec("zip $tempname_zip content.xml"); + if (!is_null($styles_xsl)) + exec("zip $tempname_zip styles.xml"); + + // bilder hinzufuegen + if (count($images) > 0) + { + // Unterordner fuer die Bilder erstellen + mkdir('Pictures'); + + // Manifest Datei holen + exec('unzip ' . $tempname_zip . ' META-INF/manifest.xml'); + + // Bild zur Manifest Datei hinzufuegen + $manifest = file_get_contents('META-INF/manifest.xml'); + + $manifest_xml = new DOMDocument; + if (!$manifest_xml->loadXML($manifest)) + return error($this->ci->documentExportPhrases->t("document_export", "error_manifest")); + + //root-node holen + $root = $manifest_xml->getElementsByTagName('manifest')->item(0); + + foreach ($images as $bild) { + copy($bild['path'], 'Pictures/' . $bild['name']); + + //Neues Element unterhalb des Root Nodes anlegen + $node = $manifest_xml->createElement("manifest:file-entry"); + $node->setAttribute("manifest:full-path", 'Pictures/' . $bild['name']); + $node->setAttribute("manifest:media-type", $bild['contenttype']); + $root->appendChild($node); + } + + $out = $manifest_xml->saveXML(); + + //geaenderte Manifest Datei speichern und wieder ins Zip packen + file_put_contents('META-INF/manifest.xml', $out); + exec('zip ' . $tempname_zip . ' META-INF/*'); + + // Bilder zum ZIP-File hinzufuegen + exec('zip ' . $tempname_zip . ' Pictures/*'); + } + + clearstatcache(); + + switch ($outputformat) { + case 'pdf': + case 'doc': + $ret = 0; + $temp_filename = $temp_folder . '/out.' . $outputformat; + + if (defined('DOCSBOX_ENABLED') && DOCSBOX_ENABLED === true) { + // Use docsbox + + $this->ci->load->library("DocsboxLib"); + + $docboxlib = get_class($this->ci->docboxlib); + + $ret = $docboxlib::convert($tempname_zip, $temp_filename, $outputformat); + } else { + // Use unoconv + + // Unoconv Version 0.6 hat eine Bug wodurch die Berechtigungen des PDF/Doc nicht korrekt gesetzt + // werden. Deshalb wird dies hier speziell behandelt. + // Die 2. Variante hat den Vorteil dass hier eine bessere Fehlerbehandlung moeglich ist + if ($this->unoconv_version == '0.6') + $command = 'unoconv -e IsSkipEmptyPages=false -f ' . $outputformat . ' %2$s > %1$s'; + else + $command = 'unoconv -e IsSkipEmptyPages=false -f ' . $outputformat . ' --output %s %s 2>&1'; + + $command = sprintf($command, $temp_filename, $tempname_zip); + + exec($command, $out, $ret); + } + + if ($ret) + return error($this->ci->documentExportPhrases->t("document_export", "error_conv_timeout")); + break; + case 'odt': + default: + $temp_filename = $tempname_zip; + } + + return success($temp_filename); + } + + /** + * Helper function for createAndSignContent. + * Signs the main file in the temp folder. + * + * @param string $temp_folder + * @param string $temp_filename + * @param string $outputformat + * @param string $user Must be a valid uid + * @param string $profile Signatureprofile for signing + * + * @return stdClass + */ + protected function sign($temp_folder, $temp_filename, $outputformat, $user, $profile) + { + if ($outputformat != 'pdf') + return error($this->ci->documentExportPhrases->t("document_export", "error_sign_pdf")); + + // Load the File + $file_data = file_get_contents($temp_filename); + + $data = new stdClass(); + $data->document = base64_encode($file_data); + + // Signatur Profil + if (!is_null($profile)) + $data->profile = $profile; + else + $data->profile = SIGNATUR_DEFAULT_PROFILE; + + // Username des Endusers der die Signatur angefordert hat + $data->user = $user; + + $ch = curl_init(); + + curl_setopt($ch, CURLOPT_URL, SIGNATUR_URL . '/' . SIGNATUR_SIGN_API); + curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 7); + curl_setopt($ch, CURLOPT_USERAGENT, "FH-Complete"); + + // SSL Zertifikatsprüfung deaktivieren + // Besser ist es das Zertifikat am Server zu installieren! + //curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); + //curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); + + $data_string = json_encode($data, JSON_FORCE_OBJECT); + + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST"); + curl_setopt($ch, CURLOPT_POSTFIELDS, $data_string); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_HTTPHEADER, [ + 'Content-Type: application/json', + 'Content-Length:' . mb_strlen($data_string), + 'Authorization: Basic ' . base64_encode(SIGNATUR_USER . ":" . SIGNATUR_PASSWORD) + ]); + + $result = curl_exec($ch); + if (curl_errno($ch)) { + curl_close($ch); + return error($this->ci->documentExportPhrases->t("document_export", "error_sign_timeout")); + } + curl_close($ch); + $resultdata = json_decode($result); + + // If it is success + if (isset($resultdata->error) && $resultdata->error == 0) { + $signed_filename = $temp_folder . '/signed.pdf'; + file_put_contents($signed_filename, base64_decode($resultdata->retval)); + return success($signed_filename); + } + + // otherwise if it is an error + return error($resultdata->retval ?? $this->ci->documentExportPhrases->t("global", "unknown_error", ["error" => $result])); + } + + /** + * Deletes all files in the $temp_folder and changes back to the source_folder + * + * @param string $temp_folder + * @param string $source_folder + * + * @return void + */ + protected function close($temp_folder, $source_folder) + { + $files = glob($temp_folder . '/*'); // get all file names + foreach ($files as $file) + if (is_file($file)) + unlink($file); + + chdir($source_folder); + rmdir($temp_folder); + } + + /** + * Convert an array to XML + * + * @param array $data + * @param string $root + * @param SimpleXMLElement $xml_data + * + * @return string|boolean + */ + private function convertArrayToXML($data, $root = null, $xml_data = null) + { + $_xml_data = $xml_data; + if ($_xml_data === null) + $_xml_data = new SimpleXMLElement($root !== null ? '<' . $root . ' />' : ''); + + foreach ($data as $key => $value) { + if (is_array($value)) { + if (is_numeric($key)) { + $key = 'item' . $key; // dealing with <0/>.. issues + $this->convertArrayToXML($value, null, $_xml_data); + } else { + $subnode = $_xml_data->addChild($key); + $this->convertArrayToXML($value, null, $subnode); + } + } else { + // Remove UTF8 Control Characters (breaking XML) + $value = preg_replace('/[\x00-\x1F\x7F]/u', '', $value); + $_xml_data->addChild((string)$key, htmlspecialchars("$value")); + } + } + + return $_xml_data->asXML(); + } + + /** + * Get default outputformat from mimetype if its not set + * + * @param string $outputformat + * @param string $mimetype + * + * @return string + */ + private function getDefaultOutputFormat($outputformat, $mimetype) + { + if ($outputformat) + return $outputformat; + + if ($mimetype == 'application/vnd.oasis.opendocument.spreadsheet') + return 'ods'; + if ($mimetype == 'application/vnd.oasis.opendocument.text') + return 'odt'; + + return 'pdf'; + } +} diff --git a/application/models/education/Anwesenheit_model.php b/application/models/education/Anwesenheit_model.php index 80a1fc111..b2c78fe02 100644 --- a/application/models/education/Anwesenheit_model.php +++ b/application/models/education/Anwesenheit_model.php @@ -11,4 +11,193 @@ class Anwesenheit_model extends DB_Model $this->dbTable = 'campus.tbl_anwesenheit'; $this->pk = 'anwesenheit_id'; } + + /** + * Laedt die Anwesenheiten in Prozent von Studierenden bei Lehrveranstaltungen + * Wenn die StudentUID uebergeben wird, werden alle Lehrveranstaltungen zu denen der Studierenden zugeteilt ist inkl Prozent der Anwesenheit + * Wenn die LehrveranstaltungID uebergeben wird, werden alle Studierenden geholt die zugeteilt sind inkl Prozent der Anwesenheit + * Es werden pro Student die Anwesenheiten berechnet aufgrund der Lehreinheit zu der sie zugeordnet sind + * + * @param string $studiensemester_kurzbz + * @param string|null (optional) $student_uid + * @param integer|null (optional) $lehrveranstaltung_id + * + * @return stdClass + */ + public function loadAnwesenheitStudiensemester($studiensemester_kurzbz, $student_uid = null, $lehrveranstaltung_id = null) + { + $this->addSelect("vorname"); + $this->addSelect("nachname"); + $this->addSelect("wahlname"); + $this->addSelect("lehrveranstaltung_id"); + $this->addSelect("bezeichnung"); + $this->addSelect("gruppe"); + $this->addSelect("student_uid AS uid"); + $this->addSelect("COUNT(stundenplan_id) AS gesamtstunden"); + $this->addSelect("COALESCE(anwesend.summe, 0) AS anwesend"); + $this->addSelect("COALESCE(nichtanwesend.summe, 0) AS nichtanwesend"); + $this->addSelect("COALESCE(anwesend.summe, 0) + COALESCE(nichtanwesend.summe, 0) AS erfassteanwesenheit"); + $this->addSelect("CASE + WHEN COUNT(stundenplan_id) = 0 OR COALESCE(anwesend.summe, 0) + COALESCE(nichtanwesend.summe, 0) = 0 + THEN 100 + ELSE TRUNC(100-(100/COUNT(stundenplan_id)*COALESCE(nichtanwesend.summe, 0)), 2) + END AS prozent"); + + + $this->db->join("( + SELECT + semester::text AS gruppe, + public.tbl_studentlehrverband.studiensemester_kurzbz, + student_uid, + studiengang_kz + FROM public.tbl_studentlehrverband + WHERE studiensemester_kurzbz = " . $this->escape($studiensemester_kurzbz) . " + + UNION + + SELECT + semester || verband AS gruppe, + public.tbl_studentlehrverband.studiensemester_kurzbz, + student_uid, + studiengang_kz + FROM public.tbl_studentlehrverband + WHERE studiensemester_kurzbz = " . $this->escape($studiensemester_kurzbz) . " + + UNION + + SELECT + semester || verband || gruppe AS gruppe, + public.tbl_studentlehrverband.studiensemester_kurzbz, + student_uid, + studiengang_kz + FROM public.tbl_studentlehrverband + WHERE studiensemester_kurzbz = " . $this->escape($studiensemester_kurzbz) . " + + UNION + + SELECT + gruppe_kurzbz AS gruppe, + public.tbl_benutzergruppe.studiensemester_kurzbz, + uid AS student_uid, + studiengang_kz + FROM public.tbl_benutzergruppe + JOIN public.tbl_gruppe USING (gruppe_kurzbz) + WHERE studiensemester_kurzbz = " . $this->escape($studiensemester_kurzbz) . " + ) a", "gruppe,studiensemester_kurzbz,studiengang_kz", "", false); + $this->addJoin("public.tbl_benutzer b", "b.uid = student_uid"); + $this->addJoin("public.tbl_person p", "person_id"); + $this->db->join("( + SELECT + lehrveranstaltung_id, + studiensemester_kurzbz, uid AS student_uid, + SUM(einheiten) AS summe + FROM campus.tbl_anwesenheit a + JOIN lehre.tbl_lehreinheit le USING (lehreinheit_id) + JOIN lehre.tbl_lehrveranstaltung lv USING (lehrveranstaltung_id) + WHERE anwesend = TRUE + AND studiensemester_kurzbz = " . $this->escape($studiensemester_kurzbz) . " + GROUP BY + lehrveranstaltung_id, + bezeichnung, + uid, + studiensemester_kurzbz + ) anwesend", "lehrveranstaltung_id,student_uid,studiensemester_kurzbz", "LEFT", false); + $this->db->join("( + SELECT + lehrveranstaltung_id, + studiensemester_kurzbz, + uid AS student_uid, + SUM(einheiten) AS summe + FROM campus.tbl_anwesenheit a + JOIN lehre.tbl_lehreinheit le USING (lehreinheit_id) + JOIN lehre.tbl_lehrveranstaltung lv USING (lehrveranstaltung_id) + WHERE anwesend = FALSE + AND studiensemester_kurzbz = " . $this->escape($studiensemester_kurzbz) . " + GROUP BY + lehrveranstaltung_id, bezeichnung, uid, studiensemester_kurzbz + ) nichtanwesend", "lehrveranstaltung_id,student_uid,studiensemester_kurzbz", "LEFT", false); + + $this->addGroupBy("vorname"); + $this->addGroupBy("nachname"); + $this->addGroupBy("wahlname"); + $this->addGroupBy("lehrveranstaltung_id"); + $this->addGroupBy("bezeichnung"); + $this->addGroupBy("gruppe"); + $this->addGroupBy("student_uid"); + $this->addGroupBy("anwesend.summe"); + $this->addGroupBy("nichtanwesend.summe"); + + + $where = [ + "lehrveranstaltung_id >" => 0 + ]; + + if ($student_uid) + $where["student_uid"] = $student_uid; + + if ($lehrveranstaltung_id) + $where["lehrveranstaltung_id"] = $lehrveranstaltung_id; + + if ($lehrveranstaltung_id) { + $this->addOrder("nachname"); + $this->addOrder("vorname"); + } elseif ($student_uid) { + $this->addOrder("bezeichnung"); + } + + + $tmp = $this->dbTable; + + $this->dbTable = "( + SELECT + SUM(stundenplan_id) AS stundenplan_id, + datum, + stunde, + lehrveranstaltung_id, + bezeichnung, + studiensemester_kurzbz, + studiengang_kz, + TRIM( + CASE + WHEN stp.gruppe_kurzbz IS NOT NULL + THEN stp.gruppe_kurzbz + ELSE stp.semester || ( + CASE + WHEN verband IS NULL + THEN '' + ELSE stp.verband + END + ) || ( + CASE + WHEN stp.gruppe IS NULL + THEN '' + ELSE stp.gruppe + END + ) + END + ) AS gruppe + FROM lehre.tbl_lehrveranstaltung lv + JOIN lehre.tbl_lehreinheit le USING (lehrveranstaltung_id) + JOIN lehre.tbl_stundenplan stp USING (lehreinheit_id,studiengang_kz) + WHERE studiensemester_kurzbz = " . $this->escape($studiensemester_kurzbz) . " + AND (titel NOT LIKE '%Nebenprüfung%' OR titel IS NULL) + GROUP BY + datum, + stunde, + lehrveranstaltung_id, + bezeichnung, + studiensemester_kurzbz, + studiengang_kz, + stp.gruppe_kurzbz, + stp.semester, + stp.verband, + stp.gruppe + ) x"; + + $result = $this->loadWhere($where); + + $this->dbTable = $tmp; + + return $result; + } } diff --git a/application/models/education/Lehrveranstaltung_model.php b/application/models/education/Lehrveranstaltung_model.php index e61101b14..962520fa0 100644 --- a/application/models/education/Lehrveranstaltung_model.php +++ b/application/models/education/Lehrveranstaltung_model.php @@ -735,6 +735,37 @@ class Lehrveranstaltung_model extends DB_Model return $this->execQuery($query, array($uid, $studiensemester_kurzbz, $lehrveranstaltung_id)); } + /** + * Get Lehreinheit. + * + * @param string $student_uid + * @param string $studiensemester_kurzbz + * @param integer $lehrveranstaltung_id + * + * @return stdClass + */ + public function getLeByStudent($student_uid, $studiensemester_kurzbz, $lehrveranstaltung_id) + { + $this->addSelect("lehreinheit_id"); + + $this->addOrder("lehreinheit_id", "ASC"); + + $this->addLimit(1); + + $tmp = $this->dbTable; + $this->dbTable = "campus.vw_student_lehrveranstaltung"; + + $result = $this->loadWhere([ + "uid" => $student_uid, + "lehrveranstaltung_id" => $lehrveranstaltung_id, + "studiensemester_kurzbz" => $studiensemester_kurzbz + ]); + + $this->dbTable = $tmp; + + return $result; + } + /** * Sucht nach LV Templates und gibt Id und Label ("bezeichnung [kurzbz]") aus * Diese funktion ist für autocomplete gedacht diff --git a/application/models/education/Lvgesamtnote_model.php b/application/models/education/Lvgesamtnote_model.php index 975833287..c30045ff0 100644 --- a/application/models/education/Lvgesamtnote_model.php +++ b/application/models/education/Lvgesamtnote_model.php @@ -12,4 +12,38 @@ class Lvgesamtnote_model extends DB_Model $this->pk = array('student_uid', 'studiensemester_kurzbz', 'lehrveranstaltung_id'); $this->hasSequence = false; } + + /** + * Laedt die Noten + * + * @param integer $lehrveranstaltung_id + * @param string $student_uid + * @param string $studiensemester_kurzbz + * + * @return stdClass + */ + public function getLvGesamtNoten($lehrveranstaltung_id, $student_uid, $studiensemester_kurzbz) + { + $this->addSelect($this->dbTable . ".*"); + $this->addSelect("n.bezeichnung AS note_bezeichnung"); + $this->addSelect("lv.bezeichnung AS lehrveranstaltung_bezeichnung"); + $this->addSelect("lv.studiengang_kz"); + $this->addSelect("UPPER(stg.typ || stg.kurzbz) AS studiengang"); + + $this->addJoin("lehre.tbl_note n", "note"); + $this->addJoin("lehre.tbl_lehrveranstaltung lv", "lehrveranstaltung_id"); + $this->addJoin("public.tbl_studiengang stg", "studiengang_kz"); + + $this->db->where($this->dbTable . ".freigabedatum <", "NOW()", false); + + $where = []; + if ($studiensemester_kurzbz) + $where[$this->dbTable . ".studiensemester_kurzbz"] = $studiensemester_kurzbz; + if ($lehrveranstaltung_id) + $where[$this->dbTable . ".lehrveranstaltung_id"] = $lehrveranstaltung_id; + if ($student_uid) + $where[$this->dbTable . ".student_uid"] = $student_uid; + + return $this->loadWhere($where); + } } diff --git a/application/models/education/Notenschluesselaufteilung_model.php b/application/models/education/Notenschluesselaufteilung_model.php index 5e0f2f05c..d48e16b0b 100644 --- a/application/models/education/Notenschluesselaufteilung_model.php +++ b/application/models/education/Notenschluesselaufteilung_model.php @@ -11,4 +11,34 @@ class Notenschluesselaufteilung_model extends DB_Model $this->dbTable = 'lehre.tbl_notenschluesselaufteilung'; $this->pk = 'notenschluesselaufteilung_id'; } + + /** + * Liefert die Note zu Punkten einer Lehrveranstaltung + * + * @param number $points + * @param integer $lehrveranstaltung_id + * @param string $studiensemester_kurzbz + * + * @return stdClass returns success(null) if no entry is found + */ + public function getNote($points, $lehrveranstaltung_id, $studiensemester_kurzbz) + { + $this->load->model('education/Notenschluesselzuordnung_model', 'NotenschluesselzuordnungModel'); + $notenschluessel_kurzbz = $this->NotenschluesselzuordnungModel->getKurzbzForLv($lehrveranstaltung_id, $studiensemester_kurzbz); + + $this->addSelect("note"); + $this->addOrder("punkte", "DESC"); + $this->addLimit(1); + + $result = $this->loadWhere([ + "notenschluessel_kurzbz" => $notenschluessel_kurzbz, + "punkte <=" => $points + ]); + + if (isError($result)) + return $result; + if (!hasData($result)) + return success(null); + return success(current(getData($result))->note); + } } diff --git a/application/models/education/Notenschluesselzuordnung_model.php b/application/models/education/Notenschluesselzuordnung_model.php index e6881e12b..9eb46b290 100644 --- a/application/models/education/Notenschluesselzuordnung_model.php +++ b/application/models/education/Notenschluesselzuordnung_model.php @@ -11,4 +11,71 @@ class Notenschluesselzuordnung_model extends DB_Model $this->dbTable = 'lehre.tbl_notenschluesselzuordnung'; $this->pk = 'notenschluesselzuordnung_id'; } + + /** + * Liefert den passenden Notenschluessel zu einer Lehrveranstaltung + * + * @param integer $lehrveranstaltung_id + * @param string $studiensemester_kurzbz + * + * @return integer|null + */ + public function getKurzbzForLv($lehrveranstaltung_id, $studiensemester_kurzbz) + { + $this->addSelect("notenschluessel_kurzbz"); + + $this->db->where("lehrveranstaltung_id", $lehrveranstaltung_id); + if ($studiensemester_kurzbz) { + $this->db->where("studiensemester_kurzbz", $studiensemester_kurzbz); + $this->db->or_where("studiensemester_kurzbz", null); + } else { + $this->db->where("studiensemester_kurzbz", null); + } + + $result = $this->load(); + + if (!isError($result) && hasData($result)) + return current(getData($result))->notenschluessel_kurzbz; + + + $this->addSelect("notenschluessel_kurzbz"); + + $this->addJoin("( + WITH RECURSIVE oes(oe_kurzbz, oe_parent_kurzbz, depth) AS ( + SELECT oe_kurzbz, oe_parent_kurzbz, 1 + FROM public.tbl_organisationseinheit + WHERE oe_kurzbz = ( + SELECT + oe_kurzbz + FROM + lehre.tbl_lehrveranstaltung + WHERE + lehrveranstaltung_id = " . $this->escape($lehrveranstaltung_id) . " + ) + UNION ALL + SELECT o.oe_kurzbz, o.oe_parent_kurzbz, oes.depth+1 AS depth + FROM public.tbl_organisationseinheit o, oes + WHERE o.oe_kurzbz = oes.oe_parent_kurzbz + AND aktiv = true + ) + SELECT * FROM oes + ) oes", "oe_kurzbz"); + + $this->addOrder("depth", "ASC"); + $this->addLimit(1); + + if ($studiensemester_kurzbz) { + $this->db->where_in("studiensemester_kurzbz", [$studiensemester_kurzbz, null]); + $result = $this->load(); + } else { + $result = $this->loadWhere([ + "studiensemester_kurzbz" => null + ]); + } + + if (isError($result) || !hasData($result)) + return null; + + return current(getData($result))->notenschluessel_kurzbz; + } } diff --git a/application/models/education/Studierendenantraglehrveranstaltung_model.php b/application/models/education/Studierendenantraglehrveranstaltung_model.php index 4318c773e..927343a3e 100644 --- a/application/models/education/Studierendenantraglehrveranstaltung_model.php +++ b/application/models/education/Studierendenantraglehrveranstaltung_model.php @@ -46,6 +46,15 @@ class Studierendenantraglehrveranstaltung_model extends DB_Model } } + /** + * Gets all LVs for a repeating prestudent that are either not allowed or + * already done. + * + * @param string $prestudent_id + * @param string $studiensemester_kurzbz + * + * @return stdClass + */ public function getLvsForPrestudent($prestudent_id, $studiensemester_kurzbz) { $this->addSelect($this->dbTable . '.*'); @@ -66,39 +75,53 @@ class Studierendenantraglehrveranstaltung_model extends DB_Model ); $this->addJoin('public.tbl_student s', 'prestudent_id'); - // NOTE(chris): last offizell note - $this->addJoin('( - SELECT z.* - FROM lehre.tbl_zeugnisnote z - LEFT JOIN public.tbl_studiensemester zs - USING(studiensemester_kurzbz) - JOIN ( - SELECT zi.lehrveranstaltung_id, zi.student_uid, MAX(zis.start) AS start - FROM lehre.tbl_zeugnisnote zi - LEFT JOIN lehre.tbl_note zin - USING(note) - LEFT JOIN public.tbl_studiensemester zis - USING(studiensemester_kurzbz) - WHERE zin.aktiv AND zin.offiziell - GROUP BY zi.lehrveranstaltung_id, zi.student_uid - ) zx - ON ( - z.lehrveranstaltung_id=zx.lehrveranstaltung_id - AND z.student_uid=zx.student_uid - AND zs.start = zx.start - )) z', 'z.lehrveranstaltung_id=lv.lehrveranstaltung_id AND z.student_uid=s.student_uid', 'LEFT'); - $this->addJoin('lehre.tbl_note zn', 'z.note = zn.note', 'LEFT'); - + $this->load->config('studierendenantrag'); $note_intern_angerechntet = $this->config->item('wiederholung_note_angerechnet'); - return $this->loadWhere([ + $where = [ 'ps.prestudent_id' => $prestudent_id, 'a.typ' => Studierendenantrag_model::TYP_WIEDERHOLUNG, 'stat.studierendenantrag_statustyp_kurzbz' => Studierendenantragstatus_model::STATUS_APPROVED, 'n.note <> ' => 0, - $this->dbTable . '.studiensemester_kurzbz' => $studiensemester_kurzbz, - '(n.note<>' . $this->db->escape($note_intern_angerechntet) . ' OR (z.note IS NOT NULL AND zn.positiv))' => null - ]); + // NOTE(chris): grade "intern angerechnet" needs an official grade beforehand (the subquery gets the last positive offical grade) + "(n.note<>" . $this->db->escape($note_intern_angerechntet) . " OR EXISTS ( + SELECT + 1 + FROM + lehre.tbl_zeugnisnote z + LEFT JOIN public.tbl_studiensemester zs USING(studiensemester_kurzbz) + JOIN ( + SELECT + zi.lehrveranstaltung_id, + zi.student_uid, + MAX(zis.start) AS start + FROM + lehre.tbl_zeugnisnote zi + LEFT JOIN lehre.tbl_note zin USING(note) + LEFT JOIN public.tbl_studiensemester zis USING(studiensemester_kurzbz) + WHERE + zin.aktiv + AND zin.offiziell + GROUP BY + zi.lehrveranstaltung_id, + zi.student_uid + ) zx ON ( + z.lehrveranstaltung_id = zx.lehrveranstaltung_id + AND z.student_uid = zx.student_uid + AND zs.start = zx.start + ) + JOIN lehre.tbl_note zn USING (note) + WHERE + z.lehrveranstaltung_id = lv.lehrveranstaltung_id + AND z.student_uid = s.student_uid + AND zn.positiv + ))" => null + ]; + + if ($studiensemester_kurzbz !== false) + $where[$this->dbTable . '.studiensemester_kurzbz'] = $studiensemester_kurzbz; + + return $this->loadWhere($where); } } diff --git a/application/models/education/Zeugnisnote_model.php b/application/models/education/Zeugnisnote_model.php index b4d909e37..f32713dec 100644 --- a/application/models/education/Zeugnisnote_model.php +++ b/application/models/education/Zeugnisnote_model.php @@ -177,45 +177,63 @@ class Zeugnisnote_model extends DB_Model $params[] = $studiensemester_kurzbz; } - $qry = "SELECT vw_student_lehrveranstaltung.lehrveranstaltung_id, uid, - vw_student_lehrveranstaltung.studiensemester_kurzbz, note, punkte, uebernahmedatum, benotungsdatum, - vw_student_lehrveranstaltung.ects, vw_student_lehrveranstaltung.semesterstunden, - tbl_zeugnisnote.updateamum, tbl_zeugnisnote.updatevon, tbl_zeugnisnote.insertamum, - tbl_zeugnisnote.insertvon, tbl_zeugnisnote.ext_id, - vw_student_lehrveranstaltung.bezeichnung as lehrveranstaltung_bezeichnung, - vw_student_lehrveranstaltung.bezeichnung_english as lehrveranstaltung_bezeichnung_english, - tbl_note.bezeichnung as note_bezeichnung, - tbl_note.positiv as note_positiv, - tbl_zeugnisnote.bemerkung as bemerkung, - vw_student_lehrveranstaltung.sort, - vw_student_lehrveranstaltung.zeugnis, - vw_student_lehrveranstaltung.studiengang_kz, - vw_student_lehrveranstaltung.lv_lehrform_kurzbz, - tbl_lehrveranstaltung.sws - FROM - ( - campus.vw_student_lehrveranstaltung LEFT JOIN lehre.tbl_zeugnisnote - ON(uid=student_uid - AND vw_student_lehrveranstaltung.studiensemester_kurzbz=tbl_zeugnisnote.studiensemester_kurzbz - AND vw_student_lehrveranstaltung.lehrveranstaltung_id=tbl_zeugnisnote.lehrveranstaltung_id - ) - ) LEFT JOIN lehre.tbl_note USING(note) - JOIN lehre.tbl_lehrveranstaltung ON(vw_student_lehrveranstaltung.lehrveranstaltung_id=tbl_lehrveranstaltung.lehrveranstaltung_id) - WHERE true $where + $qry = "SELECT + a.*, + lv.lehrform_kurzbz AS lehrveranstaltung_lehrform, + lv.kurzbz AS lehrveranstaltung_kurzbz, + UPPER(stg1.typ || stg1.kurzbz) AS studiengang, + s.studiengang_kz AS studiengang_kz, + UPPER(stg2.typ || stg2.kurzbz) AS studiengang_lv, + lv.studiengang_kz AS studiengang_kz_lv, + lv.semester AS semester_lv, + lv.ects AS ects_lv, + lv.zeugnis, + lv.bezeichnung_english AS lehrveranstaltung_bezeichnung_english + FROM ( + SELECT vw_student_lehrveranstaltung.lehrveranstaltung_id, uid, + vw_student_lehrveranstaltung.studiensemester_kurzbz, note, punkte, uebernahmedatum, benotungsdatum, + vw_student_lehrveranstaltung.ects, vw_student_lehrveranstaltung.semesterstunden, + tbl_zeugnisnote.updateamum, tbl_zeugnisnote.updatevon, tbl_zeugnisnote.insertamum, + tbl_zeugnisnote.insertvon, tbl_zeugnisnote.ext_id, + vw_student_lehrveranstaltung.bezeichnung as lehrveranstaltung_bezeichnung, + vw_student_lehrveranstaltung.bezeichnung_english as lehrveranstaltung_bezeichnung_english, + tbl_note.bezeichnung as note_bezeichnung, + tbl_note.positiv as note_positiv, + tbl_zeugnisnote.bemerkung as bemerkung, + vw_student_lehrveranstaltung.sort, + vw_student_lehrveranstaltung.zeugnis, + vw_student_lehrveranstaltung.studiengang_kz, + vw_student_lehrveranstaltung.lv_lehrform_kurzbz, + tbl_lehrveranstaltung.sws + FROM + ( + campus.vw_student_lehrveranstaltung LEFT JOIN lehre.tbl_zeugnisnote + ON(uid=student_uid + AND vw_student_lehrveranstaltung.studiensemester_kurzbz=tbl_zeugnisnote.studiensemester_kurzbz + AND vw_student_lehrveranstaltung.lehrveranstaltung_id=tbl_zeugnisnote.lehrveranstaltung_id + ) + ) LEFT JOIN lehre.tbl_note USING(note) + JOIN lehre.tbl_lehrveranstaltung ON(vw_student_lehrveranstaltung.lehrveranstaltung_id=tbl_lehrveranstaltung.lehrveranstaltung_id) + WHERE true $where - UNION - SELECT lehre.tbl_lehrveranstaltung.lehrveranstaltung_id,student_uid AS uid,studiensemester_kurzbz, note, punkte, - uebernahmedatum, benotungsdatum,lehre.tbl_lehrveranstaltung.ects,lehre.tbl_lehrveranstaltung.semesterstunden, tbl_zeugnisnote.updateamum, tbl_zeugnisnote.updatevon, tbl_zeugnisnote.insertamum, - tbl_zeugnisnote.insertvon, tbl_zeugnisnote.ext_id, lehre.tbl_lehrveranstaltung.bezeichnung as lehrveranstaltung_bezeichnung, lehre.tbl_lehrveranstaltung.bezeichnung_english as lehrveranstaltung_bezeichnung_english, - tbl_note.bezeichnung as note_bezeichnung, tbl_note.positiv as note_positiv, tbl_zeugnisnote.bemerkung as bemerkung, tbl_lehrveranstaltung.sort, tbl_lehrveranstaltung.zeugnis, tbl_lehrveranstaltung.studiengang_kz, - tbl_lehrveranstaltung.lehrform_kurzbz as lv_lehrform_kurzbz, tbl_lehrveranstaltung.sws - FROM - lehre.tbl_zeugnisnote - JOIN lehre.tbl_lehrveranstaltung USING (lehrveranstaltung_id) - JOIN lehre.tbl_note USING(note) - WHERE true $where2 + UNION + SELECT lehre.tbl_lehrveranstaltung.lehrveranstaltung_id,student_uid AS uid,studiensemester_kurzbz, note, punkte, + uebernahmedatum, benotungsdatum,lehre.tbl_lehrveranstaltung.ects,lehre.tbl_lehrveranstaltung.semesterstunden, tbl_zeugnisnote.updateamum, tbl_zeugnisnote.updatevon, tbl_zeugnisnote.insertamum, + tbl_zeugnisnote.insertvon, tbl_zeugnisnote.ext_id, lehre.tbl_lehrveranstaltung.bezeichnung as lehrveranstaltung_bezeichnung, lehre.tbl_lehrveranstaltung.bezeichnung_english as lehrveranstaltung_bezeichnung_english, + tbl_note.bezeichnung as note_bezeichnung, tbl_note.positiv as note_positiv, tbl_zeugnisnote.bemerkung as bemerkung, tbl_lehrveranstaltung.sort, tbl_lehrveranstaltung.zeugnis, tbl_lehrveranstaltung.studiengang_kz, + tbl_lehrveranstaltung.lehrform_kurzbz as lv_lehrform_kurzbz, tbl_lehrveranstaltung.sws + FROM + lehre.tbl_zeugnisnote + JOIN lehre.tbl_lehrveranstaltung USING (lehrveranstaltung_id) + JOIN lehre.tbl_note USING(note) + WHERE true $where2 - ORDER BY sort"; + ORDER BY sort + ) a + LEFT JOIN public.tbl_student s ON (a.uid = s.student_uid) + LEFT JOIN public.tbl_studiengang stg1 ON (s.studiengang_kz = stg1.studiengang_kz) + LEFT JOIN lehre.tbl_lehrveranstaltung lv USING (lehrveranstaltung_id) + LEFT JOIN public.tbl_studiengang stg2 ON (lv.studiengang_kz = stg2.studiengang_kz)"; return $this->execQuery($qry, $params); } diff --git a/application/models/person/Benutzerfunktion_model.php b/application/models/person/Benutzerfunktion_model.php index 398ca765e..8c43e4f84 100644 --- a/application/models/person/Benutzerfunktion_model.php +++ b/application/models/person/Benutzerfunktion_model.php @@ -50,6 +50,53 @@ class Benutzerfunktion_model extends DB_Model return $this->execQuery($qry, $params); } + /** + * Lädt alle Benutzerfunktionen zu einer UID im Zeitraum eines Studiensemesters + * + * @param string $uid + * @param string $stdsem + * @param string $funktion_kurzbz (optional) + * + * @return stdClass + */ + public function getBenutzerFunktionByUidInStdsem($uid, $stdsem, $funktion_kurzbz = null) + { + $stdsemEscaped = $this->escape($stdsem); + $this->addSelect($this->dbTable . ".*"); + $this->addSelect("oe.bezeichnung AS organisationseinheit_bezeichnung"); + $this->addSelect("oe.organisationseinheittyp_kurzbz"); + + $this->addJoin("public.tbl_organisationseinheit oe", "oe_kurzbz"); + + $this->db->where("uid", $uid); + + if ($funktion_kurzbz !== null) + $this->db->where("funktion_kurzbz", $funktion_kurzbz); + + $this->db->group_start(); + $this->db->where("datum_bis IS NULL"); + $this->db->or_where("datum_bis >=", "( + SELECT start + FROM public.tbl_studiensemester + WHERE studiensemester_kurzbz = " . $stdsemEscaped . " + )", false); + $this->db->group_end(); + + $this->db->group_start(); + $this->db->where("datum_von IS NULL"); + $this->db->or_where("datum_von <=", "( + SELECT ende + FROM public.tbl_studiensemester + WHERE studiensemester_kurzbz = " . $stdsemEscaped . " + )", false); + $this->db->group_end(); + + $this->addOrder("datum_bis", "NULLS LAST"); + $this->addOrder("datum_von", "NULLS LAST"); + + return $this->load(); + } + /** * Get the Benutzerfunktion using the person_id */ diff --git a/application/models/system/Vorlagestudiengang_model.php b/application/models/system/Vorlagestudiengang_model.php index 24af7353c..613d88226 100644 --- a/application/models/system/Vorlagestudiengang_model.php +++ b/application/models/system/Vorlagestudiengang_model.php @@ -11,4 +11,49 @@ class Vorlagestudiengang_model extends DB_Model $this->dbTable = 'public.tbl_vorlagestudiengang'; $this->pk = 'vorlagestudiengang_id'; } + + /** + * Gets the Current Vorlagestudiengang + * + * @param string $vorlage_kurzbz + * @param string $oe_kurzbz Or studiengang_kz + * @param integer $version (optional) + * @param boolean|null $active (optional) + * + * @return stdClass + */ + public function getCurrent($vorlage_kurzbz, $oe_kurzbz, $version = null, $active = true) + { + if (is_numeric($oe_kurzbz)) { + $initselect = "SELECT oe_kurzbz, 1 AS l FROM public.tbl_studiengang WHERE studiengang_kz = " . $this->escape($oe_kurzbz); + } else { + $initselect = "SELECT oe_kurzbz, 1 AS l FROM public.tbl_organisationseinheit WHERE oe_kurzbz = " . $this->escape($oe_kurzbz); + } + + $this->addJoin("( + WITH RECURSIVE tmp (oe_kurzbz, l) AS ( + " . $initselect . " + UNION ALL + SELECT o.oe_parent_kurzbz AS oe_kurzbz, l+1 AS l + FROM tmp + JOIN public.tbl_organisationseinheit o USING (oe_kurzbz) + WHERE o.oe_parent_kurzbz IS NOT NULL + ) SELECT * FROM tmp + ) oe", "oe_kurzbz"); + + if (!is_null($version)) + $this->db->where('version', $version); + if ($active) + $this->db->where('aktiv', true); + + $this->addOrder('l', 'ASC'); + $this->addOrder('version', 'DESC'); + $this->addLimit(1); + + $result = $this->loadWhere([ + 'vorlage_kurzbz' => $vorlage_kurzbz + ]); + + return $result; + } } diff --git a/application/views/templates/FHC-Footer.php b/application/views/templates/FHC-Footer.php index d78e0e9c3..db7565964 100644 --- a/application/views/templates/FHC-Footer.php +++ b/application/views/templates/FHC-Footer.php @@ -150,6 +150,7 @@ generateJSsInclude('vendor/npm-asset/primevue/toastservice/toastservice.min.js'); generateJSsInclude('vendor/npm-asset/primevue/confirmdialog/confirmdialog.min.js'); generateJSsInclude('vendor/npm-asset/primevue/confirmationservice/confirmationservice.min.js'); + generateJSsInclude('vendor/npm-asset/primevue/tieredmenu/tieredmenu.min.js'); } // -------------------------------------------------------------------------------------------------------- diff --git a/public/css/Tabulator5.css b/public/css/Tabulator5.css index aafdb996b..c03ec61bb 100644 --- a/public/css/Tabulator5.css +++ b/public/css/Tabulator5.css @@ -126,9 +126,10 @@ } .tabulator-cell .btn { - padding: 0 .7rem; - min-height: 25px; - min-width: 25px; + padding: 0 .375rem; + font-size: calc(1rem - 2px / 1.5); /* substract border (2 x 1px) modified by the line-height (1.5) */ + border-radius: .2rem; + vertical-align: baseline; } .tabulator-row.tabulator-selectable:focus { @@ -147,3 +148,11 @@ border-color:#ADB5BD !important; box-shadow: none !important; } + +/** + * Make keyboard-focused list items look like the mouse-hovered list items + */ +.tabulator-edit-list .tabulator-edit-list-item.focused { + color: #fff; + background: #1d68cd; +} diff --git a/public/css/components/FilterComponent.css b/public/css/components/FilterComponent.css index 50bfe561e..514fee8f3 100644 --- a/public/css/components/FilterComponent.css +++ b/public/css/components/FilterComponent.css @@ -67,9 +67,5 @@ .tabulator { font-size: 1rem; } -.tabulator-cell .btn { - padding: 0 .375rem; - font-size: .875rem; - border-radius: .2rem; -} + diff --git a/public/css/components/primevue.css b/public/css/components/primevue.css index 6e27bbc10..38b15004b 100644 --- a/public/css/components/primevue.css +++ b/public/css/components/primevue.css @@ -1747,7 +1747,7 @@ } .p-button.p-button-icon-only { width: 2.357rem; - padding: 0.5rem 0; + padding: 0.375rem 0; /* NOTE(chris): button padding-y from BS */ } .p-button.p-button-icon-only .p-button-icon-left, .p-button.p-button-icon-only .p-button-icon-right { diff --git a/public/js/api/stv.js b/public/js/api/stv.js index b1f902678..f6c7831b3 100644 --- a/public/js/api/stv.js +++ b/public/js/api/stv.js @@ -8,6 +8,7 @@ import status from './stv/status.js'; import details from './stv/details.js'; import exam from './stv/exam.js'; import abschlusspruefung from './stv/abschlusspruefung.js'; +import grades from './stv/grades.js'; export default { verband, @@ -20,6 +21,7 @@ export default { details, exam, abschlusspruefung, + grades, configStudent() { return this.$fhcApi.get('api/frontend/v1/stv/config/student'); }, diff --git a/public/js/api/stv/grades.js b/public/js/api/stv/grades.js new file mode 100644 index 000000000..135cd836c --- /dev/null +++ b/public/js/api/stv/grades.js @@ -0,0 +1,72 @@ +export default { + list() { + return this.$fhcApi.get('api/frontend/v1/stv/grades/list'); + }, + getCertificate(prestudent_id, all) { + all = all ? '/all' : ''; + return this.$fhcApi.get('api/frontend/v1/stv/grades/getCertificate/' + prestudent_id + all); + }, + getTeacherProposal(prestudent_id, all) { + all = all ? '/all' : ''; + return this.$fhcApi.get('api/frontend/v1/stv/grades/getTeacherProposal/' + prestudent_id + all); + }, + getRepeaterGrades(prestudent_id, all) { + all = all ? '/all' : ''; + return this.$fhcApi.get('api/frontend/v1/stv/grades/getRepeaterGrades/' + prestudent_id + all); + }, + updateCertificate({lehrveranstaltung_id, student_uid, studiensemester_kurzbz, note, lehrveranstaltung_bezeichnung}) { + return this.$fhcApi.post( + 'api/frontend/v1/stv/grades/updateCertificate', + { + lehrveranstaltung_id, + student_uid, + studiensemester_kurzbz, + note + }, + { + errorHeader: lehrveranstaltung_bezeichnung + } + ); + }, + deleteCertificate({lehrveranstaltung_id, student_uid, studiensemester_kurzbz, lehrveranstaltung_bezeichnung}) { + return this.$fhcApi.post( + 'api/frontend/v1/stv/grades/deleteCertificate', + { + lehrveranstaltung_id, + student_uid, + studiensemester_kurzbz + }, + { + errorHeader: lehrveranstaltung_bezeichnung + } + ); + }, + copyTeacherProposalToCertificate({lehrveranstaltung_id, student_uid, studiensemester_kurzbz, lehrveranstaltung_bezeichnung}) { + return this.$fhcApi.post( + 'api/frontend/v1/stv/grades/copyTeacherProposalToCertificate', + { + lehrveranstaltung_id, + student_uid, + studiensemester_kurzbz + }, + { + errorHeader: lehrveranstaltung_bezeichnung + } + ); + }, + copyRepeaterGradeToCertificate({studierendenantrag_lehrveranstaltung_id, lv_bezeichnung}) { + return this.$fhcApi.post( + 'api/frontend/v1/stv/grades/copyRepeaterGradeToCertificate', + { + studierendenantrag_lehrveranstaltung_id + }, + { + errorHeader: lv_bezeichnung + } + ); + }, + getGradeFromPoints(points, lehrveranstaltung_id, manualErrorHandling) { + const config = manualErrorHandling ? {errorHandling: false} : {}; + return this.$fhcApi.post('api/frontend/v1/stv/grades/getGradeFromPoints', {points, lehrveranstaltung_id}, config); + } +} \ No newline at end of file diff --git a/public/js/components/Stv/Studentenverwaltung/Details.js b/public/js/components/Stv/Studentenverwaltung/Details.js index 4df148063..b42611095 100644 --- a/public/js/components/Stv/Studentenverwaltung/Details.js +++ b/public/js/components/Stv/Studentenverwaltung/Details.js @@ -9,8 +9,8 @@ export default { }, data() { return { - configStudent: null, - configStudents: null + configStudent: {}, + configStudents: {} }; }, props: { @@ -19,6 +19,17 @@ export default { computed: { appRoot() { return FHC_JS_DATA_STORAGE_OBJECT.app_root; + }, + config() { + if (!this.students.length) + return {}; + if (this.students.length == 1) { + const student = this.students[0]; + if (student.uid) + return Object.fromEntries(Object.entries(this.configStudent).filter(([key, value]) => !value.showOnlyWithoutUid)); + return Object.fromEntries(Object.entries(this.configStudent).filter(([key, value]) => !value.showOnlyWithUid)); + } + return this.configStudents; } }, methods: { @@ -53,8 +64,11 @@ export default {

{{students[0].titlepre}} {{students[0].vorname}} {{students[0].nachname}} {{students[0].titlepost}}

- - + + + +
+ Loading...
` }; \ No newline at end of file diff --git a/public/js/components/Stv/Studentenverwaltung/Details/Noten.js b/public/js/components/Stv/Studentenverwaltung/Details/Noten.js index 8e8269ee5..1d7b41d26 100644 --- a/public/js/components/Stv/Studentenverwaltung/Details/Noten.js +++ b/public/js/components/Stv/Studentenverwaltung/Details/Noten.js @@ -1,19 +1,59 @@ import NotenZeugnis from './Noten/Zeugnis.js'; +import NotenTeacher from './Noten/Teacher.js'; +import NotenRepeater from './Noten/Repeater.js'; + +const LOCAL_STORAGE_ID = 'stv_details_noten_2024-11-25_stdsem_all'; export default { components: { - NotenZeugnis + NotenZeugnis, + NotenTeacher, + NotenRepeater + }, + provide() { + return { + config: this.config + } }, props: { - modelValue: Object + modelValue: Object, + config: Object + }, + data() { + return { + stdsem: '' + }; }, methods: { reload() { this.$refs.zeugnis.$refs.table.reloadTable(); + this.$refs.teacher.$refs.table.reloadTable(); + this.$refs.repeater.$refs.table.reloadTable(); + }, + saveStdsem(event) { + window.localStorage.setItem(LOCAL_STORAGE_ID, event.target.value); } }, + created() { + const savedPath = window.localStorage.getItem(LOCAL_STORAGE_ID); + this.stdsem = savedPath || ''; + }, template: ` -
- +
+
+ +
+
+
+ +
+
+ + +
+
` }; \ No newline at end of file diff --git a/public/js/components/Stv/Studentenverwaltung/Details/Noten/Repeater.js b/public/js/components/Stv/Studentenverwaltung/Details/Noten/Repeater.js new file mode 100644 index 000000000..a80b1e4c1 --- /dev/null +++ b/public/js/components/Stv/Studentenverwaltung/Details/Noten/Repeater.js @@ -0,0 +1,107 @@ +import {CoreFilterCmpt} from "../../../../filter/Filter.js"; + +export default { + components: { + CoreFilterCmpt + }, + emits: [ + "copied" + ], + props: { + student: Object, + allSemester: Boolean + }, + data() { + return { + tabulatorEvents: [] + }; + }, + computed: { + tabulatorOptions() { + return { + ajaxURL: 'dummy', + ajaxRequestFunc: (url, config, params) => { + return this.$fhcApi.factory.stv.grades.getRepeaterGrades(params.prestudent_id, params.stdsem); + }, + ajaxParams: () => { + return { + prestudent_id: this.student.prestudent_id, + stdsem: this.allSemester + }; + }, + ajaxResponse: (url, params, response) => { + return response.data || []; + }, + columns: [ + { field: 'lv_bezeichnung', title: this.$p.t('lehre/lehrveranstaltung') }, + { field: 'note_bezeichnung', title: this.$p.t('lehre/note') }, + { field: 'insertvon', title: this.$p.t('profil/mitarbeiterIn'), visible: false }, + { field: 'benotungsdatum', title: this.$p.t('stv/grades_gradingdate'), visible: false }, + { field: 'freigabedatum', title: this.$p.t('stv/grades_approvaldate'), visible: false }, + { field: 'studiensemester_kurzbz', title: this.$p.t('lehre/studiensemester'), visible: false }, + { field: 'stg_bezeichnung', title: this.$p.t('lehre/studiengang'), visible: false }, + { field: 'note', title: this.$p.t('stv/grades_numericgrade'), visible: false }, + { field: 'prestudent_uid', title: this.$p.t('global/prestudentID'), visible: false }, + { field: 'lehrveranstaltung_id', title: this.$p.t('lehre/lehrveranstaltung_id'), visible: false } + ], + layout: 'fitDataStretch', + height: '100%', + selectable: true, + selectableRangeMode: 'click', + persistenceID: 'stv-details-noten-repeater' + }; + } + }, + watch: { + student(n) { + this.$refs.table.reloadTable(); + }, + allSemester(n) { + this.$refs.table.reloadTable(); + } + }, + methods: { + copyGrades(selected) { + const promises = selected.map( + grade => this.$fhcApi.factory + .stv.grades.copyRepeaterGradeToCertificate(grade) + .then(() => { + this.$refs.table.tabulator.deselectRow(this.$refs.table.tabulator.getRows().find(el => el.getData() == grade).getElement()); + }) + ); + Promise + .allSettled(promises) + .then(results => { + if (results.some(res => res.status == "fulfilled")) { + this.$fhcAlert.alertSuccess(this.$p.t('stv/grades_updated')); + this.$emit('copied'); + } + }); + } + }, + created() { + this.$p.loadCategory(['global', 'stv', 'lehre', 'profil']) + .then(() => { + if (this.$refs.table.tableBuilt) + this.$refs.table.tabulator.columnManager.setColumns(this.tabulatorOptions.columns); + }); + }, + template: ` +
+ + + +
` +}; \ No newline at end of file diff --git a/public/js/components/Stv/Studentenverwaltung/Details/Noten/Teacher.js b/public/js/components/Stv/Studentenverwaltung/Details/Noten/Teacher.js new file mode 100644 index 000000000..c9ad0cb2f --- /dev/null +++ b/public/js/components/Stv/Studentenverwaltung/Details/Noten/Teacher.js @@ -0,0 +1,107 @@ +import {CoreFilterCmpt} from "../../../../filter/Filter.js"; + +export default { + components: { + CoreFilterCmpt + }, + emits: [ + "copied" + ], + props: { + student: Object, + allSemester: Boolean + }, + data() { + return { + tabulatorEvents: [] + }; + }, + computed: { + tabulatorOptions() { + return { + ajaxURL: 'dummy', + ajaxRequestFunc: (url, config, params) => { + return this.$fhcApi.factory.stv.grades.getTeacherProposal(params.prestudent_id, params.stdsem); + }, + ajaxParams: () => { + return { + prestudent_id: this.student.prestudent_id, + stdsem: this.allSemester + }; + }, + ajaxResponse: (url, params, response) => { + return response.data || []; + }, + columns: [ + { field: 'lehrveranstaltung_bezeichnung', title: this.$p.t('lehre/lehrveranstaltung') }, + { field: 'note_bezeichnung', title: this.$p.t('lehre/note') }, + { field: 'mitarbeiter_uid', title: this.$p.t('profil/mitarbeiterIn'), visible: false }, + { field: 'benotungsdatum', title: this.$p.t('stv/grades_gradingdate'), visible: false }, + { field: 'freigabedatum', title: this.$p.t('stv/grades_approvaldate'), visible: false }, + { field: 'studiensemester_kurzbz', title: this.$p.t('lehre/studiensemester'), visible: false }, + { field: 'note', title: this.$p.t('stv/grades_numericgrade'), visible: false }, + { field: 'student_uid', title: this.$p.t('profil/studentIn'), visible: false }, + { field: 'lehrveranstaltung_id', title: this.$p.t('lehre/lehrveranstaltung_id'), visible: false }, + { field: 'punkte', title: this.$p.t('stv/grades_points'), visible: false } + ], + layout: 'fitDataStretch', + height: '100%', + selectable: true, + selectableRangeMode: 'click', + persistenceID: 'stv-details-noten-teacher' + }; + } + }, + watch: { + student(n) { + this.$refs.table.reloadTable(); + }, + allSemester(n) { + this.$refs.table.reloadTable(); + } + }, + methods: { + copyGrades(selected) { + const promises = selected.map( + grade => this.$fhcApi.factory + .stv.grades.copyTeacherProposalToCertificate(grade) + .then(() => { + this.$refs.table.tabulator.deselectRow(this.$refs.table.tabulator.getRows().find(el => el.getData() == grade).getElement()); + }) + ); + Promise + .allSettled(promises) + .then(results => { + if (results.some(res => res.status == "fulfilled")) { + this.$fhcAlert.alertSuccess(this.$p.t('stv/grades_updated')); + this.$emit('copied'); + } + }); + } + }, + created() { + this.$p.loadCategory(['stv', 'lehre', 'profil']) + .then(() => { + if (this.$refs.table.tableBuilt) + this.$refs.table.tabulator.columnManager.setColumns(this.tabulatorOptions.columns); + }); + }, + template: ` +
+ + + +
` +}; \ No newline at end of file diff --git a/public/js/components/Stv/Studentenverwaltung/Details/Noten/Zeugnis.js b/public/js/components/Stv/Studentenverwaltung/Details/Noten/Zeugnis.js index caeb0cfaa..6e2038193 100644 --- a/public/js/components/Stv/Studentenverwaltung/Details/Noten/Zeugnis.js +++ b/public/js/components/Stv/Studentenverwaltung/Details/Noten/Zeugnis.js @@ -1,111 +1,271 @@ import {CoreFilterCmpt} from "../../../../filter/Filter.js"; -import {CoreRESTClient} from '../../../../../RESTClient.js'; import ZeugnisActions from './Zeugnis/Actions.js'; - -const LOCAL_STORAGE_ID = 'stv_details_noten_zeugnis_2024-01-11_stdsem_all'; +import ZeugnisDocuments from './Zeugnis/Documents.js'; export default { components: { CoreFilterCmpt, - ZeugnisActions + ZeugnisActions, + ZeugnisDocuments }, + inject: [ + 'config' + ], props: { - student: Object + student: Object, + allSemester: Boolean }, data() { return { - validStudent: true, - tabulatorEvents: [], - stdsem: '' + tabulatorEvents: [ + { + event: "dataLoaded", + handler: data => this.data = data.map(item => { + item.documentslist = document.createElement("div"); + return item; + }) + }, + { + event: "rowSelected", + handler: row => row.getElement().style.zIndex = 12 + }, + { + event: "rowDeselected", + handler: row => row.getElement().style.zIndex = '' + } + ], + stdsem: '', + lastGradeList: [], + lastClickedRow: null, + data: [] }; }, computed: { - ajaxURL() { - return CoreRESTClient._generateRouterURI('components/stv/Noten/getZeugnis/' + this.student.prestudent_id + this.stdsem); - }, tabulatorOptions() { + const listPromise = this.$fhcApi.factory + .stv.grades.list() + .then(res => res.data.map(({bezeichnung: label, note: value}) => ({label, value}))); + + let gradeField = { + field: 'note', + title: this.$p.t('lehre/note'), + formatter: cell => cell.getData().note_bezeichnung, + tooltip: (evt, cell) => cell.getData().note_bezeichnung + }; + if (['both', 'inline'].includes(this.config.edit)) { + gradeField.editor = 'list'; + gradeField.cellEdited = cell => { + // get changed value + const note = cell.getValue(); + if (note === '') + return; + + // get row data + const {lehrveranstaltung_id, uid: student_uid, studiensemester_kurzbz} = cell.getData(); + + listPromise + // get bezeichnung + .then(list => list.find(el => el.value == note)) + .then(found => found ? found.label : Promise.reject({message: 'grade ' + note + ' not found in list'})) + // prepare data object + .then(note_bezeichnung => ({ + lehrveranstaltung_id, + student_uid, + studiensemester_kurzbz, + note, + note_bezeichnung + })) + // send to backend + .then(this.$fhcApi.factory.stv.grades.updateCertificate) + // get bezeichnung again + .then(() => listPromise) + .then(list => list.find(el => el.value == note)) + .then(found => found?.label) + // update other fields in row + .then(note_bezeichnung => cell.getRow().update({note_bezeichnung})) + .then(() => cell.getRow().reformat()) + // cleanup + .then(cell.clearEdited) + .then(() => this.$fhcAlert.alertSuccess(this.$p.t('stv/grades_updated'))) + .catch(err => { + cell.restoreOldValue(); + cell.clearEdited(); + this.$fhcAlert.handleFormValidation(err); + }); + }; + if (this.config.usePoints) { + gradeField.editorParams = { + valuesLookup: (cell, filterTerm) => { + if (filterTerm) { + return this.$fhcApi.factory + .stv.grades.getGradeFromPoints(filterTerm, cell.getData().lehrveranstaltung_id, true) + .then(result => + result.data === null + ? [] + : listPromise.then(res => res.filter(grade => grade.value === result.data)) + ) + .catch(err => []); + } + return listPromise; + }, + autocomplete: true, + filterRemote: true, + allowEmpty: true, + listOnEmpty: true + }; + gradeField.cellEditing = cell => cell.setValue(''); + gradeField.cellEditCancelled = cell => cell; + } else { + gradeField.editorParams = { + valuesLookup: (cell, filterTerm) => listPromise + }; + } + const node = document.createElement('span'); + this.$p.loadCategory('stv') + .then(() => node.innerText = this.$p.t('ui/loading')) + .catch(this.$fhcAlert.handleSystemError); + gradeField.editorParams.placeholderLoading = node; + } + + const columns = [ + { field: 'zeugnis', title: this.$p.t('stv/grades_zeugnis'), formatter: 'tickCross' }, + { field: 'lehrveranstaltung_bezeichnung', title: this.$p.t('lehre/lehrveranstaltung') }, + gradeField, + { field: 'uebernahmedatum', title: this.$p.t('stv/grades_takeoverdate'), visible: false }, + { field: 'benotungsdatum', title: this.$p.t('stv/grades_gradingdate'), visible: false }, + { field: 'studiensemester_kurzbz', title: this.$p.t('lehre/studiensemester'), visible: false }, + { field: 'note_number', title: this.$p.t('stv/grades_numericgrade'), visible: false, formatter: cell => cell.getData().note, tooltip: (evt, cell) => cell.getData().note }, + { field: 'lehrveranstaltung_id', title: this.$p.t('lehre/lehrveranstaltung_id'), visible: false }, + { field: 'studiengang', title: this.$p.t('lehre/studiengang'), visible: false }, + { field: 'studiengang_kz', title: this.$p.t('lehre/studiengangskennzahlLehre'), visible: false }, + { field: 'studiengang_lv', title: this.$p.t('stv/grades_studiengang_lv'), visible: false }, + { field: 'studiengang_kz_lv', title: this.$p.t('stv/grades_studiengang_kz_lv'), visible: false }, + { field: 'semester_lv', title: this.$p.t('stv/grades_semester_lv'), visible: false }, + { field: 'ects_lv', title: this.$p.t('lehre/ects'), visible: false }, + { field: 'lehrform', title: this.$p.t('lehre/lehrform'), visible: false }, + { field: 'kurzbz', title: this.$p.t('lehre/kurzbz'), visible: false }, + { field: 'punkte', title: this.$p.t('stv/grades_points'), visible: false }, + { field: 'lehrveranstaltung_bezeichnung_english', title: this.$p.t('stv/grades_lehrveranstaltung_bezeichnung_english'), visible: false } + ]; + + const hasDocuments = ['both', 'inline'].includes(this.config.documents); + const hasDelete = ['both', 'inline'].includes(this.config.delete); + + if (hasDocuments || hasDelete) { + columns.push({ + field: 'actions', + title: this.$p.t('global/actions'), + cssClass: "overflow-visible", + headerSort: false, + formatter: cell => { + // get row data + const data = cell.getData(); + data.student_uid = data.uid; + + let container = document.createElement('div'); + container.className = "d-flex gap-2 justify-content-end"; + + if (hasDocuments) { + container.append(data.documentslist); + } + + if (hasDelete) { + let deleteButton = document.createElement('button'); + deleteButton.className = 'btn btn-outline-secondary'; + const icon = document.createElement('i'); + icon.className = 'fa fa-trash'; + icon.title = this.$p.t('ui/loeschen'); + deleteButton.append(icon); + deleteButton.addEventListener('click', evt => { + evt.stopPropagation(); + this.deleteGrade(data); + }); + container.append(deleteButton); + } + + return container; + }, + frozen: true + }); + } + return { - ajaxURL: this.ajaxURL, - ajaxResponse: (url, params, response) => { - if (!response.retval) - this.validStudent = false; - else - this.validStudent = true; - return response.retval || []; + ajaxURL: 'dummy', + ajaxRequestFunc: (url, config, params) => { + return this.$fhcApi.factory.stv.grades.getCertificate(params.prestudent_id, params.stdsem); }, - columns: [ - { field: 'zeugnis', title: 'Zeugnis', formatter: 'tickCross' }, - { field: 'lehrveranstaltung_bezeichnung', title: 'Lehrveranstaltung' }, - { field: 'note_bezeichnung', title: 'Note' }, - { field: 'uebernahmedatum', title: 'Übernahmedatum', visible: false }, - { field: 'benotungsdatum', title: 'Benotungsdatum', visible: false }, - { field: 'benotungsdatum-iso', title: 'Benotungsdatum ISO', visible: false }, - { field: 'studiensemester_kurzbz', title: 'Studiensemester', visible: false }, - { field: 'note', title: 'Note Numerisch', visible: false }, - { field: 'lehrveranstaltung_id', title: 'Lehrveranstaltung ID', visible: false }, - { field: 'studiengang', title: 'Studiengang', visible: false }, - { field: 'studiengang_kz', title: 'Studiengang Kennzahl', visible: false }, - { field: 'studiengang_lv', title: 'StudiengangLV', visible: false }, - { field: 'studiengang_kz_lv', title: 'Studiengang_kzLV', visible: false }, - { field: 'semester_lv', title: 'SemesterLV', visible: false }, - { field: 'ects_lv', title: 'ECTS', visible: false }, - { field: 'lehrform', title: 'Lehrform', visible: false }, - { field: 'kurzbz', title: 'Kurzbz', visible: false }, - { field: 'punkte', title: 'Punkte', visible: false }, - { field: 'lehrveranstaltung_bezeichnung_english', title: 'Englisch', visible: false } - ], - layout: 'fitDataStretch', + ajaxParams: () => { + return { + prestudent_id: this.student.prestudent_id, + stdsem: this.allSemester + }; + }, + ajaxResponse: (url, params, response) => { + return response.data || []; + }, + columns, height: '100%', - selectable: true, + selectable: 1, selectableRangeMode: 'click', persistenceID: 'stv-details-noten-zeugnis' }; } }, watch: { - ajaxURL(n) { - if (this.$refs.table) - this.$refs.table.tabulator.setData(n); + student(n) { + this.$refs.table.reloadTable(); + }, + allSemester(n) { + this.$refs.table.reloadTable(); } }, methods: { - setGrades(selected) { - CoreRESTClient - .post('components/stv/Noten/update', selected) + setGrade(data) { + this.$fhcApi.factory + .stv.grades.updateCertificate(data) .then(this.$refs.table.reloadTable) + .then(() => this.$fhcAlert.alertSuccess(this.$p.t('stv/grades_updated'))) .catch(this.$fhcAlert.handleFormValidation); }, - saveStdsem(event) { - window.localStorage.setItem(LOCAL_STORAGE_ID, event.target.value ? 'true' : ''); + deleteGrade(data) { + // NOTE(chris): There is no check if there is an entry to be deleted, but it works anyway + return this.$fhcAlert + .confirmDelete() + .then(result => result ? data : Promise.reject({handled:true})) + .then(this.$fhcApi.factory.stv.grades.deleteCertificate) + .then(this.$refs.table.reloadTable) + .then(() => this.$fhcAlert.alertSuccess(this.$p.t('ui/successDelete'))) + .catch(this.$fhcAlert.handleSystemError); } }, created() { - const savedPath = window.localStorage.getItem(LOCAL_STORAGE_ID); - this.stdsem = savedPath ? '/all' : ''; + this.$p.loadCategory(['global', 'stv', 'lehre']) + .then(() => { + if (this.$refs.table.tableBuilt) + this.$refs.table.tabulator.columnManager.setColumns(this.tabulatorOptions.columns); + }); }, - // TODO(chris): phrasen template: `
-
Kein Student
- + + + + + +
` }; \ No newline at end of file diff --git a/public/js/components/Stv/Studentenverwaltung/Details/Noten/Zeugnis/Actions.js b/public/js/components/Stv/Studentenverwaltung/Details/Noten/Zeugnis/Actions.js index 7e8e2cbe5..b55573549 100644 --- a/public/js/components/Stv/Studentenverwaltung/Details/Noten/Zeugnis/Actions.js +++ b/public/js/components/Stv/Studentenverwaltung/Details/Noten/Zeugnis/Actions.js @@ -1,54 +1,137 @@ -import {CoreRESTClient} from '../../../../../../RESTClient.js'; +import CoreForm from '../../../../../Form/Form.js'; +import FormInput from '../../../../../Form/Input.js'; +import ZeugnisDocuments from './Documents.js'; + export default { + components: { + CoreForm, + FormInput, + ZeugnisDocuments + }, emits: [ - 'setGrades' + 'setGrade', + 'deleteGrade' + ], + inject: [ + 'config' ], props: { selected: Array }, data() { return { - grades: [] + grades: [], + suggestions: null, + currentPoints: '' }; }, computed: { + selectedData() { + return this.selected.map(zeugnis => { + const { lehrveranstaltung_id, uid: student_uid, studiensemester_kurzbz } = zeugnis; + return { lehrveranstaltung_id, student_uid, studiensemester_kurzbz }; + }) + }, current: { get() { if (!this.selected.length) return ''; if (this.selected.length == 1) - return this.selected[0].note; + return this.selected.find(Boolean).note; const grades = Object.keys(this.selected.reduce((a,c) => { a[c.note] = true; return a; }, {})); if (grades.length == 1) - return grades[0]; + return grades.find(Boolean); return ''; }, set(note) { - this.$emit('setGrades', this.selected.map(zeugnis => { - const { lehrveranstaltung_id, uid: student_uid, studiensemester_kurzbz } = zeugnis; - return { lehrveranstaltung_id, student_uid, studiensemester_kurzbz, note }; - })); + this.selectedData.forEach(data => this.$emit('setGrade', {...data, ...{note}})); } + }, + currentLabel() { + if (this.current == '') + return this.$p.t('stv/grades_setgrade'); + return this.grades.find(grade => grade.note === this.current)?.bezeichnung || ''; + } + }, + methods: { + convertPoints({evt, query}) { + if (!query) { + return this.suggestions = this.grades; + } + this.$refs.points.factory + .stv.grades.getGradeFromPoints(query, this.selected.find(Boolean)?.lehrveranstaltung_id) + .then(result => { + if (result.data === null) { + this.suggestions = []; + return result; + } + this.suggestions = this.grades.filter(grade => grade.note == result.data); + }) + .catch(this.$fhcAlert.handleSystemError); + }, + setPoints({evt, value: {note}}) { + if (this.selected) + this.selected.forEach(grade => grade.note = note); + this.currentPoints = ''; + this.current = note; + }, + deleteGrades() { + this.selectedData.forEach(data => this.$emit('deleteGrade', data)); } }, created() { - CoreRESTClient - .get('components/stv/Noten/get') - .then(result => result.data) + this.$fhcApi.factory + .stv.grades.list() .then(result => { - this.grades = result.retval; + this.grades = result.data; }) .catch(this.$fhcAlert.handleSystemError); }, template: ` -
- +
+ + + +
` }; \ No newline at end of file diff --git a/public/js/components/Stv/Studentenverwaltung/Details/Noten/Zeugnis/Documents.js b/public/js/components/Stv/Studentenverwaltung/Details/Noten/Zeugnis/Documents.js new file mode 100644 index 000000000..4a02e9dfa --- /dev/null +++ b/public/js/components/Stv/Studentenverwaltung/Details/Noten/Zeugnis/Documents.js @@ -0,0 +1,97 @@ +// NOTE(chris): cache calls globally to prevent multiple calls on the same source +const calledPermissionUrls = {}; +async function callPermissionUrl($fhcApi, url) { + if (!calledPermissionUrls[url]) { + calledPermissionUrls[url] = $fhcApi.get(url); + } + return (await calledPermissionUrls[url]).data; +} + +export default { + components: { + PvTieredMenu: primevue.tieredmenu + }, + props: { + list: Array, + data: Object + }, + data() { + return { + filteredList: [] + }; + }, + watch: { + async data() { + this.filteredList = await this.copyListPart(this.list); + } + }, + methods: { + addParamsToString(link) { + for (var k in this.data) + link = link.replace("{" + k + "}", this.data[k]); + return link; + }, + async copyListPart(list) { + let result = [], res; + + for (const part of list) { + if (part.permissioncheck) { + if (!this.data) + continue; + let permissioncheckUrl = part.permissioncheck; + for (const k in this.data) { + permissioncheckUrl = permissioncheckUrl.replace("{" + k + "}", this.data[k]); + } + if (!await callPermissionUrl(this.$fhcApi, permissioncheckUrl)) + continue; + } + const item = {label: part.title}; + if (part.children) + item.items = await this.copyListPart(part.children); + if (!item.items || !item.items.length) { + if (part.action && part.action.url) { + item.command = () => { + const post = {}; + if (part.action.post) + Object.entries(part.action.post) + .forEach( + ([key, value]) => + post[key] = value[0] == '{' + ? this.data[value.slice(1,-1)] + : value + ); + this.$fhcApi + .post(this.addParamsToString(part.action.url), post) + .then(() => part.action.response || this.$p.t('ui/successSave')) + .then(this.$fhcAlert.alertSuccess) + .catch(this.$fhcAlert.handleSystemError); + }; + } else if(part.link) { + item.url = this.addParamsToString(part.link); + item.target = '_blank'; + } + } + result.push(item); + } + + return result; + } + }, + async created() { + this.filteredList = await this.copyListPart(this.list); + }, + template: ` +
+ +
` +}; \ No newline at end of file diff --git a/public/js/components/filter/Filter.js b/public/js/components/filter/Filter.js index 5520b10c9..aa6d29a09 100644 --- a/public/js/components/filter/Filter.js +++ b/public/js/components/filter/Filter.js @@ -22,6 +22,8 @@ import TableDownload from './Table/Download.js'; import collapseAutoClose from '../../directives/collapseAutoClose.js'; import { defaultHeaderFilter } from '../../tabulator/filters/defaultHeaderFilter.js'; +import moduleLayoutFitDataStretchFrozen from '../../tabulator/layouts/fitDataStretchFrozen.js'; + // const FILTER_COMPONENT_NEW_FILTER = 'Filter Component New Filter'; const FILTER_COMPONENT_NEW_FILTER_TYPE = 'Filter Component New Filter Type'; @@ -205,7 +207,7 @@ export const CoreFilterCmpt = { // Define a default tabulator options in case it was not provided let tabulatorOptions = {...{ height: 500, - layout: "fitDataStretch", + layout: "fitDataStretchFrozen", movableColumns: true, columnDefaults:{ tooltip: true, diff --git a/public/js/plugin/FhcAlert.js b/public/js/plugin/FhcAlert.js index fc3c3bace..07c13dde7 100644 --- a/public/js/plugin/FhcAlert.js +++ b/public/js/plugin/FhcAlert.js @@ -215,9 +215,9 @@ export default { header: 'Achtung', message: 'Möchten Sie sicher löschen?', acceptLabel: 'Löschen', - acceptClass: 'btn btn-danger', + acceptClass: 'p-button-danger', rejectLabel: 'Abbrechen', - rejectClass: 'btn btn-outline-secondary', + rejectClass: 'p-button-secondary', accept() { resolve(true); }, diff --git a/public/js/tabulator/layouts/fitDataStretchFrozen.js b/public/js/tabulator/layouts/fitDataStretchFrozen.js new file mode 100644 index 000000000..1e223de90 --- /dev/null +++ b/public/js/tabulator/layouts/fitDataStretchFrozen.js @@ -0,0 +1,50 @@ +/** + * This may need changes if Tabulator gets updated! + * + * Current working Version: 5.5.2 + * + * This is a copy of the fitDataStretch function. The only difference + * is the check for frozen columns on line 22. + */ + +export default window.Tabulator?.extendModule("layout", "modes", { + fitDataStretchFrozen(columns, forced) { + var colsWidth = 0, + tableWidth = this.table.rowManager.element.clientWidth, + gap = 0, + lastCol = false; + + columns.forEach((column, i) => { + if (!column.widthFixed) { + column.reinitializeWidth(); + } + + if (this.table.options.responsiveLayout ? column.modules.responsive.visible : column.visible && !column.definition.frozen) { + lastCol = column; + } + + if (column.visible) { + colsWidth += column.getWidth(); + } + }); + + if (lastCol){ + gap = tableWidth - colsWidth + lastCol.getWidth(); + + if (this.table.options.responsiveLayout && this.table.modExists("responsiveLayout", true)) { + lastCol.setWidth(0); + this.table.modules.responsiveLayout.update(); + } + + if (gap > 0) { + lastCol.setWidth(gap); + } else { + lastCol.reinitializeWidth(); + } + } else { + if (this.table.options.responsiveLayout && this.table.modExists("responsiveLayout", true)) { + this.table.modules.responsiveLayout.update(); + } + } + } +}); diff --git a/system/phrasesupdate.php b/system/phrasesupdate.php index ca32bea32..d8e93ce2a 100644 --- a/system/phrasesupdate.php +++ b/system/phrasesupdate.php @@ -1273,8 +1273,48 @@ $phrases = array( ) ) ), + array( + 'app' => 'core', + 'category' => 'global', + 'phrase' => 'unknown_error', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Ein unbekannter Fehler ist aufgetreten: {error}', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'An unknown error occurred: {error}', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), //******************************* CORE/ui + array( + 'app' => 'core', + 'category' => 'ui', + 'phrase' => 'loading', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Lädt...', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Loading...', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), array( 'app' => 'core', 'category' => 'ui', @@ -3084,6 +3124,26 @@ $phrases = array( ) ) ), + array( + 'app' => 'core', + 'category' => 'lehre', + 'phrase' => 'lehrveranstaltung_id', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Lehrveranstaltung ID', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Course ID', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), array( 'app' => 'core', 'category' => 'lehre', @@ -9596,6 +9656,46 @@ Any unusual occurrences ) ) ), + array( + 'app' => 'core', + 'category' => 'ui', + 'phrase' => 'all_semester', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Alle Semester', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'All semester', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'ui', + 'phrase' => 'current_semester', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Aktuelles Semester', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Current semester', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), array( 'app' => 'core', 'category' => 'ui', @@ -36212,6 +36312,810 @@ array( ) ) ), + // document + array( + 'app' => 'core', + 'category' => 'stv', + 'phrase' => 'document_certificate', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Zertifikat', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Certificate', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'stv', + 'phrase' => 'document_coursecertificate', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'LV Zeugnis', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Course certificate', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'stv', + 'phrase' => 'document_download', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Download', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Download', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'stv', + 'phrase' => 'document_archive', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Archivieren', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Archive', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'stv', + 'phrase' => 'document_signed_and_archived', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Dokument signiert und archiviert', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Document signed and archived', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + // grades + array( + 'app' => 'core', + 'category' => 'stv', + 'phrase' => 'grades_zeugnis', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Zeugnis', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Certificate', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'stv', + 'phrase' => 'grades_takeoverdate', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Übernahmedatum', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Takeover date', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'stv', + 'phrase' => 'grades_gradingdate', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Benotungsdatum', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Grading date', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'stv', + 'phrase' => 'grades_approvaldate', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Freigabedatum', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Approval date', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'stv', + 'phrase' => 'grades_numericgrade', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Note (Numerisch)', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Grade (numeric)', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'stv', + 'phrase' => 'grades_studiengang_lv', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Studiengang (Lehrveranstaltung)', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Degree-program (Course)', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'stv', + 'phrase' => 'grades_studiengang_kz_lv', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Studiengangskennzahl (Lehrveranstaltung)', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Study program number (Course)', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'stv', + 'phrase' => 'grades_semester_lv', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Semester (Lehrveranstaltung)', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Semester (Course)', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'stv', + 'phrase' => 'grades_points', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Punkte', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Points', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'stv', + 'phrase' => 'grades_lehrveranstaltung_bezeichnung_english', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Lehrveranstaltungsname Englisch', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Coursename english', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'stv', + 'phrase' => 'grades_setgrade', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Note setzen', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Set grade', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'stv', + 'phrase' => 'grades_updated', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Note gesetzt', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Grade set', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'stv', + 'phrase' => 'grades_title_zeugnis', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Zeugnis', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Certificate', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'stv', + 'phrase' => 'grades_title_teacher', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'LektorIn', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Lector', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'stv', + 'phrase' => 'grades_title_repeater', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Wiederholer', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Repeater', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'stv', + 'phrase' => 'grades_action_copy', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Übernehmen', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Transfer', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'stv', + 'phrase' => 'grades_error_sign', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Diese Vorlage darf nicht signiert werden', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'This template must not be signed', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'stv', + 'phrase' => 'grades_error_archive', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Dieses Dokument ist nicht archivierbar', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'This document cannot be archived', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'stv', + 'phrase' => 'grades_error_overwrite', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Nicht überschreibbar', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Not overwritable', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'stv', + 'phrase' => 'grades_error_lehreinheit_id', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Fehler beim Ermitteln der Lehreinheit ID', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Error determining the Teaching Unit ID', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'stv', + 'phrase' => 'grades_error_prestudentstatus', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'StudentIn hat keinen Status in diesem Semester', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Student has no status this semester', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + + //**************************** CORE/document_export + array( + 'app' => 'core', + 'category' => 'document_export', + 'phrase' => 'error_unoconv', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Unoconv nicht gefunden - Bitte installieren sie Unoconv', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Unoconv not found - Please install Unoconv', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'document_export', + 'phrase' => 'error_unoconv_version', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Unoconv Version konnte nicht ermittelt werden', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Could not get Unoconv Version', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'document_export', + 'phrase' => 'error_conv_timeout', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Dokumentenkonvertierung ist derzeit nicht möglich. Bitte versuchen Sie es in einer Minute erneut oder kontaktieren Sie einen Administrator', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Document conversion is currently not possible. Please try again in a minute or contact an administrator', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'document_export', + 'phrase' => 'error_sign_timeout', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Signaturserver ist derzeit nicht erreichbar', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Signature server is currently unavailable', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'document_export', + 'phrase' => 'error_sign_pdf', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Derzeit können nur PDFs signiert werden', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Currently only PDFs can be signed', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'document_export', + 'phrase' => 'error_outputformat_missing', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Ausgabeformat fehlt', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Outputformat is not defined', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'document_export', + 'phrase' => 'error_template_missing', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Keine Vorlage gefunden', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'No template found', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'document_export', + 'phrase' => 'error_headers', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Header wurden bereits gesendet -> Abbruch', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Header already sent -> Abort', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'document_export', + 'phrase' => 'error_file_copy', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Kopieren fehlgeschlagen', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Copy failed', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'document_export', + 'phrase' => 'error_file_load', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Datei konnte nicht geladen werden', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Unable to load file', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'document_export', + 'phrase' => 'error_xml_load', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'XML konnte nicht geladen werden: {url} XML: {xml} PARAMETER: {params}', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Unable to load xml: {url} XML: {xml} PARAMS: {params}', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'document_export', + 'phrase' => 'error_xsl_load', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'XSL konnte nicht geladen werden', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Unable to load xsl', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'document_export', + 'phrase' => 'error_styles_load', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Styles XSL konnte nicht geladen werden', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Unable to load styles xsl', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'document_export', + 'phrase' => 'error_manifest', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Manifest ungültig', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Manifest file invalid', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), //**************************** FHC-Core-SAP array(