Store.php 29.7 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;
12

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

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

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

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

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

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

Carsten  Rose's avatar
Carsten Rose committed
52
53
54
55
56
57
58
59
60
61
    /**
     * @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
     */
62
    private static $raw = array();
Carsten  Rose's avatar
Carsten Rose committed
63
64

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

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

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

80

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

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

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

Carsten  Rose's avatar
Carsten Rose committed
97
98
99
100
101
102
103
104
        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,

Carsten  Rose's avatar
Carsten Rose committed
105
106
107
108
            CLIENT_SIP => SANITIZE_ALLOW_ALNUMX,
            CLIENT_TYPO3VARS => SANITIZE_ALLOW_ALNUMX,
            CLIENT_RECORD_ID => SANITIZE_ALLOW_DIGIT,
            CLIENT_KEY_SEM_ID => SANITIZE_ALLOW_DIGIT,
Carsten  Rose's avatar
Carsten Rose committed
109
            CLIENT_KEY_SEM_ID_USER => SANITIZE_ALLOW_DIGIT,
Carsten  Rose's avatar
Carsten Rose committed
110
111
112
113
            CLIENT_PAGE_ID => SANITIZE_ALLOW_DIGIT,
            CLIENT_PAGE_TYPE => SANITIZE_ALLOW_DIGIT,
            CLIENT_PAGE_LANGUAGE => SANITIZE_ALLOW_DIGIT,
            CLIENT_FORM => SANITIZE_ALLOW_ALNUMX,
Carsten  Rose's avatar
Carsten Rose committed
114
115

            // Part of $_SERVER. Missing vars must be requested individual with the needed sanitize class.
Carsten  Rose's avatar
Carsten Rose committed
116
117
118
            CLIENT_SCRIPT_URL => SANITIZE_ALLOW_ALNUMX,
            CLIENT_SCRIPT_URI => SANITIZE_ALLOW_ALNUMX,
            CLIENT_HTTP_HOST => SANITIZE_ALLOW_ALNUMX,
Carsten  Rose's avatar
Carsten Rose committed
119
            CLIENT_HTTP_USER_AGENT => SANITIZE_ALLOW_ALNUMX,
Carsten  Rose's avatar
Carsten Rose committed
120
            CLIENT_SERVER_NAME => SANITIZE_ALLOW_ALNUMX,
Carsten  Rose's avatar
Carsten Rose committed
121
            CLIENT_SERVER_ADDRESS => SANITIZE_ALLOW_ALNUMX,
Carsten  Rose's avatar
Carsten Rose committed
122
            CLIENT_SERVER_PORT => SANITIZE_ALLOW_DIGIT,
Carsten  Rose's avatar
Carsten Rose committed
123
124
125
            CLIENT_REMOTE_ADDRESS => SANITIZE_ALLOW_ALNUMX,
            CLIENT_REQUEST_SCHEME => SANITIZE_ALLOW_ALNUMX,
            CLIENT_SCRIPT_FILENAME => SANITIZE_ALLOW_ALNUMX,
Carsten  Rose's avatar
Carsten Rose committed
126
127
128
129
            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 = [
Carsten  Rose's avatar
Carsten Rose committed
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
            STORE_FORM => true,
            STORE_SIP => false,
            STORE_RECORD => false,
            STORE_BEFORE => false,
            STORE_PARENT_RECORD => false,
            STORE_TABLE_DEFAULT => false,
            STORE_TABLE_COLUMN_TYPES => false,
            STORE_CLIENT => true,
            STORE_TYPO3 => false,
            STORE_VAR => false,
            STORE_ZERO => false,
            STORE_EMPTY => false,
            STORE_SYSTEM => false,
            STORE_EXTRA => false,
            STORE_LDAP => false,
Carsten  Rose's avatar
Carsten Rose committed
168
            STORE_ADDITIONAL_FORM_ELEMENTS => false,
169
170
        ];

171
172
        self::fillStoreTypo3($bodytext);  // should be filled before fillStoreSystem() to offer T3 variables
        self::fillStoreClient();  // should be filled before fillStoreSystem() to offer Client variables
173
        self::fillStoreExtra(); // should be filled before fillStoreSystem() to restore variables like SYSTEM_SHOW_DEBUG_INFO
174
        self::fillStoreSystem($fileConfigIni);
175
        self::fillStoreSip();
176
    }
177

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

186
187
        $cfg = new Config();
        $config = $cfg->readConfig($fileConfigIni);
188
189

        $config = self::doSystemPath($config);
190
        $config = self::adjustConfig($config);
191

192
        self::checkMandatoryParameter($config);
193

194
        self::setStore($config, STORE_SYSTEM, true);
195
196
197
    }

    /**
198
     * QFQ might be called via Typo3 (index.php) or directly via AJAX (directory: api). The
Carsten  Rose's avatar
Carsten Rose committed
199
     *
200
     * @param array $config
Carsten  Rose's avatar
Carsten Rose committed
201
     *
202
203
     * @return array
     */
Carsten  Rose's avatar
Carsten Rose committed
204
    private static function doSystemPath(array $config) {
205

206
207
208
209
210
211
212
        // 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);
213
214
                if ($pos === false && isset($GLOBALS['TYPO3_LOADED_EXT'][EXT_KEY]['ext_localconf.php'])) {

215
                    // Typo3 extension: probably index.php
216
                    $config[SYSTEM_PATH_EXT] = dirname($GLOBALS['TYPO3_LOADED_EXT'][EXT_KEY]['ext_localconf.php']);
217
                    $config[SYSTEM_SITE_PATH] = dirname($_SERVER['SCRIPT_FILENAME']);
218
                } else {
219
                    // API
220
                    $config[SYSTEM_PATH_EXT] = substr($_SERVER['SCRIPT_FILENAME'], 0, $pos + strlen($relExtDir));
221
                    $config[SYSTEM_SITE_PATH] = substr($_SERVER['SCRIPT_FILENAME'], 0, $pos);
222
                }
223
224
225
226
            } else {
                // No $_SERVER >>this means phpUnit.
                $config[SYSTEM_SITE_PATH] = getcwd();
                $config[SYSTEM_PATH_EXT] = getcwd();
227
228
            }
        }
229
230
231
232
233
234
235
236

        return $config;
    }

    /**
     * Depending on some configuration value, update corresponding values.
     *
     * @param array $config
Carsten  Rose's avatar
Carsten Rose committed
237
     *
238
239
240
     * @return array
     */
    private static function adjustConfig(array $config) {
241
242

        $config[SYSTEM_SHOW_DEBUG_INFO] = self::adjustConfigShowDebugInfo($config[SYSTEM_SHOW_DEBUG_INFO], self::beUserLoggdIn());
243
244

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

249
250
251
252
253
254
255
        // make SQL PATH absolute. This is necessary to work in different directories correctly.
        if (!empty($config[SYSTEM_MAIL_LOG]) && $config[SYSTEM_MAIL_LOG][0] !== '/') {
            $config[SYSTEM_MAIL_LOG] = $config[SYSTEM_PATH_EXT] . '/' . $config[SYSTEM_MAIL_LOG];
        }

        $config[SYSTEM_SEND_E_MAIL] = $config[SYSTEM_PATH_EXT] . '/qfq/external/sendEmail';

256
257
258
259
260
261
262
263
        // In case the database credentials are given in the old style: copy them to the new style
        if (!isset($config[SYSTEM_DB_1_USER]) && isset($config[SYSTEM_DB_USER])) {
            $config[SYSTEM_DB_1_USER] = $config[SYSTEM_DB_USER];
            $config[SYSTEM_DB_1_SERVER] = $config[SYSTEM_DB_SERVER];
            $config[SYSTEM_DB_1_PASSWORD] = $config[SYSTEM_DB_PASSWORD];
            $config[SYSTEM_DB_1_NAME] = $config[SYSTEM_DB_NAME];
        }

264
        return $config;
265
266
    }

267
    /**
268
     * @param string $value
Carsten  Rose's avatar
Carsten Rose committed
269
     *
270
     * @return string
271
     */
272
    private static function adjustConfigShowDebugInfo($value, $flag) {
273

274
        // Check if SHOW_DEBUG_INFO contains 'auto'. Replace with appropriate.
275
276
        if (Support::findInSet(SYSTEM_SHOW_DEBUG_INFO_AUTO, $value) && $flag) {
            $value = str_replace(SYSTEM_SHOW_DEBUG_INFO_AUTO, SYSTEM_SHOW_DEBUG_INFO_YES, $value);
277
278
        }

279
280
281
282
283
284
285
286
287
288
289
        return $value;
    }

    /**
     * Check if there is a Typo3 beUser is logged in. This is only possible if QFQ is called via T3, not via API
     *
     * @return bool  true: current Browser session is a logged in BE User.
     */
    private static function beUserLoggdIn() {

        return (!empty($GLOBALS["TSFE"]->beUserLogin) && $GLOBALS["TSFE"]->beUserLogin === true);
290
291
    }

292
293
294
295
    /**
     * Iterate over all Parameter which have to exist in the config. Throw an array if any is missing.
     *
     * @param array $config
Carsten  Rose's avatar
Carsten Rose committed
296
     *
297
298
299
     * @throws UserFormException
     */
    private static function checkMandatoryParameter(array $config) {
300

301
        // Check mandatory config vars.
302
303
304
305
        $names = array_merge(['SQL_LOG', 'SQL_LOG_MODE'],
            self::dbCredentialName($config[SYSTEM_DB_INDEX_DATA]),
            self::dbCredentialName($config[SYSTEM_DB_INDEX_QFQ]));

306
307
        foreach ($names as $name) {
            if (!isset($config[$name])) {
308
                throw new qfq\UserFormException ("Missing configuration in `" . CONFIG_INI . "`: $name", ERROR_MISSING_CONFIG_INI_VALUE);
309
310
311
312
            }
        }
    }

313
314
315
316
317
318
319
320
321
322
323
324
325
    /**
     *
     */
    private static function dbCredentialName($index) {
        $names = array();
        $names[] = 'DB_' . $index . '_USER';
        $names[] = 'DB_' . $index . '_SERVER';
        $names[] = 'DB_' . $index . '_PASSWORD';
        $names[] = 'DB_' . $index . '_NAME';

        return $names;
    }

326
    /**
Carsten  Rose's avatar
Carsten Rose committed
327
328
     * Set or overwrite a complete store.
     *
Carsten  Rose's avatar
Carsten Rose committed
329
     * @param array $dataArray
Carsten  Rose's avatar
Carsten Rose committed
330
     * @param            $store
331
     * @param bool|false $flagOverwrite
Carsten  Rose's avatar
Carsten Rose committed
332
     *
333
     * @throws UserFormException
334
     * @throws \qfq\CodeException
335
     */
336
    public static function setStore(array $dataArray, $store, $flagOverwrite = false) {
337

Carsten  Rose's avatar
Carsten Rose committed
338
        // Check valid Storename
Carsten  Rose's avatar
Carsten Rose committed
339
        if (!isset(self::$sanitizeStore))
340
            throw new UserFormException("Unknown Store: $store", ERROR_UNNOWN_STORE);
Carsten  Rose's avatar
Carsten Rose committed
341

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

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

349
350
        self::$raw[$store] = $dataArray;
    }
351

352
    /**
353
     * Copy the BodyText as well as some T3 specific vars to STORE_TYPO3.
Carsten  Rose's avatar
Carsten Rose committed
354
355
     * Attention: if called through API, there is no T3 environment. The only values which are available are fe_user
     * and fe_user_uid.
356
     *
357
     * @param $bodytext
Carsten  Rose's avatar
Carsten Rose committed
358
     *
359
360
     * @throws CodeException
     */
361
    private static function fillStoreTypo3($bodytext) {
362

363
        // form=, showDebugBodyText=, 10.20..
364
        $arr = KeyValueStringParser::parse($bodytext, "=", "\n");
365

366
        if (isset($GLOBALS["TSFE"])) {
367
            $arr = array_merge($arr, T3Info::getVars());
368
        } else {
369

370
371
            // No T3 environment (called by API): restore from SESSION
            foreach ([SESSION_FE_USER, SESSION_FE_USER_UID, SESSION_FE_USER_GROUP] as $key) {
372
373
374
                if (isset($_SESSION[SESSION_NAME][$key])) {
                    $arr[$key] = $_SESSION[SESSION_NAME][$key];
                }
375
376
            }
        }
377

378
        self::setStore($arr, STORE_TYPO3, true);
379
    }
380

381
    /**
Carsten  Rose's avatar
Carsten Rose committed
382
383
     * Fills the STORE_CLIENT
     *
384
385
     * @throws CodeException
     */
386
    private static function fillStoreClient() {
387

Carsten  Rose's avatar
Carsten Rose committed
388
        $arr = Client::getParam();
389

390
        self::setStore($arr, STORE_CLIENT, true);
391

392
    }
393

Carsten  Rose's avatar
Carsten Rose committed
394
    /**
Carsten  Rose's avatar
Carsten Rose committed
395
396
     * Fills the STORE_SIP. Reads therefore specified SIP, decode the values and stores them in STORE_SIP.
     *
Carsten  Rose's avatar
Carsten Rose committed
397
     * @throws CodeException
398
     * @throws UserFormException
Carsten  Rose's avatar
Carsten Rose committed
399
     */
400
    private static function fillStoreSip() {
Carsten  Rose's avatar
Carsten Rose committed
401

402
        self::$sip = new Sip(self::$phpUnit);
403

404
405
406
407
        $s = self::getVar(CLIENT_SIP, STORE_CLIENT);
        if ($s !== false) {
            // if session is given, copy values to store
            $param = self::$sip->getVarsFromSip($s);
408
409
            $param[SIP_SIP] = $s;
            $param[SIP_URLPARAM] = self::$sip->getQueryStringFromSip($s);
410

411
//            self::setVarArray(KeyValueStringParser::parse($param, "=", "&"), STORE_SIP);
412
            self::setStore($param, STORE_SIP, true);
413
414
415
        }
    }

416
    /**
417
     * Cycles through all stores in $useStore.
418
     * First match will return the found value.
419
     * During cycling: fill cache with requested value and sanitize raw value.
420
     *
421
     * @param string $key
422
     * @param string $useStores f.e.: 'FSRVD'
Carsten  Rose's avatar
Carsten Rose committed
423
     * @param string $sanitizeClass
Carsten  Rose's avatar
Carsten Rose committed
424
425
426
     * @param string $foundInStore Returns the name of the store where $key has been found. If $key is not found,
     *                             return ''.
     *
427
     * @return string a) if found: value, b) false
Carsten  Rose's avatar
Carsten Rose committed
428
     * @throws \qfq\CodeException
429
     */
Carsten  Rose's avatar
Carsten Rose committed
430
    public static function getVar($key, $useStores = STORE_USE_DEFAULT, $sanitizeClass = '', &$foundInStore = '') {
431
432

        // no store specifed?
433
        if ($useStores === "" || $useStores === null) {
434
            $useStores = STORE_USE_DEFAULT;
435
436
        }

437
        // no sanitizeClass specified: take predefined (if exist) or default.
438
        if ($sanitizeClass === '' || $sanitizeClass === null) {
Carsten  Rose's avatar
Carsten Rose committed
439
            $sanitizeClass = isset(self::$sanitizeClass[$key]) ? self::$sanitizeClass[$key] : SANITIZE_DEFAULT;
440
441
        }

Carsten  Rose's avatar
Carsten Rose committed
442
443
        $len = strlen(SIP_PREFIX_BASE64);

444
445
        while ($useStores !== false) {
            $store = substr($useStores, 0, 1); // next store
446
447
448
449
450
451

            $finalKey = $key;
            if ($store == STORE_LDAP) {
                $finalKey = strtolower($key); // in STORE_LDAP all keys are lowercase
            }

Carsten  Rose's avatar
Carsten Rose committed
452
            $foundInStore = $store;
453
454
            $useStores = substr($useStores, 1); // shift left remaining stores

455
            if (!isset(self::$raw[$store][$finalKey])) {
456
457
458
                switch ($store) {
                    case STORE_ZERO:
                        return 0;
459
460
                    case STORE_EMPTY:
                        return '';
461
                    case STORE_VAR:
462
                        if ($finalKey === VAR_RANDOM) {
463
                            return Support::randomAlphaNum(RANDOM_LENGTH);
464
465
466
467
468
469
470
                        } 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
471
                }
472
473
            }

474
            $rawVal = isset(self::$raw[$store][$finalKey]) ? self::$raw[$store][$finalKey] : null;
Carsten  Rose's avatar
Carsten Rose committed
475
            if (self::$sanitizeStore[$store] && $sanitizeClass != '') {
476
                if ($sanitizeClass == SANITIZE_ALLOW_PATTERN) {
477
                    // We do not have any pattern at this point. For those who be affected, they already checked earlier. So set 'no check'
478
479
                    $sanitizeClass = SANITIZE_ALLOW_ALL;
                }
Carsten  Rose's avatar
Carsten Rose committed
480

481
                return \qfq\Sanitize::sanitize($rawVal, $sanitizeClass, '', null, SANITIZE_EMPTY_STRING);
482
            } else {
Carsten  Rose's avatar
Carsten Rose committed
483
484
485
                if ($store == STORE_SIP && (substr($key, 0, $len) == SIP_PREFIX_BASE64)) {
                    $rawVal = base64_decode($rawVal);
                }
Carsten  Rose's avatar
Carsten Rose committed
486

487
                return $rawVal;
488
            }
489
        }
Carsten  Rose's avatar
Carsten Rose committed
490
        $foundInStore = '';
Carsten  Rose's avatar
Carsten Rose committed
491

492
        return false;
493
    }
494

Carsten  Rose's avatar
Carsten Rose committed
495
    /**
Carsten  Rose's avatar
Carsten Rose committed
496
497
     * Fills the STORE_EXTRA.
     *
Carsten  Rose's avatar
Carsten Rose committed
498
499
500
501
     * @throws UserFormException
     * @throws \qfq\CodeException
     */
    private static function fillStoreExtra() {
502

503
        $value = Session::get(STORE_EXTRA);
504

505
        if (!isset($_SESSION[SESSION_NAME][STORE_EXTRA]) || $_SESSION[SESSION_NAME][STORE_EXTRA] === null) {
506
507
508
509
            $value = false;
        }

        if ($value === false) {
510
            self::setStore(array(), STORE_EXTRA, true);
511
        } else {
512
            self::setStore($_SESSION[SESSION_NAME][STORE_EXTRA], STORE_EXTRA, true);
513
        }
514

Carsten  Rose's avatar
Carsten Rose committed
515
516
    }

517

518
    /**
Carsten  Rose's avatar
Carsten Rose committed
519
520
     * Returns a pointer to this Class.
     *
Carsten  Rose's avatar
Carsten Rose committed
521
     * @param string $bodytext
522
     * @param bool|false $phpUnit
Carsten  Rose's avatar
Carsten Rose committed
523
     * @param string $fileConfigIni
Carsten  Rose's avatar
Carsten Rose committed
524
     *
525
526
527
     * @return null|Store
     * @throws UserFormException
     * @throws \qfq\CodeException
528
     */
529
    public static function getInstance($bodytext = '', $phpUnit = false, $fileConfigIni = '') {
530

531
        if ($phpUnit) {
532

533
            if (self::$instance !== null) {
534
                // fake to have a clean environment for the next test.
535
536
537
538
539
                self::unsetStore(STORE_TYPO3);
                self::fillStoreTypo3($bodytext);

                self::unsetStore(STORE_CLIENT);
                self::fillStoreClient();
540
            }
541
542
543
544
545

            // Testing different config files means initialize completely
            if ($fileConfigIni != '') {
                self::$instance = null;
            }
546
547
548
549
        }

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

552
            self::$instance = new self($bodytext, $fileConfigIni);
Carsten  Rose's avatar
Carsten Rose committed
553
554
555
556
        } 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);
557
558
        }

559
560
        // Disable TYPO3_DEBUG_SHOW_BODY_TEXT=1 if SYSTEM_SHOW_DEBUG_INFO!='yes'
        if (self::getVar(TYPO3_DEBUG_SHOW_BODY_TEXT, STORE_TYPO3) === '1' &&
561
            !Support::findInSet(SYSTEM_SHOW_DEBUG_INFO_YES, self::getVar(SYSTEM_SHOW_DEBUG_INFO, STORE_SYSTEM))
562
563
564
565
        ) {
            self::setVar(TYPO3_DEBUG_SHOW_BODY_TEXT, '0', STORE_TYPO3);
        }

566
        return self::$instance;
567
    }
568

569
    /**
Carsten  Rose's avatar
Carsten Rose committed
570
571
     * Deletes a store assigning a new empty array to it.
     *
572
     * @param $store
Carsten  Rose's avatar
Carsten Rose committed
573
     *
574
575
     * @throws UserFormException
     * @throws \qfq\CodeException
576
577
     */
    public static function unsetStore($store) {
Carsten  Rose's avatar
Carsten Rose committed
578
        // Check valid Storename
Carsten  Rose's avatar
Carsten Rose committed
579
        if (!isset(self::$sanitizeStore))
580
            throw new UserFormException("Unknown Store: $store", ERROR_UNNOWN_STORE);
Carsten  Rose's avatar
Carsten Rose committed
581

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

585
586
587
        if (isset(self::$raw[$store])) {
            self::$raw[$store] = array();
        }
Carsten  Rose's avatar
Carsten Rose committed
588

589
590
    }

591
    /**
Carsten  Rose's avatar
Carsten Rose committed
592
593
     * Set's a single $key/$value pair $store.
     *
Carsten  Rose's avatar
Carsten Rose committed
594
     * @param string $key
Carsten  Rose's avatar
Carsten Rose committed
595
     * @param string|array $value
Carsten  Rose's avatar
Carsten Rose committed
596
597
     * @param string $store
     * @param bool|true $overWrite
Carsten  Rose's avatar
Carsten Rose committed
598
     *
599
600
601
602
603
604
605
606
607
608
609
     * @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);

610
611
        // Complain if already set and different.
        if ($overWrite === false && isset(self::$raw[$store][$key]) && self::$raw[$store][$key] != $value) {
612
            throw new UserFormException("Value of '$key' already set in store '$store'.", ERROR_STORE_KEY_EXIST);
613
614
615
        }

        self::$raw[$store][$key] = $value;
Carsten  Rose's avatar
Carsten Rose committed
616
617
618

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

620
            $store = Session::get(STORE_EXTRA);
621
622

            if ($store === false) {
623
                $store = array();
624
625
            }

626
627
            $store[$key] = $value;
            Session::set(STORE_EXTRA, $store);
Carsten  Rose's avatar
Carsten Rose committed
628
        }
629
630
    }

631
    /**
Carsten  Rose's avatar
Carsten Rose committed
632
633
     * Create a SIP after a form load. This is necessary on forms without a sip and on forms with r=0 (new record).
     *
634
     * @param $formName
Carsten  Rose's avatar
Carsten Rose committed
635
     *
636
637
     * @throws CodeException
     */
638
    public static function createSipAfterFormLoad($formName) {
639

640
        $recordId = self::getVar(CLIENT_RECORD_ID, STORE_TYPO3 . STORE_CLIENT);
641
642
643
644
        if ($recordId === false) {
            $recordId = 0;
        }

645
646
647
648
649
650
        // 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
651
652
653
654
        if ($recordId == 0) {
            // SIPs for 'new records' needs to be uniq per TAB! Therefore add a uniq parameter
            $tmpParam[SIP_MAKE_URLPARAM_UNIQ] = uniqid();
        }
655
656

        // Construct fake urlparam
657
        $tmpUrlparam = OnArray::toString($tmpParam);
658
659

        // Create a fake SIP which has never been passed by URL - further processing might expect this to exist.
660
        $sip = self::getSipInstance()->queryStringToSip($tmpUrlparam, RETURN_SIP);
661
        self::setVar(CLIENT_SIP, $sip, STORE_CLIENT);
662
663
664

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

667
668
669
    }

    /**
670
     * Return an array with non system SIP parameter. Take the whole STORE_SIP and search for non system parameter.
671
     *
672
673
674
     * @return array
     * @throws UserFormException
     * @throws \qfq\CodeException
675
     */
676
    public static function getNonSystemSipParam() {
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
        $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;
697
    }
698

699
700
701
702
    /**
     * Returns a complete $store.
     *
     * @param $store
Carsten  Rose's avatar
Carsten Rose committed
703
     *
704
705
706
707
708
     * @return array
     * @throws UserFormException
     * @throws \qfq\CodeException
     */
    public static function getStore($store) {
Carsten  Rose's avatar
Carsten Rose committed
709
710
        $vars = array();

711
        // Check valid Storename
Carsten  Rose's avatar
Carsten Rose committed
712
        if (!isset(self::$sanitizeStore[$store])) {
713
            throw new UserFormException("Unknown Store: $store", ERROR_UNNOWN_STORE);
Carsten  Rose's avatar
Carsten Rose committed
714
        }
715

Carsten  Rose's avatar
Carsten Rose committed
716
        if ($store === STORE_ZERO) {
717
            throw new CodeException("getStore() for STORE_ZERO is impossible - there are no values saved.", ERROR_GET_STORE_ZERO);
Carsten  Rose's avatar
Carsten Rose committed
718
        }
719
720

        if (isset(self::$raw[$store])) {
Carsten  Rose's avatar
Carsten Rose committed
721
722
723
724
            $vars = self::$raw[$store];
            if ($store == STORE_SIP) {
                $vars = self::checkDecodeBase64Arr($vars);
            }
725
726
        }

Carsten  Rose's avatar
Carsten Rose committed
727
728
729
730
731
732
733
734
        return $vars;
    }

    /**
     * Iterate over an array and look for keynames starting with SIP_PREFIX_BASE64.
     * Found elements will be base64_decode().
     *
     * @param array $vars
Carsten  Rose's avatar
Carsten Rose committed
735
     *
Carsten  Rose's avatar
Carsten Rose committed
736
737
738
739
740
741
742
743
744
745
746
     * @return array - incl. decoded base64 vars.
     */
    private static function checkDecodeBase64Arr(array $vars) {

        $len = strlen(SIP_PREFIX_BASE64);

        foreach ($vars as $key => $value) {
            if (substr($key, 0, $len) == SIP_PREFIX_BASE64) {
                $vars[$key] = base64_decode($value);
            }
        }
Carsten  Rose's avatar
Carsten Rose committed
747

Carsten  Rose's avatar
Carsten Rose committed
748
        return $vars;
749
750
    }

751
752
753
754
755
756
757
758
759
760
761
762
763
    /**
     * 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
Carsten  Rose's avatar
Carsten Rose committed
764
     *
765
766
767
768
769
770
771
772
773
774
775
776
     * @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);
    }

    /**
777
778
779
780
     * 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'
     *
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
     * @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;
    }

    /**
803
804
805
     * Get stored STORE_TYPO3 vars from SIP and restore the store.
     *
     * More docs: CODING.md > 'Faking the STORE_TYPO3 for API calls'
806
807
808
     *
     * @param string $sipTypo3Vars
     */
Carsten  Rose's avatar
Carsten Rose committed
809
    public static function fillTypo3StoreFromSip($sipTypo3Vars) {
810
811
812
813
814
815
816
817
818
819
        $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
820

821
        $value = self::getVar(SYSTEM_SHOW_DEBUG_INFO, STORE_SYSTEM);
822
823
        $flag = isset($typo3VarsArray[TYPO3_BE_USER_LOGGED_IN]) && ($typo3VarsArray[TYPO3_BE_USER_LOGGED_IN] == 'yes');
        $value = self::adjustConfigShowDebugInfo($value, $flag);
824
        self::setVar(SYSTEM_SHOW_DEBUG_INFO, $value, STORE_SYSTEM);
825
    }
826

827
    /**
828
     * Read SYSTEM_FILL_STORE_SYSTEM_BY_SQL_1|2|3 from SYSTEM_STORE and if set:
829
     * a) fire the SQL
830
     * b) merge all columns to STORE_SYSTEM
831
     */
832
    public static function StoreSystemUpdate() {
833

834
835
        $db = null;
        $storeSystemAdd = array();
836
837
        $storeSystem = self::getStore(STORE_SYSTEM);

838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
        for ($ii = 1; $ii <= 3; $ii++) {
            if (empty($storeSystem[SYSTEM_FILL_STORE_SYSTEM_BY_SQL . "_$ii"])) {
                continue;
            }

            if ($db == null) {
                $db = new qfq\Database();
            }

            $errMsg = "More than 1 record found. " . CONFIG_INI . ": " . SYSTEM_FILL_STORE_SYSTEM_BY_SQL . "_$ii";
            $mode = ROW_EXPECT_0_1;

            // If there is an error message defined, this means there should be exactly one record.
            if (!empty($storeSystem[SYSTEM_FILL_STORE_SYSTEM_ERROR_MSG . "_$ii"])) {
                $mode = ROW_EXPECT_1;
                $errMsg = $storeSystem[SYSTEM_FILL_STORE_SYSTEM_ERROR_MSG . "_$ii"];
854
            }
855
856
857
858
859
860
861
862
863

            $storeSystemAdd = $db->sql($storeSystem[SYSTEM_FILL_STORE_SYSTEM_BY_SQL . "_$ii"], $mode, array(), $errMsg);
            $storeSystemAdd = OnArray::keyNameRemoveLeadingUnderscore($storeSystemAdd);
            $storeSystem = array_merge($storeSystem, $storeSystemAdd);

        }

        if (!empty($storeSystem)) {
            self::setStore($storeSystem, STORE_SYSTEM, true);
864
865
        }
    }
866
867

    /**
868
     * Append an array (in case of 'array of array': the first row of array) to store $storeName.
869
870
     * Existing values will be overwritten.
     *
871
     * @param array $dataArray - in special cases it might be an empty string -therefore no type definition to 'array'.
872
873
874
875
     * @param $storeName
     * @throws CodeException
     * @throws UserFormException
     */
876
    public static function appendToStore($dataArray, $storeName) {
877

878
        if (empty($dataArray) || !is_array($dataArray)) {
879
880
            return;
        }
881

882
883
        if (isset($dataArray[0]) && is_array($dataArray[0])) {
            $append = $dataArray[0];
884
        } else {
885
            $append = $dataArray;
886
887
888
889
890
891
892
893
894
895
        }

        if (empty($append)) {
            return;
        }

        $store = Store::getInstance();
        $store->setStore(array_merge($store->getStore($storeName), $append), $storeName, true);
    }

896
897
898
899
900
}