diff --git a/src/main/java/de/imi/mopat/helper/controller/HtmlUtilities.java b/src/main/java/de/imi/mopat/helper/controller/HtmlUtilities.java
index 1f8d56b0..93e03253 100644
--- a/src/main/java/de/imi/mopat/helper/controller/HtmlUtilities.java
+++ b/src/main/java/de/imi/mopat/helper/controller/HtmlUtilities.java
@@ -21,4 +21,34 @@ public static String getStringWithoutHtml(final String htmlString) {
}
return htmlString.replaceAll("\\<[^>]*>", "");
}
+
+ /*
+ * Converts HTML content to countable visible text for validation.
+ * Unlike getStringWithoutHtml(...), this also preserves visible line breaks
+ * from tags like
,
or and decodes common HTML entities
+ * such as  .
+ */
+ public static String getVisibleText(String html) {
+ if (html == null || html.isEmpty()) {
+ return "";
+ }
+
+ String text = html
+ .replace("\r\n", "\n")
+ .replace("\r", "\n");
+
+ text = text.replaceAll("(?i)
", "\n");
+ text = text.replaceAll("(?i)(?:div|p|li|h[1-6])\\s*>", "\n");
+ text = text.replaceAll("(?s)<[^>]+>", "");
+
+ text = text.replace(" ", " ");
+ text = text.replace("&", "&");
+ text = text.replace("<", "<");
+ text = text.replace(">", ">");
+ text = text.replace(""", "\"");
+ text = text.replace("'", "'");
+
+ return text.replaceAll("\n{3,}", "\n\n").trim();
+ }
+
}
diff --git a/src/main/java/de/imi/mopat/validator/BundleDTOValidator.java b/src/main/java/de/imi/mopat/validator/BundleDTOValidator.java
index 21443084..6f03aeaa 100644
--- a/src/main/java/de/imi/mopat/validator/BundleDTOValidator.java
+++ b/src/main/java/de/imi/mopat/validator/BundleDTOValidator.java
@@ -1,6 +1,7 @@
package de.imi.mopat.validator;
import de.imi.mopat.dao.BundleDao;
+import de.imi.mopat.helper.controller.HtmlUtilities;
import de.imi.mopat.helper.controller.HtmlUtils;
import de.imi.mopat.model.Bundle;
import de.imi.mopat.model.dto.BundleDTO;
@@ -21,6 +22,11 @@
@Component
public class BundleDTOValidator implements Validator {
+ // Needs to be public because it's used in BundleDto
+ public static final int MAX_DESCRIPTION_TEXT_LENGTH = 2_000;
+ public static final int MAX_WELCOME_TEXT_LENGTH = 5_000;
+ public static final int MAX_FINAL_TEXT_LENGTH = 5_000;
+
@Autowired
private MessageSource messageSource;
@@ -56,12 +62,17 @@ public void validate(final Object o, final Errors errors) {
LocaleContextHolder.getLocale()));
}
- String bundleDescription = HtmlUtils.removeHtmlTags(bundleDTO.getDescription());
-
- if (bundleDescription != null && bundleDescription.isEmpty()) {
+ // Check if description text is too long or empty
+ int visibleDescpriptionLength = HtmlUtilities.getVisibleText(bundleDTO.getDescription()).length();
+ if(visibleDescpriptionLength > MAX_DESCRIPTION_TEXT_LENGTH){
+ errors.rejectValue("description",
+ MoPatValidator.ERRORCODE_ERRORMESSAGE,
+ messageSource.getMessage("questionnaire.error.descriptionTooLong",
+ new Object[]{visibleDescpriptionLength, MAX_DESCRIPTION_TEXT_LENGTH}, LocaleContextHolder.getLocale()));
+ } else if (bundleDTO.getDescription() != null && !bundleDTO.getDescription().isEmpty() && visibleDescpriptionLength == 0) {
errors.rejectValue("description", "errormessage",
- messageSource.getMessage("bundle.description.notNull", new Object[]{},
- LocaleContextHolder.getLocale()));
+ messageSource.getMessage("bundle.description.notNull", new Object[]{},
+ LocaleContextHolder.getLocale()));
}
// Check if at least the first questionnaire is enabled in this bundle
@@ -104,6 +115,17 @@ public void validate(final Object o, final Errors errors) {
messageSource.getMessage("bundle.validator" + ".welcomeText" + ".notNull",
new Object[]{}, LocaleContextHolder.getLocale()));
}
+ // Check if current welcome text is too long
+ else {
+ int visibleWelcomeTextLength = HtmlUtilities.getVisibleText(entry.getValue()).length();
+ if (visibleWelcomeTextLength > MAX_WELCOME_TEXT_LENGTH){
+ errors.rejectValue("localizedWelcomeText[" + entry.getKey() + "]",
+ MoPatValidator.ERRORCODE_ERRORMESSAGE,
+ messageSource.getMessage("questionnaire.error.welcomeTextTooLong",
+ new Object[]{visibleWelcomeTextLength, MAX_WELCOME_TEXT_LENGTH}, LocaleContextHolder.getLocale()));
+
+ }
+ }
}
}
@@ -130,6 +152,17 @@ public void validate(final Object o, final Errors errors) {
messageSource.getMessage("bundle.validator" + ".finalText" + ".notNull",
new Object[]{}, LocaleContextHolder.getLocale()));
}
+ // Check if current final text is too long
+ else {
+ int visibleFinalTextLength = HtmlUtilities.getVisibleText(entry.getValue()).length();
+ if (visibleFinalTextLength > MAX_FINAL_TEXT_LENGTH){
+ errors.rejectValue("localizedFinalText[" + entry.getKey() + "]",
+ MoPatValidator.ERRORCODE_ERRORMESSAGE,
+ messageSource.getMessage("questionnaire.error.finalTextTooLong",
+ new Object[]{visibleFinalTextLength, MAX_FINAL_TEXT_LENGTH}, LocaleContextHolder.getLocale()));
+
+ }
+ }
}
}
}
diff --git a/src/main/java/de/imi/mopat/validator/ClinicDTOValidator.java b/src/main/java/de/imi/mopat/validator/ClinicDTOValidator.java
index f8aee74d..4e0d154f 100644
--- a/src/main/java/de/imi/mopat/validator/ClinicDTOValidator.java
+++ b/src/main/java/de/imi/mopat/validator/ClinicDTOValidator.java
@@ -1,6 +1,7 @@
package de.imi.mopat.validator;
import de.imi.mopat.dao.ClinicDao;
+import de.imi.mopat.helper.controller.HtmlUtilities;
import de.imi.mopat.model.dto.ClinicConfigurationMappingDTO;
import de.imi.mopat.model.dto.ClinicDTO;
@@ -18,6 +19,8 @@
@Component
public class ClinicDTOValidator implements Validator {
+ private static final int MAX_DESCRIPTION_TEXT_LENGTH = 2_000;
+
@Autowired
private MessageSource messageSource;
@Autowired
@@ -53,6 +56,22 @@ public void validate(final Object o, final Errors errors) {
messageSource.getMessage("clinic.error.nameInUse", new Object[]{},
LocaleContextHolder.getLocale()));
}
+
+ // Check if description text is too long or empty
+ int visibleDescpriptionLength = HtmlUtilities.getVisibleText(clinicDTO.getDescription()).length();
+ if(visibleDescpriptionLength > MAX_DESCRIPTION_TEXT_LENGTH){
+ errors.rejectValue("description",
+ MoPatValidator.ERRORCODE_ERRORMESSAGE,
+ messageSource.getMessage("questionnaire.error.descriptionTooLong",
+ new Object[]{visibleDescpriptionLength, MAX_DESCRIPTION_TEXT_LENGTH}, LocaleContextHolder.getLocale()));
+ } else if (visibleDescpriptionLength == 0) {
+ errors.rejectValue("description", "errormessage",
+ messageSource.getMessage("clinic.description.notNull", new Object[]{},
+ LocaleContextHolder.getLocale()));
+ }
+
+
+
if (!checkIfAnyOnePatientRetrieverIsEnabled(clinicDTO)) {
errors.rejectValue("clinicConfigurationMappingDTOS[0].value", MoPatValidator.ERRORCODE_ERRORMESSAGE,
messageSource.getMessage("clinic.error.noConfiguration",
diff --git a/src/main/java/de/imi/mopat/validator/InvitationDTOValidator.java b/src/main/java/de/imi/mopat/validator/InvitationDTOValidator.java
index 93ca4ee6..3d4a10fb 100644
--- a/src/main/java/de/imi/mopat/validator/InvitationDTOValidator.java
+++ b/src/main/java/de/imi/mopat/validator/InvitationDTOValidator.java
@@ -1,5 +1,6 @@
package de.imi.mopat.validator;
+import de.imi.mopat.helper.controller.HtmlUtilities;
import de.imi.mopat.model.dto.InvitationDTO;
import de.imi.mopat.model.dto.InvitationUserDTO;
import org.springframework.beans.factory.annotation.Autowired;
@@ -15,6 +16,8 @@
@Component
public class InvitationDTOValidator implements Validator {
+ private static final int MAX_PERSONAL_TEXT_LENGTH = 2000;
+
@Autowired
private MessageSource messageSource;
@@ -65,5 +68,15 @@ public void validate(final Object o, final Errors errors) {
}
}
+ // Checks if message is too long (message is named personalText in DTO)
+ int personalTextLength = HtmlUtilities.getVisibleText(invitationDTO.getPersonalText()).length();
+ if (personalTextLength > MAX_PERSONAL_TEXT_LENGTH){
+ errors.rejectValue("personalText",
+ MoPatValidator.ERRORCODE_ERRORMESSAGE,
+ messageSource.getMessage("invitation.message.tooLong",
+ new Object[]{personalTextLength, MAX_PERSONAL_TEXT_LENGTH},
+ LocaleContextHolder.getLocale()));
+ }
+
}
}
diff --git a/src/main/java/de/imi/mopat/validator/QuestionDTOValidator.java b/src/main/java/de/imi/mopat/validator/QuestionDTOValidator.java
index 941988de..24c55fa3 100644
--- a/src/main/java/de/imi/mopat/validator/QuestionDTOValidator.java
+++ b/src/main/java/de/imi/mopat/validator/QuestionDTOValidator.java
@@ -3,11 +3,11 @@
import de.imi.mopat.model.enumeration.QuestionType;
import de.imi.mopat.model.dto.AnswerDTO;
import de.imi.mopat.model.dto.QuestionDTO;
+import de.imi.mopat.helper.controller.HtmlUtilities;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
-import java.util.regex.Pattern;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
@@ -25,6 +25,7 @@ public class QuestionDTOValidator implements Validator {
private static final String MIN_NUMBER_ANSWERS = "minNumberAnswers";
private static final String MAX_NUMBER_ANSWERS = "maxNumberAnswers";
+ private static final int MAX_QUESTION_TEXT_LENGTH = 2_000;
@Autowired
private SelectAnswerDTOValidator selectAnswerDTOValidator;
@Autowired
@@ -56,10 +57,10 @@ public void validate(final Object target, final Errors errors) {
// [bt] now it's my time to validate the more complex stuff
QuestionDTO questionDTO = (QuestionDTO) target;
- // [sw] Check if any added language contains an empty questionText
+ // [sw] Check if any added language contains an empty or too long questionText
for (Map.Entry entry : questionDTO.getLocalizedQuestionText().entrySet()) {
- if (entry.getValue() == null || entry.getValue().trim().isEmpty() || Pattern.matches(
- "(
|
|\\s| |
)+<\\/p>", entry.getValue())) {
+ String visibleQuestionText = HtmlUtilities.getVisibleText(entry.getValue());
+ if (visibleQuestionText.isEmpty()) {
questionDTO.getLocalizedQuestionText().put(entry.getKey(), "");
if (questionDTO.getQuestionType() == QuestionType.INFO_TEXT) {
errors.rejectValue("localizedQuestionText[" + entry.getKey() + "]",
@@ -72,6 +73,11 @@ public void validate(final Object target, final Errors errors) {
messageSource.getMessage("question.error" + ".questionTextIsNull",
new Object[]{}, LocaleContextHolder.getLocale()));
}
+ } else if(visibleQuestionText.length() > MAX_QUESTION_TEXT_LENGTH){
+ errors.rejectValue("localizedQuestionText[" + entry.getKey() + "]",
+ MoPatValidator.ERRORCODE_ERRORMESSAGE,
+ messageSource.getMessage("question.error.questionTextTooLong",
+ new Object[]{visibleQuestionText.length(),MAX_QUESTION_TEXT_LENGTH}, LocaleContextHolder.getLocale()));
}
}
diff --git a/src/main/java/de/imi/mopat/validator/QuestionnaireDTOValidator.java b/src/main/java/de/imi/mopat/validator/QuestionnaireDTOValidator.java
index 9d663658..eabaea9c 100644
--- a/src/main/java/de/imi/mopat/validator/QuestionnaireDTOValidator.java
+++ b/src/main/java/de/imi/mopat/validator/QuestionnaireDTOValidator.java
@@ -3,6 +3,7 @@
import de.imi.mopat.dao.QuestionnaireDao;
import de.imi.mopat.model.Questionnaire;
import de.imi.mopat.model.dto.QuestionnaireDTO;
+import de.imi.mopat.helper.controller.HtmlUtilities;
import java.util.Map;
import java.util.regex.Pattern;
@@ -20,12 +21,18 @@
@Component
public class QuestionnaireDTOValidator implements Validator {
+ private static final int MAX_DESCRIPTION_TEXT_LENGTH = 2_000;
+ private static final int MAX_WELCOME_TEXT_LENGTH = 5_000;
+ private static final int MAX_FINAL_TEXT_LENGTH = 5_000;
+
+
@Autowired
private MessageSource messageSource;
@Autowired
private QuestionnaireDao questionnaireDao;
+
@Override
public boolean supports(final Class> type) {
return QuestionnaireDTO.class.isAssignableFrom(type);
@@ -56,6 +63,19 @@ public void validate(final Object o, final Errors errors) {
LocaleContextHolder.getLocale()));
}
+ // Check if the description text is too long or empty
+ int visibleDescpriptionLength = HtmlUtilities.getVisibleText(questionnaireDTO.getDescription()).length();
+ if(visibleDescpriptionLength > MAX_DESCRIPTION_TEXT_LENGTH){
+ errors.rejectValue("description",
+ MoPatValidator.ERRORCODE_ERRORMESSAGE,
+ messageSource.getMessage("questionnaire.error.descriptionTooLong",
+ new Object[]{visibleDescpriptionLength, MAX_DESCRIPTION_TEXT_LENGTH}, LocaleContextHolder.getLocale()));
+ } else if (questionnaireDTO.getDescription() != null && !questionnaireDTO.getDescription().isEmpty() && visibleDescpriptionLength == 0) {
+ errors.rejectValue("description", "errormessage",
+ messageSource.getMessage("questionnaire.description.notNull", new Object[]{},
+ LocaleContextHolder.getLocale()));
+ }
+
// [sw] Check if any added language contains an empty questionText
for (Map.Entry entry : questionnaireDTO.getLocalizedDisplayName()
.entrySet()) {
@@ -93,6 +113,17 @@ public void validate(final Object o, final Errors errors) {
"questionnaire.validator" + ".welcomeText" + ".notNull", new Object[]{},
LocaleContextHolder.getLocale()));
}
+ // Check if current welcome text is too long
+ else {
+ int visibleWelcomeTextLength = HtmlUtilities.getVisibleText(entry.getValue()).length();
+ if (visibleWelcomeTextLength > MAX_WELCOME_TEXT_LENGTH){
+ errors.rejectValue("localizedWelcomeText[" + entry.getKey() + "]",
+ MoPatValidator.ERRORCODE_ERRORMESSAGE,
+ messageSource.getMessage("questionnaire.error.welcomeTextTooLong",
+ new Object[]{visibleWelcomeTextLength, MAX_WELCOME_TEXT_LENGTH}, LocaleContextHolder.getLocale()));
+
+ }
+ }
}
}
@@ -121,6 +152,17 @@ public void validate(final Object o, final Errors errors) {
"questionnaire.validator" + ".finalText" + ".notNull", new Object[]{},
LocaleContextHolder.getLocale()));
}
+ // Check if current final text is too long
+ else {
+ int visibleFinalTextLength = HtmlUtilities.getVisibleText(entry.getValue()).length();
+ if (visibleFinalTextLength > MAX_FINAL_TEXT_LENGTH){
+ errors.rejectValue("localizedFinalText[" + entry.getKey() + "]",
+ MoPatValidator.ERRORCODE_ERRORMESSAGE,
+ messageSource.getMessage("questionnaire.error.finalTextTooLong",
+ new Object[]{visibleFinalTextLength, MAX_FINAL_TEXT_LENGTH}, LocaleContextHolder.getLocale()));
+
+ }
+ }
}
}
}
diff --git a/src/main/resources/message/messages.properties b/src/main/resources/message/messages.properties
index a754a913..85d75b07 100644
--- a/src/main/resources/message/messages.properties
+++ b/src/main/resources/message/messages.properties
@@ -625,6 +625,7 @@ invitation.label.fileInfo=The personal data, first name, surname and e-mail addr
invitation.label.firstname=First name
invitation.label.lastname=Last name
invitation.label.user=User
+invitation.message.tooLong=The message contains {0} characters and exceeds the maximum of {1} characters.
layout.button.back=Go Back
layout.button.close=Cancel and exit
layout.footer.copyright=2026 Institute of Medical Informatics,
University of M\u00fcnster
@@ -734,6 +735,7 @@ question.error.noValidScoreMinMax=It is not possible to give exactly one answer.
question.error.noValidScoreQuestionType=A question type has been selected that does not support score calculation. If you save the question, all scores that contain this question will be deleted. Do you really want to save the question?
question.error.notModifiable=This question is not editable due to already existing responses from surveys
question.error.questionTextIsNull=The localized question text is required
+question.error.questionTextTooLong=The question text contains {0} characters and exceeds the maximum of {1} characters.
question.error.sliderDifferenceMaxMinNotDivisibleByStepsize=The difference between max and min values is not divisible without remainder by the step size
question.error.sliderStepsizeLessOrEqualToZero=The stepsize should not be less or equal to 0
question.heading.editQuestion=Edit question
@@ -815,11 +817,14 @@ questionnaire.button.showExportTemplates=Edit export templates
questionnaire.displayName.notNull=The localized display name is required
questionnaire.error.deleteQuestionnaireNotPossible=Questionnaire {0} cannot be deleted because the questionnaire is already answered in a survey.
questionnaire.error.deleteQuestionnairePossible=The questionnaire {0} was deleted.
+questionnaire.error.descriptionTooLong=The description contains {0} characters and exceeds the maximum of {1} characters.
questionnaire.error.editLastCondition=This is the last question of this questionnaire. Thus, conditions for this question cannot be specified.
+questionnaire.error.finalTextTooLong=The final text contains {0} characters and exceeds the maximum of {1} characters.
questionnaire.error.import=An error occured during the import of a questionnaire file: {0}
questionnaire.error.nameContainsSpecialCharacters=The name you entered contains invalid characters. Only letters, numbers and the special characters !?+-_.:()[] are allowed.
questionnaire.error.nameInUse=The chosen name is already in use. Please choose a different one.
questionnaire.error.nameIsEmpty=The name shouldn't only consist of space characters.
+questionnaire.error.welcomeTextTooLong=The welcome text contains {0} characters and exceeds the maximum of {1} characters.
questionnaire.heading.editQuestionnaire=Edit questionnaire
questionnaire.heading.title=Questionnaires
questionnaire.import.button.import=Upload & Import
@@ -1049,6 +1054,7 @@ survey.title.fontSize=Select a font size
survey.title.searchCaseNumber=Search for case number
survey.title.selectBundle=Select bundle
survey.title.selectClinic=Select clinic
+textLength.error.exceeded=Character limit exceeded ({0} / {1})
typeMismatch.answers.maxValue=The maximum's value of the slider must be an integer value
typeMismatch.answers.minValue=The minimum's value of the slider must be an integer value
typeMismatch.answers.value=The score must be an integer value
diff --git a/src/main/resources/message/messages_de_DE.properties b/src/main/resources/message/messages_de_DE.properties
index 694fcbaa..3e734f1b 100644
--- a/src/main/resources/message/messages_de_DE.properties
+++ b/src/main/resources/message/messages_de_DE.properties
@@ -634,6 +634,7 @@ invitation.label.fileInfo=Die Personendaten, Vorname, Nachname und E-Mail-Adress
invitation.label.firstname=Vorname
invitation.label.lastname=Nachname
invitation.label.user=Benutzer
+invitation.message.tooLong=Die Nachricht enth\u00e4lt {0} Zeichen und \u00fcberschreitet das Maximum von {1} Zeichen.
layout.button.back=Zur\u00fcckgehen
layout.button.close=Befragung abbrechen und beenden
layout.footer.copyright=2026 Institut f\u00fcr Medizinische Informatik,
Universit\u00e4t M\u00fcnster
@@ -743,6 +744,7 @@ question.error.noValidScoreMinMax=Es kann nicht genau eine Antwortm\u00f6glichke
question.error.noValidScoreQuestionType=Es wurde ein Fragetyp ausgew\u00e4hlt, der keine Scoreberechnung unterst\u00fctzt. Wenn Sie die Frage speichern, werden somit alle Scores, die diese Frage enthalten gel\u00f6scht. Wollen Sie die Frage wirklich speichern?
question.error.notModifiable=Diese Frage ist nicht editierbar, da bereits Antworten in Befragungen gegeben wurden
question.error.questionTextIsNull=Die Frage ben\u00f6tigt einen lokalisierten Fragetext
+question.error.questionTextTooLong=Der Fragetext enth\u00e4lt {0} Zeichen und \u00fcberschreitet das Maximum von {1} Zeichen.
question.error.sliderDifferenceMaxMinNotDivisibleByStepsize=Der Abstand zwischen Minimum und Maximum ist nicht restlos durch die Schrittweite teilbar
question.error.sliderStepsizeLessOrEqualToZero=Die Schrittgr\u00f6\u00dfe darf nicht kleiner oder gleich 0 sein
question.heading.editQuestion=Frage bearbeiten
@@ -825,11 +827,14 @@ questionnaire.displayName.notNull=Der Fragebogen ben\u00f6tigt einen lokalisiert
questionnaire.download.mopatComplete=MoPat mit Export-Templates
questionnaire.error.deleteQuestionnaireNotPossible=Der Fragebogen {0} kann nicht gel\u00f6scht werden, da der Fragebogen w\u00e4hrend einer Befragung bereits beantwortet wurde.
questionnaire.error.deleteQuestionnairePossible=Der Fragebogen {0} wurde gel\u00f6scht.
+questionnaire.error.descriptionTooLong=Die Beschreibung enth\u00e4lt {0} Zeichen und \u00fcberschreitet das Maximum von {1} Zeichen.
questionnaire.error.editLastCondition=Dies ist die letzte Frage dieses Fragebogens. Daher k\u00f6nnen f\u00fcr diese Frage keine Bedingungen festgelegt werden.
+questionnaire.error.finalTextTooLong=Der Beendigungstext enth\u00e4lt {0} Zeichen und \u00fcberschreitet das Maximum von {1} Zeichen.
questionnaire.error.import=Ein Fehler ist beim Importieren einer Fragebogen-Datei entstanden: {0}
questionnaire.error.nameContainsSpecialCharacters=Der von Ihnen eingegebene Name enth\u00e4lt ung\u00fcltige Sonderzeichen. Es d\u00fcrfen nur Buchstaben, Zahlen, sowie die Sonderzeichen !?+-_.:()[] verwendet werden.
questionnaire.error.nameInUse=Ein Fragebogen mit dem gew\u00e4hlten Namen existiert bereits. Bitte w\u00e4hlen Sie einen anderen Namen.
questionnaire.error.nameIsEmpty=Der Name darf nicht nur aus Leerzeichen bestehen.
+questionnaire.error.welcomeTextTooLong=Der Willkommenstext enth\u00e4lt {0} Zeichen und \u00fcberschreitet das Maximum von {1} Zeichen.
questionnaire.heading.editQuestionnaire=Fragebogen bearbeiten
questionnaire.heading.title=Frageb\u00f6gen
questionnaire.import.button.import=Hochladen & importieren
@@ -1059,6 +1064,7 @@ survey.title.fontSize=W\u00e4hlen Sie eine Schriftgr\u00f6\u00dfe
survey.title.searchCaseNumber=Fallnummernsuche
survey.title.selectBundle=Paketauswahl
survey.title.selectClinic=Klinikauswahl
+textLength.error.exceeded=Zeichen \u00fcberschritten ({0} / {1})
typeMismatch.answers.maxValue=Der Maximum-Wert f\u00fcr den Schieberegler muss eine Ganzzahl sein
typeMismatch.answers.minValue=Der Minimum-Wert f\u00fcr den Schieberegler muss eine Ganzzahl sein
typeMismatch.answers.value=Der Score muss eine Ganzzahl sein
diff --git a/src/main/resources/message/messages_en_GB.properties b/src/main/resources/message/messages_en_GB.properties
index a754a913..85d75b07 100644
--- a/src/main/resources/message/messages_en_GB.properties
+++ b/src/main/resources/message/messages_en_GB.properties
@@ -625,6 +625,7 @@ invitation.label.fileInfo=The personal data, first name, surname and e-mail addr
invitation.label.firstname=First name
invitation.label.lastname=Last name
invitation.label.user=User
+invitation.message.tooLong=The message contains {0} characters and exceeds the maximum of {1} characters.
layout.button.back=Go Back
layout.button.close=Cancel and exit
layout.footer.copyright=2026 Institute of Medical Informatics,
University of M\u00fcnster
@@ -734,6 +735,7 @@ question.error.noValidScoreMinMax=It is not possible to give exactly one answer.
question.error.noValidScoreQuestionType=A question type has been selected that does not support score calculation. If you save the question, all scores that contain this question will be deleted. Do you really want to save the question?
question.error.notModifiable=This question is not editable due to already existing responses from surveys
question.error.questionTextIsNull=The localized question text is required
+question.error.questionTextTooLong=The question text contains {0} characters and exceeds the maximum of {1} characters.
question.error.sliderDifferenceMaxMinNotDivisibleByStepsize=The difference between max and min values is not divisible without remainder by the step size
question.error.sliderStepsizeLessOrEqualToZero=The stepsize should not be less or equal to 0
question.heading.editQuestion=Edit question
@@ -815,11 +817,14 @@ questionnaire.button.showExportTemplates=Edit export templates
questionnaire.displayName.notNull=The localized display name is required
questionnaire.error.deleteQuestionnaireNotPossible=Questionnaire {0} cannot be deleted because the questionnaire is already answered in a survey.
questionnaire.error.deleteQuestionnairePossible=The questionnaire {0} was deleted.
+questionnaire.error.descriptionTooLong=The description contains {0} characters and exceeds the maximum of {1} characters.
questionnaire.error.editLastCondition=This is the last question of this questionnaire. Thus, conditions for this question cannot be specified.
+questionnaire.error.finalTextTooLong=The final text contains {0} characters and exceeds the maximum of {1} characters.
questionnaire.error.import=An error occured during the import of a questionnaire file: {0}
questionnaire.error.nameContainsSpecialCharacters=The name you entered contains invalid characters. Only letters, numbers and the special characters !?+-_.:()[] are allowed.
questionnaire.error.nameInUse=The chosen name is already in use. Please choose a different one.
questionnaire.error.nameIsEmpty=The name shouldn't only consist of space characters.
+questionnaire.error.welcomeTextTooLong=The welcome text contains {0} characters and exceeds the maximum of {1} characters.
questionnaire.heading.editQuestionnaire=Edit questionnaire
questionnaire.heading.title=Questionnaires
questionnaire.import.button.import=Upload & Import
@@ -1049,6 +1054,7 @@ survey.title.fontSize=Select a font size
survey.title.searchCaseNumber=Search for case number
survey.title.selectBundle=Select bundle
survey.title.selectClinic=Select clinic
+textLength.error.exceeded=Character limit exceeded ({0} / {1})
typeMismatch.answers.maxValue=The maximum's value of the slider must be an integer value
typeMismatch.answers.minValue=The minimum's value of the slider must be an integer value
typeMismatch.answers.value=The score must be an integer value
diff --git a/src/main/resources/message/messages_es_ES.properties b/src/main/resources/message/messages_es_ES.properties
index 319e895e..1d8a3d24 100644
--- a/src/main/resources/message/messages_es_ES.properties
+++ b/src/main/resources/message/messages_es_ES.properties
@@ -560,6 +560,7 @@ invitation.label.email=Email
invitation.label.expirationdate=Fecha de caducidad
invitation.label.firstname=Nombre
invitation.label.lastname=Apellidos
+invitation.message.tooLong=El mensaje contiene {0} caracteres y supera el m\u00e1ximo de {1} caracteres.
layout.button.back=Volver
layout.button.close=Salir de la encuesta y finalizar
layout.footer.copyright=2026 Instituto de Telemedicina,
Universidad de M\u00fcnster
@@ -651,6 +652,7 @@ question.error.noValidScoreMinMax=No puede incluirse solamente una opci\u00f3n c
question.error.noValidScoreQuestionType=Ha seleccionado un tipo de pregunta que no soporta el c\u00e1lculo de puntuaciones. Cuando guarde la pregunta, las puntuaciones asignadas a ella ser\u00e1n borradas.Est\u00e1 seguro/a de que desea guardar los cambios?
question.error.notModifiable=Esta pregunta no es editable debido a que ya existen respuestas
question.error.questionTextIsNull=Se requiere texto para la pregunta
+question.error.questionTextTooLong=El texto de la pregunta contiene {0} caracteres y supera el m\u00e1ximo de {1} caracteres.
question.error.sliderDifferenceMaxMinNotDivisibleByStepsize=La diferencia entre los valores m\u00e1x y m\u00edn no es divisible entre el tama\u00f1o de salto
question.error.sliderStepsizeLessOrEqualToZero=El tama\u00f1o de salto debe ser mayor o igual que 0
question.heading.editQuestion=Editar pregunta
@@ -722,10 +724,13 @@ questionnaire.button.showExportTemplates=Editar plantillas
questionnaire.displayName.notNull=Se requiere un nombre para el display
questionnaire.error.deleteQuestionnaireNotPossible=El cuestionario {0} no puede ser borrado ya que ha sido previamente contestado.
questionnaire.error.deleteQuestionnairePossible=El cuestionario {0} ha sido borrado.
-questionnaire.error.editLastCondition=\u00c9sta es la \u00faltima pregunta del cuestionario. Por ello no se puede a\u00f1adir una condici\u00f3n.
+questionnaire.error.descriptionTooLong=La descripci\u00f3n contiene {0} caracteres y supera el m\u00e1ximo de {1} caracteres.
+questionnaire.error.editLastCondition=\u00c9sta es la \u00faltima pregunta del cuestionario. Por ello no se puede a\u00f1adir una condici\u00f3n.
+questionnaire.error.finalTextTooLong=El texto de finalizaci\u00f3n contiene {0} caracteres y supera el m\u00e1ximo de {1} caracteres.
questionnaire.error.import=Ha ocurrido un error durante la importaci\u00f3n del cuestionario: {0}
questionnaire.error.nameInUse=Ya existe un cuestionario con el nombre seleccionado. Por favor elija uno diferente
questionnaire.error.nameIsEmpty=El nombre no puede ser un espacio en blanco.
+questionnaire.error.welcomeTextTooLong=El texto de bienvenida contiene {0} caracteres y supera el m\u00e1ximo de {1} caracteres.
questionnaire.heading.editQuestionnaire=Editar Cuestionario
questionnaire.heading.title=Cuestionarios
questionnaire.import.button.import=Cargar & Importar
@@ -933,6 +938,7 @@ survey.questionnaire.label.skipQuestionFalse=No saltar pregunta
survey.questionnaire.label.skipQuestionTrue=Saltar pregunta
survey.title.searchCaseNumber=Buscar por n\u00famero de caso
survey.title.selectBundle=Seleccionar grupo
+textLength.error.exceeded=Se ha superado el l\u00edmite de caracteres ({0} / {1})
typeMismatch.answers.maxValue=El valor m\u00e1ximo de la barra de desplazamiento debe ser un n\u00famero entero
typeMismatch.answers.minValue=El valor m\u00ednimo de la barra de desplazamiento debe ser un n\u00famero entero
typeMismatch.answers.value=La puntuaci\u00f3n debe ser un valor entero
diff --git a/src/main/webapp/WEB-INF/bundle/edit.html b/src/main/webapp/WEB-INF/bundle/edit.html
index c57fc573..94fa2f30 100644
--- a/src/main/webapp/WEB-INF/bundle/edit.html
+++ b/src/main/webapp/WEB-INF/bundle/edit.html
@@ -733,8 +733,16 @@
+
diff --git a/src/main/webapp/WEB-INF/question/edit.html b/src/main/webapp/WEB-INF/question/edit.html
index 51ce7c18..7cca7d3c 100644
--- a/src/main/webapp/WEB-INF/question/edit.html
+++ b/src/main/webapp/WEB-INF/question/edit.html
@@ -1244,6 +1244,7 @@
+