diff --git a/application/controllers/api/frontend/v1/Abgabe.php b/application/controllers/api/frontend/v1/Abgabe.php index e6e395aec..901c7f284 100644 --- a/application/controllers/api/frontend/v1/Abgabe.php +++ b/application/controllers/api/frontend/v1/Abgabe.php @@ -486,7 +486,17 @@ class Abgabe extends FHCAPI_Controller $this->terminateWithError($this->p->t('global', 'wrongParameters'), 'general'); } + // Reject emojis and pictographs + // allows foreign letters, math symbols, accents, and standard punctuation. + $emojiPattern = '/[\x{1F300}-\x{1F5FF}\x{1F600}-\x{1F64F}\x{1F680}-\x{1F6FF}\x{1F900}-\x{1FAFF}\x{23E9}-\x{23EF}\x{2b50}\x{2700}-\x{27BF}]/u'; + + // i would like this much more but our server does not recognize this utf-8 character range this way, so hexcodes it is +// if (preg_match('/\p{Extended_Pictographic}/u', $titel)) { + if (preg_match($emojiPattern, $titel)) { + $this->terminateWithError($this->p->t('global', 'wrongParameters'), 'general'); + } + $this->checkProjektarbeitForFinishedStatus($projektarbeit_id); $this->load->model('education/Projektarbeit_model', 'ProjektarbeitModel'); @@ -542,7 +552,8 @@ class Abgabe extends FHCAPI_Controller ); $result = $this->ProjektarbeitModel->load($projektarbeit_id); - $this->terminateWithSuccess($result); + $titel = hasData($result) ? getData($result)[0]->titel : $titel; + $this->terminateWithSuccess($titel); } /** diff --git a/public/js/components/Cis/Abgabetool/AbgabeStudentDetail.js b/public/js/components/Cis/Abgabetool/AbgabeStudentDetail.js index 73f82ccfb..e22a65dde 100644 --- a/public/js/components/Cis/Abgabetool/AbgabeStudentDetail.js +++ b/public/js/components/Cis/Abgabetool/AbgabeStudentDetail.js @@ -4,6 +4,8 @@ import VueDatePicker from '../../vueDatepicker.js.php'; import ApiAbgabe from '../../../api/factory/abgabe.js' import FhcOverlay from "../../Overlay/FhcOverlay.js"; import { formatISODate, getViennaTodayISO } from "./dateUtils.js"; +import { validateThesisTitle } from './titleValidation.js' + export const AbgabeStudentDetail = { name: "AbgabeStudentDetail", @@ -127,10 +129,17 @@ export const AbgabeStudentDetail = { this.$refs.modalTitelEdit.show(); }, async saveTitel() { - const trimmed = this.editingTitel.trim(); - if (!trimmed) { - this.$fhcAlert.alertWarning(this.$capitalize(this.$p.t('global/warningEmptyField'))); - return; + + const validation = validateThesisTitle(this.editingTitel); + + if (!validation.isValid) { + if (validation.error === 'empty') { + this.$fhcAlert.alertWarning(this.$p.t('abgabetool/c4emptyThesisTitle')) + } else if (validation.error === 'invalid_characters') { + this.$fhcAlert.alertWarning(this.$p.t('abgabetool/c4invalidCharactersThesisTitle')) + + } + return false; } const confirmed = await this.$fhcAlert.confirm({ @@ -147,14 +156,14 @@ export const AbgabeStudentDetail = { this.$api.call( ApiAbgabe.postStudentProjektarbeitTitel( this.projektarbeit.projektarbeit_id, - trimmed + validation.cleanedTitle ) ).then(res => { if (res.meta.status === 'success') { - this.projektarbeit.titel = trimmed; + this.projektarbeit.titel = res.data; this.$emit('titel-updated', { projektarbeit_id: this.projektarbeit.projektarbeit_id, - titel: trimmed + titel: res.data }); this.$fhcAlert.alertSuccess(this.$capitalize(this.$p.t('abgabetool/c4titelSavedSuccess'))); this.$refs.modalTitelEdit.hide(); diff --git a/public/js/components/Cis/Abgabetool/AbgabetoolStudent.js b/public/js/components/Cis/Abgabetool/AbgabetoolStudent.js index 9886e096d..8061e9a10 100644 --- a/public/js/components/Cis/Abgabetool/AbgabetoolStudent.js +++ b/public/js/components/Cis/Abgabetool/AbgabetoolStudent.js @@ -4,6 +4,7 @@ import ApiAuthinfo from '../../../api/factory/authinfo.js'; import BsModal from "../../Bootstrap/Modal.js"; import FhcOverlay from "../../Overlay/FhcOverlay.js"; import { getDateStyleClass} from "./getDateStyleClass.js"; +import { validateThesisTitle } from './titleValidation.js' export const AbgabetoolStudent = { name: "AbgabetoolStudent", @@ -61,10 +62,16 @@ export const AbgabetoolStudent = { this.$refs.modalTitelEdit.show(); }, async saveTitel() { - const trimmed = this.editingTitel.trim(); - if (!trimmed) { - this.$fhcAlert.alertWarning(this.$capitalize(this.$p.t('global/warningEmptyField'))); - return; + const validation = validateThesisTitle(this.editingTitel); + + if (!validation.isValid) { + if (validation.error === 'empty') { + this.$fhcAlert.alertWarning(this.$p.t('abgabetool/c4emptyThesisTitle')) + } else if (validation.error === 'invalid_characters') { + this.$fhcAlert.alertWarning(this.$p.t('abgabetool/c4invalidCharactersThesisTitle')) + + } + return false; } const confirmed = await this.$fhcAlert.confirm({ @@ -81,15 +88,15 @@ export const AbgabetoolStudent = { this.$api.call( ApiAbgabe.postStudentProjektarbeitTitel( this.editingProjektarbeit.projektarbeit_id, - trimmed + validation.cleanedTitle ) ).then(res => { if (res.meta.status === 'success') { // update the local list entry in-place so the accordion header reflects it immediately - this.editingProjektarbeit.titel = trimmed; + this.editingProjektarbeit.titel = res.data; // keep the open detail modal in sync if it happens to be showing this projektarbeit if (this.selectedProjektarbeit?.projektarbeit_id === this.editingProjektarbeit.projektarbeit_id) { - this.selectedProjektarbeit.titel = trimmed; + this.selectedProjektarbeit.titel = res.data; } this.$fhcAlert.alertSuccess(this.$capitalize(this.$p.t('abgabetool/c4titelSavedSuccess'))); this.$refs.modalTitelEdit.hide(); diff --git a/public/js/components/Cis/Abgabetool/titleValidation.js b/public/js/components/Cis/Abgabetool/titleValidation.js new file mode 100644 index 000000000..6fe30328c --- /dev/null +++ b/public/js/components/Cis/Abgabetool/titleValidation.js @@ -0,0 +1,25 @@ +/** + * Validates the thesis title on the frontend + * @param {string} title - The raw input from the title field + * @returns {object} Validation result containing status and cleaned title + */ +export function validateThesisTitle(title) { + if (!title) { + return { isValid: false, error: 'empty' }; + } + + // Replicate strip_tags / trim + const cleanedTitle = title.replace(/<\/?[^>]+(>|$)/g, "").trim(); + + if (cleanedTitle === '') { + return { isValid: false, error: 'empty' }; + } + + // Replicate the emoji/pictograph rejection + const emojiRegex = /\p{Extended_Pictographic}/u; + if (emojiRegex.test(cleanedTitle)) { + return { isValid: false, error: 'invalid_characters' }; + } + + return { isValid: true, cleanedTitle: cleanedTitle }; +} \ No newline at end of file diff --git a/system/phrasesupdate.php b/system/phrasesupdate.php index d8a5393e8..038608c53 100644 --- a/system/phrasesupdate.php +++ b/system/phrasesupdate.php @@ -46941,6 +46941,46 @@ array( ) ) ), + array( + 'app' => 'core', + 'category' => 'abgabetool', + 'phrase' => 'c4emptyThesisTitle', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Projektarbeit Titel darf nicht leer sein.', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Projekt work title is not allowed to be empty.', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'abgabetool', + 'phrase' => 'c4invalidCharactersThesisTitle', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Ungültige Zeichen im Titel der Projektarbeit gefunden.', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Invalid characters detected in thesis title.', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), // ABGABETOOL PHRASEN END array( 'app' => 'core',