Report.php 46.8 KB
Newer Older
1
2
3
4
<?php
/***************************************************************
 *  Copyright notice
 *
Carsten  Rose's avatar
Carsten Rose committed
5
 *  (c) 2010 Glowbase GmbH
6
 *
Carsten  Rose's avatar
Carsten Rose committed
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
 *  This script is part of the TYPO3 project. The TYPO3 project is
 *  free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  The GNU General Public License can be found at
 *  http://www.gnu.org/copyleft/gpl.html.
 *
 *  This script is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  This copyright notice MUST APPEAR in all copies of the script!
22
23
24
25
26
27
28
29
 ***************************************************************/

namespace qfq;

//use qfq;

require_once(__DIR__ . '/Define.php');
require_once(__DIR__ . '/Variables.php');
30
//require_once(__DIR__ . '/Error.php');
31
require_once(__DIR__ . '/../database/Database.php');
32
require_once(__DIR__ . '/Link.php');
33
require_once(__DIR__ . '/SendMail.php');
34
require_once(__DIR__ . '/../exceptions/UserReportException.php');
35
require_once(__DIR__ . '/../Evaluate.php');
36
require_once(__DIR__ . '/../helper/KeyValueStringParser.php');
37
38
require_once(__DIR__ . '/../helper/Token.php');
require_once(__DIR__ . '/Thumbnail.php');
39
//require_once(__DIR__ . '/Monitor.php');
Carsten  Rose's avatar
Carsten Rose committed
40
require_once(__DIR__ . '/../utils/DataImport.php');
41

42
43
const DEFAULT_QUESTION = 'question';
const DEFAULT_ICON = 'icon';
44
const DEFAULT_BOOTSTRAP_BUTTON = 'bootstrapButton';
45

46
47
48
49
/**
 * Class Report
 * @package qfq
 */
50
51
class Report {

Carsten  Rose's avatar
Carsten Rose committed
52
53
54
55
56
    /**
     * @var SIP
     */
    private $sip = null;

Carsten  Rose's avatar
Carsten Rose committed
57
58
59
60
61
    /**
     * @var Link
     */
    private $link = null;

62
63
64
65
66
    /**
     * @var Store
     */
    private $store = null;

67
68
69
70
71
72
73
    /**
     * @var string
     */
    private $dbAlias = '';

    // frArray[10.50.5.sql][select ...]
    private $frArray = array();
74

75
76
    // $indexArray[10][50][5]   one entry per 'sql' statement
    private $indexArray = array();
77

78
79
80
81
    // TODO to explain
//	private $resultArray = array();
    private $levelCount = 0;
    //private $counter = 0;
82

83
84
85
86
    /**
     * @var Variables
     */
    private $variables = null;
87

88
    /**
89
     * @var Database
90
91
     */
    private $db = null;
92

93
    private $dbIndexData = false;
94

95
96
97
98
99
    /**
     * @var Thumbnail
     */
    private $thumbnail = null;

100
101
102
103
104
    /**
     * @var Monitor
     */
    private $monitor = null;

105
106
107
108
    /**
     * @var array
     */
    private $pageDefaults = array();
109

110
    /**
Carsten  Rose's avatar
Carsten Rose committed
111
112
     * @var array - Emulate global variable: will be set much earlier in other functions. Will be shown in error
     *      messages.
113
     */
114
115
    private $fr_error = array('uid' => '', 'pid' => '', 'row' => '', 'debug_level' => '0', 'full_level' => '');

116
117
    private $phpUnit = false;

118
    private $showDebugInfoFlag = false;
119

120
    /**
121
     * Report constructor.
122
     *
Carsten  Rose's avatar
Carsten Rose committed
123
     * @param array $t3data
124
     * @param Evaluate $eval
Carsten  Rose's avatar
Carsten Rose committed
125
     * @param bool $phpUnit
126
     * @throws CodeException
127
     * @throws DbException
128
     * @throws UserFormException
129
     * @throws UserReportException
130
     */
131
    public function __construct(array $t3data, Evaluate $eval, $phpUnit = false) {
132
133

        $this->phpUnit = $phpUnit;
134

135
136
        Support::setIfNotSet($t3data, "uid", 0);

137
        $this->sip = new Sip($phpUnit);
138
139
        if ($phpUnit) {
            $this->sip->sipUniqId('badcaffee1234');
140
141
            //TODO Webserver Umgebung besser faken
            $_SERVER['REQUEST_URI'] = 'localhost';
142
143
        }

144
        $this->store = Store::getInstance();
145

146
        $this->showDebugInfoFlag = (Support::findInSet(SYSTEM_SHOW_DEBUG_INFO_YES, $this->store->getVar(SYSTEM_SHOW_DEBUG_INFO, STORE_SYSTEM)));
147

148
149
        $this->checkUpdateSqlLog();

150
151
        $this->pageDefaults[DEFAULT_QUESTION]["pagec"] = "Please confirm!:info";
        $this->pageDefaults[DEFAULT_QUESTION]["paged"] = "Do you really want to delete the record?:warning";
152

153
154
155
156
157
158
        $this->pageDefaults[DEFAULT_ICON]["paged"] = TOKEN_DELETE;
        $this->pageDefaults[DEFAULT_ICON]["pagee"] = TOKEN_EDIT;
        $this->pageDefaults[DEFAULT_ICON]["pageh"] = TOKEN_HELP;
        $this->pageDefaults[DEFAULT_ICON]["pagei"] = TOKEN_INFO;
        $this->pageDefaults[DEFAULT_ICON]["pagen"] = TOKEN_NEW;
        $this->pageDefaults[DEFAULT_ICON]["pages"] = TOKEN_SHOW;
159

160
161
162
163
164
165
        $this->pageDefaults[DEFAULT_BOOTSTRAP_BUTTON]["pagec"] = TOKEN_BOOTSTRAP_BUTTON;
        $this->pageDefaults[DEFAULT_BOOTSTRAP_BUTTON]["paged"] = TOKEN_BOOTSTRAP_BUTTON;
        $this->pageDefaults[DEFAULT_BOOTSTRAP_BUTTON]["pagee"] = TOKEN_BOOTSTRAP_BUTTON;
        $this->pageDefaults[DEFAULT_BOOTSTRAP_BUTTON]["pagen"] = TOKEN_BOOTSTRAP_BUTTON;
        $this->pageDefaults[DEFAULT_BOOTSTRAP_BUTTON]["pages"] = TOKEN_BOOTSTRAP_BUTTON;

166
        // Default should already set in QuickFormQuery() Constructor
Carsten  Rose's avatar
Carsten Rose committed
167
168
169
170
        $this->dbIndexData = $this->store->getVar(TOKEN_DB_INDEX, STORE_TYPO3);
        if ($this->dbIndexData === false) {
            $this->dbIndexData = DB_INDEX_DEFAULT;
        }
171

172
        $this->db = new Database($this->dbIndexData);
173
        $this->variables = new Variables($eval, $t3data["uid"]);
174

175
176
        $this->link = new Link($this->sip, $this->dbIndexData, $phpUnit);

177
        // Set static values, which won't change during this run.
178
        $this->fr_error["pid"] = isset($this->variables->resultArray['global.']['page_id']) ? $this->variables->resultArray['global.']['page_id'] : 0;
179
        $this->fr_error["uid"] = $t3data['uid'];
180
        $this->fr_error["debug_level"] = 0;
181

Carsten  Rose's avatar
Carsten Rose committed
182
        // Sanitize function for POST and GET Parameters.
183
        // Merged URL-Parameter (key1, id etc...) in resultArray.
184
        $this->variables->resultArray = array_merge($this->variables->resultArray, array("global." => $this->variables->collectGlobalVariables()));
185
186
187

    }

188
    /**
Carsten  Rose's avatar
Carsten Rose committed
189
190
     * If a variable 'sqlLog' is given in STORE_TYPO3 (=Bodytext) make them relative to SYSTEM_PATH_EXT and copy it to
     * STORE_SYSTEM
191
192
193
     *
     * @throws CodeException
     * @throws UserFormException
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
     */
    private function checkUpdateSqlLog() {

        $sqlLog = $this->store->getVar(TYPO3_SQL_LOG, STORE_TYPO3);
        if (false !== $sqlLog) {
            if ($sqlLog != '' && $sqlLog[0] !== '/') {
                $sqlLog = $this->store->getVar(SYSTEM_PATH_EXT, STORE_SYSTEM) . '/' . $sqlLog;
            }

            $this->store->setVar(SYSTEM_SQL_LOG, $sqlLog, STORE_SYSTEM);
        }

        $sqlLogMode = $this->store->getVar(TYPO3_SQL_LOG_MODE, STORE_TYPO3);
        if (false !== $sqlLogMode) {
            $this->store->setVar(SYSTEM_SQL_LOG_MODE, $sqlLogMode, STORE_SYSTEM);
        }
    }

212
213
214
    /**
     * Main function. Parses bodytext and iterates over all queries.
     *
215
     * @param $bodyText
216
     * @return string
217
218
     * @throws CodeException
     * @throws DbException
219
     * @throws DownloadException
220
221
     * @throws UserFormException
     * @throws UserReportException
222
     */
223
    public function process($bodyText) {
224

225
226
227
228
229
        //phpUnit Test: clean environment
        $this->frArray = array();
        $this->indexArray = array();
        $this->levelCount = 0;

230
        // Iteration over Bodytext
231
        $ttLineArray = explode("\n", $bodyText);
232

233
234
        foreach ($ttLineArray as $index => $line) {
            // Fill $frArray, $indexArray, $resultArray
235
            $this->parseLine($line);
236
        }
237

238
239
240
241
242
243
        // Sort array
        $this->sortIndexArray($this->indexArray, $this->generateSortArg());

        // Report
        $content = $this->triggerReport();

244
        return $content;
245
    }
246
247
248
249
250
251

    /**
     * Split line in level, command, content and fill 'frArray', 'levelCount', 'indexArray'
     * Example: 10.50.5.sql = select * from person
     *
     * @param    string $ttLine : line to split in level, command, content
Carsten  Rose's avatar
Carsten Rose committed
252
     *
253
     * @throws UserReportException
254
     */
255
    private function parseLine($ttLine) {
256
257
258
259

        // 10.50.5.sql = select ...
        $arr = explode("=", trim($ttLine), 2);

260
        // no elements or only one: do nothing
261
        if (count($arr) < 2) {
262
            return;
263
        }
264

265
266
267
268
        // 10.50.5.sql
        $key = strtolower(trim($arr[0]));

        // comment ?
269
        if (empty($key) || $key[0] === "#") {
270
            return;
271
        }
272

273
        // select ... - if needed, trim surrounding single ticks
274
        $value = trim($arr[1]);
275
        $value = OnString::trimQuote($value);
276
277
278
279
280
281
282

        // 10.50.5.sql
        $arrKey = explode('.', $key);

        // frCmd = "sql"
        $frCmd = $arrKey[count($arrKey) - 1];

283
        if (strpos('|' . strtolower(TOKEN_VALID_LIST) . '|', '|' . $frCmd . '|') === false) {
284
            throw new UserReportException ("Unknown token: $frCmd in Line '$ttLine''", ERROR_UNKNOWN_TOKEN);
285
286
        }

287
288
289
290
291
292
293
294
295
296
297
        // remove last item (cmd)
        unset($arrKey[count($arrKey) - 1]);

        // save elements only if there is a level specified
        if (count($arrKey)) {
            // level = "10.50.5"
            $level = implode(".", $arrKey);

            // fill Array
            $this->setLine($level, $frCmd, $value);
        }
298
    }
299

300
301
302
303
304
    /**
     * @param $level
     * @param $frCmd
     * @param $value
     */
305
306
307
308
309
310
    private function setLine($level, $frCmd, $value) {

        // store complete line reformatted in frArray
        $this->frArray[$level . "." . $frCmd] = $value;

        // per sql command
311
        //pro sql cmd wird der Indexarray abgefüllt. Dieser wird später verwendet um auf den $frArray zuzugreifen
312
        //if(preg_match("/^sql/i", $frCmd) == 1){
313
314
//        if ($frCmd === TOKEN_FORM || $frCmd === TOKEN_SQL) {
        if ($frCmd === TOKEN_SQL) {
315
316
317
318
319
            // Remember max level
            $this->levelCount = max(substr_count($level, '.') + 1, $this->levelCount);
            // $indexArray[10][50][5]
            $this->indexArray[] = explode(".", $level);
        }
320
321
322

        // set defaults
        if ($frCmd === TOKEN_SQL) {
323
324
            $arr = explode('|', TOKEN_VALID_LIST);
            foreach ($arr as $key) {
325
                if (!isset($this->frArray[$level . "." . $key])) {
326
                    $this->frArray[$level . "." . $key] = '';
327
                }
328
329
            }
        }
330
    }
331
332
333
334

    /**
     * Sorts the associative array.
     *
Carsten  Rose's avatar
Carsten Rose committed
335
     * @param array $ary : The unsorted Level Array
336
     * @param string $clause : the sort argument 0 ASC, 1 ASC... according to the number of columns
337
     * @param bool|true $ascending
338
     */
339
    private function sortIndexArray(array &$ary, $clause, $ascending = true) {
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383

        $clause = str_ireplace('order by', '', $clause);
        $clause = preg_replace('/\s+/', ' ', $clause);
        $keys = explode(',', $clause);
        $dirMap = array('desc' => 1, 'asc' => -1);
        $def = $ascending ? -1 : 1;

        $keyAry = array();
        $dirAry = array();
        foreach ($keys as $key) {
            $key = explode(' ', trim($key));
            $keyAry[] = trim($key[0]);
            if (isset($key[1])) {
                $dir = strtolower(trim($key[1]));
                $dirAry[] = $dirMap[$dir] ? $dirMap[$dir] : $def;
            } else {
                $dirAry[] = $def;
            }
        }
        $fnBody = '';
        for ($i = count($keyAry) - 1; $i >= 0; $i--) {
            $k = $keyAry[$i];
            $t = $dirAry[$i];
            $f = -1 * $t;
            $aStr = '$a[\'' . $k . '\']';
            $bStr = '$b[\'' . $k . '\']';

            if (strpos($k, '(') !== false) {
                $aStr = '$a->' . $k;
                $bStr = '$b->' . $k;
            }

            if ($fnBody == '') {
                $fnBody .= "if({$aStr} == {$bStr}) { return 0; }\n";
                $fnBody .= "return ({$aStr} < {$bStr}) ? {$t} : {$f};\n";
            } else {
                $fnBody = "if({$aStr} == {$bStr}) {\n" . $fnBody;
                $fnBody .= "}\n";
                $fnBody .= "return ({$aStr} < {$bStr}) ? {$t} : {$f};\n";
            }
        }

        if ($fnBody) {
            $sortFn = create_function('$a,$b', $fnBody);
384
385
386
387
388

            // TODO: at the moment, $sortFn() triggers some E_NOTICE warnings. We stop these here for a short time.
            $errorSet = error_reporting();
            error_reporting($errorSet & ~E_NOTICE);

389
            usort($ary, $sortFn);
390
391

            error_reporting($errorSet);
392
        }
393
    }
394

395
396
397
398
399
    /**
     * generateSortArg
     *
     * @return string
     */
400
401
402
403
404
405
406
    private function generateSortArg() {

        $sortArg = "";

        for ($i = 0; $i < $this->levelCount; $i++) {
            $sortArg = $sortArg . $i . " ASC, ";
        }
407

408
        $sortArg = substr($sortArg, 0, strlen($sortArg) - 2);
409

410
        return $sortArg;
411
    }
412
413

    /**
414
     * Executes the queries recursive. This Method is called for each sublevel.
415
416
417
418
419
420
     *
     * ROOTLEVEL
     * This method is called once from the main method.
     * For the first call the method executes the rootlevels
     *
     * SUBLEVEL
421
     * For each rootlevel the method calls it self with the level mode 0
422
423
424
425
     * If the next Level is a Sublevel it will be executed and $this->counter will be added by 1
     * The sublevel calls the method again for a following sublevel
     *
     * @param int $cur_level Which level it will call [10] = level 1, [10.10] = level 2 ...
426
     * @param array $super_level_array The Value-Array of the indexarray [0=>10, 1=>50]
Carsten  Rose's avatar
Carsten Rose committed
427
     * @param int $counter The outer numeric Arraykey of indexarray
Carsten  Rose's avatar
Carsten Rose committed
428
     *
429
     * @return string                       The content that is displayed on the website
430
431
     * @throws CodeException
     * @throws DbException
432
     * @throws DownloadException
433
     * @throws UserFormException
434
435
436
     * @throws UserReportException
     */

437
438
    private function triggerReport($cur_level = 1, array $super_level_array = array(), $counter = 0) {
        $keys = array();
439
        $stat = array();
440
441
442
443
444
445

        $lineDebug = 0;
        $content = "";
        $rowTotal = 0;

        // CurrentLevel "10.10.50"
446
447
448
449
450
451
        if (isset($this->indexArray[$counter]) && is_array($this->indexArray[$counter])) {
            $full_level = implode(".", $this->indexArray[$counter]);
        } else {
            $full_level = '';
        }

452
        // Superlevel "10.10"
453
454
455
456
457
        if (isset($super_level_array) && is_array($super_level_array)) {
            $full_super_level = implode(".", $super_level_array);
        } else {
            $full_super_level = '';
        }
458
459
460
461
462
463
464

        //condition1: indexArray
        //condition2: full_level == Superlevel (but at the length of the Superlevel)
        while ($counter < count($this->indexArray) && $full_super_level == substr($full_level, 0, strlen($full_super_level))) {

            //True: The cur_level is a subquery -> continue
            if ($cur_level != count($this->indexArray[$counter])) {
465
466
467
468
469
470
                ++$counter;
                if (isset($this->indexArray[$counter]) && is_array($this->indexArray[$counter])) {
                    $full_level = implode(".", $this->indexArray[$counter]);
                } else {
                    $full_level = '';
                }
471
472
473
474
                continue;
            }

            // Set debug, if one is specified else keep the parent one.
475
            $lineDebug = $this->getValueParentDefault(TOKEN_DEBUG, $full_super_level, $full_level, $cur_level, 0);
476
477

            // Prepare Error reporting
478
            $this->store->setVar(SYSTEM_SQL_RAW, $this->frArray[$full_level . "." . TOKEN_SQL], STORE_SYSTEM);
479
            $this->store->setVar(SYSTEM_REPORT_FULL_LEVEL, $full_level, STORE_SYSTEM);
480

481
482
483
            // Prepare SQL: replace variables. Actual 'line.total' or 'line.count' will recalculated: don't replace them now!
            unset($this->variables->resultArray[$full_level . ".line."]["total"]);
            unset($this->variables->resultArray[$full_level . ".line."]["count"]);
484

485
            $sql = $this->variables->doVariables($this->frArray[$full_level . "." . TOKEN_SQL]);
486

487
            $this->store->setVar(SYSTEM_SQL_FINAL, $sql, STORE_SYSTEM);
488
489
490

            //Execute SQL. All errors have been already catched.
            unset($result);
491
            $result = $this->db->sql($sql, ROW_KEYS, array(), '', $keys, $stat);
492
493
494

            // If an array is returned, $sql was a query, otherwise an 'insert', 'update', 'delete', ...
            // Query: total nummber of rows
495
496
            // insert, delete, update: number of affected rows
            $rowTotal = isset($stat[DB_NUM_ROWS]) ? $stat[DB_NUM_ROWS] : $stat[DB_AFFECTED_ROWS];
497
498

            $this->variables->resultArray[$full_level . ".line."]["total"] = $rowTotal;
499
500
            $this->variables->resultArray[$full_level . ".line."]["count"] = is_array($result) ? 1 : 0;
            $this->variables->resultArray[$full_level . ".line."]["insertId"] = isset($stat[DB_INSERT_ID]) ? $stat[DB_INSERT_ID] : 0;
501

502
            $content .= $this->variables->doVariables($this->frArray[$full_level . "." . TOKEN_SHEAD]);
503
            // HEAD: If there is at least one record, do 'head'.
504
            if ($rowTotal > 0) {
505
                $content .= $this->variables->doVariables($this->frArray[$full_level . "." . TOKEN_HEAD]);
506
            }
507
508


509
            if (is_array($result)) {
510
511
512
513
514
515
516
517

                // Prepare row alteration
                $arrRbgd = explode("|", $this->frArray[$full_level . "." . TOKEN_RBGD], 2);
                if (count($arrRbgd) < 2) {
                    $arrRbgd[] = '';
                    $arrRbgd[] = '';
                }

518
519
                //---------------------------------
                // Process each row of resultset
520
                $columnValueSeparator = "";
521
522
523
524
525
526
527
528
529
530
                $rowIndex = 0;
                foreach ($result as $row) {
                    // record number counter
                    $this->variables->resultArray[$full_level . ".line."]["count"] = ++$rowIndex;

                    // replace {{<level>.line.count}} and {{<level>.line.total}} in __result__, if the variables specify their own full_level. This can't be replaced before firing the query.
                    for ($ii = 0; $ii < count($row); $ii++) {
                        $row[$ii] = str_replace("{{" . $full_level . ".line.count}}", $rowIndex, $row[$ii]);
                        $row[$ii] = str_replace("{{" . $full_level . ".line.total}}", $rowTotal, $row[$ii]);
                    }
531

532
533
534
535
                    // SEP set separator (empty on first run)
                    $content .= $columnValueSeparator;
                    $columnValueSeparator = $this->variables->doVariables($this->frArray[$full_level . "." . TOKEN_RSEP]);

Carsten  Rose's avatar
Carsten Rose committed
536
                    // RBEG
537
                    $rbeg = $this->variables->doVariables($this->frArray[$full_level . "." . TOKEN_RBEG]);
538

539
                    // RBGD: even/odd rows
540
                    $content .= str_replace(TOKEN_RBGD, $arrRbgd[$rowIndex % 2], $rbeg);
541

542
543
544
                    //-----------------------------
                    // COLUMNS: Collect all columns
                    $content .= $this->collectRow($row, $keys, $full_level, $rowIndex);
545

546
                    // REND
547
                    $content .= $this->variables->doVariables($this->frArray[$full_level . "." . TOKEN_REND]);
548

549
550
                    // Trigger subqueries of this level
                    $content .= $this->triggerReport($cur_level + 1, $this->indexArray[$counter], $counter + 1);
551

552
                    // RENR
553
                    $content .= $this->variables->doVariables($this->frArray[$full_level . "." . TOKEN_RENR]);
554
                }
555
556
557
            }

            if ($rowTotal > 0) {
558
                // tail
559
                $content .= $this->variables->doVariables($this->frArray[$full_level . "." . TOKEN_TAIL]);
560
            } else {
561
                // althead
562
                $content .= $this->variables->doVariables($this->frArray[$full_level . "." . TOKEN_ALT_HEAD]);
563
564
565
566
567
568
569
570
571
                // altsql
                $sql = $this->variables->doVariables($this->frArray[$full_level . "." . TOKEN_ALT_SQL]);
                if (!empty($sql)) {
                    $result = $this->db->sql($sql, ROW_KEYS, array(), '', $keys, $stat);
                    foreach ($result as $row) {
                        $rowIndex = 0;
                        $content .= $this->collectRow($row, $keys, $full_level, $rowIndex);
                    }
                }
572
            }
573
            $content .= $this->variables->doVariables($this->frArray[$full_level . "." . TOKEN_STAIL]);
574

575
576
577
578
579
580
            ++$counter;
            if (isset($this->indexArray[$counter]) && is_array($this->indexArray[$counter])) {
                $full_level = implode(".", $this->indexArray[$counter]);
            } else {
                $full_level = '';
            }
581
582
583
        }

        return $content;
584
    }
585
586
587
588
589
590
591
592

    /**
     * Determine value:
     * 1) if one specified in line: take it
     * 2) if one specified in upper level: take it
     * 3) if none above take default
     * Set value on $full_level
     *
Carsten  Rose's avatar
Carsten Rose committed
593
     * @param    string $level_key - 'db' or 'debug'
594
595
     * @param    string $full_super_level - f.e.: 10.10.
     * @param    string $full_level - f.e.: 10.10.10.
Carsten  Rose's avatar
Carsten Rose committed
596
597
     * @param    string $cur_level - f.e.: 2
     * @param    string $default - f.e.: 0
Carsten  Rose's avatar
Carsten Rose committed
598
     *
599
600
601
602
     * @return   string  The calculated value.
     */
    private function getValueParentDefault($level_key, $full_super_level, $full_level, $cur_level, $default) {

603
        if (!empty($this->frArray[$full_level . "." . $level_key])) {
604
605
606
607
608
609
610
611
612
613
614
            $value = $this->frArray[$full_level . "." . $level_key];
        } else {
            if ($cur_level == 1) {
                $value = $default;
            } else {
                $value = $this->variables->resultArray[$full_super_level . ".line."][$level_key];
            }
        }
        $this->variables->resultArray[$full_level . ".line."][$level_key] = $value;

        return ($value);
615
    }
616
617
618
619

    /**
     * Steps through 'row' and collects all columns
     *
Carsten  Rose's avatar
Carsten Rose committed
620
621
     * @param array $row Recent row fetch from sql resultset.
     * @param array $keys List of all columnnames
622
623
     * @param string $full_level Recent position to work on.
     * @param string $rowIndex Index of recent row in resultset.
Carsten  Rose's avatar
Carsten Rose committed
624
     *
625
     * @return string               Collected content of all printable columns
626
627
     * @throws CodeException
     * @throws DbException
628
     * @throws DownloadException
629
630
     * @throws UserFormException
     * @throws UserReportException
631
632
633
     */
    private function collectRow(array $row, array $keys, $full_level, $rowIndex) {
        $content = "";
634
        $assoc = array();
635

636
        $fsep = '';
637
638
        for ($ii = 0; $ii < count($keys); $ii++) {

639
            // Debugging
640
641
642
            $this->store->setVar(SYSTEM_REPORT_COLUMN_INDEX, $ii + 1, STORE_SYSTEM);
            $this->store->setVar(SYSTEM_REPORT_COLUMN_NAME, $keys[$ii], STORE_SYSTEM);
            $this->store->setVar(SYSTEM_REPORT_COLUMN_VALUE, $row[$ii], STORE_SYSTEM);
643

644
645
            $flagOutput = false;
            $renderedColumn = $this->renderColumn($ii, $keys[$ii], $row[$ii], $full_level, $rowIndex, $flagOutput);
646

647
            $keyAssoc = OnString::stripFirstCharIf(TOKEN_COLUMN_CTRL, $keys[$ii]);
648
649
            if ($keyAssoc != '') {
                $assoc[$keyAssoc] = $row[$ii];
650
                $assoc[REPORT_TOKEN_FINAL_VALUE . $keyAssoc] = $renderedColumn;
651
652
            }

653
            if ($flagOutput) {
654
                //prints
655
                $content .= $this->variables->doVariables($fsep);
656
                $content .= $this->variables->doVariables($this->frArray[$full_level . "." . TOKEN_FBEG]);
657
                $content .= $renderedColumn;
658
659
                $content .= $this->variables->doVariables($this->frArray[$full_level . "." . TOKEN_FEND]);
                $fsep = $this->frArray[$full_level . "." . TOKEN_FSEP];
660
661
            }
        }
Carsten  Rose's avatar
Carsten Rose committed
662

663
        $this->store->appendToStore($assoc, STORE_RECORD);
664

665
        return ($content);
666
    }
667
668
669
670

    /**
     * Renders column depending of column name (if name is a reserved column name)
     *
671
672
673
674
675
     * @param string $columnIndex
     * @param string $columnName
     * @param string $columnValue
     * @param string $full_level
     * @param string $rowIndex
676
     * @param $flagOutput
Carsten  Rose's avatar
Carsten Rose committed
677
     *
678
     * @return string rendered column
679
     * @throws CodeException
Carsten  Rose's avatar
Carsten Rose committed
680
     * @throws DbException
681
     * @throws DownloadException
682
683
     * @throws UserFormException
     * @throws UserReportException
684
     */
685
    private function renderColumn($columnIndex, $columnName, $columnValue, $full_level, $rowIndex, &$flagOutput) {
686
        $content = "";
687
688
        $flagControl = false;
        $flagOutput = true;
689

690
        // Special column name:  '_...'? Empty column names are allowed: check with isset
691
        if (isset($columnName[0]) && $columnName[0] === TOKEN_COLUMN_CTRL) {
692
            $flagControl = true;
693
            $columnName = substr($columnName, 1);
694
695
696
697
698
699

            // Special column name and hide output: '__...'? (double '_' at the beginning)
            if (isset($columnName[0]) && $columnName[0] === TOKEN_COLUMN_CTRL) {
                $flagOutput = false;
                $columnName = substr($columnName, 1);
            }
700
        }
701

702
        //TODO: reserved names,not starting with '_' will be still accepted - stop this!
703
        switch ($columnName) {
704
            case COLUMN_LINK:
Carsten  Rose's avatar
Carsten Rose committed
705
                $content .= $this->link->renderLink($columnValue);
706
                break;
707

708
709
            case COLUMN_EXEC:
                $rc = '';
710
                $content .= Support::qfqExec($columnValue, $rc);
711
712
                break;

713
714
715
716
717
718
719
720
721
            // Uppercase 'P'
            case COLUMN_PPAGE:
            case COLUMN_PPAGEC:
            case COLUMN_PPAGED:
            case COLUMN_PPAGEE:
            case COLUMN_PPAGEH:
            case COLUMN_PPAGEI:
            case COLUMN_PPAGEN:
            case COLUMN_PPAGES:
722
                $lowerColumnName = strtolower($columnName);
723
                $tokenizedValue = $this->doFixColPosPage($columnName, $columnValue);
724
725
                $linkValue = $this->doPage($lowerColumnName, $tokenizedValue);
                $content .= $this->link->renderLink($linkValue);
726
727
                break;

728
729
730
731
732
733
734
735
736
            // Lowercase 'P'
            case COLUMN_PAGE:
            case COLUMN_PAGEC:
            case COLUMN_PAGED:
            case COLUMN_PAGEE:
            case COLUMN_PAGEH:
            case COLUMN_PAGEI:
            case COLUMN_PAGEN:
            case COLUMN_PAGES:
737
                $linkValue = $this->doPage($columnName, $columnValue);
738
                $content .= $this->link->renderLink($linkValue);
739
740
                break;

741
742
743
744
            case COLUMN_PPDF:
            case COLUMN_FFILE:
            case COLUMN_ZZIP:
                $lowerColumnName = strtolower($columnName);
Carsten  Rose's avatar
Carsten Rose committed
745
                $tokenizedValue = $this->doFixColPosDownload($columnValue);
746
                $linkValue = $this->doDownload($lowerColumnName, $tokenizedValue);
Carsten  Rose's avatar
Carsten Rose committed
747
                $content .= $this->link->renderLink($linkValue);
Carsten  Rose's avatar
Carsten Rose committed
748
749
                break;

750
751
752
            case COLUMN_PDF:
            case COLUMN_FILE:
            case COLUMN_ZIP:
753
            case COLUMN_EXCEL:
754
                $linkValue = $this->doDownload($columnName, $columnValue);
Carsten  Rose's avatar
Carsten Rose committed
755
                $content .= $this->link->renderLink($linkValue);
Carsten  Rose's avatar
Carsten Rose committed
756
757
                break;

758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
            case COLUMN_SAVE_PDF:
                $tokenGiven = [];
                $vars = $this->link->fillParameter($columnValue, $tokenGiven);
                $vars[DOWNLOAD_MODE] = DOWNLOAD_MODE_PDF;
                $vars[SIP_DOWNLOAD_PARAMETER] = implode(PARAM_DELIMITER, $vars[NAME_COLLECT_ELEMENTS]);
                $download = new Download();

                // Save file with specified export filename
                $pathFileName = $vars[DOWNLOAD_EXPORT_FILENAME];
                $sanitizedFileName = Sanitize::safeFilename($pathFileName, false, true);
                if ($pathFileName !== $sanitizedFileName) {
                    throw new UserReportException("The provided filename '$pathFileName' does not meet sanitize criteria. Use '$sanitizedFileName' instead.", ERROR_INVALID_SAVE_PDF_FILENAME);
                } elseif(substr($pathFileName, 0, strlen("fileadmin/")) !== "fileadmin/") {
                    throw new UserReportException( "savePdf filenames need to be in the fileadmin/ directory for security reasons.", ERROR_INVALID_SAVE_PDF_FILENAME);
                } else {
                    $file = $download->process($vars, OUTPUT_MODE_FILE);
                    Support::copyFile($file, $pathFileName, true);
                }
                break;

778
779
780
781
            case COLUMN_THUMBNAIL:
                if ($this->thumbnail == null) {
                    $this->thumbnail = new Thumbnail();
                }
782
                $content .= $this->thumbnail->process($columnValue);
783
784
                break;

785
            case COLUMN_MONITOR:
786
                $content .= $this->link->renderLink(TOKEN_MONITOR . '|' . $columnValue);
787
788
                break;

789
            case COLUMN_MIME_TYPE:
790
                $content .= HelperFile::getMimeType($columnValue, true);
791
792
793
794
795
796
                break;

            case COLUMN_FILE_SIZE:
                $content .= filesize($columnValue);
                break;

797
798
799
800
801
802
803
804
805
806
807
808
            case COLUMN_NL2BR:
                $content .= nl2br($columnValue);
                break;

            case COLUMN_HTMLENTITIES:
                $content .= htmlentities($columnValue);
                break;

            case COLUMN_STRIPTAGS:
                $content .= strip_tags($columnValue);
                break;

Carsten  Rose's avatar
Carsten Rose committed
809
            case COLUMN_IMPORT:
Carsten  Rose's avatar
Carsten Rose committed
810
                $dataImport = new DataImport($this->db);
811
                $content = $dataImport->process($columnValue, 'test');
Carsten  Rose's avatar
Carsten Rose committed
812
                break;
813
814
815
816
            case COLUMN_EXCEL_PLAIN:
                $content .= $columnValue . PHP_EOL;
                break;
            case COLUMN_EXCEL_STRING:
817
                $content .= EXCEL_STRING . '=' . $columnValue . PHP_EOL;
818
819
                break;
            case COLUMN_EXCEL_NUMERIC:
820
                $content .= EXCEL_NUMERIC . '=' . $columnValue . PHP_EOL;
821
                break;
822

823
            case "bullet":
824
                if ($columnValue === '') {
825
                    break;
826
                }
827

Carsten  Rose's avatar
Carsten Rose committed
828
829
                // r:3|B:
                $linkValue = TOKEN_RENDER . ":3|" . TOKEN_BULLET . ":" . $columnValue;
Carsten  Rose's avatar
Carsten Rose committed
830
                $content .= $this->link->renderLink($linkValue);
831
832
833
                break;

            case "check":
834
                if ($columnValue === '') {
835
                    break;
836
                }
837

Carsten  Rose's avatar
Carsten Rose committed
838
839
                // "r:3|C:
                $linkValue = TOKEN_RENDER . ":3|" . TOKEN_CHECK . ":" . $columnValue;
Carsten  Rose's avatar
Carsten Rose committed
840
                $content .= $this->link->renderLink($linkValue);
841
842
                break;

843
844
            case "img":
                // "<path to image>|[alttext]|[text behind]" renders to: <img src="<path to image>" alt="[alttext]">[text behind]
845
                if (empty($columnValue)) {
Carsten  Rose's avatar
Carsten Rose committed
846
                    break;
847
                }
Carsten  Rose's avatar
Carsten Rose committed
848

849
                $mailConfig = explode("|", $columnValue, 3);
850
851

                // Fake values for tmp[1], tmp[2] to suppress access errors.
852
853
                $mailConfig[] = '';
                $mailConfig[] = '';
854

855
                if (empty($mailConfig[0])) {
856
                    break;
857
                }
858
859
                $attribute = Support::doAttribute('src', $mailConfig[0]);
                $attribute .= Support::doAttribute('alt', $mailConfig[1]);
860

861
                $content .= '<img ' . $attribute . '>' . $mailConfig[2];
862
863
864
865
                break;

            case "mailto":
                // "<email address>|[Real Name]"  renders to (encrypted via JS): <a href="mailto://<email address>"><email address></a> OR <a href="mailto://<email address>">[Real Name]</a>
866
867
                $mailConfig = explode("|", $columnValue, 2);
                if (empty($mailConfig[0])) {
868
                    break;
869
                }
870

871
                $t1 = explode("@", $mailConfig[0], 2);
872
                $content .= "<script language=javascript><!--" . chr(10);
873
874
                if (empty($mailConfig[1])) {
                    $mailConfig[1] = $mailConfig[0];
875
                }
876

877
878
                $content .= 'var contact = "' . substr($mailConfig[1], 0, 2) . '"' . chr(10);
                $content .= 'var contact1 = "' . substr($mailConfig[1], 2) . '"' . chr(10);
879
880
881
882
883
884
885
886
887
                $content .= 'var email = "' . $t1[0] . '"' . chr(10);
                $content .= 'var emailHost = "' . $t1[1] . '"' . chr(10);

                $content .= 'document.write("<a href=" + "mail" + "to:" + email + "@" + emailHost+ ">" + contact + "</a>")' . chr(10);
                $content .= 'document.write("<a href=" + "mail" + "to:" + email + "@" + emailHost+ ">" + contact1 + "</a>")' . chr(10);
                $content .= '//--></script>';
                break;

            case "sendmail":
888
889
                $sendMail = new SendMail();
                $mailConfig = $sendMail->parseStringToArray($columnValue);
890
                if (count($mailConfig) < 4) {
891
                    throw new UserReportException ("Too few parameter for sendmail: $columnValue", ERROR_TOO_FEW_PARAMETER_FOR_SENDMAIL);
892
                }
893

894
                $mailConfig[SENDMAIL_TOKEN_SRC] = "Report: T3 pageId=" . $this->store->getVar('pageId', STORE_TYPO3) .
895
896
                    ", T3 ttcontentId=" . $this->store->getVar('ttcontentUid', STORE_TYPO3) .
                    ", Level=" . $full_level;
897

898
899
                $sendMail->process($mailConfig);

900
901
902
                break;

            case "vertical":
903
                // '<Text>|[angle]|[width]|[height]|[tag]'   , width and height needs a unit (px, em ,...), 'tag' might be 'div', 'span', ...
904
905
906
907
908
909
910
                $arr = explode("|", $columnValue, 5);

                # angle
                $angle = $arr[1] ? $arr[1] : 270;

                # width
                $width = $arr[2] ? $arr[2] : "1em";
911
                $extra = "width:$width; ";
912
913

                # height
914
                if ($arr[3]) {
915
                    $extra .= "height:" . $arr[3] . "; ";
916
                }
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936

                # tag
                if ($arr[4]) {
                    $tag = explode(" ", trim($arr[4]), 2);
                    $tag_open = "<" . $arr[4] . " ";
                    $tag_close = "</" . $tag[0] . ">";
                } else {
                    $tag_open = "<div ";
                    $tag_close = "</div>";
                }

                # http://scottgale.com/blog/css-vertical-text/2010/03/01/
                #$style = "writing-mode:tb-rl; -webkit-transform:rotate(270deg); -moz-transform:rotate(270deg); -o-transform: rotate(270deg); white-space:nowrap; width:1em;  display: block;";
                $style = "width:1em;  filter: flipv fliph; transform: rotate(" . $angle . "deg) translate(-10em,0); transform-origin: 0 0; writing-mode:tb-rl; -webkit-transform:rotate(" . $angle . "deg); -moz-transform:rotate(" . $angle . "deg); -o-transform: rotate(" . $angle . "deg); white-space:nowrap; display: block;";

                #$style = "line-height: 1.5em; background:#eee; display: block; white-space: nowrap; padding-left: 3px; writing-mode: tb-rl; filter: flipv fliph; transform: rotate(270deg) translate(-10em,0); transform-origin: 0 0; -moz-transform: rotate(270deg) translate(-10em,0); -moz-transform-origin: 0 0; -webkit-transform: rotate(270deg) translate(-10em,0); -webkit-transform-origin: 0 0;";
                $content = $tag_open . 'style="' . $style . '">' . $arr[0] . $tag_close;
                break;

            default :
937
                if ($flagControl) {
938
939
940
941
942
943
944

                    // Columnnames starting with '+' will wrap the content. EG "SELECT 'apple' AS '+h1 class=best'" >> <h1 class=best>apple</h1>
                    if (isset($columnName[0]) && $columnName[0] == COLUMN_WRAP_TOKEN && isset($columnName[1])) {
                        $content = Support::wrapTag('<' . substr($columnName, 1) . '>', $columnValue);
                        break;
                    }

945
946
                    $flagOutput = false;
                } else {
947
                    // No special Columnname: just add the column value.
948
949
                    $content .= $columnValue;
                }
950
951
                break;
        }
952

953
954
        // Always save column values, even if they are hidden.
        $this->variables->resultArray[$full_level . "."][$columnName] = ($content == '' && $flagControl) ? $columnValue : $content;
955
956

        return $content;
957
    }
958
959
960
961
962
963
964
965
966
967

    /**
     * Renders PageX: convert position content to token content. Respect default values depending on PageX
     *
     * @param    string $columnName
     * @param    string $columnValue
     * @return string rendered link
     *
     * $columnValue:
     * -------------
968
     * [<page id|alias>[&param=value&...]] | [text] | [tooltip] | [msgbox] | [class] | [target] | [render mode]
969
     *
970
     * param[0]: <page id|alias>[&param=value&...]
971