expanded title validation in frontend aswell before validation for the same patterns in backend just in case; phrasen alert messages & use backend response instead of frontend validated title

This commit is contained in:
Johann Hoffmann
2026-05-29 11:47:46 +02:00
parent 9f462fe3d6
commit ae71517ceb
5 changed files with 107 additions and 15 deletions
@@ -486,7 +486,17 @@ class Abgabe extends FHCAPI_Controller
$this->terminateWithError($this->p->t('global', 'wrongParameters'), 'general'); $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->checkProjektarbeitForFinishedStatus($projektarbeit_id);
$this->load->model('education/Projektarbeit_model', 'ProjektarbeitModel'); $this->load->model('education/Projektarbeit_model', 'ProjektarbeitModel');
@@ -542,7 +552,8 @@ class Abgabe extends FHCAPI_Controller
); );
$result = $this->ProjektarbeitModel->load($projektarbeit_id); $result = $this->ProjektarbeitModel->load($projektarbeit_id);
$this->terminateWithSuccess($result); $titel = hasData($result) ? getData($result)[0]->titel : $titel;
$this->terminateWithSuccess($titel);
} }
/** /**
@@ -4,6 +4,8 @@ import VueDatePicker from '../../vueDatepicker.js.php';
import ApiAbgabe from '../../../api/factory/abgabe.js' import ApiAbgabe from '../../../api/factory/abgabe.js'
import FhcOverlay from "../../Overlay/FhcOverlay.js"; import FhcOverlay from "../../Overlay/FhcOverlay.js";
import { formatISODate, getViennaTodayISO } from "./dateUtils.js"; import { formatISODate, getViennaTodayISO } from "./dateUtils.js";
import { validateThesisTitle } from './titleValidation.js'
export const AbgabeStudentDetail = { export const AbgabeStudentDetail = {
name: "AbgabeStudentDetail", name: "AbgabeStudentDetail",
@@ -127,10 +129,17 @@ export const AbgabeStudentDetail = {
this.$refs.modalTitelEdit.show(); this.$refs.modalTitelEdit.show();
}, },
async saveTitel() { async saveTitel() {
const trimmed = this.editingTitel.trim();
if (!trimmed) { const validation = validateThesisTitle(this.editingTitel);
this.$fhcAlert.alertWarning(this.$capitalize(this.$p.t('global/warningEmptyField')));
return; 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({ const confirmed = await this.$fhcAlert.confirm({
@@ -147,14 +156,14 @@ export const AbgabeStudentDetail = {
this.$api.call( this.$api.call(
ApiAbgabe.postStudentProjektarbeitTitel( ApiAbgabe.postStudentProjektarbeitTitel(
this.projektarbeit.projektarbeit_id, this.projektarbeit.projektarbeit_id,
trimmed validation.cleanedTitle
) )
).then(res => { ).then(res => {
if (res.meta.status === 'success') { if (res.meta.status === 'success') {
this.projektarbeit.titel = trimmed; this.projektarbeit.titel = res.data;
this.$emit('titel-updated', { this.$emit('titel-updated', {
projektarbeit_id: this.projektarbeit.projektarbeit_id, projektarbeit_id: this.projektarbeit.projektarbeit_id,
titel: trimmed titel: res.data
}); });
this.$fhcAlert.alertSuccess(this.$capitalize(this.$p.t('abgabetool/c4titelSavedSuccess'))); this.$fhcAlert.alertSuccess(this.$capitalize(this.$p.t('abgabetool/c4titelSavedSuccess')));
this.$refs.modalTitelEdit.hide(); this.$refs.modalTitelEdit.hide();
@@ -4,6 +4,7 @@ import ApiAuthinfo from '../../../api/factory/authinfo.js';
import BsModal from "../../Bootstrap/Modal.js"; import BsModal from "../../Bootstrap/Modal.js";
import FhcOverlay from "../../Overlay/FhcOverlay.js"; import FhcOverlay from "../../Overlay/FhcOverlay.js";
import { getDateStyleClass} from "./getDateStyleClass.js"; import { getDateStyleClass} from "./getDateStyleClass.js";
import { validateThesisTitle } from './titleValidation.js'
export const AbgabetoolStudent = { export const AbgabetoolStudent = {
name: "AbgabetoolStudent", name: "AbgabetoolStudent",
@@ -61,10 +62,16 @@ export const AbgabetoolStudent = {
this.$refs.modalTitelEdit.show(); this.$refs.modalTitelEdit.show();
}, },
async saveTitel() { async saveTitel() {
const trimmed = this.editingTitel.trim(); const validation = validateThesisTitle(this.editingTitel);
if (!trimmed) {
this.$fhcAlert.alertWarning(this.$capitalize(this.$p.t('global/warningEmptyField'))); if (!validation.isValid) {
return; 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({ const confirmed = await this.$fhcAlert.confirm({
@@ -81,15 +88,15 @@ export const AbgabetoolStudent = {
this.$api.call( this.$api.call(
ApiAbgabe.postStudentProjektarbeitTitel( ApiAbgabe.postStudentProjektarbeitTitel(
this.editingProjektarbeit.projektarbeit_id, this.editingProjektarbeit.projektarbeit_id,
trimmed validation.cleanedTitle
) )
).then(res => { ).then(res => {
if (res.meta.status === 'success') { if (res.meta.status === 'success') {
// update the local list entry in-place so the accordion header reflects it immediately // 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 // keep the open detail modal in sync if it happens to be showing this projektarbeit
if (this.selectedProjektarbeit?.projektarbeit_id === this.editingProjektarbeit.projektarbeit_id) { 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.$fhcAlert.alertSuccess(this.$capitalize(this.$p.t('abgabetool/c4titelSavedSuccess')));
this.$refs.modalTitelEdit.hide(); this.$refs.modalTitelEdit.hide();
@@ -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 };
}
+40
View File
@@ -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 // ABGABETOOL PHRASEN END
array( array(
'app' => 'core', 'app' => 'core',