Evaluate.php 18.6 KB
Newer Older
Carsten  Rose's avatar
Carsten Rose committed
1
2
3
4
5
6
7
8
<?php
/**
 * Created by PhpStorm.
 * User: crose
 * Date: 1/12/16
 * Time: 4:36 PM
 */

Marc Egger's avatar
Marc Egger committed
9
namespace IMATHUZH\Qfq\Core;
Carsten  Rose's avatar
Carsten Rose committed
10

Marc Egger's avatar
Marc Egger committed
11
12
use IMATHUZH\Qfq\Core\Database\Database;
use IMATHUZH\Qfq\Core\Helper\KeyValueStringParser;
13
use IMATHUZH\Qfq\Core\Helper\OnString;
14
use IMATHUZH\Qfq\Core\Helper\Path;
15
use IMATHUZH\Qfq\Core\Helper\Support;
Marc Egger's avatar
Marc Egger committed
16
use IMATHUZH\Qfq\Core\Report\Link;
17
18
19
use IMATHUZH\Qfq\Core\Report\Tablesorter;
use IMATHUZH\Qfq\Core\Store\Sip;
use IMATHUZH\Qfq\Core\Store\Store;
20
use IMATHUZH\Qfq\Core\Typo3\T3Handler;
Carsten  Rose's avatar
Carsten Rose committed
21
22


23
const EVALUATE_DB_INDEX_DEFAULT = 0;
Carsten  Rose's avatar
Carsten Rose committed
24
25
26
27
/**
 * Class Evaluate
 * @package qfq
 */
Carsten  Rose's avatar
Carsten Rose committed
28
class Evaluate {
29
30
31
    /**
     * @var Store
     */
Carsten  Rose's avatar
Carsten Rose committed
32
    private $store = null;
33

34
    /**
Carsten  Rose's avatar
Carsten Rose committed
35
     * @var Database[] - Array of Database instantiated class
36
     */
37
    private $dbArray = array();
38

39
40
41
42
43
    /**
     * @var Link
     */
    private $link = null;

44
45
46
47
48
    /**
     * @var Tablesorter
     */
    private $tablesorter = null;

49
    private $dbIndex = EVALUATE_DB_INDEX_DEFAULT;
Carsten  Rose's avatar
Carsten Rose committed
50
51
52
53
    private $startDelimiter = '';
    private $startDelimiterLength = 0;
    private $endDelimiter = '';
    private $endDelimiterLength = 0;
54
55
    private $sqlKeywords = array('SELECT ', 'INSERT ', 'DELETE ', 'UPDATE ', 'SHOW ', 'REPLACE ', 'TRUNCATE ', 'DESCRIBE ', 'EXPLAIN ', 'SET ');

56
    private $escapeTypeDefault = '';
57

58

59
//    private $debugStack = array();
Carsten  Rose's avatar
Carsten Rose committed
60
61


62
    /**
Marc Egger's avatar
Marc Egger committed
63
     * @param Store $store
Carsten  Rose's avatar
Carsten Rose committed
64
65
66
     * @param Database $db
     * @param string $startDelimiter
     * @param string $endDelimiter
Marc Egger's avatar
Marc Egger committed
67
68
     * @throws \CodeException
     * @throws \UserFormException
69
     */
Carsten  Rose's avatar
Carsten Rose committed
70
71
    public function __construct(Store $store, Database $db, $startDelimiter = '{{', $endDelimiter = '}}') {
        $this->store = $store;
72
73

        $this->dbArray[EVALUATE_DB_INDEX_DEFAULT] = $db;
Carsten  Rose's avatar
Carsten Rose committed
74
75
76
77
        $this->startDelimiter = $startDelimiter;
        $this->startDelimiterLength = strlen($startDelimiter);
        $this->endDelimiter = $endDelimiter;
        $this->endDelimiterLength = strlen($endDelimiter);
78
        $this->escapeTypeDefault = $this->store::getVar(F_ESCAPE_TYPE_DEFAULT, STORE_SYSTEM);
79
        if (empty($this->escapeTypeDefault) || $this->escapeTypeDefault == TOKEN_ESCAPE_CONFIG) {
80
            $this->escapeTypeDefault = $this->store::getVar(SYSTEM_ESCAPE_TYPE_DEFAULT, STORE_SYSTEM);
81
        }
Carsten  Rose's avatar
Carsten Rose committed
82
83
84
    }

    /**
85
     * Evaluate a whole array or an array of arrays.
86
     *
Carsten  Rose's avatar
Carsten Rose committed
87
     * @param       $tokenArray
88
89
     * @param array $skip Optional Array with keynames, which will not be evaluated.
     * @param array $debugStack
Carsten  Rose's avatar
Carsten Rose committed
90
     *
91
     * @return array
Marc Egger's avatar
Marc Egger committed
92
93
94
95
     * @throws \CodeException
     * @throws \DbException
     * @throws \UserFormException
     * @throws \UserReportException
96
     */
97
    public function parseArray($tokenArray, array $skip = array(), &$debugStack = array()) {
98
99
        $arr = array();

100
101
102
        // In case there is an Element 'fillStoreVar', process that first (if not defined to skip).
        $flagSkipFillStoreVar = (array_search(FE_FILL_STORE_VAR, $skip) !== false);
        if (!$flagSkipFillStoreVar && !empty($tokenArray[FE_FILL_STORE_VAR]) && is_string($tokenArray[FE_FILL_STORE_VAR])) {
103

104
105
106
            $arr = $this->parse($tokenArray[FE_FILL_STORE_VAR], ROW_REGULAR, 0, $debugStack);
            if (!empty($arr)) {
                $this->store::appendToStore($arr[0], STORE_VAR);
107
108
109
110
            }
            unset($tokenArray[FE_FILL_STORE_VAR]);
        }

111
        foreach ($tokenArray as $key => $value) {
112
113

            if (array_search($key, $skip) !== false) {
114
                $arr[$key] = $value;
115
116
117
                continue;
            }

118
            if (is_array($value)) {
119
                $arr[] = $this->parseArray($value, $skip);
120
            } else {
121
                $value = Support::handleEscapeSpaceComment($value);
122

123
                $arr[$key] = $this->parse($value, ROW_IMPLODE_ALL, 0, $debugStack);
124
            }
125
126
127
128
129
130
        }

        return $arr;
    }

    /**
Carsten  Rose's avatar
Carsten Rose committed
131
132
     * Recursive evaluation of 'line'. Constant string, Variables or SQL Query or all of them. All queries will be
     * fired. In case of an 'INSERT' statement, return the last_insert_id().
133
134
     *
     * Token to replace have to be enclosed by '{{' and '}}'
135
     *
136
     * @param string $line
137
     * @param string $sqlMode ROW_IMPLODE | ROW_REGULAR | ... - might be overwritten in $line by '{{!...'
138
     * @param int $recursion
139
140
     * @param array $debugStack
     * @param string $foundInStore
141
142
143
     *
     * @return array|mixed|null|string - in case of INSERT: last_insert_id()
     *
Marc Egger's avatar
Marc Egger committed
144
145
146
147
     * @throws \CodeException
     * @throws \DbException
     * @throws \UserFormException
     * @throws \UserReportException
Carsten  Rose's avatar
Carsten Rose committed
148
     */
149
    public function parse($line, $sqlMode = ROW_IMPLODE_ALL, $recursion = 0, &$debugStack = array(), &$foundInStore = '') {
150

Carsten  Rose's avatar
Carsten Rose committed
151
152
153
154
        if ($line === '') {
            return '';
        }

155
        $flagTokenReplaced = false;
Carsten  Rose's avatar
Carsten Rose committed
156
157

        if ($recursion > 4) {
Marc Egger's avatar
Marc Egger committed
158
            throw new \UserFormException(
Marc Egger's avatar
Marc Egger committed
159
                json_encode([ERROR_MESSAGE_TO_USER => 'Recursion too deep', ERROR_MESSAGE_TO_DEVELOPER => "Level: $recursion, Line: $line"]),
160
                ERROR_RECURSION_TOO_DEEP);
Carsten  Rose's avatar
Carsten Rose committed
161
162
        }

163
        $result = $line;
Carsten  Rose's avatar
Carsten Rose committed
164

165
        $debugIndent = str_repeat(' ', $recursion);
166
        $debugLocal[] = $debugIndent . "Parse: $result";
Carsten  Rose's avatar
Carsten Rose committed
167

168
        $posFirstClose = strpos($result, $this->endDelimiter);
169
        $posLastClose = strrpos($result, $this->endDelimiter);
170

171
        // Variables like 'fillStoreVar' might contain SQL statements. Put them in store in case a DB exception is thrown.
172
        $this->store::setVar(SYSTEM_SQL_RAW, $line, STORE_SYSTEM);
173

174
175
176
        while ($posFirstClose !== false) {

            $posMatchOpen = strrpos(substr($result, 0, $posFirstClose), $this->startDelimiter);
Carsten  Rose's avatar
Carsten Rose committed
177
            if ($posMatchOpen === false) {
Marc Egger's avatar
Marc Egger committed
178
                throw new \UserFormException(
Marc Egger's avatar
Marc Egger committed
179
                    json_encode([ERROR_MESSAGE_TO_USER => 'Missing open delimiter', ERROR_MESSAGE_TO_DEVELOPER => "Text: $result"]),
180
                    ERROR_MISSING_OPEN_DELIMITER);
Carsten  Rose's avatar
Carsten Rose committed
181
182
            }

183
184
185
            $pre = substr($result, 0, $posMatchOpen);
            $post = substr($result, $posFirstClose + $this->endDelimiterLength);
            $match = substr($result, $posMatchOpen + $this->startDelimiterLength, $posFirstClose - $posMatchOpen - $this->startDelimiterLength);
Carsten  Rose's avatar
Carsten Rose committed
186

187
188
            $tmpSqlMode = ($posFirstClose == $posLastClose) ? $sqlMode : ROW_IMPLODE_ALL;
            $evaluated = $this->substitute($match, $foundInStore, $tmpSqlMode);
189

190
191
192
            // newline
            $debugLocal[] = '';

193
            $debugLocal[] = $debugIndent . "Replace: $match";
Carsten  Rose's avatar
Carsten Rose committed
194

195
            if ($foundInStore === '') {
196
                // Encode the non replaceable part as preparation not to process again. Recode them at the end.
197
                $evaluated = Support::encryptDoubleCurlyBraces($this->startDelimiter . $match . $this->endDelimiter);
198
                $debugLocal[] = $debugIndent . "BY: <nothing found - not replaced>";
199

200
201
202
            } else {

                $flagTokenReplaced = true;
Carsten  Rose's avatar
Carsten Rose committed
203

204
205
206
                // If an array is returned, break everything and return this assoc array.
                if (is_array($evaluated)) {
                    $result = $evaluated;
207
                    $debugLocal[] = $debugIndent . "BY: array(" . count($result) . ")";
208
209
                    break;
                }
210

211
                $debugLocal[] = $debugIndent . "BY: $evaluated";
Carsten  Rose's avatar
Carsten Rose committed
212

213
                // More to substitute in the new evaluated result? Start recursion just with the new result..
214
215
216
217
218
219
                if ($foundInStore === TOKEN_FOUND_STOP_REPLACE) {
                    $evaluated = Support::encryptDoubleCurlyBraces($evaluated);
                } else {
                    if (strpos($evaluated, $this->endDelimiter) !== false) {
                        $evaluated = $this->parse($evaluated, ROW_IMPLODE_ALL, $recursion + 1, $debugLocal, $foundInStore);
                    }
220
221
                }
            }
222
            $result = $pre . $evaluated . $post;
223

224
225
            $posFirstClose = strpos($result, $this->endDelimiter);
        }
Carsten  Rose's avatar
Carsten Rose committed
226

227
228
        $result = Support::decryptDoubleCurlyBraces($result);

229
        if ($flagTokenReplaced === true) {
230
231
232
            if (is_array($result)) {
                $str = "array(" . count($result) . ")";
            } else {
233
                $str = "$result";
234
235
            }
            $debugLocal[] = $debugIndent . "FINAL: " . $str;
236

237
            $debugStack = $debugLocal;
Carsten  Rose's avatar
Carsten Rose committed
238
239
        }

240
        return $result;
Carsten  Rose's avatar
Carsten Rose committed
241
242
    }

243
244
245
246
247
    /**
     * @param $arrToken
     * @param $dbIndex
     * @param $foundInStore
     * @return string
Marc Egger's avatar
Marc Egger committed
248
249
250
     * @throws \CodeException
     * @throws \UserFormException
     * @throws \UserReportException
251
     */
252
    private function inlineLink($arrToken, $dbIndex, &$foundInStore) {
253
254
255
256

        $token = OnString::trimQuote(trim(implode(' ', $arrToken)));

        if ($this->link === null) {
257
            $this->link = new Link($this->store::getSipInstance(), $dbIndex);
258
259
260
261
262
263
264
        }

        $foundInStore = TOKEN_FOUND_AS_COLUMN;

        return $this->link->renderLink($token);
    }

265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289

    /**
     * Get the CET/CEST Timezone for a given date, or if date is '' based on the current date.
     *
     * @param string $dateStr
     * @return string   // CET, CEST or GMT+?/GMT-?
     */
    public function getEuropeanTimezone($dateStr = '') {

        $ts = ($dateStr == '') ? time() : strtotime($dateStr);
        $offset = date("Z", $ts) / 3600;
        switch ($offset) {
            case 1:
                $tz = "CET";
                break;
            case 2:
                $tz = "CEST";
                break;
            default:
                $tz = 'GMT' . sprintf("%+d", $offset);
        }

        return $tz;
    }

290
291
292
293
294
    /**
     * @param $arrToken
     * @param $dbIndex
     * @param $foundInStore
     * @return string
Marc Egger's avatar
Marc Egger committed
295
296
297
     * @throws \CodeException
     * @throws \UserFormException
     * @throws \UserReportException
298
     */
299
    private function inlineDataDndApi($arrToken, $dbIndex, &$foundInStore) {
300
301

        $token = OnString::trimQuote(trim(implode(' ', $arrToken)));
302
303
304

        # Include current SIP store, to fetch SIP parameter later.
        $token .= '&' . DND_FORM_SIP_VALUES . '=' . $this->store::getVar(SIP_SIP, STORE_SIP);
305
        if (empty($token)) {
Marc Egger's avatar
Marc Egger committed
306
            throw new \UserReportException('Missing form name for "data-dnd-api"', ERROR_MISSING_FORM);
307
308
309
        }

        if ($this->link === null) {
310
            $this->link = new Link($this->store::getSipInstance(), $dbIndex);
311
312
313
314
        }

        $foundInStore = TOKEN_FOUND_AS_COLUMN;

315
        $s = $this->link->renderLink('U:' . $token . '|s|r:8');
316
317

        // Flag to add DND JS code later on.
318
        $this->store::setVar(SYSTEM_DRAG_AND_DROP_JS, 'true', STORE_SYSTEM);
319

Marc Egger's avatar
Marc Egger committed
320
        // data-dnd-api="typo3conf/ext/qfq/qfq/Api/dragAndDrop.php?s={{'U:form=<form name>[&paramX=<any value>]|s|r:8' AS _link}}"
321
        return DND_DATA_DND_API . '="' . Path::urlApi(API_DRAG_AND_DROP_PHP) . '?s=' . $s . '"';
322
323
    }

Carsten  Rose's avatar
Carsten Rose committed
324
325
    /**
     * Tries to substitute $token.
326
     * Token might be:
327
     *   a) a SQL statement to fire
328
     *   b) fetch from a store. Syntax: '\[db index\]form:[store]:[sanitize]:[escape]:[default]:[type violate message]', ''
329
     *
330
     * The token have to be *without* Delimiter '{{' , '}}'
331
     * If neither a) or b) match, return the token itself.
Carsten  Rose's avatar
Carsten Rose committed
332
     *
333
     * @param string $token
334
     * @param string $foundInStore Returns the name of the store where $key has been found. If $key is not found, return ''.
335
     * @param string $sqlMode - ROW_IMPLODE | ROW_REGULAR | ... - might be overwritten in $line by '{{!...'
Carsten  Rose's avatar
Carsten Rose committed
336
     *
337
     * @return array|null|string
Marc Egger's avatar
Marc Egger committed
338
339
340
341
     * @throws \CodeException
     * @throws \DbException
     * @throws \UserFormException
     * @throws \UserReportException
Carsten  Rose's avatar
Carsten Rose committed
342
     */
343
    public function substitute($token, &$foundInStore = '', $sqlMode = ROW_IMPLODE_ALL) {
Carsten  Rose's avatar
Carsten Rose committed
344
345

        $token = trim($token);
346
        $dbIndex = $this->dbIndex;
347
        $flagWipe = false;
348
349

        // Check if the $token starts with '[<int>]...' - yes: open the necessary database.
350
        if (strlen($token) > 2 && $token[0] === '[') {
351
            if ($token[2] !== ']') {
Marc Egger's avatar
Marc Egger committed
352
                throw new \UserFormException(json_encode(
353
                    [ERROR_MESSAGE_TO_USER => "Missing token ']' on position 3",
Marc Egger's avatar
Marc Egger committed
354
                        ERROR_MESSAGE_TO_DEVELOPER => "In string '$token'"]), ERROR_TOKEN_MISSING);
355
356
357
358
359
360
361
362
            }
            $dbIndex = $token[1];
            $token = trim(substr($token, 3));

            if (empty($this->dbArray[$dbIndex])) {
                $this->dbArray[$dbIndex] = new Database($dbIndex);
            }
        }
Carsten  Rose's avatar
Carsten Rose committed
363

364
365
366
367
        if ($token === '') {
            return '';
        }

368
        // Get SQL column / row separated
Carsten  Rose's avatar
Carsten Rose committed
369
        if ($token[0] === '!') {
370
            $token = trim(substr($token, 1));
Carsten  Rose's avatar
Carsten Rose committed
371
372
373
            $sqlMode = ROW_REGULAR;
        }

374
        // Extract token: check if this is a 'variable', 'SQL Statement', 'link', 'data-dnd-api'
375
        $arrToken = explode(' ', $token);
376

377
        // Variable Type 'SQL Statement'
378
        if (in_array(strtoupper($arrToken[VAR_INDEX_VALUE] . ' '), $this->sqlKeywords)) {
379
            $foundInStore = TOKEN_FOUND_IN_STORE_QUERY;
Carsten  Rose's avatar
Carsten Rose committed
380

381
            return $this->dbArray[$dbIndex]->sql($token, $sqlMode);
Carsten  Rose's avatar
Carsten Rose committed
382
383
        }

384
385

        // Variable Type '... AS _link', '... as data-dnd-api', '... AS _tablesorter-view-saver'
386
        $countToken = count($arrToken);
387
        if ($countToken > 2 && strtolower($arrToken[$countToken - 2]) == 'as') {
388
389
390

            $type = OnString::stripFirstCharIf('_', $arrToken[$countToken - 1]);

391
            array_pop($arrToken); // remove 'link' | 'data-dnd-api' | 'tablesorter-view-saver'
392
            array_pop($arrToken); // remove 'as'
Carsten  Rose's avatar
Carsten Rose committed
393

394
395
396
397
398
399
400
401
            switch (strtolower($type)) {
                case COLUMN_LINK:
                    return ($this->inlineLink($arrToken, $dbIndex, $foundInStore));
                    break;

                case DND_DATA_DND_API:
                    return ($this->inlineDataDndApi($arrToken, $dbIndex, $foundInStore));
                    break;
402

403
404
405
406
407
408
409
410
                case TABLESORTER_VIEW_SAVER:
                    if ($this->tablesorter === null) {
                        $this->tablesorter = new Tablesorter($dbIndex);
                    }
                    return ($this->tablesorter->inlineTablesorterView($arrToken[VAR_INDEX_VALUE], $foundInStore));
                    break;
                default:
                    break;
411
412
413
            }
        }

414
415
        // explode for: <key>:<store priority>:<sanitize class>:<escape>:<default>:<type violate message>
        $arrToken = array_merge(KeyValueStringParser::explodeEscape(':', $token, 6), [null, null, null, null, null, null]);
416

417
418
        $escapeTypes = (empty($arrToken[VAR_INDEX_ESCAPE])) ? $this->escapeTypeDefault : $arrToken[VAR_INDEX_ESCAPE];
        $typeMessageViolate = ($arrToken[VAR_INDEX_MESSAGE] === null || $arrToken[VAR_INDEX_MESSAGE] === '') ? SANITIZE_TYPE_MESSAGE_VIOLATE_CLASS : $arrToken[VAR_INDEX_MESSAGE];
Carsten  Rose's avatar
Carsten Rose committed
419
420

        // search for value in stores
Carsten  Rose's avatar
Carsten Rose committed
421
        $value = $this->store::getVar($arrToken[VAR_INDEX_VALUE], $arrToken[VAR_INDEX_STORE], $arrToken[VAR_INDEX_SANITIZE],
422
            $foundInStore, $typeMessageViolate, $arrToken[VAR_INDEX_DEFAULT]);
Carsten  Rose's avatar
Carsten Rose committed
423

424
425
        // escape ticks
        if (is_string($value)) {
426
            // Process all escape requests in the given order.
427
            for ($ii = 0; $ii < strlen($escapeTypes); $ii++) {
428
                $escape = $escapeTypes[$ii];
429
430
431
                if ($escape == TOKEN_ESCAPE_CONFIG) {
                    $escape = $this->escapeTypeDefault;
                }
432
433
434
435
436
437
438
                switch ($escape) {
                    case TOKEN_ESCAPE_SINGLE_TICK:
                        $value = str_replace("'", "\\'", $value);
                        break;
                    case TOKEN_ESCAPE_DOUBLE_TICK:
                        $value = str_replace('"', '\\"', $value);
                        break;
439
440
441
                    case TOKEN_ESCAPE_COLON:
                        $value = str_replace(':', '\\:', $value);
                        break;
442
                    case TOKEN_ESCAPE_LDAP_FILTER:
443
444
                        $value = Support::ldap_escape($value, null, LDAP_ESCAPE_FILTER);
                        break;
445
                    case TOKEN_ESCAPE_LDAP_DN:
446
447
                        $value = Support::ldap_escape($value, null, LDAP_ESCAPE_DN);
                        break;
448
                    case TOKEN_ESCAPE_MYSQL:
449
                        $value = $this->dbArray[$dbIndex]->realEscapeString($value);
450
451
452
                        break;
                    case TOKEN_ESCAPE_NONE: // do nothing
                        break;
453
                    case TOKEN_ESCAPE_PASSWORD_T3FE:
454
                        $value = T3Handler::getHash($value);
455
                        break;
456
457
458
459
                    case TOKEN_ESCAPE_STOP_REPLACE:
                        $value = Support::encryptDoubleCurlyBraces($value);
                        break;
                    case TOKEN_ESCAPE_EXCEPTION:
460
                        // empty values will be handled later.
461
                        break;
462
463
464
                    case TOKEN_ESCAPE_WIPE:
                        $flagWipe = true;
                        break;
465
466
467
                    case TOKEN_ESCAPE_TIMEZONE:
                        $value = $this->getEuropeanTimezone($value);
                        break;
468
469
470
                    case TOKEN_ESCAPE_HTML_SPECIAL_CHAR:
                        $value = Support::htmlEntityEncodeDecode(MODE_ENCODE, $value);
                        break;
471
                    default:
Marc Egger's avatar
Marc Egger committed
472
                        throw new \UserFormException("Unknown escape qualifier: $escape", ERROR_UNKNOW_SANITIZE_CLASS);
473
474
                        break;
                }
475
            }
476
477
478
        } else {
            // In case the value is not found and the escape class forces a full stop
            if (strpos($escapeTypes, TOKEN_ESCAPE_EXCEPTION) !== false) {
Marc Egger's avatar
Marc Egger committed
479
                throw new \UserFormException($arrToken[VAR_INDEX_MESSAGE] ?? '', ERROR_QUIT_QFQ_REGULAR);
480
            }
481
482
        }

483
        // Not found and a default is given: take the default.
Carsten  Rose's avatar
Carsten Rose committed
484
        if ($foundInStore == '' && $arrToken[VAR_INDEX_DEFAULT] != '') {
485
            $foundInStore = TOKEN_FOUND_AS_DEFAULT;
486
            $value = str_replace('\\:', ':', $arrToken[VAR_INDEX_DEFAULT]);
487
        }
488

489
490
491
        if ($flagWipe) {
            switch ($foundInStore) {
                case STORE_SIP:
492
                    $this->store::unsetVar($arrToken[VAR_INDEX_VALUE], STORE_SIP);
493
494

                    $sip = new Sip();
495
                    $sip->removeKeyFromSip($this->store::getVar(SIP_SIP, STORE_SIP), $arrToken[VAR_INDEX_VALUE]);
496
497
498
499
500
                    break;
                case STORE_EMPTY:
                case STORE_ZERO:
                    break;
                default:
Carsten  Rose's avatar
Carsten Rose committed
501
                    throw new \UserReportException("Wipe not implemented for store $foundInStore", ERROR_WIPE_NOT_IMPLEMENTED_FOR_STORE);
502
503
504
            }
        }

505
        return $value;
Carsten  Rose's avatar
Carsten Rose committed
506
    }
507

508
509
510
    /**
     * @return string
     */
511
512
513
//    public function getDebug() {
//        return '<pre>' . implode("\n", $this->debugStack) . '</pre>';
//    }
514
}