From b9c299891e70e5c371a63e15c05c0ed124074419 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Frank=20N=C3=A4gler?= <typo3@naegler.net>
Date: Fri, 3 Jul 2015 10:01:27 +0200
Subject: [PATCH] [TASK] Resident Eval - The Removal from FormEngine

A long long time ago jsfunc.evalfield.js found its way into the core.
Now - after 12 years - it is time to leave.

This patch removes the usage of jsfunc.evalfield.js from FormEngine
and moves the logic into FormEngineValidation.js as a first step.
The validators and processors have been split up into two methods.

Resolves: #67852
Releases: master
Change-Id: I781bae602ea09a4a4e359df0a461f2cfc1cdf6d8
Reviewed-on: http://review.typo3.org/40912
Reviewed-by: Stefan Froemken <froemken@gmail.com>
Tested-by: Stefan Froemken <froemken@gmail.com>
Reviewed-by: Christian Kuhn <lolli@schwarzbu.ch>
Tested-by: Christian Kuhn <lolli@schwarzbu.ch>
Reviewed-by: Markus Klein <markus.klein@typo3.org>
Tested-by: Markus Klein <markus.klein@typo3.org>
---
 .../backend/Classes/Form/AbstractNode.php     |  16 +
 .../Classes/Form/Element/InputElement.php     |  29 +-
 .../backend/Classes/Form/FormEngine.php       |  12 +-
 .../Public/JavaScript/FormEngineValidation.js | 971 ++++++++++++++++--
 .../Public/JavaScript/jsfunc.evalfield.js     |  70 +-
 .../Public/JavaScript/jsfunc.tbe_editor.js    |  13 +-
 ...-RemoveJsfuncevalfieldjsFromFormEngine.rst |  12 +
 .../frontend/Configuration/TCA/tt_content.php |   8 +-
 .../Classes/Browser/ElementBrowser.php        |   4 +-
 9 files changed, 991 insertions(+), 144 deletions(-)
 create mode 100644 typo3/sysext/core/Documentation/Changelog/master/Important-67852-RemoveJsfuncevalfieldjsFromFormEngine.rst

diff --git a/typo3/sysext/backend/Classes/Form/AbstractNode.php b/typo3/sysext/backend/Classes/Form/AbstractNode.php
index 04c483048e87..98cfd32c0268 100644
--- a/typo3/sysext/backend/Classes/Form/AbstractNode.php
+++ b/typo3/sysext/backend/Classes/Form/AbstractNode.php
@@ -153,6 +153,22 @@ abstract class AbstractNode implements NodeInterface {
 	 */
 	protected function getValidationDataAsJsonString(array $config) {
 		$validationRules = array();
+		if (!empty($config['eval'])) {
+			$evalList = GeneralUtility::trimExplode(',', $config['eval'], TRUE);
+			unset($config['eval']);
+			foreach ($evalList as $evalType) {
+				$validationRules[] = array(
+					'type' => $evalType,
+					'config' => $config
+				);
+			}
+		}
+		if (!empty($config['range'])) {
+			$validationRules[] = array(
+				'type' => 'range',
+				'config' => $config['range']
+			);
+		}
 		if (!empty($config['maxitems']) || !empty($config['minitems'])) {
 			$minItems = (isset($config['minitems'])) ? (int)$config['minitems'] : 0;
 			$maxItems = (isset($config['maxitems'])) ? (int)$config['maxitems'] : 10000;
diff --git a/typo3/sysext/backend/Classes/Form/Element/InputElement.php b/typo3/sysext/backend/Classes/Form/Element/InputElement.php
index bb319c6137f7..d589b9e94e87 100644
--- a/typo3/sysext/backend/Classes/Form/Element/InputElement.php
+++ b/typo3/sysext/backend/Classes/Form/Element/InputElement.php
@@ -153,20 +153,25 @@ class InputElement extends AbstractFormElement {
 					}
 			}
 		}
-		$paramsList = GeneralUtility::quoteJSvalue($parameterArray['itemFormElName']) . ',' . GeneralUtility::quoteJSvalue(implode(',', $evalList)) . ',' . GeneralUtility::quoteJSvalue(trim($config['is_in'])) . ',' . ($config['checkbox'] ? 1 : 0) . ',' . GeneralUtility::quoteJSvalue($config['checkbox']);
-		$parameterArray['fieldChangeFunc'] = array_merge(array('typo3form.fieldGet' => 'typo3form.fieldGet(' . $paramsList . ');'), $parameterArray['fieldChangeFunc']);
-
+		$paramsList = array(
+			'field' => $parameterArray['itemFormElName'],
+			'evalList' => implode(',', $evalList),
+			'is_in' => trim($config['is_in']),
+			'checkbox' => ($config['checkbox'] ? 1 : 0),
+			'checkboxValue' => $config['checkbox'],
+		);
 		// set classes
 		$classes[] = 'form-control';
 		$classes[] = 't3js-clearable';
 		$classes[] = 'hasDefaultValue';
 
 		// calculate attributes
+		$attributes['data-formengine-validation-rules'] = $this->getValidationDataAsJsonString($config);
+		$attributes['data-formengine-input-params'] = json_encode($paramsList);
 		$attributes['id'] = str_replace('.', '', uniqid('formengine-input-', TRUE));
 		$attributes['name'] = $parameterArray['itemFormElName'] . '_hr';
 		$attributes['value'] = '';
 		$attributes['maxlength'] = $config['max'] ?: 256;
-		$attributes['onchange'] = implode('', $parameterArray['fieldChangeFunc']);
 
 		if (!empty($styles)) {
 			$attributes['style'] = implode(' ', $styles);
@@ -178,30 +183,26 @@ class InputElement extends AbstractFormElement {
 			$attributes['maxlength'] = (int)$config['max'];
 		}
 
+		// This is the EDITABLE form field.
+		$placeholderValue = $this->getPlaceholderValue($table, $config, $row);
+		if (!empty($placeholderValue)) {
+			$attributes['placeholder'] = trim($languageService->sL($placeholderValue));
+		}
+
 		// Build the attribute string
 		$attributeString = '';
 		foreach ($attributes as $attributeName => $attributeValue) {
 			$attributeString .= ' ' . $attributeName . '="' . htmlspecialchars($attributeValue) . '"';
 		}
 
-		// This is the EDITABLE form field.
-		$placeholderValue = $this->getPlaceholderValue($table, $config, $row);
-		$placeholderAttribute = '';
-		if (!empty($placeholderValue)) {
-			$placeholderAttribute = ' placeholder="' . htmlspecialchars(trim($languageService->sL($placeholderValue))) . '" ';
-		}
-
 		$html = '
 			<input type="text"'
 				. $attributeString
-				. $placeholderAttribute
 				. $parameterArray['onFocus'] . ' />';
 
 		// This is the ACTUAL form field - values from the EDITABLE field must be transferred to this field which is the one that is written to the database.
 		$html .= '<input type="hidden" name="' . $parameterArray['itemFormElName'] . '" value="' . htmlspecialchars($parameterArray['itemFormElValue']) . '" />';
 
-		$resultArray['extJSCODE'] = 'typo3form.fieldSet(' . $paramsList . ');';
-
 		// Going through all custom evaluations configured for this field
 		// @todo: Similar to above code!
 		foreach ($evalList as $evalData) {
diff --git a/typo3/sysext/backend/Classes/Form/FormEngine.php b/typo3/sysext/backend/Classes/Form/FormEngine.php
index d5952a826d39..84ed66148ef9 100644
--- a/typo3/sysext/backend/Classes/Form/FormEngine.php
+++ b/typo3/sysext/backend/Classes/Form/FormEngine.php
@@ -211,9 +211,7 @@ class FormEngine {
 	 *
 	 * @var array
 	 */
-	protected $requireJsModules = array(
-		'TYPO3/CMS/Backend/FormEngineValidation' => NULL
-	);
+	protected $requireJsModules = array();
 
 	/**
 	 * Constructor function, setting internal variables, loading the styles used.
@@ -1227,6 +1225,11 @@ class FormEngine {
 			$this->requireJsModules['TYPO3/CMS/Backend/FormEngine'] = 'function(FormEngine) {
 				FormEngine.setBrowserUrl(' . GeneralUtility::quoteJSvalue(BackendUtility::getModuleUrl('browser')) . ');
 			}';
+			$this->requireJsModules['TYPO3/CMS/Backend/FormEngineValidation'] = 'function(FormEngineValidation) {
+				FormEngineValidation.setUsMode(' . ($GLOBALS['TYPO3_CONF_VARS']['SYS']['USdateFormat'] ? '1' : '0') . ');
+				FormEngineValidation.registerReady();
+			}';
+
 			foreach ($this->requireJsModules as $moduleName => $callbacks) {
 				if (!is_array($callbacks)) {
 					$callbacks = array($callbacks);
@@ -1247,7 +1250,6 @@ class FormEngine {
 			);
 			$pageRenderer->addInlineSettingArray('Textarea', $textareaSettings);
 
-			$this->loadJavascriptLib('sysext/backend/Resources/Public/JavaScript/jsfunc.evalfield.js');
 			$this->loadJavascriptLib('sysext/backend/Resources/Public/JavaScript/jsfunc.tbe_editor.js');
 			$pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/ValueSlider');
 			// Needed for FormEngine manipulation (date picker)
@@ -1290,8 +1292,6 @@ class FormEngine {
 			TBE_EDITOR.labels.refresh_login = ' . GeneralUtility::quoteJSvalue($languageService->sL('LLL:EXT:lang/locallang_core.xlf:mess.refresh_login')) . ';
 			TBE_EDITOR.labels.onChangeAlert = ' . GeneralUtility::quoteJSvalue($languageService->sL('LLL:EXT:lang/locallang_core.xlf:mess.onChangeAlert')) . ';
 			TBE_EDITOR.labels.remainingCharacters = ' . GeneralUtility::quoteJSvalue($languageService->sL('LLL:EXT:lang/locallang_core.xlf:labels.remainingCharacters')) . ';
-			evalFunc.USmode = ' . ($GLOBALS['TYPO3_CONF_VARS']['SYS']['USdateFormat'] ? '1' : '0') . ';
-
 			TBE_EDITOR.customEvalFunctions = {};
 
 			';
diff --git a/typo3/sysext/backend/Resources/Public/JavaScript/FormEngineValidation.js b/typo3/sysext/backend/Resources/Public/JavaScript/FormEngineValidation.js
index 5c975ee46a15..a8cff87cf870 100644
--- a/typo3/sysext/backend/Resources/Public/JavaScript/FormEngineValidation.js
+++ b/typo3/sysext/backend/Resources/Public/JavaScript/FormEngineValidation.js
@@ -13,138 +13,478 @@
 
 /**
  * contains all JS functions related to TYPO3 TCEforms/FormEngineValidation
+ * @internal
  */
 define('TYPO3/CMS/Backend/FormEngineValidation', ['jquery', 'TYPO3/CMS/Backend/FormEngine'], function ($, FormEngine) {
 
 	/**
-	 * the main FormEngineValidation object
+	 * The main FormEngineValidation object
 	 *
-	 * @type {{rulesSelector: string, dateTimeSelector: string, groupFieldHiddenElement: string, relatedFieldSelector: string}}
+	 * @type {{rulesSelector: string, inputSelector: string, markerSelector: string, dateTimeSelector: string, groupFieldHiddenElement: string, relatedFieldSelector: string, errorClass: string, lastYear: number, lastDate: number, lastTime: number, refDate: Date, USmode: number, passwordDummy: string}}
 	 */
 	var FormEngineValidation = {
 		rulesSelector: '[data-formengine-validation-rules]',
+		inputSelector: '[data-formengine-input-params]',
+		markerSelector: '.t3js-formengine-validation-marker',
 		dateTimeSelector: '.t3js-datetimepicker',
 		groupFieldHiddenElement: '.t3js-formengine-field-group input[type=hidden]',
-		relatedFieldSelector: '[data-relatedfieldname]'
+		relatedFieldSelector: '[data-relatedfieldname]',
+		errorClass: 'has-error',
+		lastYear: 0,
+		lastDate: 0,
+		lastTime: 0,
+		refDate: new Date(),
+		USmode: 0,
+		passwordDummy: '********'
 	};
 
 	/**
-	 * initialize validation for the first time
+	 * Initialize validation for the first time
 	 */
 	FormEngineValidation.initialize = function() {
-		$(document).find('.has-error').removeClass('has-error');
+		$(document).find('.' + FormEngineValidation.errorClass).removeClass(FormEngineValidation.errorClass);
 
-		// bind to field changes
+		// Bind to field changes
 		$(document).on('change', FormEngineValidation.rulesSelector, function() {
-			// we need to wait, because the update of the select field needs some time
+			// we need to wait, because the update of the select fields needs some time
 			window.setTimeout(function() {
 				FormEngineValidation.validate();
 			}, 500);
+			var $paletteField = $(this).closest('.t3js-formengine-palette-field');
+			$paletteField.addClass('has-change');
 		});
 
-		// bind to datepicker changes
+		// Bind to datepicker changes
 		$(document).on('dp.change', FormEngineValidation.dateTimeSelector, function(event) {
 			FormEngineValidation.validate();
+			var $paletteField = $(this).closest('.t3js-formengine-palette-field');
+			$paletteField.addClass('has-change');
 		});
 
-		if (typeof RTEarea !== 'undefined') {
-			console.log(RTEarea);
+		// Initialize input fields
+		$(document).find(FormEngineValidation.inputSelector).each(function() {
+			var config = $(this).data('formengine-input-params');
+			var fieldName = config.field;
+			var $field = $('[name="' + fieldName + '"]');
+			$field.data('main-field', fieldName);
+			$field.data('config', config);
+			FormEngineValidation.initializeInputField(fieldName);
+		});
+
+		var today = new Date();
+		FormEngineValidation.lastYear = FormEngineValidation.getYear(today);
+		FormEngineValidation.lastDate = FormEngineValidation.getDate(today);
+		FormEngineValidation.lastTime = 0;
+		FormEngineValidation.refDate = today;
+		FormEngineValidation.USmode = 0;
+	};
+
+	/**
+	 *
+	 * @param {number} mode
+	 */
+	FormEngineValidation.setUsMode = function(mode) {
+		FormEngineValidation.USmode = mode;
+	};
+
+	/**
+	 * Initialize field by name
+	 *
+	 * @param {string} fieldName
+	 */
+	FormEngineValidation.initializeInputField = function(fieldName) {
+		var $field = $('[name="' + fieldName + '"]');
+		var $humanReadableField = $('[name="' + fieldName + '_hr"]');
+		var $checkboxField = $('[name="' + fieldName + '_cb"]');
+		var $mainField = $('[name="' + $field.data('main-field') + '"]');
+		if ($mainField.length === 0) {
+			$mainField = $field;
+		}
+
+		var config = $mainField.data('config');
+		if (typeof config !== 'undefined') {
+			var evalList = FormEngineValidation.trimExplode(',', config.evalList);
+			var value = $field.val();
+
+			if (config.checkbox && value == config.checkboxValue) {
+				$field.val('');
+				if ($checkboxField.length) {
+					$checkboxField.attr('checked', '');
+				}
+			} else {
+				for (var i = 0; i < evalList.length; i++) {
+					value = FormEngineValidation.formatValue(evalList[i], value, config)
+				}
+				if (value.length) {
+					$humanReadableField.val(value);
+				}
+				if ($checkboxField.length) {
+					$checkboxField.attr('checked', 'checked');
+				}
+			}
 		}
+
+		$humanReadableField.data('main-field', fieldName);
+		$humanReadableField.data('config', config);
+		$humanReadableField.on('change', function() {
+			FormEngineValidation.updateInputField($(this).attr('name'));
+		});
+
+		$checkboxField.data('main-field', fieldName);
+		$checkboxField.data('config', config);
+		$checkboxField.on('click', function() {
+			FormEngineValidation.updateInputField($(this).attr('name'));
+		});
 	};
 
 	/**
-	 * validate the complete form
+	 * Format field value
+	 *
+	 * @param {string} type
+	 * @param {string} value
+	 * @param {array} config
+	 * @returns {string}
 	 */
-	FormEngineValidation.validate = function() {
-		$(document).find('.t3js-formengine-validation-marker, .t3js-tabmenu-item')
-			.removeClass('has-error')
-			.removeClass('has-validation-error');
+	FormEngineValidation.formatValue = function(type, value, config) {
+		var theString = '';
+		switch (type) {
+			case 'date':
+				if (!parseInt(value)) {
+					return '';
+				}
+				theTime = new Date(parseInt(value) * 1000);
+				if (FormEngineValidation.USmode) {
+					theString = (theTime.getUTCMonth() + 1) + '-' + theTime.getUTCDate() + '-' + this.getYear(theTime);
+				} else {
+					theString = theTime.getUTCDate() + '-' + (theTime.getUTCMonth() + 1) + '-' + this.getYear(theTime);
+				}
+				break;
+			case 'datetime':
+				if (!parseInt(value)) {
+					return '';
+				}
+				theString = FormEngineValidation.formatValue('time', value, config) + ' ' + FormEngineValidation.formatValue('date', value, config);
+				break;
+			case 'time':
+			case 'timesec':
+				if (!parseInt(value)) {
+					return '';
+				}
+				var theTime = new Date(parseInt(value) * 1000);
+				var h = theTime.getUTCHours();
+				var m = theTime.getUTCMinutes();
+				var s = theTime.getUTCSeconds();
+				theString = h + ':' + ((m < 10) ? '0' : '') + m + ((type == 'timesec') ? ':' + ((s < 10) ? '0' : '') + s : '');
+				break;
+			case 'password':
+				theString = (value) ? FormEngineValidation.passwordDummy : '';
+				break;
+			case 'int':
+				theString = (config.checkbox && value == config.checkboxValue) ? '' : value;
+				break;
+			default:
+				theString = value;
+		}
+		return theString;
+	};
 
-		$(FormEngineValidation.rulesSelector).each(function() {
-			var $field = $(this);
-			var $rules = $field.data('formengine-validation-rules');
-			var markParent = false;
-			var selected = 0;
-			$rules.each(function(rule) {
-				switch (rule.type) {
-					case 'required':
-						if ($field.val() === '') {
-							markParent = true;
-							$field.closest('.t3js-formengine-validation-marker').addClass('has-error');
-						}
-						break;
-					case 'range':
+	/**
+	 * Update input field after change
+	 *
+	 * @param {string} fieldName
+	 */
+	FormEngineValidation.updateInputField = function(fieldName) {
+		var $field = $('[name="' + fieldName + '"]');
+		var $mainField = $('[name="' + $field.data('main-field') + '"]');
+		if ($mainField.length === 0) {
+			$mainField = $field;
+		}
+		var $humanReadableField = $('[name="' + $mainField.attr('name') + '_hr"]');
+
+		var config = $mainField.data('config');
+		if (typeof config !== 'undefined') {
+			var evalList = FormEngineValidation.trimExplode(',', config.evalList);
+			var origValue = $humanReadableField.val();
+			var newValue = $humanReadableField.val();
+
+			for (var i = 0; i < evalList.length; i++) {
+				newValue = FormEngineValidation.processValue(evalList[i], newValue, config);
+			}
+			var typeConfig = $field.data('formengine-validation-rules');
+			var type = '';
+			if (typeof typeConfig !== 'undefined' && typeConfig.length) {
+				type = typeConfig[0].type;
+			}
+			switch (type) {
+				case 'password':
+					$mainField.val(origValue);
+					$humanReadableField.val(newValue);
+					break;
+				default:
+					$mainField.val(newValue);
+					$humanReadableField.val(newValue);
+			}
+		}
+	};
+
+	/**
+	 * Run validation for field
+	 *
+	 * @param {object} $field
+	 * @param {string} value
+	 * @returns {string}
+	 */
+	FormEngineValidation.validateField = function($field, value) {
+		value = value || FormEngineValidation.ltrim($field.val());
+
+		var $rules = $field.data('formengine-validation-rules');
+		var markParent = false;
+		var selected = 0;
+		var returnValue = value;
+		$rules.each(function(rule) {
+			switch (rule.type) {
+				case 'required':
+					if (value === '') {
+						markParent = true;
+						$field.closest(FormEngineValidation.markerSelector).addClass(FormEngineValidation.errorClass);
+					}
+					break;
+				case 'range':
+					if (value !== '') {
 						if (rule.minItems || rule.maxItems) {
 							$relatedField = $(document).find('[name="' + $field.data('relatedfieldname') + '"]');
 							if ($relatedField.length) {
 								selected = FormEngineValidation.trimExplode(',', $relatedField.val()).length;
 								if (selected < rule.minItems || selected > rule.maxItems) {
 									markParent = true;
-									$field.closest('.t3js-formengine-validation-marker').addClass('has-error');
+									$field.closest(FormEngineValidation.markerSelector).addClass(FormEngineValidation.errorClass);
 								}
 							} else {
 								selected = $field.val();
 								if (selected < rule.minItems || selected > rule.maxItems) {
 									markParent = true;
-									$field.closest('.t3js-formengine-validation-marker').addClass('has-error');
+									$field.closest(FormEngineValidation.markerSelector).addClass(FormEngineValidation.errorClass);
 								}
 
 							}
 						}
-						break;
-					case 'select':
-						if (rule.minItems || rule.maxItems) {
-							$relatedField = $(document).find('[name="' + $field.data('relatedfieldname') + '"]');
-							if ($relatedField.length) {
-								selected = FormEngineValidation.trimExplode(',', $relatedField.val()).length;
-								if (selected < rule.minItems || selected > rule.maxItems) {
-									markParent = true;
-									$field.closest('.t3js-formengine-validation-marker').addClass('has-error');
-								}
-							} else {
-								selected = $field.find('option:selected').length;
-								if (selected < rule.minItems || selected > rule.maxItems) {
-									markParent = true;
-									$field.closest('.t3js-formengine-validation-marker').addClass('has-error');
-								}
-
+						if (rule.config.lower || rule.config.upper) {
+							minValue = rule.config.lower || 0;
+							maxValue = rule.config.upper || Number.MAX_VALUE;
+							if (value < minValue || value > maxValue) {
+								markParent = true;
+								$field.closest(FormEngineValidation.markerSelector).addClass(FormEngineValidation.errorClass);
 							}
 						}
-						break;
-					case 'group':
-						if (rule.minItems || rule.maxItems) {
-							selected = $field.find('option').length;
+					}
+					break;
+				case 'select':
+					if (rule.minItems || rule.maxItems) {
+						$relatedField = $(document).find('[name="' + $field.data('relatedfieldname') + '"]');
+						if ($relatedField.length) {
+							selected = FormEngineValidation.trimExplode(',', $relatedField.val()).length;
 							if (selected < rule.minItems || selected > rule.maxItems) {
 								markParent = true;
-								$field.closest('.t3js-formengine-validation-marker').addClass('has-error');
+								$field.closest(FormEngineValidation.markerSelector).addClass(FormEngineValidation.errorClass);
 							}
-						}
-						break;
-					case 'inline':
-						if (rule.minItems || rule.maxItems) {
-							selected = FormEngineValidation.trimExplode(',', $field.val()).length;
+						} else {
+							selected = $field.find('option:selected').length;
 							if (selected < rule.minItems || selected > rule.maxItems) {
 								markParent = true;
-								$field.closest('.t3js-formengine-validation-marker').addClass('has-error');
+								$field.closest(FormEngineValidation.markerSelector).addClass(FormEngineValidation.errorClass);
 							}
+
+						}
+					}
+					break;
+				case 'group':
+					if (rule.minItems || rule.maxItems) {
+						selected = $field.find('option').length;
+						if (selected < rule.minItems || selected > rule.maxItems) {
+							markParent = true;
+							$field.closest(FormEngineValidation.markerSelector).addClass(FormEngineValidation.errorClass);
+						}
+					}
+					break;
+				case 'inline':
+					if (rule.minItems || rule.maxItems) {
+						selected = FormEngineValidation.trimExplode(',', $field.val()).length;
+						if (selected < rule.minItems || selected > rule.maxItems) {
+							markParent = true;
+							$field.closest(FormEngineValidation.markerSelector).addClass(FormEngineValidation.errorClass);
+						}
+					}
+					break;
+				case 'null':
+					// unknown type null, we ignore it
+					break;
+				default:
+			}
+		});
+		if (markParent) {
+			// check tabs
+			FormEngineValidation.markParentTab($field);
+		}
+		return returnValue;
+	};
+
+	/**
+	 * Process a value by given command and config
+	 *
+	 * @param {string} command
+	 * @param {string} value
+	 * @param {array} config
+	 * @returns {string}
+	 */
+	FormEngineValidation.processValue = function(command, value, config) {
+		var newString = '';
+		var theValue = '';
+		var theCmd = '';
+		var a = 0;
+		var returnValue = value;
+		switch (command) {
+			case 'alpha':
+			case 'num':
+			case 'alphanum':
+			case 'alphanum_x':
+				newString = '';
+				for (a = 0; a < value.length; a++) {
+					theChar = value.substr(a, 1);
+					var special = (theChar === '_' || theChar === '-');
+					var alpha = (theChar >= 'a' && theChar <= 'z') || (theChar >= 'A' && theChar <= 'Z');
+					var num = (theChar >= '0' && theChar <= '9');
+					switch (command) {
+						case 'alphanum':
+							special = 0;
+							break;
+						case 'alpha':
+							num = 0;
+							special = 0;
+							break;
+						case 'num':
+							alpha = 0;
+							special = 0;
+							break;
+					}
+					if (alpha || num || theChar == ' ' || special) {
+						newString += theChar;
+					}
+				}
+				if (newString !== value) {
+					returnValue = newString;
+				}
+				break;
+			case 'is_in':
+				if (config.is_in) {
+					theValue = '' + value;
+					for (a = 0; a < theValue.length; a++) {
+						theChar = theValue.substr(a, 1);
+						if (config.is_in.indexOf(theChar) != -1) {
+							newString += theChar;
 						}
-						break;
-					default:
-						FormEngineValidation.log('unknown validation type: ' + rule.type);
-				}
-			});
-			if (markParent) {
-				// check tabs
-				FormEngineValidation.markParentTab($field);
+					}
+				} else {
+					newString = theValue;
+				}
+				returnValue = newString;
+				break;
+			case 'nospace':
+				theValue = '' + value;
+				newString = '';
+				for (a = 0; a < theValue.length; a++) {
+					var theChar = theValue.substr(a, 1);
+					if (theChar != ' ') {
+						newString += theChar;
+					}
+				}
+				returnValue = newString;
+				break;
+			case 'md5':
+				if (value !== '') {
+					returnValue = MD5(value);
+				}
+				break;
+			case 'upper':
+				returnValue = value.toUpperCase();
+				break;
+			case 'lower':
+				returnValue = value.toLowerCase();
+				break;
+			case 'int':
+				if (value !== '') {
+					returnValue = FormEngineValidation.parseInt(value);
+				}
+				break;
+			case 'double2':
+				if (value !== '') {
+					returnValue = FormEngineValidation.parseDouble(value);
+				}
+				break;
+			case 'trim':
+				returnValue = FormEngineValidation.ltrim(FormEngineValidation.btrim(value));
+				break;
+			case 'datetime':
+				if (value !== '') {
+					theCmd = value.substr(0, 1);
+					returnValue = FormEngineValidation.parseDateTime(value, theCmd);
+				}
+				break;
+			case 'date':
+				if (value !== '') {
+					theCmd = value.substr(0, 1);
+					returnValue = FormEngineValidation.parseDate(value, theCmd);
+				}
+				break;
+			case 'time':
+			case 'timesec':
+				if (value !== '') {
+					theCmd = value.substr(0, 1);
+					returnValue = FormEngineValidation.parseTime(value, theCmd, command);
+				}
+				break;
+			case 'year':
+				if (value !== '') {
+					theCmd = value.substr(0, 1);
+					returnValue = FormEngineValidation.parseYear(value, theCmd);
+				}
+				break;
+			case 'null':
+				// unknown type null, we ignore it
+				break;
+			case 'password':
+				var theString = (value) ? FormEngineValidation.passwordDummy : '';
+				returnValue = theString;
+				break;
+			default:
+				if (typeof TBE_EDITOR.customEvalFunctions !== 'undefined' && typeof TBE_EDITOR.customEvalFunctions[command] === 'function') {
+					returnValue = TBE_EDITOR.customEvalFunctions[command](value);
+				}
+		}
+		return returnValue;
+	};
+
+	/**
+	 * Validate the complete form
+	 */
+	FormEngineValidation.validate = function() {
+		$(document).find(FormEngineValidation.markerSelector + ', .t3js-tabmenu-item')
+			.removeClass(FormEngineValidation.errorClass)
+			.removeClass('has-validation-error');
+
+		$(FormEngineValidation.rulesSelector).each(function() {
+			var $field = $(this);
+			var newValue = FormEngineValidation.validateField($field);
+			if (newValue.length) {
+				$field.val(newValue);
 			}
 		});
 	};
 
 	/**
-	 * helper function to get clean trimmed array from comma list
+	 * Helper function to get clean trimmed array from comma list
 	 *
-	 * @param delimiter
-	 * @param string
+	 * @param {string} delimiter
+	 * @param {string} string
 	 * @returns {Array}
 	 */
 	FormEngineValidation.trimExplode = function(delimiter, string) {
@@ -160,9 +500,400 @@ define('TYPO3/CMS/Backend/FormEngineValidation', ['jquery', 'TYPO3/CMS/Backend/F
 	};
 
 	/**
-	 * find tab by field and mark it as has-validation-error
+	 * Parse value to integer
+	 *
+	 * @param {string} value
+	 * @returns {number}
+	 */
+	FormEngineValidation.parseInt = function(value) {
+		var theVal = '' + value;
+		if (!value) {
+			return 0;
+		}
+		for (var a = 0; a < theVal.length; a++) {
+			if (theVal.substr(a,1)!='0') {
+				return parseInt(theVal.substr(a,theVal.length)) || 0;
+			}
+		}
+		return 0;
+	};
+
+	/**
+	 * Parse value to double
+	 *
+	 * @param {string} value
+	 * @returns {string}
+	 */
+	FormEngineValidation.parseDouble = function(value) {
+		var theVal = '' + value;
+		theVal = theVal.replace(/[^0-9,\.-]/g, '');
+		var negative = theVal.substring(0, 1) === '-';
+		theVal = theVal.replace(/-/g, '');
+		theVal = theVal.replace(/,/g, '.');
+		if (theVal.indexOf('.') === -1) {
+			theVal += '.0';
+		}
+		var parts = theVal.split('.');
+		var dec = parts.pop();
+		theVal = Number(parts.join('') + '.' + dec);
+		if (negative) {
+			theVal *= -1;
+		}
+		theVal = theVal.toFixed(2);
+
+		return theVal;
+	};
+
+	/**
 	 *
-	 * @param $element
+	 * @param {string} value
+	 * @returns {string}
+	 */
+	FormEngineValidation.ltrim = function(value) {
+		var theVal = '' + value;
+		if (!value) {
+			return '';
+		}
+		for (var a = 0; a < theVal.length; a++) {
+			if (theVal.substr(a, 1) != ' ') {
+				return theVal.substr(a, theVal.length);
+			}
+		}
+		return '';
+	};
+
+	/**
+	 *
+	 * @param {string} value
+	 * @returns {string}
+	 */
+	FormEngineValidation.btrim = function(value) {
+		var theVal = '' + value;
+		if (!value) {
+			return '';
+		}
+		for (var a = theVal.length; a > 0; a--) {
+			if (theVal.substr(a-1, 1) != ' ') {
+				return theVal.substr(0, a);
+			}
+		}
+		return '';
+	};
+
+	/**
+	 * Parse datetime value
+	 *
+	 * @param {string} value
+	 * @param {string} command
+	 * @returns {*}
+	 */
+	FormEngineValidation.parseDateTime = function(value, command) {
+		var today = new Date();
+		var lastTime;
+		var values = FormEngineValidation.split(value);
+		var add;
+		switch (command) {
+			case 'd':
+			case 't':
+			case 'n':
+				lastTime = FormEngineValidation.convertClientTimestampToUTC(FormEngineValidation.getTimestamp(today), 0);
+				if (values.valPol[1]) {
+					add = FormEngineValidation.pol(values.valPol[1], FormEngineValidation.parseInt(values.values[1]));
+				}
+				break;
+			case '+':
+			case '-':
+				if (lastTime == 0) {
+					lastTime = FormEngineValidation.convertClientTimestampToUTC(FormEngineValidation.getTimestamp(today), 0);
+				}
+				if (values.valPol[1]) {
+					add = FormEngineValidation.pol(values.valPol[1], FormEngineValidation.parseInt(values.values[1]));
+				}
+				break;
+			default:
+				var index = value.indexOf(' ');
+				if (index != -1) {
+					var dateVal = FormEngineValidation.parseDate(value, value.substr(index,value.length));
+					// set refDate so that evalFunc_input on time will work with correct DST information
+					FormEngineValidation.refDate = new Date(dateVal * 1000);
+					lastTime = dateVal + FormEngineValidation.parseTime(value, value.substr(0,index));
+				} else {
+					// only date, no time
+					lastTime = FormEngineValidation.parseDate(value, value);
+				}
+		}
+		lastTime += add * 24 * 60 * 60;
+		return lastTime;
+	};
+
+	/**
+	 * Parse date value
+	 *
+	 * @param {string} value
+	 * @param {string} command
+	 * @returns {*}
+	 */
+	FormEngineValidation.parseDate = function(value, command) {
+		var today = new Date();
+		var lastDate;
+		var values = FormEngineValidation.split(value);
+		var add;
+		switch (command) {
+			case 'd':
+			case 't':
+			case 'n':
+				lastDate = FormEngineValidation.getTimestamp(today);
+				if (values.valPol[1]) {
+					add = FormEngineValidation.pol(values.valPol[1], FormEngineValidation.parseInt(values.values[1]));
+				}
+				break;
+			case '+':
+			case '-':
+				if (values.valPol[1]) {
+					add = FormEngineValidation.pol(values.valPol[1], FormEngineValidation.parseInt(values.values[1]));
+				}
+				break;
+			default:
+				var index = 4;
+				if (values.valPol[index]) {
+					add = FormEngineValidation.pol(values.valPol[index], FormEngineValidation.parseInt(values.values[index]));
+				}
+				if (values.values[1] && values.values[1].length > 2) {
+					if (values.valPol[2]) {
+						add = FormEngineValidation.pol(values.valPol[2], FormEngineValidation.parseInt(values.values[2]));
+					}
+					var temp = values.values[1];
+					values = FormEngineValidation.splitSingle(temp);
+				}
+
+				var year = (values.values[3]) ? FormEngineValidation.parseInt(values.values[3]) : FormEngineValidation.getYear(today);
+				if ((year >= 0 && year < 38) || (year >= 70 && year < 100) || (year >= 1902 && year < 2038)) {
+					if (year < 100) {
+						year = (year < 38) ? year += 2000 : year += 1900;
+					}
+				} else {
+					year = FormEngineValidation.getYear(today);
+				}
+				var usMode = FormEngineValidation.USmode ? 1 : 2;
+				var month = (values.values[usMode]) ? FormEngineValidation.parseInt(values.values[usMode]) : today.getUTCMonth() + 1;
+				usMode = FormEngineValidation.USmode ? 2 : 1;
+				var day = (values.values[usMode]) ? FormEngineValidation.parseInt(values.values[usMode]) : today.getUTCDate();
+
+				var theTime = new Date(parseInt(year), parseInt(month)-1, parseInt(day));
+
+				// Substract timezone offset from client
+				lastDate = FormEngineValidation.convertClientTimestampToUTC(FormEngineValidation.getTimestamp(theTime), 0);
+		}
+		lastDate += add * 24 * 60 * 60;
+		return lastDate;
+	};
+
+	/**
+	 * Parse time value
+	 *
+	 * @param {string} value
+	 * @param {string} command
+	 * @returns {*}
+	 */
+	FormEngineValidation.parseTime = function(value, command, type) {
+		var today = new Date();
+		var lastTime;
+		var values = FormEngineValidation.split(value);
+		var add;
+		switch (command) {
+			case 'd':
+			case 't':
+			case 'n':
+				lastTime = FormEngineValidation.getTimeSecs(today);
+				if (values.valPol[1]) {
+					add = FormEngineValidation.pol(values.valPol[1], FormEngineValidation.parseInt(values.values[1]));
+				}
+				break;
+			case '+':
+			case '-':
+				if (lastTime == 0) {
+					lastTime = FormEngineValidation.getTimeSecs(today);
+				}
+				if (values.valPol[1]) {
+					add = FormEngineValidation.pol(values.valPol[1], FormEngineValidation.parseInt(values.values[1]));
+				}
+				break;
+			default:
+				var index = (type == 'timesec') ? 4 : 3;
+				if (values.valPol[index]) {
+					add = FormEngineValidation.pol(values.valPol[index], FormEngineValidation.parseInt(values.values[index]));
+				}
+				if (values.values[1] && values.values[1].length > 2) {
+					if (values.valPol[2]) {
+						add = FormEngineValidation.pol(values.valPol[2], FormEngineValidation.parseInt(values.values[2]));
+					}
+					var temp = values.values[1];
+					values = FormEngineValidation.splitSingle(temp);
+				}
+				var sec = (values.values[3]) ? FormEngineValidation.parseInt(values.values[3]) : today.getUTCSeconds();
+				if (sec > 59) {
+					sec = 59;
+				}
+				var min = (values.values[2]) ? FormEngineValidation.parseInt(values.values[2]) : today.getUTCMinutes();
+				if (min > 59) {
+					min = 59;
+				}
+				var hour = (values.values[1]) ? FormEngineValidation.parseInt(values.values[1]) : today.getUTCHours();
+				if (hour >= 24) {
+					hour = 0;
+				}
+
+				var theTime = new Date(FormEngineValidation.getYear(FormEngineValidation.refDate), FormEngineValidation.refDate.getUTCMonth(), FormEngineValidation.refDate.getUTCDate(), hour, min, (( type == 'timesec' ) ? sec : 0));
+
+				// Substract timezone offset from client
+				lastTime = FormEngineValidation.convertClientTimestampToUTC(FormEngineValidation.getTimestamp(theTime), 1);
+		}
+		lastTime += add * 60;
+		if (lastTime < 0) {
+			lastTime += 24 * 60 * 60;
+		}
+		return lastTime;
+	};
+
+	/**
+	 * Parse year value
+	 *
+	 * @param {string} value
+	 * @param {string} command
+	 * @returns {*}
+	 */
+	FormEngineValidation.parseYear = function(value, command) {
+		var today = new Date();
+		var values = FormEngineValidation.split(value);
+		var add = 0;
+		switch (command) {
+			case 'd':
+			case 't':
+			case 'n':
+				FormEngineValidation.lastYear = FormEngineValidation.getYear(today);
+				if (values.valPol[1]) {
+					add = FormEngineValidation.pol(values.valPol[1], FormEngineValidation.parseInt(values.values[1]));
+				}
+				break;
+			case '+':
+			case '-':
+				if (values.valPol[1]) {
+					add = FormEngineValidation.pol(values.valPol[1], FormEngineValidation.parseInt(values.values[1]));
+				}
+				break;
+			default:
+				if (values.valPol[2]) {
+					add = FormEngineValidation.pol(values.valPol[2], FormEngineValidation.parseInt(values.values[2]));
+				}
+				var year = (values.values[1]) ? FormEngineValidation.parseInt(values.values[1]) : FormEngineValidation.getYear(today);
+				if ((year >= 0 && year < 38) || (year >= 70 && year<100) || (year >= 1902 && year < 2038)) {
+					if (year < 100) {
+						year = (year < 38) ? year += 2000 : year += 1900;
+					}
+				} else {
+					year = FormEngineValidation.getYear(today);
+				}
+				FormEngineValidation.lastYear = year;
+		}
+		FormEngineValidation.lastYear += add;
+		return FormEngineValidation.lastYear;
+	};
+
+	/**
+	 * Get year from date object
+	 *
+	 * @param {Date} timeObj
+	 * @returns {number}
+	 */
+	FormEngineValidation.getYear = function(timeObj) {
+		if (timeObj === null) {
+			return;
+		}
+		return timeObj.getUTCFullYear();
+	};
+
+	/**
+	 * Get date as timestamp from Date object
+	 *
+	 * @param {Date} timeObj
+	 * @returns {number}
+	 */
+	FormEngineValidation.getDate = function(timeObj) {
+		var theTime = new Date(FormEngineValidation.getYear(timeObj), timeObj.getUTCMonth(), timeObj.getUTCDate());
+		return FormEngineValidation.getTimestamp(theTime);
+	};
+
+	/**
+	 *
+	 * @param {string} foreign
+	 * @param {string} value
+	 * @returns {Object}
+	 */
+	FormEngineValidation.pol = function(foreign, value) {
+		return eval(((foreign == '-') ? '-' : '') + value);
+	};
+
+	/**
+	 * Substract timezone offset from client to a timestamp to get UTC-timestamp to be send to server
+	 *
+	 * @param {number} timestamp
+	 * @param {number} timeonly
+	 * @returns {*}
+	 */
+	FormEngineValidation.convertClientTimestampToUTC = function(timestamp, timeonly) {
+		var timeObj = new Date(timestamp*1000);
+		timeObj.setTime((timestamp - timeObj.getTimezoneOffset()*60)*1000);
+		if (timeonly) {
+			// only seconds since midnight
+			return FormEngineValidation.getTime(timeObj);
+		} else {
+			// seconds since the "unix-epoch"
+			return FormEngineValidation.getTimestamp(timeObj);
+		}
+	};
+
+	/**
+	 * Parse date string or object and return unix timestamp
+	 *
+	 * @param {string} timeObj
+	 * @returns {number}
+	 */
+	FormEngineValidation.getTimestamp = function(timeObj) {
+		return Date.parse(timeObj)/1000;
+	};
+
+	/**
+	 * Seconds since midnight
+	 *
+	 * @param timeObj
+	 * @returns {*}
+	 */
+	FormEngineValidation.getTime = function(timeObj) {
+		return timeObj.getUTCHours() * 60 * 60 + timeObj.getUTCMinutes() * 60 + FormEngineValidation.getSecs(timeObj);
+	};
+
+	/**
+	 *
+	 * @param timeObj
+	 * @returns {number}
+	 */
+	FormEngineValidation.getSecs = function(timeObj) {
+		return timeObj.getUTCSeconds();
+	};
+
+	/**
+	 *
+	 * @param timeObj
+	 * @returns {number}
+	 */
+	FormEngineValidation.getTimeSecs = function(timeObj) {
+		return timeObj.getHours() * 60 * 60 + timeObj.getMinutes() * 60 + timeObj.getSeconds();
+	};
+
+	/**
+	 * Find tab by field and mark it as has-validation-error
+	 *
+	 * @param {object} $element
 	 */
 	FormEngineValidation.markParentTab = function($element) {
 		var $panes = $element.parents('.tab-pane');
@@ -177,24 +908,98 @@ define('TYPO3/CMS/Backend/FormEngineValidation', ['jquery', 'TYPO3/CMS/Backend/F
 	};
 
 	/**
-	 * helper function for console.log message
 	 *
-	 * @param msg
+	 * @param value
+	 * @returns {{values: Array, pointer: number}}
+	 */
+	FormEngineValidation.splitSingle = function(value) {
+		var theVal = '' + value;
+		var result = {
+			values: [],
+			pointer: 3
+		};
+		result.values[1] = theVal.substr(0,2);
+		result.values[2] = theVal.substr(2,2);
+		result.values[3] = theVal.substr(4,10);
+		return result;
+	};
+
+	/**
+	 *
+	 * @param theStr1
+	 * @param delim
+	 * @param index
+	 * @returns {*}
 	 */
-	FormEngineValidation.log = function(msg) {
-		if (typeof console !== 'undefined') {
-			console.log(msg);
+	FormEngineValidation.splitStr = function(theStr1, delim, index) {
+		var theStr = '' + theStr1;
+		var lengthOfDelim = delim.length;
+		sPos = -lengthOfDelim;
+		if (index < 1) {
+			index = 1;
 		}
+		for (a = 1; a < index; a++) {
+			sPos = theStr.indexOf(delim, sPos + lengthOfDelim);
+			if (sPos == -1) {
+				return null;
+			}
+		}
+		ePos = theStr.indexOf(delim, sPos + lengthOfDelim);
+		if (ePos == -1) {
+			ePos = theStr.length;
+		}
+		return (theStr.substring(sPos + lengthOfDelim, ePos));
 	};
 
 	/**
-	 * initialize function
+	 *
+	 * @param value
+	 * @returns {{values: Array, valPol: Array, pointer: number, numberMode: number, theVal: string}}
 	 */
-	FormEngineValidation.initialize();
-	// Start first validation after one second, because all fields are initial empty (typo3form.fieldSet)
-	window.setTimeout(function() {
-		FormEngineValidation.validate();
-	}, 1000);
+	FormEngineValidation.split = function(value) {
+		var result = {
+			values: [],
+			valPol: [],
+			pointer: 0,
+			numberMode: 0,
+			theVal: ''
+		};
+		value += ' ';
+		for (var a=0; a < value.length; a++) {
+			var theChar = value.substr(a, 1);
+			if (theChar < '0' || theChar > '9') {
+				if (result.numberMode) {
+					result.pointer++;
+					result.values[result.pointer] = result.theVal;
+					result.theVal = '';
+					result.numberMode = 0;
+				}
+				if (theChar == '+' || theChar == '-') {
+					result.valPol[result.pointer + 1] = theChar;
+				}
+			} else {
+				result.theVal += theChar;
+				result.numberMode = 1;
+			}
+		}
+		return result;
+	};
+
+	FormEngineValidation.registerReady = function() {
+		$(document).ready(function() {
+			FormEngineValidation.initialize();
+			// Start first validation after one second, because all fields are initial empty (typo3form.fieldSet)
+			window.setTimeout(function() {
+				FormEngineValidation.validate();
+			}, 1000);
+		});
+	};
+
+	/**
+	 * Initialize function
+	 */
+
 
 	FormEngine.Validation = FormEngineValidation;
+	return FormEngine.Validation;
 });
diff --git a/typo3/sysext/backend/Resources/Public/JavaScript/jsfunc.evalfield.js b/typo3/sysext/backend/Resources/Public/JavaScript/jsfunc.evalfield.js
index ce40145dbc41..d62fe626af7e 100644
--- a/typo3/sysext/backend/Resources/Public/JavaScript/jsfunc.evalfield.js
+++ b/typo3/sysext/backend/Resources/Public/JavaScript/jsfunc.evalfield.js
@@ -38,9 +38,9 @@ function evalFunc() {
 	this.ltrim = evalFunc_ltrim;
 	this.btrim = evalFunc_btrim;
 	var today = new Date();
- 	this.lastYear = this.getYear(today);
- 	this.lastDate = this.getDate(today);
- 	this.lastTime = 0;
+	this.lastYear = this.getYear(today);
+	this.lastDate = this.getDate(today);
+	this.lastTime = 0;
 	this.refDate = today;
 	this.isInString = '';
 	this.USmode = 0;
@@ -101,7 +101,7 @@ function evalFunc_caseSwitch(type,inVal) {
 					newString+=theChar;
 				}
 			}
-		break;
+			break;
 		case "is_in":
 			if (this.isInString) {
 				for (var a=0;a<theVal.length;a++) {
@@ -111,16 +111,16 @@ function evalFunc_caseSwitch(type,inVal) {
 					}
 				}
 			} else {newString = theVal;}
-		break;
+			break;
 		case "nospace":
 			newString = this.noSpace(theVal);
-		break;
+			break;
 		case "upper":
 			newString = theVal.toUpperCase();
-		break;
+			break;
 		case "lower":
 			newString = theVal.toLowerCase();
-		break;
+			break;
 		default:
 			return inVal;
 	}
@@ -164,7 +164,7 @@ function evalFunc_parseDouble(value) {
 	var dec = parts.pop();
 	theVal = Number(parts.join("") + "." + dec);
 	if (negative) {
-	    theVal *= -1;
+		theVal *= -1;
 	}
 	theVal = theVal.toFixed(2);
 
@@ -272,31 +272,31 @@ function evalFunc_input(type,inVal) {
 					if (values.valPol[1]) {
 						add = this.pol(values.valPol[1],this.parseInt(values.values[1]));
 					}
-				break;
+					break;
 				case "+":
 				case "-":
 					if (this.lastTime == 0) {
 						this.lastTime = this.convertClientTimestampToUTC(this.getTimestamp(today), 0);
 					}
 					if (values.valPol[1]) {
-						add = this.pol(values.valPol[1],this.parseInt(values.values[1]));
+						add = this.pol(values.valPol[1], this.parseInt(values.values[1]));
 					}
-				break;
+					break;
 				default:
 					var index = value.indexOf(' ');
 					if (index!=-1) {
-						var dateVal = this.input("date",value.substr(index,value.length));
-							// set refDate so that evalFunc_input on time will work with correct DST information
+						var dateVal = this.input("date", value.substr(index,value.length));
+						// set refDate so that evalFunc_input on time will work with correct DST information
 						this.refDate = new Date(dateVal*1000);
-						this.lastTime = dateVal + this.input("time",value.substr(0,index));
+						this.lastTime = dateVal + this.input("time", value.substr(0,index));
 					} else	{
-							// only date, no time
+						// only date, no time
 						this.lastTime = this.input("date", value);
 					}
 			}
 			this.lastTime+=add*24*60*60;
 			return this.lastTime;
-		break;
+			break;
 		case "year":
 			switch (theCmd) {
 				case "d":
@@ -306,13 +306,13 @@ function evalFunc_input(type,inVal) {
 					if (values.valPol[1]) {
 						add = this.pol(values.valPol[1],this.parseInt(values.values[1]));
 					}
-				break;
+					break;
 				case "+":
 				case "-":
 					if (values.valPol[1]) {
 						add = this.pol(values.valPol[1],this.parseInt(values.values[1]));
 					}
-				break;
+					break;
 				default:
 					if (values.valPol[2]) {
 						add = this.pol(values.valPol[2],this.parseInt(values.values[2]));
@@ -329,7 +329,7 @@ function evalFunc_input(type,inVal) {
 			}
 			this.lastYear+=add;
 			return this.lastYear;
-		break;
+			break;
 		case "date":
 			switch (theCmd) {
 				case "d":
@@ -339,13 +339,13 @@ function evalFunc_input(type,inVal) {
 					if (values.valPol[1]) {
 						add = this.pol(values.valPol[1],this.parseInt(values.values[1]));
 					}
-				break;
+					break;
 				case "+":
 				case "-":
 					if (values.valPol[1]) {
 						add = this.pol(values.valPol[1],this.parseInt(values.values[1]));
 					}
-				break;
+					break;
 				default:
 					var index = 4;
 					if (values.valPol[index]) {
@@ -372,12 +372,12 @@ function evalFunc_input(type,inVal) {
 
 					var theTime = new Date(parseInt(year), parseInt(month)-1, parseInt(day));
 
-						// Substract timezone offset from client
+					// Substract timezone offset from client
 					this.lastDate = this.convertClientTimestampToUTC(this.getTimestamp(theTime), 0);
 			}
 			this.lastDate+=add*24*60*60;
 			return this.lastDate;
-		break;
+			break;
 		case "time":
 		case "timesec":
 			switch (theCmd) {
@@ -388,7 +388,7 @@ function evalFunc_input(type,inVal) {
 					if (values.valPol[1]) {
 						add = this.pol(values.valPol[1],this.parseInt(values.values[1]));
 					}
-				break;
+					break;
 				case "+":
 				case "-":
 					if (this.lastTime == 0) {
@@ -397,7 +397,7 @@ function evalFunc_input(type,inVal) {
 					if (values.valPol[1]) {
 						add = this.pol(values.valPol[1],this.parseInt(values.values[1]));
 					}
-				break;
+					break;
 				default:
 					var index = (type=="timesec")?4:3;
 					if (values.valPol[index]) {
@@ -419,13 +419,13 @@ function evalFunc_input(type,inVal) {
 
 					var theTime = new Date(this.getYear(this.refDate), this.refDate.getUTCMonth(), this.refDate.getUTCDate(), hour, min, ((type=="timesec")?sec:0));
 
-						// Substract timezone offset from client
+					// Substract timezone offset from client
 					this.lastTime = this.convertClientTimestampToUTC(this.getTimestamp(theTime), 1);
 			}
 			this.lastTime+=add*60;
 			if (this.lastTime<0) {this.lastTime+=24*60*60;}
 			return this.lastTime;
-		break;
+			break;
 		default:
 			return value;
 	}
@@ -441,11 +441,11 @@ function evalFunc_output(type,value,FObj) {
 			} else {
 				theString = theTime.getUTCDate()+'-'+(theTime.getUTCMonth()+1)+'-'+this.getYear(theTime);
 			}
-		break;
+			break;
 		case "datetime":
 			if (!parseInt(value))	{return '';}
 			theString = this.output("time",value)+' '+this.output("date",value);
-		break;
+			break;
 		case "time":
 		case "timesec":
 			if (!parseInt(value))	{return '';}
@@ -454,13 +454,13 @@ function evalFunc_output(type,value,FObj) {
 			var m = theTime.getUTCMinutes();
 			var s = theTime.getUTCSeconds();
 			theString = h+':'+((m<10)?'0':'')+m + ((type=="timesec")?':'+((s<10)?'0':'')+s:'');
-		break;
+			break;
 		case "password":
 			theString = (value)	? TS.passwordDummy : "";
-		break;
+			break;
 		case "int":
 			theString = (FObj.checkbox && value==FObj.checkboxValue)?'':value;
-		break;
+			break;
 		default:
 			theString = value;
 	}
@@ -512,10 +512,10 @@ function evalFunc_convertClientTimestampToUTC(timestamp, timeonly) {
 	var timeObj = new Date(timestamp*1000);
 	timeObj.setTime((timestamp - timeObj.getTimezoneOffset()*60)*1000);
 	if (timeonly) {
-			// only seconds since midnight
+		// only seconds since midnight
 		return this.getTime(timeObj);
 	} else	{
-			// seconds since the "unix-epoch"
+		// seconds since the "unix-epoch"
 		return this.getTimestamp(timeObj);
 	}
 }
diff --git a/typo3/sysext/backend/Resources/Public/JavaScript/jsfunc.tbe_editor.js b/typo3/sysext/backend/Resources/Public/JavaScript/jsfunc.tbe_editor.js
index ce56cd7fde45..dbc1c7e1b02b 100644
--- a/typo3/sysext/backend/Resources/Public/JavaScript/jsfunc.tbe_editor.js
+++ b/typo3/sysext/backend/Resources/Public/JavaScript/jsfunc.tbe_editor.js
@@ -150,6 +150,8 @@ var TBE_EDITOR = {
 
 		// modify the "field has changed" info by adding a class to the container element (based on palette or main field)
 		var $formField = TYPO3.jQuery('[name="' + el + '"]');
+		var $humanReadableField = TYPO3.jQuery('[name="' + el + '_hr"]');
+		$humanReadableField.triggerHandler('change');
 		var $paletteField = $formField.closest('.t3js-formengine-palette-field');
 		$paletteField.addClass('has-change');
 
@@ -347,8 +349,8 @@ function typoSetup	() {
 	this.passwordDummy = '********';
 	this.decimalSign = '.';
 }
+// @todo: maybe obsolete, need a deeper check
 var TS = new typoSetup();
-var evalFunc = new evalFunc();
 
 // backwards compatibility for extensions
 var TBE_EDITOR_setHiddenContent = TBE_EDITOR.setHiddenContent;
@@ -440,6 +442,15 @@ var typo3form = {
 	}
 };
 
+// @TODO: This function is a copy from jsfunc.evalfield.js
+// @TODO: Remove it later, after TBE_EDITOR is not used anymore.
+function evalFunc_dummy (evallist,is_in,checkbox,checkboxValue) {
+	this.evallist = evallist;
+	this.is_in = is_in;
+	this.checkboxValue = checkboxValue;
+	this.checkbox = checkbox;
+}
+
 // backwards compatibility for extensions
 var typo3FormFieldSet = typo3form.fieldSet;
 var typo3FormFieldGet = typo3form.fieldGet;
diff --git a/typo3/sysext/core/Documentation/Changelog/master/Important-67852-RemoveJsfuncevalfieldjsFromFormEngine.rst b/typo3/sysext/core/Documentation/Changelog/master/Important-67852-RemoveJsfuncevalfieldjsFromFormEngine.rst
new file mode 100644
index 000000000000..b2dab2054fdb
--- /dev/null
+++ b/typo3/sysext/core/Documentation/Changelog/master/Important-67852-RemoveJsfuncevalfieldjsFromFormEngine.rst
@@ -0,0 +1,12 @@
+==============================================================
+Important: #67852 - Remove jsfunc.evalfield.js from FormEngine
+==============================================================
+
+Description
+===========
+
+After 12 years, the usage of ``jsfunc.evalfield.js`` has been removed from ``FormEngine``.
+The JavaScript has been moved into FormEngineValidation AMD module.
+Processors and Validator has been split up in two different function.
+
+Including the ``jsfunc.evalfield.js`` still works, but will be removed shortly.
\ No newline at end of file
diff --git a/typo3/sysext/frontend/Configuration/TCA/tt_content.php b/typo3/sysext/frontend/Configuration/TCA/tt_content.php
index ba85fe66974a..f959ab8d85b0 100644
--- a/typo3/sysext/frontend/Configuration/TCA/tt_content.php
+++ b/typo3/sysext/frontend/Configuration/TCA/tt_content.php
@@ -471,8 +471,8 @@ return array(
 				'max' => '4',
 				'eval' => 'int',
 				'range' => array(
-					'upper' => '999',
-					'lower' => '25'
+					'upper' => 1999,
+					'lower' => 0,
 				),
 				'default' => 0
 			)
@@ -486,8 +486,8 @@ return array(
 				'max' => '4',
 				'eval' => 'int',
 				'range' => array(
-					'upper' => '700',
-					'lower' => '25'
+					'upper' => 1999,
+					'lower' => 0,
 				),
 				'default' => 0
 			)
diff --git a/typo3/sysext/recordlist/Classes/Browser/ElementBrowser.php b/typo3/sysext/recordlist/Classes/Browser/ElementBrowser.php
index 468fb6b4dcf0..150dfb0b9c12 100644
--- a/typo3/sysext/recordlist/Classes/Browser/ElementBrowser.php
+++ b/typo3/sysext/recordlist/Classes/Browser/ElementBrowser.php
@@ -618,7 +618,9 @@ class ElementBrowser {
 						} else {
 							field.value = input;
 						}
-						field.onchange();
+						if (typeof field.onchange === \'function\') {
+							field.onchange();
+						}
 						' . $update . '
 					}
 				}
-- 
GitLab