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');
39

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

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

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

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

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

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

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

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

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

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

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

91
    private $dbIndexData = false;
92
93
94
95
96
    /**
     * @var Thumbnail
     */
    private $thumbnail = null;

97
98
99
100
    /**
     * @var array
     */
    private $pageDefaults = array();
101

102
    /**
Carsten  Rose's avatar
Carsten Rose committed
103
104
     * @var array - Emulate global variable: will be set much earlier in other functions. Will be shown in error
     *      messages.
105
     */
106
107
    private $fr_error = array('uid' => '', 'pid' => '', 'row' => '', 'debug_level' => '0', 'full_level' => '');

108
109
    private $phpUnit = false;

110
    private $showDebugInfoFlag = false;
111

112
113
    private $flagFillStoreRecord = true;

114
    /**
115
     * Report constructor.
116
     *
Carsten  Rose's avatar
Carsten Rose committed
117
     * @param array $t3data
118
     * @param Evaluate $eval
Carsten  Rose's avatar
Carsten Rose committed
119
     * @param bool $phpUnit
120
121
     * @throws CodeException
     * @throws UserFormException
122
     */
123
    public function __construct(array $t3data, Evaluate $eval, $phpUnit = false) {
124
125

        $this->phpUnit = $phpUnit;
126

127
128
        Support::setIfNotSet($t3data, "uid", 0);

129
        $this->sip = new Sip($phpUnit);
130
131
        if ($phpUnit) {
            $this->sip->sipUniqId('badcaffee1234');
132
133
            //TODO Webserver Umgebung besser faken
            $_SERVER['REQUEST_URI'] = 'localhost';
134
135
        }

136
        $this->store = Store::getInstance();
137

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

140
141
        $this->checkUpdateSqlLog();

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

145
146
147
148
149
150
        $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;
151

152
153
154
155
156
157
        $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;

158
        // Default should already set in QuickFormQuery() Constructor
Carsten  Rose's avatar
Carsten Rose committed
159
160
161
162
        $this->dbIndexData = $this->store->getVar(TOKEN_DB_INDEX, STORE_TYPO3);
        if ($this->dbIndexData === false) {
            $this->dbIndexData = DB_INDEX_DEFAULT;
        }
163

164
        $this->db = new Database($this->dbIndexData);
165
        $this->variables = new Variables($eval, $t3data["uid"]);
166

167
168
        $this->link = new Link($this->sip, $this->dbIndexData, $phpUnit);

169
        // Set static values, which won't change during this run.
170
        $this->fr_error["pid"] = isset($this->variables->resultArray['global.']['page_id']) ? $this->variables->resultArray['global.']['page_id'] : 0;
171
        $this->fr_error["uid"] = $t3data['uid'];
172
        $this->fr_error["debug_level"] = 0;
173

Carsten  Rose's avatar
Carsten Rose committed
174
        // Sanitize function for POST and GET Parameters.
175
        // Merged URL-Parameter (key1, id etc...) in resultArray.
176
        $this->variables->resultArray = array_merge($this->variables->resultArray, array("global." => $this->variables->collectGlobalVariables()));
177
178
179

    }

180
    /**
Carsten  Rose's avatar
Carsten Rose committed
181
182
     * If a variable 'sqlLog' is given in STORE_TYPO3 (=Bodytext) make them relative to SYSTEM_PATH_EXT and copy it to
     * STORE_SYSTEM
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
     */
    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);
        }
    }

201
202
203
    /**
     * Main function. Parses bodytext and iterates over all queries.
     *
204
205
     * @param $bodyText
     * @param bool $flagFillStoreRecord
206
     * @return string
207
208
209
210
     * @throws CodeException
     * @throws DbException
     * @throws UserFormException
     * @throws UserReportException
211
     */
212
    public function process($bodyText, $flagFillStoreRecord = true) {
213

214
215
216
217
        //phpUnit Test: clean environment
        $this->frArray = array();
        $this->indexArray = array();
        $this->levelCount = 0;
218
        $this->flagFillStoreRecord = $flagFillStoreRecord;
219

220
        // Iteration over Bodytext
221
        $ttLineArray = explode("\n", $bodyText);
222

223
224
        foreach ($ttLineArray as $index => $line) {
            // Fill $frArray, $indexArray, $resultArray
225
            $this->parseLine($line);
226
        }
227

228
229
230
231
232
233
        // Sort array
        $this->sortIndexArray($this->indexArray, $this->generateSortArg());

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

234
        return $content;
235
    }
236
237
238
239
240
241

    /**
     * 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
242
     *
243
     * @throws UserReportException
244
     */
245
    private function parseLine($ttLine) {
246
247
248
249

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

250
        // no elements or only one: do nothing
251
        if (count($arr) < 2) {
252
            return;
253
        }
254

255
256
257
258
        // 10.50.5.sql
        $key = strtolower(trim($arr[0]));

        // comment ?
259
        if (empty($key) || $key[0] === "#") {
260
            return;
261
        }
262

263
        // select ... - if needed, trim surrounding single ticks
264
        $value = trim($arr[1]);
265
        $value = OnString::trimQuote($value);
266
267
268
269
270
271
272

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

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

273
        if (strpos('|' . strtolower(TOKEN_VALID_LIST) . '|', '|' . $frCmd . '|') === false) {
274
            throw new UserReportException ("Unknown token: $frCmd in Line '$ttLine''", ERROR_UNKNOWN_TOKEN);
275
276
        }

277
278
279
280
281
282
283
284
285
286
287
        // 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);
        }
288
    }
289

290
291
292
293
294
    /**
     * @param $level
     * @param $frCmd
     * @param $value
     */
295
296
297
298
299
300
301
302
    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){
303
304
//        if ($frCmd === TOKEN_FORM || $frCmd === TOKEN_SQL) {
        if ($frCmd === TOKEN_SQL) {
305
306
307
308
309
            // Remember max level
            $this->levelCount = max(substr_count($level, '.') + 1, $this->levelCount);
            // $indexArray[10][50][5]
            $this->indexArray[] = explode(".", $level);
        }
310
311
312

        // set defaults
        if ($frCmd === TOKEN_SQL) {
313
314
            $arr = explode('|', TOKEN_VALID_LIST);
            foreach ($arr as $key) {
315
                if (!isset($this->frArray[$level . "." . $key])) {
316
                    $this->frArray[$level . "." . $key] = '';
317
                }
318
319
            }
        }
320
    }
321
322
323
324

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

        $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);
374
375
376
377
378

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

379
            usort($ary, $sortFn);
380
381

            error_reporting($errorSet);
382
        }
383
    }
384

385
386
387
388
389
    /**
     * generateSortArg
     *
     * @return string
     */
390
391
392
393
394
395
396
    private function generateSortArg() {

        $sortArg = "";

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

398
        $sortArg = substr($sortArg, 0, strlen($sortArg) - 2);
399

400
        return $sortArg;
401
    }
402
403

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

426
427
    private function triggerReport($cur_level = 1, array $super_level_array = array(), $counter = 0) {
        $keys = array();
428
        $stat = array();
429
430
431
432
433
434

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

        // CurrentLevel "10.10.50"
435
436
437
438
439
440
        if (isset($this->indexArray[$counter]) && is_array($this->indexArray[$counter])) {
            $full_level = implode(".", $this->indexArray[$counter]);
        } else {
            $full_level = '';
        }

441
        // Superlevel "10.10"
442
443
444
445
446
        if (isset($super_level_array) && is_array($super_level_array)) {
            $full_super_level = implode(".", $super_level_array);
        } else {
            $full_super_level = '';
        }
447
448
449
450
451
452
453

        //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])) {
454
455
456
457
458
459
                ++$counter;
                if (isset($this->indexArray[$counter]) && is_array($this->indexArray[$counter])) {
                    $full_level = implode(".", $this->indexArray[$counter]);
                } else {
                    $full_level = '';
                }
460
461
462
463
                continue;
            }

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

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

470
471
472
            // 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"]);
473

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

476
            $this->store->setVar(SYSTEM_SQL_FINAL, $sql, STORE_SYSTEM);
477
478
479

            //Execute SQL. All errors have been already catched.
            unset($result);
480
            $result = $this->db->sql($sql, ROW_KEYS, array(), '', $keys, $stat);
481
482
483

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

            $this->variables->resultArray[$full_level . ".line."]["total"] = $rowTotal;
488
489
            $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;
490

491
            $content .= $this->variables->doVariables($this->frArray[$full_level . "." . TOKEN_SHEAD]);
492
            // HEAD: If there is at least one record, do 'head'.
493
            if ($rowTotal > 0) {
494
                $content .= $this->variables->doVariables($this->frArray[$full_level . "." . TOKEN_HEAD]);
495
            }
496
497


498
            if (is_array($result)) {
499
500
501
502
503
504
505
506

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

507
508
                //---------------------------------
                // Process each row of resultset
509
                $columnValueSeparator = "";
510
511
512
513
514
515
516
517
518
519
                $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]);
                    }
520

521
522
523
524
                    // 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
525
                    // RBEG
526
                    $rbeg = $this->variables->doVariables($this->frArray[$full_level . "." . TOKEN_RBEG]);
527

528
                    // RBGD: even/odd rows
529
                    $content .= str_replace(TOKEN_RBGD, $arrRbgd[$rowIndex % 2], $rbeg);
530

Carsten  Rose's avatar
Carsten Rose committed
531
532
533
534
                    //-----------------------------
                    // COLUMNS: Collect all columns
                    $content .= $this->collectRow($row, $keys, $full_level, $rowIndex);

535
                    // REND
536
                    $content .= $this->variables->doVariables($this->frArray[$full_level . "." . TOKEN_REND]);
537

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

541
                    // RENR
542
                    $content .= $this->variables->doVariables($this->frArray[$full_level . "." . TOKEN_RENR]);
543
                }
544
545
546
            }

            if ($rowTotal > 0) {
547
                // tail
548
                $content .= $this->variables->doVariables($this->frArray[$full_level . "." . TOKEN_TAIL]);
549
            } else {
550
                // althead
551
                $content .= $this->variables->doVariables($this->frArray[$full_level . "." . TOKEN_ALT_HEAD]);
552
553
554
555
556
557
558
559
560
                // 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);
                    }
                }
561
            }
562
            $content .= $this->variables->doVariables($this->frArray[$full_level . "." . TOKEN_STAIL]);
563

564
565
566
567
568
569
            ++$counter;
            if (isset($this->indexArray[$counter]) && is_array($this->indexArray[$counter])) {
                $full_level = implode(".", $this->indexArray[$counter]);
            } else {
                $full_level = '';
            }
570
571
572
        }

        return $content;
573
    }
574
575
576
577
578
579
580
581

    /**
     * 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
582
     * @param    string $level_key - 'db' or 'debug'
583
584
     * @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
585
586
     * @param    string $cur_level - f.e.: 2
     * @param    string $default - f.e.: 0
Carsten  Rose's avatar
Carsten Rose committed
587
     *
588
589
590
591
     * @return   string  The calculated value.
     */
    private function getValueParentDefault($level_key, $full_super_level, $full_level, $cur_level, $default) {

592
        if (!empty($this->frArray[$full_level . "." . $level_key])) {
593
594
595
596
597
598
599
600
601
602
603
            $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);
604
    }
605
606
607
608

    /**
     * Steps through 'row' and collects all columns
     *
Carsten  Rose's avatar
Carsten Rose committed
609
610
     * @param array $row Recent row fetch from sql resultset.
     * @param array $keys List of all columnnames
611
612
     * @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
613
     *
614
     * @return string               Collected content of all printable columns
615
616
617
618
     * @throws CodeException
     * @throws DbException
     * @throws UserFormException
     * @throws UserReportException
619
620
621
     */
    private function collectRow(array $row, array $keys, $full_level, $rowIndex) {
        $content = "";
622
        $assoc = array();
623

624
        $fsep = '';
625
626
        for ($ii = 0; $ii < count($keys); $ii++) {

627
            // Debugging
628
629
630
            $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);
631

632
633
            $flagOutput = false;
            $renderedColumn = $this->renderColumn($ii, $keys[$ii], $row[$ii], $full_level, $rowIndex, $flagOutput);
634

635
            $keyAssoc = OnString::stripFirstCharIf(TOKEN_COLUMN_CTRL, $keys[$ii]);
636
637
            if ($keyAssoc != '') {
                $assoc[$keyAssoc] = $row[$ii];
638
                $assoc[REPORT_TOKEN_FINAL_VALUE . $keyAssoc] = $renderedColumn;
639
640
            }

641
            if ($flagOutput) {
642
                //prints
643
                $content .= $this->variables->doVariables($fsep);
644
                $content .= $this->variables->doVariables($this->frArray[$full_level . "." . TOKEN_FBEG]);
645
                $content .= $renderedColumn;
646
647
                $content .= $this->variables->doVariables($this->frArray[$full_level . "." . TOKEN_FEND]);
                $fsep = $this->frArray[$full_level . "." . TOKEN_FSEP];
648
649
            }
        }
Carsten  Rose's avatar
Carsten Rose committed
650

651
        if ($this->flagFillStoreRecord) {
652
            $this->store->appendToStore($assoc, STORE_RECORD);
653
        }
654

655
        return ($content);
656
    }
657
658
659
660

    /**
     * Renders column depending of column name (if name is a reserved column name)
     *
661
662
663
664
665
     * @param string $columnIndex
     * @param string $columnName
     * @param string $columnValue
     * @param string $full_level
     * @param string $rowIndex
666
     * @param $flagOutput
Carsten  Rose's avatar
Carsten Rose committed
667
     *
668
     * @return string rendered column
669
     * @throws CodeException
Carsten  Rose's avatar
Carsten Rose committed
670
     * @throws DbException
671
672
     * @throws UserFormException
     * @throws UserReportException
673
     */
674
    private function renderColumn($columnIndex, $columnName, $columnValue, $full_level, $rowIndex, &$flagOutput) {
675
        $content = "";
676
677
        $flagControl = false;
        $flagOutput = true;
Carsten  Rose's avatar
Carsten Rose committed
678
        $dummy = false;
679

680
        // Special column name:  '_...'? Empty column names are allowed: check with isset
681
        if (isset($columnName[0]) && $columnName[0] === TOKEN_COLUMN_CTRL) {
682
            $flagControl = true;
683
            $columnName = substr($columnName, 1);
684
685
686
687
688
689

            // 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);
            }
690
        }
691

692
        //TODO: reserved names,not starting with '_' will be still accepted - stop this!
693
        switch ($columnName) {
694
            case COLUMN_LINK:
Carsten  Rose's avatar
Carsten Rose committed
695
                $content .= $this->link->renderLink($columnValue);
696
                break;
697

698
699
            case COLUMN_EXEC:
                $rc = '';
700
                $content .= Support::qfqExec($columnValue, $rc);
701
702
                break;

703
704
705
706
707
708
709
710
711
            // 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:
712
            $lowerColumnName = strtolower($columnName);
713
                $tokenizedValue = $this->doFixColPosPage($columnName, $columnValue);
714
            $linkValue = $this->doPage($lowerColumnName, $tokenizedValue);
Carsten  Rose's avatar
Carsten Rose committed
715
            $content .= $this->link->renderLink($linkValue);
716
717
                break;

718
719
720
721
722
723
724
725
726
            // 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:
727
                $linkValue = $this->doPage($columnName, $columnValue);
Carsten  Rose's avatar
Carsten Rose committed
728
            $content .= $this->link->renderLink($linkValue);
729
730
                break;

731
732
733
734
            case COLUMN_PPDF:
            case COLUMN_FFILE:
            case COLUMN_ZZIP:
                $lowerColumnName = strtolower($columnName);
Carsten  Rose's avatar
Carsten Rose committed
735
                $tokenizedValue = $this->doFixColPosDownload($columnValue);
736
                $linkValue = $this->doDownload($lowerColumnName, $tokenizedValue);
Carsten  Rose's avatar
Carsten Rose committed
737
                $content .= $this->link->renderLink($linkValue);
Carsten  Rose's avatar
Carsten Rose committed
738
739
                break;

740
741
742
743
            case COLUMN_PDF:
            case COLUMN_FILE:
            case COLUMN_ZIP:
                $linkValue = $this->doDownload($columnName, $columnValue);
Carsten  Rose's avatar
Carsten Rose committed
744
                $content .= $this->link->renderLink($linkValue);
Carsten  Rose's avatar
Carsten Rose committed
745
746
                break;

747
748
749
750
            case COLUMN_THUMBNAIL:
                if ($this->thumbnail == null) {
                    $this->thumbnail = new Thumbnail();
                }
751
                $content .= $this->thumbnail->process($columnValue);
752
753
                break;

754
            case COLUMN_MIME_TYPE:
755
                $content .= HelperFile::getMimeType($columnValue, true);
756
757
758
759
760
761
                break;

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

762
763
764
765
766
767
768
769
770
771
772
773
774
            case COLUMN_NL2BR:
                $content .= nl2br($columnValue);
                break;

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

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


775
            case "bullet":
776
                if ($columnValue === '') {
777
                    break;
778
                }
779

Carsten  Rose's avatar
Carsten Rose committed
780
781
                // r:3|B:
                $linkValue = TOKEN_RENDER . ":3|" . TOKEN_BULLET . ":" . $columnValue;
Carsten  Rose's avatar
Carsten Rose committed
782
                $content .= $this->link->renderLink($linkValue);
783
784
785
                break;

            case "check":
786
                if ($columnValue === '') {
787
                    break;
788
                }
789

Carsten  Rose's avatar
Carsten Rose committed
790
791
                // "r:3|C:
                $linkValue = TOKEN_RENDER . ":3|" . TOKEN_CHECK . ":" . $columnValue;
Carsten  Rose's avatar
Carsten Rose committed
792
                $content .= $this->link->renderLink($linkValue);
793
794
                break;

795
796
            case "img":
                // "<path to image>|[alttext]|[text behind]" renders to: <img src="<path to image>" alt="[alttext]">[text behind]
797
                if (empty($columnValue)) {
Carsten  Rose's avatar
Carsten Rose committed
798
                    break;
799
                }
Carsten  Rose's avatar
Carsten Rose committed
800

801
                $mailConfig = explode("|", $columnValue, 3);
802
803

                // Fake values for tmp[1], tmp[2] to suppress access errors.
804
805
                $mailConfig[] = '';
                $mailConfig[] = '';
806

807
                if (empty($mailConfig[0])) {
808
                    break;
809
                }
810
811
                $attribute = Support::doAttribute('src', $mailConfig[0]);
                $attribute .= Support::doAttribute('alt', $mailConfig[1]);
812

813
                $content .= '<img ' . $attribute . '>' . $mailConfig[2];
814
815
816
817
                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>
818
819
                $mailConfig = explode("|", $columnValue, 2);
                if (empty($mailConfig[0])) {
820
                    break;
821
                }
822

823
                $t1 = explode("@", $mailConfig[0], 2);
824
                $content .= "<script language=javascript><!--" . chr(10);
825
826
                if (empty($mailConfig[1])) {
                    $mailConfig[1] = $mailConfig[0];
827
                }
828

829
830
                $content .= 'var contact = "' . substr($mailConfig[1], 0, 2) . '"' . chr(10);
                $content .= 'var contact1 = "' . substr($mailConfig[1], 2) . '"' . chr(10);
831
832
833
834
835
836
837
838
839
                $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":
840
841
                $sendMail = new SendMail();
                $mailConfig = $sendMail->parseStringToArray($columnValue);
842
                if (count($mailConfig) < 4) {
843
                    throw new UserReportException ("Too few parameter for sendmail: $columnValue", ERROR_TOO_FEW_PARAMETER_FOR_SENDMAIL);
844
                }
845

846
                $mailConfig[SENDMAIL_TOKEN_SRC] = "Report: T3 pageId=" . $this->store->getVar('pageId', STORE_TYPO3) .
847
848
                    ", T3 ttcontentId=" . $this->store->getVar('ttcontentUid', STORE_TYPO3) .
                    ", Level=" . $full_level;
849

850
851
                $sendMail->process($mailConfig);

852
853
854
                break;

            case "vertical":
855
                // '<Text>|[angle]|[width]|[height]|[tag]'   , width and height needs a unit (px, em ,...), 'tag' might be 'div', 'span', ...
856
857
858
859
860
861
862
                $arr = explode("|", $columnValue, 5);

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

                # width
                $width = $arr[2] ? $arr[2] : "1em";
863
                $extra = "width:$width; ";
864
865

                # height
866
                if ($arr[3]) {
867
                    $extra .= "height:" . $arr[3] . "; ";
868
                }
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888

                # 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 :
889
                if ($flagControl) {
890
891
892
893
894
895
896

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

897
898
                    $flagOutput = false;
                } else {
899
                    // No special Columnname: just add the column value.
900
901
                    $content .= $columnValue;
                }
902
903
                break;
        }
904

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

        return $content;
909
    }
910
911
912
913
914
915

    /**
     * The main method of the PlugIn
     *
     * @param    string $content : The PlugIn content
     * @param    array $conf : The PlugIn configuration
Carsten  Rose's avatar
Carsten Rose committed
916
     *
Carsten  Rose's avatar
Carsten Rose committed
917
     * @return    string The content that is displayed on the website
918
     */
Carsten  Rose's avatar
Carsten Rose committed
919
920
921
    //Checkt ob der Beginn von Array2 gleich ist wie Array1
    // gibt true/false zurück

922
923
924
925
926
927
928
929
930
    /**
     * 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:
     * -------------
931
     * [<page id|alias>[&param=value&...]] | [text] | [tooltip] | [msgbox] | [class] | [target] | [render mode]
932
     *
933
     * param[0]: <page id|alias>[&param=value&...]
934
935
936
937
938
939
     * param[2]: text
     * param[3]: tooltip
     * param[4]: msgbox
     * param[5]: class
     * param[6]: target
     * param[7]: render mode
940
     * @throws UserReportException
941
942
943
     */
    private function doFixColPosPage($columnName, $columnValue) {

Carsten  Rose's avatar
Carsten Rose committed
944
        $tokenList = "";
945

Carsten  Rose's avatar
Carsten Rose committed
946
        if (empty($columnName)) {
947
            return '';
Carsten  Rose's avatar
Carsten Rose committed
948
        }
949

950
        // Split definition
951
        $allParam = explode('|', $columnValue);
Carsten  Rose's avatar
Carsten Rose committed
952
        if (count($allParam) > 8) {
953
            throw new UserReportException ("Too many parameter (max=8): $columnValue", ERROR_TOO_MANY_PARAMETER);
Carsten  Rose's avatar
Carsten Rose committed
954
        }
955

956
957
958
959
960
961
        // First Parameter: Split PageId|PageAlias and  URL Params
        $firstParam = explode('&', $allParam[0], 2);
        if (empty($firstParam[1])) {
            $firstParam[] = '';
        }

962
963
964
965
966
967
968
969
970
        switch ($columnName) {
            case COLUMN_PPAGED:
                // no pageid /pagealias given.
                $tokenList .= $this->composeLinkPart(TOKEN_URL_PARAM, $allParam[0]);
                break;
            default:
                $tokenList .= $this->composeLinkPart(TOKEN_PAGE, $firstParam[0]);            // -- PageID --
                $tokenList .= $this->composeLinkPart(TOKEN_URL_PARAM, $firstParam[1]);
        }
971

972
        if (isset($allParam[1]) && $allParam[1] !== '') {
Carsten  Rose's avatar
Carsten Rose committed
973
            $tokenList .= $this->composeLinkPart(TOKEN_TEXT, $allParam[1]);             // -- Text --
974
        }
975

976
        if (isset($allParam[2]) && $allParam[2] !== '') {
Carsten  Rose's avatar
Carsten Rose committed
977
            $tokenList .= $this->composeLinkPart(TOKEN_TOOL_TIP, $allParam[2]);         // -- tooltip --