From e1682605741061a2ba562f0012614491e618b944 Mon Sep 17 00:00:00 2001 From: hail-cookies <81558994+hail-cookies@users.noreply.github.com> Date: Wed, 4 Feb 2026 11:34:00 +0100 Subject: [PATCH 1/2] DEV StringDataType "validator_regex_negative" added --- DataTypes/StringDataType.php | 101 ++++++++++++++++-- .../Formatters/JsStringFormatter.php | 83 +++++++++----- Translations/exface.Core.de.json | 1 + Translations/exface.Core.en.json | 1 + 4 files changed, 152 insertions(+), 34 deletions(-) diff --git a/DataTypes/StringDataType.php b/DataTypes/StringDataType.php index a4d37ec52..e3cbbc83b 100644 --- a/DataTypes/StringDataType.php +++ b/DataTypes/StringDataType.php @@ -22,11 +22,12 @@ class StringDataType extends AbstractDataType { private $lengthMin = 0; - + private $lengthMax = null; - + private $regexValidator = null; + private $regexValidatorNegative = null; private $emptyAsNULL = false; /** @@ -38,7 +39,10 @@ public function getValidatorRegex() : ?string } /** - * Defines a regular expression to validate values of this data type. + * Defines a regular expression to validate values of this data type. If the regex finds a match, validation + * succeeds. + * + * Can be used in tandem with `validator_regex_negative`. * * Example: * @@ -65,6 +69,47 @@ public function setValidatorRegex($regularExpression) $this->regexValidator = $regularExpression; return $this; } + + /** + * @return string|null + */ + public function getValidatorRegexNegative() : ?string + { + return $this->regexValidatorNegative; + } + + /** + * Defines a regular expression to validate values of this data type. If the regex finds any match, validation + * fails. Some systems output the detected matches as "issues". To support this write your regex in such a way + * that it identifies meaningful issues with a given input string. + * + * Can be used in tandem with `validator_regex`. + * + * Example: + * + * ``` + * { + * "validator_regex": "/[^0-9ÄÖÜäöüßA-Za-z-_,+= .#&§$']/", + * "validation_error_text": "The string may only contain the following characters: Digits from `0-9`, Umlauts `ÄÖÜäöüß`, `A-Z`, `a-z` and these special characters `-_,+= .#&§$'`" + * } + * + * ``` + * + * Use regular expressions compatible with PHP preg_match(). A good + * tool to create and test regular expressions can be found here: + * https://regex101.com/. + * + * @uxon-property validator_regex_negative + * @uxon-type string + * + * @param string $regularExpression + * @return StringDataType + */ + public function setValidatorRegexNegative(string $regularExpression) : StringDataType + { + $this->regexValidatorNegative = $regularExpression; + return $this; + } /** * @@ -89,6 +134,10 @@ protected function getValidationDescription() : string if ($this->getValidatorRegex()) { $text = ($text ? $text . ' ' . $and . ' ' : '') . $translator->translate('DATATYPE.VALIDATION.REGEX_CONDITION', ['%regex%' => $this->getValidatorRegex()]); } + if ($this->getValidatorRegexNegative()) { + $text = ($text ? $text . ' ' . $and . ' ' : '') . + $translator->translate('DATATYPE.VALIDATION.REGEX_CONDITION_NEGATIVE', ['%regex%' => $this->getValidatorRegexNegative()]); + } if ($text !== '') { $text = $translator->translate('DATATYPE.VALIDATION.MUST') . ' ' . $text . '.'; @@ -270,9 +319,42 @@ public function parse($string) if (! $match){ $excValue = ''; if (! $this->isSensitiveData()) { - $excValue = '"' . $value . '"'; + $excValue = '"' . $value . '" '; + } + throw $this->createValidationRuleError($value, 'Value ' . $excValue . 'does not match the regular expression mask "' . $this->getValidatorRegex() . '" of data type ' . $this->getAliasWithNamespace() . '!', false); + } + } + + if ($this->getValidatorRegexNegative()){ + $matches = []; + + try { + preg_match_all($this->getValidatorRegexNegative(), $value, $matches); + } catch (\Throwable $e) { + throw $this->createValidationRuleError($value, 'Validation regex "' . $this->getValidatorRegexNegative() . '" is invalid!'); + } + + if (!empty($matches)){ + $excValue = ''; + if (! $this->isSensitiveData()) { + $excValue = '"' . $value . '" '; + } + + $issues = []; + + foreach ($matches as $matchGroup) { + for($i = 1; $i < count($matchGroup); $i++){ + $val = $matchGroup[$i]; + $issues[$val] = $val; + } + } + + if(!empty($issues)) { + throw $this->createValidationRuleError($value, 'Value ' . $excValue . + 'must not match the regular expression mask "' . $this->getValidatorRegexNegative() . + '" of data type ' . $this->getAliasWithNamespace() . '! The following issues were detected: "' . + implode(', ', $issues) . '".', false); } - throw $this->createValidationRuleError($value, 'Value ' . $excValue . ' does not match the regular expression mask "' . $this->getValidatorRegex() . '" of data type ' . $this->getAliasWithNamespace() . '!', false); } } @@ -582,7 +664,8 @@ public static function encodeUTF8(string $string, string $originalEncoding = nul * @param int $length * @param bool $stickToWords prevents words getting cut in the middle * @param bool $ellipsis adds `...` at the end if the string is really shortened - * @param bool $endHint adds `[truncated characters]` if the string is really shortened (usefull for debug output) + * @param bool $endHint adds `[truncated characters]` if the string is really shortened (usefull + * for debug output) * @return string */ public static function truncate(string $string, int $length, bool $stickToWords = false, bool $removeLineBreaks = false, bool $ellipsis = false, bool $endHint = false) : string @@ -718,7 +801,8 @@ public static function endSentence(string $text, string $puct = '.') : string * - `transliterate('Änderung')` -> Anderung * - `transliterate('Änderung', ':: Any-Latin; :: Latin-ASCII; :: Lower()')` -> anderung * - `transliterate('ä/B', ':: Any-Latin; [:Punctuation:] Remove;')` -> a b - * - `transliterate('Aufgaben im Überblick', ':: Any-Latin; :: Latin-ASCII; :: NFD; :: [:Nonspacing Mark:] Remove; :: Lower(); :: NFC;')` -> aufgaben im uberblick + * - `transliterate('Aufgaben im Überblick', ':: Any-Latin; :: Latin-ASCII; :: NFD; :: [:Nonspacing Mark:] Remove; + * :: Lower(); :: NFC;')` -> aufgaben im uberblick * * @link https://unicode-org.github.io/icu/userguide/transforms/general/ * @@ -747,7 +831,8 @@ public static function transliterate(string $string, string $translitRules = ':: * Returns TRUE if the given string is one enclosed is quotes (single or double quotes) and FALSE otherwise * * Currently this does not check, if there are also some closing quotes in the middle of the string. - * Possible enhanced solution: https://stackoverflow.com/questions/74963883/php-regular-expression-to-grab-values-enclosed-in-double-quotes + * Possible enhanced solution: + * https://stackoverflow.com/questions/74963883/php-regular-expression-to-grab-values-enclosed-in-double-quotes * * @param string $str * @return bool diff --git a/Facades/AbstractAjaxFacade/Formatters/JsStringFormatter.php b/Facades/AbstractAjaxFacade/Formatters/JsStringFormatter.php index e769fc758..72c7334f1 100644 --- a/Facades/AbstractAjaxFacade/Formatters/JsStringFormatter.php +++ b/Facades/AbstractAjaxFacade/Formatters/JsStringFormatter.php @@ -40,8 +40,13 @@ public function buildJsValidator(string $jsValue) : string } if ($type->getValidatorRegex() !== null) { - $checksOk[] = "{$type->getValidatorRegex()}.test({$jsValue}) !== false \n"; + $checksOk[] = "(function(){debugger;return {$type->getValidatorRegex()}.test({$jsValue}) !== false;})() \n"; } + + if ($type->getValidatorRegexNegative() !== null) { + $checksOk[] = "{$type->getValidatorRegexNegative()}.test({$jsValue}) === false \n"; + } + $checksOkJs = ! empty($checksOk) ? implode(' && ', $checksOk) : 'true'; $nullStr = '" . EXF_LOGICAL_NULL . "'; @@ -65,46 +70,72 @@ public function buildJsGetValidatorIssues(string $jsValue): string } $regex = $dataType->getValidatorRegex(); - if($regex === null) { + $regexNegative = $dataType->getValidatorRegexNegative(); + if($regex === null && $regexNegative === null) { return parent::buildJsGetValidatorIssues($jsValue); } $translator = $this->getWorkbench()->getCoreApp()->getTranslator(); - $regexIssuePreamble = json_encode($translator->translate('DATATYPE.VALIDATION.FILENAME_INVALID_SYMBOLS')); - if(null !== $message = $dataType->getValidationErrorMessage()) { $msg = StringDataType::endSentence($message->getTitle()); } else { $msg = $translator->translate('DATATYPE.VALIDATION.FILENAME_INVALID'); } $msg = json_encode($msg); - - // Make sure the regex is global and not sticky. - $regex = StringDataType::removeRegexFlags($regex, ['g','y']); - $regex .= 'g'; - - return << 0) { - // Extract unqiue matches. - for (const match of matches) { - if (aRegexIssues.indexOf(match) === -1) { - aRegexIssues.push(match); - } +// If the negative regex did not produce issues, test the positive one. +// StringDataType::getValidatorRegex() +var regex = {$regex}; +// Apply validator regex to string to extract matches. +if (regex.test(sValue) === false) { + return {$msg}; +} +JS; + } + + $negativeJs = ''; + if($regexNegative !== null) { + $regexIssuePreamble = json_encode($translator->translate('DATATYPE.VALIDATION.FILENAME_INVALID_SYMBOLS')); + + // Make sure the regex is global and not sticky. + $regexNegative = StringDataType::removeRegexFlags($regexNegative, ['g','y']); + $regexNegative .= 'g'; + + $negativeJs = << 0) { + // Extract unqiue matches. + for (const match of matches) { + if (aRegexIssues.indexOf(match) === -1) { + aRegexIssues.push(match); } - - return sIssues + ' ' + {$regexIssuePreamble} + JSON.stringify(aRegexIssues, null, 1).slice(1,-1) + '.'; } + return sIssues + ' ' + {$regexIssuePreamble} + JSON.stringify(aRegexIssues, null, 1).slice(1,-1) + '.'; +} +JS; + } + + + return << Date: Wed, 4 Feb 2026 11:47:47 +0100 Subject: [PATCH 2/2] FIX StringDataType cleanup --- DataTypes/StringDataType.php | 44 +++++++++---------- .../Formatters/JsStringFormatter.php | 2 +- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/DataTypes/StringDataType.php b/DataTypes/StringDataType.php index e3cbbc83b..2220a2de9 100644 --- a/DataTypes/StringDataType.php +++ b/DataTypes/StringDataType.php @@ -89,7 +89,7 @@ public function getValidatorRegexNegative() : ?string * * ``` * { - * "validator_regex": "/[^0-9ÄÖÜäöüßA-Za-z-_,+= .#&§$']/", + * "validator_regex_negative": "/[^0-9ÄÖÜäöüßA-Za-z-_,+= .#&§$']/", * "validation_error_text": "The string may only contain the following characters: Digits from `0-9`, Umlauts `ÄÖÜäöüß`, `A-Z`, `a-z` and these special characters `-_,+= .#&§$'`" * } * @@ -309,25 +309,9 @@ public function parse($string) } // validate against regex - if ($this->getValidatorRegex()){ - try { - $match = preg_match($this->getValidatorRegex(), $value); - } catch (\Throwable $e) { - $match = 0; - } - - if (! $match){ - $excValue = ''; - if (! $this->isSensitiveData()) { - $excValue = '"' . $value . '" '; - } - throw $this->createValidationRuleError($value, 'Value ' . $excValue . 'does not match the regular expression mask "' . $this->getValidatorRegex() . '" of data type ' . $this->getAliasWithNamespace() . '!', false); - } - } - if ($this->getValidatorRegexNegative()){ $matches = []; - + try { preg_match_all($this->getValidatorRegexNegative(), $value, $matches); } catch (\Throwable $e) { @@ -339,16 +323,16 @@ public function parse($string) if (! $this->isSensitiveData()) { $excValue = '"' . $value . '" '; } - + $issues = []; - + foreach ($matches as $matchGroup) { - for($i = 1; $i < count($matchGroup); $i++){ + for($i = 0; $i < count($matchGroup); $i++){ $val = $matchGroup[$i]; $issues[$val] = $val; } } - + if(!empty($issues)) { throw $this->createValidationRuleError($value, 'Value ' . $excValue . 'must not match the regular expression mask "' . $this->getValidatorRegexNegative() . @@ -357,6 +341,22 @@ public function parse($string) } } } + + if ($this->getValidatorRegex()){ + try { + $match = preg_match($this->getValidatorRegex(), $value); + } catch (\Throwable $e) { + $match = 0; + } + + if (! $match){ + $excValue = ''; + if (! $this->isSensitiveData()) { + $excValue = '"' . $value . '" '; + } + throw $this->createValidationRuleError($value, 'Value ' . $excValue . 'does not match the regular expression mask "' . $this->getValidatorRegex() . '" of data type ' . $this->getAliasWithNamespace() . '!', false); + } + } return $value; } diff --git a/Facades/AbstractAjaxFacade/Formatters/JsStringFormatter.php b/Facades/AbstractAjaxFacade/Formatters/JsStringFormatter.php index 72c7334f1..940a6c9d5 100644 --- a/Facades/AbstractAjaxFacade/Formatters/JsStringFormatter.php +++ b/Facades/AbstractAjaxFacade/Formatters/JsStringFormatter.php @@ -40,7 +40,7 @@ public function buildJsValidator(string $jsValue) : string } if ($type->getValidatorRegex() !== null) { - $checksOk[] = "(function(){debugger;return {$type->getValidatorRegex()}.test({$jsValue}) !== false;})() \n"; + $checksOk[] = "{$type->getValidatorRegex()}.test({$jsValue}) !== false \n"; } if ($type->getValidatorRegexNegative() !== null) {