Link.php 60.9 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
 *
 *  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;

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

/*
 * a:AltText
37
 * A:Attribute
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
 * f:
47
48
49
 * F:File
 * g:target
 * G:Glyph
50
 * h:
51
 * H:Help
52
 * i:
53
 * I:information
54
55
56
57
58
59
 * j:
 * J:
 * k:
 * K:
 * l:
 * L:
60
61
 * m:mailto
 * M:Mode
62
 * n:
63
64
 * N:new
 * o:ToolTip
65
 * O:Monitor
66
 * p:page
67
 * P:picture       [file]
68
 * q:question  <text>
69
 * Q:
70
71
 * r:render
 * R:right
Carsten  Rose's avatar
Carsten Rose committed
72
 * s:sip
73
74
75
76
77
 * S:Show
 * t:text
 * T:Thumbnail
 * u:url
 * U:URL Param
78
79
80
 * v:
 * V:
 * w:
81
82
 * W:Dimension
 * x:Delete
83
 * X:
84
 * y:Copy to clipboard
85
86
87
 * Y:
 * z:DropDown Menu
 * Z:
88
 *
89
90
 */

91
92
93
const NAME_URL = 'url';
const NAME_MAIL = 'mail';
const NAME_PAGE = 'page';
94
const NAME_UID = 'uid';
95
const NAME_TEXT = 'text';
96
const NAME_DROPDOWN = 'dropdown';
Carsten  Rose's avatar
Carsten Rose committed
97
const NAME_DOWNLOAD = DOWNLOAD_EXPORT_FILENAME;
98
const NAME_COLLECT_ELEMENTS = 'downloadElements';  // array with element sources
Carsten  Rose's avatar
Carsten Rose committed
99
const NAME_DOWNLOAD_MODE = 'mode';
100
const NAME_BOOTSTRAP_BUTTON = 'bootstrapButton';
101
102
const NAME_ALT_TEXT = 'altText';
const NAME_TOOL_TIP = 'toolTip';
103
const NAME_TOOL_TIP_JS = 'toolTipJs';
104
105
const NAME_IMAGE = 'image';
const NAME_IMAGE_TITLE = 'imageTitle';
106
107
const NAME_GLYPH = 'glyph';
const NAME_GLYPH_TITLE = 'glyphTitle';
108
109
110
111
112
113
114
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
115
const NAME_SIP = 'sip';
116
const NAME_URL_PARAM = 'param';
117
const NAME_RIGHT = 'picturePositionRight';
118
const NAME_ACTION_DELETE = 'actionDelete';
Carsten  Rose's avatar
Carsten Rose committed
119
const NAME_EXTRA_CONTENT_WRAP = 'extraContentWrap';
Carsten  Rose's avatar
Carsten Rose committed
120
const NAME_FILE = 'file';
121
122
const NAME_THUMBNAIL = 'thumbnail';
const NAME_THUMBNAIL_DIMENSION = 'thumbnailDimension';
123
const NAME_COPY_TO_CLIPBOARD = 'copyToClipBoard';
124
const NAME_MONITOR = 'monitor';
125
const NAME_ATTRIBUTE = 'attribute';
126
127
128
129

const FINAL_HREF = 'finalHref';
const FINAL_ANCHOR = 'finalAnchor';
const FINAL_CONTENT = 'finalContent';
Carsten  Rose's avatar
Carsten Rose committed
130
const FINAL_CONTENT_PURE = 'finalContentPure';
131
132
133
134
const FINAL_SYMBOL = 'finalSymbol';
const FINAL_TOOL_TIP = 'finalToolTip';
const FINAL_CLASS = 'finalClass';
const FINAL_QUESTION = 'finalQuestion';
135
const FINAL_THUMBNAIL = 'finalThumbnail';
136

137
138
139
const LINK_ANCHOR = 'linkAnchor';
const LINK_PICTURE = 'linkPicture';

140
141
const NO_CLASS = 'no_class';

Carsten  Rose's avatar
Carsten Rose committed
142
143
const DEFAULT_BULLET_COLOR = 'green';
const DEFAULT_CHECK_COLOR = 'green';
144
const DEFAULT_RENDER_MODE = '0';
Carsten  Rose's avatar
Carsten Rose committed
145
146
const DEFAULT_QUESTION_TEXT = 'Please confirm';
const DEFAULT_QUESTION_LEVEL = 'info';
147
const DEFAULT_ACTION_DELETE = 'r';
Carsten  Rose's avatar
Carsten Rose committed
148

Carsten  Rose's avatar
Carsten Rose committed
149
150
151
152
const QUESTION_INDEX_TEXT = 0;
const QUESTION_INDEX_LEVEL = 1;
const QUESTION_INDEX_BUTTON_OK = 2;
const QUESTION_INDEX_BUTTON_FALSE = 3;
153
154
const QUESTION_INDEX_TIMEOUT = 4;
const QUESTION_INDEX_FLAG_MODAL = 5;
Carsten  Rose's avatar
Carsten Rose committed
155

156
157
158
159
/**
 * Class Link
 * @package qfq
 */
160
161
class Link {

Carsten  Rose's avatar
Carsten Rose committed
162
    /**
163
     * @var Sip
Carsten  Rose's avatar
Carsten Rose committed
164
165
166
     */
    private $sip = null;

167
168
169
170
171
    /**
     * @var Store
     */
    private $store = null;

172
173
174
175
176
    /**
     * @var Thumbnail
     */
    private $thumbnail = null;

177
178
    private $dbIndexData = false;

Carsten  Rose's avatar
Carsten Rose committed
179
    private $phpUnit;
180
    private $renderControl = array();
181
//    private $linkClassSelector = array(TOKEN_CLASS_INTERNAL => "internal ", TOKEN_CLASS_EXTERNAL => "external ");
182
183
//    private $cssLinkClassInternal = '';
//    private $cssLinkClassExternal = '';
Carsten  Rose's avatar
Carsten Rose committed
184
    private $ttContentUid = '';
185

186
    private $callTable = [
Carsten  Rose's avatar
Carsten Rose committed
187
188
189
        TOKEN_URL => 'buildUrl',
        TOKEN_MAIL => 'buildMail',
        TOKEN_PAGE => 'buildPage',
190
        TOKEN_COPY_TO_CLIPBOARD => 'buildCopyToClipboard',
Carsten  Rose's avatar
Carsten Rose committed
191
        TOKEN_DOWNLOAD => 'buildDownload',
192
        TOKEN_DROPDOWN => 'buildDropdown',
Carsten  Rose's avatar
Carsten Rose committed
193
194
195
196
197
198
199
200
201
202
203
204
        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',
205
        TOKEN_FILE_DEPRECATED => 'buildFile',
Carsten  Rose's avatar
Carsten Rose committed
206
        TOKEN_GLYPH => 'buildGlyph',
207
        TOKEN_BOOTSTRAP_BUTTON => 'buildBootstrapButton',
208
209
    ];

210
    private $tableVarName = [
Carsten  Rose's avatar
Carsten Rose committed
211
212
213
        TOKEN_URL => NAME_URL,
        TOKEN_MAIL => NAME_MAIL,
        TOKEN_PAGE => NAME_PAGE,
214
        TOKEN_UID => NAME_UID,
215
        TOKEN_DROPDOWN => NAME_DROPDOWN,
Carsten  Rose's avatar
Carsten Rose committed
216
217
218
219
        TOKEN_DOWNLOAD => NAME_DOWNLOAD,
        TOKEN_DOWNLOAD_MODE => NAME_DOWNLOAD_MODE,
        TOKEN_TEXT => NAME_TEXT,
        TOKEN_ALT_TEXT => NAME_ALT_TEXT,
220
        TOKEN_BOOTSTRAP_BUTTON => NAME_BOOTSTRAP_BUTTON,
Carsten  Rose's avatar
Carsten Rose committed
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
        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,
242
        TOKEN_FILE_DEPRECATED => NAME_FILE,
243
244
        TOKEN_THUMBNAIL => NAME_THUMBNAIL,
        TOKEN_THUMBNAIL_DIMENSION => NAME_THUMBNAIL_DIMENSION,
245
        TOKEN_COPY_TO_CLIPBOARD => NAME_COPY_TO_CLIPBOARD,
246
        TOKEN_ATTRIBUTE => NAME_ATTRIBUTE,
247
248
249
250
251
252
253
254

        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,
255
256
    ];

Carsten  Rose's avatar
Carsten Rose committed
257
    // Used to find double definitions.
258
    private $tokenMapping = [
Carsten  Rose's avatar
Carsten Rose committed
259
260
261
        TOKEN_URL => LINK_ANCHOR,
        TOKEN_MAIL => LINK_ANCHOR,
        TOKEN_PAGE => LINK_ANCHOR,
262
        TOKEN_UID => LINK_ANCHOR,
Carsten  Rose's avatar
Carsten Rose committed
263
        TOKEN_DOWNLOAD => LINK_ANCHOR,
264
        TOKEN_FILE => NAME_FILE,
265
        TOKEN_COPY_TO_CLIPBOARD => LINK_ANCHOR,
Carsten  Rose's avatar
Carsten Rose committed
266

267
        TOKEN_PICTURE => LINK_PICTURE,
Carsten  Rose's avatar
Carsten Rose committed
268
269
270
271
272
273
274
275
276
        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,
277
    ];
278

279
280
281
    /**
     * __construct
     *
282
     * @param Sip $sip
283
     * @param string $dbIndexData
284
     * @param bool $phpUnit
285
286
     * @throws CodeException
     * @throws UserFormException
287
     * @throws UserReportException
288
     */
289
    public function __construct(Sip $sip, $dbIndexData = DB_INDEX_DEFAULT, $phpUnit = false) {
Carsten  Rose's avatar
Carsten Rose committed
290
        $this->phpUnit = $phpUnit;
291
292
293
294
295

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

Carsten  Rose's avatar
Carsten Rose committed
296
        $this->sip = $sip;
Carsten  Rose's avatar
Carsten Rose committed
297
        $this->store = Store::getInstance('', $phpUnit);
298
299
//        $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
300
        $this->ttContentUid = $this->store->getVar(TYPO3_TT_CONTENT_UID, STORE_TYPO3);
301
        $this->dbIndexData = $dbIndexData;
302
303
304
        /*
         * mode:
         * 0: no output
305
306
         * 1: <span title='...'>text</span>   (no href)
         * 2: <span title='...'>url</span>    (no href)
307
308
         * 3: <a href=url>url</a>
         * 4: <a href=url>Text</a>
309
310
         * 5: text
         * 6: url
311
         * 8: SIP only - 's=badcaffee1234'
312
         *
Carsten  Rose's avatar
Carsten Rose committed
313
         *  r=render mode, u=url, t:text and/or image.
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
         *
         *                  [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;
347

348
349
350
351
352
        $this->renderControl[6][0][0] = 0;
        $this->renderControl[6][0][1] = 5;
        $this->renderControl[6][1][0] = 0;
        $this->renderControl[6][1][1] = 5;

353
354
355
356
        $this->renderControl[7][0][0] = 0;
        $this->renderControl[7][0][1] = 0;
        $this->renderControl[7][1][0] = 6;
        $this->renderControl[7][1][1] = 6;
357

358
359
360
361
        $this->renderControl[8][0][0] = 0;
        $this->renderControl[8][0][1] = 0;
        $this->renderControl[8][1][0] = 8;
        $this->renderControl[8][1][1] = 8;
362
    }
363

364
    /**
365
     * In render mode 3,4,5 there is no '<a href ...>'. Nevertheless, tooltip and BS Button should be displayed.
366
     * Do this by applying a '<span>' attribute around the text.
367
     *
368
369
370
371
     * @param array $vars
     * @param       $keyName
     *
     * @return string
372
     * @throws CodeException
373
     */
374
375
376
    private function wrapLinkTextOnly(array $vars, $keyName) {

        $text = $vars[$keyName];
377
        if ($vars[NAME_BOOTSTRAP_BUTTON] == '' && $vars[FINAL_TOOL_TIP] == '' && $vars[NAME_ATTRIBUTE] == '') {
378
379
380
            return $text;
        }

381
        $attributes = Support::doAttribute('title', $vars[FINAL_TOOL_TIP]);
382
383
384
        if ($vars[NAME_ATTRIBUTE] != '') {
            $attributes .= $vars[NAME_ATTRIBUTE] . ' ';
        }
385
386
387
388
389
390

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

        return Support::wrapTag("<span $attributes>", $text);
391
392
393

    }

394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
    /**
     * @param string $str
     *
     * @return string
     * @throws CodeException
     * @throws UserFormException
     * @throws UserReportException
     */
    private function processDropdown($str) {
        $menuHtml = '';
        $menuEntryStrArr = array();
        $menuEntryLinkArr = array();
        $tokenCollect = array();
        $dropdownStr = null;

        $paramArr = KeyValueStringParser::explodeEscape(PARAM_DELIMITER, $str);

        // Iterate over token. Find delimiter to separate dropdown definition and all menu entries.
        foreach ($paramArr as $tokenStr) {
            $tokenArr = explode(PARAM_TOKEN_DELIMITER, $tokenStr, 2);
            switch ($tokenArr[0] ?? '') {
                // No further need: skip
                case TOKEN_DROPDOWN:
                    break;

                // Indicator to start menu entry: force a flush of existing token and start a new round.
                case TOKEN_URL:
                case TOKEN_MAIL:
                case TOKEN_PAGE:
                case TOKEN_DOWNLOAD:
                case TOKEN_COPY_TO_CLIPBOARD:
                case '':
                    // New menu entry
                    $menuEntryStrArr[] = implode(PARAM_DELIMITER, $tokenCollect);
                    $tokenCollect = array();
                    $tokenCollect[] = $tokenStr;
                    break;

                default:
                    $tokenCollect[] = $tokenStr;
            }
        }

        // Flush remaining element
        $menuEntryStrArr[] = implode(PARAM_DELIMITER, $tokenCollect);
        $dropdownStr = array_shift($menuEntryStrArr);

        // For each menu entry get the link
        foreach ($menuEntryStrArr as $str) {
            $menuEntryLinkArr[] = $this->renderLink($str);
        }

        return $this->renderDropdown($dropdownStr, $menuEntryLinkArr);

    }

    /**
     * Start
     * <span class="dropdown">
     *   <span class="glyphicon glyphicon-option-vertical dropdown-toggle" id="dropdownMenu11" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
     *   </span>
     *   <ul class="dropdown-menu" aria-labelledby="dropdownMenu11">
     *    <li><a href="#">Action</a></li>
     *    <li><a href="#">Another action</a></li>
     *    <li><a href="#">Something else here</a></li>
     *    <li role="separator" class="divider"></li>
     *    <li><a href="#">Separated link</a></li>
     *   </ul>
     * </span>
     * End
     *
     * @param $dropdownStr
     * @param $menuEntryLinkArr
     * @return string
     */
    private function renderDropdown($dropdownStr, $menuEntryLinkArr) {
        static $count = 0;
        $time = microtime(false);

        if (defined('PHPUNIT_QFQ')) {
            $time = '0.123 1234';
        }
        $htmlId = $time . $count++;

        $tmp = '';
        foreach ($menuEntryLinkArr as $link) {
            $tmp .= '<li>' . $link . '</li>';
        }

        $ul = Support::wrapTag('<ul class="dropdown-menu" aria-labelledby="' . $htmlId . '">', $tmp);
        $symbol = '<span class="glyphicon glyphicon-option-vertical dropdown-toggle" id="' . $htmlId . '" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true"></span>';

        return Support::wrapTag('<span class="dropdown">', $symbol . $ul);
    }

489
    /**
490
     * Build the whole link.
491
     *
492
     * @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
493
     *
494
495
     * @return string The complete HTML encoded Link like
     *           <a href='http://example.com' class='external'><img src='iconf.gif' title='help text'>Description</a>
496
497
     * @throws CodeException
     * @throws UserFormException
498
     * @throws UserReportException
499
     */
Carsten  Rose's avatar
Carsten Rose committed
500
    public function renderLink($str) {
501

502
        $tokenGiven = array();
503
        $link = "";
504

505
        if (empty($str)) {
506
            return '';
507
        }
508

509
510
511
512
513
        // Check for dropdown menu
        if (($str[0] ?? '') == TOKEN_DROPDOWN) {
            return $this->processDropdown($str);
        }

514
        $vars = $this->fillParameter($str, $tokenGiven);
Carsten  Rose's avatar
Carsten Rose committed
515
        $vars = $this->processParameter($vars, $tokenGiven);
516
        $mode = $this->getModeRender($vars, $tokenGiven);
517

Carsten  Rose's avatar
Carsten Rose committed
518
519
520
521
        if (isset($tokenGiven[TOKEN_DOWNLOAD]) && $tokenGiven[TOKEN_DOWNLOAD] === true) {
            $this->store->setVar(SYSTEM_DOWNLOAD_POPUP, DOWNLOAD_POPUP_REQUEST, STORE_SYSTEM);
        }

522
        // 0-6 URL, plain email
523
        // 10-14 encrypted email
524
        // 20-24 delete / ajax
525
526
527
528
529
530
531
532
533
534
        switch ($mode) {
            // 0: No Output
            case '0':
            case '10':
            case '20':
                break;

            // 1: 'text'
            case '1':
            case '11':
535
                $link = $this->wrapLinkTextOnly($vars, FINAL_CONTENT);
536
537
538
539
540
                break;

            // 2: 'url'
            case '2':
            case '12':
541
                $link = $this->wrapLinkTextOnly($vars, FINAL_HREF);
542
543
                break;

544
            // 3: <a href=url ...>url</a>
545
546
            case '3':
            case '13':
547
                $link = Support::wrapTag($vars[FINAL_ANCHOR], $vars[FINAL_HREF]);
548
549
                break;

550
            // 4: <a href=url ...>Text</a>
551
552
            case '4':
            case '14':
553
                $link = Support::wrapTag($vars[FINAL_ANCHOR], $vars[FINAL_CONTENT]);
554
                break;
555

556
557
558
559
            case '21':
            case '22':
            case '23':
            case '24':
560
561
562
563
                //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;
564
565
566

            // 5: plain text, no <span> around
            case '5':
567
                $link = $vars[NAME_TEXT];
568
569
570
571
572
                break;
            case '15':
            case '25':
                throw new UserReportException ("Mode not implemented. internal render mode=$mode", ERROR_UNKNOWN_MODE);

573
            // 6: plain url, no <span> around
574
575
576
577
578
579
            case '6':
                $link = $vars[FINAL_HREF];
                break;
            case '16':
            case '26':
                throw new UserReportException ("Mode not implemented. internal render mode=$mode", ERROR_UNKNOWN_MODE);
580
                break;
581
582
583
            case '8':
                $link = substr($vars[FINAL_HREF], 12); // strip 'index.php?s='
                break;
584
585
586

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

589
590
591
        return $link;
    }

592
    /**
593
     * Order $param. Parameter with priority are hardcoded at the moment.
594
595
     *
     * @param array $param
Carsten  Rose's avatar
Carsten Rose committed
596
     *
597
598
599
600
601
602
603
     * @return array
     */
    private function paramPriority(array $param) {
        $prio = array();
        $regular = array();

        foreach ($param as $value) {
604
605
606
607
608

            if ($value == '') {
                continue;
            }

609
            $key = substr($value, 0, 2);
610
611

            if (strlen($key) == 1) {
612
                $key .= PARAM_TOKEN_DELIMITER;
613
614
            }

615
            if ($key == TOKEN_DOWNLOAD . PARAM_TOKEN_DELIMITER) {
616
617
618
                $prio[] = $value;
            } else {
                $regular[] = $value;
619
620
621
622
623
            }
        }

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

625
    /**
626
     * Iterate over all given token. Check for double definition.
627
     *
628
     * @param string $str
629
     * @param array $rcTokenGiven - return an array with found token.
Carsten  Rose's avatar
Carsten Rose committed
630
     *
631
     * @return array
632
633
     * @throws CodeException
     * @throws UserFormException
634
     * @throws UserReportException
635
     */
636
    public function fillParameter($str, array &$rcTokenGiven) {
637

Carsten  Rose's avatar
Carsten Rose committed
638
        // Define all possible vars: no more isset().
639
640
        $vars = $this->initVars();
        $flagArray = array();
641

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

646
647
        $param = $this->paramPriority($param);

Carsten  Rose's avatar
Carsten Rose committed
648
        // Parse all parameter, fill variables.
649
        foreach ($param as $item) {
650

651
            // Skip empty entries
652
653
654
            if ($item === '') {
                continue;
            }
655

656
657
658
659
            // u:www.example.com
            $arr = explode(":", $item, 2);
            $key = isset($arr[0]) ? $arr[0] : '';
            $value = isset($arr[1]) ? $arr[1] : '';
660

661
            // Bookkeeping defined parameter.
662
            if (isset($rcTokenGiven[$key])) {
663
664
                throw new UserReportException ("Multiple definitions for key '$key'", ERROR_MULTIPLE_DEFINITION);
            }
665
            $rcTokenGiven[$key] = true;
666
667

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

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

675
676
677
678
679
            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);

680
            $value = $this->checkValue($key, $value);
681

682
            // Store value
683
            if ((isset($rcTokenGiven[TOKEN_DOWNLOAD]) || isset($rcTokenGiven[TOKEN_COPY_TO_CLIPBOARD])) &&
684
685
                ($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
686

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

689
                unset($rcTokenGiven[$key]); // Skip Bookkeeping for TOKEN_URL_PARAM | TOKEN_FILE | TOKEN_URL.
Carsten  Rose's avatar
Carsten Rose committed
690
691
                continue;
            } else {
692
693
694
695
                // 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
696
697
            }

698
            // Check for double anchor or picture definition.
699
700
701
702
            if (isset($this->tokenMapping[$key])) {
                $type = $this->tokenMapping[$key];

                if (isset($flagArray[$type])) {
Carsten  Rose's avatar
Carsten Rose committed
703
                    throw new UserReportException ("Multiple definitions of url/mail/page/download or picture", ERROR_MULTIPLE_DEFINITION);
704
705
706
707
708
709
710
                }
                $flagArray[$type] = true;

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

713
714
715
716
            if (isset($this->callTable[$key])) {
                $build = $this->callTable[$key];
                $vars = $this->$build($vars, $value);
            }
717
718
        }

719
        // Download Link needs some extra work
Carsten  Rose's avatar
Carsten Rose committed
720
        if (isset($rcTokenGiven[TOKEN_DOWNLOAD]) && $rcTokenGiven[TOKEN_DOWNLOAD]) {
721
            $vars = $this->buildDownloadLate($vars);
722
723
        }

724
        // CopyToClipboard (Download) Link needs some extra work
Carsten  Rose's avatar
Carsten Rose committed
725
        if (isset($rcTokenGiven[TOKEN_COPY_TO_CLIPBOARD]) && $rcTokenGiven[TOKEN_COPY_TO_CLIPBOARD]) {
726
727
728
            $vars = $this->buildCopyToClipboardLate($vars);
        }

729
730
731
732
733
        // Check for special default setting.
        if ($vars[NAME_SIP] === false) {
            $vars[NAME_SIP] = "0";
        }

734
        // Final Checks
735
        $this->checkParam($rcTokenGiven, $vars);
736

737
738
739
        return $vars;
    }

740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
    /**
     * 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 => '',
766
            NAME_COLLECT_ELEMENTS => array(),
767
            NAME_COPY_TO_CLIPBOARD => '',
768
            NAME_ATTRIBUTE => '',
769
770
771

            NAME_RENDER => '0',
            NAME_RIGHT => 'l',
772
            NAME_SIP => false,
773
774
775
            NAME_ENCRYPTION => '0',
            NAME_DELETE => '',

776
            NAME_MONITOR => '0',
777
            NAME_COPY_TO_CLIPBOARD => '',
778

779
            NAME_LINK_CLASS => '', // class name
780
            NAME_LINK_CLASS_DEFAULT => '', // Depending of 'as page' or 'as url'. Only used if class is not explicit set.
781
782
783
784
785
786
787
788
789

            NAME_ACTION_DELETE => '',

            FINAL_HREF => '',
            FINAL_CONTENT => '',
            FINAL_SYMBOL => '',
            FINAL_TOOL_TIP => '',
            FINAL_CLASS => '',
            FINAL_QUESTION => '',
790
            FINAL_THUMBNAIL => '',
791
792
793
        ];

    }
794
795

    /**
796
797
     * Validate value for token
     *
798
799
     * @param $key
     * @param $value
Carsten  Rose's avatar
Carsten Rose committed
800
     *
801
802
803
804
     * @return mixed
     * @throws UserReportException
     */
    private function checkValue($key, $value) {
805

806
807
808
809
        switch ($key) {
            case TOKEN_ENCRYPTION:
            case TOKEN_SIP:
                if ($value !== '0' && $value !== '1') {
810
                    throw new UserReportException ("Invalid value for token '$key': '$value''", ERROR_INVALID_VALUE);
811
812
                }
                break;
813
814
815
816
817
818
819
820
821
822
            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;
823
824
825
826
827
            case TOKEN_BOOTSTRAP_BUTTON:
                if ($value == '1') {
                    $value = 'btn-default';
                }
                break;
828
829
830
831
832
833
            default:
        }

        return $value;
    }

834
835
836
837
    /**
     * Check for double definition.
     *
     * @param array $tokenGiven
Carsten  Rose's avatar
Carsten Rose committed
838
     *
839
     * @param array $vars
840
841
     * @throws UserReportException
     */
Carsten  Rose's avatar
Carsten Rose committed
842
    private function checkParam(array $tokenGiven, array $vars) {
843
844
        $countLinkAnchor = 0;
        $countLinkPicture = 0;
845
        $countSources = 0;
846
847
848
849
850
851
852
853
854
855

        foreach ($tokenGiven as $token => $value) {
            if (isset($this->tokenMapping[$token])) {
                switch ($this->tokenMapping[$token]) {
                    case LINK_ANCHOR:
                        $countLinkAnchor++;
                        break;
                    case LINK_PICTURE:
                        $countLinkPicture++;
                        break;
856
857
858
859
860
                    case NAME_FILE:
                    case NAME_URL:
                    case NAME_PAGE:
                        $countSources++;
                        break;
861
862
863
864
865
866
867
                    default:
                        break;
                }
            }
        }

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

871
872
873
874
875
876
        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);
877
        }
Carsten  Rose's avatar
Carsten Rose committed
878

879
        if (isset($tokenGiven[TOKEN_DOWNLOAD]) && count($vars[NAME_COLLECT_ELEMENTS]) == 0 && $countSources == 0) {
Carsten  Rose's avatar
Carsten Rose committed
880
881
            throw new UserReportException ("Missing element sources for download", ERROR_MISSING_REQUIRED_PARAMETER);
        }
882
    }
883
884

    /**
885
     * Compute final link parameter.
886
     *
Carsten  Rose's avatar
Carsten Rose committed
887
     * @param array $vars
Carsten  Rose's avatar
Carsten Rose committed
888
     * @param array $tokenGiven
Carsten  Rose's avatar
Carsten Rose committed
889
     *
890
891
892
     * @return array
     * @throws CodeException
     * @throws UserFormException
893
     * @throws UserReportException
894
     */
Carsten  Rose's avatar
Carsten Rose committed
895
    private function processParameter(array $vars, array $tokenGiven) {
896

Carsten  Rose's avatar
Carsten Rose committed
897
        $vars[FINAL_HREF] = $this->doHref($vars, $tokenGiven); // must be called before doToolTip()
898
899
900
        $vars[FINAL_TOOL_TIP] = $this->doToolTip($vars);
        $vars[FINAL_CLASS] = $this->doCssClass($vars);
        $vars[FINAL_SYMBOL] = $this->doSymbol($vars);
901
        $vars[FINAL_THUMBNAIL] = $this->doThumbnail($vars);
Carsten  Rose's avatar
Carsten Rose committed
902
        $vars[FINAL_CONTENT] = $this->doContent($vars, $vars[FINAL_CONTENT_PURE]); // must be called after doSymbol()
Carsten  Rose's avatar
Carsten Rose committed
903
        $vars[FINAL_QUESTION] = $this->doQuestion($vars);
904
        $vars[FINAL_ANCHOR] = $this->doAnchor($vars);
905

906
        return $vars;
907
    }
908

Carsten  Rose's avatar
Carsten Rose committed
909
    /**
910
911
912
     * 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
913
     * @param array $vars
Carsten  Rose's avatar
Carsten Rose committed
914
     *
915
     * @return string - DOWNLOAD_MODE_PDF | DOWNLOAD_MODE_ZIP | DOWNLOAD_MODE_FILE | DOWNLOAD_MODE_EXCEL
Carsten  Rose's avatar
Carsten Rose committed
916
917
918
919
     * @throws UserFormException
     */
    private function getDownloadModeNCheck(array $vars) {

920
        $cnt = count($vars[NAME_COLLECT_ELEMENTS]);
Carsten  Rose's avatar
Carsten Rose committed
921
922
923
924
925
        $mode = $vars[NAME_DOWNLOAD_MODE];

        // Determine default.
        if ($mode == '') {
            if ($cnt == 1) {
926
                $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
927
928
929
930
931
932
933
934
            } else {
                $mode = DOWNLOAD_MODE_PDF;
            }
        }

        // Do some checks.
        switch ($mode) {
            case DOWNLOAD_MODE_PDF:
935
            case DOWNLOAD_MODE_SAVE_PDF:
Carsten  Rose's avatar
Carsten Rose committed
936
            case DOWNLOAD_MODE_ZIP:
937
            case DOWNLOAD_MODE_EXCEL:
Carsten  Rose's avatar
Carsten Rose committed
938
939
940
                break;
            case DOWNLOAD_MODE_FILE:
                if ($cnt > 1) {
Carsten  Rose's avatar
Carsten Rose committed
941
                    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
942
943
944
945
946
947
948
949
950
951
952
                }
                break;
            default:
                throw new UserFormException("Unknown mode: $mode", ERROR_UNKNOWN_MODE);
                break;

        }

        return $mode;
    }

953
    /**
954
     * Concat final HREF string. Might be used directly (load new page) or as an AJAX call (e.g. Download).
955
     *
956
     * @param array $vars
Carsten  Rose's avatar
Carsten Rose committed
957
     * @param array $tokenGiven
Carsten  Rose's avatar
Carsten Rose committed
958
     *
959
     * @return string
Carsten  Rose's avatar
Carsten Rose committed
960
     * @throws CodeException
961
     * @throws UserFormException
962
     * @throws UserReportException
963
     */
Carsten  Rose's avatar
Carsten Rose committed
964
    private function doHref(array &$vars, array $tokenGiven) {
965
        $urlNParam = '';
966

967
        // Download
Carsten  Rose's avatar
Carsten Rose committed
968
969
970
971
972
973
        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]);

974
            $tmpUrlParam = array();
Carsten  Rose's avatar
Carsten Rose committed
975
976
            $tmpUrlParam[DOWNLOAD_MODE] = $this->getDownloadModeNCheck($vars);
            $tmpUrlParam[DOWNLOAD_EXPORT_FILENAME] = $vars[NAME_DOWNLOAD];
977
978
979
980
981
            $tmpUrlParam[SIP_DOWNLOAD_PARAMETER] = base64_encode(implode(PARAM_DELIMITER, $vars[NAME_COLLECT_ELEMENTS]));
            $vars[NAME_URL_PARAM] = KeyValueStringParser::unparse($tmpUrlParam, '=', '&');
        }

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

984
985
986
987
988
989
//            $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
990
991
        }

992
        // Delete
993
994
995
996
        if ($vars[NAME_ACTION_DELETE] !== '') {
            $vars[NAME_URL_PARAM] = $this->adjustDeleteParameter($vars[NAME_ACTION_DELETE], $vars[NAME_URL_PARAM]);
        }

997
        // Regular Link
998
        if ($vars[NAME_MAIL] === '') {
999

1000
1001
1002
1003
1004
            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] .= '&';
                }
1005

1006
1007
1008
                $vars[NAME_URL_PARAM] .= PARAM_DB_INDEX_DATA . '=' . $this->dbIndexData;
            }

1009
            // Normalize '?pageAlias' to 'index.php?pageAlias'
1010
1011
1012
1013
            if (substr($vars[NAME_URL], 0, 1) === '?') {
                $vars[NAME_URL] = INDEX_PHP . $vars[NAME_URL];
            }

1014
1015
            // Either NAME_URL is empty or NAME_PAGE is empty
            $urlNParam = Support::concatUrlParam($vars[NAME_URL] . $vars[NAME_PAGE], $vars[NAME_URL_PARAM]);
1016

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

1019
                $paramArray = $this->sip->queryStringToSip($urlNParam, RETURN_ARRAY);