Store.php 16.2 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\UserException
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\UserException ("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
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
        // 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);
                if ($pos === false) {
                    // 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];
        }


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

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


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

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

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

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

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

        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"];
239

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        return self::$instance;
365
    }
366

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

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

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

382
383
    }

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

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

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

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

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

408
409
410
    }

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

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

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

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

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

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

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

Carsten  Rose's avatar
Carsten Rose committed
457

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

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

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