Support.php 48.8 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
<?php
/**
 * Created by PhpStorm.
 * User: crose
 * Date: 1/28/16
 * Time: 8:05 AM
 */

namespace qfq;

11
require_once(__DIR__ . '/../Constants.php');
Carsten  Rose's avatar
Carsten Rose committed
12
require_once(__DIR__ . '/Sanitize.php');
13
require_once(__DIR__ . '/../store/Store.php');
14

15
16
17
const LONG_CURLY_OPEN = '#/+open+/#';
const LONG_CURLY_CLOSE = '#/+close+/#';

18
19
20
21
/**
 * Class Support
 * @package qfq
 */
22
23
class Support {

24
25
26
27
28
    /**
     * @var Store
     */
    private static $store = null;

29
    /**
Carsten  Rose's avatar
Carsten Rose committed
30
31
     * @param array $queryArray Empty or prefilled assoc array with url parameter
     * @param string $mode PARAM_T3_NO_ID, PARAM_T3_ALL
32
33
     * @throws CodeException
     * @throws UserFormException
34
     * @throws UserReportException
35
     */
36
    public static function appendTypo3ParameterToArray(array &$queryArray, $mode = PARAM_T3_ALL) {
37

38
39
40
        if (!isset(self::$store)) {
            self::$store = Store::getInstance();
        }
41

42
        if ($mode === PARAM_T3_ALL) {
43
            $queryArray['id'] = self::$store->getVar(TYPO3_PAGE_ID, STORE_TYPO3);
44
45
46
        }

        // TYPE
47
        $tmp = self::$store->getVar(TYPO3_PAGE_TYPE, STORE_TYPO3);
48
49
50
        if ($tmp !== false && $tmp != 0) {
            $queryArray['type'] = $tmp;
        }
51

52
        // Language
53
        $tmp = self::$store->getVar(TYPO3_PAGE_LANGUAGE, STORE_TYPO3);
54
55
56
        if ($tmp !== false && $tmp != 0) {
            $queryArray['L'] = $tmp;
        }
57
58
    }

59
60
61
62
    /**
     * Build the form log filename. Depending on $formLog=FORM_LOG_ALL one file for all BE_USER, or $formLog=FORM_LOG_SESSION one file per BE_USER.
     *
     * @param $formName
63
     * @param $formLogMode
64
65
66
67
68
     * @return string
     * @throws CodeException
     * @throws UserFormException
     * @throws UserReportException
     */
69
    public static function getFormLogFileName($formName, $formLogMode) {
70
71
72
73
74

        if (!isset(self::$store)) {
            self::$store = Store::getInstance();
        }

75
        switch ($formLogMode) {
76
77
78
79
            case FORM_LOG_ALL:
                $perBeSession = '';
                break;
            case FORM_LOG_SESSION:
80
                $perBeSession = self::$store->getVar(TYPO3_BE_USER, STORE_TYPO3) . '.';
81
82
83
                if(empty($perBeSession)){
                    throw new UserFormException('formLog: no BE User logged in', ERROR_NO_BE_USER_LOGGED);
                }
84
85
                break;
            default:
86
                throw new CodeException('Unknown mode: ' . $formLogMode, ERROR_UNKNOWN_TOKEN);
87
88
        }

89
90
91
        $filename = self::$store->getVar(SYSTEM_LOG_DIR, STORE_SYSTEM) . '/' . $formName . "." . $perBeSession . "log";

        return sanitize::safeFilename($filename, false, true);
92
93
    }

94
    /**
95
96
     * Builds a urlencoded query string of an assoc array.
     *
97
     * @param array $queryArray
Carsten  Rose's avatar
Carsten Rose committed
98
     *
99
     * @return string Querystring (e.g.: id=23&type=99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
     */
    public static function arrayToQueryString(array $queryArray) {
        $items = array();

        foreach ($queryArray as $key => $value) {
            if (is_int($key)) {
                $items[] = urlencode($value);
            } else {
                $items[] = $key . '=' . urlencode($value);
            }
        }

        return implode('&', $items);
    }
114
115

    /**
Carsten  Rose's avatar
Carsten Rose committed
116
117
     * Extract Tag(s) from $tag (eg: <div><input class="form-control">, might contain further attributes) and wrap it
     * around
Carsten  Rose's avatar
Carsten Rose committed
118
     * $value. If $flagOmitEmpty==true && $value=='': return ''.
119
     *
Carsten  Rose's avatar
Carsten Rose committed
120
121
     * @param string $tag
     * @param string $value
122
     * @param bool|false $omitIfValueEmpty
Carsten  Rose's avatar
Carsten Rose committed
123
     *
124
125
126
     * @return string
     */
    public static function wrapTag($tag, $value, $omitIfValueEmpty = false) {
127
128
129
130
131
132
133
134
135
136
137
138

        $tag = trim($tag);

        if ($tag == '' || ($omitIfValueEmpty && $value == "")) {
            return $value;
        }

        $tagArray = explode('>', $tag, 2);
        if (count($tagArray) > 1 && $tagArray[1] != '') {
            $value = self::wrapTag($tagArray[1], $value, $omitIfValueEmpty);
            $tag = $tagArray[0] . '>';
        }
139
140
141
142
143
144
145
146
147
148

        // a) <div class="container-fluid">, b) <label>
        $arr = explode(' ', $tag);

        $tagPlain = (count($arr) === 1) ? substr($arr[0], 1, strlen($arr[0]) - 2) : substr($arr[0], 1);
        $closing = '</' . $tagPlain . '>';

        return $tag . $value . $closing;
    }

149
150
151
152
153
    /**
     * @param $glyphIcon
     * @param string $value
     * @return string
     */
154
155
156
157
    public static function renderGlyphIcon($glyphIcon, $value = '') {
        return Support::wrapTag("<span class='" . GLYPH_ICON . " $glyphIcon'>", $value);
    }

158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
    /**
     * Removes '$tag' and closing $tag from $value, if they are the outermost.
     * E.g.  unWrapTag('<p>', '<p>Hello World</p>')    returns  'Hello World'
     *
     * @param string $tag
     * @param string $value
     *
     * @return string
     */
    public static function unWrapTag($tag, $value) {

        if ($tag == '' || $value == '') {
            return $value;
        }

        $lenTag = strlen($tag);
        $lenValue = strlen($value);

        if ($lenValue < $lenTag + $lenTag + 1) {
            return $value;
        }

        $closeTag = $tag[0] . '/' . substr($tag, 1);

        if (substr($value, 0, $lenTag) == $tag && substr($value, $lenValue - $lenTag - 1) == $closeTag) {
            $value = substr($value, $lenTag, $lenValue - $lenTag - $lenTag - 1);
        }

        return $value;
    }

189
190
191
192
193
    /**
     * Wraps some $inner fragment with a CSS styled $tooltipText . CSS is configured in 'Resources/Public/qfq-jqw.css'.
     *
     * Based on: http://www.w3schools.com/howto/howto_css_tooltip.asp
     *
194
     * @param string $htmlId
195
     * @param string $tooltipText
Carsten  Rose's avatar
Carsten Rose committed
196
     *
197
     * @return string
198
     * @throws CodeException
199
200
201
202
203
204
     */
    public static function doTooltip($htmlId, $tooltipText) {

        return "<img " . self::doAttribute('id', $htmlId) . " src='" . GFX_INFO . "' title=\"" . htmlentities($tooltipText) . "\">";
    }

205
206
    /**
     * Format's an attribute: $type=$value. If $flagOmitEmpty==true && $value=='': return ''.
207
     * Add's a space at the end.
208
     *
Carsten  Rose's avatar
Carsten Rose committed
209
     * @param string $type
210
     * @param string|array $value
Carsten  Rose's avatar
Carsten Rose committed
211
212
     * @param bool $flagOmitEmpty
     * @param string $modeEscape
Carsten  Rose's avatar
Carsten Rose committed
213
     *
214
     * @return string correctly formatted attribute. Space at the end.
215
     * @throws CodeException
216
     */
217
    public static function doAttribute($type, $value, $flagOmitEmpty = true, $modeEscape = ESCAPE_WITH_HTML_QUOTE) {
218
219
220
221
222
223

        // several attributes might be given as an array - concat to a string
        if (is_array($value)) {
            $value = implode(' ', $value);
        }

224
        if ($flagOmitEmpty && trim($value) === "") {
225
            return '';
226
227
        }

228
        switch (strtolower($type)) {
229
230
            case 'size':
            case 'maxlength':
231
                // empty or '0' for attributes of type 'size' or 'maxlength' result in unsuable input elements: skip this.
232
233
234
235
                if ($value === '' || $value == 0) {
                    return '';
                }
                break;
236
237
238
239
240
            // Bad idea to do urlencode on this place: it will convert ?, &, ... which are necessary for a proper URL.
            // Instead the value of a parameter needs to encode. Unfortunately, it's too late on this place.
//            case 'href':
//                $value = urlencode($value);
//                break;
241
242
243
            default:
                break;
        }
244

245
246
        $value = self::escapeDoubleTick(trim($value), $modeEscape);

247
248
249
250
251
        return $type . '="' . $value . '" ';
    }

    /**
     * Escapes Double Ticks ("), which are not already escaped.
252
253
254
     * modeEscape: ESCAPE_WITH_BACKSLASH | ESCAPE_WITH_HTML_QUOTE
     *
     * TinyMCE: Encoding JS Attributes (keys & values) for TinyMCE needs to be encapsulated in '&quot;' instead of '\"'.
255
     *
Carsten  Rose's avatar
Carsten Rose committed
256
     * @param        $str
257
     * @param string $modeEscape
Carsten  Rose's avatar
Carsten Rose committed
258
     *
259
     * @return string
260
     * @throws CodeException
261
     */
262
    public static function escapeDoubleTick($str, $modeEscape = ESCAPE_WITH_BACKSLASH) {
263
264
265
266
267
        $newStr = '';

        for ($ii = 0; $ii < strlen($str); $ii++) {
            if ($str[$ii] === '"') {
                if ($ii === 0 || $str[$ii - 1] != '\\') {
268
269
270
271
272
273
274
275
276
277
                    switch ($modeEscape) {
                        case ESCAPE_WITH_BACKSLASH:
                            $newStr .= '\\';
                            break;
                        case ESCAPE_WITH_HTML_QUOTE:
                            $newStr .= '&quot;';
                            continue 2;
                        default:
                            throw new CodeException('Unknown modeEscape=' . $modeEscape, ERROR_UNKNOWN_ESCAPE_MODE);
                    }
278
279
280
281
282
283
                }
            }
            $newStr .= $str[$ii];
        }

        return $newStr;
284
285
    }

286
287
288
289
    /**
     * Format's an attribute and inserts them at the beginning of the $htmlTag:
     * If $flagOmitEmpty==true && $value=='': insert nothing
     *
Carsten  Rose's avatar
Carsten Rose committed
290
291
     * @param string $htmlTag with open and closing angle.
     * @param string $type
292
     * @param string|array $value
Carsten  Rose's avatar
Carsten Rose committed
293
294
     * @param bool $flagOmitEmpty
     * @param string $modeEscape
Carsten  Rose's avatar
Carsten Rose committed
295
     *
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
     * @return string correctly fomratted attribute. Space at the end.
     * @throws CodeException
     */
    public static function insertAttribute($htmlTag, $type, $value, $flagOmitEmpty = true, $modeEscape = ESCAPE_WITH_BACKSLASH) {
        $htmlTag = trim($htmlTag);

        // e.g. '<div class=...' will be exploded to '<div' and 'class=...'
        $parts = explode(' ', $htmlTag, 2);
        if (count($parts) < 2) {
            if (strlen($htmlTag) < 3) {
                throw new CodeException('HTML Token too short (<3 chars):' . $htmlTag, ERROR_HTML_TOKEN_TOO_SHORT);
            } else {
                $parts[0] = substr($htmlTag, 0, -1);
                $parts[1] = '>';
            }
        }

        $attr = self::doAttribute($type, $value, $flagOmitEmpty, $modeEscape);

        return $parts[0] . ' ' . $attr . $parts[1];
    }

318
    /**
319
     * Search for the parameter $needle in $haystack. The arguments has to be separated by ','.
320
     *
321
     * Returns false if not found or index of found place. Be careful: use unary operator to compare for 'false'
322
     *
Carsten  Rose's avatar
Carsten Rose committed
323
324
     * @param string $needle
     * @param string $haystack
Carsten  Rose's avatar
Carsten Rose committed
325
     *
326
     * @return boolean     true if found, else false
327
     */
328
329
330
331
    public static function findInSet($needle, $haystack) {
        $arr = explode(',', $haystack);

        return array_search($needle, $arr) !== false;
332
    }
333

334
    /**
335
     * Converts a dateTime String to the international format:
336
337
338
339
     * 1.2.79 > 1979-02-01 00:00:00
     * 01.02.13 3:24 >  1979-02-01 03:24:00
     * 1.2.1979 14:21:5 > 1979-02-01 14:21:05
     *
Carsten  Rose's avatar
Carsten Rose committed
340
     * @param string $dateTimeString
Carsten  Rose's avatar
Carsten Rose committed
341
     *
342
     * @return string
343
     * @throws UserFormException
344
     */
345
    public static function dateTimeGermanToInternational($dateTimeString) {
346
347
348
        $dateRaw = '';
        $timeRaw = '';

349
350
351
352
353

//        const REGEXP_DATE_INT_ = '^\d{2,4}-\d{2}-\d{2}$';
//        const REGEXP_DATE_GER = '^\d{1,2}\.\d{1,2}\.\d{2}(\d{2})?$';
//        const REGEXP_TIME = '^\d{1,2}:\d{1,2}(:\d{1,2})?$';

354
355
356
357
358
359
        $tmpArr = explode(' ', $dateTimeString);
        switch (count($tmpArr)) {
            case 0:
                return '';

            case 1:
360
                if (strpos($tmpArr[0], ':') === false) {
361
                    $dateRaw = $tmpArr[0];
362
363
                } else {
                    $timeRaw = $tmpArr[0];
364
365
366
367
368
369
370
371
372
                }
                break;

            case 2:
                $dateRaw = $tmpArr[0];
                $timeRaw = $tmpArr[1];
                break;

            default:
373
                throw new UserFormException('Date/time format not recognised.', ERROR_DATE_TIME_FORMAT_NOT_RECOGNISED);
374
375
376
                break;
        }

377
        if ($dateRaw === '' || $dateRaw === '0000-00-00' || $dateRaw === '00.00.0000') {
378
379
            $date = '0000-00-00';
        } else {
380
            // International format: YYYY-MM-DD
381
382

            if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $dateRaw) === 1) {
383
384
385
                $date = $dateRaw;

                // German format: 1.1.01 - 11.12.1234
386
            } elseif (preg_match('/^\d{1,2}\.\d{1,2}\.\d{2}(\d{2})?$/', $dateRaw) === 1) {
387
388
389
390
391
392
393
394
395
396
397
                $tmpArr = explode('.', $dateRaw);

                if ($tmpArr[2] < 70) {
                    $tmpArr[2] = 2000 + $tmpArr[2];
                } elseif ($tmpArr[2] < 100) {
                    $tmpArr[2] = 1900 + $tmpArr[2];
                }
                $date = sprintf("%04d-%02d-%02d", $tmpArr[2], $tmpArr[1], $tmpArr[0]);

            } else {
                throw new UserFormException('Date/time format not recognised.', ERROR_DATE_TIME_FORMAT_NOT_RECOGNISED);
398
399
400
            }
        }

401
        if ($timeRaw === '' || $timeRaw === '00:00:00') {
402
403
            $time = '00:00:00';
        } else {
404
            if (preg_match('/^\d{1,2}:\d{1,2}(:\d{1,2})?$/', $timeRaw) !== 1) {
405
406
407
                throw new UserFormException('Date/time format not recognised.', ERROR_DATE_TIME_FORMAT_NOT_RECOGNISED);
            }

408
409
410
411
412
413
414
415
416
            $tmpArr = explode(':', $timeRaw);
            switch (count($tmpArr)) {
                case 2:
                    $time = sprintf("%02d:%02d:00", $tmpArr[0], $tmpArr[1]);
                    break;
                case 3:
                    $time = sprintf("%02d:%02d:%02d", $tmpArr[0], $tmpArr[1], $tmpArr[2]);
                    break;
                default:
417
                    throw new UserFormException('Date/time format not recognised.', ERROR_DATE_TIME_FORMAT_NOT_RECOGNISED);
418
419
420
421
422
423
                    break;
            }
        }

        return $date . ' ' . $time;
    }
424

425
426
427
    /**
     * @param string $type date | datetime | time
     * @param string $format FORMAT_DATE_INTERNATIONAL | FORMAT_DATE_GERMAN
428
     * @param string $timeIsOptional
Carsten  Rose's avatar
Carsten Rose committed
429
     *
430
431
432
     * @return string
     * @throws UserFormException
     */
433
    public static function dateTimeRegexp($type, $format, $timeIsOptional = '0') {
434
435
436

        if ($format === FORMAT_DATE_GERMAN) {
            // yyyy-mm-dd | 0000-00-00
Carsten  Rose's avatar
Carsten Rose committed
437
            $date = '(([1-9]|0[1-9]|1[0-9]|2[0-9]|3[01])\.([1-9]|0[1-9]|1[012])\.([0-9]{4}|[0-9]{2})|00\.00\.(00){1,2})';
438
439
        } else {
            // FORMAT_DATE_INTERNATIONAL: [d]d.[m]m.[yy]yy | 00.00.0000
Carsten  Rose's avatar
Carsten Rose committed
440
            $date = '([0-9]{4}-([1-9]|0[1-9]|1[012])-([1-9]|0[1-9]|1[0-9]|2[0-9]|3[01])|0000-00-00)';
441
442
443
444
445
446
447
448
449
450
        }

        // hh:mm[:ss]
        $time = '(0[0-9]|1[0-9]|2[0-3])(:[0-5][0-9]){1,2}';

        switch ($type) {
            case 'date':
                $pattern = $date;
                break;
            case 'datetime':
451
452
453
454
                if ($timeIsOptional == '1')
                    $pattern = $date . '( ' . $time . ')?';
                else
                    $pattern = $date . ' ' . $time;
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
                break;
            case 'time':
                $pattern = $time;
                break;
            default:
                throw new UserFormException("Unknown mode: '$type'", ERROR_UNKNOWN_MODE);
        }

        return '^' . $pattern . '$';
    }

    /**
     * Parses $dateTimeString. If a date is given, detect international or german format automatically.
     * Convert to $dateFormat. 'yy' and 'd' will be converted to 'yyyy' and 'dd'
     * Returned value will be 'date only', 'datetime' oder 'time only', depending on the input value.
     *
     * @param string $dateTimeString
Carsten  Rose's avatar
Carsten Rose committed
472
473
474
475
     * @param string $dateFormat
     * @param string $showZero
     * @param string $showTime
     * @param string $showSeconds
Carsten  Rose's avatar
Carsten Rose committed
476
     *
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
     * @return string
     * @throws UserFormException
     */
    public static function convertDateTime($dateTimeString, $dateFormat, $showZero, $showTime, $showSeconds) {
        $givenDate = '';
        $givenTime = '';
        $newDate = '';
        $newTime = '';
        $delim = '';

        $dateTimeString = trim($dateTimeString);


        switch ($dateTimeString) {
            case '':
            case '0':
            case '0000-00-00 00:00:00':
            case '0000-00-00':
495
496
            case '00.00.0000 00:00:00':
            case '00.00.0000':
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
            case '00:00:00':
                return (self::dateTimeZero($dateFormat, $showZero, $showTime, $showSeconds));
                break;

            default:
                break;
        }

        // Main convert
        $arr = explode(' ', $dateTimeString);
        switch (count($arr)) {
            case 1:
                if (strpos($dateTimeString, ':') === false) {
                    $givenDate = $dateTimeString;
                } else {
                    $givenTime = $dateTimeString;
                }
                break;

            case 2:
                $givenDate = $arr[0];
                $givenTime = $arr[1];
            default:
        }

        // Date
        if ($givenDate != '') {
            if ($givenDate == '0')
                $givenDate = '0000-00-00';

            $arr = self::splitDateToArray($givenDate);

            switch ($dateFormat) {
                case FORMAT_DATE_INTERNATIONAL:
                    $newDate = sprintf("%04d-%02d-%02d", $arr[0], $arr[1], $arr[2]);
                    break;
                case FORMAT_DATE_GERMAN:
                    $newDate = sprintf("%02d.%02d.%04d", $arr[2], $arr[1], $arr[0]);
                default:
            }
        }

        // Time
        if ($givenTime != '') {
            if ($givenTime == '0')
                $givenTime = '0:0:0';

            $arr = explode(':', $givenTime);
            if (count($arr) < 3) {
                $arr[2] = 0;
            }

            if ($showSeconds == 1) {
                $newTime = sprintf("%02d:%02d:%02d", $arr[0], $arr[1], $arr[2]);
            } else {
                $newTime = sprintf("%02d:%02d", $arr[0], $arr[1]);
            }
        }

        if (strlen($dateTimeString) > 15) {
            $delim = ' ';
        }

        return $newDate . $delim . $newTime;
    }

    /**
     * Returns a representation of 0 in a choosen variant.
     *
     * @param string $dateFormat FORMAT_DATE_INTERNATIONAL | FORMAT_DATE_GERMAN
567
     * @param string $showZero
Carsten  Rose's avatar
Carsten Rose committed
568
     * @param string $showTime '0' | '1'
569
     * @param string $showSeconds '0' | '1'
Carsten  Rose's avatar
Carsten Rose committed
570
     *
571
572
573
574
     * @return string
     */
    private static function dateTimeZero($dateFormat, $showZero, $showTime, $showSeconds) {

575
        if ($showZero != '1') {
576
            return '';
577
        }
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596

        // $dateFormat (INT/GER),  $showTime, $showSeconds
        $arr[0][0][0] = '0000-00-00';
        $arr[0][0][1] = '0000-00-00';
        $arr[0][1][0] = '0000-00-00 00:00';
        $arr[0][1][1] = '0000-00-00 00:00:00';
        $arr[1][0][0] = '00.00.0000';
        $arr[1][0][1] = '00.00.0000';
        $arr[1][1][0] = '00.00.0000 00:00';
        $arr[1][1][1] = '00.00.0000 00:00:00';

        $showFormat = ($dateFormat === FORMAT_DATE_INTERNATIONAL) ? 0 : 1;

        return $arr[$showFormat][$showTime][$showSeconds];
    }

    /**
     * Split date FORMAT_DATE_GERMAN | FORMAT_DATE_INTERNATIONAL to array with arr[0]=yyyy, arr[1]=mm, arr[2]=dd.
     *
Carsten  Rose's avatar
Carsten Rose committed
597
     * @param string $dateString
Carsten  Rose's avatar
Carsten Rose committed
598
     *
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
     * @return array
     * @throws UserFormException
     */
    private static function splitDateToArray($dateString) {

        if (strpos($dateString, '-') === false) {
            // FORMAT_DATE_GERMAN
            $arr = explode('.', $dateString);
            if (count($arr) != 3) {
                throw new UserFormException("Unexpected format for date: $dateString", ERROR_DATE_UNEXPECTED_FORMAT);
            }
            $tmp = $arr[0];
            $arr[0] = $arr[2];
            $arr[2] = $tmp;
        } else {
            // FORMAT_DATE_INTERNATIONAL
            $arr = explode('-', $dateString);
            if (count($arr) != 3) {
                throw new UserFormException("Unexpected format for date: $dateString", ERROR_DATE_UNEXPECTED_FORMAT);
            }
        }

        // Adjust yy to yyyy. See https://dev.mysql.com/doc/refman/5.7/en/datetime.html > Dates containing two-digit year values ...
        if ($arr[0] < 100) {
            $add = ($arr[0] < 70) ? 2000 : 1900;
            $arr[0] = $add + $arr[0];
        }

        return $arr;
    }

    /**
     * Calculates the placeholder for date/dateTime/local input fields.
     *
     * @param array $formElement
Carsten  Rose's avatar
Carsten Rose committed
634
     *
635
636
637
638
639
640
     * @return string
     * @throws UserFormException
     */
    public static function getDateTimePlaceholder(array $formElement) {

        $timePattern = ($formElement[FE_SHOW_SECONDS] == 1) ? 'hh:mm:ss' : 'hh:mm';
641
        switch ($formElement[FE_TYPE]) {
642
            case 'date':
643
                $placeholder = $formElement[FE_DATE_FORMAT];
644
645
                break;
            case 'datetime':
646
                $placeholder = $formElement[FE_DATE_FORMAT] . ' ' . $timePattern;
647
648
649
650
651
                break;
            case 'time':
                $placeholder = $timePattern;
                break;
            default:
652
                throw new UserFormException("Unexpected Formelement type: '" . $formElement[FE_TYPE] . "'", ERROR_FORMELEMENT_TYPE);
653
654
655
656
657
658
        }

        return $placeholder;
    }


659
660
661
    /**
     * Encrypt curly braces by an uncommon string. Helps preventing unwished action on curly braces.
     *
Carsten  Rose's avatar
Carsten Rose committed
662
     * @param string $text
Carsten  Rose's avatar
Carsten Rose committed
663
     *
664
665
     * @return mixed
     */
666
    public static function encryptDoubleCurlyBraces($text) {
667
668
        $text = str_replace('{{', LONG_CURLY_OPEN, $text);
        $text = str_replace('}}', LONG_CURLY_CLOSE, $text);
669
670
671
672
673
674
675

        return $text;
    }

    /**
     * Decrypt curly braces by an uncommon string. Helps preventing unwished action on curly braces
     *
Carsten  Rose's avatar
Carsten Rose committed
676
     * @param string $text
Carsten  Rose's avatar
Carsten Rose committed
677
     *
678
679
680
     * @return mixed
     */
    public static function decryptDoubleCurlyBraces($text) {
681
682
683
684
685

        if (!is_string($text)) {
            return $text;
        }

686
687
        $text = str_replace(LONG_CURLY_OPEN, '{{', $text);
        $text = str_replace(LONG_CURLY_CLOSE, '}}', $text);
688
689
690
691

        return $text;
    }

692
693
    /**
     * @param int $length Length of the required hash string
Carsten  Rose's avatar
Carsten Rose committed
694
     *
695
696
697
698
699
700
701
702
703
704
705
706
     * @return string       A random alphanumeric hash
     */
    public static function randomAlphaNum($length) {
        $possible_characters = "abcdefghijklmnopqrstuvwxyz1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ";
        $string = "";
        while (strlen($string) < $length) {
            $string .= substr($possible_characters, rand() % (strlen($possible_characters)), 1);
        }

        return ($string);
    }

707
    /**
Carsten  Rose's avatar
Carsten Rose committed
708
709
     * Concatenate URL and Parameter. Depending of if there is a '?' in URL or not,  append the param with '?' or '&'..
     *
Carsten  Rose's avatar
Carsten Rose committed
710
     * @param string $url
711
     * @param string|array $param
Carsten  Rose's avatar
Carsten Rose committed
712
     *
713
714
715
     * @return string
     */
    public static function concatUrlParam($url, $param) {
716
717
718
719
720

        if (is_array($param)) {
            $param = implode('&', $param);
        }

721
        if ($param == '') {
722
            return $url;
723
        }
724

725
        $token = ((strpos($url, '?') === false) && (strpos($url, '&')) === false) ? '?' : '&';
Carsten  Rose's avatar
Carsten Rose committed
726

727
728
        return $url . $token . $param;
    }
729

730
731
732
733
734
735
736
737
738
739
    /**
     * Concatenate $host, $path and $query. A given $host will always append a '/' if none is given.
     * End of  $path, there is no assumption to append '/' - the user needs full control.
     * $query: trailing 'index.php' will be skipped.
     *
     * If HOST and PATH is combined in a var, pass the value as $hostOrPath, leave $host empty.
     *
     * @param string $host
     * @param string $hostOrPath
     * @param string $query
Carsten  Rose's avatar
Carsten Rose committed
740
     *
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
     * @return string
     * @throws CodeException
     */
    public static function mergeUrlComponents($host, $hostOrPath, $query) {
        $url = '';


        if ($host != '' && substr($host, -1, 1) != '/') {
            $host .= '/';
        }

        if ($host != '' && substr($hostOrPath, 0, 1) == '/') {
            $hostOrPath = substr($hostOrPath, 1);
        }

        if ($host != '' || $hostOrPath != '') {
            $url = $host . $hostOrPath;
        }

        if (substr($query, 0, 9) == 'index.php') {
            $query = substr($query, 9);
        }

        if (false !== strpos(substr($query, 1), '?')) {
            throw new CodeException('Found a "?" after the beginning of the query - this is forbidden', ERROR_BROKEN_PARAMETER);
        }

        if ($query != '') {
            $url = $url . '?' . $query;
            $url = str_replace('??', '?', $url);
        }

        return $url;
    }

776
777
778
779
    /**
     * Set Defaults for the current formElement.
     *
     * @param array $formElement
780
     * @param array $formSpec
781
     * @return array
782
783
     *
     * @throws CodeException
784
     * @throws UserFormException
785
     * @throws UserReportException
786
     */
787
    public static function setFeDefaults(array $formElement, array $formSpec = array()) {
788
789
790
791
792

        $store = Store::getInstance();

        // Some Defaults
        self::setIfNotSet($formElement, FE_SHOW_SECONDS, '0');
793
        self::setIfNotSet($formElement, FE_TIME_IS_OPTIONAL, '0');
794
        self::setIfNotSet($formElement, FE_SHOW_ZERO, '0');
795
        self::setIfNotSet($formElement, FE_HIDE_ZERO, '0');
796
797
        self::setIfNotSet($formElement, FE_DATE_FORMAT, $store->getVar(SYSTEM_DATE_FORMAT, STORE_SYSTEM));

798
799
800
        self::setIfNotSet($formElement, FE_HTML_BEFORE);
        self::setIfNotSet($formElement, FE_HTML_AFTER);

Carsten  Rose's avatar
Carsten Rose committed
801
        self::setIfNotSet($formElement, FE_SUBRECORD_TABLE_CLASS, SUBRECORD_TABLE_CLASS_DEFAULT);
802

803
        if (isset($formSpec[F_BS_LABEL_COLUMNS])) {
804
805
806
807
808
809
            self::setIfNotSet($formElement, F_BS_LABEL_COLUMNS, $formSpec[F_BS_LABEL_COLUMNS], '');
            self::setIfNotSet($formElement, F_BS_INPUT_COLUMNS, $formSpec[F_BS_INPUT_COLUMNS], '');
            self::setIfNotSet($formElement, F_BS_NOTE_COLUMNS, $formSpec[F_BS_NOTE_COLUMNS], '');
        }

        if ($formElement[FE_MODE_SQL] != '') {
810
            $formElement[FE_MODE] = $formElement[FE_MODE_SQL];
811
        }
812

813
814
815
816
        if (isset($formSpec[F_MODE])) {
            $formElement[FE_MODE] = self::applyFormModeToFormElement($formElement[FE_MODE], $formSpec[F_MODE]);
        }

817
        // set typeAheadPedantic
818
        if (isset($formElement[FE_TYPEAHEAD_PEDANTIC]) && $formElement[FE_TYPEAHEAD_PEDANTIC] === '') {
819
820
821
822
823
824
            $formElement[FE_TYPEAHEAD_PEDANTIC] = '1'; // support legacy option of 'typeAheadPedantic' without a value
        }
        if (isset($formElement[FE_TYPEAHEAD_LDAP]) || isset($formElement[FE_TYPEAHEAD_SQL])) {
            self::setIfNotSet($formElement, FE_TYPEAHEAD_PEDANTIC, '1');
        }

825
826
827
828
829
830
831
832
833
        // Will be used to change dynamicUpdate behaviour
        if (isset($formElement[FE_WRAP_ROW_LABEL_INPUT_NOTE])) {
            $formElement[FE_FLAG_ROW_OPEN_TAG] = Support::findInSet('row', $formElement[FE_WRAP_ROW_LABEL_INPUT_NOTE]);
            $formElement[FE_FLAG_ROW_CLOSE_TAG] = Support::findInSet('/row', $formElement[FE_WRAP_ROW_LABEL_INPUT_NOTE]);
        } else {
            $formElement[FE_FLAG_ROW_OPEN_TAG] = true;
            $formElement[FE_FLAG_ROW_CLOSE_TAG] = false;
        }

834
835
        self::setIfNotSet($formElement, FE_INPUT_EXTRA_BUTTON_INFO_CLASS, $store->getVar(FE_INPUT_EXTRA_BUTTON_INFO_CLASS, STORE_SYSTEM));

836
        // For specific FE hardcode 'checkType'
837
838
839
840
        switch ($formElement[FE_TYPE]) {
            case FE_TYPE_IMAGE_CUT:
                $formElement[FE_CHECK_TYPE] = SANITIZE_ALLOW_ALLBUT;
                break;
841
842
843
            case FE_TYPE_ANNOTATE:
                $formElement[FE_CHECK_TYPE] = SANITIZE_ALLOW_ALL;
                break;
844
845
846
847
            default:
                break;
        }

848
849
        self::setIfNotSet($formElement, FE_MIN);
        self::setIfNotSet($formElement, FE_MAX);
850
851
852
853
        self::setIfNotSet($formElement, FE_DECIMAL_FORMAT);

        self::setIfNotSet($formElement, F_FE_DATA_PATTERN_ERROR);

854
855
        $typeSpec = $store->getVar($formElement[FE_NAME], STORE_TABLE_COLUMN_TYPES);
        self::adjustFeToColumnDefinition($formElement, $typeSpec);
856
857
858
859
860
861
862
863
864
865
866

        return $formElement;
    }

    /**
     * Adjusts several FE parameters using smart guesses based on the table column definition and other parameters.
     *
     * @param array $formElement
     * @param $typeSpec
     * @throws UserFormException
     */
867
    public static function adjustFeToColumnDefinition(array &$formElement, $typeSpec) {
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891

        self::adjustMaxLength($formElement, $typeSpec);
        self::adjustDecimalFormat($formElement, $typeSpec);

        // Make educated guesses about the desired $min, $max, $checkType, and $inputType

        // $typeSpec = 'tinyint(3) UNSIGNED NOT NULL' | 'int(11) NOT NULL'
        $arr = explode(' ', $typeSpec, 2);
        if (empty($arr[1])) {
            $sign = 'signed';
        } else {
            $arr = explode(' ', $arr[1], 2);
            $sign = $arr[0] == 'unsigned' ? 'unsigned' : 'signed';
        }

        $arr = explode('(', $typeSpec, 2);
        $token = $arr[0];

        # s: signed, u: unsigned.
        # s-min, s-max, s-checktype, u-min, u-max, u-checktype
        $control = [
            'tinyint' => [-128, 127, SANITIZE_ALLOW_NUMERICAL, 0, 255, SANITIZE_ALLOW_DIGIT],
            'smallint' => [-32768, 32767, SANITIZE_ALLOW_NUMERICAL, 0, 65535, SANITIZE_ALLOW_DIGIT],
            'mediumint' => [-8388608, 8388607, SANITIZE_ALLOW_NUMERICAL, 0, 16777215, SANITIZE_ALLOW_DIGIT],
892
            'int' => [-2147483648, 2147483647, SANITIZE_ALLOW_NUMERICAL, 0, 4294967295, SANITIZE_ALLOW_DIGIT],
893
894
895
896
897
            'bigint' => [-9223372036854775808, 9223372036854775807, SANITIZE_ALLOW_NUMERICAL, 0, 18446744073709551615, SANITIZE_ALLOW_DIGIT],
        ];

        $min = '';
        $max = '';
898
        $checkType = SANITIZE_ALLOW_ALNUMX;
899
        $inputType = '';
900
        $isANumber = true;
901

902
903
904
905
906
907
908
909
910
911
912
913
914
        switch ($formElement[FE_TYPE]) {
            case FE_TYPE_PASSWORD:
            case FE_TYPE_NOTE:
                $checkType = SANITIZE_ALLOW_ALL;
                break;

            case FE_TYPE_EDITOR:
            case FE_TYPE_TEXT:
                if ($formElement[FE_ENCODE] === FE_ENCODE_SPECIALCHAR)
                    $checkType = SANITIZE_ALLOW_ALL;
                break;
        }

915
916
917
918
919
920
        switch ($token) {
            case 'tinyint':
            case 'smallint':
            case 'mediumint':
            case 'int':
            case 'bigint':
921
                $inputType = HTML_INPUT_TYPE_NUMBER;
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
                $arr = $control[$token];
                if ($sign == 'signed') {
                    $min = $arr[0];
                    $max = $arr[1];
                    $checkType = $arr[2];
                } else {
                    $min = $arr[3];
                    $max = $arr[4];
                    $checkType = $arr[5];
                }
                break;

            case 'decimal':
            case 'float':
            case 'double':
                $checkType = SANITIZE_ALLOW_NUMERICAL;
                break;

            case 'bit':
941
                $inputType = HTML_INPUT_TYPE_NUMBER;
942
943
                $checkType = SANITIZE_ALLOW_DIGIT;
                break;
944
945
946
947

            default:
                $isANumber = false;
                break;
948
949
        }

950
        // Numbers don't need a maxLength because they are being handled by min/max and/or decimalFormat
951
        if ($isANumber) {
952
            $formElement[FE_MAX_LENGTH] = '';
953
        }
954

955
        if (!empty($formElement[FE_TYPEAHEAD_SQL]) || !empty($formElement[FE_TYPEAHEAD_LDAP])) {
956
957
958
959
960
961
962
            $inputType = '';
            $checkType = SANITIZE_ALLOW_ALNUMX;
        }

        // Set parameters if not set by user

        if ($formElement[FE_CHECK_TYPE] === SANITIZE_ALLOW_AUTO) {
963
            $formElement[FE_CHECK_TYPE] = $checkType;
964
        }
965

966
967
968
        if (empty($formElement[FE_MIN])) $formElement[FE_MIN] = $min;
        if (empty($formElement[FE_MAX])) $formElement[FE_MAX] = $max;
        if (empty($formElement[FE_INPUT_TYPE])) $formElement[FE_INPUT_TYPE] = $inputType;
969
970
971
972
973
974
975
976
977
978

        // If a  $formElement[FE_STEP] is given, the optional boundaries (FE_MIN / FE_MAX) have to be aligned to a multiple of $formElement[FE_STEP].
        if (!empty($formElement[FE_STEP])) {
            if (!empty($formElement[FE_MIN])) {
                $formElement[FE_MIN] = ceil($formElement[FE_MIN] / $formElement[FE_STEP]) * $formElement[FE_STEP];
            }
            if (!empty($formElement[FE_MAX])) {
                $formElement[FE_MAX] = floor($formElement[FE_MAX] / $formElement[FE_STEP]) * $formElement[FE_STEP];
            }
        }
979
980
981
982
983
984
985
986
987
988
    }

    /**
     * Sets the decimalFormat of a FormElement based on the parameter definition and table.field definition
     * Affected FormElement fields: FE_DECIMAL_FORMAT
     *
     * @param array $formElement
     * @param $typeSpec
     * @throws UserFormException
     */
989
    private static function adjustDecimalFormat(array &$formElement, $typeSpec) {
990
991
992
        if (isset($formElement[FE_DECIMAL_FORMAT])) {
            if ($formElement[FE_DECIMAL_FORMAT] === '') {
                // Get decimal format from column definition
993
994
                if ($typeSpec !== false) {
                    $fieldTypeInfoArray = preg_split("/[()]/", $typeSpec);
995
996
997
998
999
                    if ($fieldTypeInfoArray[0] === 'decimal')
                        $formElement[FE_DECIMAL_FORMAT] = $fieldTypeInfoArray[1];
                }
            } else {
                // Decimal format is defined in parameter field
1000
                $isValidDecimalFormat = preg_match("/^[0-9]+,[0-9]+$/", $formElement[FE_DECIMAL_FORMAT]);