Store.php 25.9 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
    private function __construct($bodytext = '', $fileConfigIni = '') {
86

87
//        self::$session = Session::getInstance(self::$phpUnit);
88

89
90
91
92
93
        // 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
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
        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,
            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,
126
//            CLIENT_UPLOAD_FILENAME => SANITIZE_ALLOW_ALLBUT,
Carsten  Rose's avatar
Carsten Rose committed
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144

//            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
145

146
147
        ];

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

165
        self::fillSystemStore($fileConfigIni);
166
        self::fillStoreTypo3($bodytext);
167
        self::fillStoreClient();
168
        self::fillStoreSip();
Carsten  Rose's avatar
Carsten Rose committed
169
        self::fillStoreExtra();
170

171
    }
172

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

181
        if ($fileConfigIni == '') {
182
            // Production Path to CONFIG_INI
183
184
185
186
            $fileConfigIni = __DIR__ . '/../../../../../' . CONFIG_INI;
            if (!file_exists($fileConfigIni)) {
                // PHPUnit Path to CONFIG_INI
                $fileConfigIni = __DIR__ . '/../../../' . CONFIG_INI;
187
188
189
            }
        }

190
        try {
191
            $config = parse_ini_file($fileConfigIni, false);
192

193
        } catch (\Exception $e) {
194
            throw new qfq\UserFormException ("Error read file " . $fileConfigIni . ": " . $e->getMessage(), ERROR_IO_READ_FILE);
195
        }
196

197
198
199
200
201
202
203
204
        $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');
205
206
        Support::setIfNotSet($config, F_CLASS_PILL, 'qfq-color-grey-1');
        Support::setIfNotSet($config, F_CLASS_BODY, 'qfq-color-grey-2');
207
        Support::setIfNotSet($config, F_BUTTON_ON_CHANGE_CLASS, 'btn-info alert-info');
208
209
210


        $config = self::doSystemPath($config);
211
        $config = self::adjustConfig($config);
212

213
        self::checkMandatoryParameter($config);
214
215
216
217
218
219
220
221
222
223
224

        self::setVarArray($config, STORE_SYSTEM, true);
    }

    /**
     * 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
     */
225
    private static function renameConfigElements(array $config) {
226
227
228
229
230
231

        // 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],
232
233
234
235
236
237
238
            [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],
239
            [SYSTEM_FORM_BUTTON_ON_CHANGE_CLASS, F_BUTTON_ON_CHANGE_CLASS],
240
241
242
243
244
245
246
247
248
249
250
251
        ];

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

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

254
255
256
257
        return $config;
    }

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

264
265
266
267
268
269
270
        // 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);
271
272
                if ($pos === false && isset($GLOBALS['TYPO3_LOADED_EXT'][EXT_KEY]['ext_localconf.php'])) {

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

        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') {
            $config[SYSTEM_SHOW_DEBUG_INFO] = (isset($GLOBALS["TSFE"]->beUserLogin) && $GLOBALS["TSFE"]->beUserLogin === true) ? 'yes' : 'no';
        }

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

308
        return $config;
309
310
    }

311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
    /**
     * 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);
            }
        }
    }

327
    /**
Carsten  Rose's avatar
Carsten Rose committed
328
329
     * Set or overwrite a complete store.
     *
330
331
     * @param array $dataArray
     * @param $store
332
     * @param bool|false $flagOverwrite
333
     * @throws UserFormException
334
     * @throws \qfq\CodeException
335
     */
336
    public static function setVarArray(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
354
355
     * 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.
     *
356
357
358
     * @param $bodytext
     * @throws CodeException
     */
359
    private static function fillStoreTypo3($bodytext) {
360

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

364
        if (isset($GLOBALS["TSFE"])) {
365

366
367
368
            if (isset($GLOBALS["TSFE"]->fe_user->user["username"])) {
                $arr[TYPO3_FE_USER] = $GLOBALS["TSFE"]->fe_user->user["username"];
            }
369

370
371
372
373
            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"];
            }
374

375
376
377
            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
378

379
380
381
            if (isset($GLOBALS["TSFE"]->page["uid"])) {
                $arr[TYPO3_TT_CONTENT_UID] = $GLOBALS["TSFE"]->page["uid"];
            }
382

383
384
385
386
387
388
389
            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
390

391
392
393
            if (isset($GLOBALS["TSFE"]->sys_language_uid)) {
                $arr[TYPO3_PAGE_LANGUAGE] = $GLOBALS["TSFE"]->sys_language_uid;
            }
394

395
        } else {
396

397
            // NO T3 environment (called by API): restore from SESSION
398
399
400
401
            foreach([ SESSION_FE_USER, SESSION_FE_USER_UID, SESSION_FE_USER_GROUP ] as $key) {
                if (isset($_SESSION[SESSION_NAME][$key])) {
                    $arr[$key] = $_SESSION[SESSION_NAME][$key];
                }
402
403
            }
        }
404

405
        self::setVarArray($arr, STORE_TYPO3, true);
406
    }
407

408
    /**
Carsten  Rose's avatar
Carsten Rose committed
409
410
     * Fills the STORE_CLIENT
     *
411
412
     * @throws CodeException
     */
413
    private static function fillStoreClient() {
414
        // copy GET and POST and SERVER Parameter. Priority: SERVER, POST, GET
415
416
417
418
419
420
421
        $arr = array();
        if (isset($_GET))
            $arr = array_merge($arr, $_GET);

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

422
        // It's important to merge the SERVER array last: those entries shall overwrite client values.
423
424
        if (isset($_SERVER))
            $arr = array_merge($arr, $_SERVER);
425

426
        $arr = \qfq\Sanitize::normalize($arr);
427

428
        self::setVarArray($arr, STORE_CLIENT, true);
429

430
    }
431

Carsten  Rose's avatar
Carsten Rose committed
432
    /**
Carsten  Rose's avatar
Carsten Rose committed
433
434
     * Fills the STORE_SIP. Reads therefore specified SIP, decode the values and stores them in STORE_SIP.
     *
Carsten  Rose's avatar
Carsten Rose committed
435
     * @throws CodeException
436
     * @throws UserFormException
Carsten  Rose's avatar
Carsten Rose committed
437
     */
438
    private static function fillStoreSip() {
Carsten  Rose's avatar
Carsten Rose committed
439

440
        self::$sip = new Sip(self::$phpUnit);
441

442
443
444
445
        $s = self::getVar(CLIENT_SIP, STORE_CLIENT);
        if ($s !== false) {
            // if session is given, copy values to store
            $param = self::$sip->getVarsFromSip($s);
446
447
            $param[SIP_SIP] = $s;
            $param[SIP_URLPARAM] = self::$sip->getQueryStringFromSip($s);
448

449
//            self::setVarArray(KeyValueStringParser::parse($param, "=", "&"), STORE_SIP);
450
            self::setVarArray($param, STORE_SIP, true);
451
452
453
        }
    }

454
    /**
455
     * Cycles through all stores in $useStore.
456
     * First match will return the found value.
Carsten  Rose's avatar
Carsten Rose committed
457
     * During cycling: fill cache with requestet value and sanitize raw value.
458
     *
459
     * @param string $key
460
     * @param string $useStores f.e.: 'FSRD'
Carsten  Rose's avatar
Carsten Rose committed
461
     * @param string $sanitizeClass
Carsten  Rose's avatar
Carsten Rose committed
462
     * @param string $foundInStore Returns the name of the store where $key has been found. If $key is not found, return ''.
463
     * @return string a) if found: value, b) false
Carsten  Rose's avatar
Carsten Rose committed
464
     * @throws \qfq\CodeException
465
     */
Carsten  Rose's avatar
Carsten Rose committed
466
    public static function getVar($key, $useStores = STORE_USE_DEFAULT, $sanitizeClass = '', &$foundInStore = '') {
467
468

        // no store specifed?
469
        if ($useStores === "" || $useStores === null) {
470
            $useStores = STORE_USE_DEFAULT;
471
472
        }

473
        // no sanitizeClass specified: take predefined (if exist) or default.
474
        if ($sanitizeClass === '' || $sanitizeClass === null) {
Carsten  Rose's avatar
Carsten Rose committed
475
            $sanitizeClass = isset(self::$sanitizeClass[$key]) ? self::$sanitizeClass[$key] : SANITIZE_DEFAULT;
476
477
        }

478
479
480
        while ($useStores !== false) {

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

484
            if (!isset(self::$raw[$store][$key])) {
485
486
487
                switch ($store) {
                    case STORE_ZERO:
                        return 0;
488
489
                    case STORE_EMPTY:
                        return '';
490
491
                    case STORE_VAR:
                        if ($key === VAR_RANDOM) {
492
                            return Support::randomAlphaNum(RANDOM_LENGTH);
493
494
495
496
497
498
499
                        } 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
500
                }
501
502
            }

503
            $rawVal = isset(self::$raw[$store][$key]) ? self::$raw[$store][$key] : null;
Carsten  Rose's avatar
Carsten Rose committed
504
            if (self::$sanitizeStore[$store] && $sanitizeClass != '') {
505
506
507
508
509
                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);
510
511
            } else {
                return $rawVal;
512
            }
513
        }
Carsten  Rose's avatar
Carsten Rose committed
514
        $foundInStore = '';
515
        return false;
516
    }
517

Carsten  Rose's avatar
Carsten Rose committed
518
    /**
Carsten  Rose's avatar
Carsten Rose committed
519
520
     * Fills the STORE_EXTRA.
     *
Carsten  Rose's avatar
Carsten Rose committed
521
522
523
524
     * @throws UserFormException
     * @throws \qfq\CodeException
     */
    private static function fillStoreExtra() {
525

526
        $value = Session::get(STORE_EXTRA);
527

528
        if (!isset($_SESSION[SESSION_NAME][STORE_EXTRA]) || $_SESSION[SESSION_NAME][STORE_EXTRA] === null) {
529
530
531
532
            $value = false;
        }

        if ($value === false) {
Carsten  Rose's avatar
Carsten Rose committed
533
            self::setVarArray(array(), STORE_EXTRA, true);
534
        } else {
535
            self::setVarArray($_SESSION[SESSION_NAME][STORE_EXTRA], STORE_EXTRA, true);
536
        }
Carsten  Rose's avatar
Carsten Rose committed
537
538
    }

539
    /**
Carsten  Rose's avatar
Carsten Rose committed
540
541
     * Returns a pointer to this Class.
     *
542
     * @param string $bodytext
543
     * @param bool|false $phpUnit
544
545
546
547
     * @param string $fileConfigIni
     * @return null|Store
     * @throws UserFormException
     * @throws \qfq\CodeException
548
     */
549
    public static function getInstance($bodytext = '', $phpUnit = false, $fileConfigIni = '') {
550

551
        if ($phpUnit) {
552

553
            if (self::$instance !== null) {
554
                // fake to have a clean environment for the next test.
555
556
557
558
559
                self::unsetStore(STORE_TYPO3);
                self::fillStoreTypo3($bodytext);

                self::unsetStore(STORE_CLIENT);
                self::fillStoreClient();
560
            }
561
562
563
564
565

            // Testing different config files means initialize completely
            if ($fileConfigIni != '') {
                self::$instance = null;
            }
566
567
568
569
        }

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

572
            self::$instance = new self($bodytext, $fileConfigIni);
Carsten  Rose's avatar
Carsten Rose committed
573
574
575
576
        } 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);
577
578
        }

579
580
581
582
583
584
585
        // 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);
        }

586
        return self::$instance;
587
    }
588

589
    /**
Carsten  Rose's avatar
Carsten Rose committed
590
591
     * Deletes a store assigning a new empty array to it.
     *
592
     * @param $store
593
594
     * @throws UserFormException
     * @throws \qfq\CodeException
595
596
     */
    public static function unsetStore($store) {
Carsten  Rose's avatar
Carsten Rose committed
597
        // Check valid Storename
Carsten  Rose's avatar
Carsten Rose committed
598
        if (!isset(self::$sanitizeStore))
599
            throw new UserFormException("Unknown Store: $store", ERROR_UNNOWN_STORE);
Carsten  Rose's avatar
Carsten Rose committed
600

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

604
605
606
        if (isset(self::$raw[$store])) {
            self::$raw[$store] = array();
        }
Carsten  Rose's avatar
Carsten Rose committed
607

608
609
    }

610
    /**
Carsten  Rose's avatar
Carsten Rose committed
611
612
     * Set's a single $key/$value pair $store.
     *
Carsten  Rose's avatar
Carsten Rose committed
613
614
615
     * @param string $key
     * @param string|array $value
     * @param string $store
616
617
618
619
620
621
622
623
624
625
626
627
628
     * @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])) {
629
            throw new UserFormException("Value of '$key' already set in store '$store'.", ERROR_STORE_KEY_EXIST);
630
631
632
        }

        self::$raw[$store][$key] = $value;
Carsten  Rose's avatar
Carsten Rose committed
633
634
635

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

637
            $store = Session::get(STORE_EXTRA);
638
639

            if ($store === false) {
640
                $store = array();
641
642
            }

643
644
            $store[$key] = $value;
            Session::set(STORE_EXTRA, $store);
645

Carsten  Rose's avatar
Carsten Rose committed
646
        }
647
648
    }

649
    /**
Carsten  Rose's avatar
Carsten Rose committed
650
651
     * Create a SIP after a form load. This is necessary on forms without a sip and on forms with r=0 (new record).
     *
652
653
654
     * @param $formName
     * @throws CodeException
     */
655
    public static function createSipAfterFormLoad($formName) {
656

657
        $recordId = self::getVar(CLIENT_RECORD_ID, STORE_TYPO3 . STORE_CLIENT);
658
659
660
661
        if ($recordId === false) {
            $recordId = 0;
        }

662
663
664
665
666
667
        // 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
668
669
670
671
        if ($recordId == 0) {
            // SIPs for 'new records' needs to be uniq per TAB! Therefore add a uniq parameter
            $tmpParam[SIP_MAKE_URLPARAM_UNIQ] = uniqid();
        }
672
673

        // Construct fake urlparam
674
        $tmpUrlparam = OnArray::toString($tmpParam);
675
676

        // Create a fake SIP which has never been passed by URL - further processing might expect this to exist.
677
        $sip = self::getSipInstance()->queryStringToSip($tmpUrlparam, RETURN_SIP);
678
        self::setVar(CLIENT_SIP, $sip, STORE_CLIENT);
679
680
681

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

684
685
686
    }

    /**
687
     * Return an array with non system SIP parameter. Take the whole STORE_SIP and search for non system parameter.
688
     *
689
690
691
     * @return array
     * @throws UserFormException
     * @throws \qfq\CodeException
692
     */
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
    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;
714
    }
715

716
    /**
Carsten  Rose's avatar
Carsten Rose committed
717
718
     * Returns a complete $store.
     *
719
     * @param $store
720
721
722
     * @return array
     * @throws UserFormException
     * @throws \qfq\CodeException
723
724
     */
    public static function getStore($store) {
Carsten  Rose's avatar
Carsten Rose committed
725
        // Check valid Storename
Carsten  Rose's avatar
Carsten Rose committed
726
        if (!isset(self::$sanitizeStore[$store]))
727
            throw new UserFormException("Unknown Store: $store", ERROR_UNNOWN_STORE);
Carsten  Rose's avatar
Carsten Rose committed
728

729
        if ($store === STORE_ZERO)
Carsten  Rose's avatar
Carsten Rose committed
730
731
            throw new CodeException("getStore() for STORE_ZERO is impossible - there are no values saved.", ERROR_GET_STORE_ZERO);

732
733
734
        if (isset(self::$raw[$store])) {
            return self::$raw[$store];
        }
735

736
737
        return array();
    }
738

739
740
741
742
743
744
745
746
    /**
     * Returns a pointer to this class.
     *
     * @return null|Sip
     */
    public static function getSipInstance() {
        return self::$sip;
    }
Carsten  Rose's avatar
Carsten Rose committed
747

748
    /**
Carsten  Rose's avatar
Carsten Rose committed
749
750
     * Fills STORE_TABLE_DEFAULT and STORE_TABLE_COLUMN_TYPES
     *
751
752
753
     * @param $tableName
     * @throws CodeException
     */
754
    public static function fillStoreTableDefaultColumnType($tableName) {
755
756
757
758
        $db = new qfq\Database();

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

Carsten  Rose's avatar
Carsten Rose committed
759
760
        self::setVarArray(array_column($tableDefinition, 'Default', 'Field'), STORE_TABLE_DEFAULT, true);
        self::setVarArray(array_column($tableDefinition, 'Type', 'Field'), STORE_TABLE_COLUMN_TYPES, true);
761
    }
762
763
764
765
766
}