8ba8c8a39a86f377331f5cd3ea5c1b2f4efdba25
[Packages/TYPO3.CMS.git] / typo3 / sysext / backend / Resources / Public / JavaScript / FormEngineValidation.js
1 /*
2 * This file is part of the TYPO3 CMS project.
3 *
4 * It is free software; you can redistribute it and/or modify it under
5 * the terms of the GNU General Public License, either version 2
6 * of the License, or any later version.
7 *
8 * For the full copyright and license information, please read the
9 * LICENSE.txt file that was distributed with this source code.
10 *
11 * The TYPO3 project - inspiring people to share!
12 */
13
14 /**
15 * Module: TYPO3/CMS/Backend/FormEngineValidation
16 * Contains all JS functions related to TYPO3 TCEforms/FormEngineValidation
17 * @internal
18 */
19 define(['jquery', 'TYPO3/CMS/Backend/FormEngine'], function ($, FormEngine) {
20
21 /**
22 * The main FormEngineValidation object
23 *
24 * @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}}
25 * @exports TYPO3/CMS/Backend/FormEngineValidation
26 */
27 var FormEngineValidation = {
28 rulesSelector: '[data-formengine-validation-rules]',
29 inputSelector: '[data-formengine-input-params]',
30 markerSelector: '.t3js-formengine-validation-marker',
31 dateTimeSelector: '.t3js-datetimepicker',
32 groupFieldHiddenElement: '.t3js-formengine-field-group input[type=hidden]',
33 relatedFieldSelector: '[data-relatedfieldname]',
34 errorClass: 'has-error',
35 lastYear: 0,
36 lastDate: 0,
37 lastTime: 0,
38 refDate: new Date(),
39 USmode: 0,
40 passwordDummy: '********'
41 };
42
43 /**
44 * Initialize validation for the first time
45 */
46 FormEngineValidation.initialize = function() {
47 $(document).find('.' + FormEngineValidation.errorClass).removeClass(FormEngineValidation.errorClass);
48
49 // Initialize input fields
50 FormEngineValidation.initializeInputFields().promise().done(function () {
51 // Bind to field changes
52 $(document).on('change', FormEngineValidation.rulesSelector, function() {
53 // we need to wait, because the update of the select fields needs some time
54 window.setTimeout(function() {
55 FormEngineValidation.validate();
56 }, 500);
57 var $paletteField = $(this).closest('.t3js-formengine-palette-field');
58 $paletteField.addClass('has-change');
59 });
60
61 // Bind to datepicker change event, but wait some milliseconds, because the init is not so fast
62 window.setTimeout(function() {
63 //noinspection JSUnusedLocalSymbols
64 $(document).on('dp.change', FormEngineValidation.dateTimeSelector, function(event) {
65 FormEngineValidation.validate();
66 var $paletteField = $(this).closest('.t3js-formengine-palette-field');
67 $paletteField.addClass('has-change');
68 });
69 }, 500);
70 });
71
72 var today = new Date();
73 FormEngineValidation.lastYear = FormEngineValidation.getYear(today);
74 FormEngineValidation.lastDate = FormEngineValidation.getDate(today);
75 FormEngineValidation.lastTime = 0;
76 FormEngineValidation.refDate = today;
77 FormEngineValidation.USmode = 0;
78 };
79
80 /**
81 * Initialize all input fields
82 *
83 * @returns {Object}
84 */
85 FormEngineValidation.initializeInputFields = function() {
86 return $(document).find(FormEngineValidation.inputSelector).each(function() {
87 var config = $(this).data('formengine-input-params');
88 var fieldName = config.field;
89 var $field = $('[name="' + fieldName + '"]');
90
91 // ignore fields which already have been initialized
92 if ($field.data('main-field') === undefined) {
93 $field.data('main-field', fieldName);
94 $field.data('config', config);
95 FormEngineValidation.initializeInputField(fieldName);
96 }
97 });
98 };
99
100 /**
101 *
102 * @param {Number} mode
103 */
104 FormEngineValidation.setUsMode = function(mode) {
105 FormEngineValidation.USmode = mode;
106 };
107
108 /**
109 * Initialize field by name
110 *
111 * @param {String} fieldName
112 */
113 FormEngineValidation.initializeInputField = function(fieldName) {
114 var $field = $('[name="' + fieldName + '"]');
115 var $humanReadableField = $('[data-formengine-input-name="' + fieldName + '"]');
116 var $mainField = $('[name="' + $field.data('main-field') + '"]');
117 if ($mainField.length === 0) {
118 $mainField = $field;
119 }
120
121 var config = $mainField.data('config');
122 if (typeof config !== 'undefined') {
123 var evalList = FormEngineValidation.trimExplode(',', config.evalList);
124 var value = $field.val();
125
126 for (var i = 0; i < evalList.length; i++) {
127 value = FormEngineValidation.formatValue(evalList[i], value, config)
128 }
129 // Prevent password fields to be overwritten with original value
130 if (value.length && $humanReadableField.attr('type') != 'password') {
131 $humanReadableField.val(value);
132 }
133 }
134 $humanReadableField.data('main-field', fieldName);
135 $humanReadableField.data('config', config);
136 $humanReadableField.on('change', function() {
137 FormEngineValidation.updateInputField($(this).attr('data-formengine-input-name'));
138 });
139 $humanReadableField.on('keyup', FormEngineValidation.validate);
140
141 // add the attribute so that acceptance tests can know when the field initialization has completed
142 $humanReadableField.attr('data-formengine-input-initialized', 'true');
143 };
144
145 /**
146 * Format field value
147 *
148 * @param {String} type
149 * @param {String} value
150 * @param {array} config
151 * @returns {String}
152 */
153 FormEngineValidation.formatValue = function(type, value, config) {
154 var theString = '';
155 switch (type) {
156 case 'date':
157 var parsedInt = parseInt(value);
158 if (!parsedInt) {
159 return '';
160 }
161 theTime = new Date(parsedInt * 1000);
162 if (FormEngineValidation.USmode) {
163 theString = (theTime.getUTCMonth() + 1) + '-' + theTime.getUTCDate() + '-' + this.getYear(theTime);
164 } else {
165 theString = theTime.getUTCDate() + '-' + (theTime.getUTCMonth() + 1) + '-' + this.getYear(theTime);
166 }
167 break;
168 case 'datetime':
169 if (!parseInt(value)) {
170 return '';
171 }
172 theString = FormEngineValidation.formatValue('time', value, config) + ' ' + FormEngineValidation.formatValue('date', value, config);
173 break;
174 case 'time':
175 case 'timesec':
176 var parsedInt = parseInt(value);
177 if (!parsedInt && value.toString() !== '0') {
178 return '';
179 }
180 var theTime = new Date(parsedInt * 1000);
181 var h = theTime.getUTCHours();
182 var m = theTime.getUTCMinutes();
183 var s = theTime.getUTCSeconds();
184 theString = h + ':' + ((m < 10) ? '0' : '') + m + ((type == 'timesec') ? ':' + ((s < 10) ? '0' : '') + s : '');
185 break;
186 case 'password':
187 theString = (value) ? FormEngineValidation.passwordDummy : '';
188 break;
189 default:
190 theString = value;
191 }
192 return theString;
193 };
194
195 /**
196 * Update input field after change
197 *
198 * @param {String} fieldName
199 */
200 FormEngineValidation.updateInputField = function(fieldName) {
201 var $field = $('[name="' + fieldName + '"]');
202 var $mainField = $('[name="' + $field.data('main-field') + '"]');
203 if ($mainField.length === 0) {
204 $mainField = $field;
205 }
206 var $humanReadableField = $('[data-formengine-input-name="' + $mainField.attr('name') + '"]');
207
208 var config = $mainField.data('config');
209 if (typeof config !== 'undefined') {
210 var evalList = FormEngineValidation.trimExplode(',', config.evalList);
211 var origValue = $humanReadableField.val();
212 var newValue = $humanReadableField.val();
213 var i;
214
215 for (i = 0; i < evalList.length; i++) {
216 newValue = FormEngineValidation.processValue(evalList[i], newValue, config);
217 }
218
219 var formattedValue = newValue;
220 for (i = 0; i < evalList.length; i++) {
221 formattedValue = FormEngineValidation.formatValue(evalList[i], formattedValue, config);
222 }
223
224 if ($.inArray('password', evalList) !== -1) {
225 $mainField.val(origValue);
226 $humanReadableField.val(newValue);
227 } else {
228 $mainField.val(newValue);
229 $humanReadableField.val(formattedValue);
230 }
231 }
232 };
233
234 /**
235 * Run validation for field
236 *
237 * @param {Object} $field
238 * @param {String} [value=$field.val()]
239 * @returns {String}
240 */
241 FormEngineValidation.validateField = function($field, value) {
242 value = value || $field.val() || '';
243
244 var rules = $field.data('formengine-validation-rules');
245 var markParent = false;
246 var selected = 0;
247 // keep the original value, validateField should not alter it
248 var returnValue = value;
249 var $relatedField;
250 var minItems;
251 var maxItems;
252
253 if (!$.isArray(value)) {
254 value = FormEngineValidation.ltrim(value);
255 }
256
257 $.each(rules, function(k, rule) {
258 switch (rule.type) {
259 case 'required':
260 if (value === '') {
261 markParent = true;
262 $field.closest(FormEngineValidation.markerSelector).addClass(FormEngineValidation.errorClass);
263 }
264 break;
265 case 'range':
266 if (value !== '') {
267 if (rule.minItems || rule.maxItems) {
268 $relatedField = $(document).find('[name="' + $field.data('relatedfieldname') + '"]');
269 if ($relatedField.length) {
270 selected = FormEngineValidation.trimExplode(',', $relatedField.val()).length;
271 } else {
272 selected = $field.val();
273 }
274 if (typeof rule.minItems !== 'undefined') {
275 minItems = rule.minItems * 1;
276 if (!isNaN(minItems) && selected < minItems) {
277 markParent = true;
278 }
279 }
280 if (typeof rule.maxItems !== 'undefined') {
281 maxItems = rule.maxItems * 1;
282 if (!isNaN(maxItems) && selected > maxItems) {
283 markParent = true;
284 }
285 }
286 }
287 if (typeof rule.config.lower !== 'undefined') {
288 var minValue = rule.config.lower * 1;
289 if (!isNaN(minValue) && value < minValue) {
290 markParent = true;
291 }
292 }
293 if (typeof rule.config.upper !== 'undefined') {
294 var maxValue = rule.config.upper * 1;
295 if (!isNaN(maxValue) && value > maxValue) {
296 markParent = true;
297 }
298 }
299 }
300 break;
301 case 'select':
302 if (rule.minItems || rule.maxItems) {
303 $relatedField = $(document).find('[name="' + $field.data('relatedfieldname') + '"]');
304 if ($relatedField.length) {
305 selected = FormEngineValidation.trimExplode(',', $relatedField.val()).length;
306 } else {
307 selected = $field.find('option:selected').length;
308 }
309 if (typeof rule.minItems !== 'undefined') {
310 minItems = rule.minItems * 1;
311 if (!isNaN(minItems) && selected < minItems) {
312 markParent = true;
313 }
314 }
315 if (typeof rule.maxItems !== 'undefined') {
316 maxItems = rule.maxItems * 1;
317 if (!isNaN(maxItems) && selected > maxItems) {
318 markParent = true;
319 }
320 }
321 }
322 break;
323 case 'group':
324 if (rule.minItems || rule.maxItems) {
325 selected = $field.find('option').length;
326 if (typeof rule.minItems !== 'undefined') {
327 minItems = rule.minItems * 1;
328 if (!isNaN(minItems) && selected < minItems) {
329 markParent = true;
330 }
331 }
332 if (typeof rule.maxItems !== 'undefined') {
333 maxItems = rule.maxItems * 1;
334 if (!isNaN(maxItems) && selected > maxItems) {
335 markParent = true;
336 }
337 }
338 }
339 break;
340 case 'inline':
341 if (rule.minItems || rule.maxItems) {
342 selected = FormEngineValidation.trimExplode(',', $field.val()).length;
343 if (typeof rule.minItems !== 'undefined') {
344 minItems = rule.minItems * 1;
345 if (!isNaN(minItems) && selected < minItems) {
346 markParent = true;
347 }
348 }
349 if (typeof rule.maxItems !== 'undefined') {
350 maxItems = rule.maxItems * 1;
351 if (!isNaN(maxItems) && selected > maxItems) {
352 markParent = true;
353 }
354 }
355 }
356 break;
357 case 'null':
358 // unknown type null, we ignore it
359 break;
360 }
361 });
362 if (markParent) {
363 // mark field
364 $field.closest(FormEngineValidation.markerSelector).addClass(FormEngineValidation.errorClass);
365
366 // check tabs
367 FormEngineValidation.markParentTab($field);
368 }
369 return returnValue;
370 };
371
372 /**
373 * Process a value by given command and config
374 *
375 * @param {String} command
376 * @param {String} value
377 * @param {Array} config
378 * @returns {String}
379 */
380 FormEngineValidation.processValue = function(command, value, config) {
381 var newString = '';
382 var theValue = '';
383 var theCmd = '';
384 var a = 0;
385 var returnValue = value;
386 switch (command) {
387 case 'alpha':
388 case 'num':
389 case 'alphanum':
390 case 'alphanum_x':
391 newString = '';
392 for (a = 0; a < value.length; a++) {
393 theChar = value.substr(a, 1);
394 var special = (theChar === '_' || theChar === '-');
395 var alpha = (theChar >= 'a' && theChar <= 'z') || (theChar >= 'A' && theChar <= 'Z');
396 var num = (theChar >= '0' && theChar <= '9');
397 switch (command) {
398 case 'alphanum':
399 special = 0;
400 break;
401 case 'alpha':
402 num = 0;
403 special = 0;
404 break;
405 case 'num':
406 alpha = 0;
407 special = 0;
408 break;
409 }
410 if (alpha || num || special) {
411 newString += theChar;
412 }
413 }
414 if (newString !== value) {
415 returnValue = newString;
416 }
417 break;
418 case 'is_in':
419 if (config.is_in) {
420 theValue = '' + value;
421 for (a = 0; a < theValue.length; a++) {
422 var theChar = theValue.substr(a, 1);
423 if (config.is_in.indexOf(theChar) != -1) {
424 newString += theChar;
425 }
426 }
427 } else {
428 newString = theValue;
429 }
430 returnValue = newString;
431 break;
432 case 'nospace':
433 returnValue = '' + value.replace(/ /g, '');
434 break;
435 case 'md5':
436 if (value !== '') {
437 returnValue = MD5(value);
438 }
439 break;
440 case 'upper':
441 returnValue = value.toUpperCase();
442 break;
443 case 'lower':
444 returnValue = value.toLowerCase();
445 break;
446 case 'int':
447 if (value !== '') {
448 returnValue = FormEngineValidation.parseInt(value);
449 }
450 break;
451 case 'double2':
452 if (value !== '') {
453 returnValue = FormEngineValidation.parseDouble(value);
454 }
455 break;
456 case 'trim':
457 returnValue = String(value).trim();
458 break;
459 case 'datetime':
460 if (value !== '') {
461 theCmd = value.substr(0, 1);
462 returnValue = FormEngineValidation.parseDateTime(value, theCmd);
463 }
464 break;
465 case 'date':
466 if (value !== '') {
467 theCmd = value.substr(0, 1);
468 returnValue = FormEngineValidation.parseDate(value, theCmd);
469 }
470 break;
471 case 'time':
472 case 'timesec':
473 if (value !== '') {
474 theCmd = value.substr(0, 1);
475 returnValue = FormEngineValidation.parseTime(value, theCmd, command);
476 }
477 break;
478 case 'year':
479 if (value !== '') {
480 theCmd = value.substr(0, 1);
481 returnValue = FormEngineValidation.parseYear(value, theCmd);
482 }
483 break;
484 case 'null':
485 // unknown type null, we ignore it
486 break;
487 case 'password':
488 var theString = (value) ? FormEngineValidation.passwordDummy : '';
489 returnValue = theString;
490 break;
491 default:
492 if (typeof TBE_EDITOR.customEvalFunctions !== 'undefined' && typeof TBE_EDITOR.customEvalFunctions[command] === 'function') {
493 returnValue = TBE_EDITOR.customEvalFunctions[command](value);
494 }
495 }
496 return returnValue;
497 };
498
499 /**
500 * Validate the complete form
501 */
502 FormEngineValidation.validate = function() {
503 $(document).find(FormEngineValidation.markerSelector + ', .t3js-tabmenu-item')
504 .removeClass(FormEngineValidation.errorClass)
505 .removeClass('has-validation-error');
506
507 $(FormEngineValidation.rulesSelector).each(function() {
508 var $field = $(this);
509 if (!$field.closest('.t3js-flex-section-deleted, .t3js-inline-record-deleted').length) {
510 var modified = false;
511 var currentValue = $field.val();
512 var newValue = FormEngineValidation.validateField($field, currentValue);
513 if ($.isArray(newValue) && $.isArray(currentValue)) {
514 // handling for multi-selects
515 if (newValue.length !== currentValue.length) {
516 modified = true;
517 } else {
518 for (var i = 0; i < newValue.length; i++) {
519 if (newValue[i] !== currentValue[i]) {
520 modified = true;
521 break;
522 }
523 }
524 }
525 } else if (newValue.length && currentValue !== newValue) {
526 modified = true;
527 }
528 if (modified) {
529 $field.val(newValue);
530 }
531 }
532 });
533 };
534
535 /**
536 * Helper function to get clean trimmed array from comma list
537 *
538 * @param {String} delimiter
539 * @param {String} string
540 * @returns {Array}
541 */
542 FormEngineValidation.trimExplode = function(delimiter, string) {
543 var result = [];
544 var items = string.split(delimiter);
545 for (var i=0; i<items.length; i++) {
546 var item = items[i].trim();
547 if (item.length > 0) {
548 result.push(item);
549 }
550 }
551 return result;
552 };
553
554 /**
555 * Parse value to integer
556 *
557 * @param {(Number|String)} value
558 * @returns {Number}
559 */
560 FormEngineValidation.parseInt = function(value) {
561 var theVal = '' + value,
562 returnValue;
563
564 if (!value) {
565 return 0;
566 }
567
568 returnValue = parseInt(theVal, 10);
569 if (isNaN(returnValue)) {
570 return 0;
571 }
572 return returnValue;
573 };
574
575 /**
576 * Parse value to double
577 *
578 * @param {String} value
579 * @returns {String}
580 */
581 FormEngineValidation.parseDouble = function(value) {
582 var theVal = '' + value;
583 theVal = theVal.replace(/[^0-9,\.-]/g, '');
584 var negative = theVal.substring(0, 1) === '-';
585 theVal = theVal.replace(/-/g, '');
586 theVal = theVal.replace(/,/g, '.');
587 if (theVal.indexOf('.') === -1) {
588 theVal += '.0';
589 }
590 var parts = theVal.split('.');
591 var dec = parts.pop();
592 theVal = Number(parts.join('') + '.' + dec);
593 if (negative) {
594 theVal *= -1;
595 }
596 theVal = theVal.toFixed(2);
597
598 return theVal;
599 };
600
601 /**
602 *
603 * @param {String} value
604 * @returns {String}
605 */
606 FormEngineValidation.ltrim = function(value) {
607 var theVal = '' + value;
608 if (!value) {
609 return '';
610 }
611 return theVal.replace(/^\s+/, '');
612 };
613
614 /**
615 *
616 * @param {String} value
617 * @returns {String}
618 */
619 FormEngineValidation.btrim = function(value) {
620 var theVal = '' + value;
621 if (!value) {
622 return '';
623 }
624 return theVal.replace(/\s+$/, '');
625 };
626
627 /**
628 * Parse datetime value
629 *
630 * @param {String} value
631 * @param {String} command
632 * @returns {*}
633 */
634 FormEngineValidation.parseDateTime = function(value, command) {
635 var today = new Date();
636 var values = FormEngineValidation.split(value);
637 var add = 0;
638 switch (command) {
639 case 'd':
640 case 't':
641 case 'n':
642 FormEngineValidation.lastTime = FormEngineValidation.convertClientTimestampToUTC(FormEngineValidation.getTimestamp(today), 0);
643 if (values.valPol[1]) {
644 add = FormEngineValidation.pol(values.valPol[1], FormEngineValidation.parseInt(values.values[1]));
645 }
646 break;
647 case '+':
648 case '-':
649 if (FormEngineValidation.lastTime == 0) {
650 FormEngineValidation.lastTime = FormEngineValidation.convertClientTimestampToUTC(FormEngineValidation.getTimestamp(today), 0);
651 }
652 if (values.valPol[1]) {
653 add = FormEngineValidation.pol(values.valPol[1], FormEngineValidation.parseInt(values.values[1]));
654 }
655 break;
656 default:
657 var index = value.indexOf(' ');
658 if (index != -1) {
659 var dateVal = FormEngineValidation.parseDate(value.substr(index, value.length), value.substr(0, 1));
660 // set refDate so that evalFunc_input on time will work with correct DST information
661 FormEngineValidation.refDate = new Date(dateVal * 1000);
662 FormEngineValidation.lastTime = dateVal + FormEngineValidation.parseTime(value.substr(0,index), value.substr(0, 1), 'time');
663 } else {
664 // only date, no time
665 FormEngineValidation.lastTime = FormEngineValidation.parseDate(value, value.substr(0, 1));
666 }
667 }
668 FormEngineValidation.lastTime += add * 24 * 60 * 60;
669 return FormEngineValidation.lastTime;
670 };
671
672 /**
673 * Parse date value
674 *
675 * @param {String} value
676 * @param {String} command
677 * @returns {*}
678 */
679 FormEngineValidation.parseDate = function(value, command) {
680 var today = new Date();
681 var values = FormEngineValidation.split(value);
682 var add = 0;
683 switch (command) {
684 case 'd':
685 case 't':
686 case 'n':
687 FormEngineValidation.lastDate = FormEngineValidation.getTimestamp(today);
688 if (values.valPol[1]) {
689 add = FormEngineValidation.pol(values.valPol[1], FormEngineValidation.parseInt(values.values[1]));
690 }
691 break;
692 case '+':
693 case '-':
694 if (values.valPol[1]) {
695 add = FormEngineValidation.pol(values.valPol[1], FormEngineValidation.parseInt(values.values[1]));
696 }
697 break;
698 default:
699 var index = 4;
700 if (values.valPol[index]) {
701 add = FormEngineValidation.pol(values.valPol[index], FormEngineValidation.parseInt(values.values[index]));
702 }
703 if (values.values[1] && values.values[1].length > 2) {
704 if (values.valPol[2]) {
705 add = FormEngineValidation.pol(values.valPol[2], FormEngineValidation.parseInt(values.values[2]));
706 }
707 var temp = values.values[1];
708 values = FormEngineValidation.splitSingle(temp);
709 }
710
711 var year = (values.values[3]) ? FormEngineValidation.parseInt(values.values[3]) : FormEngineValidation.getYear(today);
712 if ((year >= 0 && year < 38) || (year >= 70 && year < 100) || (year >= 1902 && year < 2038)) {
713 if (year < 100) {
714 year = (year < 38) ? year += 2000 : year += 1900;
715 }
716 } else {
717 year = FormEngineValidation.getYear(today);
718 }
719 var usMode = FormEngineValidation.USmode ? 1 : 2;
720 var month = (values.values[usMode]) ? FormEngineValidation.parseInt(values.values[usMode]) : today.getUTCMonth() + 1;
721 usMode = FormEngineValidation.USmode ? 2 : 1;
722 var day = (values.values[usMode]) ? FormEngineValidation.parseInt(values.values[usMode]) : today.getUTCDate();
723
724 var theTime = new Date(parseInt(year), parseInt(month)-1, parseInt(day));
725
726 // Substract timezone offset from client
727 FormEngineValidation.lastDate = FormEngineValidation.convertClientTimestampToUTC(FormEngineValidation.getTimestamp(theTime), 0);
728 }
729 FormEngineValidation.lastDate += add * 24 * 60 * 60;
730 return FormEngineValidation.lastDate;
731 };
732
733 /**
734 * Parse time value
735 *
736 * @param {String} value
737 * @param {String} command
738 * @param {String} type
739 * @returns {*}
740 */
741 FormEngineValidation.parseTime = function(value, command, type) {
742 var today = new Date();
743 var values = FormEngineValidation.split(value);
744 var add = 0;
745 switch (command) {
746 case 'd':
747 case 't':
748 case 'n':
749 FormEngineValidation.lastTime = FormEngineValidation.getTimeSecs(today);
750 if (values.valPol[1]) {
751 add = FormEngineValidation.pol(values.valPol[1], FormEngineValidation.parseInt(values.values[1]));
752 }
753 break;
754 case '+':
755 case '-':
756 if (FormEngineValidation.lastTime == 0) {
757 FormEngineValidation.lastTime = FormEngineValidation.getTimeSecs(today);
758 }
759 if (values.valPol[1]) {
760 add = FormEngineValidation.pol(values.valPol[1], FormEngineValidation.parseInt(values.values[1]));
761 }
762 break;
763 default:
764 var index = (type == 'timesec') ? 4 : 3;
765 if (values.valPol[index]) {
766 add = FormEngineValidation.pol(values.valPol[index], FormEngineValidation.parseInt(values.values[index]));
767 }
768 if (values.values[1] && values.values[1].length > 2) {
769 if (values.valPol[2]) {
770 add = FormEngineValidation.pol(values.valPol[2], FormEngineValidation.parseInt(values.values[2]));
771 }
772 var temp = values.values[1];
773 values = FormEngineValidation.splitSingle(temp);
774 }
775 var sec = (values.values[3]) ? FormEngineValidation.parseInt(values.values[3]) : today.getUTCSeconds();
776 if (sec > 59) {
777 sec = 59;
778 }
779 var min = (values.values[2]) ? FormEngineValidation.parseInt(values.values[2]) : today.getUTCMinutes();
780 if (min > 59) {
781 min = 59;
782 }
783 var hour = (values.values[1]) ? FormEngineValidation.parseInt(values.values[1]) : today.getUTCHours();
784 if (hour >= 24) {
785 hour = 0;
786 }
787
788 var theTime = new Date(FormEngineValidation.getYear(FormEngineValidation.refDate), FormEngineValidation.refDate.getUTCMonth(), FormEngineValidation.refDate.getUTCDate(), hour, min, (( type == 'timesec' ) ? sec : 0));
789
790 // Substract timezone offset from client
791 FormEngineValidation.lastTime = FormEngineValidation.convertClientTimestampToUTC(FormEngineValidation.getTimestamp(theTime), 1);
792 }
793 FormEngineValidation.lastTime += add * 60;
794 if (FormEngineValidation.lastTime < 0) {
795 FormEngineValidation.lastTime += 24 * 60 * 60;
796 }
797 return FormEngineValidation.lastTime;
798 };
799
800 /**
801 * Parse year value
802 *
803 * @param {String} value
804 * @param {String} command
805 * @returns {*}
806 */
807 FormEngineValidation.parseYear = function(value, command) {
808 var today = new Date();
809 var values = FormEngineValidation.split(value);
810 var add = 0;
811 switch (command) {
812 case 'd':
813 case 't':
814 case 'n':
815 FormEngineValidation.lastYear = FormEngineValidation.getYear(today);
816 if (values.valPol[1]) {
817 add = FormEngineValidation.pol(values.valPol[1], FormEngineValidation.parseInt(values.values[1]));
818 }
819 break;
820 case '+':
821 case '-':
822 if (values.valPol[1]) {
823 add = FormEngineValidation.pol(values.valPol[1], FormEngineValidation.parseInt(values.values[1]));
824 }
825 break;
826 default:
827 if (values.valPol[2]) {
828 add = FormEngineValidation.pol(values.valPol[2], FormEngineValidation.parseInt(values.values[2]));
829 }
830 var year = (values.values[1]) ? FormEngineValidation.parseInt(values.values[1]) : FormEngineValidation.getYear(today);
831 if ((year >= 0 && year < 38) || (year >= 70 && year<100) || (year >= 1902 && year < 2038)) {
832 if (year < 100) {
833 year = (year < 38) ? year += 2000 : year += 1900;
834 }
835 } else {
836 year = FormEngineValidation.getYear(today);
837 }
838 FormEngineValidation.lastYear = year;
839 }
840 FormEngineValidation.lastYear += add;
841 return FormEngineValidation.lastYear;
842 };
843
844 /**
845 * Get year from date object
846 *
847 * @param {Date} timeObj
848 * @returns {?number}
849 */
850 FormEngineValidation.getYear = function(timeObj) {
851 if (timeObj === null) {
852 return null;
853 }
854 return timeObj.getUTCFullYear();
855 };
856
857 /**
858 * Get date as timestamp from Date object
859 *
860 * @param {Date} timeObj
861 * @returns {Number}
862 */
863 FormEngineValidation.getDate = function(timeObj) {
864 var theTime = new Date(FormEngineValidation.getYear(timeObj), timeObj.getUTCMonth(), timeObj.getUTCDate());
865 return FormEngineValidation.getTimestamp(theTime);
866 };
867
868 /**
869 *
870 * @param {String} foreign
871 * @param {String} value
872 * @returns {Object}
873 */
874 FormEngineValidation.pol = function(foreign, value) {
875 return eval(((foreign == '-') ? '-' : '') + value);
876 };
877
878 /**
879 * Substract timezone offset from client to a timestamp to get UTC-timestamp to be send to server
880 *
881 * @param {Number} timestamp
882 * @param {Number} timeonly
883 * @returns {*}
884 */
885 FormEngineValidation.convertClientTimestampToUTC = function(timestamp, timeonly) {
886 var timeObj = new Date(timestamp*1000);
887 timeObj.setTime((timestamp - timeObj.getTimezoneOffset()*60)*1000);
888 if (timeonly) {
889 // only seconds since midnight
890 return FormEngineValidation.getTime(timeObj);
891 } else {
892 // seconds since the "unix-epoch"
893 return FormEngineValidation.getTimestamp(timeObj);
894 }
895 };
896
897 /**
898 * Parse date string or object and return unix timestamp
899 *
900 * @param {(String|Date)} timeObj
901 * @returns {Number}
902 */
903 FormEngineValidation.getTimestamp = function(timeObj) {
904 return Date.parse(timeObj)/1000;
905 };
906
907 /**
908 * Seconds since midnight
909 *
910 * @param timeObj
911 * @returns {*}
912 */
913 FormEngineValidation.getTime = function(timeObj) {
914 return timeObj.getUTCHours() * 60 * 60 + timeObj.getUTCMinutes() * 60 + FormEngineValidation.getSecs(timeObj);
915 };
916
917 /**
918 *
919 * @param timeObj
920 * @returns {Number}
921 */
922 FormEngineValidation.getSecs = function(timeObj) {
923 return timeObj.getUTCSeconds();
924 };
925
926 /**
927 *
928 * @param timeObj
929 * @returns {Number}
930 */
931 FormEngineValidation.getTimeSecs = function(timeObj) {
932 return timeObj.getHours() * 60 * 60 + timeObj.getMinutes() * 60 + timeObj.getSeconds();
933 };
934
935 /**
936 * Find tab by field and mark it as has-validation-error
937 *
938 * @param {Object} $element
939 */
940 FormEngineValidation.markParentTab = function($element) {
941 var $panes = $element.parents('.tab-pane');
942 $panes.each(function() {
943 var $pane = $(this);
944 var id = $pane.attr('id');
945 $(document)
946 .find('a[href="#' + id + '"]')
947 .closest('.t3js-tabmenu-item')
948 .addClass('has-validation-error');
949 });
950 };
951
952 /**
953 *
954 * @param value
955 * @returns {{values: Array, pointer: number}}
956 */
957 FormEngineValidation.splitSingle = function(value) {
958 var theVal = '' + value;
959 var result = {
960 values: [],
961 pointer: 3
962 };
963 result.values[1] = theVal.substr(0,2);
964 result.values[2] = theVal.substr(2,2);
965 result.values[3] = theVal.substr(4,10);
966 return result;
967 };
968
969 /**
970 *
971 * @param theStr1
972 * @param delim
973 * @param index
974 * @returns {*}
975 */
976 FormEngineValidation.splitStr = function(theStr1, delim, index) {
977 var theStr = '' + theStr1;
978 var lengthOfDelim = delim.length;
979 var sPos = -lengthOfDelim;
980 if (index < 1) {
981 index = 1;
982 }
983 for (var a = 1; a < index; a++) {
984 sPos = theStr.indexOf(delim, sPos + lengthOfDelim);
985 if (sPos == -1) {
986 return null;
987 }
988 }
989 var ePos = theStr.indexOf(delim, sPos + lengthOfDelim);
990 if (ePos == -1) {
991 ePos = theStr.length;
992 }
993 return (theStr.substring(sPos + lengthOfDelim, ePos));
994 };
995
996 /**
997 *
998 * @param value
999 * @returns {{values: Array, valPol: Array, pointer: number, numberMode: number, theVal: string}}
1000 */
1001 FormEngineValidation.split = function(value) {
1002 var result = {
1003 values: [],
1004 valPol: [],
1005 pointer: 0,
1006 numberMode: 0,
1007 theVal: ''
1008 };
1009 value += ' ';
1010 for (var a=0; a < value.length; a++) {
1011 var theChar = value.substr(a, 1);
1012 if (theChar < '0' || theChar > '9') {
1013 if (result.numberMode) {
1014 result.pointer++;
1015 result.values[result.pointer] = result.theVal;
1016 result.theVal = '';
1017 result.numberMode = 0;
1018 }
1019 if (theChar == '+' || theChar == '-') {
1020 result.valPol[result.pointer + 1] = theChar;
1021 }
1022 } else {
1023 result.theVal += theChar;
1024 result.numberMode = 1;
1025 }
1026 }
1027 return result;
1028 };
1029
1030 FormEngineValidation.registerReady = function() {
1031 $(function() {
1032 FormEngineValidation.initialize();
1033 // Start first validation after one second, because all fields are initial empty (typo3form.fieldSet)
1034 window.setTimeout(function() {
1035 FormEngineValidation.validate();
1036 }, 1000);
1037 });
1038 };
1039
1040 FormEngine.Validation = FormEngineValidation;
1041
1042 return FormEngine.Validation;
1043 });