Store.php 28.3 KB
Newer Older
1
2
3
4
5
6
7
8
<?php
/**
 * Created by PhpStorm.
 * User: crose
 * Date: 1/1/16
 * Time: 6:51 PM
 */

9
namespace qfq;
10

11
use qfq\CodeException;
12
13
use qfq\keyValueStringParser;
use qfq\OnArray;
14
use qfq;
15

16
require_once(__DIR__ . '/../../qfq/helper/KeyValueStringParser.php');
Carsten  Rose's avatar
Carsten Rose committed
17
require_once(__DIR__ . '/../../qfq/helper/Sanitize.php');
18
require_once(__DIR__ . '/../../qfq/Constants.php');
19
require_once(__DIR__ . '/../../qfq/store/Sip.php');
20
//require_once(__DIR__ . '/../../qfq/store/Session.php');
21
require_once(__DIR__ . '/../../qfq/Database.php');
22
23
24
25
26
27
28
29
30
31

/*
 * Stores:
 * - SIP
 * - webVar
 * - record
 * - form
 * - formElement
 */

Carsten  Rose's avatar
Carsten Rose committed
32
33
/**
 * Class Store
34
 * @package qfq
Carsten  Rose's avatar
Carsten Rose committed
35
 */
36
37
class Store {

Carsten  Rose's avatar
Carsten Rose committed
38
39
40
    /**
     * @var Store Instance of class Store. There should only be one class 'Store' at a time.
     */
41
42
    private static $instance = null;

Carsten  Rose's avatar
Carsten Rose committed
43
44
45
    /**
     * @var Sip Instance of class SIP
     */
46
47
    private static $sip = null;

48
49
50
    /**
     * @var Session Instance of class Session
     */
51
//    private static $session = null;
52

Carsten  Rose's avatar
Carsten Rose committed
53
54
55
56
57
58
59
60
61
62
    /**
     * @var array Stores all indiviudal stores with the variable raw values
     *
     * $raw['D']['id'] = 0  - Defaultvalues from Tabledefinition
     * ...
     * $raw['S']['r'] = 1234 - record ID from current SIP identifier
     * ...
     * $raw['C']['HTTP_SERVER'] = 'qfq' - Servername
     * $raw['C']['s'] = 'badcaffee1234' - recent SIP
     */
63
    private static $raw = array();
Carsten  Rose's avatar
Carsten Rose committed
64
65

    /**
Carsten  Rose's avatar
Carsten Rose committed
66
     * @var array Default sanitize classes.
Carsten  Rose's avatar
Carsten Rose committed
67
     */
Carsten  Rose's avatar
Carsten Rose committed
68
    private static $sanitizeClass = array();
Carsten  Rose's avatar
Carsten Rose committed
69
70

    /**
Carsten  Rose's avatar
Carsten Rose committed
71
72
     * $sanitizeClass['S'] = false
     * $sanitizeClass['C'] = true
Carsten  Rose's avatar
Carsten Rose committed
73
74
     * ...
     *
Carsten  Rose's avatar
Carsten Rose committed
75
     * @var array each entry with true/false - depending if store needs to be sanitized.
Carsten  Rose's avatar
Carsten Rose committed
76
     */
Carsten  Rose's avatar
Carsten Rose committed
77
    private static $sanitizeStore = array();
78

Carsten  Rose's avatar
Carsten Rose committed
79
    private static $phpUnit = false;
80

81

82
    /**
83
     * @param string $bodytext
84
85
86
     * @param string $fileConfigIni
     * @throws UserFormException
     * @throws \qfq\CodeException
87
     */
88
    private function __construct($bodytext = '', $fileConfigIni = '') {
89

90
//        self::$session = Session::getInstance(self::$phpUnit);
91

92
93
94
95
96
        // This check is critical for some unwanted exception recursion during startup.
        if(!function_exists('normalizer_normalize')) {
            throw new CodeException("Function normalizer_normalize() not found - Please install 'php5-intl' / 'php7.0-intl'", ERROR_MISSING_INTL );
        }

Carsten  Rose's avatar
Carsten Rose committed
97
98
99
100
101
102
103
104
105
        self::$sanitizeClass = [
//            TYPO3_DEBUG_LOAD => SANITIZE_ALLOW_DIGIT,
//            TYPO3_DEBUG_SAVE => SANITIZE_ALLOW_DIGIT,
//            TYPO3_FORM => SANITIZE_ALLOW_ALNUMX,
//            TYPO3_FE_USER => SANITIZE_ALLOW_ALNUMX,
//            TYPO3_FE_USER_UID => SANITIZE_ALLOW_DIGIT,
//            TYPO3_FE_USER_GROUP => SANITIZE_ALLOW_ALNUMX,

            CLIENT_SIP => SANITIZE_ALLOW_ALNUMX,
106
            CLIENT_TYPO3VARS => SANITIZE_ALLOW_ALNUMX,
Carsten  Rose's avatar
Carsten Rose committed
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
            CLIENT_RECORD_ID => SANITIZE_ALLOW_DIGIT,
            CLIENT_KEY_SEM_ID => SANITIZE_ALLOW_DIGIT,
            CLIENT_KEY_SEM_ID_USER => SANITIZE_ALLOW_DIGIT,
            CLIENT_PAGE_ID => SANITIZE_ALLOW_DIGIT,
            CLIENT_PAGE_TYPE => SANITIZE_ALLOW_DIGIT,
            CLIENT_PAGE_LANGUAGE => SANITIZE_ALLOW_DIGIT,
            CLIENT_FORM => SANITIZE_ALLOW_ALNUMX,

            // Part of $_SERVER. Missing vars must be requested individual with the needed sanitize class.
            CLIENT_SCRIPT_URL => SANITIZE_ALLOW_ALNUMX,
            CLIENT_SCRIPT_URI => SANITIZE_ALLOW_ALNUMX,
            CLIENT_HTTP_HOST => SANITIZE_ALLOW_ALNUMX,
            CLIENT_HTTP_USER_AGENT => SANITIZE_ALLOW_ALNUMX,
            CLIENT_SERVER_NAME => SANITIZE_ALLOW_ALNUMX,
            CLIENT_SERVER_ADDRESS => SANITIZE_ALLOW_ALNUMX,
            CLIENT_SERVER_PORT => SANITIZE_ALLOW_DIGIT,
            CLIENT_REMOTE_ADDRESS => SANITIZE_ALLOW_ALNUMX,
            CLIENT_REQUEST_SCHEME => SANITIZE_ALLOW_ALNUMX,
            CLIENT_SCRIPT_FILENAME => SANITIZE_ALLOW_ALNUMX,
            CLIENT_QUERY_STRING => SANITIZE_ALLOW_ALL,
            CLIENT_REQUEST_URI => SANITIZE_ALLOW_ALL,
            CLIENT_SCRIPT_NAME => SANITIZE_ALLOW_ALNUMX,
            CLIENT_PHP_SELF => SANITIZE_ALLOW_ALNUMX,
130
//            CLIENT_UPLOAD_FILENAME => SANITIZE_ALLOW_ALLBUT,
Carsten  Rose's avatar
Carsten Rose committed
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148

//            SYSTEM_DBUSER => SANITIZE_ALLOW_ALNUMX,
//            SYSTEM_DBSERVER => SANITIZE_ALLOW_ALNUMX,
//            SYSTEM_DBPW => SANITIZE_ALLOW_ALL,
//            SYSTEM_DB => SANITIZE_ALLOW_ALNUMX,
//            SYSTEM_TESTDB => SANITIZE_ALLOW_ALNUMX,
//            SYSTEM_SESSIONNAME => SANITIZE_ALLOW_ALNUMX,
//            SYSTEM_DBH => SANITIZE_ALLOW_ALL,

//            SYSTEM_SQL_RAW => SANITIZE_ALLOW_ALL,
//            SYSTEM_SQL_FINAL => SANITIZE_ALLOW_ALL,
//            SYSTEM_SQL_COUNT => SANITIZE_ALLOW_DIGIT,
//            SYSTEM_SQL_PARAM_ARRAY => SANITIZE_ALLOW_ALL,

//            SIP_SIP => SANITIZE_ALLOW_ALNUMX,
//            SIP_RECORD_ID => SANITIZE_ALLOW_DIGIT,
//            SIP_FORM => SANITIZE_ALLOW_ALNUMX,
//            SIP_URLPARAM => SANITIZE_ALLOW_ALL
149

150
151
        ];

Carsten  Rose's avatar
Carsten Rose committed
152
        self::$sanitizeStore = [
153
154
155
            STORE_FORM => true,
            STORE_SIP => false,
            STORE_RECORD => false,
156
            STORE_BEFORE => false,
157
            STORE_PARENT_RECORD => false,
158
159
            STORE_TABLE_DEFAULT => false,
            STORE_TABLE_COLUMN_TYPES => false,
160
161
            STORE_CLIENT => true,
            STORE_TYPO3 => false,
162
            STORE_VAR => false,
163
            STORE_ZERO => false,
164
            STORE_EMPTY => false,
165
            STORE_SYSTEM => false,
166
167
            STORE_EXTRA => false,
            STORE_ADDITIONAL_FORM_ELEMENTS => false
168
169
        ];

170
        self::fillSystemStore($fileConfigIni);
171
        self::fillStoreTypo3($bodytext);
172
        self::fillStoreClient();
173
        self::fillStoreSip();
Carsten  Rose's avatar
Carsten Rose committed
174
        self::fillStoreExtra();
175
    }
176

177
    /**
178
     * Fill the system store by reading config.qfq.ini. Also setup config defaults.
Carsten  Rose's avatar
Carsten Rose committed
179
     *
180
     * @throws CodeException
181
     * @throws qfq\UserFormException
182
     */
183
    private static function fillSystemStore($fileConfigIni = '') {
184

185
        if ($fileConfigIni == '') {
186
            // Production Path to CONFIG_INI
187
188
189
190
            $fileConfigIni = __DIR__ . '/../../../../../' . CONFIG_INI;
            if (!file_exists($fileConfigIni)) {
                // PHPUnit Path to CONFIG_INI
                $fileConfigIni = __DIR__ . '/../../../' . CONFIG_INI;
191
192
193
            }
        }

194
        try {
195
            $config = parse_ini_file($fileConfigIni, false);
196

197
        } catch (\Exception $e) {
198
            throw new qfq\UserFormException ("Error read file " . $fileConfigIni . ": " . $e->getMessage(), ERROR_IO_READ_FILE);
199
        }
200

201
202
203
204
205
206
207
208
        $config = self::renameConfigElements($config);

        // Defaults
        Support::setIfNotSet($config, SYSTEM_DATE_FORMAT, 'yyyy-mm-dd');
        Support::setIfNotSet($config, SYSTEM_SHOW_DEBUG_INFO, 'auto');
        Support::setIfNotSet($config, F_BS_LABEL_COLUMNS, '3');
        Support::setIfNotSet($config, F_BS_INPUT_COLUMNS, '6');
        Support::setIfNotSet($config, F_BS_NOTE_COLUMNS, '3');
209
210
        Support::setIfNotSet($config, F_CLASS_PILL, 'qfq-color-grey-1');
        Support::setIfNotSet($config, F_CLASS_BODY, 'qfq-color-grey-2');
211
        Support::setIfNotSet($config, F_BUTTON_ON_CHANGE_CLASS, 'btn-info alert-info');
212
213
214


        $config = self::doSystemPath($config);
215
        $config = self::adjustConfig($config);
216

217
        self::checkMandatoryParameter($config);
218

219
        self::setStore($config, STORE_SYSTEM, true);
220
221
222
223
224
225
226
227
228
    }

    /**
     * Rename Elements defined in config.qfq.ini to more appropriate in user interaction.
     * E.g.: in config.qfq.ini everything is in upper case and word space is '_'. In Form.parameter it's lowercase and camel hook.
     *
     * @param array $config
     * @return array
     */
229
    private static function renameConfigElements(array $config) {
230
231
232
233
234
235

        // oldname > newname
        $setting = [
            [SYSTEM_FORM_BS_LABEL_COLUMNS, F_BS_LABEL_COLUMNS],
            [SYSTEM_FORM_BS_INPUT_COLUMNS, F_BS_INPUT_COLUMNS],
            [SYSTEM_FORM_BS_NOTE_COLUMNS, F_BS_NOTE_COLUMNS],
236
237
238
239
240
241
242
            [SYSTEM_FORM_DATA_PATTERN_ERROR, F_FE_DATA_PATTERN_ERROR],
            [SYSTEM_FORM_DATA_REQUIRED_ERROR, F_FE_DATA_REQUIRED_ERROR],
            [SYSTEM_FORM_DATA_MATCH_ERROR, F_FE_DATA_MATCH_ERROR],
            [SYSTEM_FORM_DATA_ERROR, F_FE_DATA_ERROR],
            [SYSTEM_CSS_CLASS_QFQ_FORM, F_CLASS],
            [SYSTEM_CSS_CLASS_QFQ_FORM_PILL, F_CLASS_PILL],
            [SYSTEM_CSS_CLASS_QFQ_FORM_BODY, F_CLASS_BODY],
243
            [SYSTEM_FORM_BUTTON_ON_CHANGE_CLASS, F_BUTTON_ON_CHANGE_CLASS],
244
245
246
247
248
249
250
251
252
253
254
255
        ];

        foreach ($setting as $row) {
            $oldName = $row[0];
            $newName = $row[1];

            if (isset($config[$oldName])) {
                $config[$newName] = $config[$oldName];
                if ($oldName != $newName) {
                    unset($config[$oldName]);
                }
            }
256
257
        }

258
259
260
261
        return $config;
    }

    /**
262
     * QFQ might be called via Typo3 (index.php) or directly via AJAX (directory: api). The
263
264
265
266
267
     * @param array $config
     * @return array
     */
    private function doSystemPath(array $config) {

268
269
270
271
272
273
274
        // SYSTEM_PATH_EXT: compute only if not already defined.
        if (!isset($config[SYSTEM_PATH_EXT]) || $config[SYSTEM_PATH_EXT] === '' || $config[SYSTEM_PATH_EXT][0] !== '/') {
            $relExtDir = '/typo3conf/ext/' . EXT_KEY;

            // If we are called through AJAX API (e.g. api/save.php), there is no TYPO3 environment.
            if (isset($_SERVER['SCRIPT_FILENAME'])) {
                $pos = strpos($_SERVER['SCRIPT_FILENAME'], $relExtDir);
275
276
                if ($pos === false && isset($GLOBALS['TYPO3_LOADED_EXT'][EXT_KEY]['ext_localconf.php'])) {

277
                    // Typo3 extension: probably index.php
278
                    $config[SYSTEM_PATH_EXT] = dirname($GLOBALS['TYPO3_LOADED_EXT'][EXT_KEY]['ext_localconf.php']);
279
                    $config[SYSTEM_SITE_PATH] = dirname($_SERVER['SCRIPT_FILENAME']);
280
                } else {
281
                    // API
282
                    $config[SYSTEM_PATH_EXT] = substr($_SERVER['SCRIPT_FILENAME'], 0, $pos + strlen($relExtDir));
283
                    $config[SYSTEM_SITE_PATH] = substr($_SERVER['SCRIPT_FILENAME'], 0, $pos);
284
                }
285
286
287
288
            } else {
                // No $_SERVER >>this means phpUnit.
                $config[SYSTEM_SITE_PATH] = getcwd();
                $config[SYSTEM_PATH_EXT] = getcwd();
289
290
            }
        }
291
292
293
294
295
296
297
298
299
300
301
302
303

        return $config;
    }

    /**
     * Depending on some configuration value, update corresponding values.
     *
     * @param array $config
     * @return array
     */
    private static function adjustConfig(array $config) {
        // Adjust config
        if ($config[SYSTEM_SHOW_DEBUG_INFO] === 'auto') {
304
305
306
307
            $rc = self::beUserLoggdIn();
            if ($rc !== false) {
                $config[SYSTEM_SHOW_DEBUG_INFO] = $rc;
            }
308
309
310
311
312
313
314
        }

        // make SQL PATH absolute. This is necessary to work in different directories correctly.
        if (isset($config[SYSTEM_SQL_LOG]) && $config[SYSTEM_SQL_LOG][0] !== '/') {
            $config[SYSTEM_SQL_LOG] = $config[SYSTEM_PATH_EXT] . '/' . $config[SYSTEM_SQL_LOG];
        }

315
        return $config;
316
317
    }

318
319
320
321
322
323
324
325
326
327
328
329
330
331
    /**
     * @return bool|string
     */
    private static function beUserLoggdIn() {

        if (isset($GLOBALS["TSFE"])) {
            $rc = (isset($GLOBALS["TSFE"]->beUserLogin) && $GLOBALS["TSFE"]->beUserLogin === true) ? 'yes' : 'no';
        } else {
            $rc = false;
        }

        return $rc;
    }

332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
    /**
     * Iterate over all Parameter which have to exist in the config. Throw an array if any is missing.
     *
     * @param array $config
     * @throws UserFormException
     */
    private static function checkMandatoryParameter(array $config) {
        // Check mandatory config vars.
        $names = array('DB_USER', 'DB_SERVER', 'DB_PASSWORD', 'DB_NAME', 'SQL_LOG', 'SQL_LOG_MODE');
        foreach ($names as $name) {
            if (!isset($config[$name])) {
                throw new qfq\UserFormException ("Missing configuration in `config.ini`: $name", ERROR_MISSING_CONFIG_INI_VALUE);
            }
        }
    }

348
    /**
Carsten  Rose's avatar
Carsten Rose committed
349
350
     * Set or overwrite a complete store.
     *
351
352
     * @param array $dataArray
     * @param $store
353
     * @param bool|false $flagOverwrite
354
     * @throws UserFormException
355
     * @throws \qfq\CodeException
356
     */
357
    public static function setStore(array $dataArray, $store, $flagOverwrite = false) {
358

Carsten  Rose's avatar
Carsten Rose committed
359
        // Check valid Storename
Carsten  Rose's avatar
Carsten Rose committed
360
        if (!isset(self::$sanitizeStore))
361
            throw new UserFormException("Unknown Store: $store", ERROR_UNNOWN_STORE);
Carsten  Rose's avatar
Carsten Rose committed
362

363
        if ($store === STORE_ZERO)
Carsten  Rose's avatar
Carsten Rose committed
364
            throw new CodeException("setVarArray() for STORE_ZERO is impossible - there are no values.", ERROR_SET_STORE_ZERO);
365

Carsten  Rose's avatar
Carsten Rose committed
366
        if (!$flagOverwrite && isset(self::$raw[$store]) && count(self::$raw[$store]) > 0) {
367
            throw new CodeException("Raw values already been copied to store '$store'. Do this only one time.", ERROR_STORE_VALUE_ALREADY_CODPIED);
368
        }
369

370
371
        self::$raw[$store] = $dataArray;
    }
372

373
    /**
374
375
376
     * Copy the BodyText as well as some T3 specific vars to STORE_TYPO3.
     * Attention: if called through API, there is no T3 environment. The only values which are available are fe_user and fe_user_uid.
     *
377
378
379
     * @param $bodytext
     * @throws CodeException
     */
380
    private static function fillStoreTypo3($bodytext) {
381

382
        // form=, showDebugBodyText=, 10.20..
383
        $arr = KeyValueStringParser::parse($bodytext, "=", "\n");
384

385
        if (isset($GLOBALS["TSFE"])) {
386

387
388
389
            if (isset($GLOBALS["TSFE"]->fe_user->user["username"])) {
                $arr[TYPO3_FE_USER] = $GLOBALS["TSFE"]->fe_user->user["username"];
            }
390

391
392
393
394
            if (isset($GLOBALS["TSFE"]->fe_user->user["uid"])) {
                $feUid = $GLOBALS["TSFE"]->fe_user->user["uid"];
                $arr[TYPO3_FE_USER_UID] = $GLOBALS["TSFE"]->fe_user->user["uid"];
            }
395

396
397
398
            if (isset($GLOBALS["TSFE"]->fe_user->user["usergroup"])) {
                $arr[TYPO3_FE_USER_GROUP] = $GLOBALS["TSFE"]->fe_user->user["usergroup"];
            }
Carsten  Rose's avatar
Carsten Rose committed
399

400
401
402
            if (isset($GLOBALS["TSFE"]->page["uid"])) {
                $arr[TYPO3_TT_CONTENT_UID] = $GLOBALS["TSFE"]->page["uid"];
            }
403

404
405
406
407
408
409
410
            if (isset($GLOBALS["TSFE"]->id)) {
                $arr[TYPO3_PAGE_ID] = $GLOBALS["TSFE"]->id;
            }

            if (isset($GLOBALS["TSFE"]->type)) {
                $arr[TYPO3_PAGE_TYPE] = $GLOBALS["TSFE"]->type;
            }
Carsten  Rose's avatar
Carsten Rose committed
411

412
413
414
            if (isset($GLOBALS["TSFE"]->sys_language_uid)) {
                $arr[TYPO3_PAGE_LANGUAGE] = $GLOBALS["TSFE"]->sys_language_uid;
            }
415

416
        } else {
417

418
419
            // No T3 environment (called by API): restore from SESSION
            foreach ([SESSION_FE_USER, SESSION_FE_USER_UID, SESSION_FE_USER_GROUP] as $key) {
420
421
422
                if (isset($_SESSION[SESSION_NAME][$key])) {
                    $arr[$key] = $_SESSION[SESSION_NAME][$key];
                }
423
424
            }
        }
425

426
        self::setStore($arr, STORE_TYPO3, true);
427
    }
428

429
    /**
Carsten  Rose's avatar
Carsten Rose committed
430
431
     * Fills the STORE_CLIENT
     *
432
433
     * @throws CodeException
     */
434
    private static function fillStoreClient() {
435
        // copy GET and POST and SERVER Parameter. Priority: SERVER, POST, GET
436
437
438
439
440
441
442
        $arr = array();
        if (isset($_GET))
            $arr = array_merge($arr, $_GET);

        if (isset($_POST))
            $arr = array_merge($arr, $_POST);

443
        // It's important to merge the SERVER array last: those entries shall overwrite client values.
444
445
        if (isset($_SERVER))
            $arr = array_merge($arr, $_SERVER);
446

447
        $arr = \qfq\Sanitize::normalize($arr);
448

449
        self::setStore($arr, STORE_CLIENT, true);
450

451
    }
452

Carsten  Rose's avatar
Carsten Rose committed
453
    /**
Carsten  Rose's avatar
Carsten Rose committed
454
455
     * Fills the STORE_SIP. Reads therefore specified SIP, decode the values and stores them in STORE_SIP.
     *
Carsten  Rose's avatar
Carsten Rose committed
456
     * @throws CodeException
457
     * @throws UserFormException
Carsten  Rose's avatar
Carsten Rose committed
458
     */
459
    private static function fillStoreSip() {
Carsten  Rose's avatar
Carsten Rose committed
460

461
        self::$sip = new Sip(self::$phpUnit);
462

463
464
465
466
        $s = self::getVar(CLIENT_SIP, STORE_CLIENT);
        if ($s !== false) {
            // if session is given, copy values to store
            $param = self::$sip->getVarsFromSip($s);
467
468
            $param[SIP_SIP] = $s;
            $param[SIP_URLPARAM] = self::$sip->getQueryStringFromSip($s);
469

470
//            self::setVarArray(KeyValueStringParser::parse($param, "=", "&"), STORE_SIP);
471
            self::setStore($param, STORE_SIP, true);
472
473
474
        }
    }

475
    /**
476
     * Cycles through all stores in $useStore.
477
     * First match will return the found value.
Carsten  Rose's avatar
Carsten Rose committed
478
     * During cycling: fill cache with requestet value and sanitize raw value.
479
     *
480
     * @param string $key
481
     * @param string $useStores f.e.: 'FSRD'
Carsten  Rose's avatar
Carsten Rose committed
482
     * @param string $sanitizeClass
Carsten  Rose's avatar
Carsten Rose committed
483
     * @param string $foundInStore Returns the name of the store where $key has been found. If $key is not found, return ''.
484
     * @return string a) if found: value, b) false
Carsten  Rose's avatar
Carsten Rose committed
485
     * @throws \qfq\CodeException
486
     */
Carsten  Rose's avatar
Carsten Rose committed
487
    public static function getVar($key, $useStores = STORE_USE_DEFAULT, $sanitizeClass = '', &$foundInStore = '') {
488
489

        // no store specifed?
490
        if ($useStores === "" || $useStores === null) {
491
            $useStores = STORE_USE_DEFAULT;
492
493
        }

494
        // no sanitizeClass specified: take predefined (if exist) or default.
495
        if ($sanitizeClass === '' || $sanitizeClass === null) {
Carsten  Rose's avatar
Carsten Rose committed
496
            $sanitizeClass = isset(self::$sanitizeClass[$key]) ? self::$sanitizeClass[$key] : SANITIZE_DEFAULT;
497
498
        }

499
500
501
        while ($useStores !== false) {

            $store = substr($useStores, 0, 1); // next store
Carsten  Rose's avatar
Carsten Rose committed
502
            $foundInStore = $store;
503
504
            $useStores = substr($useStores, 1); // shift left remaining stores

505
            if (!isset(self::$raw[$store][$key])) {
506
507
508
                switch ($store) {
                    case STORE_ZERO:
                        return 0;
509
510
                    case STORE_EMPTY:
                        return '';
511
512
                    case STORE_VAR:
                        if ($key === VAR_RANDOM) {
513
                            return Support::randomAlphaNum(RANDOM_LENGTH);
514
515
516
517
518
519
520
                        } else {
                            continue 2;  // no value provided, continue with while loop
                        }
                        break;
                    default:
                        continue 2; // no value provided, continue with while loop
                        break;
Carsten  Rose's avatar
Carsten Rose committed
521
                }
522
523
            }

524
            $rawVal = isset(self::$raw[$store][$key]) ? self::$raw[$store][$key] : null;
Carsten  Rose's avatar
Carsten Rose committed
525
            if (self::$sanitizeStore[$store] && $sanitizeClass != '') {
526
527
528
529
530
                if ($sanitizeClass == SANITIZE_ALLOW_PATTERN || $sanitizeClass == SANITIZE_ALLOW_MIN_MAX || $sanitizeClass == SANITIZE_ALLOW_MIN_MAX_DATE) {
                    // We do not have any pattern or min|max values at this point. For those who be affected, they already checked earlier. So set 'no check'
                    $sanitizeClass = SANITIZE_ALLOW_ALL;
                }
                return \qfq\Sanitize::sanitize($rawVal, $sanitizeClass, '', SANATIZE_EMPTY_STRING);
531
532
            } else {
                return $rawVal;
533
            }
534
        }
Carsten  Rose's avatar
Carsten Rose committed
535
        $foundInStore = '';
536
        return false;
537
    }
538

Carsten  Rose's avatar
Carsten Rose committed
539
    /**
Carsten  Rose's avatar
Carsten Rose committed
540
541
     * Fills the STORE_EXTRA.
     *
Carsten  Rose's avatar
Carsten Rose committed
542
543
544
545
     * @throws UserFormException
     * @throws \qfq\CodeException
     */
    private static function fillStoreExtra() {
546

547
        $value = Session::get(STORE_EXTRA);
548

549
        if (!isset($_SESSION[SESSION_NAME][STORE_EXTRA]) || $_SESSION[SESSION_NAME][STORE_EXTRA] === null) {
550
551
552
553
            $value = false;
        }

        if ($value === false) {
554
            self::setStore(array(), STORE_EXTRA, true);
555
        } else {
556
            self::setStore($_SESSION[SESSION_NAME][STORE_EXTRA], STORE_EXTRA, true);
557
        }
Carsten  Rose's avatar
Carsten Rose committed
558
559
    }

560
    /**
Carsten  Rose's avatar
Carsten Rose committed
561
562
     * Returns a pointer to this Class.
     *
563
     * @param string $bodytext
564
     * @param bool|false $phpUnit
565
566
567
568
     * @param string $fileConfigIni
     * @return null|Store
     * @throws UserFormException
     * @throws \qfq\CodeException
569
     */
570
    public static function getInstance($bodytext = '', $phpUnit = false, $fileConfigIni = '') {
571

572
        if ($phpUnit) {
573

574
            if (self::$instance !== null) {
575
                // fake to have a clean environment for the next test.
576
577
578
579
580
                self::unsetStore(STORE_TYPO3);
                self::fillStoreTypo3($bodytext);

                self::unsetStore(STORE_CLIENT);
                self::fillStoreClient();
581
            }
582
583
584
585
586

            // Testing different config files means initialize completely
            if ($fileConfigIni != '') {
                self::$instance = null;
            }
587
588
589
590
        }

        // Design Pattern: Singleton
        if (self::$instance === null) {
591
592
            self::$phpUnit = $phpUnit;

593
            self::$instance = new self($bodytext, $fileConfigIni);
Carsten  Rose's avatar
Carsten Rose committed
594
595
596
597
        } else {
            // Class Store seems to be presistent over multiple QFQ instantiation. Set bodytext again, with every new request (if bodytext is given).
            if ($bodytext !== '')
                self::fillStoreTypo3($bodytext);
598
599
        }

600
601
602
603
604
605
606
        // Disable TYPO3_DEBUG_SHOW_BODY_TEXT=1 if SYSTEM_SHOW_DEBUG_INFO!='yes'
        if (self::getVar(TYPO3_DEBUG_SHOW_BODY_TEXT, STORE_TYPO3) === '1' &&
            self::getVar(SYSTEM_SHOW_DEBUG_INFO, STORE_SYSTEM) !== 'yes'
        ) {
            self::setVar(TYPO3_DEBUG_SHOW_BODY_TEXT, '0', STORE_TYPO3);
        }

607
        return self::$instance;
608
    }
609

610
    /**
Carsten  Rose's avatar
Carsten Rose committed
611
612
     * Deletes a store assigning a new empty array to it.
     *
613
     * @param $store
614
615
     * @throws UserFormException
     * @throws \qfq\CodeException
616
617
     */
    public static function unsetStore($store) {
Carsten  Rose's avatar
Carsten Rose committed
618
        // Check valid Storename
Carsten  Rose's avatar
Carsten Rose committed
619
        if (!isset(self::$sanitizeStore))
620
            throw new UserFormException("Unknown Store: $store", ERROR_UNNOWN_STORE);
Carsten  Rose's avatar
Carsten Rose committed
621

622
        if ($store === STORE_ZERO)
Carsten  Rose's avatar
Carsten Rose committed
623
624
            throw new CodeException("unsetStore() for STORE_ZERO is impossible - there are no values.", ERROR_SET_STORE_ZERO);

625
626
627
        if (isset(self::$raw[$store])) {
            self::$raw[$store] = array();
        }
Carsten  Rose's avatar
Carsten Rose committed
628

629
630
    }

631
    /**
Carsten  Rose's avatar
Carsten Rose committed
632
633
     * Set's a single $key/$value pair $store.
     *
Carsten  Rose's avatar
Carsten Rose committed
634
635
636
     * @param string $key
     * @param string|array $value
     * @param string $store
637
638
639
640
641
642
643
644
645
646
647
648
649
     * @param bool|true $overWrite
     * @throws UserFormException
     * @throws \qfq\CodeException
     */
    public static function setVar($key, $value, $store, $overWrite = true) {
        // Check valid Storename
        if (!isset(self::$sanitizeStore))
            throw new UserFormException("Unknown Store: $store", ERROR_UNNOWN_STORE);

        if ($store === STORE_ZERO)
            throw new CodeException("setVar() for STORE_ZERO is impossible - there are no values.", ERROR_SET_STORE_ZERO);

        if ($overWrite === false && isset(self::$raw[$store][$key])) {
650
            throw new UserFormException("Value of '$key' already set in store '$store'.", ERROR_STORE_KEY_EXIST);
651
652
653
        }

        self::$raw[$store][$key] = $value;
Carsten  Rose's avatar
Carsten Rose committed
654
655
656

        // The STORE_EXTRA saves arrays and is persistent
        if ($store === STORE_EXTRA) {
657

658
            $store = Session::get(STORE_EXTRA);
659
660

            if ($store === false) {
661
                $store = array();
662
663
            }

664
665
            $store[$key] = $value;
            Session::set(STORE_EXTRA, $store);
666

Carsten  Rose's avatar
Carsten Rose committed
667
        }
668
669
    }

670
    /**
Carsten  Rose's avatar
Carsten Rose committed
671
672
     * Create a SIP after a form load. This is necessary on forms without a sip and on forms with r=0 (new record).
     *
673
674
675
     * @param $formName
     * @throws CodeException
     */
676
    public static function createSipAfterFormLoad($formName) {
677

678
        $recordId = self::getVar(CLIENT_RECORD_ID, STORE_TYPO3 . STORE_CLIENT);
679
680
681
682
        if ($recordId === false) {
            $recordId = 0;
        }

683
684
685
686
687
688
        // If there are existing SIP param, keep them by copying to the new SIP Param Array
        $tmpParam = self::getNonSystemSipParam();

        $tmpParam[SIP_RECORD_ID] = $recordId;
        $tmpParam[SIP_FORM] = $formName;

Carsten  Rose's avatar
#2067    
Carsten Rose committed
689
690
691
692
        if ($recordId == 0) {
            // SIPs for 'new records' needs to be uniq per TAB! Therefore add a uniq parameter
            $tmpParam[SIP_MAKE_URLPARAM_UNIQ] = uniqid();
        }
693
694

        // Construct fake urlparam
695
        $tmpUrlparam = OnArray::toString($tmpParam);
696
697

        // Create a fake SIP which has never been passed by URL - further processing might expect this to exist.
698
        $sip = self::getSipInstance()->queryStringToSip($tmpUrlparam, RETURN_SIP);
699
        self::setVar(CLIENT_SIP, $sip, STORE_CLIENT);
700
701
702

        // Store in SIP Store (cause it's empty until now).
        $tmpParam[SIP_SIP] = $sip;
703
        self::setStore($tmpParam, STORE_SIP, true);
704

705
706
707
    }

    /**
708
     * Return an array with non system SIP parameter. Take the whole STORE_SIP and search for non system parameter.
709
     *
710
711
712
     * @return array
     * @throws UserFormException
     * @throws \qfq\CodeException
713
     */
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
    private static function getNonSystemSipParam() {
        $tmpParam = array();

        $sipArray = self::getStore(STORE_SIP);

        foreach ($sipArray as $key => $value) {
            if ($key[0] === '_') {
                continue;
            }
            switch ($key) {
                case SIP_SIP:
                case SIP_RECORD_ID:
                case SIP_FORM;
                case SIP_URLPARAM:
                    continue;
                default:
                    $tmpParam[$key] = $value;
            }
        }

        return $tmpParam;
735
    }
736

737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
    /**
     * Returns a complete $store.
     *
     * @param $store
     * @return array
     * @throws UserFormException
     * @throws \qfq\CodeException
     */
    public static function getStore($store) {
        // Check valid Storename
        if (!isset(self::$sanitizeStore[$store]))
            throw new UserFormException("Unknown Store: $store", ERROR_UNNOWN_STORE);

        if ($store === STORE_ZERO)
            throw new CodeException("getStore() for STORE_ZERO is impossible - there are no values saved.", ERROR_GET_STORE_ZERO);

        if (isset(self::$raw[$store])) {
            return self::$raw[$store];
        }

        return array();
    }

760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
    /**
     * Returns a pointer to this class.
     *
     * @return null|Sip
     */
    public static function getSipInstance() {
        return self::$sip;
    }

    /**
     * Fills STORE_TABLE_DEFAULT and STORE_TABLE_COLUMN_TYPES
     *
     * @param $tableName
     * @throws CodeException
     */
    public static function fillStoreTableDefaultColumnType($tableName) {
        $db = new qfq\Database();

        $tableDefinition = $db->getTableDefinition($tableName);

        self::setStore(array_column($tableDefinition, 'Default', 'Field'), STORE_TABLE_DEFAULT, true);
        self::setStore(array_column($tableDefinition, 'Type', 'Field'), STORE_TABLE_COLUMN_TYPES, true);
    }

    /**
785
786
787
788
     * Saves a subset of STORE_TYPO3 vars as a SIP. The SIP will be transmitted as hidden form element.
     *
     * More docs: CODING.md > 'Faking the STORE_TYPO3 for API calls'
     *
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
     * @throws CodeException
     * @throws UserFormException
     */
    public static function copyT3VarsToSip() {

        $tempArray = self::getStore(STORE_TYPO3);

        foreach ([TYPO3_FE_USER, TYPO3_FE_USER_UID, TYPO3_FE_USER_GROUP, TYPO3_TT_CONTENT_UID, TYPO3_PAGE_ID, TYPO3_PAGE_TYPE, TYPO3_PAGE_LANGUAGE, TYPO3_BE_USER_LOGGED_IN] as $key) {
            if (isset($tempArray[$key])) {
                $t3varsArray[$key] = $tempArray[$key];
            }
        }

        $t3varsArray[TYPO3_BE_USER_LOGGED_IN] = (isset($GLOBALS["TSFE"]->beUserLogin) && $GLOBALS["TSFE"]->beUserLogin === true) ? 'yes' : 'no';

        $t3varsString = KeyValueStringParser::unparse($t3varsArray, '=', '&');
        $t3sip = self::$sip->queryStringToSip($t3varsString, RETURN_SIP);

        return $t3sip;
    }

    /**
811
812
813
     * Get stored STORE_TYPO3 vars from SIP and restore the store.
     *
     * More docs: CODING.md > 'Faking the STORE_TYPO3 for API calls'
814
815
816
817
818
819
820
821
822
823
824
825
826
827
     *
     * @param string $sipTypo3Vars
     */
    public function fillTypo3StoreFromSip($sipTypo3Vars) {
        $t3vars = self::getStore(STORE_TYPO3);

        // Do nothing if STORE_TYPO3 is already filled
        if (isset($t3vars[TYPO3_TT_CONTENT_UID]) && $t3vars[TYPO3_TT_CONTENT_UID] != '0') {
            return;
        }

        $typo3VarsArray = self::$sip->getVarsFromSip($sipTypo3Vars);

        self::setStore($typo3VarsArray, STORE_TYPO3, true);
Carsten  Rose's avatar
Carsten Rose committed
828

829
830
831
832
        // If necessary, update SYSTEM_SHOW_DEBUG_INFO
        if (self::getVar(SYSTEM_SHOW_DEBUG_INFO, STORE_SYSTEM) == 'auto') {
            $val = ($typo3VarsArray[TYPO3_BE_USER_LOGGED_IN] == 'yes') ? 'yes' : 'no';
            self::setVar(SYSTEM_SHOW_DEBUG_INFO, $val, STORE_SYSTEM);
833
834
        }
    }
835

836
837
838
839
840
}