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

Marc Egger's avatar
Marc Egger committed
9
namespace IMATHUZH\Qfq\Core\Helper;
10

11

Marc Egger's avatar
Marc Egger committed
12
use IMATHUZH\Qfq\Core\Store\Store;
13

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

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

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

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

37
        self::$store = Store::getInstance();
38

39
        if ($mode === PARAM_T3_ALL) {
40
            $queryArray[CLIENT_PAGE_ID] = self::$store->getVar(TYPO3_PAGE_ID, STORE_TYPO3);
41
42
43
        }

        // TYPE
44
        $tmp = self::$store->getVar(TYPO3_PAGE_TYPE, STORE_TYPO3);
45
        if ($tmp !== false && $tmp != 0) {
46
            $queryArray[CLIENT_PAGE_TYPE] = $tmp;
47
        }
48

49
        // Language
50
        $tmp = self::$store->getVar(TYPO3_PAGE_LANGUAGE, STORE_TYPO3);
51
        if ($tmp !== false && $tmp != 0) {
52
            $queryArray[CLIENT_PAGE_LANGUAGE] = $tmp;
53
        }
54
55
    }

56
57
58
59
60
61
62
63
64
65
    /**
     * Check $uri if 'type' and/or 'L' is missing. If yes, append it.
     *
     * Do nothing if $uri is empty.
     * Do nothing if $uri is absolute (=starts with http)
     *
     * @param $uri
     * @return string
     * @throws \CodeException
     * @throws \UserFormException
66
     * @throws \UserReportException
67
68
69
     */
    public static function appendTypo3ParameterToUrl($uri) {

70
71
72
        self::$store = Store::getInstance();


73
74
75
76
77
78
79
80
81
82
83
84
85
86
        if ($uri == '') {
            return '';
        }

        // Full URL: no processing
        $protocol = explode(':', $uri, 2);
        if ($protocol[0] == 'http' || $protocol[0] == 'https') {
            return $uri;
        }

        $arr = KeyValueStringParser::parse($uri, '=', '&');
        $type = self::$store->getVar(TYPO3_PAGE_TYPE, STORE_TYPO3);
        $language = self::$store->getVar(TYPO3_PAGE_LANGUAGE, STORE_TYPO3);

87
        if ($type != 0 && $type !== false && !isset($arr[CLIENT_PAGE_TYPE])) {
88
89
90
            $uri .= '&type=' . $type;
        }

91
        if ($language != 0 && $language !== false && !isset($arr[CLIENT_PAGE_LANGUAGE])) {
92
93
94
95
96
97
            $uri .= '&L=' . $language;
        }

        return $uri;
    }

98
99
100
101
    /**
     * 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
102
     * @param $formLogMode
103
     * @return string
Marc Egger's avatar
Marc Egger committed
104
105
106
     * @throws \CodeException
     * @throws \UserFormException
     * @throws \UserReportException
107
     */
108
    public static function getFormLogFileName($formName, $formLogMode) {
109

110
        self::$store = Store::getInstance();
111

112
        switch ($formLogMode) {
113
114
115
116
            case FORM_LOG_ALL:
                $perBeSession = '';
                break;
            case FORM_LOG_SESSION:
117
                $perBeSession = self::$store->getVar(TYPO3_BE_USER, STORE_TYPO3) . '.';
118
                if (empty($perBeSession)) {
Marc Egger's avatar
Marc Egger committed
119
                    throw new \UserFormException('formLog: no BE User logged in', ERROR_NO_BE_USER_LOGGED);
120
                }
121
122
                break;
            default:
Marc Egger's avatar
Marc Egger committed
123
                throw new \CodeException('Unknown mode: ' . $formLogMode, ERROR_UNKNOWN_TOKEN);
124
125
        }

126
        $filename = Path::absoluteLog() . '/' . $formName . "." . $perBeSession . "log";
127
128

        return sanitize::safeFilename($filename, false, true);
129
130
    }

131
    /**
132
133
     * Builds a urlencoded query string of an assoc array.
     *
134
     * @param array $queryArray
Carsten  Rose's avatar
Carsten Rose committed
135
     *
136
     * @return string Querystring (e.g.: id=23&type=99
137
138
139
140
141
142
143
144
145
146
147
148
149
150
     */
    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);
    }
151
152

    /**
Carsten  Rose's avatar
Carsten Rose committed
153
154
     * 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
155
     * $value. If $flagOmitEmpty==true && $value=='': return ''.
156
     *
Carsten  Rose's avatar
Carsten Rose committed
157
158
     * @param string $tag
     * @param string $value
159
     * @param bool|false $omitIfValueEmpty
Carsten  Rose's avatar
Carsten Rose committed
160
     *
161
162
163
     * @return string
     */
    public static function wrapTag($tag, $value, $omitIfValueEmpty = false) {
164
165
166
167
168
169
170
171
172
173
174
175

        $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] . '>';
        }
176
177
178
179
180
181
182
183
184
185

        // 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;
    }

186
187
188
189
190
    /**
     * @param $glyphIcon
     * @param string $value
     * @return string
     */
191
192
193
194
    public static function renderGlyphIcon($glyphIcon, $value = '') {
        return Support::wrapTag("<span class='" . GLYPH_ICON . " $glyphIcon'>", $value);
    }

195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
    /**
     * 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;
    }

226
227
228
229
230
    /**
     * 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
     *
231
     * @param string $htmlId
232
     * @param string $tooltipText
Carsten  Rose's avatar
Carsten Rose committed
233
     *
234
     * @return string
Marc Egger's avatar
Marc Egger committed
235
     * @throws \CodeException
236
     * @throws \UserFormException
237
238
239
     */
    public static function doTooltip($htmlId, $tooltipText) {

240
        return "<img " . self::doAttribute('id', $htmlId) . " src='" . Path::urlExt(Path::EXT_TO_GFX_INFO_FILE) . "' title=\"" . htmlentities($tooltipText) . "\">";
241
242
    }

243
244
    /**
     * Format's an attribute: $type=$value. If $flagOmitEmpty==true && $value=='': return ''.
245
     * Escape double tick - assumes that attributes will always be enclosed by double ticks.
246
     * Add's a space at the end.
247
     *
Carsten  Rose's avatar
Carsten Rose committed
248
     * @param string $type
249
     * @param string|array $value
250
251
     * @param bool $flagOmitEmpty true|false
     * @param string $modeEscape ESCAPE_WITH_BACKSLASH | ESCAPE_WITH_HTML_QUOTE
Carsten  Rose's avatar
Carsten Rose committed
252
     *
Carsten  Rose's avatar
Carsten Rose committed
253
     *
254
     * @return string correctly formatted attribute. Space at the end.
Marc Egger's avatar
Marc Egger committed
255
     * @throws \CodeException
256
     */
257
    public static function doAttribute($type, $value, $flagOmitEmpty = true, $modeEscape = ESCAPE_WITH_HTML_QUOTE) {
258
259
260
261
262
263

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

264
        if ($flagOmitEmpty && trim($value) === "") {
265
            return '';
266
267
        }

268
        switch (strtolower($type)) {
269
270
            case 'size':
            case 'maxlength':
271
                // empty or '0' for attributes of type 'size' or 'maxlength' result in unsuable input elements: skip this.
272
273
274
275
                if ($value === '' || $value == 0) {
                    return '';
                }
                break;
276
277
278
279
280
            // 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;
281
282
283
            default:
                break;
        }
284

285
286
        $value = self::escapeDoubleTick(trim($value), $modeEscape);

287
288
289
290
291
        return $type . '="' . $value . '" ';
    }

    /**
     * Escapes Double Ticks ("), which are not already escaped.
292
293
294
     * modeEscape: ESCAPE_WITH_BACKSLASH | ESCAPE_WITH_HTML_QUOTE
     *
     * TinyMCE: Encoding JS Attributes (keys & values) for TinyMCE needs to be encapsulated in '&quot;' instead of '\"'.
295
     *
296
297
     * @param string $str
     * @param string $modeEscape ESCAPE_WITH_BACKSLASH | ESCAPE_WITH_HTML_QUOTE
Carsten  Rose's avatar
Carsten Rose committed
298
     *
299
     * @return string
Marc Egger's avatar
Marc Egger committed
300
     * @throws \CodeException
301
     */
302
    public static function escapeDoubleTick($str, $modeEscape = ESCAPE_WITH_BACKSLASH) {
303
304
305
306
307
        $newStr = '';

        for ($ii = 0; $ii < strlen($str); $ii++) {
            if ($str[$ii] === '"') {
                if ($ii === 0 || $str[$ii - 1] != '\\') {
308
309
310
311
312
313
314
315
                    switch ($modeEscape) {
                        case ESCAPE_WITH_BACKSLASH:
                            $newStr .= '\\';
                            break;
                        case ESCAPE_WITH_HTML_QUOTE:
                            $newStr .= '&quot;';
                            continue 2;
                        default:
Marc Egger's avatar
Marc Egger committed
316
                            throw new \CodeException('Unknown modeEscape=' . $modeEscape, ERROR_UNKNOWN_ESCAPE_MODE);
317
                    }
318
319
320
321
322
323
                }
            }
            $newStr .= $str[$ii];
        }

        return $newStr;
324
325
    }

326
327
328
329
    /**
     * 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
330
331
     * @param string $htmlTag with open and closing angle.
     * @param string $type
332
     * @param string|array $value
Carsten  Rose's avatar
Carsten Rose committed
333
334
     * @param bool $flagOmitEmpty
     * @param string $modeEscape
Carsten  Rose's avatar
Carsten Rose committed
335
     *
336
     * @return string correctly fomratted attribute. Space at the end.
Marc Egger's avatar
Marc Egger committed
337
     * @throws \CodeException
338
339
340
341
342
343
344
345
     */
    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) {
Marc Egger's avatar
Marc Egger committed
346
                throw new \CodeException('HTML Token too short (<3 chars):' . $htmlTag, ERROR_HTML_TOKEN_TOO_SHORT);
347
348
349
350
351
352
353
354
355
356
357
            } else {
                $parts[0] = substr($htmlTag, 0, -1);
                $parts[1] = '>';
            }
        }

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

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

358
    /**
359
     * Search for the parameter $needle in $haystack. The arguments has to be separated by ','.
360
     *
361
     * Returns false if not found, or index (starting with 0) of found place. Be careful: use unary operator to compare for 'false'
362
     *
Carsten  Rose's avatar
Carsten Rose committed
363
364
     * @param string $needle
     * @param string $haystack
Carsten  Rose's avatar
Carsten Rose committed
365
     *
366
     * @return boolean     true if found, else false
367
     */
368
369
370
371
    public static function findInSet($needle, $haystack) {
        $arr = explode(',', $haystack);

        return array_search($needle, $arr) !== false;
372
    }
373

374
    /**
375
     * Converts a dateTime String to the international format:
376
377
378
379
     * 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
380
     * @param string $dateTimeString
Carsten  Rose's avatar
Carsten Rose committed
381
     *
382
     * @return string
Marc Egger's avatar
Marc Egger committed
383
     * @throws \UserFormException
384
     */
385
    public static function dateTimeGermanToInternational($dateTimeString) {
386
387
388
        $dateRaw = '';
        $timeRaw = '';

389
390
391
392
393

//        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})?$';

394
395
396
397
398
399
        $tmpArr = explode(' ', $dateTimeString);
        switch (count($tmpArr)) {
            case 0:
                return '';

            case 1:
400
                if (strpos($tmpArr[0], ':') === false) {
401
                    $dateRaw = $tmpArr[0];
402
403
                } else {
                    $timeRaw = $tmpArr[0];
404
405
406
407
408
409
410
411
412
                }
                break;

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

            default:
Marc Egger's avatar
Marc Egger committed
413
                throw new \UserFormException('Date/time format not recognised.', ERROR_DATE_TIME_FORMAT_NOT_RECOGNISED);
414
415
416
                break;
        }

417
        if ($dateRaw === '' || $dateRaw === '0000-00-00' || $dateRaw === '00.00.0000') {
418
419
            $date = '0000-00-00';
        } else {
420
            // International format: YYYY-MM-DD
421
422

            if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $dateRaw) === 1) {
423
424
425
                $date = $dateRaw;

                // German format: 1.1.01 - 11.12.1234
426
            } elseif (preg_match('/^\d{1,2}\.\d{1,2}\.\d{2}(\d{2})?$/', $dateRaw) === 1) {
427
428
429
430
431
432
433
434
435
436
                $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 {
Marc Egger's avatar
Marc Egger committed
437
                throw new \UserFormException('Date/time format not recognised.', ERROR_DATE_TIME_FORMAT_NOT_RECOGNISED);
438
439
440
            }
        }

441
        if ($timeRaw === '' || $timeRaw === '00:00:00') {
442
443
            $time = '00:00:00';
        } else {
444
            if (preg_match('/^\d{1,2}:\d{1,2}(:\d{1,2})?$/', $timeRaw) !== 1) {
Marc Egger's avatar
Marc Egger committed
445
                throw new \UserFormException('Date/time format not recognised.', ERROR_DATE_TIME_FORMAT_NOT_RECOGNISED);
446
447
            }

448
449
450
451
452
453
454
455
456
            $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:
Marc Egger's avatar
Marc Egger committed
457
                    throw new \UserFormException('Date/time format not recognised.', ERROR_DATE_TIME_FORMAT_NOT_RECOGNISED);
458
459
460
461
462
            }
        }

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

464
465
466
    /**
     * @param string $type date | datetime | time
     * @param string $format FORMAT_DATE_INTERNATIONAL | FORMAT_DATE_GERMAN
467
     * @param string $timeIsOptional
Carsten  Rose's avatar
Carsten Rose committed
468
     *
469
     * @return string
Marc Egger's avatar
Marc Egger committed
470
     * @throws \UserFormException
471
     */
472
    public static function dateTimeRegexp($type, $format, $timeIsOptional = '0') {
473
474
475

        if ($format === FORMAT_DATE_GERMAN) {
            // yyyy-mm-dd | 0000-00-00
Carsten  Rose's avatar
Carsten Rose committed
476
            $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})';
477
478
        } else {
            // FORMAT_DATE_INTERNATIONAL: [d]d.[m]m.[yy]yy | 00.00.0000
Carsten  Rose's avatar
Carsten Rose committed
479
            $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)';
480
481
482
483
484
485
486
487
488
489
        }

        // 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':
490
491
492
493
                if ($timeIsOptional == '1')
                    $pattern = $date . '( ' . $time . ')?';
                else
                    $pattern = $date . ' ' . $time;
494
495
496
497
498
                break;
            case 'time':
                $pattern = $time;
                break;
            default:
Marc Egger's avatar
Marc Egger committed
499
                throw new \UserFormException("Unknown mode: '$type'", ERROR_UNKNOWN_MODE);
500
501
502
503
504
505
506
507
508
509
510
        }

        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
511
     * @param string $dateFormat FORMAT_DATE_INTERNATIONAL | FORMAT_DATE_GERMAN
Carsten  Rose's avatar
Carsten Rose committed
512
513
514
     * @param string $showZero
     * @param string $showTime
     * @param string $showSeconds
Carsten  Rose's avatar
Carsten Rose committed
515
     *
516
     * @return string
Marc Egger's avatar
Marc Egger committed
517
     * @throws \UserFormException
518
519
520
521
522
523
524
     */
    public static function convertDateTime($dateTimeString, $dateFormat, $showZero, $showTime, $showSeconds) {
        $givenDate = '';
        $givenTime = '';
        $newDate = '';
        $newTime = '';
        $delim = '';
Carsten  Rose's avatar
Carsten Rose committed
525
        $flagDateAndTime = false;
526
527
528
529
530
531
532
533
534

        $dateTimeString = trim($dateTimeString);


        switch ($dateTimeString) {
            case '':
            case '0':
            case '0000-00-00 00:00:00':
            case '0000-00-00':
535
536
            case '00.00.0000 00:00:00':
            case '00.00.0000':
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
            case '00:00:00':
                return (self::dateTimeZero($dateFormat, $showZero, $showTime, $showSeconds));

            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];
Carsten  Rose's avatar
Carsten Rose committed
558
                $flagDateAndTime = true;
559
560
561
562
563
            default:
        }

        // Date
        if ($givenDate != '') {
Carsten  Rose's avatar
Carsten Rose committed
564
            if ($givenDate == '0') {
565
                $givenDate = '0000-00-00';
Carsten  Rose's avatar
Carsten Rose committed
566
            }
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581

            $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 != '') {
Carsten  Rose's avatar
Carsten Rose committed
582
            if ($givenTime == '0') {
583
                $givenTime = '0:0:0';
Carsten  Rose's avatar
Carsten Rose committed
584
            }
585
586
587
588
589
590
591
592
593
594
595
596
597

            $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]);
            }
        }

Carsten  Rose's avatar
Carsten Rose committed
598
        if ($flagDateAndTime) {
599
600
601
602
603
604
605
606
607
608
            $delim = ' ';
        }

        return $newDate . $delim . $newTime;
    }

    /**
     * Returns a representation of 0 in a choosen variant.
     *
     * @param string $dateFormat FORMAT_DATE_INTERNATIONAL | FORMAT_DATE_GERMAN
609
     * @param string $showZero
Carsten  Rose's avatar
Carsten Rose committed
610
     * @param string $showTime '0' | '1'
611
     * @param string $showSeconds '0' | '1'
Carsten  Rose's avatar
Carsten Rose committed
612
     *
613
614
615
616
     * @return string
     */
    private static function dateTimeZero($dateFormat, $showZero, $showTime, $showSeconds) {

617
        if ($showZero != '1') {
618
            return '';
619
        }
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638

        // $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
639
     * @param string $dateString
Carsten  Rose's avatar
Carsten Rose committed
640
     *
641
     * @return array
Marc Egger's avatar
Marc Egger committed
642
     * @throws \UserFormException
643
644
645
646
647
648
649
     */
    private static function splitDateToArray($dateString) {

        if (strpos($dateString, '-') === false) {
            // FORMAT_DATE_GERMAN
            $arr = explode('.', $dateString);
            if (count($arr) != 3) {
Marc Egger's avatar
Marc Egger committed
650
                throw new \UserFormException("Unexpected format for date: $dateString", ERROR_DATE_UNEXPECTED_FORMAT);
651
652
653
654
655
656
657
658
            }
            $tmp = $arr[0];
            $arr[0] = $arr[2];
            $arr[2] = $tmp;
        } else {
            // FORMAT_DATE_INTERNATIONAL
            $arr = explode('-', $dateString);
            if (count($arr) != 3) {
Marc Egger's avatar
Marc Egger committed
659
                throw new \UserFormException("Unexpected format for date: $dateString", ERROR_DATE_UNEXPECTED_FORMAT);
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
            }
        }

        // 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
676
     *
677
     * @return string
Marc Egger's avatar
Marc Egger committed
678
     * @throws \UserFormException
679
680
681
682
     */
    public static function getDateTimePlaceholder(array $formElement) {

        $timePattern = ($formElement[FE_SHOW_SECONDS] == 1) ? 'hh:mm:ss' : 'hh:mm';
683
        switch ($formElement[FE_TYPE]) {
684
            case 'date':
685
                $placeholder = $formElement[FE_DATE_FORMAT];
686
687
                break;
            case 'datetime':
688
                $placeholder = $formElement[FE_DATE_FORMAT] . ' ' . $timePattern;
689
690
691
692
693
                break;
            case 'time':
                $placeholder = $timePattern;
                break;
            default:
Marc Egger's avatar
Marc Egger committed
694
                throw new \UserFormException("Unexpected Formelement type: '" . $formElement[FE_TYPE] . "'", ERROR_FORMELEMENT_TYPE);
695
696
697
698
699
700
        }

        return $placeholder;
    }


701
702
703
    /**
     * Encrypt curly braces by an uncommon string. Helps preventing unwished action on curly braces.
     *
Carsten  Rose's avatar
Carsten Rose committed
704
     * @param string $text
Carsten  Rose's avatar
Carsten Rose committed
705
     *
706
707
     * @return mixed
     */
708
    public static function encryptDoubleCurlyBraces($text) {
709
710
        $text = str_replace('{{', LONG_CURLY_OPEN, $text);
        $text = str_replace('}}', LONG_CURLY_CLOSE, $text);
711
712
713
714
715
716
717

        return $text;
    }

    /**
     * Decrypt curly braces by an uncommon string. Helps preventing unwished action on curly braces
     *
Carsten  Rose's avatar
Carsten Rose committed
718
     * @param string $text
Carsten  Rose's avatar
Carsten Rose committed
719
     *
720
721
722
     * @return mixed
     */
    public static function decryptDoubleCurlyBraces($text) {
723
724
725
726
727

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

728
729
        $text = str_replace(LONG_CURLY_OPEN, '{{', $text);
        $text = str_replace(LONG_CURLY_CLOSE, '}}', $text);
730
731
732
733

        return $text;
    }

734
    /**
735
736
737
     * Creates a random string, starting with uniq microseconds timestamp.
     * After a discussion of ME & CR, the uniqid() should be sufficient to guarantee uniqness.
     *
738
     * @param int $length Length of the required hash string
Carsten  Rose's avatar
Carsten Rose committed
739
     *
740
741
742
743
     * @return string       A random alphanumeric hash
     */
    public static function randomAlphaNum($length) {
        $possible_characters = "abcdefghijklmnopqrstuvwxyz1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ";
744
745
746
747
748
749
750
751
        $allChars = strlen($possible_characters);

        $string = uniqid();

        $ii = $length - strlen($string);

        while ($ii-- > 0) {
            $string .= substr($possible_characters, rand() % ($allChars), 1);
752
753
754
755
756
        }

        return ($string);
    }

757
    /**
Carsten  Rose's avatar
Carsten Rose committed
758
759
     * 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
760
     * @param string $url
761
     * @param string|array $param
Carsten  Rose's avatar
Carsten Rose committed
762
     *
763
764
765
     * @return string
     */
    public static function concatUrlParam($url, $param) {
766
767
768
769
770

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

771
        if ($param == '') {
772
            return $url;
773
        }
774

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

777
778
        return $url . $token . $param;
    }
779

780
781
782
783
784
785
786
787
788
789
    /**
     * 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
790
     *
791
     * @return string
Marc Egger's avatar
Marc Egger committed
792
     * @throws \CodeException
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
     */
    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), '?')) {
Marc Egger's avatar
Marc Egger committed
815
            throw new \CodeException('Found a "?" after the beginning of the query - this is forbidden', ERROR_BROKEN_PARAMETER);
816
817
818
819
820
821
822
823
824
825
        }

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

        return $url;
    }

826
827
828
829
    /**
     * Set Defaults for the current formElement.
     *
     * @param array $formElement
830
     * @param array $formSpec
831
     * @return array
832
     *
Marc Egger's avatar
Marc Egger committed
833
834
835
     * @throws \CodeException
     * @throws \UserFormException
     * @throws \UserReportException
836
     */
837
    public static function setFeDefaults(array $formElement, array $formSpec = array()) {
838
839
840
841

        $store = Store::getInstance();

        // Some Defaults
Carsten  Rose's avatar
Carsten Rose committed
842
        self::setIfNotSet($formElement, FE_INPUT_CLEAR_ME, $formSpec[F_INPUT_CLEAR_ME] ?? '');
843
        self::setIfNotSet($formElement, FE_SHOW_SECONDS, '0');
844
        self::setIfNotSet($formElement, FE_TIME_IS_OPTIONAL, '0');
845
        self::setIfNotSet($formElement, FE_SHOW_ZERO, '0');
846
        self::setIfNotSet($formElement, FE_HIDE_ZERO, '0');
847
848
        self::setIfNotSet($formElement, FE_DATE_FORMAT, $store->getVar(SYSTEM_DATE_FORMAT, STORE_SYSTEM));

849
850
851
        self::setIfNotSet($formElement, FE_HTML_BEFORE);
        self::setIfNotSet($formElement, FE_HTML_AFTER);

852
        self::setIfNotSet($formElement, FE_DATA_REFERENCE, ($formElement[FE_NAME] == '' ? $formElement[FE_ID] : $formElement[FE_NAME]));
853

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

856
        if (isset($formSpec[F_BS_LABEL_COLUMNS])) {
857
858
859
860
861
862
            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] != '') {
863
            $formElement[FE_MODE] = $formElement[FE_MODE_SQL];
864
        }
865

866
867
        if (isset($formSpec[F_MODE_GLOBAL])) {
            $formElement[FE_MODE] = self::applyFormModeToFormElement($formElement[FE_MODE], $formSpec[F_MODE_GLOBAL]);
868
869
        }

870
        // set typeAheadPedantic
871
        if (isset($formElement[FE_TYPEAHEAD_PEDANTIC]) && $formElement[FE_TYPEAHEAD_PEDANTIC] === '') {
872
873
874
875
876
877
            $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');
        }

878
879
        // Will be used to change dynamicUpdate behaviour
        if (isset($formElement[FE_WRAP_ROW_LABEL_INPUT_NOTE])) {
880
881
            $formElement[FE_FLAG_ROW_OPEN_TAG] = self::findInSet('row', $formElement[FE_WRAP_ROW_LABEL_INPUT_NOTE]);
            $formElement[FE_FLAG_ROW_CLOSE_TAG] = self::findInSet('/row', $formElement[FE_WRAP_ROW_LABEL_INPUT_NOTE]);
882
883
884
885
886
        } else {
            $formElement[FE_FLAG_ROW_OPEN_TAG] = true;
            $formElement[FE_FLAG_ROW_CLOSE_TAG] = false;
        }

887
888
        self::setIfNotSet($formElement, FE_INPUT_EXTRA_BUTTON_INFO_CLASS, $store->getVar(FE_INPUT_EXTRA_BUTTON_INFO_CLASS, STORE_SYSTEM));

889
        // For specific FE hard coded 'checkType'
890
891
892
893
        switch ($formElement[FE_TYPE]) {
            case FE_TYPE_IMAGE_CUT:
                $formElement[FE_CHECK_TYPE] = SANITIZE_ALLOW_ALLBUT;
                break;
894
895
896
            case FE_TYPE_ANNOTATE:
                $formElement[FE_CHECK_TYPE] = SANITIZE_ALLOW_ALL;
                break;
897
898
899
900
            default:
                break;
        }

901
902
        self::setIfNotSet($formElement, FE_MIN);
        self::setIfNotSet($formElement, FE_MAX);
903
904
905
906
        self::setIfNotSet($formElement, FE_DECIMAL_FORMAT);

        self::setIfNotSet($formElement, F_FE_DATA_PATTERN_ERROR);

907
908
        $typeSpec = $store->getVar($formElement[FE_NAME], STORE_TABLE_COLUMN_TYPES);
        self::adjustFeToColumnDefinition($formElement, $typeSpec);
909
910
911
912
913
914
915
916
917

        return $formElement;
    }

    /**
     * Adjusts several FE parameters using smart guesses based on the table column definition and other parameters.
     *
     * @param array $formElement
     * @param $typeSpec
Marc Egger's avatar
Marc Egger committed
918
     * @throws \UserFormException
919
     */
920
    public static function adjustFeToColumnDefinition(array &$formElement, $typeSpec) {
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944

        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],
945
            'int' => [-2147483648, 2147483647, SANITIZE_ALLOW_NUMERICAL, 0, 4294967295, SANITIZE_ALLOW_DIGIT],
946
947
948
949
950
            'bigint' => [-9223372036854775808, 9223372036854775807, SANITIZE_ALLOW_NUMERICAL, 0, 18446744073709551615, SANITIZE_ALLOW_DIGIT],
        ];

        $min = '';
        $max = '';
951
        $checkType = SANITIZE_ALLOW_ALNUMX;
952
        $inputType = '';
953
        $isANumber = true;
954

955
956
957
958
959
960
961
962
        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:
963
                if ($formElement[FE_ENCODE] === FE_ENCODE_SPECIALCHAR || $formElement[FE_ENCODE] === FE_ENCODE_SINGLE_TICK)
964
965
966
967
                    $checkType = SANITIZE_ALLOW_ALL;
                break;
        }

968
969
970
971
972
973
        switch ($token) {
            case 'tinyint':
            case 'smallint':
            case 'mediumint':
            case 'int':
            case 'bigint':
974
                $inputType = HTML_INPUT_TYPE_NUMBER;
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
                $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':
994
                $inputType = HTML_INPUT_TYPE_NUMBER;
995
996
                $checkType = SANITIZE_ALLOW_DIGIT;
                break;
997
998
999
1000

            default:
                $isANumber = false;
                break;
For faster browsing, not all history is shown. View entire blame