Test Failed
Branch master (137376)
by Tymoteusz
20:39
created

ContentObjectRenderer::stdWrap_parseFunc()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 2
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
namespace TYPO3\CMS\Frontend\ContentObject;
3
4
/*
5
 * This file is part of the TYPO3 CMS project.
6
 *
7
 * It is free software; you can redistribute it and/or modify it under
8
 * the terms of the GNU General Public License, either version 2
9
 * of the License, or any later version.
10
 *
11
 * For the full copyright and license information, please read the
12
 * LICENSE.txt file that was distributed with this source code.
13
 *
14
 * The TYPO3 project - inspiring people to share!
15
 */
16
17
use Doctrine\DBAL\DBALException;
18
use Doctrine\DBAL\Driver\Statement;
19
use TYPO3\CMS\Core\Cache\CacheManager;
20
use TYPO3\CMS\Core\Database\Connection;
21
use TYPO3\CMS\Core\Database\ConnectionPool;
22
use TYPO3\CMS\Core\Database\Query\QueryBuilder;
23
use TYPO3\CMS\Core\Database\Query\QueryHelper;
24
use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
25
use TYPO3\CMS\Core\Database\Query\Restriction\FrontendRestrictionContainer;
26
use TYPO3\CMS\Core\FrontendEditing\FrontendEditingController;
27
use TYPO3\CMS\Core\Html\HtmlParser;
28
use TYPO3\CMS\Core\Imaging\ImageManipulation\CropVariantCollection;
29
use TYPO3\CMS\Core\LinkHandling\LinkService;
30
use TYPO3\CMS\Core\Log\LogManager;
31
use TYPO3\CMS\Core\Mail\MailMessage;
32
use TYPO3\CMS\Core\Resource\Exception;
33
use TYPO3\CMS\Core\Resource\Exception\ResourceDoesNotExistException;
34
use TYPO3\CMS\Core\Resource\File;
35
use TYPO3\CMS\Core\Resource\FileInterface;
36
use TYPO3\CMS\Core\Resource\FileReference;
37
use TYPO3\CMS\Core\Resource\Folder;
38
use TYPO3\CMS\Core\Resource\ProcessedFile;
39
use TYPO3\CMS\Core\Resource\ResourceFactory;
40
use TYPO3\CMS\Core\Service\DependencyOrderingService;
41
use TYPO3\CMS\Core\Service\MarkerBasedTemplateService;
42
use TYPO3\CMS\Core\TimeTracker\TimeTracker;
43
use TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser;
44
use TYPO3\CMS\Core\TypoScript\TypoScriptService;
45
use TYPO3\CMS\Core\Utility\ArrayUtility;
46
use TYPO3\CMS\Core\Utility\DebugUtility;
47
use TYPO3\CMS\Core\Utility\GeneralUtility;
48
use TYPO3\CMS\Core\Utility\MailUtility;
49
use TYPO3\CMS\Core\Utility\MathUtility;
50
use TYPO3\CMS\Core\Utility\PathUtility;
51
use TYPO3\CMS\Core\Utility\StringUtility;
52
use TYPO3\CMS\Core\Versioning\VersionState;
53
use TYPO3\CMS\Extbase\Service\FlexFormService;
54
use TYPO3\CMS\Frontend\ContentObject\Exception\ContentRenderingException;
55
use TYPO3\CMS\Frontend\ContentObject\Exception\ExceptionHandlerInterface;
56
use TYPO3\CMS\Frontend\ContentObject\Exception\ProductionExceptionHandler;
57
use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
58
use TYPO3\CMS\Frontend\Http\UrlProcessorInterface;
59
use TYPO3\CMS\Frontend\Imaging\GifBuilder;
60
use TYPO3\CMS\Frontend\Page\PageRepository;
61
use TYPO3\CMS\Frontend\Service\TypoLinkCodecService;
62
use TYPO3\CMS\Frontend\Typolink\AbstractTypolinkBuilder;
63
use TYPO3\CMS\Frontend\Typolink\UnableToLinkException;
64
65
/**
66
 * This class contains all main TypoScript features.
67
 * This includes the rendering of TypoScript content objects (cObjects).
68
 * Is the backbone of TypoScript Template rendering.
69
 *
70
 * There are lots of functions you can use from your include-scripts.
71
 * The class is normally instantiated and referred to as "cObj".
72
 * When you call your own PHP-code typically through a USER or USER_INT cObject then it is this class that instantiates the object and calls the main method. Before it does so it will set (if you are using classes) a reference to itself in the internal variable "cObj" of the object. Thus you can access all functions and data from this class by $this->cObj->... from within you classes written to be USER or USER_INT content objects.
73
 */
74
class ContentObjectRenderer
75
{
76
    /**
77
     * @var array
78
     */
79
    public $align = [
80
        'center',
81
        'right',
82
        'left'
83
    ];
84
85
    /**
86
     * stdWrap functions in their correct order
87
     *
88
     * @see stdWrap()
89
     */
90
    public $stdWrapOrder = [
91
        'stdWrapPreProcess' => 'hook',
92
        // this is a placeholder for the first Hook
93
        'cacheRead' => 'hook',
94
        // this is a placeholder for checking if the content is available in cache
95
        'setContentToCurrent' => 'boolean',
96
        'setContentToCurrent.' => 'array',
97
        'addPageCacheTags' => 'string',
98
        'addPageCacheTags.' => 'array',
99
        'setCurrent' => 'string',
100
        'setCurrent.' => 'array',
101
        'lang.' => 'array',
102
        'data' => 'getText',
103
        'data.' => 'array',
104
        'field' => 'fieldName',
105
        'field.' => 'array',
106
        'current' => 'boolean',
107
        'current.' => 'array',
108
        'cObject' => 'cObject',
109
        'cObject.' => 'array',
110
        'numRows.' => 'array',
111
        'filelist' => 'dir',
112
        'filelist.' => 'array',
113
        'preUserFunc' => 'functionName',
114
        'stdWrapOverride' => 'hook',
115
        // this is a placeholder for the second Hook
116
        'override' => 'string',
117
        'override.' => 'array',
118
        'preIfEmptyListNum' => 'listNum',
119
        'preIfEmptyListNum.' => 'array',
120
        'ifNull' => 'string',
121
        'ifNull.' => 'array',
122
        'ifEmpty' => 'string',
123
        'ifEmpty.' => 'array',
124
        'ifBlank' => 'string',
125
        'ifBlank.' => 'array',
126
        'listNum' => 'listNum',
127
        'listNum.' => 'array',
128
        'trim' => 'boolean',
129
        'trim.' => 'array',
130
        'strPad.' => 'array',
131
        'stdWrap' => 'stdWrap',
132
        'stdWrap.' => 'array',
133
        'stdWrapProcess' => 'hook',
134
        // this is a placeholder for the third Hook
135
        'required' => 'boolean',
136
        'required.' => 'array',
137
        'if.' => 'array',
138
        'fieldRequired' => 'fieldName',
139
        'fieldRequired.' => 'array',
140
        'csConv' => 'string',
141
        'csConv.' => 'array',
142
        'parseFunc' => 'objectpath',
143
        'parseFunc.' => 'array',
144
        'HTMLparser' => 'boolean',
145
        'HTMLparser.' => 'array',
146
        'split.' => 'array',
147
        'replacement.' => 'array',
148
        'prioriCalc' => 'boolean',
149
        'prioriCalc.' => 'array',
150
        'char' => 'integer',
151
        'char.' => 'array',
152
        'intval' => 'boolean',
153
        'intval.' => 'array',
154
        'hash' => 'string',
155
        'hash.' => 'array',
156
        'round' => 'boolean',
157
        'round.' => 'array',
158
        'numberFormat.' => 'array',
159
        'expandList' => 'boolean',
160
        'expandList.' => 'array',
161
        'date' => 'dateconf',
162
        'date.' => 'array',
163
        'strtotime' => 'strtotimeconf',
164
        'strtotime.' => 'array',
165
        'strftime' => 'strftimeconf',
166
        'strftime.' => 'array',
167
        'age' => 'boolean',
168
        'age.' => 'array',
169
        'case' => 'case',
170
        'case.' => 'array',
171
        'bytes' => 'boolean',
172
        'bytes.' => 'array',
173
        'substring' => 'parameters',
174
        'substring.' => 'array',
175
        'cropHTML' => 'crop',
176
        'cropHTML.' => 'array',
177
        'stripHtml' => 'boolean',
178
        'stripHtml.' => 'array',
179
        'crop' => 'crop',
180
        'crop.' => 'array',
181
        'rawUrlEncode' => 'boolean',
182
        'rawUrlEncode.' => 'array',
183
        'htmlSpecialChars' => 'boolean',
184
        'htmlSpecialChars.' => 'array',
185
        'encodeForJavaScriptValue' => 'boolean',
186
        'encodeForJavaScriptValue.' => 'array',
187
        'doubleBrTag' => 'string',
188
        'doubleBrTag.' => 'array',
189
        'br' => 'boolean',
190
        'br.' => 'array',
191
        'brTag' => 'string',
192
        'brTag.' => 'array',
193
        'encapsLines.' => 'array',
194
        'keywords' => 'boolean',
195
        'keywords.' => 'array',
196
        'innerWrap' => 'wrap',
197
        'innerWrap.' => 'array',
198
        'innerWrap2' => 'wrap',
199
        'innerWrap2.' => 'array',
200
        'addParams.' => 'array',
201
        'filelink.' => 'array',
202
        'preCObject' => 'cObject',
203
        'preCObject.' => 'array',
204
        'postCObject' => 'cObject',
205
        'postCObject.' => 'array',
206
        'wrapAlign' => 'align',
207
        'wrapAlign.' => 'array',
208
        'typolink.' => 'array',
209
        'TCAselectItem.' => 'array',
210
        'wrap' => 'wrap',
211
        'wrap.' => 'array',
212
        'noTrimWrap' => 'wrap',
213
        'noTrimWrap.' => 'array',
214
        'wrap2' => 'wrap',
215
        'wrap2.' => 'array',
216
        'dataWrap' => 'dataWrap',
217
        'dataWrap.' => 'array',
218
        'prepend' => 'cObject',
219
        'prepend.' => 'array',
220
        'append' => 'cObject',
221
        'append.' => 'array',
222
        'wrap3' => 'wrap',
223
        'wrap3.' => 'array',
224
        'orderedStdWrap' => 'stdWrap',
225
        'orderedStdWrap.' => 'array',
226
        'outerWrap' => 'wrap',
227
        'outerWrap.' => 'array',
228
        'insertData' => 'boolean',
229
        'insertData.' => 'array',
230
        'postUserFunc' => 'functionName',
231
        'postUserFuncInt' => 'functionName',
232
        'prefixComment' => 'string',
233
        'prefixComment.' => 'array',
234
        'editIcons' => 'string',
235
        'editIcons.' => 'array',
236
        'editPanel' => 'boolean',
237
        'editPanel.' => 'array',
238
        'cacheStore' => 'hook',
239
        // this is a placeholder for storing the content in cache
240
        'stdWrapPostProcess' => 'hook',
241
        // this is a placeholder for the last Hook
242
        'debug' => 'boolean',
243
        'debug.' => 'array',
244
        'debugFunc' => 'boolean',
245
        'debugFunc.' => 'array',
246
        'debugData' => 'boolean',
247
        'debugData.' => 'array'
248
    ];
249
250
    /**
251
     * Class names for accordant content object names
252
     *
253
     * @var array
254
     */
255
    protected $contentObjectClassMap = [];
256
257
    /**
258
     * Loaded with the current data-record.
259
     *
260
     * If the instance of this class is used to render records from the database those records are found in this array.
261
     * The function stdWrap has TypoScript properties that fetch field-data from this array.
262
     *
263
     * @var array
264
     * @see start()
265
     */
266
    public $data = [];
267
268
    /**
269
     * @var string
270
     */
271
    protected $table = '';
272
273
    /**
274
     * Used for backup
275
     *
276
     * @var array
277
     */
278
    public $oldData = [];
279
280
    /**
281
     * If this is set with an array before stdWrap, it's used instead of $this->data in the data-property in stdWrap
282
     *
283
     * @var string
284
     */
285
    public $alternativeData = '';
286
287
    /**
288
     * Used by the parseFunc function and is loaded with tag-parameters when parsing tags.
289
     *
290
     * @var array
291
     */
292
    public $parameters = [];
293
294
    /**
295
     * @var string
296
     */
297
    public $currentValKey = 'currentValue_kidjls9dksoje';
298
299
    /**
300
     * This is set to the [table]:[uid] of the record delivered in the $data-array, if the cObjects CONTENT or RECORD is in operation.
301
     * Note that $GLOBALS['TSFE']->currentRecord is set to an equal value but always indicating the latest record rendered.
302
     *
303
     * @var string
304
     */
305
    public $currentRecord = '';
306
307
    /**
308
     * Set in RecordsContentObject and ContentContentObject to the current number of records selected in a query.
309
     *
310
     * @var int
311
     */
312
    public $currentRecordTotal = 0;
313
314
    /**
315
     * Incremented in RecordsContentObject and ContentContentObject before each record rendering.
316
     *
317
     * @var int
318
     */
319
    public $currentRecordNumber = 0;
320
321
    /**
322
     * Incremented in RecordsContentObject and ContentContentObject before each record rendering.
323
     *
324
     * @var int
325
     */
326
    public $parentRecordNumber = 0;
327
328
    /**
329
     * If the ContentObjectRender was started from ContentContentObject, RecordsContentObject or SearchResultContentObject this array has two keys, 'data' and 'currentRecord' which indicates the record and data for the parent cObj.
330
     *
331
     * @var array
332
     */
333
    public $parentRecord = [];
334
335
    /**
336
     * This is used by checkPid, that checks if pages are accessible. The $checkPid_cache['page_uid'] is set TRUE or FALSE upon this check featuring a caching function for the next request.
337
     *
338
     * @var array
339
     */
340
    public $checkPid_cache = [];
341
342
    /**
343
     * @var string
344
     */
345
    public $checkPid_badDoktypeList = '255';
346
347
    /**
348
     * This will be set by typoLink() to the url of the most recent link created.
349
     *
350
     * @var string
351
     */
352
    public $lastTypoLinkUrl = '';
353
354
    /**
355
     * DO. link target.
356
     *
357
     * @var string
358
     */
359
    public $lastTypoLinkTarget = '';
360
361
    /**
362
     * @var array
363
     */
364
    public $lastTypoLinkLD = [];
365
366
    /**
367
     * array that registers rendered content elements (or any table) to make sure they are not rendered recursively!
368
     *
369
     * @var array
370
     */
371
    public $recordRegister = [];
372
373
    /**
374
     * Additionally registered content object types and class names
375
     *
376
     * @var array
377
     */
378
    protected $cObjHookObjectsRegistry = [];
379
380
    /**
381
     * @var array
382
     */
383
    public $cObjHookObjectsArr = [];
384
385
    /**
386
     * Containing hook objects for stdWrap
387
     *
388
     * @var array
389
     */
390
    protected $stdWrapHookObjects = [];
391
392
    /**
393
     * Containing hook objects for getImgResource
394
     *
395
     * @var array
396
     */
397
    protected $getImgResourceHookObjects;
398
399
    /**
400
     * @var File Current file objects (during iterations over files)
401
     */
402
    protected $currentFile = null;
403
404
    /**
405
     * Set to TRUE by doConvertToUserIntObject() if USER object wants to become USER_INT
406
     */
407
    public $doConvertToUserIntObject = false;
408
409
    /**
410
     * Indicates current object type. Can hold one of OBJECTTYPE_ constants or FALSE.
411
     * The value is set and reset inside USER() function. Any time outside of
412
     * USER() it is FALSE.
413
     */
414
    protected $userObjectType = false;
415
416
    /**
417
     * @var array
418
     */
419
    protected $stopRendering = [];
420
421
    /**
422
     * @var int
423
     */
424
    protected $stdWrapRecursionLevel = 0;
425
426
    /**
427
     * @var TypoScriptFrontendController
428
     */
429
    protected $typoScriptFrontendController;
430
431
    /**
432
     * @var MarkerBasedTemplateService
433
     */
434
    protected $templateService;
435
436
    /**
437
     * Indicates that object type is USER.
438
     *
439
     * @see ContentObjectRender::$userObjectType
440
     */
441
    const OBJECTTYPE_USER_INT = 1;
442
    /**
443
     * Indicates that object type is USER.
444
     *
445
     * @see ContentObjectRender::$userObjectType
446
     */
447
    const OBJECTTYPE_USER = 2;
448
449
    /**
450
     * @param TypoScriptFrontendController $typoScriptFrontendController
451
     */
452
    public function __construct(TypoScriptFrontendController $typoScriptFrontendController = null)
453
    {
454
        $this->typoScriptFrontendController = $typoScriptFrontendController;
455
        $this->contentObjectClassMap = $GLOBALS['TYPO3_CONF_VARS']['FE']['ContentObjects'];
456
        $this->templateService = GeneralUtility::makeInstance(MarkerBasedTemplateService::class);
457
    }
458
459
    /**
460
     * Prevent several objects from being serialized.
461
     * If currentFile is set, it is either a File or a FileReference object. As the object itself can't be serialized,
462
     * we have store a hash and restore the object in __wakeup()
463
     *
464
     * @return array
465
     */
466
    public function __sleep()
467
    {
468
        $vars = get_object_vars($this);
469
        unset($vars['typoScriptFrontendController']);
470
        if ($this->currentFile instanceof FileReference) {
471
            $this->currentFile = 'FileReference:' . $this->currentFile->getUid();
0 ignored issues
show
Documentation Bug introduced by
It seems like 'FileReference:' . $this->currentFile->getUid() of type string is incompatible with the declared type TYPO3\CMS\Core\Resource\File of property $currentFile.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
472
        } elseif ($this->currentFile instanceof File) {
473
            $this->currentFile = 'File:' . $this->currentFile->getIdentifier();
474
        } else {
475
            unset($vars['currentFile']);
476
        }
477
        return array_keys($vars);
478
    }
479
480
    /**
481
     * Restore currentFile from hash.
482
     * If currentFile references a File, the identifier equals file identifier.
483
     * If it references a FileReference the identifier equals the uid of the reference.
484
     */
485
    public function __wakeup()
486
    {
487
        if (isset($GLOBALS['TSFE'])) {
488
            $this->typoScriptFrontendController = $GLOBALS['TSFE'];
489
        }
490
        if ($this->currentFile !== null && is_string($this->currentFile)) {
491
            list($objectType, $identifier) = explode(':', $this->currentFile, 2);
492
            try {
493
                if ($objectType === 'File') {
494
                    $this->currentFile = ResourceFactory::getInstance()->retrieveFileOrFolderObject($identifier);
495
                } elseif ($objectType === 'FileReference') {
496
                    $this->currentFile = ResourceFactory::getInstance()->getFileReferenceObject($identifier);
497
                }
498
            } catch (ResourceDoesNotExistException $e) {
499
                $this->currentFile = null;
500
            }
501
        }
502
    }
503
504
    /**
505
     * Allow injecting content object class map.
506
     *
507
     * This method is private API, please use configuration
508
     * $GLOBALS['TYPO3_CONF_VARS']['FE']['ContentObjects'] to add new content objects
509
     *
510
     * @internal
511
     * @param array $contentObjectClassMap
512
     */
513
    public function setContentObjectClassMap(array $contentObjectClassMap)
514
    {
515
        $this->contentObjectClassMap = $contentObjectClassMap;
516
    }
517
518
    /**
519
     * Register a single content object name to class name
520
     *
521
     * This method is private API, please use configuration
522
     * $GLOBALS['TYPO3_CONF_VARS']['FE']['ContentObjects'] to add new content objects
523
     *
524
     * @param string $className
525
     * @param string $contentObjectName
526
     * @internal
527
     */
528
    public function registerContentObjectClass($className, $contentObjectName)
529
    {
530
        $this->contentObjectClassMap[$contentObjectName] = $className;
531
    }
532
533
    /**
534
     * Class constructor.
535
     * Well, it has to be called manually since it is not a real constructor function.
536
     * So after making an instance of the class, call this function and pass to it a database record and the tablename from where the record is from. That will then become the "current" record loaded into memory and accessed by the .fields property found in eg. stdWrap.
537
     *
538
     * @param array $data The record data that is rendered.
539
     * @param string $table The table that the data record is from.
540
     */
541
    public function start($data, $table = '')
542
    {
543
        $this->data = $data;
544
        $this->table = $table;
545
        $this->currentRecord = $table !== '' ? $table . ':' . $this->data['uid'] : '';
546
        $this->parameters = [];
547
        foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['cObjTypeAndClass'] ?? [] as $classArr) {
548
            $this->cObjHookObjectsRegistry[$classArr[0]] = $classArr[1];
549
        }
550
        $this->stdWrapHookObjects = [];
551
        foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['stdWrap'] ?? [] as $className) {
552
            $hookObject = GeneralUtility::makeInstance($className);
553
            if (!$hookObject instanceof ContentObjectStdWrapHookInterface) {
554
                throw new \UnexpectedValueException($className . ' must implement interface ' . ContentObjectStdWrapHookInterface::class, 1195043965);
555
            }
556
            $this->stdWrapHookObjects[] = $hookObject;
557
        }
558
        foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['postInit'] ?? [] as $className) {
559
            $postInitializationProcessor = GeneralUtility::makeInstance($className);
560
            if (!$postInitializationProcessor instanceof ContentObjectPostInitHookInterface) {
561
                throw new \UnexpectedValueException($className . ' must implement interface ' . ContentObjectPostInitHookInterface::class, 1274563549);
562
            }
563
            $postInitializationProcessor->postProcessContentObjectInitialization($this);
564
        }
565
    }
566
567
    /**
568
     * Returns the current table
569
     *
570
     * @return string
571
     */
572
    public function getCurrentTable()
573
    {
574
        return $this->table;
575
    }
576
577
    /**
578
     * Gets the 'getImgResource' hook objects.
579
     * The first call initializes the accordant objects.
580
     *
581
     * @return array The 'getImgResource' hook objects (if any)
582
     */
583 View Code Duplication
    protected function getGetImgResourceHookObjects()
584
    {
585
        if (!isset($this->getImgResourceHookObjects)) {
586
            $this->getImgResourceHookObjects = [];
587
            foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['getImgResource'] ?? [] as $className) {
588
                $hookObject = GeneralUtility::makeInstance($className);
589
                if (!$hookObject instanceof ContentObjectGetImageResourceHookInterface) {
590
                    throw new \UnexpectedValueException('$hookObject must implement interface ' . ContentObjectGetImageResourceHookInterface::class, 1218636383);
591
                }
592
                $this->getImgResourceHookObjects[] = $hookObject;
593
            }
594
        }
595
        return $this->getImgResourceHookObjects;
596
    }
597
598
    /**
599
     * Sets the internal variable parentRecord with information about current record.
600
     * If the ContentObjectRender was started from CONTENT, RECORD or SEARCHRESULT cObject's this array has two keys, 'data' and 'currentRecord' which indicates the record and data for the parent cObj.
601
     *
602
     * @param array $data The record array
603
     * @param string $currentRecord This is set to the [table]:[uid] of the record delivered in the $data-array, if the cObjects CONTENT or RECORD is in operation. Note that $GLOBALS['TSFE']->currentRecord is set to an equal value but always indicating the latest record rendered.
604
     * @access private
605
     */
606
    public function setParent($data, $currentRecord)
607
    {
608
        $this->parentRecord = [
609
            'data' => $data,
610
            'currentRecord' => $currentRecord
611
        ];
612
    }
613
614
    /***********************************************
615
     *
616
     * CONTENT_OBJ:
617
     *
618
     ***********************************************/
619
    /**
620
     * Returns the "current" value.
621
     * The "current" value is just an internal variable that can be used by functions to pass a single value on to another function later in the TypoScript processing.
622
     * It's like "load accumulator" in the good old C64 days... basically a "register" you can use as you like.
623
     * The TSref will tell if functions are setting this value before calling some other object so that you know if it holds any special information.
624
     *
625
     * @return mixed The "current" value
626
     */
627
    public function getCurrentVal()
628
    {
629
        return $this->data[$this->currentValKey];
630
    }
631
632
    /**
633
     * Sets the "current" value.
634
     *
635
     * @param mixed $value The variable that you want to set as "current
636
     * @see getCurrentVal()
637
     */
638
    public function setCurrentVal($value)
639
    {
640
        $this->data[$this->currentValKey] = $value;
641
    }
642
643
    /**
644
     * Rendering of a "numerical array" of cObjects from TypoScript
645
     * Will call ->cObjGetSingle() for each cObject found and accumulate the output.
646
     *
647
     * @param array $setup array with cObjects as values.
648
     * @param string $addKey A prefix for the debugging information
649
     * @return string Rendered output from the cObjects in the array.
650
     * @see cObjGetSingle()
651
     */
652
    public function cObjGet($setup, $addKey = '')
653
    {
654
        if (!is_array($setup)) {
655
            return '';
656
        }
657
        $sKeyArray = ArrayUtility::filterAndSortByNumericKeys($setup);
658
        $content = '';
659
        foreach ($sKeyArray as $theKey) {
660
            $theValue = $setup[$theKey];
661
            if ((int)$theKey && strpos($theKey, '.') === false) {
662
                $conf = $setup[$theKey . '.'];
663
                $content .= $this->cObjGetSingle($theValue, $conf, $addKey . $theKey);
664
            }
665
        }
666
        return $content;
667
    }
668
669
    /**
670
     * Renders a content object
671
     *
672
     * @param string $name The content object name, eg. "TEXT" or "USER" or "IMAGE
673
     * @param array $conf The array with TypoScript properties for the content object
674
     * @param string $TSkey A string label used for the internal debugging tracking.
675
     * @return string cObject output
676
     * @throws \UnexpectedValueException
677
     */
678
    public function cObjGetSingle($name, $conf, $TSkey = '__')
679
    {
680
        $content = '';
681
        // Checking that the function is not called eternally. This is done by interrupting at a depth of 100
682
        $this->getTypoScriptFrontendController()->cObjectDepthCounter--;
683
        if ($this->getTypoScriptFrontendController()->cObjectDepthCounter > 0) {
684
            $timeTracker = $this->getTimeTracker();
685
            $name = trim($name);
686
            if ($timeTracker->LR) {
687
                $timeTracker->push($TSkey, $name);
688
            }
689
            // Checking if the COBJ is a reference to another object. (eg. name of 'blabla.blabla = < styles.something')
690
            if ($name[0] === '<') {
691
                $key = trim(substr($name, 1));
0 ignored issues
show
Bug introduced by
It seems like substr($name, 1) can also be of type false; however, parameter $str of trim() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

691
                $key = trim(/** @scrutinizer ignore-type */ substr($name, 1));
Loading history...
692
                $cF = GeneralUtility::makeInstance(TypoScriptParser::class);
693
                // $name and $conf is loaded with the referenced values.
694
                $confOverride = is_array($conf) ? $conf : [];
695
                list($name, $conf) = $cF->getVal($key, $this->getTypoScriptFrontendController()->tmpl->setup);
696
                $conf = array_replace_recursive(is_array($conf) ? $conf : [], $confOverride);
697
                // Getting the cObject
698
                $timeTracker->incStackPointer();
699
                $content .= $this->cObjGetSingle($name, $conf, $key);
700
                $timeTracker->decStackPointer();
701
            } else {
702
                $hooked = false;
703
                // Application defined cObjects
704
                if (!empty($this->cObjHookObjectsRegistry[$name])) {
705
                    if (empty($this->cObjHookObjectsArr[$name])) {
706
                        $this->cObjHookObjectsArr[$name] = GeneralUtility::makeInstance($this->cObjHookObjectsRegistry[$name]);
707
                    }
708
                    $hookObj = $this->cObjHookObjectsArr[$name];
709
                    if (method_exists($hookObj, 'cObjGetSingleExt')) {
710
                        $content .= $hookObj->cObjGetSingleExt($name, $conf, $TSkey, $this);
711
                        $hooked = true;
712
                    }
713
                }
714
                if (!$hooked) {
715
                    $contentObject = $this->getContentObject($name);
716
                    if ($contentObject) {
717
                        $content .= $this->render($contentObject, $conf);
718
                    } else {
719
                        // Call hook functions for extra processing
720
                        if ($name) {
721
                            foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['cObjTypeAndClassDefault'] ?? [] as $className) {
722
                                $hookObject = GeneralUtility::makeInstance($className);
723
                                if (!$hookObject instanceof ContentObjectGetSingleHookInterface) {
724
                                    throw new \UnexpectedValueException('$hookObject must implement interface ' . ContentObjectGetSingleHookInterface::class, 1195043731);
725
                                }
726
                                /** @var $hookObject ContentObjectGetSingleHookInterface */
727
                                $content .= $hookObject->getSingleContentObject($name, (array)$conf, $TSkey, $this);
728
                            }
729
                        } else {
730
                            // Log error in AdminPanel
731
                            $warning = sprintf('Content Object "%s" does not exist', $name);
732
                            $timeTracker->setTSlogMessage($warning, 2);
733
                        }
734
                    }
735
                }
736
            }
737
            if ($timeTracker->LR) {
738
                $timeTracker->pull($content);
739
            }
740
        }
741
        // Increasing on exit...
742
        $this->getTypoScriptFrontendController()->cObjectDepthCounter++;
743
        return $content;
744
    }
745
746
    /**
747
     * Returns a new content object of type $name.
748
     * This content object needs to be registered as content object
749
     * in $this->contentObjectClassMap
750
     *
751
     * @param string $name
752
     * @return AbstractContentObject|null
753
     * @throws ContentRenderingException
754
     */
755
    public function getContentObject($name)
756
    {
757
        if (!isset($this->contentObjectClassMap[$name])) {
758
            return null;
759
        }
760
        $fullyQualifiedClassName = $this->contentObjectClassMap[$name];
761
        $contentObject = GeneralUtility::makeInstance($fullyQualifiedClassName, $this);
0 ignored issues
show
Bug introduced by
$this of type TYPO3\CMS\Frontend\Conte...t\ContentObjectRenderer is incompatible with the type array<integer,mixed> expected by parameter $constructorArguments of TYPO3\CMS\Core\Utility\G...Utility::makeInstance(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

761
        $contentObject = GeneralUtility::makeInstance($fullyQualifiedClassName, /** @scrutinizer ignore-type */ $this);
Loading history...
762
        if (!($contentObject instanceof AbstractContentObject)) {
763
            throw new ContentRenderingException(sprintf('Registered content object class name "%s" must be an instance of AbstractContentObject, but is not!', $fullyQualifiedClassName), 1422564295);
764
        }
765
        return $contentObject;
766
    }
767
768
    /********************************************
769
     *
770
     * Functions rendering content objects (cObjects)
771
     *
772
     ********************************************/
773
774
    /**
775
     * Renders a content object by taking exception and cache handling
776
     * into consideration
777
     *
778
     * @param AbstractContentObject $contentObject Content object instance
779
     * @param array $configuration Array of TypoScript properties
780
     *
781
     * @throws ContentRenderingException
782
     * @throws \Exception
783
     * @return string
784
     */
785
    public function render(AbstractContentObject $contentObject, $configuration = [])
786
    {
787
        $content = '';
788
789
        // Evaluate possible cache and return
790
        $cacheConfiguration = isset($configuration['cache.']) ? $configuration['cache.'] : null;
791
        if ($cacheConfiguration !== null) {
792
            unset($configuration['cache.']);
793
            $cache = $this->getFromCache($cacheConfiguration);
794
            if ($cache !== false) {
795
                return $cache;
796
            }
797
        }
798
799
        // Render content
800
        try {
801
            $content .= $contentObject->render($configuration);
802
        } catch (ContentRenderingException $exception) {
803
            // Content rendering Exceptions indicate a critical problem which should not be
804
            // caught e.g. when something went wrong with Exception handling itself
805
            throw $exception;
806
        } catch (\Exception $exception) {
807
            $exceptionHandler = $this->createExceptionHandler($configuration);
808
            if ($exceptionHandler === null) {
809
                throw $exception;
810
            }
811
            $content = $exceptionHandler->handle($exception, $contentObject, $configuration);
812
        }
813
814
        // Store cache
815
        if ($cacheConfiguration !== null) {
816
            $key = $this->calculateCacheKey($cacheConfiguration);
817
            if (!empty($key)) {
818
                /** @var $cacheFrontend \TYPO3\CMS\Core\Cache\Frontend\VariableFrontend */
819
                $cacheFrontend = GeneralUtility::makeInstance(CacheManager::class)->getCache('cache_hash');
820
                $tags = $this->calculateCacheTags($cacheConfiguration);
821
                $lifetime = $this->calculateCacheLifetime($cacheConfiguration);
822
                $cacheFrontend->set($key, $content, $tags, $lifetime);
823
            }
824
        }
825
826
        return $content;
827
    }
828
829
    /**
830
     * Creates the content object exception handler from local content object configuration
831
     * or, from global configuration if not explicitly disabled in local configuration
832
     *
833
     * @param array $configuration
834
     * @return ExceptionHandlerInterface|null
835
     * @throws ContentRenderingException
836
     */
837
    protected function createExceptionHandler($configuration = [])
838
    {
839
        $exceptionHandler = null;
840
        $exceptionHandlerClassName = $this->determineExceptionHandlerClassName($configuration);
841
        if (!empty($exceptionHandlerClassName)) {
842
            $exceptionHandler = GeneralUtility::makeInstance($exceptionHandlerClassName, $this->mergeExceptionHandlerConfiguration($configuration));
843
            if (!$exceptionHandler instanceof ExceptionHandlerInterface) {
844
                throw new ContentRenderingException('An exception handler was configured but the class does not exist or does not implement the ExceptionHandlerInterface', 1403653369);
845
            }
846
        }
847
848
        return $exceptionHandler;
849
    }
850
851
    /**
852
     * Determine exception handler class name from global and content object configuration
853
     *
854
     * @param array $configuration
855
     * @return string|null
856
     */
857
    protected function determineExceptionHandlerClassName($configuration)
858
    {
859
        $exceptionHandlerClassName = null;
860
        $tsfe = $this->getTypoScriptFrontendController();
861
        if (!isset($tsfe->config['config']['contentObjectExceptionHandler'])) {
862
            if (GeneralUtility::getApplicationContext()->isProduction()) {
863
                $exceptionHandlerClassName = '1';
864
            }
865
        } else {
866
            $exceptionHandlerClassName = $tsfe->config['config']['contentObjectExceptionHandler'];
867
        }
868
869
        if (isset($configuration['exceptionHandler'])) {
870
            $exceptionHandlerClassName = $configuration['exceptionHandler'];
871
        }
872
873
        if ($exceptionHandlerClassName === '1') {
874
            $exceptionHandlerClassName = ProductionExceptionHandler::class;
875
        }
876
877
        return $exceptionHandlerClassName;
878
    }
879
880
    /**
881
     * Merges global exception handler configuration with the one from the content object
882
     * and returns the merged exception handler configuration
883
     *
884
     * @param array $configuration
885
     * @return array
886
     */
887
    protected function mergeExceptionHandlerConfiguration($configuration)
888
    {
889
        $exceptionHandlerConfiguration = [];
890
        $tsfe = $this->getTypoScriptFrontendController();
891
        if (!empty($tsfe->config['config']['contentObjectExceptionHandler.'])) {
892
            $exceptionHandlerConfiguration = $tsfe->config['config']['contentObjectExceptionHandler.'];
893
        }
894
        if (!empty($configuration['exceptionHandler.'])) {
895
            $exceptionHandlerConfiguration = array_replace_recursive($exceptionHandlerConfiguration, $configuration['exceptionHandler.']);
896
        }
897
898
        return $exceptionHandlerConfiguration;
899
    }
900
901
    /**
902
     * Retrieves a type of object called as USER or USER_INT. Object can detect their
903
     * type by using this call. It returns OBJECTTYPE_USER_INT or OBJECTTYPE_USER depending on the
904
     * current object execution. In all other cases it will return FALSE to indicate
905
     * a call out of context.
906
     *
907
     * @return mixed One of OBJECTTYPE_ class constants or FALSE
908
     */
909
    public function getUserObjectType()
910
    {
911
        return $this->userObjectType;
912
    }
913
914
    /**
915
     * Sets the user object type
916
     *
917
     * @param mixed $userObjectType
918
     */
919
    public function setUserObjectType($userObjectType)
920
    {
921
        $this->userObjectType = $userObjectType;
922
    }
923
924
    /**
925
     * Requests the current USER object to be converted to USER_INT.
926
     */
927
    public function convertToUserIntObject()
928
    {
929
        if ($this->userObjectType !== self::OBJECTTYPE_USER) {
930
            $this->getTimeTracker()->setTSlogMessage(self::class . '::convertToUserIntObject() is called in the wrong context or for the wrong object type', 2);
931
        } else {
932
            $this->doConvertToUserIntObject = true;
933
        }
934
    }
935
936
    /************************************
937
     *
938
     * Various helper functions for content objects:
939
     *
940
     ************************************/
941
    /**
942
     * Converts a given config in Flexform to a conf-array
943
     *
944
     * @param string|array $flexData Flexform data
945
     * @param array $conf Array to write the data into, by reference
946
     * @param bool $recursive Is set if called recursive. Don't call function with this parameter, it's used inside the function only
947
     */
948
    public function readFlexformIntoConf($flexData, &$conf, $recursive = false)
949
    {
950
        if ($recursive === false && is_string($flexData)) {
951
            $flexData = GeneralUtility::xml2array($flexData, 'T3');
952
        }
953
        if (is_array($flexData) && isset($flexData['data']['sDEF']['lDEF'])) {
954
            $flexData = $flexData['data']['sDEF']['lDEF'];
955
        }
956
        if (!is_array($flexData)) {
957
            return;
958
        }
959
        foreach ($flexData as $key => $value) {
960
            if (!is_array($value)) {
961
                continue;
962
            }
963
            if (isset($value['el'])) {
964
                if (is_array($value['el']) && !empty($value['el'])) {
965
                    foreach ($value['el'] as $ekey => $element) {
966
                        if (isset($element['vDEF'])) {
967
                            $conf[$ekey] = $element['vDEF'];
968
                        } else {
969
                            if (is_array($element)) {
970
                                $this->readFlexformIntoConf($element, $conf[$key][key($element)][$ekey], true);
971
                            } else {
972
                                $this->readFlexformIntoConf($element, $conf[$key][$ekey], true);
973
                            }
974
                        }
975
                    }
976
                } else {
977
                    $this->readFlexformIntoConf($value['el'], $conf[$key], true);
978
                }
979
            }
980
            if (isset($value['vDEF'])) {
981
                $conf[$key] = $value['vDEF'];
982
            }
983
        }
984
    }
985
986
    /**
987
     * Returns all parents of the given PID (Page UID) list
988
     *
989
     * @param string $pidList A list of page Content-Element PIDs (Page UIDs) / stdWrap
990
     * @param array $pidConf stdWrap array for the list
991
     * @return string A list of PIDs
992
     * @access private
993
     */
994
    public function getSlidePids($pidList, $pidConf)
995
    {
996
        $pidList = isset($pidConf) ? trim($this->stdWrap($pidList, $pidConf)) : trim($pidList);
997
        if ($pidList === '') {
998
            $pidList = 'this';
999
        }
1000
        $tsfe = $this->getTypoScriptFrontendController();
1001
        $listArr = null;
1002
        if (trim($pidList)) {
1003
            $listArr = GeneralUtility::intExplode(',', str_replace('this', $tsfe->contentPid, $pidList));
1004
            $listArr = $this->checkPidArray($listArr);
1005
        }
1006
        $pidList = [];
1007
        if (is_array($listArr) && !empty($listArr)) {
1008
            foreach ($listArr as $uid) {
1009
                $page = $tsfe->sys_page->getPage($uid);
1010
                if (!$page['is_siteroot']) {
1011
                    $pidList[] = $page['pid'];
1012
                }
1013
            }
1014
        }
1015
        return implode(',', $pidList);
1016
    }
1017
1018
    /**
1019
     * Returns a <img> tag with the image file defined by $file and processed according to the properties in the TypoScript array.
1020
     * Mostly this function is a sub-function to the IMAGE function which renders the IMAGE cObject in TypoScript.
1021
     * This function is called by "$this->cImage($conf['file'], $conf);" from IMAGE().
1022
     *
1023
     * @param string $file File TypoScript resource
1024
     * @param array $conf TypoScript configuration properties
1025
     * @return string <img> tag, (possibly wrapped in links and other HTML) if any image found.
1026
     * @access private
1027
     * @see IMAGE()
1028
     */
1029
    public function cImage($file, $conf)
1030
    {
1031
        $tsfe = $this->getTypoScriptFrontendController();
1032
        $info = $this->getImgResource($file, $conf['file.']);
1033
        $tsfe->lastImageInfo = $info;
1034
        if (!is_array($info)) {
1035
            return '';
1036
        }
1037
        if (is_file(PATH_site . $info['3'])) {
1038
            $source = $tsfe->absRefPrefix . str_replace('%2F', '/', rawurlencode($info['3']));
1039
        } else {
1040
            $source = $info[3];
1041
        }
1042
1043
        $layoutKey = $this->stdWrap($conf['layoutKey'], $conf['layoutKey.']);
1044
        $imageTagTemplate = $this->getImageTagTemplate($layoutKey, $conf);
1045
        $sourceCollection = $this->getImageSourceCollection($layoutKey, $conf, $file);
1046
1047
        // This array is used to collect the image-refs on the page...
1048
        $tsfe->imagesOnPage[] = $source;
1049
        $altParam = $this->getAltParam($conf);
1050
        $params = $this->stdWrapValue('params', $conf);
1051
        if ($params !== '' && $params[0] !== ' ') {
1052
            $params = ' ' . $params;
1053
        }
1054
1055
        $imageTagValues = [
1056
            'width' =>  (int)$info[0],
1057
            'height' => (int)$info[1],
1058
            'src' => htmlspecialchars($source),
1059
            'params' => $params,
1060
            'altParams' => $altParam,
1061
            'border' =>  $this->getBorderAttr(' border="' . (int)$conf['border'] . '"'),
1062
            'sourceCollection' => $sourceCollection,
1063
            'selfClosingTagSlash' => (!empty($tsfe->xhtmlDoctype) ? ' /' : ''),
1064
        ];
1065
1066
        $theValue = $this->templateService->substituteMarkerArray($imageTagTemplate, $imageTagValues, '###|###', true, true);
1067
1068
        $linkWrap = isset($conf['linkWrap.']) ? $this->stdWrap($conf['linkWrap'], $conf['linkWrap.']) : $conf['linkWrap'];
1069
        if ($linkWrap) {
1070
            $theValue = $this->linkWrap($theValue, $linkWrap);
1071
        } elseif ($conf['imageLinkWrap']) {
1072
            $originalFile = !empty($info['originalFile']) ? $info['originalFile'] : $info['origFile'];
1073
            $theValue = $this->imageLinkWrap($theValue, $originalFile, $conf['imageLinkWrap.']);
1074
        }
1075
        $wrap = isset($conf['wrap.']) ? $this->stdWrap($conf['wrap'], $conf['wrap.']) : $conf['wrap'];
1076
        if ((string)$wrap !== '') {
1077
            $theValue = $this->wrap($theValue, $conf['wrap']);
1078
        }
1079
        return $theValue;
1080
    }
1081
1082
    /**
1083
     * Returns the 'border' attribute for an <img> tag only if the doctype is not xhtml_strict, xhtml_11 or html5
1084
     * or if the config parameter 'disableImgBorderAttr' is not set.
1085
     *
1086
     * @param string $borderAttr The border attribute
1087
     * @return string The border attribute
1088
     */
1089
    public function getBorderAttr($borderAttr)
1090
    {
1091
        $tsfe = $this->getTypoScriptFrontendController();
1092
        $docType = $tsfe->xhtmlDoctype;
1093
        if (
1094
            $docType !== 'xhtml_strict' && $docType !== 'xhtml_11'
1095
            && $tsfe->config['config']['doctype'] !== 'html5'
1096
            && !$tsfe->config['config']['disableImgBorderAttr']
1097
        ) {
1098
            return $borderAttr;
1099
        }
1100
        return '';
1101
    }
1102
1103
    /**
1104
     * Returns the html-template for rendering the image-Tag if no template is defined via typoscript the
1105
     * default <img> tag template is returned
1106
     *
1107
     * @param string $layoutKey rendering key
1108
     * @param array $conf TypoScript configuration properties
1109
     * @return string
1110
     */
1111
    public function getImageTagTemplate($layoutKey, $conf)
1112
    {
1113
        if ($layoutKey && isset($conf['layout.']) && isset($conf['layout.'][$layoutKey . '.'])) {
1114
            $imageTagLayout = $this->stdWrap($conf['layout.'][$layoutKey . '.']['element'], $conf['layout.'][$layoutKey . '.']['element.']);
1115
        } else {
1116
            $imageTagLayout = '<img src="###SRC###" width="###WIDTH###" height="###HEIGHT###" ###PARAMS### ###ALTPARAMS### ###BORDER######SELFCLOSINGTAGSLASH###>';
1117
        }
1118
        return $imageTagLayout;
1119
    }
1120
1121
    /**
1122
     * Render alternate sources for the image tag. If no source collection is given an empty string is returned.
1123
     *
1124
     * @param string $layoutKey rendering key
1125
     * @param array $conf TypoScript configuration properties
1126
     * @param string $file
1127
     * @throws \UnexpectedValueException
1128
     * @return string
1129
     */
1130
    public function getImageSourceCollection($layoutKey, $conf, $file)
1131
    {
1132
        $sourceCollection = '';
1133
        if ($layoutKey && $conf['sourceCollection.'] && ($conf['layout.'][$layoutKey . '.']['source'] || $conf['layout.'][$layoutKey . '.']['source.'])) {
1134
1135
            // find active sourceCollection
1136
            $activeSourceCollections = [];
1137
            foreach ($conf['sourceCollection.'] as $sourceCollectionKey => $sourceCollectionConfiguration) {
1138
                if (substr($sourceCollectionKey, -1) === '.') {
1139
                    if (empty($sourceCollectionConfiguration['if.']) || $this->checkIf($sourceCollectionConfiguration['if.'])) {
1140
                        $activeSourceCollections[] = $sourceCollectionConfiguration;
1141
                    }
1142
                }
1143
            }
1144
1145
            // apply option split to configurations
1146
            $tsfe = $this->getTypoScriptFrontendController();
1147
            $typoScriptService = GeneralUtility::makeInstance(TypoScriptService::class);
1148
            $srcLayoutOptionSplitted = $typoScriptService->explodeConfigurationForOptionSplit((array)$conf['layout.'][$layoutKey . '.'], count($activeSourceCollections));
1149
1150
            // render sources
1151
            foreach ($activeSourceCollections as $key => $sourceConfiguration) {
1152
                $sourceLayout = $this->stdWrap($srcLayoutOptionSplitted[$key]['source'], $srcLayoutOptionSplitted[$key]['source.']);
1153
1154
                $sourceRenderConfiguration = [
1155
                    'file' => $file,
1156
                    'file.' => $conf['file.']
1157
                ];
1158
1159
                if (isset($sourceConfiguration['quality']) || isset($sourceConfiguration['quality.'])) {
1160
                    $imageQuality = isset($sourceConfiguration['quality']) ? $sourceConfiguration['quality'] : '';
1161
                    if (isset($sourceConfiguration['quality.'])) {
1162
                        $imageQuality = $this->stdWrap($sourceConfiguration['quality'], $sourceConfiguration['quality.']);
1163
                    }
1164
                    if ($imageQuality) {
1165
                        $sourceRenderConfiguration['file.']['params'] = '-quality ' . (int)$imageQuality;
1166
                    }
1167
                }
1168
1169
                if (isset($sourceConfiguration['pixelDensity'])) {
1170
                    $pixelDensity = (int)$this->stdWrap($sourceConfiguration['pixelDensity'], $sourceConfiguration['pixelDensity.']);
1171
                } else {
1172
                    $pixelDensity = 1;
1173
                }
1174
                $dimensionKeys = ['width', 'height', 'maxW', 'minW', 'maxH', 'minH', 'maxWidth', 'maxHeight', 'XY'];
1175
                foreach ($dimensionKeys as $dimensionKey) {
1176
                    $dimension = $this->stdWrap($sourceConfiguration[$dimensionKey], $sourceConfiguration[$dimensionKey . '.']);
1177
                    if (!$dimension) {
1178
                        $dimension = $this->stdWrap($conf['file.'][$dimensionKey], $conf['file.'][$dimensionKey . '.']);
1179
                    }
1180
                    if ($dimension) {
1181
                        if (strstr($dimension, 'c') !== false && ($dimensionKey === 'width' || $dimensionKey === 'height')) {
1182
                            $dimensionParts = explode('c', $dimension, 2);
1183
                            $dimension = ((int)$dimensionParts[0] * $pixelDensity) . 'c';
1184
                            if ($dimensionParts[1]) {
1185
                                $dimension .= $dimensionParts[1];
1186
                            }
1187
                        } elseif ($dimensionKey === 'XY') {
1188
                            $dimensionParts = GeneralUtility::intExplode(',', $dimension, false, 2);
1189
                            $dimension = $dimensionParts[0] * $pixelDensity;
1190
                            if ($dimensionParts[1]) {
1191
                                $dimension .= ',' . $dimensionParts[1] * $pixelDensity;
1192
                            }
1193
                        } else {
1194
                            $dimension = (int)$dimension * $pixelDensity;
1195
                        }
1196
                        $sourceRenderConfiguration['file.'][$dimensionKey] = $dimension;
1197
                        // Remove the stdWrap properties for dimension as they have been processed already above.
1198
                        unset($sourceRenderConfiguration['file.'][$dimensionKey . '.']);
1199
                    }
1200
                }
1201
                $sourceInfo = $this->getImgResource($sourceRenderConfiguration['file'], $sourceRenderConfiguration['file.']);
1202
                if ($sourceInfo) {
1203
                    $sourceConfiguration['width'] = $sourceInfo[0];
1204
                    $sourceConfiguration['height'] = $sourceInfo[1];
1205
                    $urlPrefix = '';
1206
                    if (parse_url($sourceInfo[3], PHP_URL_HOST) === null) {
1207
                        $urlPrefix = $tsfe->absRefPrefix;
1208
                    }
1209
                    $sourceConfiguration['src'] = htmlspecialchars($urlPrefix . $sourceInfo[3]);
1210
                    $sourceConfiguration['selfClosingTagSlash'] = !empty($tsfe->xhtmlDoctype) ? ' /' : '';
1211
1212
                    $oneSourceCollection = $this->templateService->substituteMarkerArray($sourceLayout, $sourceConfiguration, '###|###', true, true);
1213
1214
                    foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['getImageSourceCollection'] ?? [] as $className) {
1215
                        $hookObject = GeneralUtility::makeInstance($className);
1216
                        if (!$hookObject instanceof ContentObjectOneSourceCollectionHookInterface) {
1217
                            throw new \UnexpectedValueException(
1218
                                '$hookObject must implement interface ' . ContentObjectOneSourceCollectionHookInterface::class,
1219
                                1380007853
1220
                            );
1221
                        }
1222
                        $oneSourceCollection = $hookObject->getOneSourceCollection((array)$sourceRenderConfiguration, (array)$sourceConfiguration, $oneSourceCollection, $this);
1223
                    }
1224
1225
                    $sourceCollection .= $oneSourceCollection;
1226
                }
1227
            }
1228
        }
1229
        return $sourceCollection;
1230
    }
1231
1232
    /**
1233
     * Wraps the input string in link-tags that opens the image in a new window.
1234
     *
1235
     * @param string $string String to wrap, probably an <img> tag
1236
     * @param string|File|FileReference $imageFile The original image file
1237
     * @param array $conf TypoScript properties for the "imageLinkWrap" function
1238
     * @return string The input string, $string, wrapped as configured.
1239
     * @see cImage()
1240
     */
1241
    public function imageLinkWrap($string, $imageFile, $conf)
1242
    {
1243
        $string = (string)$string;
1244
        $enable = isset($conf['enable.']) ? $this->stdWrap($conf['enable'], $conf['enable.']) : $conf['enable'];
1245
        if (!$enable) {
1246
            return $string;
1247
        }
1248
        $content = (string)$this->typoLink($string, $conf['typolink.']);
1249
        if (isset($conf['file.'])) {
1250
            $imageFile = $this->stdWrap($imageFile, $conf['file.']);
0 ignored issues
show
Bug introduced by
It seems like $imageFile can also be of type TYPO3\CMS\Core\Resource\FileReference and TYPO3\CMS\Core\Resource\File; however, parameter $content of TYPO3\CMS\Frontend\Conte...jectRenderer::stdWrap() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1250
            $imageFile = $this->stdWrap(/** @scrutinizer ignore-type */ $imageFile, $conf['file.']);
Loading history...
1251
        }
1252
1253
        if ($imageFile instanceof File) {
1254
            $file = $imageFile;
1255
        } elseif ($imageFile instanceof FileReference) {
1256
            $file = $imageFile->getOriginalFile();
1257
        } else {
1258
            if (MathUtility::canBeInterpretedAsInteger($imageFile)) {
1259
                $file = ResourceFactory::getInstance()->getFileObject((int)$imageFile);
1260
            } else {
1261
                $file = ResourceFactory::getInstance()->getFileObjectFromCombinedIdentifier($imageFile);
1262
            }
1263
        }
1264
1265
        // Create imageFileLink if not created with typolink
1266
        if ($content === $string) {
1267
            $parameterNames = ['width', 'height', 'effects', 'bodyTag', 'title', 'wrap', 'crop'];
1268
            $parameters = [];
1269
            $sample = isset($conf['sample.']) ? $this->stdWrap($conf['sample'], $conf['sample.']) : $conf['sample'];
1270
            if ($sample) {
1271
                $parameters['sample'] = 1;
1272
            }
1273
            foreach ($parameterNames as $parameterName) {
1274
                if (isset($conf[$parameterName . '.'])) {
1275
                    $conf[$parameterName] = $this->stdWrap($conf[$parameterName], $conf[$parameterName . '.']);
1276
                }
1277
                if (isset($conf[$parameterName]) && $conf[$parameterName]) {
1278
                    $parameters[$parameterName] = $conf[$parameterName];
1279
                }
1280
            }
1281
            $parametersEncoded = base64_encode(serialize($parameters));
1282
            $hmac = GeneralUtility::hmac(implode('|', [$file->getUid(), $parametersEncoded]));
1283
            $params = '&md5=' . $hmac;
1284
            foreach (str_split($parametersEncoded, 64) as $index => $chunk) {
1285
                $params .= '&parameters' . rawurlencode('[') . $index . rawurlencode(']') . '=' . rawurlencode($chunk);
1286
            }
1287
            $url = $this->getTypoScriptFrontendController()->absRefPrefix . 'index.php?eID=tx_cms_showpic&file=' . $file->getUid() . $params;
1288
            $directImageLink = isset($conf['directImageLink.']) ? $this->stdWrap($conf['directImageLink'], $conf['directImageLink.']) : $conf['directImageLink'];
1289
            if ($directImageLink) {
1290
                $imgResourceConf = [
1291
                    'file' => $imageFile,
1292
                    'file.' => $conf
1293
                ];
1294
                $url = $this->cObjGetSingle('IMG_RESOURCE', $imgResourceConf);
1295
                if (!$url) {
1296
                    // If no imagemagick / gm is available
1297
                    $url = $imageFile;
1298
                }
1299
            }
1300
            // Create TARGET-attribute only if the right doctype is used
1301
            $target = '';
1302
            $xhtmlDocType = $this->getTypoScriptFrontendController()->xhtmlDoctype;
1303
            if ($xhtmlDocType !== 'xhtml_strict' && $xhtmlDocType !== 'xhtml_11') {
1304
                $target = isset($conf['target.'])
1305
                    ? (string)$this->stdWrap($conf['target'], $conf['target.'])
1306
                    : (string)$conf['target'];
1307
                if ($target === '') {
1308
                    $target = 'thePicture';
1309
                }
1310
            }
1311
            $a1 = '';
1312
            $a2 = '';
1313
            $conf['JSwindow'] = isset($conf['JSwindow.']) ? $this->stdWrap($conf['JSwindow'], $conf['JSwindow.']) : $conf['JSwindow'];
1314
            if ($conf['JSwindow']) {
1315
                if ($conf['JSwindow.']['altUrl'] || $conf['JSwindow.']['altUrl.']) {
1316
                    $altUrl = isset($conf['JSwindow.']['altUrl.']) ? $this->stdWrap($conf['JSwindow.']['altUrl'], $conf['JSwindow.']['altUrl.']) : $conf['JSwindow.']['altUrl'];
1317
                    if ($altUrl) {
1318
                        $url = $altUrl . ($conf['JSwindow.']['altUrl_noDefaultParams'] ? '' : '?file=' . rawurlencode($imageFile) . $params);
0 ignored issues
show
Bug introduced by
It seems like $imageFile can also be of type TYPO3\CMS\Core\Resource\FileReference and TYPO3\CMS\Core\Resource\File; however, parameter $str of rawurlencode() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1318
                        $url = $altUrl . ($conf['JSwindow.']['altUrl_noDefaultParams'] ? '' : '?file=' . rawurlencode(/** @scrutinizer ignore-type */ $imageFile) . $params);
Loading history...
1319
                    }
1320
                }
1321
1322
                $processedFile = $file->process(ProcessedFile::CONTEXT_IMAGECROPSCALEMASK, $conf);
1323
                $JSwindowExpand = isset($conf['JSwindow.']['expand.']) ? $this->stdWrap($conf['JSwindow.']['expand'], $conf['JSwindow.']['expand.']) : $conf['JSwindow.']['expand'];
1324
                $offset = GeneralUtility::intExplode(',', $JSwindowExpand . ',');
1325
                $newWindow = isset($conf['JSwindow.']['newWindow.']) ? $this->stdWrap($conf['JSwindow.']['newWindow'], $conf['JSwindow.']['newWindow.']) : $conf['JSwindow.']['newWindow'];
1326
                $onClick = 'openPic('
1327
                    . GeneralUtility::quoteJSvalue($this->getTypoScriptFrontendController()->baseUrlWrap($url)) . ','
0 ignored issues
show
Bug introduced by
It seems like $url can also be of type TYPO3\CMS\Core\Resource\FileReference and TYPO3\CMS\Core\Resource\File; however, parameter $url of TYPO3\CMS\Frontend\Contr...ntroller::baseUrlWrap() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1327
                    . GeneralUtility::quoteJSvalue($this->getTypoScriptFrontendController()->baseUrlWrap(/** @scrutinizer ignore-type */ $url)) . ','
Loading history...
1328
                    . '\'' . ($newWindow ? md5($url) : 'thePicture') . '\','
0 ignored issues
show
Bug introduced by
It seems like $url can also be of type TYPO3\CMS\Core\Resource\FileReference and TYPO3\CMS\Core\Resource\File; however, parameter $str of md5() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1328
                    . '\'' . ($newWindow ? md5(/** @scrutinizer ignore-type */ $url) : 'thePicture') . '\','
Loading history...
1329
                    . GeneralUtility::quoteJSvalue('width=' . ($processedFile->getProperty('width') + $offset[0])
1330
                        . ',height=' . ($processedFile->getProperty('height') + $offset[1]) . ',status=0,menubar=0')
1331
                    . '); return false;';
1332
                $a1 = '<a href="' . htmlspecialchars($url) . '"'
0 ignored issues
show
Bug introduced by
It seems like $url can also be of type TYPO3\CMS\Core\Resource\FileReference and TYPO3\CMS\Core\Resource\File; however, parameter $string of htmlspecialchars() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1332
                $a1 = '<a href="' . htmlspecialchars(/** @scrutinizer ignore-type */ $url) . '"'
Loading history...
1333
                    . ' onclick="' . htmlspecialchars($onClick) . '"'
1334
                    . ($target !== '' ? ' target="' . htmlspecialchars($target) . '"' : '')
1335
                    . $this->getTypoScriptFrontendController()->ATagParams . '>';
1336
                $a2 = '</a>';
1337
                $this->getTypoScriptFrontendController()->setJS('openPic');
1338
            } else {
1339
                $conf['linkParams.']['parameter'] = $url;
1340
                $string = $this->typoLink($string, $conf['linkParams.']);
1341
            }
1342
            if (isset($conf['stdWrap.'])) {
1343
                $string = $this->stdWrap($string, $conf['stdWrap.']);
1344
            }
1345
            $content = $a1 . $string . $a2;
1346
        }
1347
        return $content;
1348
    }
1349
1350
    /**
1351
     * Sets the SYS_LASTCHANGED timestamp if input timestamp is larger than current value.
1352
     * The SYS_LASTCHANGED timestamp can be used by various caching/indexing applications to determine if the page has new content.
1353
     * Therefore you should call this function with the last-changed timestamp of any element you display.
1354
     *
1355
     * @param int $tstamp Unix timestamp (number of seconds since 1970)
1356
     * @see TypoScriptFrontendController::setSysLastChanged()
1357
     */
1358
    public function lastChanged($tstamp)
1359
    {
1360
        $tstamp = (int)$tstamp;
1361
        $tsfe = $this->getTypoScriptFrontendController();
1362
        if ($tstamp > (int)$tsfe->register['SYS_LASTCHANGED']) {
1363
            $tsfe->register['SYS_LASTCHANGED'] = $tstamp;
1364
        }
1365
    }
1366
1367
    /**
1368
     * Wraps the input string by the $wrap value and implements the "linkWrap" data type as well.
1369
     * The "linkWrap" data type means that this function will find any integer encapsulated in {} (curly braces) in the first wrap part and substitute it with the corresponding page uid from the rootline where the found integer is pointing to the key in the rootline. See link below.
1370
     *
1371
     * @param string $content Input string
1372
     * @param string $wrap A string where the first two parts separated by "|" (vertical line) will be wrapped around the input string
1373
     * @return string Wrapped output string
1374
     * @see wrap(), cImage(), FILE()
1375
     */
1376
    public function linkWrap($content, $wrap)
1377
    {
1378
        $wrapArr = explode('|', $wrap);
1379
        if (preg_match('/\\{([0-9]*)\\}/', $wrapArr[0], $reg)) {
1380
            if ($uid = $this->getTypoScriptFrontendController()->tmpl->rootLine[$reg[1]]['uid']) {
1381
                $wrapArr[0] = str_replace($reg[0], $uid, $wrapArr[0]);
1382
            }
1383
        }
1384
        return trim($wrapArr[0]) . $content . trim($wrapArr[1]);
1385
    }
1386
1387
    /**
1388
     * An abstraction method which creates an alt or title parameter for an HTML img, applet, area or input element and the FILE content element.
1389
     * From the $conf array it implements the properties "altText", "titleText" and "longdescURL"
1390
     *
1391
     * @param array $conf TypoScript configuration properties
1392
     * @param bool $longDesc If set, the longdesc attribute will be generated - must only be used for img elements!
1393
     * @return string Parameter string containing alt and title parameters (if any)
1394
     * @see IMGTEXT(), FILE(), FORM(), cImage(), filelink()
1395
     */
1396
    public function getAltParam($conf, $longDesc = true)
1397
    {
1398
        $altText = isset($conf['altText.']) ? trim($this->stdWrap($conf['altText'], $conf['altText.'])) : trim($conf['altText']);
1399
        $titleText = isset($conf['titleText.']) ? trim($this->stdWrap($conf['titleText'], $conf['titleText.'])) : trim($conf['titleText']);
1400
        if (isset($conf['longdescURL.']) && $this->getTypoScriptFrontendController()->config['config']['doctype'] != 'html5') {
1401
            $longDescUrl = $this->typoLink_URL($conf['longdescURL.']);
1402
        } else {
1403
            $longDescUrl = trim($conf['longdescURL']);
1404
        }
1405
        $longDescUrl = strip_tags($longDescUrl);
1406
1407
        // "alt":
1408
        $altParam = ' alt="' . htmlspecialchars($altText) . '"';
1409
        // "title":
1410
        $emptyTitleHandling = isset($conf['emptyTitleHandling.']) ? $this->stdWrap($conf['emptyTitleHandling'], $conf['emptyTitleHandling.']) : $conf['emptyTitleHandling'];
1411
        // Choices: 'keepEmpty' | 'useAlt' | 'removeAttr'
1412
        if ($titleText || $emptyTitleHandling === 'keepEmpty') {
1413
            $altParam .= ' title="' . htmlspecialchars($titleText) . '"';
1414
        } elseif (!$titleText && $emptyTitleHandling === 'useAlt') {
1415
            $altParam .= ' title="' . htmlspecialchars($altText) . '"';
1416
        }
1417
        // "longDesc" URL
1418
        if ($longDesc && !empty($longDescUrl)) {
1419
            $altParam .= ' longdesc="' . htmlspecialchars($longDescUrl) . '"';
1420
        }
1421
        return $altParam;
1422
    }
1423
1424
    /**
1425
     * An abstraction method to add parameters to an A tag.
1426
     * Uses the ATagParams property.
1427
     *
1428
     * @param array $conf TypoScript configuration properties
1429
     * @param bool|int $addGlobal If set, will add the global config.ATagParams to the link
1430
     * @return string String containing the parameters to the A tag (if non empty, with a leading space)
1431
     * @see IMGTEXT(), filelink(), makelinks(), typolink()
1432
     */
1433
    public function getATagParams($conf, $addGlobal = 1)
1434
    {
1435
        $aTagParams = '';
1436
        if ($conf['ATagParams.']) {
1437
            $aTagParams = ' ' . $this->stdWrap($conf['ATagParams'], $conf['ATagParams.']);
1438
        } elseif ($conf['ATagParams']) {
1439
            $aTagParams = ' ' . $conf['ATagParams'];
1440
        }
1441
        if ($addGlobal) {
1442
            $aTagParams = ' ' . trim($this->getTypoScriptFrontendController()->ATagParams . $aTagParams);
1443
        }
1444
        // Extend params
1445
        $_params = [
1446
            'conf' => &$conf,
1447
            'aTagParams' => &$aTagParams
1448
        ];
1449
        foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['getATagParamsPostProc'] ?? [] as $className) {
1450
            $processor =& GeneralUtility::makeInstance($className);
1451
            $aTagParams = $processor->process($_params, $this);
1452
        }
1453
1454
        $aTagParams = trim($aTagParams);
1455
        if (!empty($aTagParams)) {
1456
            $aTagParams = ' ' . $aTagParams;
1457
        }
1458
1459
        return $aTagParams;
1460
    }
1461
1462
    /**
1463
     * All extension links should ask this function for additional properties to their tags.
1464
     * Designed to add for instance an "onclick" property for site tracking systems.
1465
     *
1466
     * @param string $URL URL of the website
1467
     * @param string $TYPE
1468
     * @return string The additional tag properties
1469
     */
1470
    public function extLinkATagParams($URL, $TYPE)
1471
    {
1472
        $out = '';
1473
        if ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['extLinkATagParamsHandler']) {
1474
            $extLinkATagParamsHandler = GeneralUtility::makeInstance($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['extLinkATagParamsHandler']);
1475
            if (method_exists($extLinkATagParamsHandler, 'main')) {
1476
                $out .= trim($extLinkATagParamsHandler->main($URL, $TYPE, $this));
1477
            }
1478
        }
1479
        return trim($out) ? ' ' . trim($out) : '';
1480
    }
1481
1482
    /***********************************************
1483
     *
1484
     * HTML template processing functions
1485
     *
1486
     ***********************************************/
1487
1488
    /**
1489
     * Sets the current file object during iterations over files.
1490
     *
1491
     * @param File $fileObject The file object.
1492
     */
1493
    public function setCurrentFile($fileObject)
1494
    {
1495
        $this->currentFile = $fileObject;
1496
    }
1497
1498
    /**
1499
     * Gets the current file object during iterations over files.
1500
     *
1501
     * @return File The current file object.
1502
     */
1503
    public function getCurrentFile()
1504
    {
1505
        return $this->currentFile;
1506
    }
1507
1508
    /***********************************************
1509
     *
1510
     * "stdWrap" + sub functions
1511
     *
1512
     ***********************************************/
1513
    /**
1514
     * The "stdWrap" function. This is the implementation of what is known as "stdWrap properties" in TypoScript.
1515
     * Basically "stdWrap" performs some processing of a value based on properties in the input $conf array(holding the TypoScript "stdWrap properties")
1516
     * See the link below for a complete list of properties and what they do. The order of the table with properties found in TSref (the link) follows the actual order of implementation in this function.
1517
     *
1518
     * If $this->alternativeData is an array it's used instead of the $this->data array in ->getData
1519
     *
1520
     * @param string $content Input value undergoing processing in this function. Possibly substituted by other values fetched from another source.
1521
     * @param array $conf TypoScript "stdWrap properties".
1522
     * @return string The processed input value
1523
     */
1524
    public function stdWrap($content = '', $conf = [])
1525
    {
1526
        $content = (string)$content;
1527
        // If there is any hook object, activate all of the process and override functions.
1528
        // The hook interface ContentObjectStdWrapHookInterface takes care that all 4 methods exist.
1529
        if ($this->stdWrapHookObjects) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->stdWrapHookObjects of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
1530
            $conf['stdWrapPreProcess'] = 1;
1531
            $conf['stdWrapOverride'] = 1;
1532
            $conf['stdWrapProcess'] = 1;
1533
            $conf['stdWrapPostProcess'] = 1;
1534
        }
1535
1536
        if (!is_array($conf) || !$conf) {
1537
            return $content;
1538
        }
1539
1540
        // Cache handling
1541
        if (is_array($conf['cache.'])) {
1542
            $conf['cache.']['key'] = $this->stdWrap($conf['cache.']['key'], $conf['cache.']['key.']);
1543
            $conf['cache.']['tags'] = $this->stdWrap($conf['cache.']['tags'], $conf['cache.']['tags.']);
1544
            $conf['cache.']['lifetime'] = $this->stdWrap($conf['cache.']['lifetime'], $conf['cache.']['lifetime.']);
1545
            $conf['cacheRead'] = 1;
1546
            $conf['cacheStore'] = 1;
1547
        }
1548
        // The configuration is sorted and filtered by intersection with the defined stdWrapOrder.
1549
        $sortedConf = array_keys(array_intersect_key($this->stdWrapOrder, $conf));
1550
        // Functions types that should not make use of nested stdWrap function calls to avoid conflicts with internal TypoScript used by these functions
1551
        $stdWrapDisabledFunctionTypes = 'cObject,functionName,stdWrap';
1552
        // Additional Array to check whether a function has already been executed
1553
        $isExecuted = [];
1554
        // Additional switch to make sure 'required', 'if' and 'fieldRequired'
1555
        // will still stop rendering immediately in case they return FALSE
1556
        $this->stdWrapRecursionLevel++;
1557
        $this->stopRendering[$this->stdWrapRecursionLevel] = false;
1558
        // execute each function in the predefined order
1559
        foreach ($sortedConf as $stdWrapName) {
1560
            // eliminate the second key of a pair 'key'|'key.' to make sure functions get called only once and check if rendering has been stopped
1561
            if (!$isExecuted[$stdWrapName] && !$this->stopRendering[$this->stdWrapRecursionLevel]) {
1562
                $functionName = rtrim($stdWrapName, '.');
1563
                $functionProperties = $functionName . '.';
1564
                $functionType = $this->stdWrapOrder[$functionName];
1565
                // If there is any code on the next level, check if it contains "official" stdWrap functions
1566
                // if yes, execute them first - will make each function stdWrap aware
1567
                // so additional stdWrap calls within the functions can be removed, since the result will be the same
1568
                if (!empty($conf[$functionProperties]) && !GeneralUtility::inList($stdWrapDisabledFunctionTypes, $functionType)) {
1569
                    if (array_intersect_key($this->stdWrapOrder, $conf[$functionProperties])) {
1570
                        $conf[$functionName] = $this->stdWrap($conf[$functionName], $conf[$functionProperties]);
1571
                    }
1572
                }
1573
                // Check if key is still containing something, since it might have been changed by next level stdWrap before
1574
                if ((isset($conf[$functionName]) || $conf[$functionProperties]) && ($functionType !== 'boolean' || $conf[$functionName])) {
1575
                    // Get just that part of $conf that is needed for the particular function
1576
                    $singleConf = [
1577
                        $functionName => $conf[$functionName],
1578
                        $functionProperties => $conf[$functionProperties]
1579
                    ];
1580
                    // Hand over the whole $conf array to the stdWrapHookObjects
1581
                    if ($functionType === 'hook') {
1582
                        $singleConf = $conf;
1583
                    }
1584
                    // Add both keys - with and without the dot - to the set of executed functions
1585
                    $isExecuted[$functionName] = true;
1586
                    $isExecuted[$functionProperties] = true;
1587
                    // Call the function with the prefix stdWrap_ to make sure nobody can execute functions just by adding their name to the TS Array
1588
                    $functionName = 'stdWrap_' . $functionName;
1589
                    $content = $this->{$functionName}($content, $singleConf);
1590
                } elseif ($functionType === 'boolean' && !$conf[$functionName]) {
1591
                    $isExecuted[$functionName] = true;
1592
                    $isExecuted[$functionProperties] = true;
1593
                }
1594
            }
1595
        }
1596
        unset($this->stopRendering[$this->stdWrapRecursionLevel]);
1597
        $this->stdWrapRecursionLevel--;
1598
1599
        return $content;
1600
    }
1601
1602
    /**
1603
     * Gets a configuration value by passing them through stdWrap first and taking a default value if stdWrap doesn't yield a result.
1604
     *
1605
     * @param string $key The config variable key (from TS array).
1606
     * @param array $config The TypoScript array.
1607
     * @param string $defaultValue Optional default value.
1608
     * @return string Value of the config variable
1609
     */
1610
    public function stdWrapValue($key, array $config, $defaultValue = '')
1611
    {
1612
        if (isset($config[$key])) {
1613
            if (!isset($config[$key . '.'])) {
1614
                return $config[$key];
1615
            }
1616
        } elseif (isset($config[$key . '.'])) {
1617
            $config[$key] = '';
1618
        } else {
1619
            return $defaultValue;
1620
        }
1621
        $stdWrapped = $this->stdWrap($config[$key], $config[$key . '.']);
1622
        return $stdWrapped ?: $defaultValue;
1623
    }
1624
1625
    /**
1626
     * stdWrap pre process hook
1627
     * can be used by extensions authors to modify the behaviour of stdWrap functions to their needs
1628
     * this hook will execute functions before any other stdWrap function can modify anything
1629
     *
1630
     * @param string $content Input value undergoing processing in these functions.
1631
     * @param array $conf All stdWrap properties, not just the ones for a particular function.
1632
     * @return string The processed input value
1633
     */
1634
    public function stdWrap_stdWrapPreProcess($content = '', $conf = [])
1635
    {
1636
        foreach ($this->stdWrapHookObjects as $hookObject) {
1637
            /** @var ContentObjectStdWrapHookInterface $hookObject */
1638
            $content = $hookObject->stdWrapPreProcess($content, $conf, $this);
1639
        }
1640
        return $content;
1641
    }
1642
1643
    /**
1644
     * Check if content was cached before (depending on the given cache key)
1645
     *
1646
     * @param string $content Input value undergoing processing in these functions.
1647
     * @param array $conf All stdWrap properties, not just the ones for a particular function.
1648
     * @return string The processed input value
1649
     */
1650
    public function stdWrap_cacheRead($content = '', $conf = [])
1651
    {
1652
        if (!isset($conf['cache.'])) {
1653
            return $content;
1654
        }
1655
        $result = $this->getFromCache($conf['cache.']);
1656
        return $result === false ? $content : $result;
1657
    }
1658
1659
    /**
1660
     * Add tags to page cache (comma-separated list)
1661
     *
1662
     * @param string $content Input value undergoing processing in these functions.
1663
     * @param array $conf All stdWrap properties, not just the ones for a particular function.
1664
     * @return string The processed input value
1665
     */
1666
    public function stdWrap_addPageCacheTags($content = '', $conf = [])
1667
    {
1668
        $tags = isset($conf['addPageCacheTags.'])
1669
            ? $this->stdWrap($conf['addPageCacheTags'], $conf['addPageCacheTags.'])
1670
            : $conf['addPageCacheTags'];
1671
        if (!empty($tags)) {
1672
            $cacheTags = GeneralUtility::trimExplode(',', $tags, true);
1673
            $this->getTypoScriptFrontendController()->addCacheTags($cacheTags);
1674
        }
1675
        return $content;
1676
    }
1677
1678
    /**
1679
     * setContentToCurrent
1680
     * actually it just does the contrary: Sets the value of 'current' based on current content
1681
     *
1682
     * @param string $content Input value undergoing processing in this function.
1683
     * @return string The processed input value
1684
     */
1685
    public function stdWrap_setContentToCurrent($content = '')
1686
    {
1687
        $this->data[$this->currentValKey] = $content;
1688
        return $content;
1689
    }
1690
1691
    /**
1692
     * setCurrent
1693
     * Sets the value of 'current' based on the outcome of stdWrap operations
1694
     *
1695
     * @param string $content Input value undergoing processing in this function.
1696
     * @param array $conf stdWrap properties for setCurrent.
1697
     * @return string The processed input value
1698
     */
1699
    public function stdWrap_setCurrent($content = '', $conf = [])
1700
    {
1701
        $this->data[$this->currentValKey] = $conf['setCurrent'];
1702
        return $content;
1703
    }
1704
1705
    /**
1706
     * lang
1707
     * Translates content based on the language currently used by the FE
1708
     *
1709
     * @param string $content Input value undergoing processing in this function.
1710
     * @param array $conf stdWrap properties for lang.
1711
     * @return string The processed input value
1712
     */
1713
    public function stdWrap_lang($content = '', $conf = [])
1714
    {
1715
        $tsfe = $this->getTypoScriptFrontendController();
1716
        if (isset($conf['lang.']) && $tsfe->config['config']['language'] && isset($conf['lang.'][$tsfe->config['config']['language']])) {
1717
            $content = $conf['lang.'][$tsfe->config['config']['language']];
1718
        }
1719
        return $content;
1720
    }
1721
1722
    /**
1723
     * data
1724
     * Gets content from different sources based on getText functions, makes use of alternativeData, when set
1725
     *
1726
     * @param string $content Input value undergoing processing in this function.
1727
     * @param array $conf stdWrap properties for data.
1728
     * @return string The processed input value
1729
     */
1730
    public function stdWrap_data($content = '', $conf = [])
1731
    {
1732
        $content = $this->getData($conf['data'], is_array($this->alternativeData) ? $this->alternativeData : $this->data);
1733
        // This must be unset directly after
1734
        $this->alternativeData = '';
1735
        return $content;
1736
    }
1737
1738
    /**
1739
     * field
1740
     * Gets content from a DB field
1741
     *
1742
     * @param string $content Input value undergoing processing in this function.
1743
     * @param array $conf stdWrap properties for field.
1744
     * @return string The processed input value
1745
     */
1746
    public function stdWrap_field($content = '', $conf = [])
1747
    {
1748
        return $this->getFieldVal($conf['field']);
1749
    }
1750
1751
    /**
1752
     * current
1753
     * Gets content that has been perviously set as 'current'
1754
     * Can be set via setContentToCurrent or setCurrent or will be set automatically i.e. inside the split function
1755
     *
1756
     * @param string $content Input value undergoing processing in this function.
1757
     * @param array $conf stdWrap properties for current.
1758
     * @return string The processed input value
1759
     */
1760
    public function stdWrap_current($content = '', $conf = [])
1761
    {
1762
        return $this->data[$this->currentValKey];
1763
    }
1764
1765
    /**
1766
     * cObject
1767
     * Will replace the content with the value of an official TypoScript cObject
1768
     * like TEXT, COA, HMENU
1769
     *
1770
     * @param string $content Input value undergoing processing in this function.
1771
     * @param array $conf stdWrap properties for cObject.
1772
     * @return string The processed input value
1773
     */
1774
    public function stdWrap_cObject($content = '', $conf = [])
1775
    {
1776
        return $this->cObjGetSingle($conf['cObject'], $conf['cObject.'], '/stdWrap/.cObject');
1777
    }
1778
1779
    /**
1780
     * numRows
1781
     * Counts the number of returned records of a DB operation
1782
     * makes use of select internally
1783
     *
1784
     * @param string $content Input value undergoing processing in this function.
1785
     * @param array $conf stdWrap properties for numRows.
1786
     * @return string The processed input value
1787
     */
1788
    public function stdWrap_numRows($content = '', $conf = [])
1789
    {
1790
        return $this->numRows($conf['numRows.']);
1791
    }
1792
1793
    /**
1794
     * filelist
1795
     * Will create a list of files based on some additional parameters
1796
     *
1797
     * @param string $content Input value undergoing processing in this function.
1798
     * @param array $conf stdWrap properties for filelist.
1799
     * @return string The processed input value
1800
     */
1801
    public function stdWrap_filelist($content = '', $conf = [])
1802
    {
1803
        return $this->filelist($conf['filelist']);
1804
    }
1805
1806
    /**
1807
     * preUserFunc
1808
     * Will execute a user public function before the content will be modified by any other stdWrap function
1809
     *
1810
     * @param string $content Input value undergoing processing in this function.
1811
     * @param array $conf stdWrap properties for preUserFunc.
1812
     * @return string The processed input value
1813
     */
1814
    public function stdWrap_preUserFunc($content = '', $conf = [])
1815
    {
1816
        return $this->callUserFunction($conf['preUserFunc'], $conf['preUserFunc.'], $content);
1817
    }
1818
1819
    /**
1820
     * stdWrap override hook
1821
     * can be used by extensions authors to modify the behaviour of stdWrap functions to their needs
1822
     * this hook will execute functions on existing content but still before the content gets modified or replaced
1823
     *
1824
     * @param string $content Input value undergoing processing in these functions.
1825
     * @param array $conf All stdWrap properties, not just the ones for a particular function.
1826
     * @return string The processed input value
1827
     */
1828
    public function stdWrap_stdWrapOverride($content = '', $conf = [])
1829
    {
1830
        foreach ($this->stdWrapHookObjects as $hookObject) {
1831
            /** @var ContentObjectStdWrapHookInterface $hookObject */
1832
            $content = $hookObject->stdWrapOverride($content, $conf, $this);
1833
        }
1834
        return $content;
1835
    }
1836
1837
    /**
1838
     * override
1839
     * Will override the current value of content with its own value'
1840
     *
1841
     * @param string $content Input value undergoing processing in this function.
1842
     * @param array $conf stdWrap properties for override.
1843
     * @return string The processed input value
1844
     */
1845
    public function stdWrap_override($content = '', $conf = [])
1846
    {
1847
        if (trim($conf['override'])) {
1848
            $content = $conf['override'];
1849
        }
1850
        return $content;
1851
    }
1852
1853
    /**
1854
     * preIfEmptyListNum
1855
     * Gets a value off a CSV list before the following ifEmpty check
1856
     * Makes sure that the result of ifEmpty will be TRUE in case the CSV does not contain a value at the position given by preIfEmptyListNum
1857
     *
1858
     * @param string $content Input value undergoing processing in this function.
1859
     * @param array $conf stdWrap properties for preIfEmptyListNum.
1860
     * @return string The processed input value
1861
     */
1862
    public function stdWrap_preIfEmptyListNum($content = '', $conf = [])
1863
    {
1864
        return $this->listNum($content, $conf['preIfEmptyListNum'], $conf['preIfEmptyListNum.']['splitChar']);
1865
    }
1866
1867
    /**
1868
     * ifNull
1869
     * Will set content to a replacement value in case the value of content is NULL
1870
     *
1871
     * @param string|null $content Input value undergoing processing in this function.
1872
     * @param array $conf stdWrap properties for ifNull.
1873
     * @return string The processed input value
1874
     */
1875
    public function stdWrap_ifNull($content = '', $conf = [])
1876
    {
1877
        return $content !== null ? $content : $conf['ifNull'];
1878
    }
1879
1880
    /**
1881
     * ifEmpty
1882
     * Will set content to a replacement value in case the trimmed value of content returns FALSE
1883
     * 0 (zero) will be replaced as well
1884
     *
1885
     * @param string $content Input value undergoing processing in this function.
1886
     * @param array $conf stdWrap properties for ifEmpty.
1887
     * @return string The processed input value
1888
     */
1889
    public function stdWrap_ifEmpty($content = '', $conf = [])
1890
    {
1891
        if (!trim($content)) {
1892
            $content = $conf['ifEmpty'];
1893
        }
1894
        return $content;
1895
    }
1896
1897
    /**
1898
     * ifBlank
1899
     * Will set content to a replacement value in case the trimmed value of content has no length
1900
     * 0 (zero) will not be replaced
1901
     *
1902
     * @param string $content Input value undergoing processing in this function.
1903
     * @param array $conf stdWrap properties for ifBlank.
1904
     * @return string The processed input value
1905
     */
1906
    public function stdWrap_ifBlank($content = '', $conf = [])
1907
    {
1908
        if (trim($content) === '') {
1909
            $content = $conf['ifBlank'];
1910
        }
1911
        return $content;
1912
    }
1913
1914
    /**
1915
     * listNum
1916
     * Gets a value off a CSV list after ifEmpty check
1917
     * Might return an empty value in case the CSV does not contain a value at the position given by listNum
1918
     * Use preIfEmptyListNum to avoid that behaviour
1919
     *
1920
     * @param string $content Input value undergoing processing in this function.
1921
     * @param array $conf stdWrap properties for listNum.
1922
     * @return string The processed input value
1923
     */
1924
    public function stdWrap_listNum($content = '', $conf = [])
1925
    {
1926
        return $this->listNum($content, $conf['listNum'], $conf['listNum.']['splitChar']);
1927
    }
1928
1929
    /**
1930
     * trim
1931
     * Cuts off any whitespace at the beginning and the end of the content
1932
     *
1933
     * @param string $content Input value undergoing processing in this function.
1934
     * @return string The processed input value
1935
     */
1936
    public function stdWrap_trim($content = '')
1937
    {
1938
        return trim($content);
1939
    }
1940
1941
    /**
1942
     * strPad
1943
     * Will return a string padded left/right/on both sides, based on configuration given as stdWrap properties
1944
     *
1945
     * @param string $content Input value undergoing processing in this function.
1946
     * @param array $conf stdWrap properties for strPad.
1947
     * @return string The processed input value
1948
     */
1949
    public function stdWrap_strPad($content = '', $conf = [])
1950
    {
1951
        // Must specify a length in conf for this to make sense
1952
        $length = 0;
1953
        // Padding with space is PHP-default
1954
        $padWith = ' ';
1955
        // Padding on the right side is PHP-default
1956
        $padType = STR_PAD_RIGHT;
1957
        if (!empty($conf['strPad.']['length'])) {
1958
            $length = isset($conf['strPad.']['length.']) ? $this->stdWrap($conf['strPad.']['length'], $conf['strPad.']['length.']) : $conf['strPad.']['length'];
1959
            $length = (int)$length;
1960
        }
1961
        if (isset($conf['strPad.']['padWith']) && (string)$conf['strPad.']['padWith'] !== '') {
1962
            $padWith = isset($conf['strPad.']['padWith.']) ? $this->stdWrap($conf['strPad.']['padWith'], $conf['strPad.']['padWith.']) : $conf['strPad.']['padWith'];
1963
        }
1964
        if (!empty($conf['strPad.']['type'])) {
1965
            $type = isset($conf['strPad.']['type.']) ? $this->stdWrap($conf['strPad.']['type'], $conf['strPad.']['type.']) : $conf['strPad.']['type'];
1966
            if (strtolower($type) === 'left') {
1967
                $padType = STR_PAD_LEFT;
1968
            } elseif (strtolower($type) === 'both') {
1969
                $padType = STR_PAD_BOTH;
1970
            }
1971
        }
1972
        return str_pad($content, $length, $padWith, $padType);
1973
    }
1974
1975
    /**
1976
     * stdWrap
1977
     * A recursive call of the stdWrap function set
1978
     * This enables the user to execute stdWrap functions in another than the predefined order
1979
     * It modifies the content, not the property
1980
     * while the new feature of chained stdWrap functions modifies the property and not the content
1981
     *
1982
     * @param string $content Input value undergoing processing in this function.
1983
     * @param array $conf stdWrap properties for stdWrap.
1984
     * @return string The processed input value
1985
     */
1986
    public function stdWrap_stdWrap($content = '', $conf = [])
1987
    {
1988
        return $this->stdWrap($content, $conf['stdWrap.']);
1989
    }
1990
1991
    /**
1992
     * stdWrap process hook
1993
     * can be used by extensions authors to modify the behaviour of stdWrap functions to their needs
1994
     * this hook executes functions directly after the recursive stdWrap function call but still before the content gets modified
1995
     *
1996
     * @param string $content Input value undergoing processing in these functions.
1997
     * @param array $conf All stdWrap properties, not just the ones for a particular function.
1998
     * @return string The processed input value
1999
     */
2000
    public function stdWrap_stdWrapProcess($content = '', $conf = [])
2001
    {
2002
        foreach ($this->stdWrapHookObjects as $hookObject) {
2003
            /** @var ContentObjectStdWrapHookInterface $hookObject */
2004
            $content = $hookObject->stdWrapProcess($content, $conf, $this);
2005
        }
2006
        return $content;
2007
    }
2008
2009
    /**
2010
     * required
2011
     * Will immediately stop rendering and return an empty value
2012
     * when there is no content at this point
2013
     *
2014
     * @param string $content Input value undergoing processing in this function.
2015
     * @return string The processed input value
2016
     */
2017
    public function stdWrap_required($content = '')
2018
    {
2019
        if ((string)$content === '') {
2020
            $content = '';
2021
            $this->stopRendering[$this->stdWrapRecursionLevel] = true;
2022
        }
2023
        return $content;
2024
    }
2025
2026
    /**
2027
     * if
2028
     * Will immediately stop rendering and return an empty value
2029
     * when the result of the checks returns FALSE
2030
     *
2031
     * @param string $content Input value undergoing processing in this function.
2032
     * @param array $conf stdWrap properties for if.
2033
     * @return string The processed input value
2034
     */
2035
    public function stdWrap_if($content = '', $conf = [])
2036
    {
2037
        if (empty($conf['if.']) || $this->checkIf($conf['if.'])) {
2038
            return $content;
2039
        }
2040
        $this->stopRendering[$this->stdWrapRecursionLevel] = true;
2041
        return '';
2042
    }
2043
2044
    /**
2045
     * fieldRequired
2046
     * Will immediately stop rendering and return an empty value
2047
     * when there is no content in the field given by fieldRequired
2048
     *
2049
     * @param string $content Input value undergoing processing in this function.
2050
     * @param array $conf stdWrap properties for fieldRequired.
2051
     * @return string The processed input value
2052
     */
2053
    public function stdWrap_fieldRequired($content = '', $conf = [])
2054
    {
2055
        if (!trim($this->data[$conf['fieldRequired']])) {
2056
            $content = '';
2057
            $this->stopRendering[$this->stdWrapRecursionLevel] = true;
2058
        }
2059
        return $content;
2060
    }
2061
2062
    /**
2063
     * stdWrap csConv: Converts the input to UTF-8
2064
     *
2065
     * The character set of the input must be specified. Returns the input if
2066
     * matters go wrong, for example if an invalid character set is given.
2067
     *
2068
     * @param string $content The string to convert.
2069
     * @param array $conf stdWrap properties for csConv.
2070
     * @return string The processed input.
2071
     */
2072
    public function stdWrap_csConv($content = '', $conf = [])
2073
    {
2074
        if (!empty($conf['csConv'])) {
2075
            $output = mb_convert_encoding($content, 'utf-8', trim(strtolower($conf['csConv'])));
2076
            return $output !== false && $output !== '' ? $output : $content;
2077
        }
2078
        return $content;
2079
    }
2080
2081
    /**
2082
     * parseFunc
2083
     * Will parse the content based on functions given as stdWrap properties
2084
     * Heavily used together with RTE based content
2085
     *
2086
     * @param string $content Input value undergoing processing in this function.
2087
     * @param array $conf stdWrap properties for parseFunc.
2088
     * @return string The processed input value
2089
     */
2090
    public function stdWrap_parseFunc($content = '', $conf = [])
2091
    {
2092
        return $this->parseFunc($content, $conf['parseFunc.'], $conf['parseFunc']);
2093
    }
2094
2095
    /**
2096
     * HTMLparser
2097
     * Will parse HTML content based on functions given as stdWrap properties
2098
     * Heavily used together with RTE based content
2099
     *
2100
     * @param string $content Input value undergoing processing in this function.
2101
     * @param array $conf stdWrap properties for HTMLparser.
2102
     * @return string The processed input value
2103
     */
2104
    public function stdWrap_HTMLparser($content = '', $conf = [])
2105
    {
2106
        if (is_array($conf['HTMLparser.'])) {
2107
            $content = $this->HTMLparser_TSbridge($content, $conf['HTMLparser.']);
2108
        }
2109
        return $content;
2110
    }
2111
2112
    /**
2113
     * split
2114
     * Will split the content by a given token and treat the results separately
2115
     * Automatically fills 'current' with a single result
2116
     *
2117
     * @param string $content Input value undergoing processing in this function.
2118
     * @param array $conf stdWrap properties for split.
2119
     * @return string The processed input value
2120
     */
2121
    public function stdWrap_split($content = '', $conf = [])
2122
    {
2123
        return $this->splitObj($content, $conf['split.']);
2124
    }
2125
2126
    /**
2127
     * replacement
2128
     * Will execute replacements on the content (optionally with preg-regex)
2129
     *
2130
     * @param string $content Input value undergoing processing in this function.
2131
     * @param array $conf stdWrap properties for replacement.
2132
     * @return string The processed input value
2133
     */
2134
    public function stdWrap_replacement($content = '', $conf = [])
2135
    {
2136
        return $this->replacement($content, $conf['replacement.']);
2137
    }
2138
2139
    /**
2140
     * prioriCalc
2141
     * Will use the content as a mathematical term and calculate the result
2142
     * Can be set to 1 to just get a calculated value or 'intval' to get the integer of the result
2143
     *
2144
     * @param string $content Input value undergoing processing in this function.
2145
     * @param array $conf stdWrap properties for prioriCalc.
2146
     * @return string The processed input value
2147
     */
2148
    public function stdWrap_prioriCalc($content = '', $conf = [])
2149
    {
2150
        $content = MathUtility::calculateWithParentheses($content);
2151
        if ($conf['prioriCalc'] === 'intval') {
2152
            $content = (int)$content;
2153
        }
2154
        return $content;
2155
    }
2156
2157
    /**
2158
     * char
2159
     * Returns a one-character string containing the character specified by ascii code.
2160
     *
2161
     * Reliable results only for character codes in the integer range 0 - 127.
2162
     *
2163
     * @see http://php.net/manual/en/function.chr.php
2164
     * @param string $content Input value undergoing processing in this function.
2165
     * @param array $conf stdWrap properties for char.
2166
     * @return string The processed input value
2167
     */
2168
    public function stdWrap_char($content = '', $conf = [])
2169
    {
2170
        return chr((int)$conf['char']);
2171
    }
2172
2173
    /**
2174
     * intval
2175
     * Will return an integer value of the current content
2176
     *
2177
     * @param string $content Input value undergoing processing in this function.
2178
     * @return string The processed input value
2179
     */
2180
    public function stdWrap_intval($content = '')
2181
    {
2182
        return (int)$content;
2183
    }
2184
2185
    /**
2186
     * Will return a hashed value of the current content
2187
     *
2188
     * @param string $content Input value undergoing processing in this function.
2189
     * @param array $conf stdWrap properties for hash.
2190
     * @return string The processed input value
2191
     * @link http://php.net/manual/de/function.hash-algos.php for a list of supported hash algorithms
2192
     */
2193
    public function stdWrap_hash($content = '', array $conf = [])
2194
    {
2195
        $algorithm = isset($conf['hash.']) ? $this->stdWrap($conf['hash'], $conf['hash.']) : $conf['hash'];
2196
        if (function_exists('hash') && in_array($algorithm, hash_algos())) {
2197
            return hash($algorithm, $content);
2198
        }
2199
        // Non-existing hashing algorithm
2200
        return '';
2201
    }
2202
2203
    /**
2204
     * stdWrap_round will return a rounded number with ceil(), floor() or round(), defaults to round()
2205
     * Only the english number format is supported . (dot) as decimal point
2206
     *
2207
     * @param string $content Input value undergoing processing in this function.
2208
     * @param array $conf stdWrap properties for round.
2209
     * @return string The processed input value
2210
     */
2211
    public function stdWrap_round($content = '', $conf = [])
2212
    {
2213
        return $this->round($content, $conf['round.']);
2214
    }
2215
2216
    /**
2217
     * numberFormat
2218
     * Will return a formatted number based on configuration given as stdWrap properties
2219
     *
2220
     * @param string $content Input value undergoing processing in this function.
2221
     * @param array $conf stdWrap properties for numberFormat.
2222
     * @return string The processed input value
2223
     */
2224
    public function stdWrap_numberFormat($content = '', $conf = [])
2225
    {
2226
        return $this->numberFormat($content, $conf['numberFormat.']);
0 ignored issues
show
Bug introduced by
$content of type string is incompatible with the type double expected by parameter $content of TYPO3\CMS\Frontend\Conte...enderer::numberFormat(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

2226
        return $this->numberFormat(/** @scrutinizer ignore-type */ $content, $conf['numberFormat.']);
Loading history...
2227
    }
2228
2229
    /**
2230
     * expandList
2231
     * Will return a formatted number based on configuration given as stdWrap properties
2232
     *
2233
     * @param string $content Input value undergoing processing in this function.
2234
     * @return string The processed input value
2235
     */
2236
    public function stdWrap_expandList($content = '')
2237
    {
2238
        return GeneralUtility::expandList($content);
2239
    }
2240
2241
    /**
2242
     * date
2243
     * Will return a formatted date based on configuration given according to PHP date/gmdate properties
2244
     * Will return gmdate when the property GMT returns TRUE
2245
     *
2246
     * @param string $content Input value undergoing processing in this function.
2247
     * @param array $conf stdWrap properties for date.
2248
     * @return string The processed input value
2249
     */
2250
    public function stdWrap_date($content = '', $conf = [])
2251
    {
2252
        // Check for zero length string to mimic default case of date/gmdate.
2253
        $content = (string)$content === '' ? $GLOBALS['EXEC_TIME'] : (int)$content;
2254
        $content = $conf['date.']['GMT'] ? gmdate($conf['date'], $content) : date($conf['date'], $content);
2255
        return $content;
2256
    }
2257
2258
    /**
2259
     * strftime
2260
     * Will return a formatted date based on configuration given according to PHP strftime/gmstrftime properties
2261
     * Will return gmstrftime when the property GMT returns TRUE
2262
     *
2263
     * @param string $content Input value undergoing processing in this function.
2264
     * @param array $conf stdWrap properties for strftime.
2265
     * @return string The processed input value
2266
     */
2267
    public function stdWrap_strftime($content = '', $conf = [])
2268
    {
2269
        // Check for zero length string to mimic default case of strtime/gmstrftime
2270
        $content = (string)$content === '' ? $GLOBALS['EXEC_TIME'] : (int)$content;
2271
        $content = $conf['strftime.']['GMT'] ? gmstrftime($conf['strftime'], $content) : strftime($conf['strftime'], $content);
2272
        if (!empty($conf['strftime.']['charset'])) {
2273
            $output = mb_convert_encoding($content, 'utf-8', trim(strtolower($conf['strftime.']['charset'])));
2274
            return $output ?: $content;
2275
        }
2276
        return $content;
2277
    }
2278
2279
    /**
2280
     * strtotime
2281
     * Will return a timestamp based on configuration given according to PHP strtotime
2282
     *
2283
     * @param string $content Input value undergoing processing in this function.
2284
     * @param array $conf stdWrap properties for strtotime.
2285
     * @return string The processed input value
2286
     */
2287
    public function stdWrap_strtotime($content = '', $conf = [])
2288
    {
2289
        if ($conf['strtotime'] !== '1') {
2290
            $content .= ' ' . $conf['strtotime'];
2291
        }
2292
        return strtotime($content, $GLOBALS['EXEC_TIME']);
2293
    }
2294
2295
    /**
2296
     * age
2297
     * Will return the age of a given timestamp based on configuration given by stdWrap properties
2298
     *
2299
     * @param string $content Input value undergoing processing in this function.
2300
     * @param array $conf stdWrap properties for age.
2301
     * @return string The processed input value
2302
     */
2303
    public function stdWrap_age($content = '', $conf = [])
2304
    {
2305
        return $this->calcAge((int)$GLOBALS['EXEC_TIME'] - (int)$content, $conf['age']);
2306
    }
2307
2308
    /**
2309
     * case
2310
     * Will transform the content to be upper or lower case only
2311
     * Leaves HTML tags untouched
2312
     *
2313
     * @param string $content Input value undergoing processing in this function.
2314
     * @param array $conf stdWrap properties for case.
2315
     * @return string The processed input value
2316
     */
2317
    public function stdWrap_case($content = '', $conf = [])
2318
    {
2319
        return $this->HTMLcaseshift($content, $conf['case']);
2320
    }
2321
2322
    /**
2323
     * bytes
2324
     * Will return the size of a given number in Bytes	 *
2325
     *
2326
     * @param string $content Input value undergoing processing in this function.
2327
     * @param array $conf stdWrap properties for bytes.
2328
     * @return string The processed input value
2329
     */
2330
    public function stdWrap_bytes($content = '', $conf = [])
2331
    {
2332
        return GeneralUtility::formatSize($content, $conf['bytes.']['labels'], $conf['bytes.']['base']);
0 ignored issues
show
Bug introduced by
$content of type string is incompatible with the type integer expected by parameter $sizeInBytes of TYPO3\CMS\Core\Utility\G...alUtility::formatSize(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

2332
        return GeneralUtility::formatSize(/** @scrutinizer ignore-type */ $content, $conf['bytes.']['labels'], $conf['bytes.']['base']);
Loading history...
2333
    }
2334
2335
    /**
2336
     * substring
2337
     * Will return a substring based on position information given by stdWrap properties
2338
     *
2339
     * @param string $content Input value undergoing processing in this function.
2340
     * @param array $conf stdWrap properties for substring.
2341
     * @return string The processed input value
2342
     */
2343
    public function stdWrap_substring($content = '', $conf = [])
2344
    {
2345
        return $this->substring($content, $conf['substring']);
2346
    }
2347
2348
    /**
2349
     * cropHTML
2350
     * Crops content to a given size while leaving HTML tags untouched
2351
     *
2352
     * @param string $content Input value undergoing processing in this function.
2353
     * @param array $conf stdWrap properties for cropHTML.
2354
     * @return string The processed input value
2355
     */
2356
    public function stdWrap_cropHTML($content = '', $conf = [])
2357
    {
2358
        return $this->cropHTML($content, $conf['cropHTML']);
2359
    }
2360
2361
    /**
2362
     * stripHtml
2363
     * Copmletely removes HTML tags from content
2364
     *
2365
     * @param string $content Input value undergoing processing in this function.
2366
     * @return string The processed input value
2367
     */
2368
    public function stdWrap_stripHtml($content = '')
2369
    {
2370
        return strip_tags($content);
2371
    }
2372
2373
    /**
2374
     * crop
2375
     * Crops content to a given size without caring about HTML tags
2376
     *
2377
     * @param string $content Input value undergoing processing in this function.
2378
     * @param array $conf stdWrap properties for crop.
2379
     * @return string The processed input value
2380
     */
2381
    public function stdWrap_crop($content = '', $conf = [])
2382
    {
2383
        return $this->crop($content, $conf['crop']);
2384
    }
2385
2386
    /**
2387
     * rawUrlEncode
2388
     * Encodes content to be used within URLs
2389
     *
2390
     * @param string $content Input value undergoing processing in this function.
2391
     * @return string The processed input value
2392
     */
2393
    public function stdWrap_rawUrlEncode($content = '')
2394
    {
2395
        return rawurlencode($content);
2396
    }
2397
2398
    /**
2399
     * htmlSpecialChars
2400
     * Transforms HTML tags to readable text by replacing special characters with their HTML entity
2401
     * When preserveEntities returns TRUE, existing entities will be left untouched
2402
     *
2403
     * @param string $content Input value undergoing processing in this function.
2404
     * @param array $conf stdWrap properties for htmlSpecalChars.
2405
     * @return string The processed input value
2406
     */
2407
    public function stdWrap_htmlSpecialChars($content = '', $conf = [])
2408
    {
2409
        if (!empty($conf['htmlSpecialChars.']['preserveEntities'])) {
2410
            $content = htmlspecialchars($content, ENT_COMPAT, 'UTF-8', false);
2411
        } else {
2412
            $content = htmlspecialchars($content);
2413
        }
2414
        return $content;
2415
    }
2416
2417
    /**
2418
     * encodeForJavaScriptValue
2419
     * Escapes content to be used inside JavaScript strings. No quotes are added around the value
2420
     * as this can easily be done in TypoScript
2421
     *
2422
     * @param string $content Input value undergoing processing in this function
2423
     * @return string The processed input value
2424
     */
2425
    public function stdWrap_encodeForJavaScriptValue($content = '')
2426
    {
2427
        return GeneralUtility::quoteJSvalue($content);
2428
    }
2429
2430
    /**
2431
     * doubleBrTag
2432
     * Searches for double line breaks and replaces them with the given value
2433
     *
2434
     * @param string $content Input value undergoing processing in this function.
2435
     * @param array $conf stdWrap properties for doubleBrTag.
2436
     * @return string The processed input value
2437
     */
2438
    public function stdWrap_doubleBrTag($content = '', $conf = [])
2439
    {
2440
        return preg_replace('/\R{1,2}[\t\x20]*\R{1,2}/', $conf['doubleBrTag'], $content);
2441
    }
2442
2443
    /**
2444
     * br
2445
     * Searches for single line breaks and replaces them with a <br />/<br> tag
2446
     * according to the doctype
2447
     *
2448
     * @param string $content Input value undergoing processing in this function.
2449
     * @return string The processed input value
2450
     */
2451
    public function stdWrap_br($content = '')
2452
    {
2453
        return nl2br($content, !empty($this->getTypoScriptFrontendController()->xhtmlDoctype));
2454
    }
2455
2456
    /**
2457
     * brTag
2458
     * Searches for single line feeds and replaces them with the given value
2459
     *
2460
     * @param string $content Input value undergoing processing in this function.
2461
     * @param array $conf stdWrap properties for brTag.
2462
     * @return string The processed input value
2463
     */
2464
    public function stdWrap_brTag($content = '', $conf = [])
2465
    {
2466
        return str_replace(LF, $conf['brTag'], $content);
2467
    }
2468
2469
    /**
2470
     * encapsLines
2471
     * Modifies text blocks by searching for lines which are not surrounded by HTML tags yet
2472
     * and wrapping them with values given by stdWrap properties
2473
     *
2474
     * @param string $content Input value undergoing processing in this function.
2475
     * @param array $conf stdWrap properties for erncapsLines.
2476
     * @return string The processed input value
2477
     */
2478
    public function stdWrap_encapsLines($content = '', $conf = [])
2479
    {
2480
        return $this->encaps_lineSplit($content, $conf['encapsLines.']);
2481
    }
2482
2483
    /**
2484
     * keywords
2485
     * Transforms content into a CSV list to be used i.e. as keywords within a meta tag
2486
     *
2487
     * @param string $content Input value undergoing processing in this function.
2488
     * @return string The processed input value
2489
     */
2490
    public function stdWrap_keywords($content = '')
2491
    {
2492
        return $this->keywords($content);
2493
    }
2494
2495
    /**
2496
     * innerWrap
2497
     * First of a set of different wraps which will be applied in a certain order before or after other functions that modify the content
2498
     * See wrap
2499
     *
2500
     * @param string $content Input value undergoing processing in this function.
2501
     * @param array $conf stdWrap properties for innerWrap.
2502
     * @return string The processed input value
2503
     */
2504
    public function stdWrap_innerWrap($content = '', $conf = [])
2505
    {
2506
        return $this->wrap($content, $conf['innerWrap']);
2507
    }
2508
2509
    /**
2510
     * innerWrap2
2511
     * Second of a set of different wraps which will be applied in a certain order before or after other functions that modify the content
2512
     * See wrap
2513
     *
2514
     * @param string $content Input value undergoing processing in this function.
2515
     * @param array $conf stdWrap properties for innerWrap2.
2516
     * @return string The processed input value
2517
     */
2518
    public function stdWrap_innerWrap2($content = '', $conf = [])
2519
    {
2520
        return $this->wrap($content, $conf['innerWrap2']);
2521
    }
2522
2523
    /**
2524
     * addParams
2525
     * Adds tag attributes to any content that is a tag
2526
     *
2527
     * @param string $content Input value undergoing processing in this function.
2528
     * @param array $conf stdWrap properties for addParams.
2529
     * @return string The processed input value
2530
     */
2531
    public function stdWrap_addParams($content = '', $conf = [])
2532
    {
2533
        return $this->addParams($content, $conf['addParams.']);
2534
    }
2535
2536
    /**
2537
     * filelink
2538
     * Used to make lists of links to files
2539
     * See wrap
2540
     *
2541
     * @param string $content Input value undergoing processing in this function.
2542
     * @param array $conf stdWrap properties for filelink.
2543
     * @return string The processed input value
2544
     */
2545
    public function stdWrap_filelink($content = '', $conf = [])
2546
    {
2547
        return $this->filelink($content, $conf['filelink.']);
2548
    }
2549
2550
    /**
2551
     * preCObject
2552
     * A content object that is prepended to the current content but between the innerWraps and the rest of the wraps
2553
     *
2554
     * @param string $content Input value undergoing processing in this function.
2555
     * @param array $conf stdWrap properties for preCObject.
2556
     * @return string The processed input value
2557
     */
2558
    public function stdWrap_preCObject($content = '', $conf = [])
2559
    {
2560
        return $this->cObjGetSingle($conf['preCObject'], $conf['preCObject.'], '/stdWrap/.preCObject') . $content;
2561
    }
2562
2563
    /**
2564
     * postCObject
2565
     * A content object that is appended to the current content but between the innerWraps and the rest of the wraps
2566
     *
2567
     * @param string $content Input value undergoing processing in this function.
2568
     * @param array $conf stdWrap properties for postCObject.
2569
     * @return string The processed input value
2570
     */
2571
    public function stdWrap_postCObject($content = '', $conf = [])
2572
    {
2573
        return $content . $this->cObjGetSingle($conf['postCObject'], $conf['postCObject.'], '/stdWrap/.postCObject');
2574
    }
2575
2576
    /**
2577
     * wrapAlign
2578
     * Wraps content with a div container having the style attribute text-align set to the given value
2579
     * See wrap
2580
     *
2581
     * @param string $content Input value undergoing processing in this function.
2582
     * @param array $conf stdWrap properties for wrapAlign.
2583
     * @return string The processed input value
2584
     */
2585
    public function stdWrap_wrapAlign($content = '', $conf = [])
2586
    {
2587
        $wrapAlign = trim($conf['wrapAlign']);
2588
        if ($wrapAlign) {
2589
            $content = $this->wrap($content, '<div style="text-align:' . htmlspecialchars($wrapAlign) . ';">|</div>');
2590
        }
2591
        return $content;
2592
    }
2593
2594
    /**
2595
     * typolink
2596
     * Wraps the content with a link tag
2597
     * URLs and other attributes are created automatically by the values given in the stdWrap properties
2598
     * See wrap
2599
     *
2600
     * @param string $content Input value undergoing processing in this function.
2601
     * @param array $conf stdWrap properties for typolink.
2602
     * @return string The processed input value
2603
     */
2604
    public function stdWrap_typolink($content = '', $conf = [])
2605
    {
2606
        return $this->typoLink($content, $conf['typolink.']);
2607
    }
2608
2609
    /**
2610
     * TCAselectItem
2611
     * Returns a list of options available for a given field in the DB which has to be of the type select
2612
     *
2613
     * @param string $content Input value undergoing processing in this function.
2614
     * @param array $conf stdWrap properties for TCAselectItem.
2615
     * @return string The processed input value
2616
     */
2617
    public function stdWrap_TCAselectItem($content = '', $conf = [])
2618
    {
2619
        if (is_array($conf['TCAselectItem.'])) {
2620
            $content = $this->TCAlookup($content, $conf['TCAselectItem.']);
2621
        }
2622
        return $content;
2623
    }
2624
2625
    /**
2626
     * wrap
2627
     * This is the "mother" of all wraps
2628
     * Third of a set of different wraps which will be applied in a certain order before or after other functions that modify the content
2629
     * Basically it will put additional content before and after the current content using a split character as a placeholder for the current content
2630
     * The default split character is | but it can be replaced with other characters by the property splitChar
2631
     * Any other wrap that does not have own splitChar settings will be using the default split char though
2632
     *
2633
     * @param string $content Input value undergoing processing in this function.
2634
     * @param array $conf stdWrap properties for wrap.
2635
     * @return string The processed input value
2636
     */
2637
    public function stdWrap_wrap($content = '', $conf = [])
2638
    {
2639
        return $this->wrap($content, $conf['wrap'], $conf['wrap.']['splitChar'] ? $conf['wrap.']['splitChar'] : '|');
2640
    }
2641
2642
    /**
2643
     * noTrimWrap
2644
     * Fourth of a set of different wraps which will be applied in a certain order before or after other functions that modify the content
2645
     * The major difference to any other wrap is, that this one can make use of whitespace without trimming	 *
2646
     *
2647
     * @param string $content Input value undergoing processing in this function.
2648
     * @param array $conf stdWrap properties for noTrimWrap.
2649
     * @return string The processed input value
2650
     */
2651
    public function stdWrap_noTrimWrap($content = '', $conf = [])
2652
    {
2653
        $splitChar = isset($conf['noTrimWrap.']['splitChar.'])
2654
            ? $this->stdWrap($conf['noTrimWrap.']['splitChar'], $conf['noTrimWrap.']['splitChar.'])
2655
            : $conf['noTrimWrap.']['splitChar'];
2656
        if ($splitChar === null || $splitChar === '') {
2657
            $splitChar = '|';
2658
        }
2659
        $content = $this->noTrimWrap(
2660
            $content,
2661
            $conf['noTrimWrap'],
2662
            $splitChar
2663
        );
2664
        return $content;
2665
    }
2666
2667
    /**
2668
     * wrap2
2669
     * Fifth of a set of different wraps which will be applied in a certain order before or after other functions that modify the content
2670
     * The default split character is | but it can be replaced with other characters by the property splitChar
2671
     *
2672
     * @param string $content Input value undergoing processing in this function.
2673
     * @param array $conf stdWrap properties for wrap2.
2674
     * @return string The processed input value
2675
     */
2676
    public function stdWrap_wrap2($content = '', $conf = [])
2677
    {
2678
        return $this->wrap($content, $conf['wrap2'], $conf['wrap2.']['splitChar'] ? $conf['wrap2.']['splitChar'] : '|');
2679
    }
2680
2681
    /**
2682
     * dataWrap
2683
     * Sixth of a set of different wraps which will be applied in a certain order before or after other functions that modify the content
2684
     * Can fetch additional content the same way data does (i.e. {field:whatever}) and apply it to the wrap before that is applied to the content
2685
     *
2686
     * @param string $content Input value undergoing processing in this function.
2687
     * @param array $conf stdWrap properties for dataWrap.
2688
     * @return string The processed input value
2689
     */
2690
    public function stdWrap_dataWrap($content = '', $conf = [])
2691
    {
2692
        return $this->dataWrap($content, $conf['dataWrap']);
2693
    }
2694
2695
    /**
2696
     * prepend
2697
     * A content object that will be prepended to the current content after most of the wraps have already been applied
2698
     *
2699
     * @param string $content Input value undergoing processing in this function.
2700
     * @param array $conf stdWrap properties for prepend.
2701
     * @return string The processed input value
2702
     */
2703
    public function stdWrap_prepend($content = '', $conf = [])
2704
    {
2705
        return $this->cObjGetSingle($conf['prepend'], $conf['prepend.'], '/stdWrap/.prepend') . $content;
2706
    }
2707
2708
    /**
2709
     * append
2710
     * A content object that will be appended to the current content after most of the wraps have already been applied
2711
     *
2712
     * @param string $content Input value undergoing processing in this function.
2713
     * @param array $conf stdWrap properties for append.
2714
     * @return string The processed input value
2715
     */
2716
    public function stdWrap_append($content = '', $conf = [])
2717
    {
2718
        return $content . $this->cObjGetSingle($conf['append'], $conf['append.'], '/stdWrap/.append');
2719
    }
2720
2721
    /**
2722
     * wrap3
2723
     * Seventh of a set of different wraps which will be applied in a certain order before or after other functions that modify the content
2724
     * The default split character is | but it can be replaced with other characters by the property splitChar
2725
     *
2726
     * @param string $content Input value undergoing processing in this function.
2727
     * @param array $conf stdWrap properties for wrap3.
2728
     * @return string The processed input value
2729
     */
2730
    public function stdWrap_wrap3($content = '', $conf = [])
2731
    {
2732
        return $this->wrap($content, $conf['wrap3'], $conf['wrap3.']['splitChar'] ? $conf['wrap3.']['splitChar'] : '|');
2733
    }
2734
2735
    /**
2736
     * orderedStdWrap
2737
     * Calls stdWrap for each entry in the provided array
2738
     *
2739
     * @param string $content Input value undergoing processing in this function.
2740
     * @param array $conf stdWrap properties for orderedStdWrap.
2741
     * @return string The processed input value
2742
     */
2743
    public function stdWrap_orderedStdWrap($content = '', $conf = [])
2744
    {
2745
        $sortedKeysArray = ArrayUtility::filterAndSortByNumericKeys($conf['orderedStdWrap.'], true);
2746
        foreach ($sortedKeysArray as $key) {
2747
            $content = $this->stdWrap($content, $conf['orderedStdWrap.'][$key . '.']);
2748
        }
2749
        return $content;
2750
    }
2751
2752
    /**
2753
     * outerWrap
2754
     * Eighth of a set of different wraps which will be applied in a certain order before or after other functions that modify the content
2755
     *
2756
     * @param string $content Input value undergoing processing in this function.
2757
     * @param array $conf stdWrap properties for outerWrap.
2758
     * @return string The processed input value
2759
     */
2760
    public function stdWrap_outerWrap($content = '', $conf = [])
2761
    {
2762
        return $this->wrap($content, $conf['outerWrap']);
2763
    }
2764
2765
    /**
2766
     * insertData
2767
     * Can fetch additional content the same way data does and replaces any occurrence of {field:whatever} with this content
2768
     *
2769
     * @param string $content Input value undergoing processing in this function.
2770
     * @return string The processed input value
2771
     */
2772
    public function stdWrap_insertData($content = '')
2773
    {
2774
        return $this->insertData($content);
2775
    }
2776
2777
    /**
2778
     * postUserFunc
2779
     * Will execute a user function after the content has been modified by any other stdWrap function
2780
     *
2781
     * @param string $content Input value undergoing processing in this function.
2782
     * @param array $conf stdWrap properties for postUserFunc.
2783
     * @return string The processed input value
2784
     */
2785
    public function stdWrap_postUserFunc($content = '', $conf = [])
2786
    {
2787
        return $this->callUserFunction($conf['postUserFunc'], $conf['postUserFunc.'], $content);
2788
    }
2789
2790
    /**
2791
     * postUserFuncInt
2792
     * Will execute a user function after the content has been created and each time it is fetched from Cache
2793
     * The result of this function itself will not be cached
2794
     *
2795
     * @param string $content Input value undergoing processing in this function.
2796
     * @param array $conf stdWrap properties for postUserFuncInt.
2797
     * @return string The processed input value
2798
     */
2799
    public function stdWrap_postUserFuncInt($content = '', $conf = [])
2800
    {
2801
        $substKey = 'INT_SCRIPT.' . $this->getTypoScriptFrontendController()->uniqueHash();
2802
        $this->getTypoScriptFrontendController()->config['INTincScript'][$substKey] = [
2803
            'content' => $content,
2804
            'postUserFunc' => $conf['postUserFuncInt'],
2805
            'conf' => $conf['postUserFuncInt.'],
2806
            'type' => 'POSTUSERFUNC',
2807
            'cObj' => serialize($this)
2808
        ];
2809
        $content = '<!--' . $substKey . '-->';
2810
        return $content;
2811
    }
2812
2813
    /**
2814
     * prefixComment
2815
     * Will add HTML comments to the content to make it easier to identify certain content elements within the HTML output later on
2816
     *
2817
     * @param string $content Input value undergoing processing in this function.
2818
     * @param array $conf stdWrap properties for prefixComment.
2819
     * @return string The processed input value
2820
     */
2821
    public function stdWrap_prefixComment($content = '', $conf = [])
2822
    {
2823
        if (!$this->getTypoScriptFrontendController()->config['config']['disablePrefixComment'] && !empty($conf['prefixComment'])) {
2824
            $content = $this->prefixComment($conf['prefixComment'], [], $content);
2825
        }
2826
        return $content;
2827
    }
2828
2829
    /**
2830
     * editIcons
2831
     * Will render icons for frontend editing as long as there is a BE user logged in
2832
     *
2833
     * @param string $content Input value undergoing processing in this function.
2834
     * @param array $conf stdWrap properties for editIcons.
2835
     * @return string The processed input value
2836
     */
2837
    public function stdWrap_editIcons($content = '', $conf = [])
2838
    {
2839
        if ($this->getTypoScriptFrontendController()->beUserLogin && $conf['editIcons']) {
2840
            if (!is_array($conf['editIcons.'])) {
2841
                $conf['editIcons.'] = [];
2842
            }
2843
            $content = $this->editIcons($content, $conf['editIcons'], $conf['editIcons.']);
2844
        }
2845
        return $content;
2846
    }
2847
2848
    /**
2849
     * editPanel
2850
     * Will render the edit panel for frontend editing as long as there is a BE user logged in
2851
     *
2852
     * @param string $content Input value undergoing processing in this function.
2853
     * @param array $conf stdWrap properties for editPanel.
2854
     * @return string The processed input value
2855
     */
2856
    public function stdWrap_editPanel($content = '', $conf = [])
2857
    {
2858
        if ($this->getTypoScriptFrontendController()->beUserLogin) {
2859
            $content = $this->editPanel($content, $conf['editPanel.']);
2860
        }
2861
        return $content;
2862
    }
2863
2864
    /**
2865
     * Store content into cache
2866
     *
2867
     * @param string $content Input value undergoing processing in these functions.
2868
     * @param array $conf All stdWrap properties, not just the ones for a particular function.
2869
     * @return string The processed input value
2870
     */
2871
    public function stdWrap_cacheStore($content = '', $conf = [])
2872
    {
2873
        if (!isset($conf['cache.'])) {
2874
            return $content;
2875
        }
2876
        $key = $this->calculateCacheKey($conf['cache.']);
2877
        if (empty($key)) {
2878
            return $content;
2879
        }
2880
        /** @var $cacheFrontend \TYPO3\CMS\Core\Cache\Frontend\VariableFrontend */
2881
        $cacheFrontend = GeneralUtility::makeInstance(CacheManager::class)->getCache('cache_hash');
2882
        $tags = $this->calculateCacheTags($conf['cache.']);
2883
        $lifetime = $this->calculateCacheLifetime($conf['cache.']);
2884
        foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['stdWrap_cacheStore'] ?? [] as $_funcRef) {
2885
            $params = [
2886
                'key' => $key,
2887
                'content' => $content,
2888
                'lifetime' => $lifetime,
2889
                'tags' => $tags
2890
            ];
2891
            GeneralUtility::callUserFunction($_funcRef, $params, $this);
2892
        }
2893
        $cacheFrontend->set($key, $content, $tags, $lifetime);
2894
        return $content;
2895
    }
2896
2897
    /**
2898
     * stdWrap post process hook
2899
     * can be used by extensions authors to modify the behaviour of stdWrap functions to their needs
2900
     * this hook executes functions at after the content has been modified by the rest of the stdWrap functions but still before debugging
2901
     *
2902
     * @param string $content Input value undergoing processing in these functions.
2903
     * @param array $conf All stdWrap properties, not just the ones for a particular function.
2904
     * @return string The processed input value
2905
     */
2906
    public function stdWrap_stdWrapPostProcess($content = '', $conf = [])
2907
    {
2908
        foreach ($this->stdWrapHookObjects as $hookObject) {
2909
            /** @var ContentObjectStdWrapHookInterface $hookObject */
2910
            $content = $hookObject->stdWrapPostProcess($content, $conf, $this);
2911
        }
2912
        return $content;
2913
    }
2914
2915
    /**
2916
     * debug
2917
     * Will output the content as readable HTML code
2918
     *
2919
     * @param string $content Input value undergoing processing in this function.
2920
     * @return string The processed input value
2921
     */
2922
    public function stdWrap_debug($content = '')
2923
    {
2924
        return '<pre>' . htmlspecialchars($content) . '</pre>';
2925
    }
2926
2927
    /**
2928
     * debugFunc
2929
     * Will output the content in a debug table
2930
     *
2931
     * @param string $content Input value undergoing processing in this function.
2932
     * @param array $conf stdWrap properties for debugFunc.
2933
     * @return string The processed input value
2934
     */
2935
    public function stdWrap_debugFunc($content = '', $conf = [])
2936
    {
2937
        debug((int)$conf['debugFunc'] === 2 ? [$content] : $content);
2938
        return $content;
2939
    }
2940
2941
    /**
2942
     * debugData
2943
     * Will output the data used by the current record in a debug table
2944
     *
2945
     * @param string $content Input value undergoing processing in this function.
2946
     * @return string The processed input value
2947
     */
2948
    public function stdWrap_debugData($content = '')
2949
    {
2950
        debug($this->data, '$cObj->data:');
2951
        if (is_array($this->alternativeData)) {
2952
            debug($this->alternativeData, '$this->alternativeData');
2953
        }
2954
        return $content;
2955
    }
2956
2957
    /**
2958
     * Returns number of rows selected by the query made by the properties set.
2959
     * Implements the stdWrap "numRows" property
2960
     *
2961
     * @param array $conf TypoScript properties for the property (see link to "numRows")
2962
     * @return int The number of rows found by the select
2963
     * @access private
2964
     * @see stdWrap()
2965
     */
2966
    public function numRows($conf)
2967
    {
2968
        $conf['select.']['selectFields'] = 'count(*)';
2969
        $statement = $this->exec_getQuery($conf['table'], $conf['select.']);
2970
2971
        return (int)$statement->fetchColumn(0);
2972
    }
2973
2974
    /**
2975
     * Exploding a string by the $char value (if integer its an ASCII value) and returning index $listNum
2976
     *
2977
     * @param string $content String to explode
2978
     * @param string $listNum Index-number. You can place the word "last" in it and it will be substituted with the pointer to the last value. You can use math operators like "+-/*" (passed to calc())
2979
     * @param string $char Either a string used to explode the content string or an integer value which will then be changed into a character, eg. "10" for a linebreak char.
2980
     * @return string
2981
     */
2982
    public function listNum($content, $listNum, $char)
2983
    {
2984
        $char = $char ?: ',';
2985
        if (MathUtility::canBeInterpretedAsInteger($char)) {
2986
            $char = chr($char);
0 ignored issues
show
Bug introduced by
$char of type string is incompatible with the type integer expected by parameter $ascii of chr(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

2986
            $char = chr(/** @scrutinizer ignore-type */ $char);
Loading history...
2987
        }
2988
        $temp = explode($char, $content);
2989
        $last = '' . (count($temp) - 1);
2990
        // Take a random item if requested
2991
        if ($listNum === 'rand') {
2992
            $listNum = rand(0, count($temp) - 1);
2993
        }
2994
        $index = $this->calc(str_ireplace('last', $last, $listNum));
2995
        return $temp[$index];
2996
    }
2997
2998
    /**
2999
     * Compares values together based on the settings in the input TypoScript array and returns the comparison result.
3000
     * Implements the "if" function in TYPO3 TypoScript
3001
     *
3002
     * @param array $conf TypoScript properties defining what to compare
3003
     * @return bool
3004
     * @see stdWrap(), _parseFunc()
3005
     */
3006
    public function checkIf($conf)
3007
    {
3008
        if (!is_array($conf)) {
3009
            return true;
3010
        }
3011
        if (isset($conf['directReturn'])) {
3012
            return (bool)$conf['directReturn'];
3013
        }
3014
        $flag = true;
3015
        if (isset($conf['isNull.'])) {
3016
            $isNull = $this->stdWrap('', $conf['isNull.']);
3017
            if ($isNull !== null) {
3018
                $flag = false;
3019
            }
3020
        }
3021 View Code Duplication
        if (isset($conf['isTrue']) || isset($conf['isTrue.'])) {
3022
            $isTrue = isset($conf['isTrue.']) ? trim($this->stdWrap($conf['isTrue'], $conf['isTrue.'])) : trim($conf['isTrue']);
3023
            if (!$isTrue) {
3024
                $flag = false;
3025
            }
3026
        }
3027 View Code Duplication
        if (isset($conf['isFalse']) || isset($conf['isFalse.'])) {
3028
            $isFalse = isset($conf['isFalse.']) ? trim($this->stdWrap($conf['isFalse'], $conf['isFalse.'])) : trim($conf['isFalse']);
3029
            if ($isFalse) {
3030
                $flag = false;
3031
            }
3032
        }
3033
        if (isset($conf['isPositive']) || isset($conf['isPositive.'])) {
3034
            $number = isset($conf['isPositive.']) ? $this->calc($this->stdWrap($conf['isPositive'], $conf['isPositive.'])) : $this->calc($conf['isPositive']);
3035
            if ($number < 1) {
3036
                $flag = false;
3037
            }
3038
        }
3039
        if ($flag) {
3040
            $value = isset($conf['value.']) ? trim($this->stdWrap($conf['value'], $conf['value.'])) : trim($conf['value']);
3041 View Code Duplication
            if (isset($conf['isGreaterThan']) || isset($conf['isGreaterThan.'])) {
3042
                $number = isset($conf['isGreaterThan.']) ? trim($this->stdWrap($conf['isGreaterThan'], $conf['isGreaterThan.'])) : trim($conf['isGreaterThan']);
3043
                if ($number <= $value) {
3044
                    $flag = false;
3045
                }
3046
            }
3047 View Code Duplication
            if (isset($conf['isLessThan']) || isset($conf['isLessThan.'])) {
3048
                $number = isset($conf['isLessThan.']) ? trim($this->stdWrap($conf['isLessThan'], $conf['isLessThan.'])) : trim($conf['isLessThan']);
3049
                if ($number >= $value) {
3050
                    $flag = false;
3051
                }
3052
            }
3053 View Code Duplication
            if (isset($conf['equals']) || isset($conf['equals.'])) {
3054
                $number = isset($conf['equals.']) ? trim($this->stdWrap($conf['equals'], $conf['equals.'])) : trim($conf['equals']);
3055
                if ($number != $value) {
3056
                    $flag = false;
3057
                }
3058
            }
3059
            if (isset($conf['isInList']) || isset($conf['isInList.'])) {
3060
                $number = isset($conf['isInList.']) ? trim($this->stdWrap($conf['isInList'], $conf['isInList.'])) : trim($conf['isInList']);
3061
                if (!GeneralUtility::inList($value, $number)) {
3062
                    $flag = false;
3063
                }
3064
            }
3065
        }
3066
        if ($conf['negate']) {
3067
            $flag = !$flag;
3068
        }
3069
        return $flag;
3070
    }
3071
3072
    /**
3073
     * Reads a directory for files and returns the filepaths in a string list separated by comma.
3074
     * Implements the stdWrap property "filelist"
3075
     *
3076
     * @param string $data The command which contains information about what files/directory listing to return. See the "filelist" property of stdWrap for details.
3077
     * @return string Comma list of files.
3078
     * @access private
3079
     * @see stdWrap()
3080
     */
3081
    public function filelist($data)
3082
    {
3083
        $data = trim($data);
3084
        if ($data === '') {
3085
            return '';
3086
        }
3087
        $data_arr = explode('|', $data);
3088
        // read directory:
3089
        // MUST exist!
3090
        $path = '';
3091
        if ($this->getTypoScriptFrontendController()->lockFilePath) {
3092
            // Cleaning name..., only relative paths accepted.
3093
            $path = $this->clean_directory($data_arr[0]);
3094
            // See if path starts with lockFilePath, the additional '/' is needed because clean_directory gets rid of it
3095
            $path = GeneralUtility::isFirstPartOfStr($path . '/', $this->getTypoScriptFrontendController()->lockFilePath) ? $path : '';
3096
        }
3097
        if (!$path) {
3098
            return '';
3099
        }
3100
        $items = [
3101
            'files' => [],
3102
            'sorting' => []
3103
        ];
3104
        $ext_list = strtolower(GeneralUtility::uniqueList($data_arr[1]));
3105
        $sorting = trim($data_arr[2]);
3106
        // Read dir:
3107
        $d = @dir($path);
0 ignored issues
show
Bug introduced by
The call to dir() has too few arguments starting with context. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

3107
        $d = @/** @scrutinizer ignore-call */ dir($path);

This check compares calls to functions or methods with their respective definitions. If the call has less arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
3108
        if (is_object($d)) {
3109
            $count = 0;
3110
            while ($entry = $d->read()) {
3111
                if ($entry !== '.' && $entry !== '..') {
3112
                    // Because of odd PHP-error where <br />-tag is sometimes placed after a filename!!
3113
                    $wholePath = $path . '/' . $entry;
3114
                    if (file_exists($wholePath) && filetype($wholePath) === 'file') {
3115
                        $info = GeneralUtility::split_fileref($wholePath);
3116
                        if (!$ext_list || GeneralUtility::inList($ext_list, $info['fileext'])) {
3117
                            $items['files'][] = $info['file'];
3118
                            switch ($sorting) {
3119
                                case 'name':
3120
                                    $items['sorting'][] = strtolower($info['file']);
3121
                                    break;
3122
                                case 'size':
3123
                                    $items['sorting'][] = filesize($wholePath);
3124
                                    break;
3125
                                case 'ext':
3126
                                    $items['sorting'][] = $info['fileext'];
3127
                                    break;
3128
                                case 'date':
3129
                                    $items['sorting'][] = filectime($wholePath);
3130
                                    break;
3131
                                case 'mdate':
3132
                                    $items['sorting'][] = filemtime($wholePath);
3133
                                    break;
3134
                                default:
3135
                                    $items['sorting'][] = $count;
3136
                            }
3137
                            $count++;
3138
                        }
3139
                    }
3140
                }
3141
            }
3142
            $d->close();
0 ignored issues
show
Bug introduced by
The call to Directory::close() has too few arguments starting with dir_handle. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

3142
            $d->/** @scrutinizer ignore-call */ 
3143
                close();

This check compares calls to functions or methods with their respective definitions. If the call has less arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
3143
        }
3144
        // Sort if required
3145
        if (!empty($items['sorting'])) {
3146
            if (strtolower(trim($data_arr[3])) !== 'r') {
3147
                asort($items['sorting']);
3148
            } else {
3149
                arsort($items['sorting']);
3150
            }
3151
        }
3152
        if (!empty($items['files'])) {
3153
            // Make list
3154
            reset($items['sorting']);
3155
            $fullPath = trim($data_arr[4]);
3156
            $list_arr = [];
3157
            foreach ($items['sorting'] as $key => $v) {
3158
                $list_arr[] = $fullPath ? $path . '/' . $items['files'][$key] : $items['files'][$key];
3159
            }
3160
            return implode(',', $list_arr);
3161
        }
3162
        return '';
3163
    }
3164
3165
    /**
3166
     * Cleans $theDir for slashes in the end of the string and returns the new path, if it exists on the server.
3167
     *
3168
     * @param string $theDir Absolute path to directory
3169
     * @return string The directory path if it existed as was valid to access.
3170
     * @access private
3171
     * @see filelist()
3172
     */
3173
    public function clean_directory($theDir)
3174
    {
3175
        // proceeds if no '//', '..' or '\' is in the $theFile
3176
        if (GeneralUtility::validPathStr($theDir)) {
3177
            // Removes all dots, slashes and spaces after a path...
3178
            $theDir = preg_replace('/[\\/\\. ]*$/', '', $theDir);
3179
            if (!GeneralUtility::isAbsPath($theDir) && @is_dir($theDir)) {
3180
                return $theDir;
3181
            }
3182
        }
3183
        return '';
3184
    }
3185
3186
    /**
3187
     * Passes the input value, $theValue, to an instance of "\TYPO3\CMS\Core\Html\HtmlParser"
3188
     * together with the TypoScript options which are first converted from a TS style array
3189
     * to a set of arrays with options for the \TYPO3\CMS\Core\Html\HtmlParser class.
3190
     *
3191
     * @param string $theValue The value to parse by the class \TYPO3\CMS\Core\Html\HtmlParser
3192
     * @param array $conf TypoScript properties for the parser. See link.
3193
     * @return string Return value.
3194
     * @see stdWrap(), \TYPO3\CMS\Core\Html\HtmlParser::HTMLparserConfig(), \TYPO3\CMS\Core\Html\HtmlParser::HTMLcleaner()
3195
     */
3196
    public function HTMLparser_TSbridge($theValue, $conf)
3197
    {
3198
        $htmlParser = GeneralUtility::makeInstance(HtmlParser::class);
3199
        $htmlParserCfg = $htmlParser->HTMLparserConfig($conf);
3200
        return $htmlParser->HTMLcleaner($theValue, $htmlParserCfg[0], $htmlParserCfg[1], $htmlParserCfg[2], $htmlParserCfg[3]);
3201
    }
3202
3203
    /**
3204
     * Wrapping input value in a regular "wrap" but parses the wrapping value first for "insertData" codes.
3205
     *
3206
     * @param string $content Input string being wrapped
3207
     * @param string $wrap The wrap string, eg. "<strong></strong>" or more likely here '<a href="index.php?id={TSFE:id}"> | </a>' which will wrap the input string in a <a> tag linking to the current page.
3208
     * @return string Output string wrapped in the wrapping value.
3209
     * @see insertData(), stdWrap()
3210
     */
3211
    public function dataWrap($content, $wrap)
3212
    {
3213
        return $this->wrap($content, $this->insertData($wrap));
3214
    }
3215
3216
    /**
3217
     * Implements the "insertData" property of stdWrap meaning that if strings matching {...} is found in the input string they
3218
     * will be substituted with the return value from getData (datatype) which is passed the content of the curly braces.
3219
     * If the content inside the curly braces starts with a hash sign {#...} it is a field name that must be quoted by Doctrine
3220
     * DBAL and is skipped here for later processing.
3221
     *
3222
     * Example: If input string is "This is the page title: {page:title}" then the part, '{page:title}', will be substituted with
3223
     * the current pages title field value.
3224
     *
3225
     * @param string $str Input value
3226
     * @return string Processed input value
3227
     * @see getData(), stdWrap(), dataWrap()
3228
     */
3229
    public function insertData($str)
3230
    {
3231
        $inside = 0;
3232
        $newVal = '';
3233
        $pointer = 0;
3234
        $totalLen = strlen($str);
3235
        do {
3236
            if (!$inside) {
3237
                $len = strcspn(substr($str, $pointer), '{');
0 ignored issues
show
Bug introduced by
It seems like substr($str, $pointer) can also be of type false; however, parameter $str1 of strcspn() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

3237
                $len = strcspn(/** @scrutinizer ignore-type */ substr($str, $pointer), '{');
Loading history...
3238
                $newVal .= substr($str, $pointer, $len);
3239
                $inside = true;
3240
                if (substr($str, $pointer + $len + 1, 1) === '#') {
3241
                    $len2 = strcspn(substr($str, $pointer + $len), '}');
3242
                    $newVal .= substr($str, $pointer + $len, $len2);
3243
                    $len += $len2;
3244
                    $inside = false;
3245
                }
3246
            } else {
3247
                $len = strcspn(substr($str, $pointer), '}') + 1;
3248
                $newVal .= $this->getData(substr($str, $pointer + 1, $len - 2), $this->data);
0 ignored issues
show
Bug introduced by
It seems like substr($str, $pointer + 1, $len - 2) can also be of type false; however, parameter $string of TYPO3\CMS\Frontend\Conte...jectRenderer::getData() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

3248
                $newVal .= $this->getData(/** @scrutinizer ignore-type */ substr($str, $pointer + 1, $len - 2), $this->data);
Loading history...
3249
                $inside = false;
3250
            }
3251
            $pointer += $len;
3252
        } while ($pointer < $totalLen);
3253
        return $newVal;
3254
    }
3255
3256
    /**
3257
     * Returns a HTML comment with the second part of input string (divided by "|") where first part is an integer telling how many trailing tabs to put before the comment on a new line.
3258
     * Notice; this function (used by stdWrap) can be disabled by a "config.disablePrefixComment" setting in TypoScript.
3259
     *
3260
     * @param string $str Input value
3261
     * @param array $conf TypoScript Configuration (not used at this point.)
3262
     * @param string $content The content to wrap the comment around.
3263
     * @return string Processed input value
3264
     * @see stdWrap()
3265
     */
3266
    public function prefixComment($str, $conf, $content)
3267
    {
3268
        if (empty($str)) {
3269
            return $content;
3270
        }
3271
        $parts = explode('|', $str);
3272
        $indent = (int)$parts[0];
3273
        $comment = htmlspecialchars($this->insertData($parts[1]));
3274
        $output = LF
3275
            . str_pad('', $indent, TAB) . '<!-- ' . $comment . ' [begin] -->' . LF
3276
            . str_pad('', ($indent + 1), TAB) . $content . LF
3277
            . str_pad('', $indent, TAB) . '<!-- ' . $comment . ' [end] -->' . LF
3278
            . str_pad('', ($indent + 1), TAB);
3279
        return $output;
3280
    }
3281
3282
    /**
3283
     * Implements the stdWrap property "substring" which is basically a TypoScript implementation of the PHP function, substr()
3284
     *
3285
     * @param string $content The string to perform the operation on
3286
     * @param string $options The parameters to substring, given as a comma list of integers where the first and second number is passed as arg 1 and 2 to substr().
3287
     * @return string The processed input value.
3288
     * @access private
3289
     * @see stdWrap()
3290
     */
3291
    public function substring($content, $options)
3292
    {
3293
        $options = GeneralUtility::intExplode(',', $options . ',');
3294
        if ($options[1]) {
3295
            return mb_substr($content, $options[0], $options[1], 'utf-8');
3296
        }
3297
        return mb_substr($content, $options[0], null, 'utf-8');
3298
    }
3299
3300
    /**
3301
     * Implements the stdWrap property "crop" which is a modified "substr" function allowing to limit a string length to a certain number of chars (from either start or end of string) and having a pre/postfix applied if the string really was cropped.
3302
     *
3303
     * @param string $content The string to perform the operation on
3304
     * @param string $options The parameters splitted by "|": First parameter is the max number of chars of the string. Negative value means cropping from end of string. Second parameter is the pre/postfix string to apply if cropping occurs. Third parameter is a boolean value. If set then crop will be applied at nearest space.
3305
     * @return string The processed input value.
3306
     * @access private
3307
     * @see stdWrap()
3308
     */
3309
    public function crop($content, $options)
3310
    {
3311
        $options = explode('|', $options);
3312
        $chars = (int)$options[0];
3313
        $afterstring = trim($options[1]);
3314
        $crop2space = trim($options[2]);
3315
        if ($chars) {
3316
            if (mb_strlen($content, 'utf-8') > abs($chars)) {
3317
                $truncatePosition = false;
3318
                if ($chars < 0) {
3319
                    $content = mb_substr($content, $chars, null, 'utf-8');
3320
                    if ($crop2space) {
3321
                        $truncatePosition = strpos($content, ' ');
3322
                    }
3323
                    $content = $truncatePosition ? $afterstring . substr($content, $truncatePosition) : $afterstring . $content;
0 ignored issues
show
Bug introduced by
Are you sure substr($content, $truncatePosition) of type false|string can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

3323
                    $content = $truncatePosition ? $afterstring . /** @scrutinizer ignore-type */ substr($content, $truncatePosition) : $afterstring . $content;
Loading history...
3324
                } else {
3325
                    $content = mb_substr($content, 0, $chars, 'utf-8');
3326
                    if ($crop2space) {
3327
                        $truncatePosition = strrpos($content, ' ');
3328
                    }
3329
                    $content = $truncatePosition ? substr($content, 0, $truncatePosition) . $afterstring : $content . $afterstring;
0 ignored issues
show
Bug introduced by
Are you sure substr($content, 0, $truncatePosition) of type false|string can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

3329
                    $content = $truncatePosition ? /** @scrutinizer ignore-type */ substr($content, 0, $truncatePosition) . $afterstring : $content . $afterstring;
Loading history...
3330
                }
3331
            }
3332
        }
3333
        return $content;
3334
    }
3335
3336
    /**
3337
     * Implements the stdWrap property "cropHTML" which is a modified "substr" function allowing to limit a string length
3338
     * to a certain number of chars (from either start or end of string) and having a pre/postfix applied if the string
3339
     * really was cropped.
3340
     *
3341
     * Compared to stdWrap.crop it respects HTML tags and entities.
3342
     *
3343
     * @param string $content The string to perform the operation on
3344
     * @param string $options The parameters splitted by "|": First parameter is the max number of chars of the string. Negative value means cropping from end of string. Second parameter is the pre/postfix string to apply if cropping occurs. Third parameter is a boolean value. If set then crop will be applied at nearest space.
3345
     * @return string The processed input value.
3346
     * @access private
3347
     * @see stdWrap()
3348
     */
3349
    public function cropHTML($content, $options)
3350
    {
3351
        $options = explode('|', $options);
3352
        $chars = (int)$options[0];
3353
        $absChars = abs($chars);
3354
        $replacementForEllipsis = trim($options[1]);
3355
        $crop2space = trim($options[2]) === '1';
3356
        // Split $content into an array(even items in the array are outside the tags, odd numbers are tag-blocks).
3357
        $tags = 'a|abbr|address|area|article|aside|audio|b|bdi|bdo|blockquote|body|br|button|caption|cite|code|col|colgroup|data|datalist|dd|del|dfn|div|dl|dt|em|embed|fieldset|figcaption|figure|font|footer|form|h1|h2|h3|h4|h5|h6|header|hr|i|iframe|img|input|ins|kbd|keygen|label|legend|li|link|main|map|mark|meter|nav|object|ol|optgroup|option|output|p|param|pre|progress|q|rb|rp|rt|rtc|ruby|s|samp|section|select|small|source|span|strong|sub|sup|table|tbody|td|textarea|tfoot|th|thead|time|tr|track|u|ul|ut|var|video|wbr';
3358
        $tagsRegEx = '
3359
			(
3360
				(?:
3361
					<!--.*?-->					# a comment
3362
					|
3363
					<canvas[^>]*>.*?</canvas>   # a canvas tag
3364
					|
3365
					<script[^>]*>.*?</script>   # a script tag
3366
					|
3367
					<noscript[^>]*>.*?</noscript> # a noscript tag
3368
					|
3369
					<template[^>]*>.*?</template> # a template tag
3370
				)
3371
				|
3372
				</?(?:' . $tags . ')+			# opening tag (\'<tag\') or closing tag (\'</tag\')
3373
				(?:
3374
					(?:
3375
						(?:
3376
							\\s+\\w[\\w-]*		# EITHER spaces, followed by attribute names
3377
							(?:
3378
								\\s*=?\\s*		# equals
3379
								(?>
3380
									".*?"		# attribute values in double-quotes
3381
									|
3382
									\'.*?\'		# attribute values in single-quotes
3383
									|
3384
									[^\'">\\s]+	# plain attribute values
3385
								)
3386
							)?
3387
						)
3388
						|						# OR a single dash (for TYPO3 link tag)
3389
						(?:
3390
							\\s+-
3391
						)
3392
					)+\\s*
3393
					|							# OR only spaces
3394
					\\s*
3395
				)
3396
				/?>								# closing the tag with \'>\' or \'/>\'
3397
			)';
3398
        $splittedContent = preg_split('%' . $tagsRegEx . '%xs', $content, -1, PREG_SPLIT_DELIM_CAPTURE);
3399
        // Reverse array if we are cropping from right.
3400
        if ($chars < 0) {
3401
            $splittedContent = array_reverse($splittedContent);
0 ignored issues
show
Bug introduced by
It seems like $splittedContent can also be of type false; however, parameter $array of array_reverse() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

3401
            $splittedContent = array_reverse(/** @scrutinizer ignore-type */ $splittedContent);
Loading history...
3402
        }
3403
        // Crop the text (chars of tag-blocks are not counted).
3404
        $strLen = 0;
3405
        // This is the offset of the content item which was cropped.
3406
        $croppedOffset = null;
3407
        $countSplittedContent = count($splittedContent);
0 ignored issues
show
Bug introduced by
It seems like $splittedContent can also be of type false; however, parameter $var of count() does only seem to accept Countable|array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

3407
        $countSplittedContent = count(/** @scrutinizer ignore-type */ $splittedContent);
Loading history...
3408
        for ($offset = 0; $offset < $countSplittedContent; $offset++) {
3409
            if ($offset % 2 === 0) {
3410
                $tempContent = $splittedContent[$offset];
3411
                $thisStrLen = mb_strlen(html_entity_decode($tempContent, ENT_COMPAT, 'UTF-8'), 'utf-8');
3412
                if ($strLen + $thisStrLen > $absChars) {
3413
                    $croppedOffset = $offset;
3414
                    $cropPosition = $absChars - $strLen;
3415
                    // The snippet "&[^&\s;]{2,8};" in the RegEx below represents entities.
3416
                    $patternMatchEntityAsSingleChar = '(&[^&\\s;]{2,8};|.)';
3417
                    $cropRegEx = $chars < 0 ? '#' . $patternMatchEntityAsSingleChar . '{0,' . ($cropPosition + 1) . '}$#uis' : '#^' . $patternMatchEntityAsSingleChar . '{0,' . ($cropPosition + 1) . '}#uis';
3418
                    if (preg_match($cropRegEx, $tempContent, $croppedMatch)) {
3419
                        $tempContentPlusOneCharacter = $croppedMatch[0];
3420
                    } else {
3421
                        $tempContentPlusOneCharacter = false;
3422
                    }
3423
                    $cropRegEx = $chars < 0 ? '#' . $patternMatchEntityAsSingleChar . '{0,' . $cropPosition . '}$#uis' : '#^' . $patternMatchEntityAsSingleChar . '{0,' . $cropPosition . '}#uis';
3424
                    if (preg_match($cropRegEx, $tempContent, $croppedMatch)) {
3425
                        $tempContent = $croppedMatch[0];
3426
                        if ($crop2space && $tempContentPlusOneCharacter !== false) {
3427
                            $cropRegEx = $chars < 0 ? '#(?<=\\s)' . $patternMatchEntityAsSingleChar . '{0,' . $cropPosition . '}$#uis' : '#^' . $patternMatchEntityAsSingleChar . '{0,' . $cropPosition . '}(?=\\s)#uis';
3428
                            if (preg_match($cropRegEx, $tempContentPlusOneCharacter, $croppedMatch)) {
3429
                                $tempContent = $croppedMatch[0];
3430
                            }
3431
                        }
3432
                    }
3433
                    $splittedContent[$offset] = $tempContent;
3434
                    break;
3435
                }
3436
                $strLen += $thisStrLen;
3437
            }
3438
        }
3439
        // Close cropped tags.
3440
        $closingTags = [];
3441
        if ($croppedOffset !== null) {
3442
            $openingTagRegEx = '#^<(\\w+)(?:\\s|>)#';
3443
            $closingTagRegEx = '#^</(\\w+)(?:\\s|>)#';
3444
            for ($offset = $croppedOffset - 1; $offset >= 0; $offset = $offset - 2) {
3445
                if (substr($splittedContent[$offset], -2) === '/>') {
3446
                    // Ignore empty element tags (e.g. <br />).
3447
                    continue;
3448
                }
3449
                preg_match($chars < 0 ? $closingTagRegEx : $openingTagRegEx, $splittedContent[$offset], $matches);
3450
                $tagName = isset($matches[1]) ? $matches[1] : null;
3451
                if ($tagName !== null) {
3452
                    // Seek for the closing (or opening) tag.
3453
                    $countSplittedContent = count($splittedContent);
3454
                    for ($seekingOffset = $offset + 2; $seekingOffset < $countSplittedContent; $seekingOffset = $seekingOffset + 2) {
3455
                        preg_match($chars < 0 ? $openingTagRegEx : $closingTagRegEx, $splittedContent[$seekingOffset], $matches);
3456
                        $seekingTagName = isset($matches[1]) ? $matches[1] : null;
3457
                        if ($tagName === $seekingTagName) {
3458
                            // We found a matching tag.
3459
                            // Add closing tag only if it occurs after the cropped content item.
3460
                            if ($seekingOffset > $croppedOffset) {
3461
                                $closingTags[] = $splittedContent[$seekingOffset];
3462
                            }
3463
                            break;
3464
                        }
3465
                    }
3466
                }
3467
            }
3468
            // Drop the cropped items of the content array. The $closingTags will be added later on again.
3469
            array_splice($splittedContent, $croppedOffset + 1);
0 ignored issues
show
Bug introduced by
It seems like $splittedContent can also be of type false; however, parameter $input of array_splice() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

3469
            array_splice(/** @scrutinizer ignore-type */ $splittedContent, $croppedOffset + 1);
Loading history...
3470
        }
3471
        $splittedContent = array_merge($splittedContent, [
0 ignored issues
show
Bug introduced by
It seems like $splittedContent can also be of type false; however, parameter $array1 of array_merge() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

3471
        $splittedContent = array_merge(/** @scrutinizer ignore-type */ $splittedContent, [
Loading history...
3472
            $croppedOffset !== null ? $replacementForEllipsis : ''
3473
        ], $closingTags);
3474
        // Reverse array once again if we are cropping from the end.
3475
        if ($chars < 0) {
3476
            $splittedContent = array_reverse($splittedContent);
3477
        }
3478
        return implode('', $splittedContent);
3479
    }
3480
3481
    /**
3482
     * Implements the TypoScript function "addParams"
3483
     *
3484
     * @param string $content The string with the HTML tag.
3485
     * @param array $conf The TypoScript configuration properties
3486
     * @return string The modified string
3487
     * @todo Make it XHTML compatible. Will not present "/>" endings of tags right now. Further getting the tagname might fail if it is not separated by a normal space from the attributes.
3488
     */
3489
    public function addParams($content, $conf)
3490
    {
3491
        // For XHTML compliance.
3492
        $lowerCaseAttributes = true;
3493
        if (!is_array($conf)) {
3494
            return $content;
3495
        }
3496
        $key = 1;
3497
        $parts = explode('<', $content);
3498
        if ((int)$conf['_offset']) {
3499
            $key = (int)$conf['_offset'] < 0 ? count($parts) + (int)$conf['_offset'] : (int)$conf['_offset'];
3500
        }
3501
        $subparts = explode('>', $parts[$key]);
3502
        if (trim($subparts[0])) {
3503
            // Get attributes and name
3504
            $attribs = GeneralUtility::get_tag_attributes('<' . $subparts[0] . '>');
3505
            list($tagName) = explode(' ', $subparts[0], 2);
3506
            // adds/overrides attributes
3507
            foreach ($conf as $pkey => $val) {
3508
                if (substr($pkey, -1) !== '.' && $pkey[0] !== '_') {
3509
                    $tmpVal = isset($conf[$pkey . '.']) ? $this->stdWrap($conf[$pkey], $conf[$pkey . '.']) : (string)$val;
3510
                    if ($lowerCaseAttributes) {
3511
                        $pkey = strtolower($pkey);
3512
                    }
3513
                    if ($tmpVal !== '') {
3514
                        $attribs[$pkey] = $tmpVal;
3515
                    }
3516
                }
3517
            }
3518
            // Re-assembles the tag and content
3519
            $subparts[0] = trim($tagName . ' ' . GeneralUtility::implodeAttributes($attribs));
3520
            $parts[$key] = implode('>', $subparts);
3521
            $content = implode('<', $parts);
3522
        }
3523
        return $content;
3524
    }
3525
3526
    /**
3527
     * Creates a list of links to files.
3528
     * Implements the stdWrap property "filelink"
3529
     *
3530
     * @param string $theValue The filename to link to, possibly prefixed with $conf[path]
3531
     * @param array $conf TypoScript parameters for the TypoScript function ->filelink
3532
     * @return string The link to the file possibly with icons, thumbnails, size in bytes shown etc.
3533
     * @access private
3534
     * @see stdWrap()
3535
     */
3536
    public function filelink($theValue, $conf)
3537
    {
3538
        $conf['path'] = isset($conf['path.']) ? $this->stdWrap($conf['path'], $conf['path.']) : $conf['path'];
3539
        $theFile = trim($conf['path']) . $theValue;
3540
        if (!@is_file($theFile)) {
3541
            return '';
3542
        }
3543
        $theFileEnc = str_replace('%2F', '/', rawurlencode($theFile));
3544
        $title = $conf['title'];
3545
        if (isset($conf['title.'])) {
3546
            $title = $this->stdWrap($title, $conf['title.']);
0 ignored issues
show
Bug introduced by
It seems like $conf['title.'] can also be of type string; however, parameter $conf of TYPO3\CMS\Frontend\Conte...jectRenderer::stdWrap() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

3546
            $title = $this->stdWrap($title, /** @scrutinizer ignore-type */ $conf['title.']);
Loading history...
3547
        }
3548
        $target = $conf['target'];
3549
        if (isset($conf['target.'])) {
3550
            $target = $this->stdWrap($target, $conf['target.']);
3551
        }
3552
        $tsfe = $this->getTypoScriptFrontendController();
3553
3554
        $typoLinkConf = [
3555
            'parameter' => $theFileEnc,
3556
            'fileTarget' => $target,
3557
            'title' => $title,
3558
            'ATagParams' => $this->getATagParams($conf)
3559
        ];
3560
3561
        if (isset($conf['typolinkConfiguration.'])) {
3562
            $additionalTypoLinkConfiguration = $conf['typolinkConfiguration.'];
3563
            // We only allow additional configuration. This is why the generated conf overwrites the additional conf.
3564
            ArrayUtility::mergeRecursiveWithOverrule($additionalTypoLinkConfiguration, $typoLinkConf);
3565
            $typoLinkConf = $additionalTypoLinkConfiguration;
3566
        }
3567
3568
        $theLinkWrap = $this->typoLink('|', $typoLinkConf);
3569
        $theSize = filesize($theFile);
3570
        $fI = GeneralUtility::split_fileref($theFile);
3571
        $icon = '';
3572
        if ($conf['icon']) {
3573
            $conf['icon.']['path'] = isset($conf['icon.']['path.'])
3574
                ? $this->stdWrap($conf['icon.']['path'], $conf['icon.']['path.'])
3575
                : $conf['icon.']['path'];
3576
            $iconPath = !empty($conf['icon.']['path'])
3577
                ? $conf['icon.']['path']
3578
                : GeneralUtility::getFileAbsFileName('EXT:frontend/Resources/Public/Icons/FileIcons/');
3579
            $conf['icon.']['ext'] = isset($conf['icon.']['ext.'])
3580
                ? $this->stdWrap($conf['icon.']['ext'], $conf['icon.']['ext.'])
3581
                : $conf['icon.']['ext'];
3582
            $iconExt = !empty($conf['icon.']['ext']) ? '.' . $conf['icon.']['ext'] : '.gif';
3583
            $icon = @is_file(($iconPath . $fI['fileext'] . $iconExt))
3584
                ? $iconPath . $fI['fileext'] . $iconExt
3585
                : $iconPath . 'default' . $iconExt;
3586
            $icon = PathUtility::stripPathSitePrefix($icon);
3587
            // Checking for images: If image, then return link to thumbnail.
3588
            $IEList = isset($conf['icon_image_ext_list.']) ? $this->stdWrap($conf['icon_image_ext_list'], $conf['icon_image_ext_list.']) : $conf['icon_image_ext_list'];
3589
            $image_ext_list = str_replace(' ', '', strtolower($IEList));
3590
            if ($fI['fileext'] && GeneralUtility::inList($image_ext_list, $fI['fileext'])) {
3591
                if ($conf['iconCObject']) {
3592
                    $icon = $this->cObjGetSingle($conf['iconCObject'], $conf['iconCObject.'], 'iconCObject');
3593
                } else {
3594
                    $notFoundThumb = GeneralUtility::getFileAbsFileName('EXT:core/Resources/Public/Images/NotFound.gif');
3595
                    $notFoundThumb = PathUtility::stripPathSitePrefix($notFoundThumb);
3596
                    $sizeParts = [64, 64];
3597
                    if ($GLOBALS['TYPO3_CONF_VARS']['GFX']['thumbnails']) {
3598
                        // using the File Abstraction Layer to generate a preview image
3599
                        try {
3600
                            /** @var File $fileObject */
3601
                            $fileObject = ResourceFactory::getInstance()->retrieveFileOrFolderObject($theFile);
3602
                            if ($fileObject->isMissing()) {
3603
                                $icon = $notFoundThumb;
3604
                            } else {
3605
                                $fileExtension = $fileObject->getExtension();
3606
                                if ($fileExtension === 'ttf' || GeneralUtility::inList($GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'], $fileExtension)) {
3607
                                    if ($conf['icon_thumbSize'] || $conf['icon_thumbSize.']) {
3608
                                        $thumbSize = isset($conf['icon_thumbSize.']) ? $this->stdWrap($conf['icon_thumbSize'], $conf['icon_thumbSize.']) : $conf['icon_thumbSize'];
3609
                                        $sizeParts = explode('x', $thumbSize);
3610
                                    }
3611
                                    $icon = $fileObject->process(ProcessedFile::CONTEXT_IMAGEPREVIEW, [
3612
                                        'width' => $sizeParts[0],
3613
                                        'height' => $sizeParts[1]
3614
                                    ])->getPublicUrl(true);
3615
                                }
3616
                            }
3617
                        } catch (ResourceDoesNotExistException $exception) {
3618
                            $icon = $notFoundThumb;
3619
                        }
3620
                    } else {
3621
                        $icon = $notFoundThumb;
3622
                    }
3623
                    $urlPrefix = '';
3624
                    if (parse_url($icon, PHP_URL_HOST) === null) {
3625
                        $urlPrefix = $tsfe->absRefPrefix;
3626
                    }
3627
                    $icon = '<img src="' . htmlspecialchars($urlPrefix . $icon) . '"' .
3628
                        ' width="' . (int)$sizeParts[0] . '" height="' . (int)$sizeParts[1] . '" ' .
3629
                        $this->getBorderAttr(' border="0"') . '' . $this->getAltParam($conf) . ' />';
3630
                }
3631
            } else {
3632
                $conf['icon.']['widthAttribute'] = isset($conf['icon.']['widthAttribute.'])
3633
                    ? $this->stdWrap($conf['icon.']['widthAttribute'], $conf['icon.']['widthAttribute.'])
3634
                    : $conf['icon.']['widthAttribute'];
3635
                $iconWidth = !empty($conf['icon.']['widthAttribute']) ? $conf['icon.']['widthAttribute'] : 18;
3636
                $conf['icon.']['heightAttribute'] = isset($conf['icon.']['heightAttribute.'])
3637
                    ? $this->stdWrap($conf['icon.']['heightAttribute'], $conf['icon.']['heightAttribute.'])
3638
                    : $conf['icon.']['heightAttribute'];
3639
                $iconHeight = !empty($conf['icon.']['heightAttribute']) ? (int)$conf['icon.']['heightAttribute'] : 16;
3640
                $icon = '<img src="' . htmlspecialchars($tsfe->absRefPrefix . $icon) . '" width="' . (int)$iconWidth . '" height="' . (int)$iconHeight . '"'
3641
                    . $this->getBorderAttr(' border="0"') . $this->getAltParam($conf) . ' />';
3642
            }
3643
            if ($conf['icon_link'] && !$conf['combinedLink']) {
3644
                $icon = $this->wrap($icon, $theLinkWrap);
3645
            }
3646
            $icon = isset($conf['icon.']) ? $this->stdWrap($icon, $conf['icon.']) : $icon;
3647
        }
3648
        $size = '';
3649
        if ($conf['size']) {
3650
            $size = isset($conf['size.']) ? $this->stdWrap($theSize, $conf['size.']) : $theSize;
3651
        }
3652
        // Wrapping file label
3653
        if ($conf['removePrependedNumbers']) {
3654
            $theValue = preg_replace('/_[0-9][0-9](\\.[[:alnum:]]*)$/', '\\1', $theValue);
3655
        }
3656
        if (isset($conf['labelStdWrap.'])) {
3657
            $theValue = $this->stdWrap($theValue, $conf['labelStdWrap.']);
3658
        }
3659
        // Wrapping file
3660
        $wrap = isset($conf['wrap.']) ? $this->stdWrap($conf['wrap'], $conf['wrap.']) : $conf['wrap'];
3661
        if ($conf['combinedLink']) {
3662
            $theValue = $icon . $theValue;
3663 View Code Duplication
            if ($conf['ATagBeforeWrap']) {
3664
                $theValue = $this->wrap($this->wrap($theValue, $wrap), $theLinkWrap);
3665
            } else {
3666
                $theValue = $this->wrap($this->wrap($theValue, $theLinkWrap), $wrap);
3667
            }
3668
            $file = isset($conf['file.']) ? $this->stdWrap($theValue, $conf['file.']) : $theValue;
3669
            // output
3670
            $output = $file . $size;
3671
        } else {
3672 View Code Duplication
            if ($conf['ATagBeforeWrap']) {
3673
                $theValue = $this->wrap($this->wrap($theValue, $wrap), $theLinkWrap);
3674
            } else {
3675
                $theValue = $this->wrap($this->wrap($theValue, $theLinkWrap), $wrap);
3676
            }
3677
            $file = isset($conf['file.']) ? $this->stdWrap($theValue, $conf['file.']) : $theValue;
3678
            // output
3679
            $output = $icon . $file . $size;
3680
        }
3681
        if (isset($conf['stdWrap.'])) {
3682
            $output = $this->stdWrap($output, $conf['stdWrap.']);
3683
        }
3684
        return $output;
3685
    }
3686
3687
    /**
3688
     * Performs basic mathematical evaluation of the input string. Does NOT take parathesis and operator precedence into account! (for that, see \TYPO3\CMS\Core\Utility\MathUtility::calculateWithPriorityToAdditionAndSubtraction())
3689
     *
3690
     * @param string $val The string to evaluate. Example: "3+4*10/5" will generate "35". Only integer numbers can be used.
3691
     * @return int The result (might be a float if you did a division of the numbers).
3692
     * @see \TYPO3\CMS\Core\Utility\MathUtility::calculateWithPriorityToAdditionAndSubtraction()
3693
     */
3694
    public function calc($val)
3695
    {
3696
        $parts = GeneralUtility::splitCalc($val, '+-*/');
3697
        $value = 0;
3698
        foreach ($parts as $part) {
3699
            $theVal = $part[1];
3700
            $sign = $part[0];
3701
            if ((string)(int)$theVal === (string)$theVal) {
3702
                $theVal = (int)$theVal;
3703
            } else {
3704
                $theVal = 0;
3705
            }
3706
            if ($sign === '-') {
3707
                $value -= $theVal;
3708
            }
3709
            if ($sign === '+') {
3710
                $value += $theVal;
3711
            }
3712
            if ($sign === '/') {
3713
                if ((int)$theVal) {
3714
                    $value /= (int)$theVal;
3715
                }
3716
            }
3717
            if ($sign === '*') {
3718
                $value *= $theVal;
3719
            }
3720
        }
3721
        return $value;
3722
    }
3723
3724
    /**
3725
     * This explodes a comma-list into an array where the values are parsed through ContentObjectRender::calc() and cast to (int)(so you are sure to have integers in the output array)
3726
     * Used to split and calculate min and max values for GMENUs.
3727
     *
3728
     * @param string $delim Delimited to explode by
3729
     * @param string $string The string with parts in (where each part is evaluated by ->calc())
3730
     * @return array And array with evaluated values.
3731
     * @see calc(), \TYPO3\CMS\Frontend\ContentObject\Menu\GraphicalMenuContentObject::makeGifs()
3732
     */
3733
    public function calcIntExplode($delim, $string)
3734
    {
3735
        $temp = explode($delim, $string);
3736
        foreach ($temp as $key => $val) {
3737
            $temp[$key] = (int)$this->calc($val);
3738
        }
3739
        return $temp;
3740
    }
3741
3742
    /**
3743
     * Implements the "split" property of stdWrap; Splits a string based on a token (given in TypoScript properties), sets the "current" value to each part and then renders a content object pointer to by a number.
3744
     * In classic TypoScript (like 'content (default)'/'styles.content (default)') this is used to render tables, splitting rows and cells by tokens and putting them together again wrapped in <td> tags etc.
3745
     * Implements the "optionSplit" processing of the TypoScript options for each splitted value to parse.
3746
     *
3747
     * @param string $value The string value to explode by $conf[token] and process each part
3748
     * @param array $conf TypoScript properties for "split
3749
     * @return string Compiled result
3750
     * @access private
3751
     * @see stdWrap(), \TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject::procesItemStates()
3752
     */
3753
    public function splitObj($value, $conf)
3754
    {
3755
        $conf['token'] = isset($conf['token.']) ? $this->stdWrap($conf['token'], $conf['token.']) : $conf['token'];
3756
        if ($conf['token'] === '') {
3757
            return $value;
3758
        }
3759
        $valArr = explode($conf['token'], $value);
3760
3761
        // return value directly by returnKey. No further processing
3762
        if (!empty($valArr) && (MathUtility::canBeInterpretedAsInteger($conf['returnKey']) || $conf['returnKey.'])) {
3763
            $key = isset($conf['returnKey.']) ? (int)$this->stdWrap($conf['returnKey'], $conf['returnKey.']) : (int)$conf['returnKey'];
0 ignored issues
show
Bug introduced by
It seems like $conf['returnKey.'] can also be of type string; however, parameter $conf of TYPO3\CMS\Frontend\Conte...jectRenderer::stdWrap() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

3763
            $key = isset($conf['returnKey.']) ? (int)$this->stdWrap($conf['returnKey'], /** @scrutinizer ignore-type */ $conf['returnKey.']) : (int)$conf['returnKey'];
Loading history...
3764
            return isset($valArr[$key]) ? $valArr[$key] : '';
3765
        }
3766
3767
        // return the amount of elements. No further processing
3768
        if (!empty($valArr) && ($conf['returnCount'] || $conf['returnCount.'])) {
3769
            $returnCount = isset($conf['returnCount.']) ? (bool)$this->stdWrap($conf['returnCount'], $conf['returnCount.']) : (bool)$conf['returnCount'];
3770
            return $returnCount ? count($valArr) : 0;
3771
        }
3772
3773
        // calculate splitCount
3774
        $splitCount = count($valArr);
3775
        $max = isset($conf['max.']) ? (int)$this->stdWrap($conf['max'], $conf['max.']) : (int)$conf['max'];
3776
        if ($max && $splitCount > $max) {
3777
            $splitCount = $max;
3778
        }
3779
        $min = isset($conf['min.']) ? (int)$this->stdWrap($conf['min'], $conf['min.']) : (int)$conf['min'];
3780
        if ($min && $splitCount < $min) {
3781
            $splitCount = $min;
3782
        }
3783
        $wrap = isset($conf['wrap.']) ? (string)$this->stdWrap($conf['wrap'], $conf['wrap.']) : (string)$conf['wrap'];
3784
        $cObjNumSplitConf = isset($conf['cObjNum.']) ? (string)$this->stdWrap($conf['cObjNum'], $conf['cObjNum.']) : (string)$conf['cObjNum'];
3785
        $splitArr = [];
3786
        if ($wrap !== '' || $cObjNumSplitConf !== '') {
3787
            $splitArr['wrap'] = $wrap;
3788
            $splitArr['cObjNum'] = $cObjNumSplitConf;
3789
            $splitArr = GeneralUtility::makeInstance(TypoScriptService::class)
3790
                ->explodeConfigurationForOptionSplit($splitArr, $splitCount);
3791
        }
3792
        $content = '';
3793
        for ($a = 0; $a < $splitCount; $a++) {
3794
            $this->getTypoScriptFrontendController()->register['SPLIT_COUNT'] = $a;
3795
            $value = '' . $valArr[$a];
3796
            $this->data[$this->currentValKey] = $value;
3797
            if ($splitArr[$a]['cObjNum']) {
3798
                $objName = (int)$splitArr[$a]['cObjNum'];
3799
                $value = isset($conf[$objName . '.'])
3800
                    ? $this->stdWrap($this->cObjGet($conf[$objName . '.'], $objName . '.'), $conf[$objName . '.'])
3801
                    : $this->cObjGet($conf[$objName . '.'], $objName . '.');
3802
            }
3803
            $wrap = isset($splitArr[$a]['wrap.']) ? $this->stdWrap($splitArr[$a]['wrap'], $splitArr[$a]['wrap.']) : $splitArr[$a]['wrap'];
3804
            if ($wrap) {
3805
                $value = $this->wrap($value, $wrap);
3806
            }
3807
            $content .= $value;
3808
        }
3809
        return $content;
3810
    }
3811
3812
    /**
3813
     * Processes ordered replacements on content data.
3814
     *
3815
     * @param string $content The content to be processed
3816
     * @param array $configuration The TypoScript configuration for stdWrap.replacement
3817
     * @return string The processed content data
3818
     */
3819
    protected function replacement($content, array $configuration)
3820
    {
3821
        // Sorts actions in configuration by numeric index
3822
        ksort($configuration, SORT_NUMERIC);
3823
        foreach ($configuration as $index => $action) {
3824
            // Checks whether we have an valid action and a numeric key ending with a dot ("10.")
3825
            if (is_array($action) && substr($index, -1) === '.' && MathUtility::canBeInterpretedAsInteger(substr($index, 0, -1))) {
3826
                $content = $this->replacementSingle($content, $action);
3827
            }
3828
        }
3829
        return $content;
3830
    }
3831
3832
    /**
3833
     * Processes a single search/replace on content data.
3834
     *
3835
     * @param string $content The content to be processed
3836
     * @param array $configuration The TypoScript of the search/replace action to be processed
3837
     * @return string The processed content data
3838
     */
3839
    protected function replacementSingle($content, array $configuration)
3840
    {
3841
        if ((isset($configuration['search']) || isset($configuration['search.'])) && (isset($configuration['replace']) || isset($configuration['replace.']))) {
3842
            // Gets the strings
3843
            $search = isset($configuration['search.']) ? $this->stdWrap($configuration['search'], $configuration['search.']) : $configuration['search'];
3844
            $replace = isset($configuration['replace.']) ? $this->stdWrap($configuration['replace'], $configuration['replace.']) : $configuration['replace'];
3845
            // Determines whether regular expression shall be used
3846
            if (isset($configuration['useRegExp']) || $configuration['useRegExp.']) {
3847
                $useRegularExpression = isset($configuration['useRegExp.']) ? $this->stdWrap($configuration['useRegExp'], $configuration['useRegExp.']) : $configuration['useRegExp'];
3848
            }
3849
            // Determines whether replace-pattern uses option-split
3850
            if (isset($configuration['useOptionSplitReplace']) || isset($configuration['useOptionSplitReplace.'])) {
3851
                $useOptionSplitReplace = isset($configuration['useOptionSplitReplace.']) ? $this->stdWrap($configuration['useOptionSplitReplace'], $configuration['useOptionSplitReplace.']) : $configuration['useOptionSplitReplace'];
3852
            }
3853
3854
            // Performs a replacement by preg_replace()
3855
            if (isset($useRegularExpression)) {
3856
                // Get separator-character which precedes the string and separates search-string from the modifiers
3857
                $separator = $search[0];
3858
                $startModifiers = strrpos($search, $separator);
3859
                if ($separator !== false && $startModifiers > 0) {
3860
                    $modifiers = substr($search, $startModifiers + 1);
3861
                    // remove "e" (eval-modifier), which would otherwise allow to run arbitrary PHP-code
3862
                    $modifiers = str_replace('e', '', $modifiers);
3863
                    $search = substr($search, 0, ($startModifiers + 1)) . $modifiers;
0 ignored issues
show
Bug introduced by
Are you sure substr($search, 0, $startModifiers + 1) of type false|string can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

3863
                    $search = /** @scrutinizer ignore-type */ substr($search, 0, ($startModifiers + 1)) . $modifiers;
Loading history...
3864
                }
3865
                if (empty($useOptionSplitReplace)) {
3866
                    $content = preg_replace($search, $replace, $content);
3867
                } else {
3868
                    // init for replacement
3869
                    $splitCount = preg_match_all($search, $content, $matches);
3870
                    $typoScriptService = GeneralUtility::makeInstance(TypoScriptService::class);
3871
                    $replaceArray = $typoScriptService->explodeConfigurationForOptionSplit([$replace], $splitCount);
3872
                    $replaceCount = 0;
3873
3874
                    $replaceCallback = function ($match) use ($replaceArray, $search, &$replaceCount) {
3875
                        $replaceCount++;
3876
                        return preg_replace($search, $replaceArray[$replaceCount - 1][0], $match[0]);
3877
                    };
3878
                    $content = preg_replace_callback($search, $replaceCallback, $content);
3879
                }
3880
            } else {
3881
                if (empty($useOptionSplitReplace)) {
3882
                    $content = str_replace($search, $replace, $content);
3883
                } else {
3884
                    // turn search-string into a preg-pattern
3885
                    $searchPreg = '#' . preg_quote($search, '#') . '#';
3886
3887
                    // init for replacement
3888
                    $splitCount = preg_match_all($searchPreg, $content, $matches);
3889
                    $typoScriptService = GeneralUtility::makeInstance(TypoScriptService::class);
3890
                    $replaceArray = $typoScriptService->explodeConfigurationForOptionSplit([$replace], $splitCount);
3891
                    $replaceCount = 0;
3892
3893
                    $replaceCallback = function () use ($replaceArray, $search, &$replaceCount) {
0 ignored issues
show
Unused Code introduced by
The import $search is not used and could be removed.

This check looks for imports that have been defined, but are not used in the scope.

Loading history...
3894
                        $replaceCount++;
3895
                        return $replaceArray[$replaceCount - 1][0];
3896
                    };
3897
                    $content = preg_replace_callback($searchPreg, $replaceCallback, $content);
3898
                }
3899
            }
3900
        }
3901
        return $content;
3902
    }
3903
3904
    /**
3905
     * Implements the "round" property of stdWrap
3906
     * This is a Wrapper function for PHP's rounding functions (round,ceil,floor), defaults to round()
3907
     *
3908
     * @param string $content Value to process
3909
     * @param array $conf TypoScript configuration for round
3910
     * @return string The formatted number
3911
     */
3912
    protected function round($content, array $conf = [])
3913
    {
3914
        $decimals = isset($conf['decimals.']) ? $this->stdWrap($conf['decimals'], $conf['decimals.']) : $conf['decimals'];
3915
        $type = isset($conf['roundType.']) ? $this->stdWrap($conf['roundType'], $conf['roundType.']) : $conf['roundType'];
3916
        $floatVal = (float)$content;
3917
        switch ($type) {
3918
            case 'ceil':
3919
                $content = ceil($floatVal);
3920
                break;
3921
            case 'floor':
3922
                $content = floor($floatVal);
3923
                break;
3924
            case 'round':
3925
3926
            default:
3927
                $content = round($floatVal, (int)$decimals);
3928
        }
3929
        return $content;
3930
    }
3931
3932
    /**
3933
     * Implements the stdWrap property "numberFormat"
3934
     * This is a Wrapper function for php's number_format()
3935
     *
3936
     * @param float $content Value to process
3937
     * @param array $conf TypoScript Configuration for numberFormat
3938
     * @return string The formatted number
3939
     */
3940
    public function numberFormat($content, $conf)
3941
    {
3942
        $decimals = isset($conf['decimals.']) ? (int)$this->stdWrap($conf['decimals'], $conf['decimals.']) : (int)$conf['decimals'];
3943
        $dec_point = isset($conf['dec_point.']) ? $this->stdWrap($conf['dec_point'], $conf['dec_point.']) : $conf['dec_point'];
3944
        $thousands_sep = isset($conf['thousands_sep.']) ? $this->stdWrap($conf['thousands_sep'], $conf['thousands_sep.']) : $conf['thousands_sep'];
3945
        return number_format((float)$content, $decimals, $dec_point, $thousands_sep);
3946
    }
3947
3948
    /**
3949
     * Implements the stdWrap property, "parseFunc".
3950
     * This is a function with a lot of interesting uses. In classic TypoScript this is used to process text
3951
     * from the bodytext field; This included highlighting of search words, changing http:// and mailto: prefixed strings into etc.
3952
     * It is still a very important function for processing of bodytext which is normally stored in the database
3953
     * in a format which is not fully ready to be outputted.
3954
     * This situation has not become better by having a RTE around...
3955
     *
3956
     * This function is actually just splitting the input content according to the configuration of "external blocks".
3957
     * This means that before the input string is actually "parsed" it will be splitted into the parts configured to BE parsed
3958
     * (while other parts/blocks should NOT be parsed).
3959
     * Therefore the actual processing of the parseFunc properties goes on in ->_parseFunc()
3960
     *
3961
     * @param string $theValue The value to process.
3962
     * @param array $conf TypoScript configuration for parseFunc
3963
     * @param string $ref Reference to get configuration from. Eg. "< lib.parseFunc" which means that the configuration of the object path "lib.parseFunc" will be retrieved and MERGED with what is in $conf!
3964
     * @return string The processed value
3965
     * @see _parseFunc()
3966
     */
3967
    public function parseFunc($theValue, $conf, $ref = '')
3968
    {
3969
        // Fetch / merge reference, if any
3970
        if ($ref) {
3971
            $temp_conf = [
3972
                'parseFunc' => $ref,
3973
                'parseFunc.' => $conf
3974
            ];
3975
            $temp_conf = $this->mergeTSRef($temp_conf, 'parseFunc');
3976
            $conf = $temp_conf['parseFunc.'];
3977
        }
3978
        // Process:
3979
        if ((string)$conf['externalBlocks'] === '') {
3980
            return $this->_parseFunc($theValue, $conf);
3981
        }
3982
        $tags = strtolower(implode(',', GeneralUtility::trimExplode(',', $conf['externalBlocks'])));
3983
        $htmlParser = GeneralUtility::makeInstance(HtmlParser::class);
3984
        $parts = $htmlParser->splitIntoBlock($tags, $theValue);
3985
        foreach ($parts as $k => $v) {
3986
            if ($k % 2) {
3987
                // font:
3988
                $tagName = strtolower($htmlParser->getFirstTagName($v));
3989
                $cfg = $conf['externalBlocks.'][$tagName . '.'];
3990 View Code Duplication
                if ($cfg['stripNLprev'] || $cfg['stripNL']) {
3991
                    $parts[$k - 1] = preg_replace('/' . CR . '?' . LF . '[ ]*$/', '', $parts[$k - 1]);
3992
                }
3993 View Code Duplication
                if ($cfg['stripNLnext'] || $cfg['stripNL']) {
3994
                    $parts[$k + 1] = preg_replace('/^[ ]*' . CR . '?' . LF . '/', '', $parts[$k + 1]);
3995
                }
3996
            }
3997
        }
3998
        foreach ($parts as $k => $v) {
3999
            if ($k % 2) {
4000
                $tag = $htmlParser->getFirstTag($v);
4001
                $tagName = strtolower($htmlParser->getFirstTagName($v));
4002
                $cfg = $conf['externalBlocks.'][$tagName . '.'];
4003
                if ($cfg['callRecursive']) {
4004
                    $parts[$k] = $this->parseFunc($htmlParser->removeFirstAndLastTag($v), $conf);
4005
                    if (!$cfg['callRecursive.']['dontWrapSelf']) {
4006
                        if ($cfg['callRecursive.']['alternativeWrap']) {
4007
                            $parts[$k] = $this->wrap($parts[$k], $cfg['callRecursive.']['alternativeWrap']);
4008
                        } else {
4009
                            if (is_array($cfg['callRecursive.']['tagStdWrap.'])) {
4010
                                $tag = $this->stdWrap($tag, $cfg['callRecursive.']['tagStdWrap.']);
4011
                            }
4012
                            $parts[$k] = $tag . $parts[$k] . '</' . $tagName . '>';
4013
                        }
4014
                    }
4015
                } elseif ($cfg['HTMLtableCells']) {
4016
                    $rowParts = $htmlParser->splitIntoBlock('tr', $parts[$k]);
4017
                    foreach ($rowParts as $kk => $vv) {
4018
                        if ($kk % 2) {
4019
                            $colParts = $htmlParser->splitIntoBlock('td,th', $vv);
4020
                            $cc = 0;
4021
                            foreach ($colParts as $kkk => $vvv) {
4022
                                if ($kkk % 2) {
4023
                                    $cc++;
4024
                                    $tag = $htmlParser->getFirstTag($vvv);
4025
                                    $tagName = strtolower($htmlParser->getFirstTagName($vvv));
4026
                                    $colParts[$kkk] = $htmlParser->removeFirstAndLastTag($vvv);
4027
                                    if ($cfg['HTMLtableCells.'][$cc . '.']['callRecursive'] || !isset($cfg['HTMLtableCells.'][$cc . '.']['callRecursive']) && $cfg['HTMLtableCells.']['default.']['callRecursive']) {
4028
                                        if ($cfg['HTMLtableCells.']['addChr10BetweenParagraphs']) {
4029
                                            $colParts[$kkk] = str_replace('</p><p>', '</p>' . LF . '<p>', $colParts[$kkk]);
4030
                                        }
4031
                                        $colParts[$kkk] = $this->parseFunc($colParts[$kkk], $conf);
4032
                                    }
4033
                                    $tagStdWrap = is_array($cfg['HTMLtableCells.'][$cc . '.']['tagStdWrap.'])
4034
                                        ? $cfg['HTMLtableCells.'][$cc . '.']['tagStdWrap.']
4035
                                        : $cfg['HTMLtableCells.']['default.']['tagStdWrap.'];
4036
                                    if (is_array($tagStdWrap)) {
4037
                                        $tag = $this->stdWrap($tag, $tagStdWrap);
4038
                                    }
4039
                                    $stdWrap = is_array($cfg['HTMLtableCells.'][$cc . '.']['stdWrap.'])
4040
                                        ? $cfg['HTMLtableCells.'][$cc . '.']['stdWrap.']
4041
                                        : $cfg['HTMLtableCells.']['default.']['stdWrap.'];
4042
                                    if (is_array($stdWrap)) {
4043
                                        $colParts[$kkk] = $this->stdWrap($colParts[$kkk], $stdWrap);
4044
                                    }
4045
                                    $colParts[$kkk] = $tag . $colParts[$kkk] . '</' . $tagName . '>';
4046
                                }
4047
                            }
4048
                            $rowParts[$kk] = implode('', $colParts);
4049
                        }
4050
                    }
4051
                    $parts[$k] = implode('', $rowParts);
4052
                }
4053
                if (is_array($cfg['stdWrap.'])) {
4054
                    $parts[$k] = $this->stdWrap($parts[$k], $cfg['stdWrap.']);
4055
                }
4056
            } else {
4057
                $parts[$k] = $this->_parseFunc($parts[$k], $conf);
4058
            }
4059
        }
4060
        return implode('', $parts);
4061
    }
4062
4063
    /**
4064
     * Helper function for parseFunc()
4065
     *
4066
     * @param string $theValue The value to process.
4067
     * @param array $conf TypoScript configuration for parseFunc
4068
     * @return string The processed value
4069
     * @access private
4070
     * @see parseFunc()
4071
     */
4072
    public function _parseFunc($theValue, $conf)
4073
    {
4074
        if (!empty($conf['if.']) && !$this->checkIf($conf['if.'])) {
4075
            return $theValue;
4076
        }
4077
        // Indicates that the data is from within a tag.
4078
        $inside = false;
4079
        // Pointer to the total string position
4080
        $pointer = 0;
4081
        // Loaded with the current typo-tag if any.
4082
        $currentTag = '';
4083
        $stripNL = 0;
4084
        $contentAccum = [];
4085
        $contentAccumP = 0;
4086
        $allowTags = strtolower(str_replace(' ', '', $conf['allowTags']));
4087
        $denyTags = strtolower(str_replace(' ', '', $conf['denyTags']));
4088
        $totalLen = strlen($theValue);
4089
        do {
4090
            if (!$inside) {
4091
                if (!is_array($currentTag)) {
4092
                    // These operations should only be performed on code outside the typotags...
4093
                    // data: this checks that we enter tags ONLY if the first char in the tag is alphanumeric OR '/'
4094
                    $len_p = 0;
4095
                    $c = 100;
4096
                    do {
4097
                        $len = strcspn(substr($theValue, $pointer + $len_p), '<');
0 ignored issues
show
Bug introduced by
It seems like substr($theValue, $pointer + $len_p) can also be of type false; however, parameter $str1 of strcspn() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

4097
                        $len = strcspn(/** @scrutinizer ignore-type */ substr($theValue, $pointer + $len_p), '<');
Loading history...
4098
                        $len_p += $len + 1;
4099
                        $endChar = ord(strtolower(substr($theValue, $pointer + $len_p, 1)));
0 ignored issues
show
Bug introduced by
It seems like substr($theValue, $pointer + $len_p, 1) can also be of type false; however, parameter $str of strtolower() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

4099
                        $endChar = ord(strtolower(/** @scrutinizer ignore-type */ substr($theValue, $pointer + $len_p, 1)));
Loading history...
4100
                        $c--;
4101
                    } while ($c > 0 && $endChar && ($endChar < 97 || $endChar > 122) && $endChar != 47);
4102
                    $len = $len_p - 1;
4103
                } else {
4104
                    // If we're inside a currentTag, just take it to the end of that tag!
4105
                    $tempContent = strtolower(substr($theValue, $pointer));
4106
                    $len = strpos($tempContent, '</' . $currentTag[0]);
4107
                    if (is_string($len) && !$len) {
4108
                        $len = strlen($tempContent);
4109
                    }
4110
                }
4111
                // $data is the content until the next <tag-start or end is detected.
4112
                // In case of a currentTag set, this would mean all data between the start- and end-tags
4113
                $data = substr($theValue, $pointer, $len);
0 ignored issues
show
Bug introduced by
It seems like $len can also be of type false; however, parameter $length of substr() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

4113
                $data = substr($theValue, $pointer, /** @scrutinizer ignore-type */ $len);
Loading history...
4114
                if ($data != '') {
4115
                    if ($stripNL) {
4116
                        // If the previous tag was set to strip NewLines in the beginning of the next data-chunk.
4117
                        $data = preg_replace('/^[ ]*' . CR . '?' . LF . '/', '', $data);
4118
                    }
4119
                    // These operations should only be performed on code outside the tags...
4120
                    if (!is_array($currentTag)) {
4121
                        // Constants
4122
                        $tsfe = $this->getTypoScriptFrontendController();
4123
                        $tmpConstants = $tsfe->tmpl->setup['constants.'];
4124
                        if ($conf['constants'] && is_array($tmpConstants)) {
4125
                            foreach ($tmpConstants as $key => $val) {
4126
                                if (is_string($val)) {
4127
                                    $data = str_replace('###' . $key . '###', $val, $data);
4128
                                }
4129
                            }
4130
                        }
4131
                        // Short
4132
                        if (is_array($conf['short.'])) {
4133
                            $shortWords = $conf['short.'];
4134
                            krsort($shortWords);
4135
                            foreach ($shortWords as $key => $val) {
4136
                                if (is_string($val)) {
4137
                                    $data = str_replace($key, $val, $data);
4138
                                }
4139
                            }
4140
                        }
4141
                        // stdWrap
4142
                        if (is_array($conf['plainTextStdWrap.'])) {
4143
                            $data = $this->stdWrap($data, $conf['plainTextStdWrap.']);
0 ignored issues
show
Bug introduced by
It seems like $data can also be of type false; however, parameter $content of TYPO3\CMS\Frontend\Conte...jectRenderer::stdWrap() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

4143
                            $data = $this->stdWrap(/** @scrutinizer ignore-type */ $data, $conf['plainTextStdWrap.']);
Loading history...
4144
                        }
4145
                        // userFunc
4146
                        if ($conf['userFunc']) {
4147
                            $data = $this->callUserFunction($conf['userFunc'], $conf['userFunc.'], $data);
0 ignored issues
show
Bug introduced by
It seems like $data can also be of type false; however, parameter $content of TYPO3\CMS\Frontend\Conte...rer::callUserFunction() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

4147
                            $data = $this->callUserFunction($conf['userFunc'], $conf['userFunc.'], /** @scrutinizer ignore-type */ $data);
Loading history...
4148
                        }
4149
                        // Makelinks: (Before search-words as we need the links to be generated when searchwords go on...!)
4150
                        if ($conf['makelinks']) {
4151
                            $data = $this->http_makelinks($data, $conf['makelinks.']['http.']);
0 ignored issues
show
Bug introduced by
It seems like $data can also be of type false; however, parameter $data of TYPO3\CMS\Frontend\Conte...derer::http_makelinks() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

4151
                            $data = $this->http_makelinks(/** @scrutinizer ignore-type */ $data, $conf['makelinks.']['http.']);
Loading history...
4152
                            $data = $this->mailto_makelinks($data, $conf['makelinks.']['mailto.']);
4153
                        }
4154
                        // Search Words:
4155
                        if ($tsfe->no_cache && $conf['sword'] && is_array($tsfe->sWordList) && $tsfe->sWordRegEx) {
4156
                            $newstring = '';
4157
                            do {
4158
                                $pregSplitMode = 'i';
4159
                                if (isset($tsfe->config['config']['sword_noMixedCase']) && !empty($tsfe->config['config']['sword_noMixedCase'])) {
4160
                                    $pregSplitMode = '';
4161
                                }
4162
                                $pieces = preg_split('/' . $tsfe->sWordRegEx . '/' . $pregSplitMode, $data, 2);
0 ignored issues
show
Bug introduced by
It seems like $data can also be of type false; however, parameter $subject of preg_split() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

4162
                                $pieces = preg_split('/' . $tsfe->sWordRegEx . '/' . $pregSplitMode, /** @scrutinizer ignore-type */ $data, 2);
Loading history...
4163
                                $newstring .= $pieces[0];
4164
                                $match_len = strlen($data) - (strlen($pieces[0]) + strlen($pieces[1]));
0 ignored issues
show
Bug introduced by
It seems like $data can also be of type false; however, parameter $string of strlen() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

4164
                                $match_len = strlen(/** @scrutinizer ignore-type */ $data) - (strlen($pieces[0]) + strlen($pieces[1]));
Loading history...
4165
                                $inTag = false;
4166
                                if (strstr($pieces[0], '<') || strstr($pieces[0], '>')) {
4167
                                    // Returns TRUE, if a '<' is closer to the string-end than '>'.
4168
                                    // This is the case if we're INSIDE a tag (that could have been
4169
                                    // made by makelinks...) and we must secure, that the inside of a tag is
4170
                                    // not marked up.
4171
                                    $inTag = strrpos($pieces[0], '<') > strrpos($pieces[0], '>');
4172
                                }
4173
                                // The searchword:
4174
                                $match = substr($data, strlen($pieces[0]), $match_len);
0 ignored issues
show
Bug introduced by
It seems like $data can also be of type false; however, parameter $string of substr() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

4174
                                $match = substr(/** @scrutinizer ignore-type */ $data, strlen($pieces[0]), $match_len);
Loading history...
4175
                                if (trim($match) && strlen($match) > 1 && !$inTag) {
0 ignored issues
show
Bug introduced by
It seems like $match can also be of type false; however, parameter $str of trim() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

4175
                                if (trim(/** @scrutinizer ignore-type */ $match) && strlen($match) > 1 && !$inTag) {
Loading history...
4176
                                    $match = $this->wrap($match, $conf['sword']);
0 ignored issues
show
Bug introduced by
It seems like $match can also be of type false; however, parameter $content of TYPO3\CMS\Frontend\Conte...tObjectRenderer::wrap() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

4176
                                    $match = $this->wrap(/** @scrutinizer ignore-type */ $match, $conf['sword']);
Loading history...
4177
                                }
4178
                                // Concatenate the Search Word again.
4179
                                $newstring .= $match;
4180
                                $data = $pieces[1];
4181
                            } while ($pieces[1]);
4182
                            $data = $newstring;
4183
                        }
4184
                    }
4185
                    $contentAccum[$contentAccumP] .= $data;
4186
                }
4187
                $inside = true;
4188
            } else {
4189
                // tags
4190
                $len = strcspn(substr($theValue, $pointer), '>') + 1;
4191
                $data = substr($theValue, $pointer, $len);
4192
                if (StringUtility::endsWith($data, '/>') && strpos($data, '<link ') !== 0) {
0 ignored issues
show
Bug introduced by
It seems like $data can also be of type false; however, parameter $haystack of strpos() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

4192
                if (StringUtility::endsWith($data, '/>') && strpos(/** @scrutinizer ignore-type */ $data, '<link ') !== 0) {
Loading history...
Bug introduced by
It seems like $data can also be of type false; however, parameter $haystack of TYPO3\CMS\Core\Utility\StringUtility::endsWith() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

4192
                if (StringUtility::endsWith(/** @scrutinizer ignore-type */ $data, '/>') && strpos($data, '<link ') !== 0) {
Loading history...
4193
                    $tagContent = substr($data, 1, -2);
4194
                } else {
4195
                    $tagContent = substr($data, 1, -1);
4196
                }
4197
                $tag = explode(' ', trim($tagContent), 2);
4198
                $tag[0] = strtolower($tag[0]);
4199
                if ($tag[0][0] === '/') {
4200
                    $tag[0] = substr($tag[0], 1);
4201
                    $tag['out'] = 1;
4202
                }
4203
                if ($conf['tags.'][$tag[0]]) {
4204
                    $treated = false;
4205
                    $stripNL = false;
4206
                    // in-tag
4207
                    if (!$currentTag && !$tag['out']) {
4208
                        // $currentTag (array!) is the tag we are currently processing
4209
                        $currentTag = $tag;
4210
                        $contentAccumP++;
4211
                        $treated = true;
4212
                        // in-out-tag: img and other empty tags
4213
                        if (preg_match('/^(area|base|br|col|hr|img|input|meta|param)$/i', $tag[0])) {
4214
                            $tag['out'] = 1;
4215
                        }
4216
                    }
4217
                    // out-tag
4218
                    if ($currentTag[0] === $tag[0] && $tag['out']) {
4219
                        $theName = $conf['tags.'][$tag[0]];
4220
                        $theConf = $conf['tags.'][$tag[0] . '.'];
4221
                        // This flag indicates, that NL- (13-10-chars) should be stripped first and last.
4222
                        $stripNL = (bool)$theConf['stripNL'];
4223
                        // This flag indicates, that this TypoTag section should NOT be included in the nonTypoTag content.
4224
                        $breakOut = (bool)$theConf['breakoutTypoTagContent'];
4225
                        $this->parameters = [];
4226
                        if ($currentTag[1]) {
4227
                            $params = GeneralUtility::get_tag_attributes($currentTag[1]);
4228
                            if (is_array($params)) {
4229
                                foreach ($params as $option => $val) {
4230
                                    $this->parameters[strtolower($option)] = $val;
4231
                                }
4232
                            }
4233
                        }
4234
                        $this->parameters['allParams'] = trim($currentTag[1]);
4235
                        // Removes NL in the beginning and end of the tag-content AND at the end of the currentTagBuffer.
4236
                        // $stripNL depends on the configuration of the current tag
4237
                        if ($stripNL) {
4238
                            $contentAccum[$contentAccumP - 1] = preg_replace('/' . CR . '?' . LF . '[ ]*$/', '', $contentAccum[$contentAccumP - 1]);
4239
                            $contentAccum[$contentAccumP] = preg_replace('/^[ ]*' . CR . '?' . LF . '/', '', $contentAccum[$contentAccumP]);
4240
                            $contentAccum[$contentAccumP] = preg_replace('/' . CR . '?' . LF . '[ ]*$/', '', $contentAccum[$contentAccumP]);
4241
                        }
4242
                        $this->data[$this->currentValKey] = $contentAccum[$contentAccumP];
4243
                        $newInput = $this->cObjGetSingle($theName, $theConf, '/parseFunc/.tags.' . $tag[0]);
4244
                        // fetch the content object
4245
                        $contentAccum[$contentAccumP] = $newInput;
4246
                        $contentAccumP++;
4247
                        // If the TypoTag section
4248
                        if (!$breakOut) {
4249
                            $contentAccum[$contentAccumP - 2] .= $contentAccum[$contentAccumP - 1] . $contentAccum[$contentAccumP];
4250
                            unset($contentAccum[$contentAccumP]);
4251
                            unset($contentAccum[$contentAccumP - 1]);
4252
                            $contentAccumP -= 2;
4253
                        }
4254
                        unset($currentTag);
4255
                        $treated = true;
4256
                    }
4257
                    // other tags
4258
                    if (!$treated) {
4259
                        $contentAccum[$contentAccumP] .= $data;
4260
                    }
4261
                } else {
4262
                    // If a tag was not a typo tag, then it is just added to the content
4263
                    $stripNL = false;
4264
                    if (GeneralUtility::inList($allowTags, $tag[0]) || $denyTags !== '*' && !GeneralUtility::inList($denyTags, $tag[0])) {
4265
                        $contentAccum[$contentAccumP] .= $data;
4266
                    } else {
4267
                        $contentAccum[$contentAccumP] .= htmlspecialchars($data);
0 ignored issues
show
Bug introduced by
It seems like $data can also be of type false; however, parameter $string of htmlspecialchars() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

4267
                        $contentAccum[$contentAccumP] .= htmlspecialchars(/** @scrutinizer ignore-type */ $data);
Loading history...
4268
                    }
4269
                }
4270
                $inside = false;
4271
            }
4272
            $pointer += $len;
4273
        } while ($pointer < $totalLen);
4274
        // Parsing nonTypoTag content (all even keys):
4275
        reset($contentAccum);
4276
        $contentAccumCount = count($contentAccum);
4277
        for ($a = 0; $a < $contentAccumCount; $a++) {
4278
            if ($a % 2 != 1) {
4279
                // stdWrap
4280
                if (is_array($conf['nonTypoTagStdWrap.'])) {
4281
                    $contentAccum[$a] = $this->stdWrap($contentAccum[$a], $conf['nonTypoTagStdWrap.']);
4282
                }
4283
                // userFunc
4284
                if ($conf['nonTypoTagUserFunc']) {
4285
                    $contentAccum[$a] = $this->callUserFunction($conf['nonTypoTagUserFunc'], $conf['nonTypoTagUserFunc.'], $contentAccum[$a]);
4286
                }
4287
            }
4288
        }
4289
        return implode('', $contentAccum);
4290
    }
4291
4292
    /**
4293
     * Lets you split the content by LF and process each line independently. Used to format content made with the RTE.
4294
     *
4295
     * @param string $theValue The input value
4296
     * @param array $conf TypoScript options
4297
     * @return string The processed input value being returned; Splitted lines imploded by LF again.
4298
     * @access private
4299
     */
4300
    public function encaps_lineSplit($theValue, $conf)
4301
    {
4302
        if ((string)$theValue === '') {
4303
            return '';
4304
        }
4305
        $lParts = explode(LF, $theValue);
4306
4307
        // When the last element is an empty linebreak we need to remove it, otherwise we will have a duplicate empty line.
4308
        $lastPartIndex = count($lParts) - 1;
4309
        if ($lParts[$lastPartIndex] === '' && trim($lParts[$lastPartIndex - 1], CR) === '') {
4310
            array_pop($lParts);
4311
        }
4312
4313
        $encapTags = GeneralUtility::trimExplode(',', strtolower($conf['encapsTagList']), true);
4314
        $nonWrappedTag = $conf['nonWrappedTag'];
4315
        $defaultAlign = isset($conf['defaultAlign.'])
4316
            ? trim($this->stdWrap($conf['defaultAlign'], $conf['defaultAlign.']))
4317
            : trim($conf['defaultAlign']);
4318
4319
        $str_content = '';
4320
        foreach ($lParts as $k => $l) {
4321
            $sameBeginEnd = 0;
4322
            $emptyTag = false;
4323
            $l = trim($l);
4324
            $attrib = [];
4325
            $nonWrapped = false;
4326
            $tagName = '';
4327
            if ($l[0] === '<' && substr($l, -1) === '>') {
4328
                $fwParts = explode('>', substr($l, 1), 2);
0 ignored issues
show
Bug introduced by
It seems like substr($l, 1) can also be of type false; however, parameter $string of explode() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

4328
                $fwParts = explode('>', /** @scrutinizer ignore-type */ substr($l, 1), 2);
Loading history...
4329
                list($tagName) = explode(' ', $fwParts[0], 2);
4330
                if (!$fwParts[1]) {
4331
                    if (substr($tagName, -1) === '/') {
4332
                        $tagName = substr($tagName, 0, -1);
4333
                    }
4334
                    if (substr($fwParts[0], -1) === '/') {
4335
                        $sameBeginEnd = 1;
4336
                        $emptyTag = true;
4337
                        $attrib = GeneralUtility::get_tag_attributes('<' . substr($fwParts[0], 0, -1) . '>');
0 ignored issues
show
Bug introduced by
Are you sure substr($fwParts[0], 0, -1) of type false|string can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

4337
                        $attrib = GeneralUtility::get_tag_attributes('<' . /** @scrutinizer ignore-type */ substr($fwParts[0], 0, -1) . '>');
Loading history...
4338
                    }
4339
                } else {
4340
                    $backParts = GeneralUtility::revExplode('<', substr($fwParts[1], 0, -1), 2);
0 ignored issues
show
Bug introduced by
It seems like substr($fwParts[1], 0, -1) can also be of type false; however, parameter $string of TYPO3\CMS\Core\Utility\G...alUtility::revExplode() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

4340
                    $backParts = GeneralUtility::revExplode('<', /** @scrutinizer ignore-type */ substr($fwParts[1], 0, -1), 2);
Loading history...
4341
                    $attrib = GeneralUtility::get_tag_attributes('<' . $fwParts[0] . '>');
4342
                    $str_content = $backParts[0];
4343
                    $sameBeginEnd = substr(strtolower($backParts[1]), 1, strlen($tagName)) === strtolower($tagName);
4344
                }
4345
            }
4346
            if ($sameBeginEnd && in_array(strtolower($tagName), $encapTags)) {
0 ignored issues
show
Bug introduced by
It seems like $tagName can also be of type false; however, parameter $str of strtolower() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

4346
            if ($sameBeginEnd && in_array(strtolower(/** @scrutinizer ignore-type */ $tagName), $encapTags)) {
Loading history...
4347
                $uTagName = strtoupper($tagName);
0 ignored issues
show
Bug introduced by
It seems like $tagName can also be of type false; however, parameter $string of strtoupper() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

4347
                $uTagName = strtoupper(/** @scrutinizer ignore-type */ $tagName);
Loading history...
4348
                $uTagName = strtoupper($conf['remapTag.'][$uTagName] ? $conf['remapTag.'][$uTagName] : $uTagName);
4349
            } else {
4350
                $uTagName = strtoupper($nonWrappedTag);
4351
                // The line will be wrapped: $uTagName should not be an empty tag
4352
                $emptyTag = false;
4353
                $str_content = $lParts[$k];
4354
                $nonWrapped = true;
4355
                $attrib = [];
4356
            }
4357
            // Wrapping all inner-content:
4358
            if (is_array($conf['innerStdWrap_all.'])) {
4359
                $str_content = $this->stdWrap($str_content, $conf['innerStdWrap_all.']);
4360
            }
4361
            if ($uTagName) {
4362
                // Setting common attributes
4363
                if (is_array($conf['addAttributes.'][$uTagName . '.'])) {
4364
                    foreach ($conf['addAttributes.'][$uTagName . '.'] as $kk => $vv) {
4365
                        if (!is_array($vv)) {
4366
                            if ((string)$conf['addAttributes.'][$uTagName . '.'][$kk . '.']['setOnly'] === 'blank') {
4367
                                if ((string)$attrib[$kk] === '') {
4368
                                    $attrib[$kk] = $vv;
4369
                                }
4370
                            } elseif ((string)$conf['addAttributes.'][$uTagName . '.'][$kk . '.']['setOnly'] === 'exists') {
4371
                                if (!isset($attrib[$kk])) {
4372
                                    $attrib[$kk] = $vv;
4373
                                }
4374
                            } else {
4375
                                $attrib[$kk] = $vv;
4376
                            }
4377
                        }
4378
                    }
4379
                }
4380
                // Wrapping all inner-content:
4381
                if (is_array($conf['encapsLinesStdWrap.'][$uTagName . '.'])) {
4382
                    $str_content = $this->stdWrap($str_content, $conf['encapsLinesStdWrap.'][$uTagName . '.']);
4383
                }
4384
                // Default align
4385
                if (!$attrib['align'] && $defaultAlign) {
4386
                    $attrib['align'] = $defaultAlign;
4387
                }
4388
                $params = GeneralUtility::implodeAttributes($attrib, true);
4389
                if (!($conf['removeWrapping'] && !($emptyTag && $conf['removeWrapping.']['keepSingleTag']))) {
4390
                    if ($emptyTag) {
4391
                        $str_content = '<' . strtolower($uTagName) . (trim($params) ? ' ' . trim($params) : '') . ' />';
4392
                    } else {
4393
                        $str_content = '<' . strtolower($uTagName) . (trim($params) ? ' ' . trim($params) : '') . '>' . $str_content . '</' . strtolower($uTagName) . '>';
4394
                    }
4395
                }
4396
            }
4397
            if ($nonWrapped && $conf['wrapNonWrappedLines']) {
4398
                $str_content = $this->wrap($str_content, $conf['wrapNonWrappedLines']);
4399
            }
4400
            $lParts[$k] = $str_content;
4401
        }
4402
        return implode(LF, $lParts);
4403
    }
4404
4405
    /**
4406
     * Finds URLS in text and makes it to a real link.
4407
     * Will find all strings prefixed with "http://" and "https://" in the $data string and make them into a link,
4408
     * linking to the URL we should have found.
4409
     *
4410
     * @param string $data The string in which to search for "http://
4411
     * @param array $conf Configuration for makeLinks, see link
4412
     * @return string The processed input string, being returned.
4413
     * @see _parseFunc()
4414
     */
4415
    public function http_makelinks($data, $conf)
4416
    {
4417
        $aTagParams = $this->getATagParams($conf);
4418
        $textstr = '';
4419
        foreach ([ 'http://', 'https://' ] as $scheme) {
4420
            $textpieces = explode($scheme, $data);
4421
            $pieces = count($textpieces);
4422
            $textstr = $textpieces[0];
4423
            for ($i = 1; $i < $pieces; $i++) {
4424
                $len = strcspn($textpieces[$i], chr(32) . TAB . CRLF);
4425
                if (trim(substr($textstr, -1)) === '' && $len) {
0 ignored issues
show
Bug introduced by
It seems like substr($textstr, -1) can also be of type false; however, parameter $str of trim() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

4425
                if (trim(/** @scrutinizer ignore-type */ substr($textstr, -1)) === '' && $len) {
Loading history...
4426
                    $lastChar = substr($textpieces[$i], $len - 1, 1);
4427
                    if (!preg_match('/[A-Za-z0-9\\/#_-]/', $lastChar)) {
0 ignored issues
show
Bug introduced by
It seems like $lastChar can also be of type false; however, parameter $subject of preg_match() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

4427
                    if (!preg_match('/[A-Za-z0-9\\/#_-]/', /** @scrutinizer ignore-type */ $lastChar)) {
Loading history...
4428
                        $len--;
4429
                    }
4430
                    // Included '\/' 3/12
4431
                    $parts[0] = substr($textpieces[$i], 0, $len);
4432
                    $parts[1] = substr($textpieces[$i], $len);
4433
                    $keep = $conf['keep'];
4434
                    $linkParts = parse_url($scheme . $parts[0]);
4435
                    $linktxt = '';
4436
                    if (strstr($keep, 'scheme')) {
4437
                        $linktxt = $scheme;
4438
                    }
4439
                    $linktxt .= $linkParts['host'];
4440
                    if (strstr($keep, 'path')) {
4441
                        $linktxt .= $linkParts['path'];
4442
                        // Added $linkParts['query'] 3/12
4443
                        if (strstr($keep, 'query') && $linkParts['query']) {
4444
                            $linktxt .= '?' . $linkParts['query'];
4445
                        } elseif ($linkParts['path'] === '/') {
4446
                            $linktxt = substr($linktxt, 0, -1);
4447
                        }
4448
                    }
4449
                    if (isset($conf['extTarget'])) {
4450
                        if (isset($conf['extTarget.'])) {
4451
                            $target = $this->stdWrap($conf['extTarget'], $conf['extTarget.']);
4452
                        } else {
4453
                            $target = $conf['extTarget'];
4454
                        }
4455
                    } else {
4456
                        $target = $this->getTypoScriptFrontendController()->extTarget;
4457
                    }
4458
4459
                    // check for jump URLs or similar
4460
                    $linkUrl = $this->processUrl(UrlProcessorInterface::CONTEXT_COMMON, $scheme . $parts[0], $conf);
4461
4462
                    $res = '<a href="' . htmlspecialchars($linkUrl) . '"'
4463
                        . ($target !== '' ? ' target="' . htmlspecialchars($target) . '"' : '')
4464
                        . $aTagParams . $this->extLinkATagParams(('http://' . $parts[0]), 'url') . '>';
4465
4466
                    $wrap = isset($conf['wrap.']) ? $this->stdWrap($conf['wrap'], $conf['wrap.']) : $conf['wrap'];
4467 View Code Duplication
                    if ((string)$conf['ATagBeforeWrap'] !== '') {
4468
                        $res = $res . $this->wrap($linktxt, $wrap) . '</a>';
0 ignored issues
show
Bug introduced by
It seems like $linktxt can also be of type false; however, parameter $content of TYPO3\CMS\Frontend\Conte...tObjectRenderer::wrap() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

4468
                        $res = $res . $this->wrap(/** @scrutinizer ignore-type */ $linktxt, $wrap) . '</a>';
Loading history...
4469
                    } else {
4470
                        $res = $this->wrap($res . $linktxt . '</a>', $wrap);
0 ignored issues
show
Bug introduced by
Are you sure $linktxt of type false|string can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

4470
                        $res = $this->wrap($res . /** @scrutinizer ignore-type */ $linktxt . '</a>', $wrap);
Loading history...
4471
                    }
4472
                    $textstr .= $res . $parts[1];
4473
                } else {
4474
                    $textstr .= $scheme . $textpieces[$i];
4475
                }
4476
            }
4477
            $data = $textstr;
4478
        }
4479
        return $textstr;
4480
    }
4481
4482
    /**
4483
     * Will find all strings prefixed with "mailto:" in the $data string and make them into a link,
4484
     * linking to the email address they point to.
4485
     *
4486
     * @param string $data The string in which to search for "mailto:
4487
     * @param array $conf Configuration for makeLinks, see link
4488
     * @return string The processed input string, being returned.
4489
     * @see _parseFunc()
4490
     */
4491
    public function mailto_makelinks($data, $conf)
4492
    {
4493
        // http-split
4494
        $aTagParams = $this->getATagParams($conf);
4495
        $textpieces = explode('mailto:', $data);
4496
        $pieces = count($textpieces);
4497
        $textstr = $textpieces[0];
4498
        $tsfe = $this->getTypoScriptFrontendController();
4499
        for ($i = 1; $i < $pieces; $i++) {
4500
            $len = strcspn($textpieces[$i], chr(32) . TAB . CRLF);
4501
            if (trim(substr($textstr, -1)) === '' && $len) {
0 ignored issues
show
Bug introduced by
It seems like substr($textstr, -1) can also be of type false; however, parameter $str of trim() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

4501
            if (trim(/** @scrutinizer ignore-type */ substr($textstr, -1)) === '' && $len) {
Loading history...
4502
                $lastChar = substr($textpieces[$i], $len - 1, 1);
4503
                if (!preg_match('/[A-Za-z0-9]/', $lastChar)) {
0 ignored issues
show
Bug introduced by
It seems like $lastChar can also be of type false; however, parameter $subject of preg_match() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

4503
                if (!preg_match('/[A-Za-z0-9]/', /** @scrutinizer ignore-type */ $lastChar)) {
Loading history...
4504
                    $len--;
4505
                }
4506
                $parts[0] = substr($textpieces[$i], 0, $len);
4507
                $parts[1] = substr($textpieces[$i], $len);
4508
                $linktxt = preg_replace('/\\?.*/', '', $parts[0]);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $parts does not seem to be defined for all execution paths leading up to this point.
Loading history...
4509
                list($mailToUrl, $linktxt) = $this->getMailTo($parts[0], $linktxt);
4510
                $mailToUrl = $tsfe->spamProtectEmailAddresses === 'ascii' ? $mailToUrl : htmlspecialchars($mailToUrl);
4511
                $res = '<a href="' . $mailToUrl . '"' . $aTagParams . '>';
4512
                $wrap = isset($conf['wrap.']) ? $this->stdWrap($conf['wrap'], $conf['wrap.']) : $conf['wrap'];
4513 View Code Duplication
                if ((string)$conf['ATagBeforeWrap'] !== '') {
4514
                    $res = $res . $this->wrap($linktxt, $wrap) . '</a>';
4515
                } else {
4516
                    $res = $this->wrap($res . $linktxt . '</a>', $wrap);
4517
                }
4518
                $textstr .= $res . $parts[1];
4519
            } else {
4520
                $textstr .= 'mailto:' . $textpieces[$i];
4521
            }
4522
        }
4523
        return $textstr;
4524
    }
4525
4526
    /**
4527
     * Creates and returns a TypoScript "imgResource".
4528
     * The value ($file) can either be a file reference (TypoScript resource) or the string "GIFBUILDER".
4529
     * In the first case a current image is returned, possibly scaled down or otherwise processed.
4530
     * In the latter case a GIFBUILDER image is returned; This means an image is made by TYPO3 from layers of elements as GIFBUILDER defines.
4531
     * In the function IMG_RESOURCE() this function is called like $this->getImgResource($conf['file'], $conf['file.']);
4532
     *
4533
     * Structure of the returned info array:
4534
     *  0 => width
4535
     *  1 => height
4536
     *  2 => file extension
4537
     *  3 => file name
4538
     *  origFile => original file name
4539
     *  origFile_mtime => original file mtime
4540
     *  -- only available if processed via FAL: --
4541
     *  originalFile => original file object
4542
     *  processedFile => processed file object
4543
     *  fileCacheHash => checksum of processed file
4544
     *
4545
     * @param string|File|FileReference $file A "imgResource" TypoScript data type. Either a TypoScript file resource, a file or a file reference object or the string GIFBUILDER. See description above.
4546
     * @param array $fileArray TypoScript properties for the imgResource type
4547
     * @return array|null Returns info-array
4548
     * @see IMG_RESOURCE(), cImage(), \TYPO3\CMS\Frontend\Imaging\GifBuilder
4549
     */
4550
    public function getImgResource($file, $fileArray)
4551
    {
4552
        if (empty($file) && empty($fileArray)) {
4553
            return null;
4554
        }
4555
        if (!is_array($fileArray)) {
4556
            $fileArray = (array)$fileArray;
4557
        }
4558
        $imageResource = null;
4559
        $tsfe = $this->getTypoScriptFrontendController();
4560
        if ($file === 'GIFBUILDER') {
4561
            /** @var GifBuilder $gifCreator */
4562
            $gifCreator = GeneralUtility::makeInstance(GifBuilder::class);
4563
            $gifCreator->init();
4564
            $theImage = '';
4565
            if ($GLOBALS['TYPO3_CONF_VARS']['GFX']['gdlib']) {
4566
                $gifCreator->start($fileArray, $this->data);
4567
                $theImage = $gifCreator->gifBuild();
4568
            }
4569
            $imageResource = $gifCreator->getImageDimensions($theImage);
4570
            $imageResource['origFile'] = $theImage;
4571
        } else {
4572
            if ($file instanceof File) {
4573
                $fileObject = $file;
4574
            } elseif ($file instanceof FileReference) {
4575
                $fileObject = $file->getOriginalFile();
4576 View Code Duplication
                if (!isset($fileArray['crop'])) {
4577
                    $fileArray['crop'] = $this->getCropArea($file, $fileArray['cropVariant'] ?: 'default');
4578
                }
4579
            } else {
4580
                try {
4581
                    if ($fileArray['import.']) {
4582
                        $importedFile = trim($this->stdWrap('', $fileArray['import.']));
4583
                        if (!empty($importedFile)) {
4584
                            $file = $importedFile;
4585
                        }
4586
                    }
4587
4588
                    if (MathUtility::canBeInterpretedAsInteger($file)) {
4589
                        $treatIdAsReference = isset($fileArray['treatIdAsReference.']) ? $this->stdWrap($fileArray['treatIdAsReference'], $fileArray['treatIdAsReference.']) : $fileArray['treatIdAsReference'];
4590
                        if (!empty($treatIdAsReference)) {
4591
                            $fileReference = $this->getResourceFactory()->getFileReferenceObject($file);
0 ignored issues
show
Bug introduced by
$file of type string is incompatible with the type integer expected by parameter $uid of TYPO3\CMS\Core\Resource\...etFileReferenceObject(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

4591
                            $fileReference = $this->getResourceFactory()->getFileReferenceObject(/** @scrutinizer ignore-type */ $file);
Loading history...
4592
                            $fileObject = $fileReference->getOriginalFile();
4593 View Code Duplication
                            if (!isset($fileArray['crop'])) {
4594
                                $fileArray['crop'] = $this->getCropArea($fileReference, $fileArray['cropVariant'] ?: 'default');
4595
                            }
4596
                        } else {
4597
                            $fileObject = $this->getResourceFactory()->getFileObject($file);
0 ignored issues
show
Bug introduced by
$file of type string is incompatible with the type integer expected by parameter $uid of TYPO3\CMS\Core\Resource\...actory::getFileObject(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

4597
                            $fileObject = $this->getResourceFactory()->getFileObject(/** @scrutinizer ignore-type */ $file);
Loading history...
4598
                        }
4599
                    } elseif (preg_match('/^(0|[1-9][0-9]*):/', $file)) { // combined identifier
4600
                        $fileObject = $this->getResourceFactory()->retrieveFileOrFolderObject($file);
4601
                    } else {
4602
                        if (isset($importedFile) && !empty($importedFile) && !empty($fileArray['import'])) {
4603
                            $file = $fileArray['import'] . $file;
4604
                        }
4605
                        $fileObject = $this->getResourceFactory()->retrieveFileOrFolderObject($file);
4606
                    }
4607
                } catch (Exception $exception) {
4608
                    /** @var \TYPO3\CMS\Core\Log\Logger $logger */
4609
                    $logger = GeneralUtility::makeInstance(LogManager::class)->getLogger(__CLASS__);
4610
                    $logger->warning('The image "' . $file . '" could not be found and won\'t be included in frontend output', ['exception' => $exception]);
4611
                    return null;
4612
                }
4613
            }
4614
            if ($fileObject instanceof File) {
4615
                $processingConfiguration = [];
4616
                $processingConfiguration['width'] = isset($fileArray['width.']) ? $this->stdWrap($fileArray['width'], $fileArray['width.']) : $fileArray['width'];
4617
                $processingConfiguration['height'] = isset($fileArray['height.']) ? $this->stdWrap($fileArray['height'], $fileArray['height.']) : $fileArray['height'];
4618
                $processingConfiguration['fileExtension'] = isset($fileArray['ext.']) ? $this->stdWrap($fileArray['ext'], $fileArray['ext.']) : $fileArray['ext'];
4619
                $processingConfiguration['maxWidth'] = isset($fileArray['maxW.']) ? (int)$this->stdWrap($fileArray['maxW'], $fileArray['maxW.']) : (int)$fileArray['maxW'];
4620
                $processingConfiguration['maxHeight'] = isset($fileArray['maxH.']) ? (int)$this->stdWrap($fileArray['maxH'], $fileArray['maxH.']) : (int)$fileArray['maxH'];
4621
                $processingConfiguration['minWidth'] = isset($fileArray['minW.']) ? (int)$this->stdWrap($fileArray['minW'], $fileArray['minW.']) : (int)$fileArray['minW'];
4622
                $processingConfiguration['minHeight'] = isset($fileArray['minH.']) ? (int)$this->stdWrap($fileArray['minH'], $fileArray['minH.']) : (int)$fileArray['minH'];
4623
                $processingConfiguration['noScale'] = isset($fileArray['noScale.']) ? $this->stdWrap($fileArray['noScale'], $fileArray['noScale.']) : $fileArray['noScale'];
4624
                $processingConfiguration['additionalParameters'] = isset($fileArray['params.']) ? $this->stdWrap($fileArray['params'], $fileArray['params.']) : $fileArray['params'];
4625
                $processingConfiguration['frame'] = isset($fileArray['frame.']) ? (int)$this->stdWrap($fileArray['frame'], $fileArray['frame.']) : (int)$fileArray['frame'];
4626
                $processingConfiguration['crop'] = isset($fileArray['crop.'])
4627
                    ? $this->stdWrap($fileArray['crop'], $fileArray['crop.'])
4628
                    : (isset($fileArray['crop']) ? $fileArray['crop'] : null);
4629
                // Possibility to cancel/force profile extraction
4630
                // see $GLOBALS['TYPO3_CONF_VARS']['GFX']['processor_stripColorProfileCommand']
4631
                if (isset($fileArray['stripProfile'])) {
4632
                    $processingConfiguration['stripProfile'] = $fileArray['stripProfile'];
4633
                }
4634
                // Check if we can handle this type of file for editing
4635
                if (GeneralUtility::inList($GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'], $fileObject->getExtension())) {
4636
                    $maskArray = $fileArray['m.'];
4637
                    // Must render mask images and include in hash-calculating
4638
                    // - otherwise we cannot be sure the filename is unique for the setup!
4639
                    if (is_array($maskArray)) {
4640
                        $mask = $this->getImgResource($maskArray['mask'], $maskArray['mask.']);
4641
                        $bgImg = $this->getImgResource($maskArray['bgImg'], $maskArray['bgImg.']);
4642
                        $bottomImg = $this->getImgResource($maskArray['bottomImg'], $maskArray['bottomImg.']);
4643
                        $bottomImg_mask = $this->getImgResource($maskArray['bottomImg_mask'], $maskArray['bottomImg_mask.']);
4644
4645
                        $processingConfiguration['maskImages']['maskImage'] = $mask['processedFile'];
4646
                        $processingConfiguration['maskImages']['backgroundImage'] = $bgImg['processedFile'];
4647
                        $processingConfiguration['maskImages']['maskBottomImage'] = $bottomImg['processedFile'];
4648
                        $processingConfiguration['maskImages']['maskBottomImageMask'] = $bottomImg_mask['processedFile'];
4649
                    }
4650
                    $processedFileObject = $fileObject->process(ProcessedFile::CONTEXT_IMAGECROPSCALEMASK, $processingConfiguration);
4651
                    $hash = $processedFileObject->calculateChecksum();
4652
                    // store info in the TSFE template cache (kept for backwards compatibility)
4653
                    if ($processedFileObject->isProcessed() && !isset($tsfe->tmpl->fileCache[$hash])) {
4654
                        $tsfe->tmpl->fileCache[$hash] = [
4655
                            0 => $processedFileObject->getProperty('width'),
4656
                            1 => $processedFileObject->getProperty('height'),
4657
                            2 => $processedFileObject->getExtension(),
4658
                            3 => $processedFileObject->getPublicUrl(),
4659
                            'origFile' => $fileObject->getPublicUrl(),
4660
                            'origFile_mtime' => $fileObject->getModificationTime(),
4661
                            // This is needed by \TYPO3\CMS\Frontend\Imaging\GifBuilder,
4662
                            // in order for the setup-array to create a unique filename hash.
4663
                            'originalFile' => $fileObject,
4664
                            'processedFile' => $processedFileObject,
4665
                            'fileCacheHash' => $hash
4666
                        ];
4667
                    }
4668
                    $imageResource = $tsfe->tmpl->fileCache[$hash];
4669
                }
4670
            }
4671
        }
4672
        // If image was processed by GIFBUILDER:
4673
        // ($imageResource indicates that it was processed the regular way)
4674
        if (!isset($imageResource)) {
4675
            $theImage = $tsfe->tmpl->getFileName($file);
0 ignored issues
show
Bug introduced by
It seems like $file can also be of type TYPO3\CMS\Core\Resource\FileReference and TYPO3\CMS\Core\Resource\File; however, parameter $fileFromSetup of TYPO3\CMS\Core\TypoScrip...eService::getFileName() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

4675
            $theImage = $tsfe->tmpl->getFileName(/** @scrutinizer ignore-type */ $file);
Loading history...
4676
            if ($theImage) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $theImage of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
4677
                $gifCreator = GeneralUtility::makeInstance(GifBuilder::class);
4678
                /** @var $gifCreator GifBuilder */
4679
                $gifCreator->init();
4680
                $info = $gifCreator->imageMagickConvert($theImage, 'WEB');
4681
                $info['origFile'] = $theImage;
4682
                // This is needed by \TYPO3\CMS\Frontend\Imaging\GifBuilder, ln 100ff in order for the setup-array to create a unique filename hash.
4683
                $info['origFile_mtime'] = @filemtime($theImage);
4684
                $imageResource = $info;
4685
            }
4686
        }
4687
        // Hook 'getImgResource': Post-processing of image resources
4688
        if (isset($imageResource)) {
4689
            /** @var ContentObjectGetImageResourceHookInterface $hookObject */
4690
            foreach ($this->getGetImgResourceHookObjects() as $hookObject) {
4691
                $imageResource = $hookObject->getImgResourcePostProcess($file, (array)$fileArray, $imageResource, $this);
4692
            }
4693
        }
4694
        return $imageResource;
4695
    }
4696
4697
    /**
4698
     * @param FileReference $fileReference
4699
     * @param string $cropVariant
4700
     * @return \TYPO3\CMS\Core\Imaging\ImageManipulation\Area|null
4701
     */
4702
    protected function getCropArea(FileReference $fileReference, string $cropVariant)
4703
    {
4704
        $cropVariantCollection = CropVariantCollection::create(
4705
            (string)$fileReference->getProperty('crop')
4706
        );
4707
        $cropArea = $cropVariantCollection->getCropArea($cropVariant);
4708
        return $cropArea->isEmpty() ? null : $cropArea->makeAbsoluteBasedOnFile($fileReference);
4709
    }
4710
4711
    /***********************************************
4712
     *
4713
     * Data retrieval etc.
4714
     *
4715
     ***********************************************/
4716
    /**
4717
     * Returns the value for the field from $this->data. If "//" is found in the $field value that token will split the field values apart and the first field having a non-blank value will be returned.
4718
     *
4719
     * @param string $field The fieldname, eg. "title" or "navtitle // title" (in the latter case the value of $this->data[navtitle] is returned if not blank, otherwise $this->data[title] will be)
4720
     * @return string
4721
     */
4722
    public function getFieldVal($field)
4723
    {
4724
        if (!strstr($field, '//')) {
4725
            return $this->data[trim($field)];
4726
        }
4727
        $sections = GeneralUtility::trimExplode('//', $field, true);
4728
        foreach ($sections as $k) {
4729
            if ((string)$this->data[$k] !== '') {
4730
                return $this->data[$k];
4731
            }
4732
        }
4733
4734
        return '';
4735
    }
4736
4737
    /**
4738
     * Implements the TypoScript data type "getText". This takes a string with parameters and based on those a value from somewhere in the system is returned.
4739
     *
4740
     * @param string $string The parameter string, eg. "field : title" or "field : navtitle // field : title" (in the latter case and example of how the value is FIRST splitted by "//" is shown)
4741
     * @param array|null $fieldArray Alternative field array; If you set this to an array this variable will be used to look up values for the "field" key. Otherwise the current page record in $GLOBALS['TSFE']->page is used.
4742
     * @return string The value fetched
4743
     * @see getFieldVal()
4744
     */
4745
    public function getData($string, $fieldArray = null)
4746
    {
4747
        $tsfe = $this->getTypoScriptFrontendController();
4748
        if (!is_array($fieldArray)) {
4749
            $fieldArray = $tsfe->page;
4750
        }
4751
        $retVal = '';
4752
        $sections = explode('//', $string);
4753
        foreach ($sections as $secKey => $secVal) {
4754
            if ($retVal) {
4755
                break;
4756
            }
4757
            $parts = explode(':', $secVal, 2);
4758
            $type = strtolower(trim($parts[0]));
4759
            $typesWithOutParameters = ['level', 'date', 'current', 'pagelayout'];
4760
            $key = trim($parts[1]);
4761
            if (($key != '') || in_array($type, $typesWithOutParameters)) {
4762
                switch ($type) {
4763
                    case 'gp':
4764
                        // Merge GET and POST and get $key out of the merged array
4765
                        $getPostArray = GeneralUtility::_GET();
4766
                        ArrayUtility::mergeRecursiveWithOverrule($getPostArray, GeneralUtility::_POST());
0 ignored issues
show
Bug introduced by
It seems like $getPostArray can also be of type string; however, parameter $original of TYPO3\CMS\Core\Utility\A...RecursiveWithOverrule() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

4766
                        ArrayUtility::mergeRecursiveWithOverrule(/** @scrutinizer ignore-type */ $getPostArray, GeneralUtility::_POST());
Loading history...
Bug introduced by
It seems like TYPO3\CMS\Core\Utility\GeneralUtility::_POST() can also be of type string; however, parameter $overrule of TYPO3\CMS\Core\Utility\A...RecursiveWithOverrule() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

4766
                        ArrayUtility::mergeRecursiveWithOverrule($getPostArray, /** @scrutinizer ignore-type */ GeneralUtility::_POST());
Loading history...
4767
                        $retVal = $this->getGlobal($key, $getPostArray);
0 ignored issues
show
Bug introduced by
It seems like $getPostArray can also be of type string; however, parameter $source of TYPO3\CMS\Frontend\Conte...ctRenderer::getGlobal() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

4767
                        $retVal = $this->getGlobal($key, /** @scrutinizer ignore-type */ $getPostArray);
Loading history...
4768
                        break;
4769
                    case 'tsfe':
4770
                        $retVal = $this->getGlobal('TSFE|' . $key);
4771
                        break;
4772
                    case 'getenv':
4773
                        $retVal = getenv($key);
4774
                        break;
4775
                    case 'getindpenv':
4776
                        $retVal = $this->getEnvironmentVariable($key);
4777
                        break;
4778
                    case 'field':
4779
                        $retVal = $this->getGlobal($key, $fieldArray);
4780
                        break;
4781
                    case 'file':
4782
                        $retVal = $this->getFileDataKey($key);
4783
                        break;
4784
                    case 'parameters':
4785
                        $retVal = $this->parameters[$key];
4786
                        break;
4787
                    case 'register':
4788
                        $retVal = $tsfe->register[$key];
4789
                        break;
4790
                    case 'global':
4791
                        $retVal = $this->getGlobal($key);
4792
                        break;
4793
                    case 'level':
4794
                        $retVal = count($tsfe->tmpl->rootLine) - 1;
4795
                        break;
4796 View Code Duplication
                    case 'leveltitle':
4797
                        $keyParts = GeneralUtility::trimExplode(',', $key);
4798
                        $numericKey = $this->getKey($keyParts[0], $tsfe->tmpl->rootLine);
4799
                        $retVal = $this->rootLineValue($numericKey, 'title', strtolower($keyParts[1]) === 'slide');
4800
                        break;
4801 View Code Duplication
                    case 'levelmedia':
4802
                        $keyParts = GeneralUtility::trimExplode(',', $key);
4803
                        $numericKey = $this->getKey($keyParts[0], $tsfe->tmpl->rootLine);
4804
                        $retVal = $this->rootLineValue($numericKey, 'media', strtolower($keyParts[1]) === 'slide');
4805
                        break;
4806
                    case 'leveluid':
4807
                        $numericKey = $this->getKey($key, $tsfe->tmpl->rootLine);
0 ignored issues
show
Bug introduced by
$key of type string is incompatible with the type integer expected by parameter $key of TYPO3\CMS\Frontend\Conte...bjectRenderer::getKey(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

4807
                        $numericKey = $this->getKey(/** @scrutinizer ignore-type */ $key, $tsfe->tmpl->rootLine);
Loading history...
4808
                        $retVal = $this->rootLineValue($numericKey, 'uid');
4809
                        break;
4810 View Code Duplication
                    case 'levelfield':
4811
                        $keyParts = GeneralUtility::trimExplode(',', $key);
4812
                        $numericKey = $this->getKey($keyParts[0], $tsfe->tmpl->rootLine);
4813
                        $retVal = $this->rootLineValue($numericKey, $keyParts[1], strtolower($keyParts[2]) === 'slide');
4814
                        break;
4815
                    case 'fullrootline':
4816
                        $keyParts = GeneralUtility::trimExplode(',', $key);
4817
                        $fullKey = (int)$keyParts[0] - count($tsfe->tmpl->rootLine) + count($tsfe->rootLine);
4818
                        if ($fullKey >= 0) {
4819
                            $retVal = $this->rootLineValue($fullKey, $keyParts[1], stristr($keyParts[2], 'slide'), $tsfe->rootLine);
0 ignored issues
show
Bug introduced by
stristr($keyParts[2], 'slide') of type string is incompatible with the type boolean expected by parameter $slideBack of TYPO3\CMS\Frontend\Conte...nderer::rootLineValue(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

4819
                            $retVal = $this->rootLineValue($fullKey, $keyParts[1], /** @scrutinizer ignore-type */ stristr($keyParts[2], 'slide'), $tsfe->rootLine);
Loading history...
4820
                        }
4821
                        break;
4822
                    case 'date':
4823
                        if (!$key) {
4824
                            $key = 'd/m Y';
4825
                        }
4826
                        $retVal = date($key, $GLOBALS['EXEC_TIME']);
4827
                        break;
4828
                    case 'page':
4829
                        $retVal = $tsfe->page[$key];
4830
                        break;
4831
                    case 'pagelayout':
4832
                        // Check if the current page has a value in the DB field "backend_layout"
4833
                        // if empty, check the root line for "backend_layout_next_level"
4834
                        // same as
4835
                        //   field = backend_layout
4836
                        //   ifEmpty.data = levelfield:-2, backend_layout_next_level, slide
4837
                        //   ifEmpty.ifEmpty = default
4838
                        $retVal = $GLOBALS['TSFE']->page['backend_layout'];
4839
4840
                        // If it is set to "none" - don't use any
4841
                        if ($retVal === '-1') {
4842
                            $retVal = 'none';
4843
                        } elseif ($retVal === '' || $retVal === '0') {
4844
                            // If it not set check the root-line for a layout on next level and use this
4845
                            // Remove first element, which is the current page
4846
                            // See also \TYPO3\CMS\Backend\View\BackendLayoutView::getSelectedCombinedIdentifier()
4847
                            $rootLine = $tsfe->rootLine;
4848
                            array_shift($rootLine);
4849
                            foreach ($rootLine as $rootLinePage) {
4850
                                $retVal = (string) $rootLinePage['backend_layout_next_level'];
4851
                                // If layout for "next level" is set to "none" - don't use any and stop searching
4852
                                if ($retVal === '-1') {
4853
                                    $retVal = 'none';
4854
                                    break;
4855
                                }
4856
                                if ($retVal !== '' && $retVal !== '0') {
4857
                                    // Stop searching if a layout for "next level" is set
4858
                                    break;
4859
                                }
4860
                            }
4861
                        }
4862
                        if ($retVal === '0' || $retVal === '') {
4863
                            $retVal = 'default';
4864
                        }
4865
                        break;
4866
                    case 'current':
4867
                        $retVal = $this->data[$this->currentValKey];
4868
                        break;
4869
                    case 'db':
4870
                        $selectParts = GeneralUtility::trimExplode(':', $key);
4871
                        $db_rec = $tsfe->sys_page->getRawRecord($selectParts[0], $selectParts[1]);
4872
                        if (is_array($db_rec) && $selectParts[2]) {
4873
                            $retVal = $db_rec[$selectParts[2]];
4874
                        }
4875
                        break;
4876
                    case 'lll':
4877
                        $retVal = $tsfe->sL('LLL:' . $key);
4878
                        break;
4879
                    case 'path':
4880
                        $retVal = $tsfe->tmpl->getFileName($key);
4881
                        break;
4882
                    case 'cobj':
4883
                        switch ($key) {
4884
                            case 'parentRecordNumber':
4885
                                $retVal = $this->parentRecordNumber;
4886
                                break;
4887
                        }
4888
                        break;
4889
                    case 'debug':
4890
                        switch ($key) {
4891
                            case 'rootLine':
4892
                                $retVal = DebugUtility::viewArray($tsfe->tmpl->rootLine);
4893
                                break;
4894
                            case 'fullRootLine':
4895
                                $retVal = DebugUtility::viewArray($tsfe->rootLine);
4896
                                break;
4897
                            case 'data':
4898
                                $retVal = DebugUtility::viewArray($this->data);
4899
                                break;
4900
                            case 'register':
4901
                                $retVal = DebugUtility::viewArray($tsfe->register);
4902
                                break;
4903
                            case 'page':
4904
                                $retVal = DebugUtility::viewArray($tsfe->page);
4905
                                break;
4906
                        }
4907
                        break;
4908
                    case 'flexform':
4909
                        $keyParts = GeneralUtility::trimExplode(':', $key, true);
4910
                        if (count($keyParts) === 2 && isset($this->data[$keyParts[0]])) {
4911
                            $flexFormContent = $this->data[$keyParts[0]];
4912
                            if (!empty($flexFormContent)) {
4913
                                $flexFormService = GeneralUtility::makeInstance(FlexFormService::class);
4914
                                $flexFormKey = str_replace('.', '|', $keyParts[1]);
4915
                                $settings = $flexFormService->convertFlexFormContentToArray($flexFormContent);
4916
                                $retVal = $this->getGlobal($flexFormKey, $settings);
4917
                            }
4918
                        }
4919
                        break;
4920
                    case 'session':
4921
                        $keyParts = GeneralUtility::trimExplode('|', $key, true);
4922
                        $sessionKey = array_shift($keyParts);
4923
                        $retVal = $this->getTypoScriptFrontendController()->fe_user->getSessionData($sessionKey);
4924
                        foreach ($keyParts as $keyPart) {
4925
                            if (is_object($retVal)) {
4926
                                $retVal = $retVal->{$keyPart};
4927
                            } elseif (is_array($retVal)) {
4928
                                $retVal = $retVal[$keyPart];
4929
                            } else {
4930
                                $retVal = '';
4931
                                break;
4932
                            }
4933
                        }
4934
                        if (!is_scalar($retVal)) {
4935
                            $retVal = '';
4936
                        }
4937
                        break;
4938
                }
4939
            }
4940
4941
            foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['getData'] ?? [] as $className) {
4942
                $hookObject = GeneralUtility::makeInstance($className);
4943
                if (!$hookObject instanceof ContentObjectGetDataHookInterface) {
4944
                    throw new \UnexpectedValueException('$hookObject must implement interface ' . ContentObjectGetDataHookInterface::class, 1195044480);
4945
                }
4946
                $retVal = $hookObject->getDataExtension($string, $fieldArray, $secVal, $retVal, $this);
4947
            }
4948
        }
4949
        return $retVal;
4950
    }
4951
4952
    /**
4953
     * Gets file information. This is a helper function for the getData() method above, which resolves e.g.
4954
     * page.10.data = file:current:title
4955
     * or
4956
     * page.10.data = file:17:title
4957
     *
4958
     * @param string $key A colon-separated key, e.g. 17:name or current:sha1, with the first part being a sys_file uid or the keyword "current" and the second part being the key of information to get from file (e.g. "title", "size", "description", etc.)
4959
     * @return string|int The value as retrieved from the file object.
4960
     */
4961
    protected function getFileDataKey($key)
4962
    {
4963
        list($fileUidOrCurrentKeyword, $requestedFileInformationKey) = explode(':', $key, 3);
4964
        try {
4965
            if ($fileUidOrCurrentKeyword === 'current') {
4966
                $fileObject = $this->getCurrentFile();
4967
            } elseif (MathUtility::canBeInterpretedAsInteger($fileUidOrCurrentKeyword)) {
4968
                /** @var ResourceFactory $fileFactory */
4969
                $fileFactory = GeneralUtility::makeInstance(ResourceFactory::class);
4970
                $fileObject = $fileFactory->getFileObject($fileUidOrCurrentKeyword);
4971
            } else {
4972
                $fileObject = null;
4973
            }
4974
        } catch (Exception $exception) {
4975
            /** @var \TYPO3\CMS\Core\Log\Logger $logger */
4976
            $logger = GeneralUtility::makeInstance(LogManager::class)->getLogger(__CLASS__);
4977
            $logger->warning('The file "' . $fileUidOrCurrentKeyword . '" could not be found and won\'t be included in frontend output', ['exception' => $exception]);
4978
            $fileObject = null;
4979
        }
4980
4981
        if ($fileObject instanceof FileInterface) {
4982
            // All properties of the \TYPO3\CMS\Core\Resource\FileInterface are available here:
4983
            switch ($requestedFileInformationKey) {
4984
                case 'name':
4985
                    return $fileObject->getName();
4986
                case 'uid':
4987
                    if (method_exists($fileObject, 'getUid')) {
4988
                        return $fileObject->getUid();
4989
                    }
4990
                    return 0;
4991
                case 'originalUid':
4992
                    if ($fileObject instanceof FileReference) {
4993
                        return $fileObject->getOriginalFile()->getUid();
4994
                    }
4995
                    return null;
4996
                case 'size':
4997
                    return $fileObject->getSize();
4998
                case 'sha1':
4999
                    return $fileObject->getSha1();
5000
                case 'extension':
5001
                    return $fileObject->getExtension();
5002
                case 'mimetype':
5003
                    return $fileObject->getMimeType();
5004
                case 'contents':
5005
                    return $fileObject->getContents();
5006
                case 'publicUrl':
5007
                    return $fileObject->getPublicUrl();
5008
                default:
5009
                    // Generic alternative here
5010
                    return $fileObject->getProperty($requestedFileInformationKey);
5011
            }
5012
        } else {
5013
            // @todo fail silently as is common in tslib_content
5014
            return 'Error: no file object';
5015
        }
5016
    }
5017
5018
    /**
5019
     * Returns a value from the current rootline (site) from $GLOBALS['TSFE']->tmpl->rootLine;
5020
     *
5021
     * @param string $key Which level in the root line
5022
     * @param string $field The field in the rootline record to return (a field from the pages table)
5023
     * @param bool $slideBack If set, then we will traverse through the rootline from outer level towards the root level until the value found is TRUE
5024
     * @param mixed $altRootLine If you supply an array for this it will be used as an alternative root line array
5025
     * @return string The value from the field of the rootline.
5026
     * @access private
5027
     * @see getData()
5028
     */
5029
    public function rootLineValue($key, $field, $slideBack = false, $altRootLine = '')
5030
    {
5031
        $rootLine = is_array($altRootLine) ? $altRootLine : $this->getTypoScriptFrontendController()->tmpl->rootLine;
5032
        if (!$slideBack) {
5033
            return $rootLine[$key][$field];
5034
        }
5035
        for ($a = $key; $a >= 0; $a--) {
5036
            $val = $rootLine[$a][$field];
5037
            if ($val) {
5038
                return $val;
5039
            }
5040
        }
5041
5042
        return '';
5043
    }
5044
5045
    /**
5046
     * Return global variable where the input string $var defines array keys separated by "|"
5047
     * Example: $var = "HTTP_SERVER_VARS | something" will return the value $GLOBALS['HTTP_SERVER_VARS']['something'] value
5048
     *
5049
     * @param string $keyString Global var key, eg. "HTTP_GET_VAR" or "HTTP_GET_VARS|id" to get the GET parameter "id" back.
5050
     * @param array $source Alternative array than $GLOBAL to get variables from.
5051
     * @return mixed Whatever value. If none, then blank string.
5052
     * @see getData()
5053
     */
5054
    public function getGlobal($keyString, $source = null)
5055
    {
5056
        $keys = explode('|', $keyString);
5057
        $numberOfLevels = count($keys);
5058
        $rootKey = trim($keys[0]);
5059
        $value = isset($source) ? $source[$rootKey] : $GLOBALS[$rootKey];
5060 View Code Duplication
        for ($i = 1; $i < $numberOfLevels && isset($value); $i++) {
5061
            $currentKey = trim($keys[$i]);
5062
            if (is_object($value)) {
5063
                $value = $value->{$currentKey};
5064
            } elseif (is_array($value)) {
5065
                $value = $value[$currentKey];
5066
            } else {
5067
                $value = '';
5068
                break;
5069
            }
5070
        }
5071
        if (!is_scalar($value)) {
5072
            $value = '';
5073
        }
5074
        return $value;
5075
    }
5076
5077
    /**
5078
     * Processing of key values pointing to entries in $arr; Here negative values are converted to positive keys pointer to an entry in the array but from behind (based on the negative value).
5079
     * Example: entrylevel = -1 means that entryLevel ends up pointing at the outermost-level, -2 means the level before the outermost...
5080
     *
5081
     * @param int $key The integer to transform
5082
     * @param array $arr array in which the key should be found.
5083
     * @return int The processed integer key value.
5084
     * @access private
5085
     * @see getData()
5086
     */
5087
    public function getKey($key, $arr)
5088
    {
5089
        $key = (int)$key;
5090
        if (is_array($arr)) {
5091
            if ($key < 0) {
5092
                $key = count($arr) + $key;
5093
            }
5094
            if ($key < 0) {
5095
                $key = 0;
5096
            }
5097
        }
5098
        return $key;
5099
    }
5100
5101
    /**
5102
     * Looks up the incoming value in the defined TCA configuration
5103
     * Works only with TCA-type 'select' and options defined in 'items'
5104
     *
5105
     * @param mixed $inputValue Comma-separated list of values to look up
5106
     * @param array $conf TS-configuration array, see TSref for details
5107
     * @return string String of translated values, separated by $delimiter. If no matches were found, the input value is simply returned.
5108
     * @todo It would be nice it this function basically looked up any type of value, db-relations etc.
5109
     */
5110
    public function TCAlookup($inputValue, $conf)
5111
    {
5112
        $table = $conf['table'];
5113
        $field = $conf['field'];
5114
        $delimiter = $conf['delimiter'] ? $conf['delimiter'] : ' ,';
5115
        if (is_array($GLOBALS['TCA'][$table]) && is_array($GLOBALS['TCA'][$table]['columns'][$field]) && is_array($GLOBALS['TCA'][$table]['columns'][$field]['config']['items'])) {
5116
            $tsfe = $this->getTypoScriptFrontendController();
5117
            $values = GeneralUtility::trimExplode(',', $inputValue);
5118
            $output = [];
5119
            foreach ($values as $value) {
5120
                // Traverse the items-array...
5121
                foreach ($GLOBALS['TCA'][$table]['columns'][$field]['config']['items'] as $item) {
5122
                    // ... and return the first found label where the value was equal to $key
5123
                    if ((string)$item[1] === trim($value)) {
5124
                        $output[] = $tsfe->sL($item[0]);
5125
                    }
5126
                }
5127
            }
5128
            $returnValue = implode($delimiter, $output);
5129
        } else {
5130
            $returnValue = $inputValue;
5131
        }
5132
        return $returnValue;
5133
    }
5134
5135
    /***********************************************
5136
     *
5137
     * Link functions (typolink)
5138
     *
5139
     ***********************************************/
5140
5141
    /**
5142
     * called from the typoLink() function
5143
     *
5144
     * does the magic to split the full "typolink" string like "15,13 _blank myclass &more=1"
5145
     * into separate parts
5146
     *
5147
     * @param string $linkText The string (text) to link
5148
     * @param string $mixedLinkParameter destination data like "15,13 _blank myclass &more=1" used to create the link
5149
     * @param array $configuration TypoScript configuration
5150
     * @return array|string
5151
     * @see typoLink()
5152
     *
5153
     * @todo the functionality of the "file:" syntax + the hook should be marked as deprecated, an upgrade wizard should handle existing links
5154
     */
5155
    protected function resolveMixedLinkParameter($linkText, $mixedLinkParameter, &$configuration = [])
5156
    {
5157
        $linkParameter = null;
5158
5159
        // Link parameter value = first part
5160
        $linkParameterParts = GeneralUtility::makeInstance(TypoLinkCodecService::class)->decode($mixedLinkParameter);
5161
5162
        // Check for link-handler keyword
5163
        list($linkHandlerKeyword, $linkHandlerValue) = explode(':', $linkParameterParts['url'], 2);
5164
        if ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['typolinkLinkHandler'][$linkHandlerKeyword] && (string)$linkHandlerValue !== '') {
5165
            $linkHandlerObj = GeneralUtility::makeInstance($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['typolinkLinkHandler'][$linkHandlerKeyword]);
5166
            if (method_exists($linkHandlerObj, 'main')) {
5167
                return $linkHandlerObj->main($linkText, $configuration, $linkHandlerKeyword, $linkHandlerValue, $mixedLinkParameter, $this);
5168
            }
5169
        }
5170
5171
        // Resolve FAL-api "file:UID-of-sys_file-record" and "file:combined-identifier"
5172
        if ($linkHandlerKeyword === 'file' && strpos($linkParameterParts['url'], 'file://') !== 0) {
5173
            try {
5174
                $fileOrFolderObject = $this->getResourceFactory()->retrieveFileOrFolderObject($linkHandlerValue);
5175
                // Link to a folder or file
5176
                if ($fileOrFolderObject instanceof File || $fileOrFolderObject instanceof Folder) {
5177
                    $linkParameter = $fileOrFolderObject->getPublicUrl();
5178
                } else {
5179
                    $linkParameter = null;
5180
                }
5181
            } catch (\RuntimeException $e) {
5182
                // Element wasn't found
5183
                $linkParameter = null;
5184
            } catch (ResourceDoesNotExistException $e) {
5185
                // Resource was not found
5186
                return $linkText;
5187
            }
5188
        } elseif (in_array(strtolower(trim($linkHandlerKeyword)), ['javascript', 'data'], true)) {
5189
            // Disallow direct javascript: or data: links
5190
            return $linkText;
5191
        } else {
5192
            $linkParameter = $linkParameterParts['url'];
5193
        }
5194
5195
        // additional parameters that need to be set
5196
        if ($linkParameterParts['additionalParams'] !== '') {
5197
            $forceParams = $linkParameterParts['additionalParams'];
5198
            // params value
5199
            $configuration['additionalParams'] .= $forceParams[0] === '&' ? $forceParams : '&' . $forceParams;
5200
        }
5201
5202
        return [
5203
            'href'   => $linkParameter,
5204
            'target' => $linkParameterParts['target'],
5205
            'class'  => $linkParameterParts['class'],
5206
            'title'  => $linkParameterParts['title']
5207
        ];
5208
    }
5209
5210
    /**
5211
     * Implements the "typolink" property of stdWrap (and others)
5212
     * Basically the input string, $linktext, is (typically) wrapped in a <a>-tag linking to some page, email address, file or URL based on a parameter defined by the configuration array $conf.
5213
     * This function is best used from internal functions as is. There are some API functions defined after this function which is more suited for general usage in external applications.
5214
     * Generally the concept "typolink" should be used in your own applications as an API for making links to pages with parameters and more. The reason for this is that you will then automatically make links compatible with all the centralized functions for URL simulation and manipulation of parameters into hashes and more.
5215
     * For many more details on the parameters and how they are interpreted, please see the link to TSref below.
5216
     *
5217
     * the FAL API is handled with the namespace/prefix "file:..."
5218
     *
5219
     * @param string $linkText The string (text) to link
5220
     * @param array $conf TypoScript configuration (see link below)
5221
     * @return string A link-wrapped string.
5222
     * @see stdWrap(), \TYPO3\CMS\Frontend\Plugin\AbstractPlugin::pi_linkTP()
5223
     */
5224
    public function typoLink($linkText, $conf)
5225
    {
5226
        $linkText = (string)$linkText;
5227
        $tsfe = $this->getTypoScriptFrontendController();
5228
5229
        $linkParameter = trim(isset($conf['parameter.']) ? $this->stdWrap($conf['parameter'], $conf['parameter.']) : $conf['parameter']);
5230
        $this->lastTypoLinkUrl = '';
5231
        $this->lastTypoLinkTarget = '';
5232
5233
        $resolvedLinkParameters = $this->resolveMixedLinkParameter($linkText, $linkParameter, $conf);
5234
        // check if the link handler hook has resolved the link completely already
5235
        if (!is_array($resolvedLinkParameters)) {
5236
            return $resolvedLinkParameters;
5237
        }
5238
        $linkParameter = $resolvedLinkParameters['href'];
5239
        $target = $resolvedLinkParameters['target'];
5240
        $title = $resolvedLinkParameters['title'];
5241
5242
        if (!$linkParameter) {
5243
            return $linkText;
5244
        }
5245
5246
        // Detecting kind of link and resolve all necessary parameters
5247
        $linkService = GeneralUtility::makeInstance(LinkService::class);
5248
        $linkDetails = $linkService->resolve($linkParameter);
5249
        $linkDetails['typoLinkParameter'] = $linkParameter;
5250
        if (isset($linkDetails['type']) && isset($GLOBALS['TYPO3_CONF_VARS']['FE']['typolinkBuilder'][$linkDetails['type']])) {
5251
            /** @var AbstractTypolinkBuilder $linkBuilder */
5252
            $linkBuilder = GeneralUtility::makeInstance(
5253
                $GLOBALS['TYPO3_CONF_VARS']['FE']['typolinkBuilder'][$linkDetails['type']],
5254
                $this
5255
            );
5256
            try {
5257
                list($this->lastTypoLinkUrl, $linkText, $target) = $linkBuilder->build($linkDetails, $linkText, $target, $conf);
5258
            } catch (UnableToLinkException $e) {
5259
                // Only return the link text directly
5260
                return $e->getLinkText();
5261
            }
5262
        } elseif (isset($linkDetails['url'])) {
5263
            $this->lastTypoLinkUrl = $linkDetails['url'];
5264
        } else {
5265
            return $linkText;
5266
        }
5267
5268
        $finalTagParts = [
5269
            'aTagParams' => $this->getATagParams($conf) . $this->extLinkATagParams($this->lastTypoLinkUrl, $linkDetails['type']),
5270
            'url'        => $this->lastTypoLinkUrl,
5271
            'TYPE'       => $linkDetails['type']
5272
        ];
5273
5274
        // Ensure "href" is not in the list of aTagParams to avoid double tags, usually happens within buggy parseFunc settings
5275
        if (!empty($finalTagParts['aTagParams'])) {
5276
            $aTagParams = GeneralUtility::get_tag_attributes($finalTagParts['aTagParams']);
5277
            if (isset($aTagParams['href'])) {
5278
                unset($aTagParams['href']);
5279
                $finalTagParts['aTagParams'] = GeneralUtility::implodeAttributes($aTagParams);
5280
            }
5281
        }
5282
5283
        // Building the final <a href=".."> tag
5284
        $tagAttributes = [];
5285
5286
        // Title attribute
5287
        if (empty($title)) {
5288
            $title = $conf['title'];
5289
            if ($conf['title.']) {
5290
                $title = $this->stdWrap($title, $conf['title.']);
5291
            }
5292
        }
5293
5294
        // Check, if the target is coded as a JS open window link:
5295
        $JSwindowParts = [];
5296
        $JSwindowParams = '';
5297
        if ($target && preg_match('/^([0-9]+)x([0-9]+)(:(.*)|.*)$/', $target, $JSwindowParts)) {
5298
            // Take all pre-configured and inserted parameters and compile parameter list, including width+height:
5299
            $JSwindow_tempParamsArr = GeneralUtility::trimExplode(',', strtolower($conf['JSwindow_params'] . ',' . $JSwindowParts[4]), true);
5300
            $JSwindow_paramsArr = [];
5301
            foreach ($JSwindow_tempParamsArr as $JSv) {
5302
                list($JSp, $JSv) = explode('=', $JSv, 2);
5303
                $JSwindow_paramsArr[$JSp] = $JSp . '=' . $JSv;
5304
            }
5305
            // Add width/height:
5306
            $JSwindow_paramsArr['width'] = 'width=' . $JSwindowParts[1];
5307
            $JSwindow_paramsArr['height'] = 'height=' . $JSwindowParts[2];
5308
            // Imploding into string:
5309
            $JSwindowParams = implode(',', $JSwindow_paramsArr);
5310
        }
5311
        if (!$JSwindowParams && $linkDetails['type'] === LinkService::TYPE_EMAIL && $tsfe->spamProtectEmailAddresses === 'ascii') {
5312
            $tagAttributes['href'] = $finalTagParts['url'];
5313
        } else {
5314
            $tagAttributes['href'] = htmlspecialchars($finalTagParts['url']);
5315
        }
5316
        if (!empty($title)) {
5317
            $tagAttributes['title'] = htmlspecialchars($title);
5318
        }
5319
5320
        // Target attribute
5321
        if (!empty($target)) {
5322
            $tagAttributes['target'] = htmlspecialchars($target);
5323
        } elseif ($JSwindowParams && !in_array($tsfe->xhtmlDoctype, ['xhtml_strict', 'xhtml_11'], true)) {
5324
            // Create TARGET-attribute only if the right doctype is used
5325
            $tagAttributes['target'] = 'FEopenLink';
5326
        }
5327
5328
        if ($JSwindowParams) {
5329
            $onClick = 'vHWin=window.open(' . GeneralUtility::quoteJSvalue($tsfe->baseUrlWrap($finalTagParts['url'])) . ',\'FEopenLink\',' . GeneralUtility::quoteJSvalue($JSwindowParams) . ');vHWin.focus();return false;';
5330
            $tagAttributes['onclick'] = htmlspecialchars($onClick);
5331
        }
5332
5333
        if (!empty($resolvedLinkParameters['class'])) {
5334
            $tagAttributes['class'] = htmlspecialchars($resolvedLinkParameters['class']);
5335
        }
5336
5337
        // Prevent trouble with double and missing spaces between attributes and merge params before implode
5338
        $finalTagAttributes = array_merge($tagAttributes, GeneralUtility::get_tag_attributes($finalTagParts['aTagParams']));
5339
        $finalAnchorTag = '<a ' . GeneralUtility::implodeAttributes($finalTagAttributes) . '>';
5340
5341
        if (!empty($finalTagParts['aTagParams'])) {
5342
            $tagAttributes = array_merge($tagAttributes, GeneralUtility::get_tag_attributes($finalTagParts['aTagParams']));
5343
        }
5344
        // kept for backwards-compatibility in hooks
5345
        $finalTagParts['targetParams'] = !empty($tagAttributes['target']) ? ' target="' . $tagAttributes['target'] . '"' : '';
5346
        $this->lastTypoLinkTarget = $target;
5347
5348
        // Call user function:
5349
        if ($conf['userFunc']) {
5350
            $finalTagParts['TAG'] = $finalAnchorTag;
5351
            $finalAnchorTag = $this->callUserFunction($conf['userFunc'], $conf['userFunc.'], $finalTagParts);
5352
        }
5353
5354
        // Hook: Call post processing function for link rendering:
5355
        $_params = [
5356
            'conf' => &$conf,
5357
            'linktxt' => &$linkText,
5358
            'finalTag' => &$finalAnchorTag,
5359
            'finalTagParts' => &$finalTagParts,
5360
            'linkDetails' => &$linkDetails,
5361
            'tagAttributes' => &$tagAttributes
5362
        ];
5363
        foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['typoLink_PostProc'] ?? [] as $_funcRef) {
5364
            GeneralUtility::callUserFunction($_funcRef, $_params, $this);
5365
        }
5366
5367
        // If flag "returnLastTypoLinkUrl" set, then just return the latest URL made:
5368
        if ($conf['returnLast']) {
5369
            switch ($conf['returnLast']) {
5370
                case 'url':
5371
                    return $this->lastTypoLinkUrl;
5372
                    break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
5373
                case 'target':
5374
                    return $this->lastTypoLinkTarget;
5375
                    break;
5376
            }
5377
        }
5378
5379
        $wrap = isset($conf['wrap.']) ? $this->stdWrap($conf['wrap'], $conf['wrap.']) : $conf['wrap'];
5380
5381
        if ($conf['ATagBeforeWrap']) {
5382
            return $finalAnchorTag . $this->wrap($linkText, $wrap) . '</a>';
5383
        }
5384
        return $this->wrap($finalAnchorTag . $linkText . '</a>', $wrap);
5385
    }
5386
5387
    /**
5388
     * Based on the input "TypoLink" TypoScript configuration this will return the generated URL
5389
     *
5390
     * @param array $conf TypoScript properties for "typolink
5391
     * @return string The URL of the link-tag that typolink() would by itself return
5392
     * @see typoLink()
5393
     */
5394
    public function typoLink_URL($conf)
5395
    {
5396
        $this->typoLink('|', $conf);
5397
        return $this->lastTypoLinkUrl;
5398
    }
5399
5400
    /**
5401
     * Returns a linked string made from typoLink parameters.
5402
     *
5403
     * This function takes $label as a string, wraps it in a link-tag based on the $params string, which should contain data like that you would normally pass to the popular <LINK>-tag in the TSFE.
5404
     * Optionally you can supply $urlParameters which is an array with key/value pairs that are rawurlencoded and appended to the resulting url.
5405
     *
5406
     * @param string $label Text string being wrapped by the link.
5407
     * @param string $params Link parameter; eg. "123" for page id, "[email protected]" for email address, "http://...." for URL, "fileadmin/blabla.txt" for file.
5408
     * @param array|string $urlParameters As an array key/value pairs represent URL parameters to set. Values NOT URL-encoded yet, keys should be URL-encoded if needed. As a string the parameter is expected to be URL-encoded already.
5409
     * @param string $target Specific target set, if any. (Default is using the current)
5410
     * @return string The wrapped $label-text string
5411
     * @see getTypoLink_URL()
5412
     */
5413
    public function getTypoLink($label, $params, $urlParameters = [], $target = '')
5414
    {
5415
        $conf = [];
5416
        $conf['parameter'] = $params;
5417
        if ($target) {
5418
            $conf['target'] = $target;
5419
            $conf['extTarget'] = $target;
5420
            $conf['fileTarget'] = $target;
5421
        }
5422
        if (is_array($urlParameters)) {
5423
            if (!empty($urlParameters)) {
5424
                $conf['additionalParams'] .= GeneralUtility::implodeArrayForUrl('', $urlParameters);
5425
            }
5426
        } else {
5427
            $conf['additionalParams'] .= $urlParameters;
5428
        }
5429
        $out = $this->typoLink($label, $conf);
5430
        return $out;
5431
    }
5432
5433
    /**
5434
     * Returns the canonical URL to the current "location", which include the current page ID and type
5435
     * and optionally the query string
5436
     *
5437
     * @param bool $addQueryString Whether additional GET arguments in the query string should be included or not
5438
     * @return string
5439
     */
5440
    public function getUrlToCurrentLocation($addQueryString = true)
5441
    {
5442
        $conf = [];
5443
        $conf['parameter'] = $this->getTypoScriptFrontendController()->id . ',' . $this->getTypoScriptFrontendController()->type;
5444
        if ($addQueryString) {
5445
            $conf['addQueryString'] = '1';
5446
            $linkVars = implode(',', array_keys(GeneralUtility::explodeUrl2Array($this->getTypoScriptFrontendController()->linkVars)));
5447
            $conf['addQueryString.'] = [
5448
                'method' => 'GET',
5449
                'exclude' => 'id,type,cHash' . ($linkVars ? ',' . $linkVars : '')
5450
            ];
5451
            $conf['useCacheHash'] = GeneralUtility::_GET('cHash') ? '1' : '0';
5452
        }
5453
5454
        return $this->typoLink_URL($conf);
5455
    }
5456
5457
    /**
5458
     * Returns the URL of a "typolink" create from the input parameter string, url-parameters and target
5459
     *
5460
     * @param string $params Link parameter; eg. "123" for page id, "[email protected]" for email address, "http://...." for URL, "fileadmin/blabla.txt" for file.
5461
     * @param array|string $urlParameters As an array key/value pairs represent URL parameters to set. Values NOT URL-encoded yet, keys should be URL-encoded if needed. As a string the parameter is expected to be URL-encoded already.
5462
     * @param string $target Specific target set, if any. (Default is using the current)
5463
     * @return string The URL
5464
     * @see getTypoLink()
5465
     */
5466
    public function getTypoLink_URL($params, $urlParameters = [], $target = '')
5467
    {
5468
        $this->getTypoLink('', $params, $urlParameters, $target);
5469
        return $this->lastTypoLinkUrl;
5470
    }
5471
5472
    /**
5473
     * Generates a typolink and returns the two link tags - start and stop - in an array
5474
     *
5475
     * @param array $conf "typolink" TypoScript properties
5476
     * @return array An array with two values in key 0+1, each value being the start and close <a>-tag of the typolink properties being inputted in $conf
5477
     * @see typolink()
5478
     */
5479
    public function typolinkWrap($conf)
5480
    {
5481
        $k = md5(microtime());
5482
        return explode($k, $this->typoLink($k, $conf));
5483
    }
5484
5485
    /**
5486
     * Returns the current page URL
5487
     *
5488
     * @param array|string $urlParameters As an array key/value pairs represent URL parameters to set. Values NOT URL-encoded yet, keys should be URL-encoded if needed. As a string the parameter is expected to be URL-encoded already.
5489
     * @param int $id An alternative ID to the current id ($GLOBALS['TSFE']->id)
5490
     * @return string The URL
5491
     * @see getTypoLink_URL()
5492
     */
5493
    public function currentPageUrl($urlParameters = [], $id = 0)
5494
    {
5495
        $tsfe = $this->getTypoScriptFrontendController();
5496
        return $this->getTypoLink_URL($id ?: $tsfe->id, $urlParameters, $tsfe->sPre);
5497
    }
5498
5499
    /**
5500
     * Loops over all configured URL modifier hooks (if available) and returns the generated URL or NULL if no URL was generated.
5501
     *
5502
     * @param string $context The context in which the method is called (e.g. typoLink).
5503
     * @param string $url The URL that should be processed.
5504
     * @param array $typolinkConfiguration The current link configuration array.
5505
     * @return string|null Returns NULL if URL was not processed or the processed URL as a string.
5506
     * @throws \RuntimeException if a hook was registered but did not fulfill the correct parameters.
5507
     */
5508 View Code Duplication
    protected function processUrl($context, $url, $typolinkConfiguration = [])
5509
    {
5510
        $urlProcessors = $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['urlProcessing']['urlProcessors'] ?? [];
5511
        if (empty($urlProcessors)) {
5512
            return $url;
5513
        }
5514
5515
        foreach ($urlProcessors as $identifier => $configuration) {
5516
            if (empty($configuration) || !is_array($configuration)) {
5517
                throw new \RuntimeException('Missing configuration for URI processor "' . $identifier . '".', 1442050529);
5518
            }
5519
            if (!is_string($configuration['processor']) || empty($configuration['processor']) || !class_exists($configuration['processor']) || !is_subclass_of($configuration['processor'], UrlProcessorInterface::class)) {
5520
                throw new \RuntimeException('The URI processor "' . $identifier . '" defines an invalid provider. Ensure the class exists and implements the "' . UrlProcessorInterface::class . '".', 1442050579);
5521
            }
5522
        }
5523
5524
        $orderedProcessors = GeneralUtility::makeInstance(DependencyOrderingService::class)->orderByDependencies($urlProcessors);
5525
        $keepProcessing = true;
5526
5527
        foreach ($orderedProcessors as $configuration) {
5528
            /** @var UrlProcessorInterface $urlProcessor */
5529
            $urlProcessor = GeneralUtility::makeInstance($configuration['processor']);
5530
            $url = $urlProcessor->process($context, $url, $typolinkConfiguration, $this, $keepProcessing);
5531
            if (!$keepProcessing) {
5532
                break;
5533
            }
5534
        }
5535
5536
        return $url;
5537
    }
5538
5539
    /**
5540
     * Creates a href attibute for given $mailAddress.
5541
     * The function uses spamProtectEmailAddresses for encoding the mailto statement.
5542
     * If spamProtectEmailAddresses is disabled, it'll just return a string like "mailto:[email protected]".
5543
     *
5544
     * @param string $mailAddress Email address
5545
     * @param string $linktxt Link text, default will be the email address.
5546
     * @return string Returns a numerical array with two elements: 1) $mailToUrl, string ready to be inserted into the href attribute of the <a> tag, b) $linktxt: The string between starting and ending <a> tag.
5547
     */
5548
    public function getMailTo($mailAddress, $linktxt)
5549
    {
5550
        $mailAddress = (string)$mailAddress;
5551
        if ((string)$linktxt === '') {
5552
            $linktxt = htmlspecialchars($mailAddress);
5553
        }
5554
5555
        $originalMailToUrl = 'mailto:' . $mailAddress;
5556
        $mailToUrl = $this->processUrl(UrlProcessorInterface::CONTEXT_MAIL, $originalMailToUrl);
5557
5558
        // no processing happened, therefore, the default processing kicks in
5559
        if ($mailToUrl === $originalMailToUrl) {
5560
            $tsfe = $this->getTypoScriptFrontendController();
5561
            if ($tsfe->spamProtectEmailAddresses) {
5562
                $mailToUrl = $this->encryptEmail($mailToUrl, $tsfe->spamProtectEmailAddresses);
5563
                if ($tsfe->spamProtectEmailAddresses !== 'ascii') {
5564
                    $mailToUrl = 'javascript:linkTo_UnCryptMailto(' . GeneralUtility::quoteJSvalue($mailToUrl) . ');';
5565
                }
5566
                $atLabel = trim($tsfe->config['config']['spamProtectEmailAddresses_atSubst']) ?: '(at)';
5567
                $spamProtectedMailAddress = str_replace('@', $atLabel, htmlspecialchars($mailAddress));
5568
                if ($tsfe->config['config']['spamProtectEmailAddresses_lastDotSubst']) {
5569
                    $lastDotLabel = trim($tsfe->config['config']['spamProtectEmailAddresses_lastDotSubst']);
5570
                    $lastDotLabel = $lastDotLabel ? $lastDotLabel : '(dot)';
5571
                    $spamProtectedMailAddress = preg_replace('/\\.([^\\.]+)$/', $lastDotLabel . '$1', $spamProtectedMailAddress);
5572
                }
5573
                $linktxt = str_ireplace($mailAddress, $spamProtectedMailAddress, $linktxt);
5574
            }
5575
        }
5576
5577
        return [$mailToUrl, $linktxt];
5578
    }
5579
5580
    /**
5581
     * Encryption of email addresses for <A>-tags See the spam protection setup in TS 'config.'
5582
     *
5583
     * @param string $string Input string to en/decode: "mailto:[email protected]
5584
     * @param mixed  $type - either "ascii" or a number between -10 and 10, taken from config.spamProtectEmailAddresses
5585
     * @return string encoded version of $string
5586
     */
5587 View Code Duplication
    protected function encryptEmail($string, $type)
5588
    {
5589
        $out = '';
5590
        // obfuscates using the decimal HTML entity references for each character
5591
        if ($type === 'ascii') {
5592
            $stringLength = strlen($string);
5593
            for ($a = 0; $a < $stringLength; $a++) {
5594
                $out .= '&#' . ord(substr($string, $a, 1)) . ';';
0 ignored issues
show
Bug introduced by
It seems like substr($string, $a, 1) can also be of type false; however, parameter $string of ord() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

5594
                $out .= '&#' . ord(/** @scrutinizer ignore-type */ substr($string, $a, 1)) . ';';
Loading history...
5595
            }
5596
        } else {
5597
            // like str_rot13() but with a variable offset and a wider character range
5598
            $len = strlen($string);
5599
            $offset = (int)$type;
5600
            for ($i = 0; $i < $len; $i++) {
5601
                $charValue = ord($string[$i]);
5602
                // 0-9 . , - + / :
5603
                if ($charValue >= 43 && $charValue <= 58) {
5604
                    $out .= $this->encryptCharcode($charValue, 43, 58, $offset);
5605
                } elseif ($charValue >= 64 && $charValue <= 90) {
5606
                    // A-Z @
5607
                    $out .= $this->encryptCharcode($charValue, 64, 90, $offset);
5608
                } elseif ($charValue >= 97 && $charValue <= 122) {
5609
                    // a-z
5610
                    $out .= $this->encryptCharcode($charValue, 97, 122, $offset);
5611
                } else {
5612
                    $out .= $string[$i];
5613
                }
5614
            }
5615
        }
5616
        return $out;
5617
    }
5618
5619
    /**
5620
     * Decryption of email addresses for <A>-tags See the spam protection setup in TS 'config.'
5621
     *
5622
     * @param string $string Input string to en/decode: "mailto:[email protected]
5623
     * @param mixed  $type - either "ascii" or a number between -10 and 10 taken from config.spamProtectEmailAddresses
5624
     * @return string decoded version of $string
5625
     */
5626 View Code Duplication
    protected function decryptEmail($string, $type)
5627
    {
5628
        $out = '';
5629
        // obfuscates using the decimal HTML entity references for each character
5630
        if ($type === 'ascii') {
5631
            $stringLength = strlen($string);
5632
            for ($a = 0; $a < $stringLength; $a++) {
5633
                $out .= '&#' . ord(substr($string, $a, 1)) . ';';
0 ignored issues
show
Bug introduced by
It seems like substr($string, $a, 1) can also be of type false; however, parameter $string of ord() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

5633
                $out .= '&#' . ord(/** @scrutinizer ignore-type */ substr($string, $a, 1)) . ';';
Loading history...
5634
            }
5635
        } else {
5636
            // like str_rot13() but with a variable offset and a wider character range
5637
            $len = strlen($string);
5638
            $offset = (int)$type * -1;
5639
            for ($i = 0; $i < $len; $i++) {
5640
                $charValue = ord($string[$i]);
5641
                // 0-9 . , - + / :
5642
                if ($charValue >= 43 && $charValue <= 58) {
5643
                    $out .= $this->encryptCharcode($charValue, 43, 58, $offset);
5644
                } elseif ($charValue >= 64 && $charValue <= 90) {
5645
                    // A-Z @
5646
                    $out .= $this->encryptCharcode($charValue, 64, 90, $offset);
5647
                } elseif ($charValue >= 97 && $charValue <= 122) {
5648
                    // a-z
5649
                    $out .= $this->encryptCharcode($charValue, 97, 122, $offset);
5650
                } else {
5651
                    $out .= $string[$i];
5652
                }
5653
            }
5654
        }
5655
        return $out;
5656
    }
5657
5658
    /**
5659
     * Encryption (or decryption) of a single character.
5660
     * Within the given range the character is shifted with the supplied offset.
5661
     *
5662
     * @param int $n Ordinal of input character
5663
     * @param int $start Start of range
5664
     * @param int $end End of range
5665
     * @param int $offset Offset
5666
     * @return string encoded/decoded version of character
5667
     */
5668
    protected function encryptCharcode($n, $start, $end, $offset)
5669
    {
5670
        $n = $n + $offset;
5671
        if ($offset > 0 && $n > $end) {
5672
            $n = $start + ($n - $end - 1);
5673
        } elseif ($offset < 0 && $n < $start) {
5674
            $n = $end - ($start - $n - 1);
5675
        }
5676
        return chr($n);
5677
    }
5678
5679
    /**
5680
     * Gets the query arguments and assembles them for URLs.
5681
     * Arguments may be removed or set, depending on configuration.
5682
     *
5683
     * @param array $conf Configuration
5684
     * @param array $overruleQueryArguments Multidimensional key/value pairs that overrule incoming query arguments
5685
     * @param bool $forceOverruleArguments If set, key/value pairs not in the query but the overrule array will be set
5686
     * @return string The URL query part (starting with a &)
5687
     */
5688
    public function getQueryArguments($conf, $overruleQueryArguments = [], $forceOverruleArguments = false)
5689
    {
5690
        switch ((string)$conf['method']) {
5691
            case 'GET':
5692
                $currentQueryArray = GeneralUtility::_GET();
5693
                break;
5694
            case 'POST':
5695
                $currentQueryArray = GeneralUtility::_POST();
5696
                break;
5697
            case 'GET,POST':
5698
                $currentQueryArray = GeneralUtility::_GET();
5699
                ArrayUtility::mergeRecursiveWithOverrule($currentQueryArray, GeneralUtility::_POST());
0 ignored issues
show
Bug introduced by
It seems like TYPO3\CMS\Core\Utility\GeneralUtility::_POST() can also be of type string; however, parameter $overrule of TYPO3\CMS\Core\Utility\A...RecursiveWithOverrule() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

5699
                ArrayUtility::mergeRecursiveWithOverrule($currentQueryArray, /** @scrutinizer ignore-type */ GeneralUtility::_POST());
Loading history...
Bug introduced by
It seems like $currentQueryArray can also be of type string; however, parameter $original of TYPO3\CMS\Core\Utility\A...RecursiveWithOverrule() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

5699
                ArrayUtility::mergeRecursiveWithOverrule(/** @scrutinizer ignore-type */ $currentQueryArray, GeneralUtility::_POST());
Loading history...
5700
                break;
5701
            case 'POST,GET':
5702
                $currentQueryArray = GeneralUtility::_POST();
5703
                ArrayUtility::mergeRecursiveWithOverrule($currentQueryArray, GeneralUtility::_GET());
5704
                break;
5705
            default:
5706
                $currentQueryArray = GeneralUtility::explodeUrl2Array($this->getEnvironmentVariable('QUERY_STRING'), true);
5707
        }
5708
        if ($conf['exclude']) {
5709
            $exclude = str_replace(',', '&', $conf['exclude']);
5710
            $exclude = GeneralUtility::explodeUrl2Array($exclude, true);
5711
            // never repeat id
5712
            $exclude['id'] = 0;
5713
            $newQueryArray = ArrayUtility::arrayDiffAssocRecursive($currentQueryArray, $exclude);
0 ignored issues
show
Bug introduced by
It seems like $currentQueryArray can also be of type string; however, parameter $array1 of TYPO3\CMS\Core\Utility\A...rayDiffAssocRecursive() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

5713
            $newQueryArray = ArrayUtility::arrayDiffAssocRecursive(/** @scrutinizer ignore-type */ $currentQueryArray, $exclude);
Loading history...
5714
        } else {
5715
            $newQueryArray = $currentQueryArray;
5716
        }
5717
        if ($forceOverruleArguments) {
5718
            ArrayUtility::mergeRecursiveWithOverrule($newQueryArray, $overruleQueryArguments);
5719
        } else {
5720
            ArrayUtility::mergeRecursiveWithOverrule($newQueryArray, $overruleQueryArguments, false);
5721
        }
5722
        return GeneralUtility::implodeArrayForUrl('', $newQueryArray, '', false, true);
0 ignored issues
show
Bug introduced by
It seems like $newQueryArray can also be of type string; however, parameter $theArray of TYPO3\CMS\Core\Utility\G...y::implodeArrayForUrl() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

5722
        return GeneralUtility::implodeArrayForUrl('', /** @scrutinizer ignore-type */ $newQueryArray, '', false, true);
Loading history...
5723
    }
5724
5725
    /***********************************************
5726
     *
5727
     * Miscellaneous functions, stand alone
5728
     *
5729
     ***********************************************/
5730
    /**
5731
     * Wrapping a string.
5732
     * Implements the TypoScript "wrap" property.
5733
     * Example: $content = "HELLO WORLD" and $wrap = "<strong> | </strong>", result: "<strong>HELLO WORLD</strong>"
5734
     *
5735
     * @param string $content The content to wrap
5736
     * @param string $wrap The wrap value, eg. "<strong> | </strong>
5737
     * @param string $char The char used to split the wrapping value, default is "|
5738
     * @return string Wrapped input string
5739
     * @see noTrimWrap()
5740
     */
5741 View Code Duplication
    public function wrap($content, $wrap, $char = '|')
5742
    {
5743
        if ($wrap) {
5744
            $wrapArr = explode($char, $wrap);
5745
            $content = trim($wrapArr[0]) . $content . trim($wrapArr[1]);
5746
        }
5747
        return $content;
5748
    }
5749
5750
    /**
5751
     * Wrapping a string, preserving whitespace in wrap value.
5752
     * Notice that the wrap value uses part 1/2 to wrap (and not 0/1 which wrap() does)
5753
     *
5754
     * @param string $content The content to wrap, eg. "HELLO WORLD
5755
     * @param string $wrap The wrap value, eg. " | <strong> | </strong>
5756
     * @param string $char The char used to split the wrapping value, default is "|"
5757
     * @return string Wrapped input string, eg. " <strong> HELLO WORD </strong>
5758
     * @see wrap()
5759
     */
5760 View Code Duplication
    public function noTrimWrap($content, $wrap, $char = '|')
5761
    {
5762
        if ($wrap) {
5763
            // expects to be wrapped with (at least) 3 characters (before, middle, after)
5764
            // anything else is not taken into account
5765
            $wrapArr = explode($char, $wrap, 4);
5766
            $content = $wrapArr[1] . $content . $wrapArr[2];
5767
        }
5768
        return $content;
5769
    }
5770
5771
    /**
5772
     * Calling a user function/class-method
5773
     * Notice: For classes the instantiated object will have the internal variable, $cObj, set to be a *reference* to $this (the parent/calling object).
5774
     *
5775
     * @param string $funcName The functionname, eg "user_myfunction" or "user_myclass->main". Notice that there are rules for the names of functions/classes you can instantiate. If a function cannot be called for some reason it will be seen in the TypoScript log in the AdminPanel.
5776
     * @param array $conf The TypoScript configuration to pass the function
5777
     * @param string $content The content string to pass the function
5778
     * @return string The return content from the function call. Should probably be a string.
5779
     * @see USER(), stdWrap(), typoLink(), _parseFunc()
5780
     */
5781
    public function callUserFunction($funcName, $conf, $content)
5782
    {
5783
        // Split parts
5784
        $parts = explode('->', $funcName);
5785
        if (count($parts) === 2) {
5786
            // Check whether PHP class is available
5787
            if (class_exists($parts[0])) {
5788
                $classObj = GeneralUtility::makeInstance($parts[0]);
5789
                if (is_object($classObj) && method_exists($classObj, $parts[1])) {
5790
                    $classObj->cObj = $this;
5791
                    $content = call_user_func_array([
5792
                        $classObj,
5793
                        $parts[1]
5794
                    ], [
5795
                        $content,
5796
                        $conf
5797
                    ]);
5798
                } else {
5799
                    $this->getTimeTracker()->setTSlogMessage('Method "' . $parts[1] . '" did not exist in class "' . $parts[0] . '"', 3);
5800
                }
5801
            } else {
5802
                $this->getTimeTracker()->setTSlogMessage('Class "' . $parts[0] . '" did not exist', 3);
5803
            }
5804
        } elseif (function_exists($funcName)) {
5805
            $content = call_user_func($funcName, $content, $conf);
5806
        } else {
5807
            $this->getTimeTracker()->setTSlogMessage('Function "' . $funcName . '" did not exist', 3);
5808
        }
5809
        return $content;
5810
    }
5811
5812
    /**
5813
     * Cleans up a string of keywords. Keywords at splitted by "," (comma)  ";" (semi colon) and linebreak
5814
     *
5815
     * @param string $content String of keywords
5816
     * @return string Cleaned up string, keywords will be separated by a comma only.
5817
     */
5818
    public function keywords($content)
5819
    {
5820
        $listArr = preg_split('/[,;' . LF . ']/', $content);
5821
        foreach ($listArr as $k => $v) {
5822
            $listArr[$k] = trim($v);
5823
        }
5824
        return implode(',', $listArr);
0 ignored issues
show
Bug introduced by
It seems like $listArr can also be of type false; however, parameter $pieces of implode() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

5824
        return implode(',', /** @scrutinizer ignore-type */ $listArr);
Loading history...
5825
    }
5826
5827
    /**
5828
     * Changing character case of a string, converting typically used western charset characters as well.
5829
     *
5830
     * @param string $theValue The string to change case for.
5831
     * @param string $case The direction; either "upper" or "lower
5832
     * @return string
5833
     * @see HTMLcaseshift()
5834
     */
5835
    public function caseshift($theValue, $case)
5836
    {
5837
        switch (strtolower($case)) {
5838
            case 'upper':
5839
                $theValue = mb_strtoupper($theValue, 'utf-8');
5840
                break;
5841
            case 'lower':
5842
                $theValue = mb_strtolower($theValue, 'utf-8');
5843
                break;
5844
            case 'capitalize':
5845
                $theValue = mb_convert_case($theValue, MB_CASE_TITLE, 'utf-8');
5846
                break;
5847 View Code Duplication
            case 'ucfirst':
5848
                $firstChar = mb_substr($theValue, 0, 1, 'utf-8');
5849
                $firstChar = mb_strtoupper($firstChar, 'utf-8');
5850
                $remainder = mb_substr($theValue, 1, null, 'utf-8');
5851
                $theValue = $firstChar . $remainder;
5852
                break;
5853 View Code Duplication
            case 'lcfirst':
5854
                $firstChar = mb_substr($theValue, 0, 1, 'utf-8');
5855
                $firstChar = mb_strtolower($firstChar, 'utf-8');
5856
                $remainder = mb_substr($theValue, 1, null, 'utf-8');
5857
                $theValue = $firstChar . $remainder;
5858
                break;
5859
            case 'uppercamelcase':
5860
                $theValue = GeneralUtility::underscoredToUpperCamelCase($theValue);
5861
                break;
5862
            case 'lowercamelcase':
5863
                $theValue = GeneralUtility::underscoredToLowerCamelCase($theValue);
5864
                break;
5865
        }
5866
        return $theValue;
5867
    }
5868
5869
    /**
5870
     * Shifts the case of characters outside of HTML tags in the input string
5871
     *
5872
     * @param string $theValue The string to change case for.
5873
     * @param string $case The direction; either "upper" or "lower
5874
     * @return string
5875
     * @see caseshift()
5876
     */
5877
    public function HTMLcaseshift($theValue, $case)
5878
    {
5879
        $inside = 0;
5880
        $newVal = '';
5881
        $pointer = 0;
5882
        $totalLen = strlen($theValue);
5883
        do {
5884
            if (!$inside) {
5885
                $len = strcspn(substr($theValue, $pointer), '<');
0 ignored issues
show
Bug introduced by
It seems like substr($theValue, $pointer) can also be of type false; however, parameter $str1 of strcspn() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

5885
                $len = strcspn(/** @scrutinizer ignore-type */ substr($theValue, $pointer), '<');
Loading history...
5886
                $newVal .= $this->caseshift(substr($theValue, $pointer, $len), $case);
0 ignored issues
show
Bug introduced by
It seems like substr($theValue, $pointer, $len) can also be of type false; however, parameter $theValue of TYPO3\CMS\Frontend\Conte...ctRenderer::caseshift() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

5886
                $newVal .= $this->caseshift(/** @scrutinizer ignore-type */ substr($theValue, $pointer, $len), $case);
Loading history...
5887
                $inside = 1;
5888
            } else {
5889
                $len = strcspn(substr($theValue, $pointer), '>') + 1;
5890
                $newVal .= substr($theValue, $pointer, $len);
5891
                $inside = 0;
5892
            }
5893
            $pointer += $len;
5894
        } while ($pointer < $totalLen);
5895
        return $newVal;
5896
    }
5897
5898
    /**
5899
     * Returns the 'age' of the tstamp $seconds
5900
     *
5901
     * @param int $seconds Seconds to return age for. Example: "70" => "1 min", "3601" => "1 hrs
5902
     * @param string $labels The labels of the individual units. Defaults to : ' min| hrs| days| yrs'
5903
     * @return string The formatted string
5904
     */
5905
    public function calcAge($seconds, $labels)
5906
    {
5907
        if (MathUtility::canBeInterpretedAsInteger($labels)) {
5908
            $labels = ' min| hrs| days| yrs| min| hour| day| year';
5909
        } else {
5910
            $labels = str_replace('"', '', $labels);
5911
        }
5912
        $labelArr = explode('|', $labels);
5913
        if (count($labelArr) === 4) {
5914
            $labelArr = array_merge($labelArr, $labelArr);
5915
        }
5916
        $absSeconds = abs($seconds);
5917
        $sign = $seconds > 0 ? 1 : -1;
5918 View Code Duplication
        if ($absSeconds < 3600) {
5919
            $val = round($absSeconds / 60);
5920
            $seconds = $sign * $val . ($val == 1 ? $labelArr[4] : $labelArr[0]);
5921
        } elseif ($absSeconds < 24 * 3600) {
5922
            $val = round($absSeconds / 3600);
5923
            $seconds = $sign * $val . ($val == 1 ? $labelArr[5] : $labelArr[1]);
5924
        } elseif ($absSeconds < 365 * 24 * 3600) {
5925
            $val = round($absSeconds / (24 * 3600));
5926
            $seconds = $sign * $val . ($val == 1 ? $labelArr[6] : $labelArr[2]);
5927
        } else {
5928
            $val = round($absSeconds / (365 * 24 * 3600));
5929
            $seconds = $sign * $val . ($val == 1 ? $labelArr[7] : $labelArr[3]);
5930
        }
5931
        return $seconds;
5932
    }
5933
5934
    /**
5935
     * Sends a notification email
5936
     *
5937
     * @param string $message The message content. If blank, no email is sent.
5938
     * @param string $recipients Comma list of recipient email addresses
5939
     * @param string $cc Email address of recipient of an extra mail. The same mail will be sent ONCE more; not using a CC header but sending twice.
5940
     * @param string $senderAddress "From" email address
5941
     * @param string $senderName Optional "From" name
5942
     * @param string $replyTo Optional "Reply-To" header email address.
5943
     * @return bool Returns TRUE if sent
5944
     */
5945
    public function sendNotifyEmail($message, $recipients, $cc, $senderAddress, $senderName = '', $replyTo = '')
5946
    {
5947
        /** @var $mail MailMessage */
5948
        $mail = GeneralUtility::makeInstance(MailMessage::class);
5949
        $senderName = trim($senderName);
5950
        $senderAddress = trim($senderAddress);
5951
        if ($senderName !== '' && $senderAddress !== '') {
5952
            $mail->setFrom([$senderAddress => $senderName]);
5953
        } elseif ($senderAddress !== '') {
5954
            $mail->setFrom([$senderAddress]);
5955
        }
5956
        $parsedReplyTo = MailUtility::parseAddresses($replyTo);
5957
        if (!empty($parsedReplyTo)) {
5958
            $mail->setReplyTo($parsedReplyTo);
5959
        }
5960
        $message = trim($message);
5961
        if ($message !== '') {
5962
            // First line is subject
5963
            $messageParts = explode(LF, $message, 2);
5964
            $subject = trim($messageParts[0]);
5965
            $plainMessage = trim($messageParts[1]);
5966
            $parsedRecipients = MailUtility::parseAddresses($recipients);
5967
            if (!empty($parsedRecipients)) {
5968
                $mail->setTo($parsedRecipients)
5969
                    ->setSubject($subject)
5970
                    ->setBody($plainMessage);
5971
                $mail->send();
5972
            }
5973
            $parsedCc = MailUtility::parseAddresses($cc);
5974
            if (!empty($parsedCc)) {
5975
                $from = $mail->getFrom();
5976
                /** @var $mail MailMessage */
5977
                $mail = GeneralUtility::makeInstance(MailMessage::class);
5978
                if (!empty($parsedReplyTo)) {
5979
                    $mail->setReplyTo($parsedReplyTo);
5980
                }
5981
                $mail->setFrom($from)
5982
                    ->setTo($parsedCc)
5983
                    ->setSubject($subject)
5984
                    ->setBody($plainMessage);
5985
                $mail->send();
5986
            }
5987
            return true;
5988
        }
5989
        return false;
5990
    }
5991
5992
    /**
5993
     * Resolves a TypoScript reference value to the full set of properties BUT overridden with any local properties set.
5994
     * So the reference is resolved but overlaid with local TypoScript properties of the reference value.
5995
     *
5996
     * @param array $confArr The TypoScript array
5997
     * @param string $prop The property name: If this value is a reference (eg. " < plugins.tx_something") then the reference will be retrieved and inserted at that position (into the properties only, not the value...) AND overlaid with the old properties if any.
5998
     * @return array The modified TypoScript array
5999
     */
6000
    public function mergeTSRef($confArr, $prop)
6001
    {
6002
        if ($confArr[$prop][0] === '<') {
6003
            $key = trim(substr($confArr[$prop], 1));
0 ignored issues
show
Bug introduced by
It seems like substr($confArr[$prop], 1) can also be of type false; however, parameter $str of trim() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

6003
            $key = trim(/** @scrutinizer ignore-type */ substr($confArr[$prop], 1));
Loading history...
6004
            $cF = GeneralUtility::makeInstance(TypoScriptParser::class);
6005
            // $name and $conf is loaded with the referenced values.
6006
            $old_conf = $confArr[$prop . '.'];
6007
            list(, $conf) = $cF->getVal($key, $this->getTypoScriptFrontendController()->tmpl->setup);
6008
            if (is_array($old_conf) && !empty($old_conf)) {
6009
                $conf = is_array($conf) ? array_replace_recursive($conf, $old_conf) : $old_conf;
6010
            }
6011
            $confArr[$prop . '.'] = $conf;
6012
        }
6013
        return $confArr;
6014
    }
6015
6016
    /***********************************************
6017
     *
6018
     * Database functions, making of queries
6019
     *
6020
     ***********************************************/
6021
6022
    /**
6023
     * Returns a part of a WHERE clause which will filter out records with start/end times or hidden/fe_groups fields
6024
     * set to values that should de-select them according to the current time, preview settings or user login.
6025
     * Definitely a frontend function.
6026
     * THIS IS A VERY IMPORTANT FUNCTION: Basically you must add the output from this function for EVERY select query you create
6027
     * for selecting records of tables in your own applications - thus they will always be filtered according to the "enablefields"
6028
     * configured in TCA
6029
     * Simply calls \TYPO3\CMS\Frontend\Page\PageRepository::enableFields() BUT will send the show_hidden flag along!
6030
     * This means this function will work in conjunction with the preview facilities of the frontend engine/Admin Panel.
6031
     *
6032
     * @param string $table The table for which to get the where clause
6033
     * @param bool $show_hidden If set, then you want NOT to filter out hidden records. Otherwise hidden record are filtered based on the current preview settings.
6034
     * @param array $ignore_array Array you can pass where keys can be "disabled", "starttime", "endtime", "fe_group" (keys from "enablefields" in TCA) and if set they will make sure that part of the clause is not added. Thus disables the specific part of the clause. For previewing etc.
6035
     * @return string The part of the where clause on the form " AND [fieldname]=0 AND ...". Eg. " AND hidden=0 AND starttime < 123345567
6036
     */
6037
    public function enableFields($table, $show_hidden = false, array $ignore_array = [])
6038
    {
6039
        $tsfe = $this->getTypoScriptFrontendController();
6040
        $show_hidden = $show_hidden ?: ($table === 'pages' ? $tsfe->showHiddenPage : $tsfe->showHiddenRecords);
6041
        return $tsfe->sys_page->enableFields($table, (bool)$show_hidden, $ignore_array);
0 ignored issues
show
Bug introduced by
(bool)$show_hidden of type boolean is incompatible with the type integer expected by parameter $show_hidden of TYPO3\CMS\Frontend\Page\...ository::enableFields(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

6041
        return $tsfe->sys_page->enableFields($table, /** @scrutinizer ignore-type */ (bool)$show_hidden, $ignore_array);
Loading history...
6042
    }
6043
6044
    /**
6045
     * Generates a list of Page-uid's from $id. List does not include $id itself
6046
     * (unless the id specified is negative in which case it does!)
6047
     * The only pages WHICH PREVENTS DECENDING in a branch are
6048
     * - deleted pages,
6049
     * - pages in a recycler (doktype = 255) or of the Backend User Section (doktpe = 6) type
6050
     * - pages that has the extendToSubpages set, WHERE start/endtime, hidden
6051
     * and fe_users would hide the records.
6052
     * Apart from that, pages with enable-fields excluding them, will also be
6053
     * removed. HOWEVER $dontCheckEnableFields set will allow
6054
     * enableFields-excluded pages to be included anyway - including
6055
     * extendToSubpages sections!
6056
     * Mount Pages are also descended but notice that these ID numbers are not
6057
     * useful for links unless the correct MPvar is set.
6058
     *
6059
     * @param int $id The id of the start page from which point in the page tree to descend. IF NEGATIVE the id itself is included in the end of the list (only if $begin is 0) AND the output does NOT contain a last comma. Recommended since it will resolve the input ID for mount pages correctly and also check if the start ID actually exists!
6060
     * @param int $depth The number of levels to descend. If you want to descend infinitely, just set this to 100 or so. Should be at least "1" since zero will just make the function return (no decend...)
6061
     * @param int $begin Is an optional integer that determines at which level in the tree to start collecting uid's. Zero means 'start right away', 1 = 'next level and out'
6062
     * @param bool $dontCheckEnableFields See function description
6063
     * @param string $addSelectFields Additional fields to select. Syntax: ",[fieldname],[fieldname],...
6064
     * @param string $moreWhereClauses Additional where clauses. Syntax: " AND [fieldname]=[value] AND ...
6065
     * @param array $prevId_array array of IDs from previous recursions. In order to prevent infinite loops with mount pages.
6066
     * @param int $recursionLevel Internal: Zero for the first recursion, incremented for each recursive call.
6067
     * @return string Returns the list of ids as a comma separated string
6068
     * @see TypoScriptFrontendController::checkEnableFields(), TypoScriptFrontendController::checkPagerecordForIncludeSection()
6069
     */
6070
    public function getTreeList($id, $depth, $begin = 0, $dontCheckEnableFields = false, $addSelectFields = '', $moreWhereClauses = '', array $prevId_array = [], $recursionLevel = 0)
6071
    {
6072
        $id = (int)$id;
6073
        if (!$id) {
6074
            return '';
6075
        }
6076
6077
        // Init vars:
6078
        $allFields = 'uid,hidden,starttime,endtime,fe_group,extendToSubpages,doktype,php_tree_stop,mount_pid,mount_pid_ol,t3ver_state' . $addSelectFields;
6079
        $depth = (int)$depth;
6080
        $begin = (int)$begin;
6081
        $theList = [];
6082
        $addId = 0;
6083
        $requestHash = '';
6084
6085
        // First level, check id (second level, this is done BEFORE the recursive call)
6086
        $tsfe = $this->getTypoScriptFrontendController();
6087
        if (!$recursionLevel) {
6088
            // Check tree list cache
6089
            // First, create the hash for this request - not sure yet whether we need all these parameters though
6090
            $parameters = [
6091
                $id,
6092
                $depth,
6093
                $begin,
6094
                $dontCheckEnableFields,
6095
                $addSelectFields,
6096
                $moreWhereClauses,
6097
                $prevId_array,
6098
                $tsfe->gr_list
6099
            ];
6100
            $requestHash = md5(serialize($parameters));
6101
            $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
6102
                ->getQueryBuilderForTable('cache_treelist');
6103
            $cacheEntry = $queryBuilder->select('treelist')
6104
                ->from('cache_treelist')
6105
                ->where(
6106
                    $queryBuilder->expr()->eq(
6107
                        'md5hash',
6108
                        $queryBuilder->createNamedParameter($requestHash, \PDO::PARAM_STR)
6109
                    ),
6110
                    $queryBuilder->expr()->orX(
6111
                        $queryBuilder->expr()->gt(
6112
                            'expires',
6113
                            $queryBuilder->createNamedParameter($GLOBALS['EXEC_TIME'], \PDO::PARAM_INT)
6114
                        ),
6115
                        $queryBuilder->expr()->eq('expires', $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT))
6116
                    )
6117
                )
6118
                ->setMaxResults(1)
6119
                ->execute()
6120
                ->fetch();
6121
6122
            if (is_array($cacheEntry)) {
6123
                // Cache hit
6124
                return $cacheEntry['treelist'];
6125
            }
6126
            // If Id less than zero it means we should add the real id to list:
6127
            if ($id < 0) {
6128
                $addId = $id = abs($id);
6129
            }
6130
            // Check start page:
6131
            if ($tsfe->sys_page->getRawRecord('pages', $id, 'uid')) {
0 ignored issues
show
Bug introduced by
It seems like $id can also be of type double; however, parameter $uid of TYPO3\CMS\Frontend\Page\...ository::getRawRecord() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

6131
            if ($tsfe->sys_page->getRawRecord('pages', /** @scrutinizer ignore-type */ $id, 'uid')) {
Loading history...
6132
                // Find mount point if any:
6133
                $mount_info = $tsfe->sys_page->getMountPointInfo($id);
0 ignored issues
show
Bug introduced by
It seems like $id can also be of type double; however, parameter $pageId of TYPO3\CMS\Frontend\Page\...ry::getMountPointInfo() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

6133
                $mount_info = $tsfe->sys_page->getMountPointInfo(/** @scrutinizer ignore-type */ $id);
Loading history...
6134
                if (is_array($mount_info)) {
6135
                    $id = $mount_info['mount_pid'];
6136
                    // In Overlay mode, use the mounted page uid as added ID!:
6137
                    if ($addId && $mount_info['overlay']) {
6138
                        $addId = $id;
6139
                    }
6140
                }
6141
            } else {
6142
                // Return blank if the start page was NOT found at all!
6143
                return '';
6144
            }
6145
        }
6146
        // Add this ID to the array of IDs
6147
        if ($begin <= 0) {
6148
            $prevId_array[] = $id;
6149
        }
6150
        // Select sublevel:
6151
        if ($depth > 0) {
6152
            $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
6153
            $queryBuilder->getRestrictions()
6154
                ->removeAll()
6155
                ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
6156
            $queryBuilder->select(...GeneralUtility::trimExplode(',', $allFields, true))
6157
                ->from('pages')
6158
                ->where(
6159
                    $queryBuilder->expr()->eq(
6160
                        'pid',
6161
                        $queryBuilder->createNamedParameter($id, \PDO::PARAM_INT)
6162
                    )
6163
                )
6164
                ->orderBy('sorting');
6165
6166
            if (!empty($moreWhereClauses)) {
6167
                $queryBuilder->andWhere(QueryHelper::stripLogicalOperatorPrefix($moreWhereClauses));
6168
            }
6169
6170
            $result = $queryBuilder->execute();
6171
            while ($row = $result->fetch()) {
6172
                /** @var VersionState $versionState */
6173
                $versionState = VersionState::cast($row['t3ver_state']);
6174
                $tsfe->sys_page->versionOL('pages', $row);
6175 View Code Duplication
                if ((int)$row['doktype'] === PageRepository::DOKTYPE_RECYCLER
6176
                    || (int)$row['doktype'] === PageRepository::DOKTYPE_BE_USER_SECTION
6177
                    || $versionState->indicatesPlaceholder()
6178
                ) {
6179
                    // Doing this after the overlay to make sure changes
6180
                    // in the overlay are respected.
6181
                    // However, we do not process pages below of and
6182
                    // including of type recycler and BE user section
6183
                    continue;
6184
                }
6185
                // Find mount point if any:
6186
                $next_id = $row['uid'];
6187
                $mount_info = $tsfe->sys_page->getMountPointInfo($next_id, $row);
6188
                // Overlay mode:
6189
                if (is_array($mount_info) && $mount_info['overlay']) {
6190
                    $next_id = $mount_info['mount_pid'];
6191
                    $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
6192
                        ->getQueryBuilderForTable('pages');
6193
                    $queryBuilder->getRestrictions()
6194
                        ->removeAll()
6195
                        ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
6196
                    $queryBuilder->select(...GeneralUtility::trimExplode(',', $allFields, true))
6197
                        ->from('pages')
6198
                        ->where(
6199
                            $queryBuilder->expr()->eq(
6200
                                'uid',
6201
                                $queryBuilder->createNamedParameter($next_id, \PDO::PARAM_INT)
6202
                            )
6203
                        )
6204
                        ->orderBy('sorting')
6205
                        ->setMaxResults(1);
6206
6207
                    if (!empty($moreWhereClauses)) {
6208
                        $queryBuilder->andWhere(QueryHelper::stripLogicalOperatorPrefix($moreWhereClauses));
6209
                    }
6210
6211
                    $row = $queryBuilder->execute()->fetch();
6212
                    $tsfe->sys_page->versionOL('pages', $row);
6213 View Code Duplication
                    if ((int)$row['doktype'] === PageRepository::DOKTYPE_RECYCLER
6214
                        || (int)$row['doktype'] === PageRepository::DOKTYPE_BE_USER_SECTION
6215
                        || $versionState->indicatesPlaceholder()
6216
                    ) {
6217
                        // Doing this after the overlay to make sure
6218
                        // changes in the overlay are respected.
6219
                        // see above
6220
                        continue;
6221
                    }
6222
                }
6223
                // Add record:
6224
                if ($dontCheckEnableFields || $tsfe->checkPagerecordForIncludeSection($row)) {
6225
                    // Add ID to list:
6226
                    if ($begin <= 0) {
6227
                        if ($dontCheckEnableFields || $tsfe->checkEnableFields($row)) {
6228
                            $theList[] = $next_id;
6229
                        }
6230
                    }
6231
                    // Next level:
6232
                    if ($depth > 1 && !$row['php_tree_stop']) {
6233
                        // Normal mode:
6234
                        if (is_array($mount_info) && !$mount_info['overlay']) {
6235
                            $next_id = $mount_info['mount_pid'];
6236
                        }
6237
                        // Call recursively, if the id is not in prevID_array:
6238
                        if (!in_array($next_id, $prevId_array)) {
6239
                            $theList = array_merge(
6240
                                GeneralUtility::intExplode(
6241
                                    ',',
6242
                                    $this->getTreeList(
6243
                                        $next_id,
6244
                                        $depth - 1,
6245
                                        $begin - 1,
6246
                                        $dontCheckEnableFields,
6247
                                        $addSelectFields,
6248
                                        $moreWhereClauses,
6249
                                        $prevId_array,
6250
                                        $recursionLevel + 1
6251
                                    ),
6252
                                    true
6253
                                ),
6254
                                $theList
6255
                            );
6256
                        }
6257
                    }
6258
                }
6259
            }
6260
        }
6261
        // If first run, check if the ID should be returned:
6262
        if (!$recursionLevel) {
6263
            if ($addId) {
6264
                if ($begin > 0) {
6265
                    $theList[] = 0;
6266
                } else {
6267
                    $theList[] = $addId;
6268
                }
6269
            }
6270
            GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable('cache_treelist')->insert(
6271
                'cache_treelist',
6272
                [
6273
                    'md5hash' => $requestHash,
6274
                    'pid' => $id,
6275
                    'treelist' => implode(',', $theList),
6276
                    'tstamp' => $GLOBALS['EXEC_TIME']
6277
                ]
6278
            );
6279
        }
6280
6281
        return implode(',', $theList);
6282
    }
6283
6284
    /**
6285
     * Generates a search where clause based on the input search words (AND operation - all search words must be found in record.)
6286
     * Example: The $sw is "content management, system" (from an input form) and the $searchFieldList is "bodytext,header" then the output will be ' AND (bodytext LIKE "%content%" OR header LIKE "%content%") AND (bodytext LIKE "%management%" OR header LIKE "%management%") AND (bodytext LIKE "%system%" OR header LIKE "%system%")'
6287
     *
6288
     * @param string $searchWords The search words. These will be separated by space and comma.
6289
     * @param string $searchFieldList The fields to search in
6290
     * @param string $searchTable The table name you search in (recommended for DBAL compliance. Will be prepended field names as well)
6291
     * @return string The WHERE clause.
6292
     */
6293
    public function searchWhere($searchWords, $searchFieldList, $searchTable)
6294
    {
6295
        if (!$searchWords) {
6296
            return ' AND 1=1';
6297
        }
6298
6299
        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
6300
            ->getQueryBuilderForTable($searchTable);
6301
6302
        $prefixTableName = $searchTable ? $searchTable . '.' : '';
6303
6304
        $where = $queryBuilder->expr()->andX();
6305
        $searchFields = explode(',', $searchFieldList);
6306
        $searchWords = preg_split('/[ ,]/', $searchWords);
6307
        foreach ($searchWords as $searchWord) {
6308
            $searchWord = trim($searchWord);
6309
            if (strlen($searchWord) < 3) {
6310
                continue;
6311
            }
6312
            $searchWordConstraint = $queryBuilder->expr()->orX();
6313
            $searchWord = $queryBuilder->escapeLikeWildcards($searchWord);
6314
            foreach ($searchFields as $field) {
6315
                $searchWordConstraint->add(
6316
                    $queryBuilder->expr()->like(
6317
                        $prefixTableName . $field,
6318
                        $queryBuilder->createNamedParameter('%' . $searchWord . '%', \PDO::PARAM_STR)
6319
                    )
6320
                );
6321
            }
6322
6323
            if ($searchWordConstraint->count()) {
6324
                $where->add($searchWordConstraint);
6325
            }
6326
        }
6327
6328
        return ' AND ' . (string)$where;
6329
    }
6330
6331
    /**
6332
     * Executes a SELECT query for records from $table and with conditions based on the configuration in the $conf array
6333
     * This function is preferred over ->getQuery() if you just need to create and then execute a query.
6334
     *
6335
     * @param string $table The table name
6336
     * @param array $conf The TypoScript configuration properties
6337
     * @return Statement
6338
     * @see getQuery()
6339
     */
6340
    public function exec_getQuery($table, $conf)
6341
    {
6342
        $statement = $this->getQuery($table, $conf);
6343
        $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($table);
6344
6345
        return $connection->executeQuery($statement);
6346
    }
6347
6348
    /**
6349
     * Executes a SELECT query for records from $table and with conditions based on the configuration in the $conf array
6350
     * and overlays with translation and version if available
6351
     *
6352
     * @param string $tableName the name of the TCA database table
6353
     * @param array $queryConfiguration The TypoScript configuration properties, see .select in TypoScript reference
6354
     * @return array The records
6355
     * @throws \UnexpectedValueException
6356
     */
6357
    public function getRecords($tableName, array $queryConfiguration)
6358
    {
6359
        $records = [];
6360
6361
        $statement = $this->exec_getQuery($tableName, $queryConfiguration);
6362
6363
        $tsfe = $this->getTypoScriptFrontendController();
6364
        while ($row = $statement->fetch()) {
6365
            // Versioning preview:
6366
            $tsfe->sys_page->versionOL($tableName, $row, true);
6367
6368
            // Language overlay:
6369
            if (is_array($row) && $tsfe->sys_language_contentOL) {
6370
                if ($tableName === 'pages') {
6371
                    $row = $tsfe->sys_page->getPageOverlay($row);
6372
                } else {
6373
                    $row = $tsfe->sys_page->getRecordOverlay(
6374
                        $tableName,
6375
                        $row,
6376
                        $tsfe->sys_language_content,
6377
                        $tsfe->sys_language_contentOL
6378
                    );
6379
                }
6380
            }
6381
6382
            // Might be unset in the sys_language_contentOL
6383
            if (is_array($row)) {
6384
                $records[] = $row;
6385
            }
6386
        }
6387
6388
        return $records;
6389
    }
6390
6391
    /**
6392
     * Creates and returns a SELECT query for records from $table and with conditions based on the configuration in the $conf array
6393
     * Implements the "select" function in TypoScript
6394
     *
6395
     * @param string $table See ->exec_getQuery()
6396
     * @param array $conf See ->exec_getQuery()
6397
     * @param bool $returnQueryArray If set, the function will return the query not as a string but array with the various parts. RECOMMENDED!
6398
     * @return mixed A SELECT query if $returnQueryArray is FALSE, otherwise the SELECT query in an array as parts.
6399
     * @throws \RuntimeException
6400
     * @throws \InvalidArgumentException
6401
     * @access private
6402
     * @see CONTENT(), numRows()
6403
     */
6404
    public function getQuery($table, $conf, $returnQueryArray = false)
6405
    {
6406
        // Resolve stdWrap in these properties first
6407
        $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($table);
6408
        $properties = [
6409
            'pidInList',
6410
            'uidInList',
6411
            'languageField',
6412
            'selectFields',
6413
            'max',
6414
            'begin',
6415
            'groupBy',
6416
            'orderBy',
6417
            'join',
6418
            'leftjoin',
6419
            'rightjoin',
6420
            'recursive',
6421
            'where'
6422
        ];
6423
        foreach ($properties as $property) {
6424
            $conf[$property] = trim(
6425
                isset($conf[$property . '.'])
6426
                ? $this->stdWrap($conf[$property], $conf[$property . '.'])
6427
                : $conf[$property]
6428
            );
6429
            if ($conf[$property] === '') {
6430
                unset($conf[$property]);
6431
            } elseif (in_array($property, ['languageField', 'selectFields', 'join', 'leftJoin', 'rightJoin', 'where'], true)) {
6432
                $conf[$property] = QueryHelper::quoteDatabaseIdentifiers($connection, $conf[$property]);
6433
            }
6434
            if (isset($conf[$property . '.'])) {
6435
                // stdWrapping already done, so remove the sub-array
6436
                unset($conf[$property . '.']);
6437
            }
6438
        }
6439
        // Handle PDO-style named parameter markers first
6440
        $queryMarkers = $this->getQueryMarkers($table, $conf);
6441
        // Replace the markers in the non-stdWrap properties
6442
        foreach ($queryMarkers as $marker => $markerValue) {
6443
            $properties = [
6444
                'uidInList',
6445
                'selectFields',
6446
                'where',
6447
                'max',
6448
                'begin',
6449
                'groupBy',
6450
                'orderBy',
6451
                'join',
6452
                'leftjoin',
6453
                'rightjoin'
6454
            ];
6455
            foreach ($properties as $property) {
6456
                if ($conf[$property]) {
6457
                    $conf[$property] = str_replace('###' . $marker . '###', $markerValue, $conf[$property]);
6458
                }
6459
            }
6460
        }
6461
6462
        // Construct WHERE clause:
6463
        // Handle recursive function for the pidInList
6464
        if (isset($conf['recursive'])) {
6465
            $conf['recursive'] = (int)$conf['recursive'];
6466
            if ($conf['recursive'] > 0) {
6467
                $pidList = GeneralUtility::trimExplode(',', $conf['pidInList'], true);
6468
                array_walk($pidList, function (&$storagePid) {
6469
                    if ($storagePid === 'this') {
6470
                        $storagePid = $this->getTypoScriptFrontendController()->id;
6471
                    }
6472
                    if ($storagePid > 0) {
6473
                        $storagePid = -$storagePid;
6474
                    }
6475
                });
6476
                $expandedPidList = [];
6477
                foreach ($pidList as $value) {
6478
                    // Implementation of getTreeList allows to pass the id negative to include
6479
                    // it into the result otherwise only childpages are returned
6480
                    $expandedPidList = array_merge(
6481
                        GeneralUtility::intExplode(',', $this->getTreeList($value, $conf['recursive'])),
6482
                        $expandedPidList
6483
                    );
6484
                }
6485
                $conf['pidInList'] = implode(',', $expandedPidList);
6486
            }
6487
        }
6488
        if ((string)$conf['pidInList'] === '') {
6489
            $conf['pidInList'] = 'this';
6490
        }
6491
6492
        $queryParts = $this->getQueryConstraints($table, $conf);
6493
6494
        $queryBuilder = $connection->createQueryBuilder();
6495
        // @todo Check against getQueryConstraints, can probably use FrontendRestrictions
6496
        // @todo here and remove enableFields there.
6497
        $queryBuilder->getRestrictions()->removeAll();
6498
        $queryBuilder->select('*')->from($table);
6499
6500
        if ($queryParts['where']) {
6501
            $queryBuilder->where($queryParts['where']);
6502
        }
6503
6504
        if ($queryParts['groupBy']) {
6505
            $queryBuilder->groupBy(...$queryParts['groupBy']);
6506
        }
6507
6508
        if (is_array($queryParts['orderBy'])) {
6509
            foreach ($queryParts['orderBy'] as $orderBy) {
6510
                $queryBuilder->addOrderBy(...$orderBy);
6511
            }
6512
        }
6513
6514
        // Fields:
6515
        if ($conf['selectFields']) {
6516
            $queryBuilder->selectLiteral($this->sanitizeSelectPart($conf['selectFields'], $table));
6517
        }
6518
6519
        // Setting LIMIT:
6520
        $error = false;
6521
        if ($conf['max'] || $conf['begin']) {
6522
            // Finding the total number of records, if used:
6523
            if (strpos(strtolower($conf['begin'] . $conf['max']), 'total') !== false) {
6524
                $countQueryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
6525
                $countQueryBuilder->getRestrictions()->removeAll();
6526
                $countQueryBuilder->count('*')
6527
                    ->from($table)
6528
                    ->where($queryParts['where']);
6529
6530
                if ($queryParts['groupBy']) {
6531
                    $countQueryBuilder->groupBy(...$queryParts['groupBy']);
6532
                }
6533
6534
                try {
6535
                    $count = $countQueryBuilder->execute()->fetchColumn(0);
6536
                    $conf['max'] = str_ireplace('total', $count, $conf['max']);
6537
                    $conf['begin'] = str_ireplace('total', $count, $conf['begin']);
6538
                } catch (DBALException $e) {
6539
                    $this->getTimeTracker()->setTSlogMessage($e->getPrevious()->getMessage());
6540
                    $error = true;
6541
                }
6542
            }
6543
6544
            if (!$error) {
6545
                $conf['begin'] = MathUtility::forceIntegerInRange(ceil($this->calc($conf['begin'])), 0);
0 ignored issues
show
Bug introduced by
ceil($this->calc($conf['begin'])) of type double is incompatible with the type integer expected by parameter $theInt of TYPO3\CMS\Core\Utility\M...::forceIntegerInRange(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

6545
                $conf['begin'] = MathUtility::forceIntegerInRange(/** @scrutinizer ignore-type */ ceil($this->calc($conf['begin'])), 0);
Loading history...
6546
                $conf['max'] = MathUtility::forceIntegerInRange(ceil($this->calc($conf['max'])), 0);
6547
                if ($conf['begin'] > 0) {
6548
                    $queryBuilder->setFirstResult($conf['begin']);
6549
                }
6550
                $queryBuilder->setMaxResults($conf['max'] ?: 100000);
6551
            }
6552
        }
6553
6554
        if (!$error) {
6555
            // Setting up tablejoins:
6556
            if ($conf['join']) {
6557
                $joinParts = QueryHelper::parseJoin($conf['join']);
6558
                $queryBuilder->join(
6559
                    $table,
6560
                    $joinParts['tableName'],
6561
                    $joinParts['tableAlias'],
6562
                    $joinParts['joinCondition']
6563
                );
6564
            } elseif ($conf['leftjoin']) {
6565
                $joinParts = QueryHelper::parseJoin($conf['leftjoin']);
6566
                $queryBuilder->leftJoin(
6567
                    $table,
6568
                    $joinParts['tableName'],
6569
                    $joinParts['tableAlias'],
6570
                    $joinParts['joinCondition']
6571
                );
6572
            } elseif ($conf['rightjoin']) {
6573
                $joinParts = QueryHelper::parseJoin($conf['rightjoin']);
6574
                $queryBuilder->rightJoin(
6575
                    $table,
6576
                    $joinParts['tableName'],
6577
                    $joinParts['tableAlias'],
6578
                    $joinParts['joinCondition']
6579
                );
6580
            }
6581
6582
            // Convert the QueryBuilder object into a SQL statement.
6583
            $query = $queryBuilder->getSQL();
6584
6585
            // Replace the markers in the queryParts to handle stdWrap enabled properties
6586
            foreach ($queryMarkers as $marker => $markerValue) {
6587
                // @todo Ugly hack that needs to be cleaned up, with the current architecture
6588
                // @todo for exec_Query / getQuery it's the best we can do.
6589
                $query = str_replace('###' . $marker . '###', $markerValue, $query);
6590
                foreach ($queryParts as $queryPartKey => &$queryPartValue) {
6591
                    $queryPartValue = str_replace('###' . $marker . '###', $markerValue, $queryPartValue);
6592
                }
6593
                unset($queryPartValue);
6594
            }
6595
6596
            return $returnQueryArray ? $this->getQueryArray($queryBuilder) : $query;
6597
        }
6598
6599
        return '';
6600
    }
6601
6602
    /**
6603
     * Helper to transform a QueryBuilder object into a queryParts array that can be used
6604
     * with exec_SELECT_queryArray
6605
     *
6606
     * @param \TYPO3\CMS\Core\Database\Query\QueryBuilder $queryBuilder
6607
     * @return array
6608
     * @throws \RuntimeException
6609
     */
6610
    protected function getQueryArray(QueryBuilder $queryBuilder)
6611
    {
6612
        $fromClauses = [];
6613
        $knownAliases = [];
6614
        $queryParts = [];
6615
6616
        // Loop through all FROM clauses
6617
        foreach ($queryBuilder->getQueryPart('from') as $from) {
6618
            if ($from['alias'] === null) {
6619
                $tableSql = $from['table'];
6620
                $tableReference = $from['table'];
6621
            } else {
6622
                $tableSql = $from['table'] . ' ' . $from['alias'];
6623
                $tableReference = $from['alias'];
6624
            }
6625
6626
            $knownAliases[$tableReference] = true;
6627
6628
            $fromClauses[$tableReference] = $tableSql . $this->getQueryArrayJoinHelper(
6629
                    $tableReference,
6630
                    $queryBuilder->getQueryPart('join'),
6631
                    $knownAliases
6632
                );
6633
        }
6634
6635
        $queryParts['SELECT'] = implode(', ', $queryBuilder->getQueryPart('select'));
6636
        $queryParts['FROM'] = implode(', ', $fromClauses);
6637
        $queryParts['WHERE'] = (string)$queryBuilder->getQueryPart('where') ?: '';
6638
        $queryParts['GROUPBY'] = implode(', ', $queryBuilder->getQueryPart('groupBy'));
6639
        $queryParts['ORDERBY'] = implode(', ', $queryBuilder->getQueryPart('orderBy'));
6640
        if ($queryBuilder->getFirstResult() > 0) {
6641
            $queryParts['LIMIT'] = $queryBuilder->getFirstResult() . ',' . $queryBuilder->getMaxResults();
6642
        } elseif ($queryBuilder->getMaxResults() > 0) {
6643
            $queryParts['LIMIT'] = $queryBuilder->getMaxResults();
6644
        }
6645
6646
        return $queryParts;
6647
    }
6648
6649
    /**
6650
     * Helper to transform the QueryBuilder join part into a SQL fragment.
6651
     *
6652
     * @param string $fromAlias
6653
     * @param array $joinParts
6654
     * @param array $knownAliases
6655
     * @return string
6656
     * @throws \RuntimeException
6657
     */
6658
    protected function getQueryArrayJoinHelper(string $fromAlias, array $joinParts, array &$knownAliases): string
6659
    {
6660
        $sql = '';
6661
6662
        if (isset($joinParts['join'][$fromAlias])) {
6663
            foreach ($joinParts['join'][$fromAlias] as $join) {
6664
                if (array_key_exists($join['joinAlias'], $knownAliases)) {
6665
                    throw new \RuntimeException(
6666
                        'Non unique join alias: "' . $join['joinAlias'] . '" found.',
6667
                        1472748872
6668
                    );
6669
                }
6670
                $sql .= ' ' . strtoupper($join['joinType'])
6671
                    . ' JOIN ' . $join['joinTable'] . ' ' . $join['joinAlias']
6672
                    . ' ON ' . ((string)$join['joinCondition']);
6673
                $knownAliases[$join['joinAlias']] = true;
6674
            }
6675
6676
            foreach ($joinParts['join'][$fromAlias] as $join) {
6677
                $sql .= $this->getQueryArrayJoinHelper($join['joinAlias'], $joinParts, $knownAliases);
6678
            }
6679
        }
6680
6681
        return $sql;
6682
    }
6683
    /**
6684
     * Helper function for getQuery(), creating the WHERE clause of the SELECT query
6685
     *
6686
     * @param string $table The table name
6687
     * @param array $conf The TypoScript configuration properties
6688
     * @return array Associative array containing the prepared data for WHERE, ORDER BY and GROUP BY fragments
6689
     * @throws \InvalidArgumentException
6690
     * @see getQuery()
6691
     */
6692
    protected function getQueryConstraints(string $table, array $conf): array
6693
    {
6694
        // Init:
6695
        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
6696
        $expressionBuilder = $queryBuilder->expr();
6697
        $tsfe = $this->getTypoScriptFrontendController();
6698
        $constraints = [];
6699
        $pid_uid_flag = 0;
6700
        $enableFieldsIgnore = [];
6701
        $queryParts = [
6702
            'where' => null,
6703
            'groupBy' => null,
6704
            'orderBy' => null,
6705
        ];
6706
6707
        $considerMovePlaceholders = (
6708
            $tsfe->sys_page->versioningPreview && $table !== 'pages'
6709
            && !empty($GLOBALS['TCA'][$table]['ctrl']['versioningWS'])
6710
        );
6711
6712
        if (trim($conf['uidInList'])) {
6713
            $listArr = GeneralUtility::intExplode(',', str_replace('this', $tsfe->contentPid, $conf['uidInList']));
6714
6715
            // If move placeholder shall be considered, select via t3ver_move_id
6716
            if ($considerMovePlaceholders) {
6717
                $constraints[] = (string)$expressionBuilder->orX(
6718
                    $expressionBuilder->in($table . '.uid', $listArr),
6719
                    $expressionBuilder->andX(
6720
                        $expressionBuilder->eq(
6721
                            $table . '.t3ver_state',
6722
                            (int)(string)VersionState::cast(VersionState::MOVE_PLACEHOLDER)
6723
                        ),
6724
                        $expressionBuilder->in($table . '.t3ver_move_id', $listArr)
6725
                    )
6726
                );
6727
            } else {
6728
                $constraints[] = (string)$expressionBuilder->in($table . '.uid', $listArr);
6729
            }
6730
            $pid_uid_flag++;
6731
        }
6732
6733
        // Static_* tables are allowed to be fetched from root page
6734
        if (strpos($table, 'static_') === 0) {
6735
            $pid_uid_flag++;
6736
        }
6737
6738
        if (trim($conf['pidInList'])) {
6739
            $listArr = GeneralUtility::intExplode(',', str_replace('this', $tsfe->contentPid, $conf['pidInList']));
6740
            // Removes all pages which are not visible for the user!
6741
            $listArr = $this->checkPidArray($listArr);
6742
            if (GeneralUtility::inList($conf['pidInList'], 'root')) {
6743
                $listArr[] = 0;
6744
            }
6745
            if (GeneralUtility::inList($conf['pidInList'], '-1')) {
6746
                $listArr[] = -1;
6747
                $enableFieldsIgnore['pid'] = true;
6748
            }
6749
            if (!empty($listArr)) {
6750
                $constraints[] = $expressionBuilder->in($table . '.pid', array_map('intval', $listArr));
6751
                $pid_uid_flag++;
6752
            } else {
6753
                // If not uid and not pid then uid is set to 0 - which results in nothing!!
6754
                $pid_uid_flag = 0;
6755
            }
6756
        }
6757
6758
        // If not uid and not pid then uid is set to 0 - which results in nothing!!
6759
        if (!$pid_uid_flag) {
6760
            $constraints[] = $expressionBuilder->eq($table . '.uid', 0);
6761
        }
6762
6763
        $where = isset($conf['where.']) ? trim($this->stdWrap($conf['where'], $conf['where.'])) : trim($conf['where']);
6764
        if ($where) {
6765
            $constraints[] = QueryHelper::stripLogicalOperatorPrefix($where);
6766
        }
6767
6768
        // Check if the table is translatable, and set the language field by default from the TCA information
6769
        $languageField = '';
6770
        if (!empty($conf['languageField']) || !isset($conf['languageField'])) {
6771
            if (isset($conf['languageField']) && !empty($GLOBALS['TCA'][$table]['columns'][$conf['languageField']])) {
6772
                $languageField = $conf['languageField'];
6773
            } elseif (!empty($GLOBALS['TCA'][$table]['ctrl']['languageField']) && !empty($GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'])) {
6774
                $languageField = $table . '.' . $GLOBALS['TCA'][$table]['ctrl']['languageField'];
6775
            }
6776
        }
6777
6778
        if (!empty($languageField)) {
6779
            // The sys_language record UID of the content of the page
6780
            $sys_language_content = (int)$tsfe->sys_language_content;
6781
6782
            if ($tsfe->sys_language_contentOL && !empty($GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'])) {
6783
                // Sys language content is set to zero/-1 - and it is expected that whatever routine processes the output will
6784
                // OVERLAY the records with localized versions!
6785
                $languageQuery = $expressionBuilder->in($languageField, [0, -1]);
6786
                // Use this option to include records that don't have a default translation
6787
                // (originalpointerfield is 0 and the language field contains the requested language)
6788
                $includeRecordsWithoutDefaultTranslation = isset($conf['includeRecordsWithoutDefaultTranslation.']) ?
6789
                    $this->stdWrap($conf['includeRecordsWithoutDefaultTranslation'], $conf['includeRecordsWithoutDefaultTranslation.']) :
6790
                    $conf['includeRecordsWithoutDefaultTranslation'];
6791
                if (trim($includeRecordsWithoutDefaultTranslation) !== '') {
6792
                    $languageQuery = $expressionBuilder->orX(
6793
                        $languageQuery,
6794
                        $expressionBuilder->andX(
6795
                            $expressionBuilder->eq($GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'], 0),
6796
                            $expressionBuilder->eq($languageField, $sys_language_content)
6797
                        )
6798
                    );
6799
                }
6800
            } else {
6801
                $languageQuery = $expressionBuilder->eq($languageField, $sys_language_content);
6802
            }
6803
            $constraints[] = $languageQuery;
6804
        }
6805
6806
        // Enablefields
6807
        if ($table === 'pages') {
6808
            $constraints[] = QueryHelper::stripLogicalOperatorPrefix($tsfe->sys_page->where_hid_del);
6809
            $constraints[] = QueryHelper::stripLogicalOperatorPrefix($tsfe->sys_page->where_groupAccess);
6810
        } else {
6811
            $constraints[] = QueryHelper::stripLogicalOperatorPrefix($this->enableFields($table, false, $enableFieldsIgnore));
6812
        }
6813
6814
        // MAKE WHERE:
6815
        if (count($constraints) !== 0) {
6816
            $queryParts['where'] = $expressionBuilder->andX(...$constraints);
6817
        }
6818
        // GROUP BY
6819 View Code Duplication
        if (trim($conf['groupBy'])) {
6820
            $groupBy = isset($conf['groupBy.'])
6821
                ? trim($this->stdWrap($conf['groupBy'], $conf['groupBy.']))
6822
                : trim($conf['groupBy']);
6823
            $queryParts['groupBy'] = QueryHelper::parseGroupBy($groupBy);
6824
        }
6825
6826
        // ORDER BY
6827 View Code Duplication
        if (trim($conf['orderBy'])) {
6828
            $orderByString = isset($conf['orderBy.'])
6829
                ? trim($this->stdWrap($conf['orderBy'], $conf['orderBy.']))
6830
                : trim($conf['orderBy']);
6831
6832
            $queryParts['orderBy'] = QueryHelper::parseOrderBy($orderByString);
6833
        }
6834
6835
        // Return result:
6836
        return $queryParts;
6837
    }
6838
6839
    /**
6840
     * Helper function for getQuery, sanitizing the select part
6841
     *
6842
     * This functions checks if the necessary fields are part of the select
6843
     * and adds them if necessary.
6844
     *
6845
     * @param string $selectPart Select part
6846
     * @param string $table Table to select from
6847
     * @return string Sanitized select part
6848
     * @access private
6849
     * @see getQuery
6850
     */
6851
    protected function sanitizeSelectPart($selectPart, $table)
6852
    {
6853
        $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($table);
6854
6855
        // Pattern matching parts
6856
        $matchStart = '/(^\\s*|,\\s*|' . $table . '\\.)';
6857
        $matchEnd = '(\\s*,|\\s*$)/';
6858
        $necessaryFields = ['uid', 'pid'];
6859
        $wsFields = ['t3ver_state'];
6860
        if (isset($GLOBALS['TCA'][$table]) && !preg_match(($matchStart . '\\*' . $matchEnd), $selectPart) && !preg_match('/(count|max|min|avg|sum)\\([^\\)]+\\)/i', $selectPart)) {
6861 View Code Duplication
            foreach ($necessaryFields as $field) {
6862
                $match = $matchStart . $field . $matchEnd;
6863
                if (!preg_match($match, $selectPart)) {
6864
                    $selectPart .= ', ' . $connection->quoteIdentifier($table . '.' . $field) . ' AS ' . $connection->quoteIdentifier($field);
6865
                }
6866
            }
6867
            if ($GLOBALS['TCA'][$table]['ctrl']['versioningWS']) {
6868 View Code Duplication
                foreach ($wsFields as $field) {
6869
                    $match = $matchStart . $field . $matchEnd;
6870
                    if (!preg_match($match, $selectPart)) {
6871
                        $selectPart .= ', ' . $connection->quoteIdentifier($table . '.' . $field) . ' AS ' . $connection->quoteIdentifier($field);
6872
                    }
6873
                }
6874
            }
6875
        }
6876
        return $selectPart;
6877
    }
6878
6879
    /**
6880
     * Removes Page UID numbers from the input array which are not available due to enableFields() or the list of bad doktype numbers ($this->checkPid_badDoktypeList)
6881
     *
6882
     * @param array $listArr Array of Page UID numbers for select and for which pages with enablefields and bad doktypes should be removed.
6883
     * @return array Returns the array of remaining page UID numbers
6884
     * @access private
6885
     * @see checkPid()
6886
     */
6887
    public function checkPidArray($listArr)
6888
    {
6889
        if (!is_array($listArr) || empty($listArr)) {
6890
            return [];
6891
        }
6892
        $outArr = [];
6893
        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
6894
        $queryBuilder->setRestrictions(GeneralUtility::makeInstance(FrontendRestrictionContainer::class));
6895
        $queryBuilder->select('uid')
6896
            ->from('pages')
6897
            ->where(
6898
                $queryBuilder->expr()->in(
6899
                    'uid',
6900
                    $queryBuilder->createNamedParameter($listArr, Connection::PARAM_INT_ARRAY)
6901
                ),
6902
                $queryBuilder->expr()->notIn(
6903
                    'doktype',
6904
                    $queryBuilder->createNamedParameter(
6905
                        GeneralUtility::intExplode(',', $this->checkPid_badDoktypeList, true),
6906
                        Connection::PARAM_INT_ARRAY
6907
                    )
6908
                )
6909
            );
6910
        try {
6911
            $result = $queryBuilder->execute();
6912
            while ($row = $result->fetch()) {
6913
                $outArr[] = $row['uid'];
6914
            }
6915
        } catch (DBALException $e) {
6916
            $this->getTimeTracker()->setTSlogMessage($e->getMessage() . ': ' . $queryBuilder->getSQL(), 3);
6917
        }
6918
6919
        return $outArr;
6920
    }
6921
6922
    /**
6923
     * Checks if a page UID is available due to enableFields() AND the list of bad doktype numbers ($this->checkPid_badDoktypeList)
6924
     *
6925
     * @param int $uid Page UID to test
6926
     * @return bool TRUE if OK
6927
     * @access private
6928
     * @see checkPidArray()
6929
     */
6930
    public function checkPid($uid)
6931
    {
6932
        $uid = (int)$uid;
6933
        if (!isset($this->checkPid_cache[$uid])) {
6934
            $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
6935
            $queryBuilder->setRestrictions(GeneralUtility::makeInstance(FrontendRestrictionContainer::class));
6936
            $count = $queryBuilder->count('*')
6937
                ->from('pages')
6938
                ->where(
6939
                    $queryBuilder->expr()->eq(
6940
                        'uid',
6941
                        $queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT)
6942
                    ),
6943
                    $queryBuilder->expr()->notIn(
6944
                        'doktype',
6945
                        $queryBuilder->createNamedParameter(
6946
                            GeneralUtility::intExplode(',', $this->checkPid_badDoktypeList, true),
6947
                            Connection::PARAM_INT_ARRAY
6948
                        )
6949
                    )
6950
                )
6951
                ->execute()
6952
                ->fetchColumn(0);
6953
6954
            $this->checkPid_cache[$uid] = (bool)$count;
6955
        }
6956
        return $this->checkPid_cache[$uid];
6957
    }
6958
6959
    /**
6960
     * Builds list of marker values for handling PDO-like parameter markers in select parts.
6961
     * Marker values support stdWrap functionality thus allowing a way to use stdWrap functionality in various properties of 'select' AND prevents SQL-injection problems by quoting and escaping of numeric values, strings, NULL values and comma separated lists.
6962
     *
6963
     * @param string $table Table to select records from
6964
     * @param array $conf Select part of CONTENT definition
6965
     * @return array List of values to replace markers with
6966
     * @access private
6967
     * @see getQuery()
6968
     */
6969
    public function getQueryMarkers($table, $conf)
6970
    {
6971
        if (!is_array($conf['markers.'])) {
6972
            return [];
6973
        }
6974
        // Parse markers and prepare their values
6975
        $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($table);
6976
        $markerValues = [];
6977
        foreach ($conf['markers.'] as $dottedMarker => $dummy) {
6978
            $marker = rtrim($dottedMarker, '.');
6979
            if ($dottedMarker != $marker . '.') {
6980
                continue;
6981
            }
6982
            // Parse definition
6983
            $tempValue = isset($conf['markers.'][$dottedMarker])
6984
                ? $this->stdWrap($conf['markers.'][$dottedMarker]['value'], $conf['markers.'][$dottedMarker])
6985
                : $conf['markers.'][$dottedMarker]['value'];
6986
            // Quote/escape if needed
6987
            if (is_numeric($tempValue)) {
6988
                if ((int)$tempValue == $tempValue) {
6989
                    // Handle integer
6990
                    $markerValues[$marker] = (int)$tempValue;
6991
                } else {
6992
                    // Handle float
6993
                    $markerValues[$marker] = (float)$tempValue;
6994
                }
6995
            } elseif (is_null($tempValue)) {
6996
                // It represents NULL
6997
                $markerValues[$marker] = 'NULL';
6998
            } elseif (!empty($conf['markers.'][$dottedMarker]['commaSeparatedList'])) {
6999
                // See if it is really a comma separated list of values
7000
                $explodeValues = GeneralUtility::trimExplode(',', $tempValue);
7001
                if (count($explodeValues) > 1) {
7002
                    // Handle each element of list separately
7003
                    $tempArray = [];
7004
                    foreach ($explodeValues as $listValue) {
7005
                        if (is_numeric($listValue)) {
7006
                            if ((int)$listValue == $listValue) {
7007
                                $tempArray[] = (int)$listValue;
7008
                            } else {
7009
                                $tempArray[] = (float)$listValue;
7010
                            }
7011
                        } else {
7012
                            // If quoted, remove quotes before
7013
                            // escaping.
7014
                            if (preg_match('/^\'([^\']*)\'$/', $listValue, $matches)) {
7015
                                $listValue = $matches[1];
7016
                            } elseif (preg_match('/^\\"([^\\"]*)\\"$/', $listValue, $matches)) {
7017
                                $listValue = $matches[1];
7018
                            }
7019
                            $tempArray[] = $connection->quote($listValue);
7020
                        }
7021
                    }
7022
                    $markerValues[$marker] = implode(',', $tempArray);
7023
                } else {
7024
                    // Handle remaining values as string
7025
                    $markerValues[$marker] = $connection->quote($tempValue);
7026
                }
7027
            } else {
7028
                // Handle remaining values as string
7029
                $markerValues[$marker] = $connection->quote($tempValue);
7030
            }
7031
        }
7032
        return $markerValues;
7033
    }
7034
7035
    /***********************************************
7036
     *
7037
     * Frontend editing functions
7038
     *
7039
     ***********************************************/
7040
    /**
7041
     * Generates the "edit panels" which can be shown for a page or records on a page when the Admin Panel is enabled for a backend users surfing the frontend.
7042
     * With the "edit panel" the user will see buttons with links to editing, moving, hiding, deleting the element
7043
     * This function is used for the cObject EDITPANEL and the stdWrap property ".editPanel"
7044
     *
7045
     * @param string $content A content string containing the content related to the edit panel. For cObject "EDITPANEL" this is empty but not so for the stdWrap property. The edit panel is appended to this string and returned.
7046
     * @param array $conf TypoScript configuration properties for the editPanel
7047
     * @param string $currentRecord The "table:uid" of the record being shown. If empty string then $this->currentRecord is used. For new records (set by $conf['newRecordFromTable']) it's auto-generated to "[tablename]:NEW
7048
     * @param array $dataArr Alternative data array to use. Default is $this->data
7049
     * @return string The input content string with the editPanel appended. This function returns only an edit panel appended to the content string if a backend user is logged in (and has the correct permissions). Otherwise the content string is directly returned.
7050
     */
7051 View Code Duplication
    public function editPanel($content, $conf, $currentRecord = '', $dataArr = [])
7052
    {
7053
        if ($this->getTypoScriptFrontendController()->beUserLogin && $this->getFrontendBackendUser()->frontendEdit instanceof FrontendEditingController) {
7054
            if (!$currentRecord) {
7055
                $currentRecord = $this->currentRecord;
7056
            }
7057
            if (empty($dataArr)) {
7058
                $dataArr = $this->data;
7059
            }
7060
            // Delegate rendering of the edit panel to the frontend edit
7061
            $content = $this->getFrontendBackendUser()->frontendEdit->displayEditPanel($content, $conf, $currentRecord, $dataArr);
7062
        }
7063
        return $content;
7064
    }
7065
7066
    /**
7067
     * Adds an edit icon to the content string. The edit icon links to FormEngine with proper parameters for editing the table/fields of the context.
7068
     * This implements TYPO3 context sensitive editing facilities. Only backend users will have access (if properly configured as well).
7069
     *
7070
     * @param string $content The content to which the edit icons should be appended
7071
     * @param string $params The parameters defining which table and fields to edit. Syntax is [tablename]:[fieldname],[fieldname],[fieldname],... OR [fieldname],[fieldname],[fieldname],... (basically "[tablename]:" is optional, default table is the one of the "current record" used in the function). The fieldlist is sent as "&columnsOnly=" parameter to FormEngine
7072
     * @param array $conf TypoScript properties for configuring the edit icons.
7073
     * @param string $currentRecord The "table:uid" of the record being shown. If empty string then $this->currentRecord is used. For new records (set by $conf['newRecordFromTable']) it's auto-generated to "[tablename]:NEW
7074
     * @param array $dataArr Alternative data array to use. Default is $this->data
7075
     * @param string $addUrlParamStr Additional URL parameters for the link pointing to FormEngine
7076
     * @return string The input content string, possibly with edit icons added (not necessarily in the end but just after the last string of normal content.
7077
     */
7078 View Code Duplication
    public function editIcons($content, $params, array $conf = [], $currentRecord = '', $dataArr = [], $addUrlParamStr = '')
7079
    {
7080
        if ($this->getTypoScriptFrontendController()->beUserLogin && $this->getFrontendBackendUser()->frontendEdit instanceof FrontendEditingController) {
7081
            if (!$currentRecord) {
7082
                $currentRecord = $this->currentRecord;
7083
            }
7084
            if (empty($dataArr)) {
7085
                $dataArr = $this->data;
7086
            }
7087
            // Delegate rendering of the edit panel to frontend edit class.
7088
            $content = $this->getFrontendBackendUser()->frontendEdit->displayEditIcons($content, $params, $conf, $currentRecord, $dataArr, $addUrlParamStr);
7089
        }
7090
        return $content;
7091
    }
7092
7093
    /**
7094
     * Returns TRUE if the input table/row would be hidden in the frontend (according nto the current time and simulate user group)
7095
     *
7096
     * @param string $table The table name
7097
     * @param array $row The data record
7098
     * @return bool
7099
     * @access private
7100
     * @see editPanelPreviewBorder()
7101
     */
7102
    public function isDisabled($table, $row)
7103
    {
7104
        $tsfe = $this->getTypoScriptFrontendController();
7105
        $enablecolumns = $GLOBALS['TCA'][$table]['ctrl']['enablecolumns'];
7106
        return $enablecolumns['disabled'] && $row[$enablecolumns['disabled']]
7107
            || $enablecolumns['fe_group'] && $tsfe->simUserGroup && (int)$row[$enablecolumns['fe_group']] === (int)$tsfe->simUserGroup
7108
            || $enablecolumns['starttime'] && $row[$enablecolumns['starttime']] > $GLOBALS['EXEC_TIME']
7109
            || $enablecolumns['endtime'] && $row[$enablecolumns['endtime']] && $row[$enablecolumns['endtime']] < $GLOBALS['EXEC_TIME'];
7110
    }
7111
7112
    /**
7113
     * Get instance of FAL resource factory
7114
     *
7115
     * @return ResourceFactory
7116
     */
7117
    protected function getResourceFactory()
7118
    {
7119
        return ResourceFactory::getInstance();
7120
    }
7121
7122
    /**
7123
     * Wrapper function for GeneralUtility::getIndpEnv()
7124
     *
7125
     * @see GeneralUtility::getIndpEnv
7126
     * @param string $key Name of the "environment variable"/"server variable" you wish to get.
7127
     * @return string
7128
     */
7129
    protected function getEnvironmentVariable($key)
7130
    {
7131
        return GeneralUtility::getIndpEnv($key);
7132
    }
7133
7134
    /**
7135
     * Fetches content from cache
7136
     *
7137
     * @param array $configuration Array
7138
     * @return string|bool FALSE on cache miss
7139
     * @throws \TYPO3\CMS\Core\Cache\Exception\NoSuchCacheException
7140
     */
7141
    protected function getFromCache(array $configuration)
7142
    {
7143
        $content = false;
7144
7145
        $cacheKey = $this->calculateCacheKey($configuration);
7146
        if (!empty($cacheKey)) {
7147
            /** @var $cacheFrontend \TYPO3\CMS\Core\Cache\Frontend\VariableFrontend */
7148
            $cacheFrontend = GeneralUtility::makeInstance(CacheManager::class)
7149
                ->getCache('cache_hash');
7150
            $content = $cacheFrontend->get($cacheKey);
7151
        }
7152
        return $content;
7153
    }
7154
7155
    /**
7156
     * Calculates the lifetime of a cache entry based on the given configuration
7157
     *
7158
     * @param array $configuration
7159
     * @return int|null
7160
     */
7161
    protected function calculateCacheLifetime(array $configuration)
7162
    {
7163
        $lifetimeConfiguration = isset($configuration['lifetime'])
7164
            ? $configuration['lifetime']
7165
            : '';
7166
        $lifetimeConfiguration = isset($configuration['lifetime.'])
7167
            ? $this->stdWrap($lifetimeConfiguration, $configuration['lifetime.'])
7168
            : $lifetimeConfiguration;
7169
7170
        $lifetime = null; // default lifetime
7171
        if (strtolower($lifetimeConfiguration) === 'unlimited') {
7172
            $lifetime = 0; // unlimited
7173
        } elseif ($lifetimeConfiguration > 0) {
7174
            $lifetime = (int)$lifetimeConfiguration; // lifetime in seconds
7175
        }
7176
        return $lifetime;
7177
    }
7178
7179
    /**
7180
     * Calculates the tags for a cache entry bases on the given configuration
7181
     *
7182
     * @param array $configuration
7183
     * @return array
7184
     */
7185
    protected function calculateCacheTags(array $configuration)
7186
    {
7187
        $tags = isset($configuration['tags']) ? $configuration['tags'] : '';
7188
        $tags = isset($configuration['tags.'])
7189
            ? $this->stdWrap($tags, $configuration['tags.'])
7190
            : $tags;
7191
        return empty($tags) ? [] : GeneralUtility::trimExplode(',', $tags);
7192
    }
7193
7194
    /**
7195
     * Applies stdWrap to the cache key
7196
     *
7197
     * @param array $configuration
7198
     * @return string
7199
     */
7200
    protected function calculateCacheKey(array $configuration)
7201
    {
7202
        $key = isset($configuration['key']) ? $configuration['key'] : '';
7203
        return isset($configuration['key.'])
7204
            ? $this->stdWrap($key, $configuration['key.'])
7205
            : $key;
7206
    }
7207
7208
    /**
7209
     * Returns the current BE user.
7210
     *
7211
     * @return \TYPO3\CMS\Backend\FrontendBackendUserAuthentication
7212
     */
7213
    protected function getFrontendBackendUser()
7214
    {
7215
        return $GLOBALS['BE_USER'];
7216
    }
7217
7218
    /**
7219
     * @return TimeTracker
7220
     */
7221
    protected function getTimeTracker()
7222
    {
7223
        return GeneralUtility::makeInstance(TimeTracker::class);
7224
    }
7225
7226
    /**
7227
     * @return \TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController
7228
     */
7229
    protected function getTypoScriptFrontendController()
7230
    {
7231
        return $this->typoScriptFrontendController ?: $GLOBALS['TSFE'];
7232
    }
7233
}
7234