Report.php 43.4 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

class Report {

Carsten  Rose's avatar
Carsten Rose committed
46
47
48
49
50
    /**
     * @var SIP
     */
    private $sip = null;

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

56
57
58
59
60
    /**
     * @var Store
     */
    private $store = null;

61
62
63
64
65
66
67
    /**
     * @var string
     */
    private $dbAlias = '';

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

69
70
    // $indexArray[10][50][5]   one entry per 'sql' statement
    private $indexArray = array();
71

72
73
74
75
    // TODO to explain
//	private $resultArray = array();
    private $levelCount = 0;
    //private $counter = 0;
76

77
78
79
80
    /**
     * @var Variables
     */
    private $variables = null;
81

82
    /**
83
     * @var Database
84
85
     */
    private $db = null;
86

87
88
89
90
91
    /**
     * @var Thumbnail
     */
    private $thumbnail = null;

92
93
94
95
    /**
     * @var array
     */
    private $pageDefaults = array();
96

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

103
104
    private $phpUnit = false;

105
    private $showDebugInfoFlag = false;
106

107
108
    private $flagFillStoreRecord = true;

109
    /**
110
     * Report constructor.
111
     *
Carsten  Rose's avatar
Carsten Rose committed
112
     * @param array $t3data
113
     * @param Evaluate $eval
Carsten  Rose's avatar
Carsten Rose committed
114
     * @param bool $phpUnit
115
     */
116
    public function __construct(array $t3data, Evaluate $eval, $phpUnit = false) {
117
118

        $this->phpUnit = $phpUnit;
119

120
121
        Support::setIfNotSet($t3data, "uid", 0);

122
        $this->sip = new Sip($phpUnit);
123
124
        if ($phpUnit) {
            $this->sip->sipUniqId('badcaffee1234');
125
126
            //TODO Webserver Umgebung besser faken
            $_SERVER['REQUEST_URI'] = 'localhost';
127
128
        }

129
        $this->link = new Link($this->sip, $phpUnit);
Carsten  Rose's avatar
Carsten Rose committed
130

131
        $this->store = Store::getInstance();
132

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

135
136
        $this->checkUpdateSqlLog();

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

140
141
142
143
144
145
        $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;
146

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

153
        $this->db = new Database();
154
        $this->variables = new Variables($eval, $t3data["uid"]);
155

156
        // Set static values, which won't change during this run.
157
        $this->fr_error["pid"] = isset($this->variables->resultArray['global.']['page_id']) ? $this->variables->resultArray['global.']['page_id'] : 0;
158
        $this->fr_error["uid"] = $t3data['uid'];
159
        $this->fr_error["debug_level"] = 0;
160

Carsten  Rose's avatar
Carsten Rose committed
161
        // Sanitize function for POST and GET Parameters.
162
        // Merged URL-Parameter (key1, id etc...) in resultArray.
163
        $this->variables->resultArray = array_merge($this->variables->resultArray, array("global." => $this->variables->collectGlobalVariables()));
164
165
166

    }

167
    /**
Carsten  Rose's avatar
Carsten Rose committed
168
169
     * If a variable 'sqlLog' is given in STORE_TYPO3 (=Bodytext) make them relative to SYSTEM_PATH_EXT and copy it to
     * STORE_SYSTEM
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
     */
    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);
        }
    }

188
189
190
191
192
    /**
     * Main function. Parses bodytext and iterates over all queries.
     *
     * @return string
     */
193
    public function process($bodyText, $flagFillStoreRecord = true) {
194

195
196
197
198
        //phpUnit Test: clean environment
        $this->frArray = array();
        $this->indexArray = array();
        $this->levelCount = 0;
199
        $this->flagFillStoreRecord = $flagFillStoreRecord;
200

201
        // Iteration over Bodytext
202
        $ttLineArray = explode("\n", $bodyText);
203

204
205
206
        foreach ($ttLineArray as $index => $line) {
            // Fill $frArray, $indexArray, $resultArray
            $this->parseFRLine($line);
207
        }
208

209
210
211
212
213
214
        // Sort array
        $this->sortIndexArray($this->indexArray, $this->generateSortArg());

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

215
        return $content;
216
    }
217
218
219
220
221
222

    /**
     * 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
223
     *
224
     * @throws UserReportException
225
226
227
228
229
230
     */
    private function parseFRLine($ttLine) {

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

231
        // no elements or only one: do nothing
232
        if (count($arr) < 2) {
233
            return;
234
        }
235

236
237
238
239
        // 10.50.5.sql
        $key = strtolower(trim($arr[0]));

        // comment ?
240
        if (empty($key) || $key[0] === "#") {
241
            return;
242
        }
243
244
245
246
247
248
249
250
251
252

        // select ...
        $value = trim($arr[1]);

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

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

253
        if (strpos('|' . strtolower(TOKEN_VALID_LIST) . '|', '|' . $frCmd . '|') === false) {
254
            throw new UserReportException ("Unknown token: $frCmd in Line '$ttLine''", ERROR_UNKNOWN_TOKEN);
255
256
        }

257
258
259
260
261
262
263
264
265
266
267
        // 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);
        }
268
    }
269

270
271
272
273
274
    /**
     * @param $level
     * @param $frCmd
     * @param $value
     */
275
276
277
278
279
280
281
282
    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){
283
        if ($frCmd === TOKEN_FORM || $frCmd === TOKEN_SQL) {
284
285
286
287
288
            // Remember max level
            $this->levelCount = max(substr_count($level, '.') + 1, $this->levelCount);
            // $indexArray[10][50][5]
            $this->indexArray[] = explode(".", $level);
        }
289
290
291

        // set defaults
        if ($frCmd === TOKEN_SQL) {
292
293
            $arr = explode('|', TOKEN_VALID_LIST);
            foreach ($arr as $key) {
294
                if (!isset($this->frArray[$level . "." . $key])) {
295
                    $this->frArray[$level . "." . $key] = '';
296
                }
297
298
            }
        }
299
    }
300
301
302
303

    /**
     * Sorts the associative array.
     *
Carsten  Rose's avatar
Carsten Rose committed
304
     * @param array $ary : The unsorted Level Array
305
     * @param string $clause : the sort argument 0 ASC, 1 ASC... according to the number of columns
306
     * @param bool|true $ascending
307
     */
308
    private function sortIndexArray(array &$ary, $clause, $ascending = true) {
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352

        $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);
353
354
355
356
357

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

358
            usort($ary, $sortFn);
359
360

            error_reporting($errorSet);
361
        }
362
    }
363

364
365
366
367
368
    /**
     * generateSortArg
     *
     * @return string
     */
369
370
371
372
373
374
375
    private function generateSortArg() {

        $sortArg = "";

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

377
        $sortArg = substr($sortArg, 0, strlen($sortArg) - 2);
378

379
        return $sortArg;
380
    }
381
382

    /**
383
     * Executes the queries recursive. This Method is called for each sublevel.
384
385
386
387
388
389
     *
     * ROOTLEVEL
     * This method is called once from the main method.
     * For the first call the method executes the rootlevels
     *
     * SUBLEVEL
390
     * For each rootlevel the method calls it self with the level mode 0
391
392
393
394
     * 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 ...
395
     * @param string $super_level_array The Value-Array of the indexarray [0=>10, 1=>50]
Carsten  Rose's avatar
Carsten Rose committed
396
     * @param int $counter The outer numeric Arraykey of indexarray
Carsten  Rose's avatar
Carsten Rose committed
397
     *
398
399
400
401
402
     * @return string                       The content that is displayed on the website
     * @throws codeException
     * @throws UserReportException
     */

403
404
    private function triggerReport($cur_level = 1, array $super_level_array = array(), $counter = 0) {
        $keys = array();
405
        $stat = array();
406
407
408
409
410
411

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

        // CurrentLevel "10.10.50"
412
413
414
415
416
417
        if (isset($this->indexArray[$counter]) && is_array($this->indexArray[$counter])) {
            $full_level = implode(".", $this->indexArray[$counter]);
        } else {
            $full_level = '';
        }

418
        // Superlevel "10.10"
419
420
421
422
423
        if (isset($super_level_array) && is_array($super_level_array)) {
            $full_super_level = implode(".", $super_level_array);
        } else {
            $full_super_level = '';
        }
424
425
426
427
428
429
430

        //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])) {
431
432
433
434
435
436
                ++$counter;
                if (isset($this->indexArray[$counter]) && is_array($this->indexArray[$counter])) {
                    $full_level = implode(".", $this->indexArray[$counter]);
                } else {
                    $full_level = '';
                }
437
438
439
440
                continue;
            }

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

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

447
448
449
            // 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"]);
450

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

453
            $this->store->setVar(SYSTEM_SQL_FINAL, $sql, STORE_SYSTEM);
454
455
456

            //Execute SQL. All errors have been already catched.
            unset($result);
457
            $result = $this->db->sql($sql, ROW_KEYS, array(), '', $keys, $stat);
458
459
460

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

            $this->variables->resultArray[$full_level . ".line."]["total"] = $rowTotal;
465
466
            $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;
467

468
            $content .= $this->variables->doVariables($this->frArray[$full_level . "." . TOKEN_SHEAD]);
469
            // HEAD: If there is at least one record, do 'head'.
470
            if ($rowTotal > 0) {
471
                $content .= $this->variables->doVariables($this->frArray[$full_level . "." . TOKEN_HEAD]);
472
            }
473
474


475
            if (is_array($result)) {
476
477
478
479
480
481
482
483

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

484
485
                //---------------------------------
                // Process each row of resultset
486
                $columnValueSeparator = "";
487
488
489
490
491
492
493
494
495
496
                $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]);
                    }
497

498
499
500
501
502
503
                    // SEP set separator (empty on first run)
                    $content .= $columnValueSeparator;
                    $columnValueSeparator = $this->variables->doVariables($this->frArray[$full_level . "." . TOKEN_RSEP]);

                    // RBEG
                    $rbeg = $this->variables->doVariables($this->frArray[$full_level . "." . TOKEN_RBEG]);
504

505
                    // RBGD: even/odd rows
506
                    $content .= str_replace(TOKEN_RBGD, $arrRbgd[$rowIndex % 2], $rbeg);
507

508
509
510
                    //-----------------------------
                    // COLUMNS: Collect all columns
                    $content .= $this->collectRow($row, $keys, $full_level, $rowIndex);
511

512
                    // REND
513
                    $content .= $this->variables->doVariables($this->frArray[$full_level . "." . TOKEN_REND]);
514

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

518
                    // RENR
519
                    $content .= $this->variables->doVariables($this->frArray[$full_level . "." . TOKEN_RENR]);
520

521
                }
522
523
524
            }

            if ($rowTotal > 0) {
525
                // tail
526
                $content .= $this->variables->doVariables($this->frArray[$full_level . "." . TOKEN_TAIL]);
527
            } else {
528
                // althead
529
                $content .= $this->variables->doVariables($this->frArray[$full_level . "." . TOKEN_ALT_HEAD]);
530
531
532
533
534
535
536
537
538
                // 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);
                    }
                }
539
            }
540
            $content .= $this->variables->doVariables($this->frArray[$full_level . "." . TOKEN_STAIL]);
541

542
543
544
545
546
547
            ++$counter;
            if (isset($this->indexArray[$counter]) && is_array($this->indexArray[$counter])) {
                $full_level = implode(".", $this->indexArray[$counter]);
            } else {
                $full_level = '';
            }
548
549
550
        }

        return $content;
551
    }
552
553
554
555
556
557
558
559

    /**
     * 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
560
     * @param    string $level_key - 'db' or 'debug'
561
562
     * @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
563
564
     * @param    string $cur_level - f.e.: 2
     * @param    string $default - f.e.: 0
Carsten  Rose's avatar
Carsten Rose committed
565
     *
566
567
568
569
     * @return   string  The calculated value.
     */
    private function getValueParentDefault($level_key, $full_super_level, $full_level, $cur_level, $default) {

570
        if (!empty($this->frArray[$full_level . "." . $level_key])) {
571
572
573
574
575
576
577
578
579
580
581
            $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);
582
    }
583
584
585
586

    /**
     * Steps through 'row' and collects all columns
     *
Carsten  Rose's avatar
Carsten Rose committed
587
588
     * @param array $row Recent row fetch from sql resultset.
     * @param array $keys List of all columnnames
589
590
     * @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
591
     *
592
593
594
595
     * @return string               Collected content of all printable columns
     */
    private function collectRow(array $row, array $keys, $full_level, $rowIndex) {
        $content = "";
596
        $assoc = array();
597

598
        $fsep = '';
599
600
        for ($ii = 0; $ii < count($keys); $ii++) {

601
            // Debugging
602
603
604
            $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);
605

606
607
            $flagOutput = false;
            $renderedColumn = $this->renderColumn($ii, $keys[$ii], $row[$ii], $full_level, $rowIndex, $flagOutput);
608

609
            $keyAssoc = Support::stripFirstCharIf(TOKEN_COLUMN_CTRL, $keys[$ii]);
610
611
612
613
            if ($keyAssoc != '') {
                $assoc[$keyAssoc] = $row[$ii];
            }

614
            if ($flagOutput) {
615
                //prints
616
                $content .= $this->variables->doVariables($fsep);
617
                $content .= $this->variables->doVariables($this->frArray[$full_level . "." . TOKEN_FBEG]);
618
                $content .= $renderedColumn;
619
620
                $content .= $this->variables->doVariables($this->frArray[$full_level . "." . TOKEN_FEND]);
                $fsep = $this->frArray[$full_level . "." . TOKEN_FSEP];
621
622
            }
        }
Carsten  Rose's avatar
Carsten Rose committed
623

624
        if ($this->flagFillStoreRecord) {
625
            $this->store->appendToStore($assoc, STORE_RECORD);
626
        }
627

628
        return ($content);
629
    }
630
631
632
633

    /**
     * Renders column depending of column name (if name is a reserved column name)
     *
634
635
636
637
638
     * @param string $columnIndex
     * @param string $columnName
     * @param string $columnValue
     * @param string $full_level
     * @param string $rowIndex
639
     * @param $flagOutput
Carsten  Rose's avatar
Carsten Rose committed
640
     *
641
     * @return string rendered column
642
643
644
     * @throws CodeException
     * @throws UserFormException
     * @throws UserReportException
645
     */
646
    private function renderColumn($columnIndex, $columnName, $columnValue, $full_level, $rowIndex, &$flagOutput) {
647
        $content = "";
648
649
        $flagControl = false;
        $flagOutput = true;
Carsten  Rose's avatar
Carsten Rose committed
650
        $dummy = false;
651

Carsten  Rose's avatar
Carsten Rose committed
652
        // Empty columnnames are allowed: check with isset
653
        if (isset($columnName[0]) && $columnName[0] === TOKEN_COLUMN_CTRL) {
654
            $flagControl = true;
655
656
            $columnName = substr($columnName, 1);
        }
657

658
        //TODO: reserved names,not starting with '_' will be still accepted - stop this!
659
        switch ($columnName) {
660
            case COLUMN_LINK:
Carsten  Rose's avatar
Carsten Rose committed
661
                $content .= $this->link->renderLink($columnValue);
662
                break;
663

664
665
            case COLUMN_EXEC:
                $rc = '';
666
                $content .= Support::qfqExec($columnValue, $rc);
667
668
                break;

669
670
671
672
673
674
675
676
677
            // 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:
678
            $lowerColumnName = strtolower($columnName);
679
                $tokenizedValue = $this->doFixColPosPage($columnName, $columnValue);
680
            $linkValue = $this->doPage($lowerColumnName, $tokenizedValue);
Carsten  Rose's avatar
Carsten Rose committed
681
            $content .= $this->link->renderLink($linkValue);
682
683
                break;

684
685
686
687
688
689
690
691
692
            // 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:
693
                $linkValue = $this->doPage($columnName, $columnValue);
Carsten  Rose's avatar
Carsten Rose committed
694
            $content .= $this->link->renderLink($linkValue);
695
696
                break;

697
698
699
700
            case COLUMN_PPDF:
            case COLUMN_FFILE:
            case COLUMN_ZZIP:
                $lowerColumnName = strtolower($columnName);
Carsten  Rose's avatar
Carsten Rose committed
701
                $tokenizedValue = $this->doFixColPosDownload($columnValue);
702
                $linkValue = $this->doDownload($lowerColumnName, $tokenizedValue);
Carsten  Rose's avatar
Carsten Rose committed
703
                $content .= $this->link->renderLink($linkValue);
Carsten  Rose's avatar
Carsten Rose committed
704
705
                break;

706
707
708
709
            case COLUMN_PDF:
            case COLUMN_FILE:
            case COLUMN_ZIP:
                $linkValue = $this->doDownload($columnName, $columnValue);
Carsten  Rose's avatar
Carsten Rose committed
710
                $content .= $this->link->renderLink($linkValue);
Carsten  Rose's avatar
Carsten Rose committed
711
712
                break;

713
714
715
716
            case COLUMN_THUMBNAIL:
                if ($this->thumbnail == null) {
                    $this->thumbnail = new Thumbnail();
                }
717
                $content .= $this->thumbnail->process($columnValue);
718
719
                break;

720
721
722
723
724
725
726
727
728
729
730
731
732
            case COLUMN_NL2BR:
                $content .= nl2br($columnValue);
                break;

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

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


733
            case "bullet":
734
                if ($columnValue === '') {
735
                    break;
736
                }
737

Carsten  Rose's avatar
Carsten Rose committed
738
739
                // r:3|B:
                $linkValue = TOKEN_RENDER . ":3|" . TOKEN_BULLET . ":" . $columnValue;
Carsten  Rose's avatar
Carsten Rose committed
740
                $content .= $this->link->renderLink($linkValue);
741
742
743
                break;

            case "check":
744
                if ($columnValue === '') {
745
                    break;
746
                }
747

Carsten  Rose's avatar
Carsten Rose committed
748
749
                // "r:3|C:
                $linkValue = TOKEN_RENDER . ":3|" . TOKEN_CHECK . ":" . $columnValue;
Carsten  Rose's avatar
Carsten Rose committed
750
                $content .= $this->link->renderLink($linkValue);
751
752
                break;

753
754
            case "img":
                // "<path to image>|[alttext]|[text behind]" renders to: <img src="<path to image>" alt="[alttext]">[text behind]
755
                if (empty($columnValue)) {
Carsten  Rose's avatar
Carsten Rose committed
756
                    break;
757
                }
Carsten  Rose's avatar
Carsten Rose committed
758

759
                $mailConfig = explode("|", $columnValue, 3);
760
761

                // Fake values for tmp[1], tmp[2] to suppress access errors.
762
763
                $mailConfig[] = '';
                $mailConfig[] = '';
764

765
                if (empty($mailConfig[0])) {
766
                    break;
767
                }
768
769
                $attribute = Support::doAttribute('src', $mailConfig[0]);
                $attribute .= Support::doAttribute('alt', $mailConfig[1]);
770

771
                $content .= '<img ' . $attribute . '>' . $mailConfig[2];
772
773
774
775
                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>
776
777
                $mailConfig = explode("|", $columnValue, 2);
                if (empty($mailConfig[0])) {
778
                    break;
779
                }
780

781
                $t1 = explode("@", $mailConfig[0], 2);
782
                $content .= "<script language=javascript><!--" . chr(10);
783
784
                if (empty($mailConfig[1])) {
                    $mailConfig[1] = $mailConfig[0];
785
                }
786

787
788
                $content .= 'var contact = "' . substr($mailConfig[1], 0, 2) . '"' . chr(10);
                $content .= 'var contact1 = "' . substr($mailConfig[1], 2) . '"' . chr(10);
789
790
791
792
793
794
795
796
797
                $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":
798
799
                $sendMail = new SendMail();
                $mailConfig = $sendMail->parseStringToArray($columnValue);
800
                if (count($mailConfig) < 4) {
801
                    throw new UserReportException ("Too few parameter for sendmail: $columnValue", ERROR_TOO_FEW_PARAMETER_FOR_SENDMAIL);
802
                }
803

804
                $mailConfig[SENDMAIL_TOKEN_SRC] = "Report: T3 pageId=" . $this->store->getVar('pageId', STORE_TYPO3) .
805
806
                    ", T3 ttcontentId=" . $this->store->getVar('ttcontentUid', STORE_TYPO3) .
                    ", Level=" . $full_level;
807

808
809
                $sendMail->process($mailConfig);

810
811
812
                break;

            case "vertical":
813
                // '<Text>|[angle]|[width]|[height]|[tag]'   , width and height needs a unit (px, em ,...), 'tag' might be 'div', 'span', ...
814
815
816
817
818
819
820
                $arr = explode("|", $columnValue, 5);

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

                # width
                $width = $arr[2] ? $arr[2] : "1em";
821
                $extra = "width:$width; ";
822
823

                # height
824
                if ($arr[3]) {
825
                    $extra .= "height:" . $arr[3] . "; ";
826
                }
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846

                # 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 :
847
                if ($flagControl) {
848
849
850
851
852
853
854

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

855
856
                    $flagOutput = false;
                } else {
857
                    // No special Columnname: just add the column value.
858
859
                    $content .= $columnValue;
                }
860
861
                break;
        }
862

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

        return $content;
867
    }
868
869
870
871
872
873

    /**
     * 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
874
     *
Carsten  Rose's avatar
Carsten Rose committed
875
     * @return    string The content that is displayed on the website
876
     */
Carsten  Rose's avatar
Carsten Rose committed
877
878
879
    //Checkt ob der Beginn von Array2 gleich ist wie Array1
    // gibt true/false zurück

880
881
882
883
884
885
886
887
888
    /**
     * 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:
     * -------------
889
     * [<page id|alias>[&param=value&...]] | [text] | [tooltip] | [msgbox] | [class] | [target] | [render mode]
890
     *
891
     * param[0]: <page id|alias>[&param=value&...]
892
893
894
895
896
897
     * param[2]: text
     * param[3]: tooltip
     * param[4]: msgbox
     * param[5]: class
     * param[6]: target
     * param[7]: render mode
898
     * @throws UserReportException
899
900
901
     */
    private function doFixColPosPage($columnName, $columnValue) {

Carsten  Rose's avatar
Carsten Rose committed
902
        $tokenList = "";
903

Carsten  Rose's avatar
Carsten Rose committed
904
        if (empty($columnName)) {
905
            return '';
Carsten  Rose's avatar
Carsten Rose committed
906
        }
907

908
        // Split definition
909
        $allParam = explode('|', $columnValue);
Carsten  Rose's avatar
Carsten Rose committed
910
        if (count($allParam) > 8) {
911
            throw new UserReportException ("Too many parameter (max=8): $columnValue", ERROR_TOO_MANY_PARAMETER);
Carsten  Rose's avatar
Carsten Rose committed
912
        }
913

914
915
916
917
918
919
        // First Parameter: Split PageId|PageAlias and  URL Params
        $firstParam = explode('&', $allParam[0], 2);
        if (empty($firstParam[1])) {
            $firstParam[] = '';
        }

920
921
922
923
924
925
926
927
928
        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]);
        }
929

930
        if (isset($allParam[1]) && $allParam[1] !== '') {
Carsten  Rose's avatar
Carsten Rose committed
931
            $tokenList .= $this->composeLinkPart(TOKEN_TEXT, $allParam[1]);             // -- Text --
932
        }
933

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

938
        if (isset($allParam[3]) && $allParam[3] !== '') {
939
            $text = isset($this->pageDefaults[DEFAULT_QUESTION][$columnName]) ? $this->pageDefaults[DEFAULT_QUESTION][$columnName] : '';
Carsten  Rose's avatar
Carsten Rose committed
940
            $tokenList .= $this->composeLinkPart(TOKEN_QUESTION, $allParam[3], $text);                // -- msgbox
941
        }
942

943
        if (isset($allParam[4]) && $allParam[4] !== '') {
Carsten  Rose's avatar
Carsten Rose committed
944
            $tokenList .= $this->composeLinkPart(TOKEN_CLASS, $allParam[4]);            // -- class --
945
        }
946

947
        if (isset($allParam[5]) && $allParam[5] !== '') {
Carsten  Rose's avatar
Carsten Rose committed
948
            $tokenList .= $this->composeLinkPart(TOKEN_TARGET, $allParam[5]);           // -- target --
949
        }
950

951
        if (isset($allParam[6]) && $allParam[6] !== '') {
Carsten  Rose's avatar
Carsten Rose committed
952
            $tokenList .= $this->composeLinkPart(TOKEN_RENDER, $allParam[6]);           // -- render mode --
953
        }
Carsten  Rose's avatar
Carsten Rose committed
954

955
        if (!isset($allParam[7])) {
956
            $allParam[7] = '1'; // if no SIP behaviour defined: sip is set
957
        }
958

Carsten  Rose's avatar
Carsten Rose committed
959
        $tokenList .= $this->composeLinkPart(TOKEN_SIP, $allParam[7]);           // -- SIP --
960

961
962
        if (isset($this->pageDefaults[DEFAULT_ICON][$columnName])) {
            $tokenList .= $this->pageDefaults[DEFAULT_ICON][$columnName] . "|";
963
        }
964

Carsten  Rose's avatar
Carsten Rose committed
965
        return ($tokenList);
966
    }