Store.php 33 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/helper/Logger.php');
16
require_once(__DIR__ . '/../../qfq/Constants.php');
17
require_once(__DIR__ . '/../../qfq/store/Sip.php');
18
require_once(__DIR__ . '/../../qfq/store/T3Info.php');
19
require_once(__DIR__ . '/../../qfq/database/Database.php');
20
require_once(__DIR__ . '/../../qfq/store/Config.php');
Carsten  Rose's avatar
Carsten Rose committed
21
require_once(__DIR__ . '/../../qfq/store/Client.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
     * @param string $fileConfigIni
Carsten  Rose's avatar
Carsten Rose committed
85
     *
86
     * @throws CodeException
87
     * @throws UserFormException
88
     * @throws UserReportException
89
     */
90
    private function __construct($bodytext = '', $fileConfigIni = '') {
91

92
//        self::$session = Session::getInstance(self::$phpUnit);
93

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

Carsten  Rose's avatar
Carsten Rose committed
99
100
101
102
103
104
105
106
        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
107
108
109
110
            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
111
            CLIENT_KEY_SEM_ID_USER => SANITIZE_ALLOW_DIGIT,
Carsten  Rose's avatar
Carsten Rose committed
112
113
114
115
            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
116
117

            // Part of $_SERVER. Missing vars must be requested individual with the needed sanitize class.
Carsten  Rose's avatar
Carsten Rose committed
118
119
120
            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
121
            CLIENT_HTTP_USER_AGENT => SANITIZE_ALLOW_ALNUMX,
Carsten  Rose's avatar
Carsten Rose committed
122
            CLIENT_SERVER_NAME => SANITIZE_ALLOW_ALNUMX,
Carsten  Rose's avatar
Carsten Rose committed
123
            CLIENT_SERVER_ADDRESS => SANITIZE_ALLOW_ALNUMX,
Carsten  Rose's avatar
Carsten Rose committed
124
            CLIENT_SERVER_PORT => SANITIZE_ALLOW_DIGIT,
Carsten  Rose's avatar
Carsten Rose committed
125
126
127
            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
128
129
130
131
            CLIENT_QUERY_STRING => SANITIZE_ALLOW_ALL,
            CLIENT_REQUEST_URI => SANITIZE_ALLOW_ALL,
            CLIENT_SCRIPT_NAME => SANITIZE_ALLOW_ALNUMX,
            CLIENT_PHP_SELF => SANITIZE_ALLOW_ALNUMX,
132
//            CLIENT_UPLOAD_FILENAME => SANITIZE_ALLOW_ALLBUT,
Carsten  Rose's avatar
Carsten Rose committed
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150

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

152
153
        ];

Carsten  Rose's avatar
Carsten Rose committed
154
        self::$sanitizeStore = [
Carsten  Rose's avatar
Carsten Rose committed
155
156
157
158
159
160
161
162
163
164
165
166
167
168
            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,
169
            STORE_USER => false,
Carsten  Rose's avatar
Carsten Rose committed
170
            STORE_LDAP => false,
Carsten  Rose's avatar
Carsten Rose committed
171
            STORE_ADDITIONAL_FORM_ELEMENTS => false,
172
173
        ];

174
175
        self::fillStoreTypo3($bodytext);  // should be filled before fillStoreSystem() to offer T3 variables
        self::fillStoreClient();  // should be filled before fillStoreSystem() to offer Client variables
176
177
        self::fillStorePhpSession(STORE_EXTRA); // should be filled before fillStoreSystem() to restore variables like SYSTEM_SHOW_DEBUG_INFO
        self::fillStorePhpSession(STORE_USER); // should be filled before fillStoreSystem() to restore variables like SYSTEM_SHOW_DEBUG_INFO
178
        self::fillStoreSystem($fileConfigIni);
179
        self::fillStoreSip();
180
    }
181

182
    /**
183
     * Returns a pointer to this Class.
Carsten  Rose's avatar
Carsten Rose committed
184
     *
185
186
     * @param string $bodytext
     * @param bool|false $phpUnit
187
     * @param string $fileConfigIni
188
189
     *
     * @return null|Store
190
     * @throws CodeException
191
     * @throws UserFormException
192
     * @throws UserReportException
193
     */
194
    public static function getInstance($bodytext = '', $phpUnit = false, $fileConfigIni = '') {
195

196
        if ($phpUnit) {
197

198
199
200
201
            if (self::$instance !== null) {
                // fake to have a clean environment for the next test.
                self::unsetStore(STORE_TYPO3);
                self::fillStoreTypo3($bodytext);
202

203
204
205
                self::unsetStore(STORE_CLIENT);
                self::fillStoreClient();
            }
206

207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
            // Testing different config files means initialize completely
            if ($fileConfigIni != '') {
                self::$instance = null;
            }
        }

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

            self::$instance = new self($bodytext, $fileConfigIni);
        } else {
            // Class Store seems to be persistent over multiple QFQ instantiation. Set bodytext again, with every new request (if bodytext is given).
            if ($bodytext !== '') {
                self::fillStoreTypo3($bodytext);
                self::unsetStore(STORE_RECORD); // At least in Multi DB Setup: STORE_RECORD of former  tt-content QFQ Records interfere with loading a form.
            }
        }

        // Disable TYPO3_DEBUG_SHOW_BODY_TEXT=1 if SYSTEM_SHOW_DEBUG_INFO!='yes'
        if (self::getVar(TYPO3_DEBUG_SHOW_BODY_TEXT, STORE_TYPO3) === '1' &&
            !Support::findInSet(SYSTEM_SHOW_DEBUG_INFO_YES, self::getVar(SYSTEM_SHOW_DEBUG_INFO, STORE_SYSTEM))
        ) {
            self::setVar(TYPO3_DEBUG_SHOW_BODY_TEXT, '0', STORE_TYPO3);
        }

        return self::$instance;
234
235
236
    }

    /**
237
     * QFQ might be called via Typo3 (index.php) or directly via AJAX (directory: api). The
Carsten  Rose's avatar
Carsten Rose committed
238
     *
239
     * @param array $config
Carsten  Rose's avatar
Carsten Rose committed
240
     *
241
242
     * @return array
     */
Carsten  Rose's avatar
Carsten Rose committed
243
    private static function doSystemPath(array $config) {
244

245
246
247
248
249
250
251
        // 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);
252
253
                if ($pos === false && isset($GLOBALS['TYPO3_LOADED_EXT'][EXT_KEY]['ext_localconf.php'])) {

254
                    // Typo3 extension: probably index.php
255
                    $config[SYSTEM_PATH_EXT] = dirname($GLOBALS['TYPO3_LOADED_EXT'][EXT_KEY]['ext_localconf.php']);
256
                    $config[SYSTEM_SITE_PATH] = dirname($_SERVER['SCRIPT_FILENAME']);
257
                } else {
258
                    // API
259
                    $config[SYSTEM_PATH_EXT] = substr($_SERVER['SCRIPT_FILENAME'], 0, $pos + strlen($relExtDir));
260
                    $config[SYSTEM_SITE_PATH] = substr($_SERVER['SCRIPT_FILENAME'], 0, $pos);
261
                }
262
263
264
265
            } else {
                // No $_SERVER >>this means phpUnit.
                $config[SYSTEM_SITE_PATH] = getcwd();
                $config[SYSTEM_PATH_EXT] = getcwd();
266
267
            }
        }
268
269
270
271
272
273
274
275

        return $config;
    }

    /**
     * Depending on some configuration value, update corresponding values.
     *
     * @param array $config
Carsten  Rose's avatar
Carsten Rose committed
276
     *
277
278
279
     * @return array
     */
    private static function adjustConfig(array $config) {
280
281

        $config[SYSTEM_SHOW_DEBUG_INFO] = self::adjustConfigShowDebugInfo($config[SYSTEM_SHOW_DEBUG_INFO], self::beUserLoggdIn());
282
283

        // make SQL PATH absolute. This is necessary to work in different directories correctly.
284
        if (!empty($config[SYSTEM_SQL_LOG]) && $config[SYSTEM_SQL_LOG][0] !== '/') {
285
            $config[SYSTEM_SQL_LOG] = $config[SYSTEM_SITE_PATH] . '/' . $config[SYSTEM_SQL_LOG];
286
287
        }

288
289
        // make SQL PATH absolute. This is necessary to work in different directories correctly.
        if (!empty($config[SYSTEM_MAIL_LOG]) && $config[SYSTEM_MAIL_LOG][0] !== '/') {
290
            $config[SYSTEM_MAIL_LOG] = $config[SYSTEM_SITE_PATH] . '/' . $config[SYSTEM_MAIL_LOG];
291
292
293
294
        }

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

295
296
297
298
299
300
301
302
        // 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];
        }

303
        return $config;
304
305
    }

306
    /**
307
     * @param string $value
Carsten  Rose's avatar
Carsten Rose committed
308
     *
309
     * @param $flag
310
     * @return string
311
     */
312
    private static function adjustConfigShowDebugInfo($value, $flag) {
313

314
        // Check if SHOW_DEBUG_INFO contains 'auto'. Replace with appropriate.
315
316
        if (Support::findInSet(SYSTEM_SHOW_DEBUG_INFO_AUTO, $value) && $flag) {
            $value = str_replace(SYSTEM_SHOW_DEBUG_INFO_AUTO, SYSTEM_SHOW_DEBUG_INFO_YES, $value);
317
318
        }

319
320
321
322
323
324
325
326
327
328
329
        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);
330
331
    }

332
333
334
335
    /**
     * 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
336
     *
337
338
339
     * @throws UserFormException
     */
    private static function checkMandatoryParameter(array $config) {
340

341
        // Check mandatory config vars.
342
        $names = array_merge([SYSTEM_SQL_LOG, SYSTEM_SQL_LOG_MODE],
343
344
345
            self::dbCredentialName($config[SYSTEM_DB_INDEX_DATA]),
            self::dbCredentialName($config[SYSTEM_DB_INDEX_QFQ]));

346
347
        foreach ($names as $name) {
            if (!isset($config[$name])) {
348
                throw new qfq\UserFormException ("Missing configuration in `" . CONFIG_QFQ_PHP . "`: $name", ERROR_MISSING_CONFIG_INI_VALUE);
349
350
351
352
            }
        }
    }

353
    /**
354
355
     * @param $index
     * @return array
356
357
358
359
360
361
362
363
364
365
366
     */
    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;
    }

367
    /**
Carsten  Rose's avatar
Carsten Rose committed
368
369
     * Set or overwrite a complete store.
     *
Carsten  Rose's avatar
Carsten Rose committed
370
     * @param array $dataArray
Carsten  Rose's avatar
Carsten Rose committed
371
     * @param            $store
372
     * @param bool|false $flagOverwrite
Carsten  Rose's avatar
Carsten Rose committed
373
     *
374
     * @throws UserFormException
375
     * @throws \qfq\CodeException
376
     */
377
    public static function setStore(array $dataArray, $store, $flagOverwrite = false) {
378

Carsten  Rose's avatar
Carsten Rose committed
379
        // Check valid Storename
380
        if (!isset(self::$sanitizeStore)) {
381
            throw new UserFormException("Unknown Store: $store", ERROR_UNNOWN_STORE);
382
        }
Carsten  Rose's avatar
Carsten Rose committed
383

384
385
386
        if ($store === STORE_ZERO || $store === STORE_EMPTY) {
            throw new CodeException("setVarArray() for STORE_ZERO/STORE_EMPTY is impossible - there are no values.", ERROR_SET_STORE_ZERO);
        }
387

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

392
393
394
395
        if ($store === STORE_EXTRA || $store === STORE_USER) {
            Session::set($store, $dataArray);
        }

396
397
        self::$raw[$store] = $dataArray;
    }
398

399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
    /**
     * Deletes a store assigning a new empty array to it.
     *
     * @param $store
     *
     * @throws UserFormException
     * @throws \qfq\CodeException
     */
    public static function unsetStore($store) {

        self::setStore(array(), $store, true);
    }

    /**
     * Returns a complete $store.
     *
     * @param $store
     *
     * @return array
     * @throws UserFormException
     * @throws \qfq\CodeException
     */
    public static function getStore($store) {
        $vars = array();

        // Check valid Storename
        if (!isset(self::$sanitizeStore[$store])) {
            throw new UserFormException("Unknown Store: $store", ERROR_UNNOWN_STORE);
        }

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

        if (isset(self::$raw[$store])) {
            $vars = self::$raw[$store];
            if ($store == STORE_SIP) {
                $vars = self::checkDecodeBase64Arr($vars);
            }
        }

        return $vars;
    }


    /**
     * Fill the system store by reading config.qfq.ini. Also setup config defaults.
     *
     * @param string $fileConfigIni
     * @throws CodeException
     * @throws UserFormException
     * @throws UserReportException
     */
    private static function fillStoreSystem($fileConfigIni = '') {

        $config = Config::readConfig($fileConfigIni);

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

        self::checkMandatoryParameter($config);

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

464
    /**
465
     * Copy the BodyText as well as some T3 specific vars to STORE_TYPO3.
Carsten  Rose's avatar
Carsten Rose committed
466
467
     * Attention: if called through API, there is no T3 environment. The only values which are available are fe_user
     * and fe_user_uid.
468
     *
469
     * @param $bodytext
Carsten  Rose's avatar
Carsten Rose committed
470
     *
471
     * @throws CodeException
472
     * @throws UserFormException
473
     */
474
    private static function fillStoreTypo3($bodytext) {
475

476
        // form=, showDebugBodyText=, 10.20..
477
        $arr = KeyValueStringParser::parse($bodytext, "=", "\n");
478

479
        if (isset($GLOBALS["TSFE"])) {
480
            $arr = array_merge($arr, T3Info::getVars());
481
        } else {
482

483
            // No T3 environment (called by API): restore from SESSION
484
            foreach ([SESSION_FE_USER, SESSION_FE_USER_UID, SESSION_FE_USER_GROUP, SESSION_BE_USER] as $key) {
485
486
487
                if (isset($_SESSION[SESSION_NAME][$key])) {
                    $arr[$key] = $_SESSION[SESSION_NAME][$key];
                }
488
489
            }
        }
490

491
        self::setStore($arr, STORE_TYPO3, true);
492
    }
493

494
    /**
Carsten  Rose's avatar
Carsten Rose committed
495
496
     * Fills the STORE_CLIENT
     *
497
     * @throws CodeException
498
     * @throws UserFormException
499
     */
500
    private static function fillStoreClient() {
501

Carsten  Rose's avatar
Carsten Rose committed
502
        $arr = Client::getParam();
503

504
        self::setStore($arr, STORE_CLIENT, true);
505

506
    }
507

Carsten  Rose's avatar
Carsten Rose committed
508
    /**
Carsten  Rose's avatar
Carsten Rose committed
509
510
     * Fills the STORE_SIP. Reads therefore specified SIP, decode the values and stores them in STORE_SIP.
     *
Carsten  Rose's avatar
Carsten Rose committed
511
     * @throws CodeException
512
     * @throws UserFormException
513
     * @throws UserReportException
Carsten  Rose's avatar
Carsten Rose committed
514
     */
515
    private static function fillStoreSip() {
Carsten  Rose's avatar
Carsten Rose committed
516

517
        self::$sip = new Sip(self::$phpUnit);
518

519
520
521
522
        $s = self::getVar(CLIENT_SIP, STORE_CLIENT);
        if ($s !== false) {
            // if session is given, copy values to store
            $param = self::$sip->getVarsFromSip($s);
523
524
            $param[SIP_SIP] = $s;
            $param[SIP_URLPARAM] = self::$sip->getQueryStringFromSip($s);
525

526
//            self::setVarArray(KeyValueStringParser::parse($param, "=", "&"), STORE_SIP);
527
            self::setStore($param, STORE_SIP, true);
528
529
530
        }
    }

531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
    /**
     * Fills the STORE_EXTRA.
     *
     * @param string $storeName STORE_EXTRA, STORE_USER
     * @throws CodeException
     * @throws UserFormException
     */
    private static function fillStorePhpSession($storeName) {

        $value = Session::get($storeName);

        if (!isset($_SESSION[SESSION_NAME][$storeName]) || $_SESSION[SESSION_NAME][$storeName] === null) {
            $value = false;
        }

        if ($value === false) {
            self::setStore(array(), $storeName, true);
        } else {
            self::setStore($_SESSION[SESSION_NAME][$storeName], $storeName, true);
        }
    }

    /**
     * Fills STORE_TABLE_DEFAULT and STORE_TABLE_COLUMN_TYPES
     *
     * @param array $tableDefinition
     *
     * @throws CodeException
     * @throws UserFormException
     */
    public static function fillStoreTableDefaultColumnType(array $tableDefinition) {

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


    /**
     * Set's a single $key/$value pair $store.
     *
     * @param string $key
     * @param string|array $value
     * @param string $storeName
     * @param bool|true $overWrite
     *
     * @throws UserFormException
     * @throws \qfq\CodeException
     */
    public static function setVar($key, $value, $storeName, $overWrite = true) {
        // Check valid Storename
        if (!isset(self::$sanitizeStore)) {
            throw new UserFormException("Unknown Store: $storeName", ERROR_UNNOWN_STORE);
        }

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

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

        self::$raw[$storeName][$key] = $value;

        // The STORE_EXTRA / STORE_USER saves arrays and is persistent
        if ($storeName === STORE_EXTRA || $storeName === STORE_USER) {

            $data = Session::get($storeName);

            if ($data === false) {
                $data = array();
            }

            // Switch FeUser: log this to qfq.log
            if ($storeName === STORE_USER && $key == TYPO3_FE_USER) {
                $qfqLog = self::getVar(SYSTEM_QFQ_LOG, STORE_SYSTEM);
                $feUserOld = isset($data[$key]) ? $data[$key] : self::getVar($key, STORE_TYPO3 . STORE_EMPTY);
                Logger::logMessage(date('Y.m.d H:i:s ') . ": Switch feUser '$feUserOld' to '$value'", $qfqLog);
            }

            $data[$key] = $value;
            Session::set($storeName, $data);
        }
    }

617
618
619
620
621
622
    /**
     * Unset a variable in store.
     *
     * @param $key
     * @param $store
     */
623
    public static function unsetVar($key, $store) {
624

625
        if (isset(self::$raw[$store][$key])) {
626
627
628
629
            unset(self::$raw[$store][$key]);
        }
    }

630
    /**
631
     * Cycles through all stores in $useStore.
632
     * First match will return the found value.
633
     * During cycling: fill cache with requested value and sanitize raw value.
634
     *
635
     * @param string $key
636
     * @param string $useStores f.e.: 'FSRVD'
Carsten  Rose's avatar
Carsten Rose committed
637
     * @param string $sanitizeClass
Carsten  Rose's avatar
Carsten Rose committed
638
639
640
     * @param string $foundInStore Returns the name of the store where $key has been found. If $key is not found,
     *                             return ''.
     *
641
     * @return string|array a) if found: value, b) false. STORE_EXTRA returns an array for the given key.
642
643
     * @throws CodeException
     * @throws UserFormException
644
     */
Carsten  Rose's avatar
Carsten Rose committed
645
    public static function getVar($key, $useStores = STORE_USE_DEFAULT, $sanitizeClass = '', &$foundInStore = '') {
646

647
        // no store specified?
648
        if ($useStores === "" || $useStores === null) {
649
            $useStores = STORE_USE_DEFAULT;
650
651
        }

652
        // no sanitizeClass specified: take predefined (if exist) or default.
653
        if ($sanitizeClass === '' || $sanitizeClass === null) {
Carsten  Rose's avatar
Carsten Rose committed
654
            $sanitizeClass = isset(self::$sanitizeClass[$key]) ? self::$sanitizeClass[$key] : SANITIZE_DEFAULT;
655
656
        }

Carsten  Rose's avatar
Carsten Rose committed
657
658
        $len = strlen(SIP_PREFIX_BASE64);

659
660
        while ($useStores !== false) {
            $store = substr($useStores, 0, 1); // next store
661
662
663
664
665
666

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

Carsten  Rose's avatar
Carsten Rose committed
667
            $foundInStore = $store;
668
669
            $useStores = substr($useStores, 1); // shift left remaining stores

670
            if (!isset(self::$raw[$store][$finalKey])) {
671
672
673
                switch ($store) {
                    case STORE_ZERO:
                        return 0;
674
675
                    case STORE_EMPTY:
                        return '';
676
                    case STORE_VAR:
677
                        if ($finalKey === VAR_RANDOM) {
678
                            return Support::randomAlphaNum(RANDOM_LENGTH);
679
680
681
682
683
684
685
                        } 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
686
                }
687
688
            }

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

696
                return Sanitize::sanitize($rawVal, $sanitizeClass, '', '', SANITIZE_EMPTY_STRING);
697
            } else {
Carsten  Rose's avatar
Carsten Rose committed
698
699
700
                if ($store == STORE_SIP && (substr($key, 0, $len) == SIP_PREFIX_BASE64)) {
                    $rawVal = base64_decode($rawVal);
                }
Carsten  Rose's avatar
Carsten Rose committed
701

702
                return $rawVal;
703
            }
704
        }
Carsten  Rose's avatar
Carsten Rose committed
705
        $foundInStore = '';
Carsten  Rose's avatar
Carsten Rose committed
706

707
        return false;
708
    }
709

710
    /**
Carsten  Rose's avatar
Carsten Rose committed
711
712
     * Create a SIP after a form load. This is necessary on forms without a sip and on forms with r=0 (new record).
     *
713
     * @param $formName
Carsten  Rose's avatar
Carsten Rose committed
714
     *
715
     * @throws CodeException
716
     * @throws UserFormException
717
     */
718
    public static function createSipAfterFormLoad($formName) {
719

720
        $recordId = self::getVar(CLIENT_RECORD_ID, STORE_TYPO3 . STORE_CLIENT);
721
722
723
724
        if ($recordId === false) {
            $recordId = 0;
        }

725
726
727
728
729
730
        // 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
731
732
733
734
        if ($recordId == 0) {
            // SIPs for 'new records' needs to be uniq per TAB! Therefore add a uniq parameter
            $tmpParam[SIP_MAKE_URLPARAM_UNIQ] = uniqid();
        }
735
736

        // Construct fake urlparam
737
        $tmpUrlparam = OnArray::toString($tmpParam);
738
739

        // Create a fake SIP which has never been passed by URL - further processing might expect this to exist.
740
        $sip = self::getSipInstance()->queryStringToSip($tmpUrlparam, RETURN_SIP);
741
        self::setVar(CLIENT_SIP, $sip, STORE_CLIENT);
742
743
744

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

747
748
749
    }

    /**
750
     * Return an array with non system SIP parameter. Take the whole STORE_SIP and search for non system parameter.
751
     *
752
753
754
     * @return array
     * @throws UserFormException
     * @throws \qfq\CodeException
755
     */
756
    public static function getNonSystemSipParam() {
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
        $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;
777
    }
778

Carsten  Rose's avatar
Carsten Rose committed
779
780
781
782
783
    /**
     * 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
784
     *
Carsten  Rose's avatar
Carsten Rose committed
785
786
787
788
789
790
791
792
793
794
795
     * @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
796

Carsten  Rose's avatar
Carsten Rose committed
797
        return $vars;
798
799
    }

800
801
802
803
804
805
806
807
808
809
    /**
     * Returns a pointer to this class.
     *
     * @return null|Sip
     */
    public static function getSipInstance() {
        return self::$sip;
    }

    /**
810
811
812
813
     * 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'
     *
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
     * @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;
    }

    /**
836
837
838
     * Get stored STORE_TYPO3 vars from SIP and restore the store.
     *
     * More docs: CODING.md > 'Faking the STORE_TYPO3 for API calls'
839
840
     *
     * @param string $sipTypo3Vars
841
842
     * @throws CodeException
     * @throws UserFormException
843
     * @throws UserReportException
844
     */
Carsten  Rose's avatar
Carsten Rose committed
845
    public static function fillTypo3StoreFromSip($sipTypo3Vars) {
846
847
848
849
850
851
852
853
854
855
        $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
856

857
        $value = self::getVar(SYSTEM_SHOW_DEBUG_INFO, STORE_SYSTEM);
858
859
        $flag = isset($typo3VarsArray[TYPO3_BE_USER_LOGGED_IN]) && ($typo3VarsArray[TYPO3_BE_USER_LOGGED_IN] == 'yes');
        $value = self::adjustConfigShowDebugInfo($value, $flag);
860
        self::setVar(SYSTEM_SHOW_DEBUG_INFO, $value, STORE_SYSTEM);
861
    }
862

863
864
865
    /**
     * Load $recordId from $tableName using $db and saves it in $store.
     *
866
867
868
869
     * @param string $tableName
     * @param int $recordId
     * @param Database $db
     * @param string $primaryKey
870
871
     * @param string $store
     * @throws CodeException
872
     * @throws DbException
873
874
     * @throws UserFormException
     */
875
    public static function fillStoreWithRecord($tableName, $recordId, Database $db,
876
                                               $primaryKey = F_PRIMARY_KEY_DEFAULT, $store = STORE_RECORD) {
877
878
        if ($recordId !== false && $recordId > 0 &&
            is_string($tableName) && $tableName !== '') {
879
880
881
            if (!$primaryKey) {
                $primaryKey = F_PRIMARY_KEY_DEFAULT;
            }
882

883
            $record = $db->sql("SELECT * FROM $tableName WHERE $primaryKey = ?", ROW_EXPECT_1, [$recordId]);
884
885
886
887
            self::setStore($record, $store, true);
        }
    }

888
    /**
889
     * Read SYSTEM_FILL_STORE_SYSTEM_BY_SQL_1|2|3 from SYSTEM_STORE and if set:
890
     * a) fire the SQL
891
     * b) merge all columns to STORE_SYSTEM
892
893
894
895
     *
     * @throws CodeException
     * @throws DbException
     * @throws UserFormException
896
     * @throws UserReportException
897
     */
898
    public static function StoreSystemUpdate() {
899

900
        $db = null;
901
        $flagDirty = false;
902
903
        $storeSystem = self::getStore(STORE_SYSTEM);

904
        for ($ii = 1; $ii <= 3; $ii++) {
905
            if (empty($storeSystem[SYSTEM_FILL_STORE_SYSTEM_BY_SQL . "$ii"])) {
906
907
908
909
910
911
912
                continue;
            }

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

913
            $errMsg = "More than 1 record found. " . CONFIG_QFQ_PHP . ": " . SYSTEM_FILL_STORE_SYSTEM_BY_SQL . "$ii";
914
915
916
            $mode = ROW_EXPECT_0_1;

            // If there is an error message defined, this means there should be exactly one record.
917
            if (!empty($storeSystem[SYSTEM_FILL_STORE_SYSTEM_ERROR_MSG . "$ii"])) {
918
                $mode = ROW_EXPECT_1;
919
                $errMsg = $storeSystem[SYSTEM_FILL_STORE_SYSTEM_ERROR_MSG . "$ii"];
920
            }
921

922
            $storeSystemAdd = $db->sql($storeSystem[SYSTEM_FILL_STORE_SYSTEM_BY_SQL . "$ii"], $mode, array(), $errMsg);
923
924
            $storeSystemAdd = OnArray::keyNameRemoveLeadingUnderscore($storeSystemAdd);
            $storeSystem = array_merge($storeSystem, $storeSystemAdd);
925
            $flagDirty = true;
926
927
        }

928
        if ($flagDirty) {
929
            self::setStore($storeSystem, STORE_SYSTEM, true);
930
931
        }
    }
932
933

    /**
934
     * Append an array (in case of 'array of array': the first row of array) to store $storeName.
935
936
     * Existing values will be overwritten.
     *
937
     * @param array $dataArray - in special cases it might be an empty string -therefore no type definition to 'array'.
938
939
940
     * @param $storeName
     * @throws CodeException
     * @throws UserFormException
941
     * @throws UserReportException
942
     */
943
    public static function appendToStore($dataArray, $storeName) {
944

945
        if (empty($dataArray) || !is_array($dataArray)) {
946
947
            return;
        }
948

949
950
        if (isset($dataArray[0]) && is_array($dataArray[0])) {
            $append = $dataArray[0];
951
        } else {
952
            $append = $dataArray;
953
954
955
956
957
958
959
960
961
962
        }

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

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

963
964
965
966
967
968
969
970
971
972
973
974
    /**
     * Gets the db T3 name:
     * - if defined by T3_DB_NAME in config
     * - else: construct databasename_t3 from databasename_db
     *
     * @return string
     * @throws CodeException
     * @throws DbException
     * @throws UserFormException
     * @throws UserReportException
     */
    public static function getDbT3Name() {
975

976
        $dbT3Name = self::getVar(SYSTEM_T3_DB_NAME, STORE_SYSTEM . STORE_EMPTY, SANITIZE_ALLOW_ALNUMX);
977

978
979
980
981
982
983
984
        if (!$dbT3Name) {
            $dbIndexData = self::getVar(SYSTEM_DB_INDEX_DATA, STORE_SYSTEM);
            $dbData = new Database($dbIndexData);
            $dbDataName = $dbData->getDbName();

            $dbT3Name = substr($dbDataName, 0, strrpos($dbDataName, "_") + 1) . 't3';
        }
985

986
987
988
        return $dbT3Name;
    }

989
}