Store.php 27.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
            STORE_EXTRA => false
167
168
        ];

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

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

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

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

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

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


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

216
        self::checkMandatoryParameter($config);
217

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

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

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

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

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

257
258
259
260
        return $config;
    }

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

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

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

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

311
        return $config;
312
313
    }

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

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

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

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

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

352
353
        self::$raw[$store] = $dataArray;
    }
354

355
    /**
356
357
358
     * 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.
     *
359
360
361
     * @param $bodytext
     * @throws CodeException
     */
362
    private static function fillStoreTypo3($bodytext) {
363

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

367
        if (isset($GLOBALS["TSFE"])) {
368

369
370
371
            if (isset($GLOBALS["TSFE"]->fe_user->user["username"])) {
                $arr[TYPO3_FE_USER] = $GLOBALS["TSFE"]->fe_user->user["username"];
            }
372

373
374
375
376
            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"];
            }
377

378
379
380
            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
381

382
383
384
            if (isset($GLOBALS["TSFE"]->page["uid"])) {
                $arr[TYPO3_TT_CONTENT_UID] = $GLOBALS["TSFE"]->page["uid"];
            }
385

386
387
388
389
390
391
392
            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
393

394
395
396
            if (isset($GLOBALS["TSFE"]->sys_language_uid)) {
                $arr[TYPO3_PAGE_LANGUAGE] = $GLOBALS["TSFE"]->sys_language_uid;
            }
397

398
        } else {
399

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

408
        self::setStore($arr, STORE_TYPO3, true);
409
    }
410

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

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

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

429
        $arr = \qfq\Sanitize::normalize($arr);
430

431
        self::setStore($arr, STORE_CLIENT, true);
432

433
    }
434

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

443
        self::$sip = new Sip(self::$phpUnit);
444

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

452
//            self::setVarArray(KeyValueStringParser::parse($param, "=", "&"), STORE_SIP);
453
            self::setStore($param, STORE_SIP, true);
454
455
456
        }
    }

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

        // no store specifed?
472
        if ($useStores === "" || $useStores === null) {
473
            $useStores = STORE_USE_DEFAULT;
474
475
        }

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

481
482
483
        while ($useStores !== false) {

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

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

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

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

529
        $value = Session::get(STORE_EXTRA);
530

531
        if (!isset($_SESSION[SESSION_NAME][STORE_EXTRA]) || $_SESSION[SESSION_NAME][STORE_EXTRA] === null) {
532
533
534
535
            $value = false;
        }

        if ($value === false) {
536
            self::setStore(array(), STORE_EXTRA, true);
537
        } else {
538
            self::setStore($_SESSION[SESSION_NAME][STORE_EXTRA], STORE_EXTRA, true);
539
        }
Carsten  Rose's avatar
Carsten Rose committed
540
541
    }

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

554
        if ($phpUnit) {
555

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

                self::unsetStore(STORE_CLIENT);
                self::fillStoreClient();
563
            }
564
565
566
567
568

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

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

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

582
583
584
585
586
587
588
        // 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);
        }

589
        return self::$instance;
590
    }
591

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

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

607
608
609
        if (isset(self::$raw[$store])) {
            self::$raw[$store] = array();
        }
Carsten  Rose's avatar
Carsten Rose committed
610

611
612
    }

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

        self::$raw[$store][$key] = $value;
Carsten  Rose's avatar
Carsten Rose committed
636
637
638

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

640
            $store = Session::get(STORE_EXTRA);
641
642

            if ($store === false) {
643
                $store = array();
644
645
            }

646
647
            $store[$key] = $value;
            Session::set(STORE_EXTRA, $store);
648

Carsten  Rose's avatar
Carsten Rose committed
649
        }
650
651
    }

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

660
        $recordId = self::getVar(CLIENT_RECORD_ID, STORE_TYPO3 . STORE_CLIENT);
661
662
663
664
        if ($recordId === false) {
            $recordId = 0;
        }

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

        // Construct fake urlparam
677
        $tmpUrlparam = OnArray::toString($tmpParam);
678
679

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

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

687
688
689
    }

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

719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
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
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
    /**
     * 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);
    }

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

    /**
     *
     * @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);
    }

782
    /**
Carsten  Rose's avatar
Carsten Rose committed
783
784
     * Returns a complete $store.
     *
785
     * @param $store
786
787
788
     * @return array
     * @throws UserFormException
     * @throws \qfq\CodeException
789
790
     */
    public static function getStore($store) {
Carsten  Rose's avatar
Carsten Rose committed
791
        // Check valid Storename
Carsten  Rose's avatar
Carsten Rose committed
792
        if (!isset(self::$sanitizeStore[$store]))
793
            throw new UserFormException("Unknown Store: $store", ERROR_UNNOWN_STORE);
Carsten  Rose's avatar
Carsten Rose committed
794

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

798
799
800
        if (isset(self::$raw[$store])) {
            return self::$raw[$store];
        }
801

802
803
        return array();
    }
804

805
806
807
808
809
}