From 6a67eb750e1de110f65e929a0be1306b9e0766f6 Mon Sep 17 00:00:00 2001 From: Johann Hoffmann Date: Thu, 6 Aug 2026 14:11:44 +0200 Subject: [PATCH] check for schlagwoerter length in zusatzdaten frontend/backend - likely the cause of finished projects with missing data --- .../controllers/api/frontend/v1/Abgabe.php | 121 ++++++++++++++---- .../Cis/Abgabetool/AbgabeMitarbeiterDetail.js | 17 ++- .../Cis/Abgabetool/AbgabeStudentDetail.js | 21 ++- system/phrasesupdate.php | 120 +++++++++++++++++ 4 files changed, 240 insertions(+), 39 deletions(-) diff --git a/application/controllers/api/frontend/v1/Abgabe.php b/application/controllers/api/frontend/v1/Abgabe.php index d58874096..234f5c54d 100644 --- a/application/controllers/api/frontend/v1/Abgabe.php +++ b/application/controllers/api/frontend/v1/Abgabe.php @@ -22,6 +22,10 @@ use CI3_Events as Events; class Abgabe extends FHCAPI_Controller { + // col limits of lehre.tbl_projektarbeit, exceeding them aborts the zusatzdaten update and lead to unpublishable enduploads + const MAX_LEN_SCHLAGWOERTER = 150; // varchar(150) + const MAX_LEN_ABSTRACT = 5000; // text, capped to match the ui counter + const MAX_SEITENANZAHL = 32767; // smallint /** * Object initialization @@ -326,6 +330,10 @@ class Abgabe extends FHCAPI_Controller // block uploads for quality gate termine that are already graded $this->checkPaabgabeForGradedStatus($paabgabe_id); + // the posted typ is client state only - verify it against the row so a stale ui cannot push an + // endabgabe through here, which would mark the abgabe without ever writing the zusatzdaten + $this->checkPaabgabeTypForZwischenabgabe($paabgabe_id, $paabgabetyp_kurzbz); + // load the $student_uid by $projektarbeit_id so we dont need any post params $this->load->model('education/Projektarbeit_model', 'ProjektarbeitModel'); $res = $this->ProjektarbeitModel->getStudentInfoForProjektarbeitId($projektarbeit_id); @@ -403,8 +411,10 @@ class Abgabe extends FHCAPI_Controller $this->terminateWithError($this->p->t('global', 'wrongParameters'), 'general'); } + $this->checkZusatzdatenLengths($schlagwoerter, $schlagwoerter_en, $abstract, $abstract_en, $seitenanzahl); + $this->checkPaabgabeDeadline($paabgabe_id); - + $this->checkProjektarbeitForFinishedStatus($projektarbeit_id); // load the $student_uid by $projektarbeit_id so we dont need any post params @@ -453,17 +463,25 @@ class Abgabe extends FHCAPI_Controller $this->checkAbgabeSignatur($paabgabe, $projektarbeit->student_uid); $signaturstatus = $paabgabe->signatur; - // update projektarbeit cols with zusatzdaten AND abgabedatum! - $this->ProjektarbeitModel->update($projektarbeit->projektarbeit_id, array( - 'sprache' => $sprache, - 'seitenanzahl' => $seitenanzahl, - 'abgabedatum' => date('Y-m-d'), - 'schlagwoerter_en' => $schlagwoerter_en, - 'schlagwoerter' => $schlagwoerter, - 'abstract' => $abstract, - 'abstract_en' => $abstract_en - )); + // update projektarbeit cols with zusatzdaten AND abgabedatum! + $updateRes = $this->ProjektarbeitModel->update($projektarbeit->projektarbeit_id, array( + 'sprache' => $sprache, + 'seitenanzahl' => $seitenanzahl, + 'abgabedatum' => date('Y-m-d'), + 'schlagwoerter_en' => $schlagwoerter_en, + 'schlagwoerter' => $schlagwoerter, + 'abstract' => $abstract, + 'abstract_en' => $abstract_en + )); + // abort before marking the abgabe - a failed update used to leave the projektarbeit without + // abgabedatum and zusatzdaten while the endupload counted as done, blocking the publication + if(isError($updateRes)) { + $this->logLib->logInfoDB(array('endupload projektarbeit update failed', getError($updateRes), + $projektarbeit_id, $paabgabe_id, getAuthUID(), getAuthPersonId())); + + $this->terminateWithError($this->p->t('abgabetool', 'c4enduploadZusatzdatenSpeichernFehlgeschlagen'), 'general'); + } // update paabgabe datum $res = $this->PaabgabeModel->update($paabgabe_id, array( @@ -1070,6 +1088,22 @@ class Abgabe extends FHCAPI_Controller $this->terminateWithError($this->p->t('global', 'wrongParameters'), 'general'); } + // a termin with an upload or a note must keep its typ and datum. + // the table only hides those rows client side, so a student upload + // after the table was loaded would otherwise still let a zwischenabgabe be retyped to 'end' + $existingPaabgabe = $paabgabeArr[0]; + + if($existingPaabgabe->note !== null || $existingPaabgabe->abgabedatum !== null) { + if(isset($updateFields['paabgabetyp_kurzbz']) + && $updateFields['paabgabetyp_kurzbz'] !== $existingPaabgabe->paabgabetyp_kurzbz) { + $this->terminateWithError($this->p->t('abgabetool', 'c4abgabetypAendernNichtErlaubt')); + } + + if(isset($updateFields['datum']) && $updateFields['datum'] !== $existingPaabgabe->datum) { + $this->terminateWithError($this->p->t('abgabetool', 'c4datumAendernNichtErlaubt')); + } + } + $result = $this->PaabgabeModel->update( $paabgabe_id, array_merge($updateFields, [ @@ -1583,7 +1617,9 @@ class Abgabe extends FHCAPI_Controller $this->terminateWithError($this->p->t('global', 'wrongParameters'), 'general'); } - + + $this->checkZusatzdatenLengths($schlagwoerter, $schlagwoerter_en, $abstract, $abstract_en, $seitenanzahl); + $this->load->model('education/Projektarbeit_model', 'ProjektarbeitModel'); @@ -1618,7 +1654,7 @@ class Abgabe extends FHCAPI_Controller } // update projektarbeit cols with zusatzdaten only - $this->ProjektarbeitModel->update($projektarbeit_id, array( + $updateRes = $this->ProjektarbeitModel->update($projektarbeit_id, array( 'sprache' => $sprache, 'seitenanzahl' => $seitenanzahl, 'schlagwoerter_en' => $schlagwoerter_en, @@ -1627,6 +1663,9 @@ class Abgabe extends FHCAPI_Controller 'abstract_en' => $abstract_en )); + // dont report success on a failed update, otherwise a repair attempt silently changes nothing + $this->getDataOrTerminateWithError($updateRes, 'general'); + $this->logLib->logInfoDB(array('zusatzdatenEditMitarbeiter', array( 'updatevon' => getAuthUID(), 'updateamum' => date('Y-m-d H:i:s') @@ -2009,16 +2048,48 @@ class Abgabe extends FHCAPI_Controller $data = getData($res)[0]; if($data->note !== NULL) { - // hardcode this error msg cause phrasen arent always being updated - $message = $this->p->t('abgabetool','c4fehlerAktualitaetProjektarbeitv3'); - if(strpos($message, "<<") === 0) { // phrase could not be loaded - $this->terminateWithError('Die Projektarbeit wurde bereits benotet, Sie dürfen deshalb keine weiteren Termine anlegen oder bearbeiten.', 'general'); - } else { - $this->terminateWithError($message); - } + $this->terminateWithError($this->p->t('abgabetool','c4fehlerAktualitaetProjektarbeitv3')); } } - + + // validates the zusatzdaten against the column limits of lehre.tbl_projektarbeit. + private function checkZusatzdatenLengths($schlagwoerter, $schlagwoerter_en, $abstract, $abstract_en, $seitenanzahl) { + if(mb_strlen((string)$schlagwoerter) > self::MAX_LEN_SCHLAGWOERTER + || mb_strlen((string)$schlagwoerter_en) > self::MAX_LEN_SCHLAGWOERTER) { + $this->terminateWithError($this->p->t('abgabetool', 'c4schlagwoerterZuLang', array(self::MAX_LEN_SCHLAGWOERTER)), 'general'); + } + + if(mb_strlen((string)$abstract) > self::MAX_LEN_ABSTRACT + || mb_strlen((string)$abstract_en) > self::MAX_LEN_ABSTRACT) { + $this->terminateWithError($this->p->t('abgabetool', 'c4abstractZuLang', array(self::MAX_LEN_ABSTRACT)), 'general'); + } + + if(!is_numeric($seitenanzahl) || (int)$seitenanzahl < 1 || (int)$seitenanzahl > self::MAX_SEITENANZAHL) { + $this->terminateWithError($this->p->t('abgabetool', 'c4seitenanzahlUngueltig', array(self::MAX_SEITENANZAHL)), 'general'); + } + } + + // zwischenabgabe uploads must never touch an endabgabe termin, to avoid empty zusatzdaten + private function checkPaabgabeTypForZwischenabgabe($paabgabe_id, $posted_paabgabetyp_kurzbz) { + $this->load->model('education/Paabgabe_model', 'PaabgabeModel'); + $res = $this->PaabgabeModel->load($paabgabe_id); + + if(isError($res) || !hasData($res)) { + $this->terminateWithError($this->p->t('abgabetool', 'c4projektabgabeNichtGefunden'), 'general'); + } + + $paabgabe = getData($res)[0]; + + if($paabgabe->paabgabetyp_kurzbz === 'end') { + $this->terminateWithError($this->p->t('abgabetool', 'c4enduploadUeberZwischenabgabeNichtErlaubt'), 'general'); + } + + // typ changed between page load and upload -> the client would use the wrong upload path + if($paabgabe->paabgabetyp_kurzbz !== $posted_paabgabetyp_kurzbz) { + $this->terminateWithError($this->p->t('abgabetool', 'c4abgabeterminGeaendertNeuLaden'), 'general'); + } + } + private function checkPaabgabeForGradedStatus($paabgabe_id) { $this->load->model('education/Paabgabe_model', 'PaabgabeModel'); $res = $this->PaabgabeModel->load($paabgabe_id); @@ -2029,13 +2100,7 @@ class Abgabe extends FHCAPI_Controller $paabgabe = getData($res)[0]; if($paabgabe->note !== NULL) { - // hardcode a fallback cause phrasen arent reliable - $message = $this->p->t('abgabetool', 'c4studentAbgabeNotAllowedRegularv3'); - if(strpos($message, "<<") === 0) { // phrase could not be loaded - $this->terminateWithError('Uploads sind für bereits benotete Quality Gates gesperrt. Sollten Sie trotzdem etwas hochladen wollen, wenden Sie sich bitte an Ihre Studiengangsassistenz.', 'general'); - } else { - $this->terminateWithError($message); - } + $this->terminateWithError($this->p->t('abgabetool', 'c4studentAbgabeNotAllowedRegularv3')); } } diff --git a/public/js/components/Cis/Abgabetool/AbgabeMitarbeiterDetail.js b/public/js/components/Cis/Abgabetool/AbgabeMitarbeiterDetail.js index b3bbeb1a2..ad32464af 100644 --- a/public/js/components/Cis/Abgabetool/AbgabeMitarbeiterDetail.js +++ b/public/js/components/Cis/Abgabetool/AbgabeMitarbeiterDetail.js @@ -442,7 +442,12 @@ export const AbgabeMitarbeiterDetail = { return true }, allowedToSaveZusatzdaten() { - return this.form.schlagwoerter.length > 0 && this.form.schlagwoerter_en.length > 0 && this.form.abstract.length > 0 && this.form.abstract_en.length > 0 && this.form.seitenanzahl > 0 + // upper bounds mirror the column limits of lehre.tbl_projektarbeit, see Abgabe.php + return this.form.schlagwoerter.length > 0 && this.form.schlagwoerter.length <= 150 + && this.form.schlagwoerter_en.length > 0 && this.form.schlagwoerter_en.length <= 150 + && this.form.abstract.length > 0 && this.form.abstract.length <= 5000 + && this.form.abstract_en.length > 0 && this.form.abstract_en.length <= 5000 + && this.form.seitenanzahl > 0 && this.form.seitenanzahl <= 32767 }, getAllowedAbgabeTypeOptions() { if(this.assistenzMode) { @@ -1003,14 +1008,16 @@ export const AbgabeMitarbeiterDetail = {
{{$capitalize( $p.t('abgabetool/c4schlagwoerterGer') )}}
- + +

{{ form.schlagwoerter?.length ? form.schlagwoerter.length : 0 }} / 150 characters

- +
{{$capitalize( $p.t('abgabetool/c4schlagwoerterEng') )}}
- + +

{{ form.schlagwoerter_en?.length ? form.schlagwoerter_en.length : 0 }} / 150 characters

@@ -1035,7 +1042,7 @@ export const AbgabeMitarbeiterDetail = {
+ inputId="seitenanzahlInput" :min="1" :max="32767">
diff --git a/public/js/components/Cis/Abgabetool/AbgabeStudentDetail.js b/public/js/components/Cis/Abgabetool/AbgabeStudentDetail.js index 159cc77b3..eab69be50 100644 --- a/public/js/components/Cis/Abgabetool/AbgabeStudentDetail.js +++ b/public/js/components/Cis/Abgabetool/AbgabeStudentDetail.js @@ -258,7 +258,9 @@ export const AbgabeStudentDetail = { return false; } - if(termin.bezeichnung?.paabgabetyp_kurzbz === 'end') { + // use the field from the backend, not the client side mapped bezeichnung object - a failed + // lookup there would silently route an endupload through the zwischenabgabe endpoint + if(termin.paabgabetyp_kurzbz === 'end') { this.enduploadTermin = termin this.$refs.modalContainerEnduploadZusatzdaten.show() } else { @@ -365,7 +367,12 @@ export const AbgabeStudentDetail = { return this.$capitalize(this.$p.t('abgabetool/c4eidesstattlicheErklaerung')) }, allowedToSaveZusatzdaten() { - return this.form.schlagwoerter.length > 0 && this.form.schlagwoerter_en.length > 0 && this.form.abstract.length > 0 && this.form.abstract_en.length > 0 && this.form.seitenanzahl > 0 + // upper bounds mirror the column limits of lehre.tbl_projektarbeit, see Abgabe.php + return this.form.schlagwoerter.length > 0 && this.form.schlagwoerter.length <= 150 + && this.form.schlagwoerter_en.length > 0 && this.form.schlagwoerter_en.length <= 150 + && this.form.abstract.length > 0 && this.form.abstract.length <= 5000 + && this.form.abstract_en.length > 0 && this.form.abstract_en.length <= 5000 + && this.form.seitenanzahl > 0 && this.form.seitenanzahl <= 32767 }, getAllowedToSendEndupload() { return this.eidAkzeptiert && this.allowedToSaveZusatzdaten @@ -733,14 +740,16 @@ export const AbgabeStudentDetail = {
{{$capitalize( $p.t('abgabetool/c4schlagwoerterGer') )}}
- + +

{{ form.schlagwoerter?.length ? form.schlagwoerter.length : 0 }} / 150 characters

- +
{{$capitalize( $p.t('abgabetool/c4schlagwoerterEng') )}}
- + +

{{ form.schlagwoerter_en?.length ? form.schlagwoerter_en.length : 0 }} / 150 characters

@@ -765,7 +774,7 @@ export const AbgabeStudentDetail = {
+ inputId="seitenanzahlInput" :min="1" :max="32767">
diff --git a/system/phrasesupdate.php b/system/phrasesupdate.php index 0f9b9397e..1335c3b72 100644 --- a/system/phrasesupdate.php +++ b/system/phrasesupdate.php @@ -48056,6 +48056,126 @@ array( ) ) ), + array( + 'app' => 'core', + 'category' => 'abgabetool', + 'phrase' => 'c4schlagwoerterZuLang', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Die Schlagwörter dürfen maximal {0} Zeichen lang sein.', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Keywords may not exceed {0} characters.', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'abgabetool', + 'phrase' => 'c4abstractZuLang', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Der Abstract darf maximal {0} Zeichen lang sein.', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'The abstract may not exceed {0} characters.', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'abgabetool', + 'phrase' => 'c4seitenanzahlUngueltig', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Die Seitenanzahl muss zwischen 1 und {0} liegen.', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'The number of pages must be between 1 and {0}.', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'abgabetool', + 'phrase' => 'c4enduploadZusatzdatenSpeichernFehlgeschlagen', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Die Zusatzdaten konnten nicht gespeichert werden, der Upload wurde daher nicht abgeschlossen. Bitte wenden Sie sich an Ihre Studiengangsassistenz.', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'The additional data could not be saved, therefore the upload was not completed. Please contact your degree programme assistant.', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'abgabetool', + 'phrase' => 'c4enduploadUeberZwischenabgabeNichtErlaubt', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Dieser Termin ist eine Endabgabe und kann nur über den Endupload inklusive Zusatzdaten hochgeladen werden. Bitte laden Sie die Seite neu.', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'This deadline is a final submission and can only be uploaded via the final upload including the additional data. Please reload the page.', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'abgabetool', + 'phrase' => 'c4abgabeterminGeaendertNeuLaden', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Der Abgabetermin wurde zwischenzeitlich geändert. Bitte laden Sie die Seite neu.', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'The submission deadline has been changed in the meantime. Please reload the page.', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), // ABGABETOOL PHRASEN END array( 'app' => 'core',