Report.php 44.9 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');
Carsten  Rose's avatar
Carsten Rose committed
39
require_once(__DIR__ . '/Monitor.php');
40

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

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

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

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

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

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

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

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

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

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

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

92
    private $dbIndexData = false;
93

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

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

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

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

115
116
    private $phpUnit = false;

117
    private $showDebugInfoFlag = false;
118

119
120
    private $flagFillStoreRecord = true;

121
    /**
122
     * Report constructor.
123
     *
Carsten  Rose's avatar
Carsten Rose committed
124
     * @param array $t3data
125
     * @param Evaluate $eval
Carsten  Rose's avatar
Carsten Rose committed
126
     * @param bool $phpUnit
127
     * @throws CodeException
128
     * @throws DbException
129
     * @throws UserFormException
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
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
     */
    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);
        }
    }

209
210
211
    /**
     * Main function. Parses bodytext and iterates over all queries.
     *
212
213
     * @param $bodyText
     * @param bool $flagFillStoreRecord
214
     * @return string
215
216
217
218
     * @throws CodeException
     * @throws DbException
     * @throws UserFormException
     * @throws UserReportException
219
     */
220
    public function process($bodyText, $flagFillStoreRecord = true) {
221

222
223
224
225
        //phpUnit Test: clean environment
        $this->frArray = array();
        $this->indexArray = array();
        $this->levelCount = 0;
226
        $this->flagFillStoreRecord = $flagFillStoreRecord;
227

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

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

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

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

242
        return $content;
243
    }
244
245
246
247
248
249

    /**
     * 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
250
     *
251
     * @throws UserReportException
252
     */
253
    private function parseLine($ttLine) {
254
255
256
257

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

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

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

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

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

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

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

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

285
286
287
288
289
290
291
292
293
294
295
        // 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);
        }
296
    }
297

298
299
300
301
302
    /**
     * @param $level
     * @param $frCmd
     * @param $value
     */
303
304
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
        //pro sql cmd wir der Indexarray abgefüllt. Dieser wird später verwendet um auf den $frArray zuzugreifen
        //if(preg_match("/^sql/i", $frCmd) == 1){
311
312
//        if ($frCmd === TOKEN_FORM || $frCmd === TOKEN_SQL) {
        if ($frCmd === TOKEN_SQL) {
313
314
315
316
317
            // Remember max level
            $this->levelCount = max(substr_count($level, '.') + 1, $this->levelCount);
            // $indexArray[10][50][5]
            $this->indexArray[] = explode(".", $level);
        }
318
319
320

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

    /**
     * Sorts the associative array.
     *
Carsten  Rose's avatar
Carsten Rose committed
333
     * @param array $ary : The unsorted Level Array
334
     * @param string $clause : the sort argument 0 ASC, 1 ASC... according to the number of columns
335
     * @param bool|true $ascending
336
     */
337
    private function sortIndexArray(array &$ary, $clause, $ascending = true) {
338
339
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

        $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);
382
383
384
385
386

            // 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);

387
            usort($ary, $sortFn);
388
389

            error_reporting($errorSet);
390
        }
391
    }
392

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

        $sortArg = "";

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

406
        $sortArg = substr($sortArg, 0, strlen($sortArg) - 2);
407

408
        return $sortArg;
409
    }
410
411

    /**
412
     * Executes the queries recursive. This Method is called for each sublevel.
413
414
415
416
417
418
     *
     * ROOTLEVEL
     * This method is called once from the main method.
     * For the first call the method executes the rootlevels
     *
     * SUBLEVEL
419
     * For each rootlevel the method calls it self with the level mode 0
420
421
422
423
     * 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 ...
424
     * @param array $super_level_array The Value-Array of the indexarray [0=>10, 1=>50]
Carsten  Rose's avatar
Carsten Rose committed
425
     * @param int $counter The outer numeric Arraykey of indexarray
Carsten  Rose's avatar
Carsten Rose committed
426
     *
427
     * @return string                       The content that is displayed on the website
428
429
430
     * @throws CodeException
     * @throws DbException
     * @throws UserFormException
431
432
433
     * @throws UserReportException
     */

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

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

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

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

        //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])) {
462
463
464
465
466
467
                ++$counter;
                if (isset($this->indexArray[$counter]) && is_array($this->indexArray[$counter])) {
                    $full_level = implode(".", $this->indexArray[$counter]);
                } else {
                    $full_level = '';
                }
468
469
470
471
                continue;
            }

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

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

478
479
480
            // 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"]);
481

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

484
            $this->store->setVar(SYSTEM_SQL_FINAL, $sql, STORE_SYSTEM);
485
486
487

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

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

            $this->variables->resultArray[$full_level . ".line."]["total"] = $rowTotal;
496
497
            $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;
498

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


506
            if (is_array($result)) {
507
508
509
510
511
512
513
514

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

515
516
                //---------------------------------
                // Process each row of resultset
517
                $columnValueSeparator = "";
518
519
520
521
522
523
524
525
526
527
                $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]);
                    }
528

529
530
531
532
                    // 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
533
                    // RBEG
534
                    $rbeg = $this->variables->doVariables($this->frArray[$full_level . "." . TOKEN_RBEG]);
535

536
                    // RBGD: even/odd rows
537
                    $content .= str_replace(TOKEN_RBGD, $arrRbgd[$rowIndex % 2], $rbeg);
538

Carsten  Rose's avatar
Carsten Rose committed
539
540
541
542
                    //-----------------------------
                    // COLUMNS: Collect all columns
                    $content .= $this->collectRow($row, $keys, $full_level, $rowIndex);

543
                    // REND
544
                    $content .= $this->variables->doVariables($this->frArray[$full_level . "." . TOKEN_REND]);
545

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

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

            if ($rowTotal > 0) {
555
                // tail
556
                $content .= $this->variables->doVariables($this->frArray[$full_level . "." . TOKEN_TAIL]);
557
            } else {
558
                // althead
559
                $content .= $this->variables->doVariables($this->frArray[$full_level . "." . TOKEN_ALT_HEAD]);
560
561
562
563
564
565
566
567
568
                // 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);
                    }
                }
569
            }
570
            $content .= $this->variables->doVariables($this->frArray[$full_level . "." . TOKEN_STAIL]);
571

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

        return $content;
581
    }
582
583
584
585
586
587
588
589

    /**
     * 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
590
     * @param    string $level_key - 'db' or 'debug'
591
592
     * @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
593
594
     * @param    string $cur_level - f.e.: 2
     * @param    string $default - f.e.: 0
Carsten  Rose's avatar
Carsten Rose committed
595
     *
596
597
598
599
     * @return   string  The calculated value.
     */
    private function getValueParentDefault($level_key, $full_super_level, $full_level, $cur_level, $default) {

600
        if (!empty($this->frArray[$full_level . "." . $level_key])) {
601
602
603
604
605
606
607
608
609
610
611
            $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);
612
    }
613
614
615
616

    /**
     * Steps through 'row' and collects all columns
     *
Carsten  Rose's avatar
Carsten Rose committed
617
618
     * @param array $row Recent row fetch from sql resultset.
     * @param array $keys List of all columnnames
619
620
     * @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
621
     *
622
     * @return string               Collected content of all printable columns
623
624
     * @throws CodeException
     * @throws DbException
625
     * @throws DownloadException
626
627
     * @throws UserFormException
     * @throws UserReportException
628
629
630
     */
    private function collectRow(array $row, array $keys, $full_level, $rowIndex) {
        $content = "";
631
        $assoc = array();
632

633
        $fsep = '';
634
635
        for ($ii = 0; $ii < count($keys); $ii++) {

636
            // Debugging
637
638
639
            $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);
640

641
642
            $flagOutput = false;
            $renderedColumn = $this->renderColumn($ii, $keys[$ii], $row[$ii], $full_level, $rowIndex, $flagOutput);
643

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

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

660
        if ($this->flagFillStoreRecord) {
661
            $this->store->appendToStore($assoc, STORE_RECORD);
662
        }
663

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

    /**
     * Renders column depending of column name (if name is a reserved column name)
     *
670
671
672
673
674
     * @param string $columnIndex
     * @param string $columnName
     * @param string $columnValue
     * @param string $full_level
     * @param string $rowIndex
675
     * @param $flagOutput
Carsten  Rose's avatar
Carsten Rose committed
676
     *
677
     * @return string rendered column
678
     * @throws CodeException
Carsten  Rose's avatar
Carsten Rose committed
679
     * @throws DbException
680
     * @throws DownloadException
681
682
     * @throws UserFormException
     * @throws UserReportException
683
     */
684
    private function renderColumn($columnIndex, $columnName, $columnValue, $full_level, $rowIndex, &$flagOutput) {
685
        $content = "";
686
687
        $flagControl = false;
        $flagOutput = true;
Carsten  Rose's avatar
Carsten Rose committed
688
        $dummy = false;
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
753
            case COLUMN_PDF:
            case COLUMN_FILE:
            case COLUMN_ZIP:
                $linkValue = $this->doDownload($columnName, $columnValue);
Carsten  Rose's avatar
Carsten Rose committed
754
                $content .= $this->link->renderLink($linkValue);
Carsten  Rose's avatar
Carsten Rose committed
755
756
                break;

757
758
759
760
            case COLUMN_THUMBNAIL:
                if ($this->thumbnail == null) {
                    $this->thumbnail = new Thumbnail();
                }
761
                $content .= $this->thumbnail->process($columnValue);
762
763
                break;

764
765
766
767
768
769
770
            case COLUMN_MONITOR:
                if ($this->monitor == null) {
                    $this->monitor = new Monitor();
                }
                $content .= $this->monitor->process($columnValue);
                break;

771
            case COLUMN_MIME_TYPE:
772
                $content .= HelperFile::getMimeType($columnValue, true);
773
774
775
776
777
778
                break;

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

779
780
781
782
783
784
785
786
787
788
789
790
791
            case COLUMN_NL2BR:
                $content .= nl2br($columnValue);
                break;

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

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


792
            case "bullet":
793
                if ($columnValue === '') {
794
                    break;
795
                }
796

Carsten  Rose's avatar
Carsten Rose committed
797
798
                // r:3|B:
                $linkValue = TOKEN_RENDER . ":3|" . TOKEN_BULLET . ":" . $columnValue;
Carsten  Rose's avatar
Carsten Rose committed
799
                $content .= $this->link->renderLink($linkValue);
800
801
802
                break;

            case "check":
803
                if ($columnValue === '') {
804
                    break;
805
                }
806

Carsten  Rose's avatar
Carsten Rose committed
807
808
                // "r:3|C:
                $linkValue = TOKEN_RENDER . ":3|" . TOKEN_CHECK . ":" . $columnValue;
Carsten  Rose's avatar
Carsten Rose committed
809
                $content .= $this->link->renderLink($linkValue);
810
811
                break;

812
813
            case "img":
                // "<path to image>|[alttext]|[text behind]" renders to: <img src="<path to image>" alt="[alttext]">[text behind]
814
                if (empty($columnValue)) {
Carsten  Rose's avatar
Carsten Rose committed
815
                    break;
816
                }
Carsten  Rose's avatar
Carsten Rose committed
817

818
                $mailConfig = explode("|", $columnValue, 3);
819
820

                // Fake values for tmp[1], tmp[2] to suppress access errors.
821
822
                $mailConfig[] = '';
                $mailConfig[] = '';
823

824
                if (empty($mailConfig[0])) {
825
                    break;
826
                }
827
828
                $attribute = Support::doAttribute('src', $mailConfig[0]);
                $attribute .= Support::doAttribute('alt', $mailConfig[1]);
829

830
                $content .= '<img ' . $attribute . '>' . $mailConfig[2];
831
832
833
834
                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>
835
836
                $mailConfig = explode("|", $columnValue, 2);
                if (empty($mailConfig[0])) {
837
                    break;
838
                }
839

840
                $t1 = explode("@", $mailConfig[0], 2);
841
                $content .= "<script language=javascript><!--" . chr(10);
842
843
                if (empty($mailConfig[1])) {
                    $mailConfig[1] = $mailConfig[0];
844
                }
845

846
847
                $content .= 'var contact = "' . substr($mailConfig[1], 0, 2) . '"' . chr(10);
                $content .= 'var contact1 = "' . substr($mailConfig[1], 2) . '"' . chr(10);
848
849
850
851
852
853
854
855
856
                $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":
857
858
                $sendMail = new SendMail();
                $mailConfig = $sendMail->parseStringToArray($columnValue);
859
                if (count($mailConfig) < 4) {
860
                    throw new UserReportException ("Too few parameter for sendmail: $columnValue", ERROR_TOO_FEW_PARAMETER_FOR_SENDMAIL);
861
                }
862

863
                $mailConfig[SENDMAIL_TOKEN_SRC] = "Report: T3 pageId=" . $this->store->getVar('pageId', STORE_TYPO3) .
864
865
                    ", T3 ttcontentId=" . $this->store->getVar('ttcontentUid', STORE_TYPO3) .
                    ", Level=" . $full_level;
866

867
868
                $sendMail->process($mailConfig);

869
870
871
                break;

            case "vertical":
872
                // '<Text>|[angle]|[width]|[height]|[tag]'   , width and height needs a unit (px, em ,...), 'tag' might be 'div', 'span', ...
873
874
875
876
877
878
879
                $arr = explode("|", $columnValue, 5);

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

                # width
                $width = $arr[2] ? $arr[2] : "1em";
880
                $extra = "width:$width; ";
881
882

                # height
883
                if ($arr[3]) {
884
                    $extra .= "height:" . $arr[3] . "; ";
885
                }
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905

                # 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 :
906
                if ($flagControl) {
907
908
909
910
911
912
913

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

914
915
                    $flagOutput = false;
                } else {
916
                    // No special Columnname: just add the column value.
917
918
                    $content .= $columnValue;
                }
919
920
                break;
        }
921

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

        return $content;
926
    }
927
928
929
930
931
932
933
934
935
936

    /**
     * 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:
     * -------------
937
     * [<page id|alias>[&param=value&...]] | [text] | [tooltip] | [msgbox] | [class] | [target] | [render mode]
938
     *
939
     * param[0]: <page id|alias>[&param=value&...]
940
941
942
943
944
945
     * param[2]: text
     * param[3]: tooltip
     * param[4]: msgbox
     * param[5]: class
     * param[6]: target
     * param[7]: render mode
946
     * @throws UserReportException
947
948
949
     */
    private function doFixColPosPage($columnName, $columnValue) {

Carsten  Rose's avatar
Carsten Rose committed
950
        $tokenList = "";