Link.php 56.5 KB
Newer Older
Carsten  Rose's avatar
Carsten Rose committed
1
<?php
2
3
4
/***************************************************************
 *  Copyright notice
 *
Carsten  Rose's avatar
Carsten Rose committed
5
 *  (c) 2010 Glowbase GmbH
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
 *
 *  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!
 ***************************************************************/

namespace qfq;

//use qfq;

require_once(__DIR__ . '/Define.php');
29
30
require_once(__DIR__ . '/../store/Store.php');
require_once(__DIR__ . '/../store/Sip.php');
31
require_once(__DIR__ . '/../exceptions/UserReportException.php');
32
require_once(__DIR__ . '/../helper/KeyValueStringParser.php');
33
require_once(__DIR__ . '/../helper/Token.php');
34
require_once(__DIR__ . '/Thumbnail.php');
35
36
37

/*
 * a:AltText
38
 * b:bootstrap [0|1|<button>]
39
 * B:bullet
40
 * c:class  [n|i|e|<class>]
41
42
43
 * C:checkbox    [name]
 * d:download
 * D:delete
44
45
 * e:encryption 0|1
 * E:edit
46
47
48
 * F:File
 * g:target
 * G:Glyph
49
50
 * H:Help
 * I:information
51
52
53
54
 * m:mailto
 * M:Mode
 * N:new
 * o:ToolTip
55
 * O:Monitor
56
 * p:page
57
 * P:picture       [file]
58
59
60
 * q:question  <text>
 * r:render
 * R:right
Carsten  Rose's avatar
Carsten Rose committed
61
 * s:sip
62
63
64
65
66
67
68
 * S:Show
 * t:text
 * T:Thumbnail
 * u:url
 * U:URL Param
 * W:Dimension
 * x:Delete
69
 * y:Copy to clipboard
70
 *
71
72
 */

73
74
75
76

const NAME_URL = 'url';
const NAME_MAIL = 'mail';
const NAME_PAGE = 'page';
77
const NAME_UID = 'uid';
78
const NAME_TEXT = 'text';
Carsten  Rose's avatar
Carsten Rose committed
79
const NAME_DOWNLOAD = DOWNLOAD_EXPORT_FILENAME;
80
const NAME_COLLECT_ELEMENTS = 'downloadElements';  // array with element sources
Carsten  Rose's avatar
Carsten Rose committed
81
const NAME_DOWNLOAD_MODE = 'mode';
82
const NAME_BOOTSTRAP_BUTTON = 'bootstrapButton';
83
84
const NAME_ALT_TEXT = 'altText';
const NAME_TOOL_TIP = 'toolTip';
85
const NAME_TOOL_TIP_JS = 'toolTipJs';
86
87
const NAME_IMAGE = 'image';
const NAME_IMAGE_TITLE = 'imageTitle';
88
89
const NAME_GLYPH = 'glyph';
const NAME_GLYPH_TITLE = 'glyphTitle';
90
91
92
93
94
95
96
const NAME_DELETE = 'delete';
const NAME_RENDER = 'render';
const NAME_TARGET = 'target';
const NAME_LINK_CLASS = 'linkClass';
const NAME_LINK_CLASS_DEFAULT = 'linkClassDefault';
const NAME_QUESTION = 'question';
const NAME_ENCRYPTION = 'encryption';
Carsten  Rose's avatar
Carsten Rose committed
97
const NAME_SIP = 'sip';
98
const NAME_URL_PARAM = 'param';
99
const NAME_RIGHT = 'picturePositionRight';
100
const NAME_ACTION_DELETE = 'actionDelete';
Carsten  Rose's avatar
Carsten Rose committed
101
const NAME_EXTRA_CONTENT_WRAP = 'extraContentWrap';
Carsten  Rose's avatar
Carsten Rose committed
102
const NAME_FILE = 'file';
103
104
const NAME_THUMBNAIL = 'thumbnail';
const NAME_THUMBNAIL_DIMENSION = 'thumbnailDimension';
105
const NAME_COPY_TO_CLIPBOARD = 'copyToClipBoard';
106
const NAME_MONITOR = 'monitor';
107
108
109
110

const FINAL_HREF = 'finalHref';
const FINAL_ANCHOR = 'finalAnchor';
const FINAL_CONTENT = 'finalContent';
Carsten  Rose's avatar
Carsten Rose committed
111
const FINAL_CONTENT_PURE = 'finalContentPure';
112
113
114
115
const FINAL_SYMBOL = 'finalSymbol';
const FINAL_TOOL_TIP = 'finalToolTip';
const FINAL_CLASS = 'finalClass';
const FINAL_QUESTION = 'finalQuestion';
116
const FINAL_THUMBNAIL = 'finalThumbnail';
117

118
119
120
const LINK_ANCHOR = 'linkAnchor';
const LINK_PICTURE = 'linkPicture';

121
122
const NO_CLASS = 'no_class';

Carsten  Rose's avatar
Carsten Rose committed
123
124
const DEFAULT_BULLET_COLOR = 'green';
const DEFAULT_CHECK_COLOR = 'green';
125
const DEFAULT_RENDER_MODE = '0';
Carsten  Rose's avatar
Carsten Rose committed
126
127
const DEFAULT_QUESTION_TEXT = 'Please confirm';
const DEFAULT_QUESTION_LEVEL = 'info';
128
const DEFAULT_ACTION_DELETE = 'r';
Carsten  Rose's avatar
Carsten Rose committed
129

Carsten  Rose's avatar
Carsten Rose committed
130
131
132
133
const QUESTION_INDEX_TEXT = 0;
const QUESTION_INDEX_LEVEL = 1;
const QUESTION_INDEX_BUTTON_OK = 2;
const QUESTION_INDEX_BUTTON_FALSE = 3;
134
135
const QUESTION_INDEX_TIMEOUT = 4;
const QUESTION_INDEX_FLAG_MODAL = 5;
Carsten  Rose's avatar
Carsten Rose committed
136

137
138
139
140
/**
 * Class Link
 * @package qfq
 */
141
142
class Link {

Carsten  Rose's avatar
Carsten Rose committed
143
    /**
144
     * @var Sip
Carsten  Rose's avatar
Carsten Rose committed
145
146
147
     */
    private $sip = null;

148
149
150
151
152
    /**
     * @var Store
     */
    private $store = null;

153
154
155
156
157
    /**
     * @var Thumbnail
     */
    private $thumbnail = null;

158
159
    private $dbIndexData = false;

Carsten  Rose's avatar
Carsten Rose committed
160
    private $phpUnit;
161
    private $renderControl = array();
162
//    private $linkClassSelector = array(TOKEN_CLASS_INTERNAL => "internal ", TOKEN_CLASS_EXTERNAL => "external ");
163
164
//    private $cssLinkClassInternal = '';
//    private $cssLinkClassExternal = '';
Carsten  Rose's avatar
Carsten Rose committed
165
    private $ttContentUid = '';
166

167
    private $callTable = [
Carsten  Rose's avatar
Carsten Rose committed
168
169
170
        TOKEN_URL => 'buildUrl',
        TOKEN_MAIL => 'buildMail',
        TOKEN_PAGE => 'buildPage',
171
        TOKEN_COPY_TO_CLIPBOARD => 'buildCopyToClipboard',
Carsten  Rose's avatar
Carsten Rose committed
172
173
174
175
176
177
178
179
180
181
182
183
184
        TOKEN_DOWNLOAD => 'buildDownload',
        TOKEN_TOOL_TIP => 'buildToolTip',
        TOKEN_PICTURE => 'buildPicture',
        TOKEN_BULLET => 'buildBullet',
        TOKEN_CHECK => 'buildCheck',
        TOKEN_DELETE => 'buildDeleteIcon',
        TOKEN_ACTION_DELETE => 'buildActionDelete',
        TOKEN_EDIT => 'buildEdit',
        TOKEN_HELP => 'buildHelp',
        TOKEN_INFO => 'buildInfo',
        TOKEN_NEW => 'buildNew',
        TOKEN_SHOW => 'buildShow',
        TOKEN_FILE => 'buildFile',
185
        TOKEN_FILE_DEPRECATED => 'buildFile',
Carsten  Rose's avatar
Carsten Rose committed
186
        TOKEN_GLYPH => 'buildGlyph',
187
        TOKEN_BOOTSTRAP_BUTTON => 'buildBootstrapButton',
188
189
    ];

190
    private $tableVarName = [
Carsten  Rose's avatar
Carsten Rose committed
191
192
193
        TOKEN_URL => NAME_URL,
        TOKEN_MAIL => NAME_MAIL,
        TOKEN_PAGE => NAME_PAGE,
194
        TOKEN_UID => NAME_UID,
Carsten  Rose's avatar
Carsten Rose committed
195
196
197
198
        TOKEN_DOWNLOAD => NAME_DOWNLOAD,
        TOKEN_DOWNLOAD_MODE => NAME_DOWNLOAD_MODE,
        TOKEN_TEXT => NAME_TEXT,
        TOKEN_ALT_TEXT => NAME_ALT_TEXT,
199
        TOKEN_BOOTSTRAP_BUTTON => NAME_BOOTSTRAP_BUTTON,
Carsten  Rose's avatar
Carsten Rose committed
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
        TOKEN_TOOL_TIP => NAME_TOOL_TIP,
        TOKEN_PICTURE => NAME_IMAGE,
        TOKEN_BULLET => NAME_IMAGE,
        TOKEN_CHECK => NAME_IMAGE,
        TOKEN_DELETE => NAME_IMAGE,
        TOKEN_EDIT => NAME_IMAGE,
        TOKEN_HELP => NAME_IMAGE,
        TOKEN_INFO => NAME_IMAGE,
        TOKEN_NEW => NAME_IMAGE,
        TOKEN_SHOW => NAME_IMAGE,
        TOKEN_GLYPH => NAME_IMAGE,
        TOKEN_RENDER => NAME_RENDER,
        TOKEN_TARGET => NAME_TARGET,
        TOKEN_CLASS => NAME_LINK_CLASS,
        TOKEN_QUESTION => NAME_QUESTION,
        TOKEN_ENCRYPTION => NAME_ENCRYPTION,
        TOKEN_SIP => NAME_SIP,
        TOKEN_URL_PARAM => NAME_URL_PARAM,
        TOKEN_RIGHT => NAME_RIGHT,
        TOKEN_ACTION_DELETE => NAME_ACTION_DELETE,
        TOKEN_FILE => NAME_FILE,
221
        TOKEN_FILE_DEPRECATED => NAME_FILE,
222
223
        TOKEN_THUMBNAIL => NAME_THUMBNAIL,
        TOKEN_THUMBNAIL_DIMENSION => NAME_THUMBNAIL_DIMENSION,
224
        TOKEN_COPY_TO_CLIPBOARD => NAME_COPY_TO_CLIPBOARD,
225
226
227
228
229
230
231
232

        TOKEN_MONITOR => NAME_MONITOR,
        // The following don't need a renaming: already 'long'
        TOKEN_L_FILE => TOKEN_L_FILE,
        TOKEN_L_TAIL => TOKEN_L_TAIL,
        TOKEN_L_APPEND => TOKEN_L_APPEND,
        TOKEN_L_INTERVAL => TOKEN_L_INTERVAL,
        TOKEN_L_HTML_ID => TOKEN_L_HTML_ID,
233
234
    ];

Carsten  Rose's avatar
Carsten Rose committed
235
    // Used to find double definitions.
236
    private $tokenMapping = [
Carsten  Rose's avatar
Carsten Rose committed
237
238
239
        TOKEN_URL => LINK_ANCHOR,
        TOKEN_MAIL => LINK_ANCHOR,
        TOKEN_PAGE => LINK_ANCHOR,
240
        TOKEN_UID => LINK_ANCHOR,
Carsten  Rose's avatar
Carsten Rose committed
241
        TOKEN_DOWNLOAD => LINK_ANCHOR,
242
        TOKEN_FILE => NAME_FILE,
243
        TOKEN_COPY_TO_CLIPBOARD => LINK_ANCHOR,
Carsten  Rose's avatar
Carsten Rose committed
244

245
        TOKEN_PICTURE => LINK_PICTURE,
Carsten  Rose's avatar
Carsten Rose committed
246
247
248
249
250
251
252
253
254
        TOKEN_BULLET => LINK_PICTURE,
        TOKEN_CHECK => LINK_PICTURE,
        TOKEN_DELETE => LINK_PICTURE,
        TOKEN_EDIT => LINK_PICTURE,
        TOKEN_HELP => LINK_PICTURE,
        TOKEN_INFO => LINK_PICTURE,
        TOKEN_NEW => LINK_PICTURE,
        TOKEN_SHOW => LINK_PICTURE,
        TOKEN_GLYPH => LINK_PICTURE,
255
    ];
256

257
258
259
    /**
     * __construct
     *
260
     * @param Sip $sip
261
     * @param string $dbIndexData
262
     * @param bool $phpUnit
263
264
     * @throws CodeException
     * @throws UserFormException
265
     * @throws UserReportException
266
     */
267
    public function __construct(Sip $sip, $dbIndexData = DB_INDEX_DEFAULT, $phpUnit = false) {
Carsten  Rose's avatar
Carsten Rose committed
268
        $this->phpUnit = $phpUnit;
269
270
271
272
273

        if ($phpUnit) {
            $_SERVER['REQUEST_URI'] = 'localhost';
        }

Carsten  Rose's avatar
Carsten Rose committed
274
        $this->sip = $sip;
Carsten  Rose's avatar
Carsten Rose committed
275
        $this->store = Store::getInstance('', $phpUnit);
276
277
//        $this->cssLinkClassInternal = $this->store->getVar(SYSTEM_CSS_LINK_CLASS_INTERNAL, STORE_SYSTEM);
//        $this->cssLinkClassExternal = $this->store->getVar(SYSTEM_CSS_LINK_CLASS_EXTERNAL, STORE_SYSTEM);
Carsten  Rose's avatar
Carsten Rose committed
278
        $this->ttContentUid = $this->store->getVar(TYPO3_TT_CONTENT_UID, STORE_TYPO3);
279
        $this->dbIndexData = $dbIndexData;
280
281
282
        /*
         * mode:
         * 0: no output
283
284
         * 1: <span title='...'>text</span>   (no href)
         * 2: <span title='...'>url</span>    (no href)
285
286
         * 3: <a href=url>url</a>
         * 4: <a href=url>Text</a>
287
288
         * 5: text
         * 6: url
289
         * 8: SIP only - 's=badcaffee1234'
290
         *
Carsten  Rose's avatar
Carsten Rose committed
291
         *  r=render mode, u=url, t:text and/or image.
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
         *
         *                  [r][u][t] = mode
         */

        $this->renderControl[0][0][0] = 0;
        $this->renderControl[0][0][1] = 0;
        $this->renderControl[0][1][0] = 3;
        $this->renderControl[0][1][1] = 4;

        $this->renderControl[1][0][0] = 0;
        $this->renderControl[1][0][1] = 1;
        $this->renderControl[1][1][0] = 3;
        $this->renderControl[1][1][1] = 4;

        $this->renderControl[2][0][0] = 0;
        $this->renderControl[2][0][1] = 0;
        $this->renderControl[2][1][0] = 0;
        $this->renderControl[2][1][1] = 4;

        $this->renderControl[3][0][0] = 0;
        $this->renderControl[3][0][1] = 1;
        $this->renderControl[3][1][0] = 2;
        $this->renderControl[3][1][1] = 1;

        $this->renderControl[4][0][0] = 0;
        $this->renderControl[4][0][1] = 1;
        $this->renderControl[4][1][0] = 2;
        $this->renderControl[4][1][1] = 2;

        $this->renderControl[5][0][0] = 0;
        $this->renderControl[5][0][1] = 0;
        $this->renderControl[5][1][0] = 0;
        $this->renderControl[5][1][1] = 0;
325

326
327
328
329
330
        $this->renderControl[6][0][0] = 0;
        $this->renderControl[6][0][1] = 5;
        $this->renderControl[6][1][0] = 0;
        $this->renderControl[6][1][1] = 5;

331
332
333
334
        $this->renderControl[7][0][0] = 0;
        $this->renderControl[7][0][1] = 0;
        $this->renderControl[7][1][0] = 6;
        $this->renderControl[7][1][1] = 6;
335

336
337
338
339
        $this->renderControl[8][0][0] = 0;
        $this->renderControl[8][0][1] = 0;
        $this->renderControl[8][1][0] = 8;
        $this->renderControl[8][1][1] = 8;
340
    }
341

342
    /**
343
     * In render mode 3,4,5 there is no '<a href ...>'. Nevertheless, tooltip and BS Button should be displayed.
344
     * Do this by applying a '<span>' attribute around the text.
345
     *
346
347
348
349
     * @param array $vars
     * @param       $keyName
     *
     * @return string
350
     * @throws CodeException
351
     */
352
353
354
355
    private function wrapLinkTextOnly(array $vars, $keyName) {

        $text = $vars[$keyName];
        if ($vars[NAME_BOOTSTRAP_BUTTON] == '' && $vars[FINAL_TOOL_TIP] == '') {
356
357
358
            return $text;
        }

359
360
361
362
363
364
365
        $attributes = Support::doAttribute('title', $vars[FINAL_TOOL_TIP]);

        if ($vars[NAME_BOOTSTRAP_BUTTON] != '') {
            $attributes .= Support::doAttribute('class', [$vars[NAME_BOOTSTRAP_BUTTON], 'disabled']);
        }

        return Support::wrapTag("<span $attributes>", $text);
366
367
368

    }

369
    /**
370
     * Build the whole link.
371
     *
372
     * @param string $str Qualifier with params. 'report'-syntax. F.e.:  u:www.example.com|P:home.gif|t:Home"
Carsten  Rose's avatar
Carsten Rose committed
373
     *
374
375
     * @return string The complete HTML encoded Link like
     *           <a href='http://example.com' class='external'><img src='iconf.gif' title='help text'>Description</a>
376
377
     * @throws CodeException
     * @throws UserFormException
378
     * @throws UserReportException
379
     */
Carsten  Rose's avatar
Carsten Rose committed
380
    public function renderLink($str) {
381

382
        $tokenGiven = array();
383
        $link = "";
384

385
        if (empty($str)) {
386
            return '';
387
        }
388

389
        $vars = $this->fillParameter($str, $tokenGiven);
Carsten  Rose's avatar
Carsten Rose committed
390
        $vars = $this->processParameter($vars, $tokenGiven);
391
        $mode = $this->getModeRender($vars, $tokenGiven);
392

Carsten  Rose's avatar
Carsten Rose committed
393
394
395
396
        if (isset($tokenGiven[TOKEN_DOWNLOAD]) && $tokenGiven[TOKEN_DOWNLOAD] === true) {
            $this->store->setVar(SYSTEM_DOWNLOAD_POPUP, DOWNLOAD_POPUP_REQUEST, STORE_SYSTEM);
        }

397
        // 0-6 URL, plain email
398
        // 10-14 encrypted email
399
        // 20-24 delete / ajax
400
401
402
403
404
405
406
407
408
409
        switch ($mode) {
            // 0: No Output
            case '0':
            case '10':
            case '20':
                break;

            // 1: 'text'
            case '1':
            case '11':
410
                $link = $this->wrapLinkTextOnly($vars, FINAL_CONTENT);
411
412
413
414
415
                break;

            // 2: 'url'
            case '2':
            case '12':
416
                $link = $this->wrapLinkTextOnly($vars, FINAL_HREF);
417
418
                break;

419
            // 3: <a href=url ...>url</a>
420
421
            case '3':
            case '13':
422
                $link = Support::wrapTag($vars[FINAL_ANCHOR], $vars[FINAL_HREF]);
423
424
                break;

425
            // 4: <a href=url ...>Text</a>
426
427
            case '4':
            case '14':
428
                $link = Support::wrapTag($vars[FINAL_ANCHOR], $vars[FINAL_CONTENT]);
429
                break;
430

431
432
433
434
            case '21':
            case '22':
            case '23':
            case '24':
435
436
437
438
                //TODO: Alter Code, umstellen auf JS Client von RO. Vorlage koennte 'Delete' in Subrecord sein.
                $link = "<a href=\"javascript: void(0);\" onClick=\"var del = new FR.Delete({recordId:'',sip:'',forward:'" .
                    $vars[NAME_PAGE] . "'});\" " . $vars[NAME_LINK_CLASS] . ">" . $vars[NAME_TEXT] . "</a>";
                break;
439
440
441

            // 5: plain text, no <span> around
            case '5':
442
                $link = $vars[NAME_TEXT];
443
444
445
446
447
                break;
            case '15':
            case '25':
                throw new UserReportException ("Mode not implemented. internal render mode=$mode", ERROR_UNKNOWN_MODE);

448
            // 6: plain url, no <span> around
449
450
451
452
453
454
            case '6':
                $link = $vars[FINAL_HREF];
                break;
            case '16':
            case '26':
                throw new UserReportException ("Mode not implemented. internal render mode=$mode", ERROR_UNKNOWN_MODE);
455
                break;
456
457
458
            case '8':
                $link = substr($vars[FINAL_HREF], 12); // strip 'index.php?s='
                break;
459
460
461

            default:
                throw new UserReportException ("Mode not implemented. internal render mode=$mode", ERROR_UNKNOWN_MODE);
462
463
        }

464
465
466
        return $link;
    }

467
    /**
468
     * Order $param. Parameter with priority are hardcoded at the moment.
469
470
     *
     * @param array $param
Carsten  Rose's avatar
Carsten Rose committed
471
     *
472
473
474
475
476
477
478
     * @return array
     */
    private function paramPriority(array $param) {
        $prio = array();
        $regular = array();

        foreach ($param as $value) {
479
480
481
482
483
            $key = substr($value, 0, 2);
            if ($key == TOKEN_DOWNLOAD . ':') {
                $prio[] = $value;
            } else {
                $regular[] = $value;
484
485
486
487
488
            }
        }

        return array_merge($prio, $regular);
    }
489

490
    /**
491
     * Iterate over all given token. Check for double definition.
492
     *
493
     * @param string $str
494
     * @param array $rcTokenGiven - return an array with found token.
Carsten  Rose's avatar
Carsten Rose committed
495
     *
496
     * @return array
497
498
     * @throws CodeException
     * @throws UserFormException
499
     * @throws UserReportException
500
     */
501
    public function fillParameter($str, array &$rcTokenGiven) {
502

Carsten  Rose's avatar
Carsten Rose committed
503
        // Define all possible vars: no more isset().
504
505
        $vars = $this->initVars();
        $flagArray = array();
506

Carsten  Rose's avatar
Carsten Rose committed
507
        // str="u:http://www.example.com|c:i|t:Hello World|q:Do you really want to delete the record 25:warn:yes:no"
Carsten  Rose's avatar
Carsten Rose committed
508
        $param = explode(PARAM_DELIMITER, $str);
509

510
511
        $param = $this->paramPriority($param);

Carsten  Rose's avatar
Carsten Rose committed
512
        // Parse all parameter, fill variables.
513
        foreach ($param as $item) {
514

515
            // Skip empty entries
516
517
518
            if ($item === '') {
                continue;
            }
519

520
521
522
523
            // u:www.example.com
            $arr = explode(":", $item, 2);
            $key = isset($arr[0]) ? $arr[0] : '';
            $value = isset($arr[1]) ? $arr[1] : '';
524

525
            // Bookkeeping defined parameter.
526
            if (isset($rcTokenGiven[$key])) {
527
528
                throw new UserReportException ("Multiple definitions for key '$key'", ERROR_MULTIPLE_DEFINITION);
            }
529
            $rcTokenGiven[$key] = true;
530
531

            if (!isset($this->tableVarName[$key])) {
532
533
                $msg[ERROR_MESSAGE_TO_USER] = "Unknown link qualifier: '$key' - did you forget the one character qualifier?";
                $msg[ERROR_MESSAGE_SUPPORT] = $str;
534
                throw new UserReportException (json_encode($msg), ERROR_UNKNOWN_LINK_QUALIFIER);
535
            }
Carsten  Rose's avatar
Carsten Rose committed
536

537
538
            $keyName = $this->tableVarName[$key]; // convert token to name

539
540
541
542
543
            if ($key == TOKEN_PAGE && $value == '') {
                $value = $this->store->getVar(TYPO3_PAGE_ID, STORE_TYPO3); // If no pageid|pagealias is defined, take current page
            }
            $value = Token::checkForEmptyValue($key, $value);

544
            $value = $this->checkValue($key, $value);
545

546
            // Store value
547
            if ((isset($rcTokenGiven[TOKEN_DOWNLOAD]) || isset($rcTokenGiven[TOKEN_COPY_TO_CLIPBOARD])) &&
548
549
                ($key == TOKEN_PAGE || $key == TOKEN_URL || $key == TOKEN_URL_PARAM || $key == TOKEN_UID ||
                    $key == TOKEN_FILE || $key == TOKEN_FILE_DEPRECATED)) {
Carsten  Rose's avatar
Carsten Rose committed
550

551
                $vars[NAME_COLLECT_ELEMENTS][] = $key . ':' . $value;
Carsten  Rose's avatar
Carsten Rose committed
552

553
                unset($rcTokenGiven[$key]); // Skip Bookkeeping for TOKEN_URL_PARAM | TOKEN_FILE | TOKEN_URL.
Carsten  Rose's avatar
Carsten Rose committed
554
555
                continue;
            } else {
556
557
558
559
                // TOKEN_GLYPH should not treated as an regular image. Same applies to the other Glyph symbols, but those don't have a value, and therefore do not fill $vars['image'].
                if ($key != TOKEN_GLYPH) {
                    $vars[$keyName] = $value;
                }
Carsten  Rose's avatar
Carsten Rose committed
560
561
            }

562
            // Check for double anchor or picture definition.
563
564
565
566
            if (isset($this->tokenMapping[$key])) {
                $type = $this->tokenMapping[$key];

                if (isset($flagArray[$type])) {
Carsten  Rose's avatar
Carsten Rose committed
567
                    throw new UserReportException ("Multiple definitions of url/mail/page/download or picture", ERROR_MULTIPLE_DEFINITION);
568
569
570
571
572
573
574
                }
                $flagArray[$type] = true;

//                if ($type === LINK_PICTURE) {
//                    $build = 'build' . strtoupper($keyName[0]) . substr($keyName, 1);
//                    $this->$build($vars, $value);
//                }
575
576
            }

577
578
579
580
            if (isset($this->callTable[$key])) {
                $build = $this->callTable[$key];
                $vars = $this->$build($vars, $value);
            }
581
582
        }

583
        // Download Link needs some extra work
Carsten  Rose's avatar
Carsten Rose committed
584
        if (isset($rcTokenGiven[TOKEN_DOWNLOAD]) && $rcTokenGiven[TOKEN_DOWNLOAD]) {
585
            $vars = $this->buildDownloadLate($vars);
586
587
        }

588
        // CopyToClipboard (Download) Link needs some extra work
Carsten  Rose's avatar
Carsten Rose committed
589
        if (isset($rcTokenGiven[TOKEN_COPY_TO_CLIPBOARD]) && $rcTokenGiven[TOKEN_COPY_TO_CLIPBOARD]) {
590
591
592
            $vars = $this->buildCopyToClipboardLate($vars);
        }

593
        // Final Checks
594
        $this->checkParam($rcTokenGiven, $vars);
595

596
597
598
        return $vars;
    }

599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
    /**
     * Cleans and make existing the standard vars used every time to render a link.
     *
     * @return array
     */
    private function initVars() {

        return [
            NAME_MAIL => '',
            NAME_URL => '',
            NAME_PAGE => '',

            NAME_TEXT => '',
            NAME_ALT_TEXT => '',
            NAME_BOOTSTRAP_BUTTON => '',
            NAME_IMAGE => '',
            NAME_IMAGE_TITLE => '',
            NAME_GLYPH => '',
            NAME_GLYPH_TITLE => '',
            NAME_QUESTION => '',
            NAME_TARGET => '',
            NAME_TOOL_TIP => '',
            NAME_TOOL_TIP_JS => '',
            NAME_URL_PARAM => '',
            NAME_EXTRA_CONTENT_WRAP => '',
            NAME_DOWNLOAD_MODE => '',
625
            NAME_COLLECT_ELEMENTS => array(),
626
            NAME_COPY_TO_CLIPBOARD => '',
627
628
629
630
631
632
633

            NAME_RENDER => '0',
            NAME_RIGHT => 'l',
            NAME_SIP => '0',
            NAME_ENCRYPTION => '0',
            NAME_DELETE => '',

634
            NAME_MONITOR => '0',
635
            NAME_COPY_TO_CLIPBOARD => '',
636

637
            NAME_LINK_CLASS => '', // class name
638
            NAME_LINK_CLASS_DEFAULT => '', // Depending of 'as page' or 'as url'. Only used if class is not explicit set.
639
640
641
642
643
644
645
646
647

            NAME_ACTION_DELETE => '',

            FINAL_HREF => '',
            FINAL_CONTENT => '',
            FINAL_SYMBOL => '',
            FINAL_TOOL_TIP => '',
            FINAL_CLASS => '',
            FINAL_QUESTION => '',
648
            FINAL_THUMBNAIL => '',
649
650
651
        ];

    }
652
653

    /**
654
655
     * Validate value for token
     *
656
657
     * @param $key
     * @param $value
Carsten  Rose's avatar
Carsten Rose committed
658
     *
659
660
661
662
     * @return mixed
     * @throws UserReportException
     */
    private function checkValue($key, $value) {
663

664
665
666
667
        switch ($key) {
            case TOKEN_ENCRYPTION:
            case TOKEN_SIP:
                if ($value !== '0' && $value !== '1') {
668
                    throw new UserReportException ("Invalid value for token '$key': '$value''", ERROR_INVALID_VALUE);
669
670
                }
                break;
671
672
673
674
675
676
677
678
679
680
            case TOKEN_ACTION_DELETE:
                switch ($value) {
                    case TOKEN_ACTION_DELETE_AJAX:
                    case TOKEN_ACTION_DELETE_REPORT:
                    case TOKEN_ACTION_DELETE_CLOSE:
                        break;
                    default:
                        throw new UserReportException ("Invalid value for token '$key': '$value''", ERROR_INVALID_VALUE);
                }
                break;
681
682
683
684
685
            case TOKEN_BOOTSTRAP_BUTTON:
                if ($value == '1') {
                    $value = 'btn-default';
                }
                break;
686
687
688
689
690
691
            default:
        }

        return $value;
    }

692
693
694
695
    /**
     * Check for double definition.
     *
     * @param array $tokenGiven
Carsten  Rose's avatar
Carsten Rose committed
696
     *
697
     * @param array $vars
698
699
     * @throws UserReportException
     */
Carsten  Rose's avatar
Carsten Rose committed
700
    private function checkParam(array $tokenGiven, array $vars) {
701
702
        $countLinkAnchor = 0;
        $countLinkPicture = 0;
703
        $countSources = 0;
704
705
706
707
708
709
710
711
712
713

        foreach ($tokenGiven as $token => $value) {
            if (isset($this->tokenMapping[$token])) {
                switch ($this->tokenMapping[$token]) {
                    case LINK_ANCHOR:
                        $countLinkAnchor++;
                        break;
                    case LINK_PICTURE:
                        $countLinkPicture++;
                        break;
714
715
716
717
718
                    case NAME_FILE:
                    case NAME_URL:
                    case NAME_PAGE:
                        $countSources++;
                        break;
719
720
721
722
723
724
725
                    default:
                        break;
                }
            }
        }

        if ($countLinkAnchor > 1) {
726
            throw new UserReportException ("Multiple URL / PAGE / MAILTO or COPY_TO_CLIPBOARD definition", ERROR_MULTIPLE_URL_PAGE_MAILTO_DEFINITION);
727
        }
728

729
730
731
732
733
734
        if ($countLinkPicture > 1) {
            throw new UserReportException ("Multiple definitions for token picture/bullet/check/edit...delete'" . TOKEN_PAGE . "'", ERROR_MULTIPLE_DEFINITION);
        }

        if (isset($tokenGiven[TOKEN_MAIL]) && isset($tokenGiven[TOKEN_TARGET])) {
            throw new UserReportException ("Token Mail and Target at the same time not possible'" . TOKEN_PAGE . "'", ERROR_MULTIPLE_DEFINITION);
735
        }
Carsten  Rose's avatar
Carsten Rose committed
736

737
        if (isset($tokenGiven[TOKEN_DOWNLOAD]) && count($vars[NAME_COLLECT_ELEMENTS]) == 0 && $countSources == 0) {
Carsten  Rose's avatar
Carsten Rose committed
738
739
            throw new UserReportException ("Missing element sources for download", ERROR_MISSING_REQUIRED_PARAMETER);
        }
740
    }
741
742

    /**
743
     * Compute final link parameter.
744
     *
Carsten  Rose's avatar
Carsten Rose committed
745
     * @param array $vars
Carsten  Rose's avatar
Carsten Rose committed
746
     * @param array $tokenGiven
Carsten  Rose's avatar
Carsten Rose committed
747
     *
748
749
750
     * @return array
     * @throws CodeException
     * @throws UserFormException
751
     * @throws UserReportException
752
     */
Carsten  Rose's avatar
Carsten Rose committed
753
    private function processParameter(array $vars, array $tokenGiven) {
754

Carsten  Rose's avatar
Carsten Rose committed
755
        $vars[FINAL_HREF] = $this->doHref($vars, $tokenGiven); // must be called before doToolTip()
756
757
758
        $vars[FINAL_TOOL_TIP] = $this->doToolTip($vars);
        $vars[FINAL_CLASS] = $this->doCssClass($vars);
        $vars[FINAL_SYMBOL] = $this->doSymbol($vars);
759
        $vars[FINAL_THUMBNAIL] = $this->doThumbnail($vars);
Carsten  Rose's avatar
Carsten Rose committed
760
        $vars[FINAL_CONTENT] = $this->doContent($vars, $vars[FINAL_CONTENT_PURE]); // must be called after doSymbol()
Carsten  Rose's avatar
Carsten Rose committed
761
        $vars[FINAL_QUESTION] = $this->doQuestion($vars);
762
        $vars[FINAL_ANCHOR] = $this->doAnchor($vars);
763

764
        return $vars;
765
    }
766

Carsten  Rose's avatar
Carsten Rose committed
767
    /**
768
769
770
     * Determine DownloadMode: explicit given or detected by given download sources.
     * Do some basic checks if parameter are correct.
     *
Carsten  Rose's avatar
Carsten Rose committed
771
     * @param array $vars
Carsten  Rose's avatar
Carsten Rose committed
772
     *
773
     * @return string - DOWNLOAD_MODE_PDF | DOWNLOAD_MODE_ZIP | DOWNLOAD_MODE_FILE | DOWNLOAD_MODE_EXCEL
Carsten  Rose's avatar
Carsten Rose committed
774
775
776
777
     * @throws UserFormException
     */
    private function getDownloadModeNCheck(array $vars) {

778
        $cnt = count($vars[NAME_COLLECT_ELEMENTS]);
Carsten  Rose's avatar
Carsten Rose committed
779
780
781
782
783
        $mode = $vars[NAME_DOWNLOAD_MODE];

        // Determine default.
        if ($mode == '') {
            if ($cnt == 1) {
784
                $mode = (substr($vars[NAME_COLLECT_ELEMENTS][0], 0, 1) == TOKEN_FILE_DEPRECATED) ? DOWNLOAD_MODE_FILE : DOWNLOAD_MODE_PDF;
Carsten  Rose's avatar
Carsten Rose committed
785
786
787
788
789
790
791
792
            } else {
                $mode = DOWNLOAD_MODE_PDF;
            }
        }

        // Do some checks.
        switch ($mode) {
            case DOWNLOAD_MODE_PDF:
793
            case DOWNLOAD_MODE_SAVE_PDF:
Carsten  Rose's avatar
Carsten Rose committed
794
            case DOWNLOAD_MODE_ZIP:
795
            case DOWNLOAD_MODE_EXCEL:
Carsten  Rose's avatar
Carsten Rose committed
796
797
798
                break;
            case DOWNLOAD_MODE_FILE:
                if ($cnt > 1) {
Carsten  Rose's avatar
Carsten Rose committed
799
                    throw new UserFormException("With 'downloadMode' = 'file' only one element source is allowed.", ERROR_DOWNLOAD_UNEXPECTED_NUMBER_OF_SOURCES);
Carsten  Rose's avatar
Carsten Rose committed
800
801
802
803
804
805
806
807
808
809
810
                }
                break;
            default:
                throw new UserFormException("Unknown mode: $mode", ERROR_UNKNOWN_MODE);
                break;

        }

        return $mode;
    }

811
    /**
812
     * Concat final HREF string. Might be used directly (load new page) or as an AJAX call (e.g. Download).
813
     *
814
     * @param array $vars
Carsten  Rose's avatar
Carsten Rose committed
815
     * @param array $tokenGiven
Carsten  Rose's avatar
Carsten Rose committed
816
     *
817
     * @return string
Carsten  Rose's avatar
Carsten Rose committed
818
     * @throws CodeException
819
     * @throws UserFormException
820
     * @throws UserReportException
821
     */
Carsten  Rose's avatar
Carsten Rose committed
822
    private function doHref(array &$vars, array $tokenGiven) {
823
        $urlNParam = '';
824

825
        // Download
Carsten  Rose's avatar
Carsten Rose committed
826
827
828
829
830
831
        if (isset($tokenGiven[TOKEN_DOWNLOAD])) {
            // Message in download popup.
            $altText = ($vars[NAME_ALT_TEXT] == '') ? 'Please wait' : addslashes($vars[NAME_ALT_TEXT]);
            $vars[NAME_EXTRA_CONTENT_WRAP] = str_replace(DOWNLOAD_POPUP_REPLACE_TEXT, $altText, $vars[NAME_EXTRA_CONTENT_WRAP]);
            $vars[NAME_EXTRA_CONTENT_WRAP] = str_replace(DOWNLOAD_POPUP_REPLACE_TITLE, 'Download: ' . addslashes($vars[NAME_DOWNLOAD]), $vars[NAME_EXTRA_CONTENT_WRAP]);

832
            $tmpUrlParam = array();
Carsten  Rose's avatar
Carsten Rose committed
833
834
            $tmpUrlParam[DOWNLOAD_MODE] = $this->getDownloadModeNCheck($vars);
            $tmpUrlParam[DOWNLOAD_EXPORT_FILENAME] = $vars[NAME_DOWNLOAD];
835
836
837
838
839
            $tmpUrlParam[SIP_DOWNLOAD_PARAMETER] = base64_encode(implode(PARAM_DELIMITER, $vars[NAME_COLLECT_ELEMENTS]));
            $vars[NAME_URL_PARAM] = KeyValueStringParser::unparse($tmpUrlParam, '=', '&');
        }

        // CopyToClipboard
840
        if (isset($tokenGiven[TOKEN_COPY_TO_CLIPBOARD]) && $vars[NAME_COPY_TO_CLIPBOARD] === '') {
841

842
843
844
845
846
847
//            $tmpUrlParam = array();
//            $tmpUrlParam[DOWNLOAD_MODE] = DOWNLOAD_MODE_FILE;
//            $tmpUrlParam[DOWNLOAD_OUTPUT_FORMAT] = DOWNLOAD_OUTPUT_FORMAT_JSON;
//            $tmpUrlParam[SIP_DOWNLOAD_PARAMETER] = base64_encode(implode(PARAM_DELIMITER, $vars[NAME_COLLECT_ELEMENTS]));
//            $vars[NAME_URL_PARAM] = KeyValueStringParser::unparse($tmpUrlParam, '=', '&');
            return '';
Carsten  Rose's avatar
Carsten Rose committed
848
849
        }

850
        // Delete
851
852
853
854
        if ($vars[NAME_ACTION_DELETE] !== '') {
            $vars[NAME_URL_PARAM] = $this->adjustDeleteParameter($vars[NAME_ACTION_DELETE], $vars[NAME_URL_PARAM]);
        }

855
        // Regular Link
856
        if ($vars[NAME_MAIL] === '') {
857

858
859
860
861
862
            if ($vars[NAME_SIP] === "1") {
                // Only for SIP encoded Links: append the current $dbIndexData to make record locking work even for records in dbIndexQfq
                if ($vars[NAME_URL_PARAM] != '') {
                    $vars[NAME_URL_PARAM] .= '&';
                }
863

864
865
866
                $vars[NAME_URL_PARAM] .= PARAM_DB_INDEX_DATA . '=' . $this->dbIndexData;
            }

867
            // Normalize '?pageAlias' to 'index.php?pageAlias'
868
869
870
871
            if (substr($vars[NAME_URL], 0, 1) === '?') {
                $vars[NAME_URL] = INDEX_PHP . $vars[NAME_URL];
            }

872
873
            // Either NAME_URL is empty or NAME_PAGE is empty
            $urlNParam = Support::concatUrlParam($vars[NAME_URL] . $vars[NAME_PAGE], $vars[NAME_URL_PARAM]);
874

Carsten  Rose's avatar
Carsten Rose committed
875
            if ($vars[NAME_SIP] === "1") {
876

877
                $paramArray = $this->sip->queryStringToSip($urlNParam, RETURN_ARRAY);
Carsten  Rose's avatar
Carsten Rose committed
878
                $urlNParam = $paramArray[SIP_SIP_URL];
879

880
                if (Support::findInSet(SYSTEM_SHOW_DEBUG_INFO_YES, $this->store->getVar(SYSTEM_SHOW_DEBUG_INFO, STORE_SYSTEM))) {
Carsten  Rose's avatar
Carsten Rose committed
881
                    $vars[NAME_TOOL_TIP] .= PHP_EOL . PHP_EOL . $this->sip->debugSip($paramArray);
882
883
                }
            }
884
        } else {
885
886
            // Link: MAILTO

887
            // If there is no encryption: handle the mailto as an ordinary URL
888
            if ($vars[NAME_ENCRYPTION] === '1') {
889
                throw new UserReportException ("Oops, sorry: encrypted mail not implemented ...", ERROR_NOT_IMPLEMENTED);
890
891
            } else {
                $urlNParam = "mailto:" . $vars[NAME_MAIL];
892
893
894
            }
        }

895
896
        return $urlNParam;
    }
897

898

899
900
901
    /**
     * @param $tokenActionDelete
     * @param $nameUrlParam
Carsten  Rose's avatar
Carsten Rose committed
902
     *
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
     * @return string
     * @throws UserReportException
     */
    private function adjustDeleteParameter($tokenActionDelete, $nameUrlParam) {

        $kvp = new KeyValueStringParser();

        // Split in: [p => 'r=100&table=note&..', 'D' => ''... ],
//        $param = $kvp->parse($nameUrlParam, ':', '|');

//        Support::setIfNotSet($param, TOKEN_URL_PARAM);

        switch ($tokenActionDelete) {
            case TOKEN_ACTION_DELETE_AJAX:
                // TODO: Implement for AJAX (subrecord)
                throw new UserReportException ("Not implemented!", ERROR_NOT_IMPLEMENTED);
                break;
            case TOKEN_ACTION_DELETE_REPORT:
                $nameUrlParam .= '&' . SIP_MODE_ANSWER . '=' . MODE_HTML;
                $nameUrlParam .= '&' . SIP_TARGET_URL . '=' . $_SERVER['REQUEST_URI'];
                break;
            case TOKEN_ACTION_DELETE_CLOSE:
                // TODO: Implement for Form (primary Record wird geloescht)
                throw new UserReportException ("Not implemented!", ERROR_NOT_IMPLEMENTED);
                break;
            default:
                throw new UserReportException ("Invalid value for token '" . TOKEN_ACTION_DELETE . "': '$tokenActionDelete''", ERROR_INVALID_VALUE);
        }

        return $nameUrlParam;
    }

935
936
937
938
    /**
     * Return $vars[NAME_TOOL_TIP]. If $vars[NAME_TOOL_TIP] is empty, set $vars[NAME_GLYPH_TITLE] as tooltip.
     *
     * @param array $vars
Carsten  Rose's avatar
Carsten Rose committed
939
     *
940
941
942
     * @return mixed
     */
    private function doToolTip(array $vars) {
943

944
945
946
        // Set default tooltip
        if ($vars[NAME_TOOL_TIP] == '') {
            $vars[NAME_TOOL_TIP] = $vars[NAME_GLYPH_TITLE];
Carsten  Rose's avatar
Carsten Rose committed
947
        }
948

949
        return $vars[NAME_TOOL_TIP];
950
    }
951

952
    /**
953
     * Parse CSS Class Settings
954
     *
955
     * @param array $vars
Carsten  Rose's avatar
Carsten Rose committed
956
     *
957
     * @return    string
958
     */
959
    private function doCssClass(array $vars) {
960

961
962
//        $class = ($vars[NAME_LINK_CLASS] === '') ? $vars[NAME_LINK_CLASS_DEFAULT] : $vars[NAME_LINK_CLASS];
        $class = $vars[NAME_LINK_CLASS];
Carsten  Rose's avatar
Carsten Rose committed
963

964
965
966
967
        switch ($class) {
            case TOKEN_CLASS_NONE:
                $class = '';
                break;
968
969
970
971
972
// #5302
//            case TOKEN_CLASS_INTERNAL:
//            case TOKEN_CLASS_EXTERNAL:
//                $class = $this->linkClassSelector[$vars[NAME_LINK_CLASS]] . ' ';
//                break;
973
974
975
            default:
                break;
        }
976

977
978
979
        if ($class === NO_CLASS) {
            $class = '';
        }
980

981
        //TODO: Old way to detect if BS Button should be rendered - should be replaced by 'b:'
982
        if ($vars[NAME_GLYPH] !== '' && $vars[NAME_EXTRA_CONTENT_WRAP] == '' && $vars[NAME_BOOTSTRAP_BUTTON] == '') {
983
            $class = 'btn btn-default ' . $class;
984
        }
985

986
        return implode(' ',