Store.php 16.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/Database.php');
21
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;

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

    /**
Carsten  Rose's avatar
Carsten Rose committed
61
     * @var array Default sanitize classes.
Carsten  Rose's avatar
Carsten Rose committed
62
     */
Carsten  Rose's avatar
Carsten Rose committed
63
    private static $sanitizeClass = array();
Carsten  Rose's avatar
Carsten Rose committed
64
65

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

Carsten  Rose's avatar
Carsten Rose committed
74
    private static $phpUnit = false;
75
    /**
76
     * @param string $bodytext
77
     */
Carsten  Rose's avatar
Carsten Rose committed
78
79
80
    private function __construct($bodytext = '', $phpUnit = false) {

        self::$phpUnit = false;
81

Carsten  Rose's avatar
Carsten Rose committed
82
83
84
85
86
87
88
89
90
91
92
93
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
126
127
128
129
130
131
        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,

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

133
134
        ];

Carsten  Rose's avatar
Carsten Rose committed
135
        self::$sanitizeStore = [
136
137
138
139
            STORE_FORM => true,
            STORE_SIP => false,
            STORE_RECORD => false,
            STORE_PARENT_RECORD => false,
140
141
            STORE_TABLE_DEFAULT => false,
            STORE_TABLE_COLUMN_TYPES => false,
142
143
            STORE_CLIENT => true,
            STORE_TYPO3 => false,
144
            STORE_ZERO => false,
145
146
147
            STORE_SYSTEM => false
        ];

148
        self::fillSystemStore();
149
        self::fillStoreTypo3($bodytext);
150
        self::fillStoreClient();
151
        self::fillStoreSip();
152
    }
153

154
155
    /**
     * @throws CodeException
156
     * @throws qfq\UserFormException
157
158
     */
    private function fillSystemStore() {
159
        try {
160
161
            //TODO: Vernuenftige Fehlermeldung falls nicht auf qfq.ini zugegriffen werden kann.
            //TODO: sinnvollen Platz fuer qfq.ini bestimmen. In der Installationsdoku erwaehnen.
162
            $config = parse_ini_file(__DIR__ . '/../../../' . CONFIG_INI, false);
163

164
165
            //TODO: auskommentiert weil dann die Unittests nicht mehr laufen. Sollte eigentlich wieder aktiviert werden.
//            $config['SQLLOG'] = Support::ifRelativePathPrependExtensionPath($config['SQLLOG']);
166

167
        } catch (\Exception $e) {
168
            throw new qfq\UserFormException ("Error read file " . CONFIG_INI . ": " . $e->getMessage(), ERROR_IO_READ_FILE);
169
        }
170
171
172
173
174

        if(!isset($config['SHOW_DEBUG_INFO']) || $config['SHOW_DEBUG_INFO'] === 'auto') {
            $config['SHOW_DEBUG_INFO'] = (isset($GLOBALS["TSFE"]->beUserLogin) && $GLOBALS["TSFE"]->beUserLogin === true) ? 'yes' : 'no';
        }

175
176
177
178
179
180
181
        // 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);
182
183
                if ($pos === false && isset($GLOBALS['TYPO3_LOADED_EXT'][EXT_KEY]['ext_localconf.php'])) {

184
185
186
187
188
189
190
191
192
193
194
195
196
197
                    // probably: index.php - THERE should be a TYPO3 environment.
                    $config[SYSTEM_PATH_EXT] = dirname($GLOBALS['TYPO3_LOADED_EXT'][EXT_KEY]['ext_localconf.php']);
                } else {
                    $config[SYSTEM_PATH_EXT] = substr($_SERVER['SCRIPT_FILENAME'], 0, $pos + strlen($relExtDir));
                }
            }
        }

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


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

201
202
203
    /**
     * @param array $dataArray
     * @param $store
204
     * @param bool|false $flagOverwrite
205
     * @throws UserFormException
206
     * @throws \qfq\CodeException
207
     */
208
    public function setVarArray(array $dataArray, $store, $flagOverwrite = false) {
Carsten  Rose's avatar
Carsten Rose committed
209
        // Check valid Storename
Carsten  Rose's avatar
Carsten Rose committed
210
        if (!isset(self::$sanitizeStore))
211
            throw new UserFormException("Unknown Store: $store", ERROR_UNNOWN_STORE);
Carsten  Rose's avatar
Carsten Rose committed
212
213


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

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

221
222
        self::$raw[$store] = $dataArray;
    }
223

224
225
226
227
    /**
     * @param $bodytext
     * @throws CodeException
     */
228
229
    private function fillStoreTypo3($bodytext) {

230
        $arr = KeyValueStringParser::parse($bodytext, "=", "\n");
231
232
233
234
235
236
237
238
239

        if (isset($GLOBALS["TSFE"]->fe_user->user["username"]))
            $arr[TYPO3_FE_USER] = $GLOBALS["TSFE"]->fe_user->user["username"];

        if (isset($GLOBALS["TSFE"]->fe_user->user["uid"]))
            $arr[TYPO3_FE_USER_UID] = $GLOBALS["TSFE"]->fe_user->user["uid"];

        if (isset($GLOBALS["TSFE"]->fe_user->user["usergroup"]))
            $arr[TYPO3_FE_USER_GROUP] = $GLOBALS["TSFE"]->fe_user->user["usergroup"];
240

Carsten  Rose's avatar
Carsten Rose committed
241
242
243
        if (isset($GLOBALS["TSFE"]->page["uid"]))
            $arr[TYPO3_TT_CONTENT_UID] = $GLOBALS["TSFE"]->page["uid"];

244
245
246
        if (isset($GLOBALS["TSFE"]->id))
            $arr[TYPO3_PAGE_ID] = $GLOBALS["TSFE"]->id;

Carsten  Rose's avatar
Carsten Rose committed
247
248
249
        if (isset($GLOBALS["TSFE"]->type))
            $arr[TYPO3_PAGE_TYPE] = $GLOBALS["TSFE"]->type;

250
        self::setVarArray($arr, STORE_TYPO3, true);
251
    }
252

253
254
255
256
    /**
     * @throws CodeException
     */
    private function fillStoreClient() {
257
258
        // copy GET and POST and SERVER Parameter. Priority: SERVER, POST, GET
        $arr = array_merge($_GET, $_POST, $_SERVER);
259

260
        self::setVarArray($arr, STORE_CLIENT, true);
261
    }
262

Carsten  Rose's avatar
Carsten Rose committed
263
264
    /**
     * @throws CodeException
265
     * @throws UserFormException
Carsten  Rose's avatar
Carsten Rose committed
266
     */
267
    private function fillStoreSip() {
Carsten  Rose's avatar
Carsten Rose committed
268

269
        $sessionName = self::getVar(SYSTEM_SESSION_NAME, STORE_SYSTEM);
270
        self::$sip = new Sip($sessionName);
271

272
273
274
275
        $s = self::getVar(CLIENT_SIP, STORE_CLIENT);
        if ($s !== false) {
            // if session is given, copy values to store
            $param = self::$sip->getVarsFromSip($s);
276
277
            $param[SIP_SIP] = $s;
            $param[SIP_URLPARAM] = self::$sip->getQueryStringFromSip($s);
278

279
//            self::setVarArray(KeyValueStringParser::parse($param, "=", "&"), STORE_SIP);
280
            self::setVarArray($param, STORE_SIP, true);
281
282
283
        }
    }

284
    /**
285
     * Cycles through all stores in $useStore.
286
     * First match will return the found value.
Carsten  Rose's avatar
Carsten Rose committed
287
     * During cycling: fill cache with requestet value and sanitize raw value.
288
     *
289
     * @param string $key
290
     * @param string $useStores f.e.: 'FSRD'
Carsten  Rose's avatar
Carsten Rose committed
291
     * @param string $sanitizeClass
Carsten  Rose's avatar
Carsten Rose committed
292
     * @param string $foundInStore Returns the name of the store where $key has been found. If $key is not found, return ''.
293
     * @return string a) if found: value, b) false
Carsten  Rose's avatar
Carsten Rose committed
294
     * @throws \qfq\CodeException
295
     */
Carsten  Rose's avatar
Carsten Rose committed
296
    public static function getVar($key, $useStores = STORE_USE_DEFAULT, $sanitizeClass = '', &$foundInStore = '') {
297
298

        // no store specifed?
299
        if ($useStores === "" || $useStores === null) {
300
            $useStores = STORE_USE_DEFAULT;
301
302
        }

303
        // no sanitizeClass specified: take predefined (if exist) or default.
304
        if ($sanitizeClass === '' || $sanitizeClass === null) {
Carsten  Rose's avatar
Carsten Rose committed
305
            $sanitizeClass = isset(self::$sanitizeClass[$key]) ? self::$sanitizeClass[$key] : SANITIZE_DEFAULT;
306
307
        }

308
309
310
        while ($useStores !== false) {

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

314
            if (!isset(self::$raw[$store][$key])) {
315
                if ($store === STORE_ZERO) {
Carsten  Rose's avatar
Carsten Rose committed
316
317
318
319
                    return 0;
                } else {
                    continue; // no value provided
                }
320
321
            }

322
            $rawVal = isset(self::$raw[$store][$key]) ? self::$raw[$store][$key] : null;
Carsten  Rose's avatar
Carsten Rose committed
323
            if (self::$sanitizeStore[$store] && $sanitizeClass != '') {
324
325
326
327
328
329
//                return \qfq\Sanitize::sanitize($rawVal, $sanitizeClass);
                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);
330
331
            } else {
                return $rawVal;
332
            }
333
        }
Carsten  Rose's avatar
Carsten Rose committed
334
        $foundInStore = '';
335
        return false;
336
    }
337

338
339
    /**
     * @param string $bodytext
340
     * @param bool|false $phpUnit
341
     * @return null|\qfq\Store
342
     */
343
    public static function getInstance($bodytext = '', $phpUnit = false) {
344

345
        if ($phpUnit) {
346
347
348
349
350
351
352
            if (self::$instance !== null) {

                self::unsetStore(STORE_TYPO3);
                self::fillStoreTypo3($bodytext);

                self::unsetStore(STORE_CLIENT);
                self::fillStoreClient();
353
354
355
356
357
            }
        }

        // Design Pattern: Singleton
        if (self::$instance === null) {
Carsten  Rose's avatar
Carsten Rose committed
358
            self::$instance = new self($bodytext, self::$phpUnit);
Carsten  Rose's avatar
Carsten Rose committed
359
360
361
362
        } 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);
363
364
365
        }

        return self::$instance;
366
    }
367

368
369
370
371
    /**
     * @param $store
     */
    public static function unsetStore($store) {
Carsten  Rose's avatar
Carsten Rose committed
372
        // Check valid Storename
Carsten  Rose's avatar
Carsten Rose committed
373
        if (!isset(self::$sanitizeStore))
374
            throw new UserFormException("Unknown Store: $store", ERROR_UNNOWN_STORE);
Carsten  Rose's avatar
Carsten Rose committed
375

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

379
380
381
        if (isset(self::$raw[$store])) {
            self::$raw[$store] = array();
        }
Carsten  Rose's avatar
Carsten Rose committed
382

383
384
    }

385
386
387
388
    /**
     * @param $formName
     * @throws CodeException
     */
389
390
    public
    static function createSipAfterFormLoad($formName) {
391
        $recordId = self::getVar(CLIENT_RECORD_ID, STORE_TYPO3 . STORE_CLIENT);
392
393
394
395
396
397
398
        if ($recordId === false) {
            $recordId = 0;
        }

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

        // Construct fake urlparam
399
        $tmpUrlparam = OnArray::toString($tmpParam);
400
401

        // Create a fake SIP which has never been passed by URL - further processing might expect this to exist.
402
        $sip = self::getSipInstance()->queryStringToSip($tmpUrlparam, RETURN_SIP);
403
        self::setVar(CLIENT_SIP, $sip, STORE_CLIENT);
404
405
406
407
408

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

409
410
411
    }

    /**
412
     * @return null|Sip
413
     */
414
    public static function getSipInstance() {
415
416
        return self::$sip;
    }
417
418
419
420
421

    /**
     * @param $key
     * @param $value
     * @param $store
Carsten  Rose's avatar
Carsten Rose committed
422
     * @param bool|true $overWrite
423
     * @throws UserFormException
424
     */
Carsten  Rose's avatar
Carsten Rose committed
425
426
    public static function setVar($key, $value, $store, $overWrite = true) {
        // Check valid Storename
Carsten  Rose's avatar
Carsten Rose committed
427
        if (!isset(self::$sanitizeStore))
428
            throw new UserFormException("Unknown Store: $store", ERROR_UNNOWN_STORE);
Carsten  Rose's avatar
Carsten Rose committed
429

430
        if ($store === STORE_ZERO)
Carsten  Rose's avatar
Carsten Rose committed
431
432
433
            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])) {
434
            throw new UserFormException("Value of '$key' already be set in store '$store'.", ERROR_STORE_KEY_EXIST);
Carsten  Rose's avatar
Carsten Rose committed
435
        }
436
437

        self::$raw[$store][$key] = $value;
438
439
440
441
442
443
444
    }

    /**
     * @param $store
     * @return mixed
     */
    public static function getStore($store) {
Carsten  Rose's avatar
Carsten Rose committed
445
        // Check valid Storename
Carsten  Rose's avatar
Carsten Rose committed
446
        if (!isset(self::$sanitizeStore[$store]))
447
            throw new UserFormException("Unknown Store: $store", ERROR_UNNOWN_STORE);
Carsten  Rose's avatar
Carsten Rose committed
448

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

452
453
454
455
456
        if (isset(self::$raw[$store])) {
            return self::$raw[$store];
        }
        return array();
    }
457

Carsten  Rose's avatar
Carsten Rose committed
458

459
    /**
Carsten  Rose's avatar
Carsten Rose committed
460
461
     * Fills STORE_TABLE_DEFAULT and STORE_TABLE_COLUMN_TYPES
     *
462
463
464
     * @param $tableName
     * @throws CodeException
     */
465
    public function fillStoreTableDefaultColumnType($tableName) {
466
467
468
469
        $db = new qfq\Database();

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

Carsten  Rose's avatar
Carsten Rose committed
470
471
        self::setVarArray(array_column($tableDefinition, 'Default', 'Field'), STORE_TABLE_DEFAULT, true);
        self::setVarArray(array_column($tableDefinition, 'Type', 'Field'), STORE_TABLE_COLUMN_TYPES, true);
472
    }
473
474
475
476
477
}