Passed
Branch master (6c65a4)
by Christian
16:31
created

ContentObjectRenderer::getQuery()   F

Complexity

Conditions 35
Paths > 20000

Size

Total Lines 196
Code Lines 125

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 196
rs 2
c 0
b 0
f 0
cc 35
eloc 125
nc 1881600
nop 3

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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\Area;
29
use TYPO3\CMS\Core\Imaging\ImageManipulation\CropVariantCollection;
30
use TYPO3\CMS\Core\LinkHandling\LinkService;
31
use TYPO3\CMS\Core\Log\LogManager;
32
use TYPO3\CMS\Core\Mail\MailMessage;
33
use TYPO3\CMS\Core\Resource\Exception;
34
use TYPO3\CMS\Core\Resource\Exception\ResourceDoesNotExistException;
35
use TYPO3\CMS\Core\Resource\File;
36
use TYPO3\CMS\Core\Resource\FileInterface;
37
use TYPO3\CMS\Core\Resource\FileReference;
38
use TYPO3\CMS\Core\Resource\Folder;
39
use TYPO3\CMS\Core\Resource\ProcessedFile;
40
use TYPO3\CMS\Core\Resource\ResourceFactory;
41
use TYPO3\CMS\Core\Resource\StorageRepository;
42
use TYPO3\CMS\Core\Service\DependencyOrderingService;
43
use TYPO3\CMS\Core\Service\MarkerBasedTemplateService;
44
use TYPO3\CMS\Core\TimeTracker\TimeTracker;
45
use TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser;
46
use TYPO3\CMS\Core\TypoScript\TypoScriptService;
47
use TYPO3\CMS\Core\Utility\ArrayUtility;
48
use TYPO3\CMS\Core\Utility\DebugUtility;
49
use TYPO3\CMS\Core\Utility\GeneralUtility;
50
use TYPO3\CMS\Core\Utility\MailUtility;
51
use TYPO3\CMS\Core\Utility\MathUtility;
52
use TYPO3\CMS\Core\Utility\PathUtility;
53
use TYPO3\CMS\Core\Utility\StringUtility;
54
use TYPO3\CMS\Core\Versioning\VersionState;
55
use TYPO3\CMS\Extbase\Service\FlexFormService;
56
use TYPO3\CMS\Frontend\ContentObject\Exception\ContentRenderingException;
57
use TYPO3\CMS\Frontend\ContentObject\Exception\ExceptionHandlerInterface;
58
use TYPO3\CMS\Frontend\ContentObject\Exception\ProductionExceptionHandler;
59
use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
60
use TYPO3\CMS\Frontend\Http\UrlProcessorInterface;
61
use TYPO3\CMS\Frontend\Imaging\GifBuilder;
62
use TYPO3\CMS\Frontend\Page\PageRepository;
63
use TYPO3\CMS\Frontend\Service\TypoLinkCodecService;
64
use TYPO3\CMS\Frontend\Typolink\AbstractTypolinkBuilder;
65
use TYPO3\CMS\Frontend\Typolink\UnableToLinkException;
66
67
/**
68
 * This class contains all main TypoScript features.
69
 * This includes the rendering of TypoScript content objects (cObjects).
70
 * Is the backbone of TypoScript Template rendering.
71
 *
72
 * There are lots of functions you can use from your include-scripts.
73
 * The class is normally instantiated and referred to as "cObj".
74
 * 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.
75
 */
76
class ContentObjectRenderer
77
{
78
    /**
79
     * @var array
80
     */
81
    public $align = [
82
        'center',
83
        'right',
84
        'left'
85
    ];
86
87
    /**
88
     * stdWrap functions in their correct order
89
     *
90
     * @see stdWrap()
91
     */
92
    public $stdWrapOrder = [
93
        'stdWrapPreProcess' => 'hook',
94
        // this is a placeholder for the first Hook
95
        'cacheRead' => 'hook',
96
        // this is a placeholder for checking if the content is available in cache
97
        'setContentToCurrent' => 'boolean',
98
        'setContentToCurrent.' => 'array',
99
        'addPageCacheTags' => 'string',
100
        'addPageCacheTags.' => 'array',
101
        'setCurrent' => 'string',
102
        'setCurrent.' => 'array',
103
        'lang.' => 'array',
104
        'data' => 'getText',
105
        'data.' => 'array',
106
        'field' => 'fieldName',
107
        'field.' => 'array',
108
        'current' => 'boolean',
109
        'current.' => 'array',
110
        'cObject' => 'cObject',
111
        'cObject.' => 'array',
112
        'numRows.' => 'array',
113
        'filelist' => 'dir',
114
        'filelist.' => 'array',
115
        'preUserFunc' => 'functionName',
116
        'stdWrapOverride' => 'hook',
117
        // this is a placeholder for the second Hook
118
        'override' => 'string',
119
        'override.' => 'array',
120
        'preIfEmptyListNum' => 'listNum',
121
        'preIfEmptyListNum.' => 'array',
122
        'ifNull' => 'string',
123
        'ifNull.' => 'array',
124
        'ifEmpty' => 'string',
125
        'ifEmpty.' => 'array',
126
        'ifBlank' => 'string',
127
        'ifBlank.' => 'array',
128
        'listNum' => 'listNum',
129
        'listNum.' => 'array',
130
        'trim' => 'boolean',
131
        'trim.' => 'array',
132
        'strPad.' => 'array',
133
        'stdWrap' => 'stdWrap',
134
        'stdWrap.' => 'array',
135
        'stdWrapProcess' => 'hook',
136
        // this is a placeholder for the third Hook
137
        'required' => 'boolean',
138
        'required.' => 'array',
139
        'if.' => 'array',
140
        'fieldRequired' => 'fieldName',
141
        'fieldRequired.' => 'array',
142
        'csConv' => 'string',
143
        'csConv.' => 'array',
144
        'parseFunc' => 'objectpath',
145
        'parseFunc.' => 'array',
146
        'HTMLparser' => 'boolean',
147
        'HTMLparser.' => 'array',
148
        'split.' => 'array',
149
        'replacement.' => 'array',
150
        'prioriCalc' => 'boolean',
151
        'prioriCalc.' => 'array',
152
        'char' => 'integer',
153
        'char.' => 'array',
154
        'intval' => 'boolean',
155
        'intval.' => 'array',
156
        'hash' => 'string',
157
        'hash.' => 'array',
158
        'round' => 'boolean',
159
        'round.' => 'array',
160
        'numberFormat.' => 'array',
161
        'expandList' => 'boolean',
162
        'expandList.' => 'array',
163
        'date' => 'dateconf',
164
        'date.' => 'array',
165
        'strtotime' => 'strtotimeconf',
166
        'strtotime.' => 'array',
167
        'strftime' => 'strftimeconf',
168
        'strftime.' => 'array',
169
        'age' => 'boolean',
170
        'age.' => 'array',
171
        'case' => 'case',
172
        'case.' => 'array',
173
        'bytes' => 'boolean',
174
        'bytes.' => 'array',
175
        'substring' => 'parameters',
176
        'substring.' => 'array',
177
        'cropHTML' => 'crop',
178
        'cropHTML.' => 'array',
179
        'stripHtml' => 'boolean',
180
        'stripHtml.' => 'array',
181
        'crop' => 'crop',
182
        'crop.' => 'array',
183
        'rawUrlEncode' => 'boolean',
184
        'rawUrlEncode.' => 'array',
185
        'htmlSpecialChars' => 'boolean',
186
        'htmlSpecialChars.' => 'array',
187
        'encodeForJavaScriptValue' => 'boolean',
188
        'encodeForJavaScriptValue.' => 'array',
189
        'doubleBrTag' => 'string',
190
        'doubleBrTag.' => 'array',
191
        'br' => 'boolean',
192
        'br.' => 'array',
193
        'brTag' => 'string',
194
        'brTag.' => 'array',
195
        'encapsLines.' => 'array',
196
        'keywords' => 'boolean',
197
        'keywords.' => 'array',
198
        'innerWrap' => 'wrap',
199
        'innerWrap.' => 'array',
200
        'innerWrap2' => 'wrap',
201
        'innerWrap2.' => 'array',
202
        'addParams.' => 'array',
203
        'filelink.' => 'array',
204
        'preCObject' => 'cObject',
205
        'preCObject.' => 'array',
206
        'postCObject' => 'cObject',
207
        'postCObject.' => 'array',
208
        'wrapAlign' => 'align',
209
        'wrapAlign.' => 'array',
210
        'typolink.' => 'array',
211
        'wrap' => 'wrap',
212
        'wrap.' => 'array',
213
        'noTrimWrap' => 'wrap',
214
        'noTrimWrap.' => 'array',
215
        'wrap2' => 'wrap',
216
        'wrap2.' => 'array',
217
        'dataWrap' => 'dataWrap',
218
        'dataWrap.' => 'array',
219
        'prepend' => 'cObject',
220
        'prepend.' => 'array',
221
        'append' => 'cObject',
222
        'append.' => 'array',
223
        'wrap3' => 'wrap',
224
        'wrap3.' => 'array',
225
        'orderedStdWrap' => 'stdWrap',
226
        'orderedStdWrap.' => 'array',
227
        'outerWrap' => 'wrap',
228
        'outerWrap.' => 'array',
229
        'insertData' => 'boolean',
230
        'insertData.' => 'array',
231
        'postUserFunc' => 'functionName',
232
        'postUserFuncInt' => 'functionName',
233
        'prefixComment' => 'string',
234
        'prefixComment.' => 'array',
235
        'editIcons' => 'string',
236
        'editIcons.' => 'array',
237
        'editPanel' => 'boolean',
238
        'editPanel.' => 'array',
239
        'cacheStore' => 'hook',
240
        // this is a placeholder for storing the content in cache
241
        'stdWrapPostProcess' => 'hook',
242
        // this is a placeholder for the last Hook
243
        'debug' => 'boolean',
244
        'debug.' => 'array',
245
        'debugFunc' => 'boolean',
246
        'debugFunc.' => 'array',
247
        'debugData' => 'boolean',
248
        'debugData.' => 'array'
249
    ];
250
251
    /**
252
     * Class names for accordant content object names
253
     *
254
     * @var array
255
     */
256
    protected $contentObjectClassMap = [];
257
258
    /**
259
     * Loaded with the current data-record.
260
     *
261
     * If the instance of this class is used to render records from the database those records are found in this array.
262
     * The function stdWrap has TypoScript properties that fetch field-data from this array.
263
     *
264
     * @var array
265
     * @see start()
266
     */
267
    public $data = [];
268
269
    /**
270
     * @var string
271
     */
272
    protected $table = '';
273
274
    /**
275
     * Used for backup
276
     *
277
     * @var array
278
     */
279
    public $oldData = [];
280
281
    /**
282
     * If this is set with an array before stdWrap, it's used instead of $this->data in the data-property in stdWrap
283
     *
284
     * @var string
285
     */
286
    public $alternativeData = '';
287
288
    /**
289
     * Used by the parseFunc function and is loaded with tag-parameters when parsing tags.
290
     *
291
     * @var array
292
     */
293
    public $parameters = [];
294
295
    /**
296
     * @var string
297
     */
298
    public $currentValKey = 'currentValue_kidjls9dksoje';
299
300
    /**
301
     * This is set to the [table]:[uid] of the record delivered in the $data-array, if the cObjects CONTENT or RECORD is in operation.
302
     * Note that $GLOBALS['TSFE']->currentRecord is set to an equal value but always indicating the latest record rendered.
303
     *
304
     * @var string
305
     */
306
    public $currentRecord = '';
307
308
    /**
309
     * Set in RecordsContentObject and ContentContentObject to the current number of records selected in a query.
310
     *
311
     * @var int
312
     */
313
    public $currentRecordTotal = 0;
314
315
    /**
316
     * Incremented in RecordsContentObject and ContentContentObject before each record rendering.
317
     *
318
     * @var int
319
     */
320
    public $currentRecordNumber = 0;
321
322
    /**
323
     * Incremented in RecordsContentObject and ContentContentObject before each record rendering.
324
     *
325
     * @var int
326
     */
327
    public $parentRecordNumber = 0;
328
329
    /**
330
     * 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.
331
     *
332
     * @var array
333
     */
334
    public $parentRecord = [];
335
336
    /**
337
     * 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.
338
     *
339
     * @var array
340
     */
341
    public $checkPid_cache = [];
342
343
    /**
344
     * @var string
345
     */
346
    public $checkPid_badDoktypeList = '255';
347
348
    /**
349
     * This will be set by typoLink() to the url of the most recent link created.
350
     *
351
     * @var string
352
     */
353
    public $lastTypoLinkUrl = '';
354
355
    /**
356
     * DO. link target.
357
     *
358
     * @var string
359
     */
360
    public $lastTypoLinkTarget = '';
361
362
    /**
363
     * @var array
364
     */
365
    public $lastTypoLinkLD = [];
366
367
    /**
368
     * array that registers rendered content elements (or any table) to make sure they are not rendered recursively!
369
     *
370
     * @var array
371
     */
372
    public $recordRegister = [];
373
374
    /**
375
     * Additionally registered content object types and class names
376
     *
377
     * @var array
378
     */
379
    protected $cObjHookObjectsRegistry = [];
380
381
    /**
382
     * @var array
383
     */
384
    public $cObjHookObjectsArr = [];
385
386
    /**
387
     * Containing hook objects for stdWrap
388
     *
389
     * @var array
390
     */
391
    protected $stdWrapHookObjects = [];
392
393
    /**
394
     * Containing hook objects for getImgResource
395
     *
396
     * @var array
397
     */
398
    protected $getImgResourceHookObjects;
399
400
    /**
401
     * @var File Current file objects (during iterations over files)
402
     */
403
    protected $currentFile = null;
404
405
    /**
406
     * Set to TRUE by doConvertToUserIntObject() if USER object wants to become USER_INT
407
     */
408
    public $doConvertToUserIntObject = false;
409
410
    /**
411
     * Indicates current object type. Can hold one of OBJECTTYPE_ constants or FALSE.
412
     * The value is set and reset inside USER() function. Any time outside of
413
     * USER() it is FALSE.
414
     */
415
    protected $userObjectType = false;
416
417
    /**
418
     * @var array
419
     */
420
    protected $stopRendering = [];
421
422
    /**
423
     * @var int
424
     */
425
    protected $stdWrapRecursionLevel = 0;
426
427
    /**
428
     * @var TypoScriptFrontendController
429
     */
430
    protected $typoScriptFrontendController;
431
432
    /**
433
     * @var MarkerBasedTemplateService
434
     */
435
    protected $templateService;
436
437
    /**
438
     * Indicates that object type is USER.
439
     *
440
     * @see ContentObjectRender::$userObjectType
441
     */
442
    const OBJECTTYPE_USER_INT = 1;
443
    /**
444
     * Indicates that object type is USER.
445
     *
446
     * @see ContentObjectRender::$userObjectType
447
     */
448
    const OBJECTTYPE_USER = 2;
449
450
    /**
451
     * @param TypoScriptFrontendController $typoScriptFrontendController
452
     */
453
    public function __construct(TypoScriptFrontendController $typoScriptFrontendController = null)
454
    {
455
        $this->typoScriptFrontendController = $typoScriptFrontendController;
456
        $this->contentObjectClassMap = $GLOBALS['TYPO3_CONF_VARS']['FE']['ContentObjects'];
457
        $this->templateService = GeneralUtility::makeInstance(MarkerBasedTemplateService::class);
458
    }
459
460
    /**
461
     * Prevent several objects from being serialized.
462
     * If currentFile is set, it is either a File or a FileReference object. As the object itself can't be serialized,
463
     * we have store a hash and restore the object in __wakeup()
464
     *
465
     * @return array
466
     */
467
    public function __sleep()
468
    {
469
        $vars = get_object_vars($this);
470
        unset($vars['typoScriptFrontendController']);
471
        if ($this->currentFile instanceof FileReference) {
472
            $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...
473
        } elseif ($this->currentFile instanceof File) {
474
            $this->currentFile = 'File:' . $this->currentFile->getIdentifier();
475
        } else {
476
            unset($vars['currentFile']);
477
        }
478
        return array_keys($vars);
479
    }
480
481
    /**
482
     * Restore currentFile from hash.
483
     * If currentFile references a File, the identifier equals file identifier.
484
     * If it references a FileReference the identifier equals the uid of the reference.
485
     */
486
    public function __wakeup()
487
    {
488
        if (isset($GLOBALS['TSFE'])) {
489
            $this->typoScriptFrontendController = $GLOBALS['TSFE'];
490
        }
491
        if ($this->currentFile !== null && is_string($this->currentFile)) {
492
            list($objectType, $identifier) = explode(':', $this->currentFile, 2);
493
            try {
494
                if ($objectType === 'File') {
495
                    $this->currentFile = ResourceFactory::getInstance()->retrieveFileOrFolderObject($identifier);
496
                } elseif ($objectType === 'FileReference') {
497
                    $this->currentFile = ResourceFactory::getInstance()->getFileReferenceObject($identifier);
498
                }
499
            } catch (ResourceDoesNotExistException $e) {
500
                $this->currentFile = null;
501
            }
502
        }
503
    }
504
505
    /**
506
     * Allow injecting content object class map.
507
     *
508
     * This method is private API, please use configuration
509
     * $GLOBALS['TYPO3_CONF_VARS']['FE']['ContentObjects'] to add new content objects
510
     *
511
     * @internal
512
     * @param array $contentObjectClassMap
513
     */
514
    public function setContentObjectClassMap(array $contentObjectClassMap)
515
    {
516
        $this->contentObjectClassMap = $contentObjectClassMap;
517
    }
518
519
    /**
520
     * Register a single content object name to class name
521
     *
522
     * This method is private API, please use configuration
523
     * $GLOBALS['TYPO3_CONF_VARS']['FE']['ContentObjects'] to add new content objects
524
     *
525
     * @param string $className
526
     * @param string $contentObjectName
527
     * @internal
528
     */
529
    public function registerContentObjectClass($className, $contentObjectName)
530
    {
531
        $this->contentObjectClassMap[$contentObjectName] = $className;
532
    }
533
534
    /**
535
     * Class constructor.
536
     * Well, it has to be called manually since it is not a real constructor function.
537
     * 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.
538
     *
539
     * @param array $data The record data that is rendered.
540
     * @param string $table The table that the data record is from.
541
     */
542
    public function start($data, $table = '')
543
    {
544
        $this->data = $data;
545
        $this->table = $table;
546
        $this->currentRecord = $table !== '' ? $table . ':' . $this->data['uid'] : '';
547
        $this->parameters = [];
548
        foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['cObjTypeAndClass'] ?? [] as $classArr) {
549
            $this->cObjHookObjectsRegistry[$classArr[0]] = $classArr[1];
550
        }
551
        $this->stdWrapHookObjects = [];
552
        foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['stdWrap'] ?? [] as $className) {
553
            $hookObject = GeneralUtility::makeInstance($className);
554
            if (!$hookObject instanceof ContentObjectStdWrapHookInterface) {
555
                throw new \UnexpectedValueException($className . ' must implement interface ' . ContentObjectStdWrapHookInterface::class, 1195043965);
556
            }
557
            $this->stdWrapHookObjects[] = $hookObject;
558
        }
559
        foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['postInit'] ?? [] as $className) {
560
            $postInitializationProcessor = GeneralUtility::makeInstance($className);
561
            if (!$postInitializationProcessor instanceof ContentObjectPostInitHookInterface) {
562
                throw new \UnexpectedValueException($className . ' must implement interface ' . ContentObjectPostInitHookInterface::class, 1274563549);
563
            }
564
            $postInitializationProcessor->postProcessContentObjectInitialization($this);
565
        }
566
    }
567
568
    /**
569
     * Returns the current table
570
     *
571
     * @return string
572
     */
573
    public function getCurrentTable()
574
    {
575
        return $this->table;
576
    }
577
578
    /**
579
     * Gets the 'getImgResource' hook objects.
580
     * The first call initializes the accordant objects.
581
     *
582
     * @return array The 'getImgResource' hook objects (if any)
583
     */
584
    protected function getGetImgResourceHookObjects()
585
    {
586
        if (!isset($this->getImgResourceHookObjects)) {
587
            $this->getImgResourceHookObjects = [];
588
            foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['getImgResource'] ?? [] as $className) {
589
                $hookObject = GeneralUtility::makeInstance($className);
590
                if (!$hookObject instanceof ContentObjectGetImageResourceHookInterface) {
591
                    throw new \UnexpectedValueException('$hookObject must implement interface ' . ContentObjectGetImageResourceHookInterface::class, 1218636383);
592
                }
593
                $this->getImgResourceHookObjects[] = $hookObject;
594
            }
595
        }
596
        return $this->getImgResourceHookObjects;
597
    }
598
599
    /**
600
     * Sets the internal variable parentRecord with information about current record.
601
     * 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.
602
     *
603
     * @param array $data The record array
604
     * @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.
605
     * @access private
606
     */
607
    public function setParent($data, $currentRecord)
608
    {
609
        $this->parentRecord = [
610
            'data' => $data,
611
            'currentRecord' => $currentRecord
612
        ];
613
    }
614
615
    /***********************************************
616
     *
617
     * CONTENT_OBJ:
618
     *
619
     ***********************************************/
620
    /**
621
     * Returns the "current" value.
622
     * 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.
623
     * It's like "load accumulator" in the good old C64 days... basically a "register" you can use as you like.
624
     * 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.
625
     *
626
     * @return mixed The "current" value
627
     */
628
    public function getCurrentVal()
629
    {
630
        return $this->data[$this->currentValKey];
631
    }
632
633
    /**
634
     * Sets the "current" value.
635
     *
636
     * @param mixed $value The variable that you want to set as "current
637
     * @see getCurrentVal()
638
     */
639
    public function setCurrentVal($value)
640
    {
641
        $this->data[$this->currentValKey] = $value;
642
    }
643
644
    /**
645
     * Rendering of a "numerical array" of cObjects from TypoScript
646
     * Will call ->cObjGetSingle() for each cObject found and accumulate the output.
647
     *
648
     * @param array $setup array with cObjects as values.
649
     * @param string $addKey A prefix for the debugging information
650
     * @return string Rendered output from the cObjects in the array.
651
     * @see cObjGetSingle()
652
     */
653
    public function cObjGet($setup, $addKey = '')
654
    {
655
        if (!is_array($setup)) {
656
            return '';
657
        }
658
        $sKeyArray = ArrayUtility::filterAndSortByNumericKeys($setup);
659
        $content = '';
660
        foreach ($sKeyArray as $theKey) {
661
            $theValue = $setup[$theKey];
662
            if ((int)$theKey && strpos($theKey, '.') === false) {
663
                $conf = $setup[$theKey . '.'];
664
                $content .= $this->cObjGetSingle($theValue, $conf, $addKey . $theKey);
665
            }
666
        }
667
        return $content;
668
    }
669
670
    /**
671
     * Renders a content object
672
     *
673
     * @param string $name The content object name, eg. "TEXT" or "USER" or "IMAGE
674
     * @param array $conf The array with TypoScript properties for the content object
675
     * @param string $TSkey A string label used for the internal debugging tracking.
676
     * @return string cObject output
677
     * @throws \UnexpectedValueException
678
     */
679
    public function cObjGetSingle($name, $conf, $TSkey = '__')
680
    {
681
        $content = '';
682
        // Checking that the function is not called eternally. This is done by interrupting at a depth of 100
683
        $this->getTypoScriptFrontendController()->cObjectDepthCounter--;
684
        if ($this->getTypoScriptFrontendController()->cObjectDepthCounter > 0) {
685
            $timeTracker = $this->getTimeTracker();
686
            $name = trim($name);
687
            if ($timeTracker->LR) {
688
                $timeTracker->push($TSkey, $name);
689
            }
690
            // Checking if the COBJ is a reference to another object. (eg. name of 'blabla.blabla = < styles.something')
691
            if ($name[0] === '<') {
692
                $key = trim(substr($name, 1));
693
                $cF = GeneralUtility::makeInstance(TypoScriptParser::class);
694
                // $name and $conf is loaded with the referenced values.
695
                $confOverride = is_array($conf) ? $conf : [];
696
                list($name, $conf) = $cF->getVal($key, $this->getTypoScriptFrontendController()->tmpl->setup);
697
                $conf = array_replace_recursive(is_array($conf) ? $conf : [], $confOverride);
698
                // Getting the cObject
699
                $timeTracker->incStackPointer();
700
                $content .= $this->cObjGetSingle($name, $conf, $key);
701
                $timeTracker->decStackPointer();
702
            } else {
703
                $hooked = false;
704
                // Application defined cObjects
705
                if (!empty($this->cObjHookObjectsRegistry[$name])) {
706
                    if (empty($this->cObjHookObjectsArr[$name])) {
707
                        $this->cObjHookObjectsArr[$name] = GeneralUtility::makeInstance($this->cObjHookObjectsRegistry[$name]);
708
                    }
709
                    $hookObj = $this->cObjHookObjectsArr[$name];
710
                    if (method_exists($hookObj, 'cObjGetSingleExt')) {
711
                        $content .= $hookObj->cObjGetSingleExt($name, $conf, $TSkey, $this);
712
                        $hooked = true;
713
                    }
714
                }
715
                if (!$hooked) {
716
                    $contentObject = $this->getContentObject($name);
717
                    if ($contentObject) {
718
                        $content .= $this->render($contentObject, $conf);
719
                    } else {
720
                        // Call hook functions for extra processing
721
                        if ($name) {
722
                            foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['cObjTypeAndClassDefault'] ?? [] as $className) {
723
                                $hookObject = GeneralUtility::makeInstance($className);
724
                                if (!$hookObject instanceof ContentObjectGetSingleHookInterface) {
725
                                    throw new \UnexpectedValueException('$hookObject must implement interface ' . ContentObjectGetSingleHookInterface::class, 1195043731);
726
                                }
727
                                /** @var $hookObject ContentObjectGetSingleHookInterface */
728
                                $content .= $hookObject->getSingleContentObject($name, (array)$conf, $TSkey, $this);
729
                            }
730
                        } else {
731
                            // Log error in AdminPanel
732
                            $warning = sprintf('Content Object "%s" does not exist', $name);
733
                            $timeTracker->setTSlogMessage($warning, 2);
734
                        }
735
                    }
736
                }
737
            }
738
            if ($timeTracker->LR) {
739
                $timeTracker->pull($content);
740
            }
741
        }
742
        // Increasing on exit...
743
        $this->getTypoScriptFrontendController()->cObjectDepthCounter++;
744
        return $content;
745
    }
746
747
    /**
748
     * Returns a new content object of type $name.
749
     * This content object needs to be registered as content object
750
     * in $this->contentObjectClassMap
751
     *
752
     * @param string $name
753
     * @return AbstractContentObject|null
754
     * @throws ContentRenderingException
755
     */
756
    public function getContentObject($name)
757
    {
758
        if (!isset($this->contentObjectClassMap[$name])) {
759
            return null;
760
        }
761
        $fullyQualifiedClassName = $this->contentObjectClassMap[$name];
762
        $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

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

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

1319
                        $url = $altUrl . ($conf['JSwindow.']['altUrl_noDefaultParams'] ? '' : '?file=' . rawurlencode(/** @scrutinizer ignore-type */ $imageFile) . $params);
Loading history...
1320
                    }
1321
                }
1322
1323
                $processedFile = $file->process(ProcessedFile::CONTEXT_IMAGECROPSCALEMASK, $conf);
1324
                $JSwindowExpand = isset($conf['JSwindow.']['expand.']) ? $this->stdWrap($conf['JSwindow.']['expand'], $conf['JSwindow.']['expand.']) : $conf['JSwindow.']['expand'];
1325
                $offset = GeneralUtility::intExplode(',', $JSwindowExpand . ',');
1326
                $newWindow = isset($conf['JSwindow.']['newWindow.']) ? $this->stdWrap($conf['JSwindow.']['newWindow'], $conf['JSwindow.']['newWindow.']) : $conf['JSwindow.']['newWindow'];
1327
                $onClick = 'openPic('
1328
                    . 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

1328
                    . GeneralUtility::quoteJSvalue($this->getTypoScriptFrontendController()->baseUrlWrap(/** @scrutinizer ignore-type */ $url)) . ','
Loading history...
1329
                    . '\'' . ($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

1329
                    . '\'' . ($newWindow ? md5(/** @scrutinizer ignore-type */ $url) : 'thePicture') . '\','
Loading history...
1330
                    . GeneralUtility::quoteJSvalue('width=' . ($processedFile->getProperty('width') + $offset[0])
1331
                        . ',height=' . ($processedFile->getProperty('height') + $offset[1]) . ',status=0,menubar=0')
1332
                    . '); return false;';
1333
                $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

1333
                $a1 = '<a href="' . htmlspecialchars(/** @scrutinizer ignore-type */ $url) . '"'
Loading history...
1334
                    . ' onclick="' . htmlspecialchars($onClick) . '"'
1335
                    . ($target !== '' ? ' target="' . htmlspecialchars($target) . '"' : '')
1336
                    . $this->getTypoScriptFrontendController()->ATagParams . '>';
1337
                $a2 = '</a>';
1338
                $this->getTypoScriptFrontendController()->setJS('openPic');
1339
            } else {
1340
                $conf['linkParams.']['parameter'] = $url;
1341
                $string = $this->typoLink($string, $conf['linkParams.']);
1342
            }
1343
            if (isset($conf['stdWrap.'])) {
1344
                $string = $this->stdWrap($string, $conf['stdWrap.']);
1345
            }
1346
            $content = $a1 . $string . $a2;
1347
        }
1348
        return $content;
1349
    }
1350
1351
    /**
1352
     * Sets the SYS_LASTCHANGED timestamp if input timestamp is larger than current value.
1353
     * The SYS_LASTCHANGED timestamp can be used by various caching/indexing applications to determine if the page has new content.
1354
     * Therefore you should call this function with the last-changed timestamp of any element you display.
1355
     *
1356
     * @param int $tstamp Unix timestamp (number of seconds since 1970)
1357
     * @see TypoScriptFrontendController::setSysLastChanged()
1358
     */
1359
    public function lastChanged($tstamp)
1360
    {
1361
        $tstamp = (int)$tstamp;
1362
        $tsfe = $this->getTypoScriptFrontendController();
1363
        if ($tstamp > (int)$tsfe->register['SYS_LASTCHANGED']) {
1364
            $tsfe->register['SYS_LASTCHANGED'] = $tstamp;
1365
        }
1366
    }
1367
1368
    /**
1369
     * Wraps the input string by the $wrap value and implements the "linkWrap" data type as well.
1370
     * 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.
1371
     *
1372
     * @param string $content Input string
1373
     * @param string $wrap A string where the first two parts separated by "|" (vertical line) will be wrapped around the input string
1374
     * @return string Wrapped output string
1375
     * @see wrap(), cImage(), FILE()
1376
     */
1377
    public function linkWrap($content, $wrap)
1378
    {
1379
        $wrapArr = explode('|', $wrap);
1380
        if (preg_match('/\\{([0-9]*)\\}/', $wrapArr[0], $reg)) {
1381
            if ($uid = $this->getTypoScriptFrontendController()->tmpl->rootLine[$reg[1]]['uid']) {
1382
                $wrapArr[0] = str_replace($reg[0], $uid, $wrapArr[0]);
1383
            }
1384
        }
1385
        return trim($wrapArr[0]) . $content . trim($wrapArr[1]);
1386
    }
1387
1388
    /**
1389
     * An abstraction method which creates an alt or title parameter for an HTML img, applet, area or input element and the FILE content element.
1390
     * From the $conf array it implements the properties "altText", "titleText" and "longdescURL"
1391
     *
1392
     * @param array $conf TypoScript configuration properties
1393
     * @param bool $longDesc If set, the longdesc attribute will be generated - must only be used for img elements!
1394
     * @return string Parameter string containing alt and title parameters (if any)
1395
     * @see IMGTEXT(), FILE(), FORM(), cImage(), filelink()
1396
     */
1397
    public function getAltParam($conf, $longDesc = true)
1398
    {
1399
        $altText = isset($conf['altText.']) ? trim($this->stdWrap($conf['altText'], $conf['altText.'])) : trim($conf['altText']);
1400
        $titleText = isset($conf['titleText.']) ? trim($this->stdWrap($conf['titleText'], $conf['titleText.'])) : trim($conf['titleText']);
1401
        if (isset($conf['longdescURL.']) && $this->getTypoScriptFrontendController()->config['config']['doctype'] != 'html5') {
1402
            $longDescUrl = $this->typoLink_URL($conf['longdescURL.']);
1403
        } else {
1404
            $longDescUrl = trim($conf['longdescURL']);
1405
        }
1406
        $longDescUrl = strip_tags($longDescUrl);
1407
1408
        // "alt":
1409
        $altParam = ' alt="' . htmlspecialchars($altText) . '"';
1410
        // "title":
1411
        $emptyTitleHandling = isset($conf['emptyTitleHandling.']) ? $this->stdWrap($conf['emptyTitleHandling'], $conf['emptyTitleHandling.']) : $conf['emptyTitleHandling'];
1412
        // Choices: 'keepEmpty' | 'useAlt' | 'removeAttr'
1413
        if ($titleText || $emptyTitleHandling === 'keepEmpty') {
1414
            $altParam .= ' title="' . htmlspecialchars($titleText) . '"';
1415
        } elseif (!$titleText && $emptyTitleHandling === 'useAlt') {
1416
            $altParam .= ' title="' . htmlspecialchars($altText) . '"';
1417
        }
1418
        // "longDesc" URL
1419
        if ($longDesc && !empty($longDescUrl)) {
1420
            $altParam .= ' longdesc="' . htmlspecialchars($longDescUrl) . '"';
1421
        }
1422
        return $altParam;
1423
    }
1424
1425
    /**
1426
     * An abstraction method to add parameters to an A tag.
1427
     * Uses the ATagParams property.
1428
     *
1429
     * @param array $conf TypoScript configuration properties
1430
     * @param bool|int $addGlobal If set, will add the global config.ATagParams to the link
1431
     * @return string String containing the parameters to the A tag (if non empty, with a leading space)
1432
     * @see IMGTEXT(), filelink(), makelinks(), typolink()
1433
     */
1434
    public function getATagParams($conf, $addGlobal = 1)
1435
    {
1436
        $aTagParams = '';
1437
        if ($conf['ATagParams.']) {
1438
            $aTagParams = ' ' . $this->stdWrap($conf['ATagParams'], $conf['ATagParams.']);
1439
        } elseif ($conf['ATagParams']) {
1440
            $aTagParams = ' ' . $conf['ATagParams'];
1441
        }
1442
        if ($addGlobal) {
1443
            $aTagParams = ' ' . trim($this->getTypoScriptFrontendController()->ATagParams . $aTagParams);
1444
        }
1445
        // Extend params
1446
        $_params = [
1447
            'conf' => &$conf,
1448
            'aTagParams' => &$aTagParams
1449
        ];
1450
        foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['getATagParamsPostProc'] ?? [] as $className) {
1451
            $processor =& GeneralUtility::makeInstance($className);
1452
            $aTagParams = $processor->process($_params, $this);
1453
        }
1454
1455
        $aTagParams = trim($aTagParams);
1456
        if (!empty($aTagParams)) {
1457
            $aTagParams = ' ' . $aTagParams;
1458
        }
1459
1460
        return $aTagParams;
1461
    }
1462
1463
    /**
1464
     * All extension links should ask this function for additional properties to their tags.
1465
     * Designed to add for instance an "onclick" property for site tracking systems.
1466
     *
1467
     * @param string $URL URL of the website
1468
     * @param string $TYPE
1469
     * @return string The additional tag properties
1470
     */
1471
    public function extLinkATagParams($URL, $TYPE)
1472
    {
1473
        $out = '';
1474
        if ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['extLinkATagParamsHandler']) {
1475
            $extLinkATagParamsHandler = GeneralUtility::makeInstance($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['extLinkATagParamsHandler']);
1476
            if (method_exists($extLinkATagParamsHandler, 'main')) {
1477
                $out .= trim($extLinkATagParamsHandler->main($URL, $TYPE, $this));
1478
            }
1479
        }
1480
        return trim($out) ? ' ' . trim($out) : '';
1481
    }
1482
1483
    /***********************************************
1484
     *
1485
     * HTML template processing functions
1486
     *
1487
     ***********************************************/
1488
1489
    /**
1490
     * Sets the current file object during iterations over files.
1491
     *
1492
     * @param File $fileObject The file object.
1493
     */
1494
    public function setCurrentFile($fileObject)
1495
    {
1496
        $this->currentFile = $fileObject;
1497
    }
1498
1499
    /**
1500
     * Gets the current file object during iterations over files.
1501
     *
1502
     * @return File The current file object.
1503
     */
1504
    public function getCurrentFile()
1505
    {
1506
        return $this->currentFile;
1507
    }
1508
1509
    /***********************************************
1510
     *
1511
     * "stdWrap" + sub functions
1512
     *
1513
     ***********************************************/
1514
    /**
1515
     * The "stdWrap" function. This is the implementation of what is known as "stdWrap properties" in TypoScript.
1516
     * Basically "stdWrap" performs some processing of a value based on properties in the input $conf array(holding the TypoScript "stdWrap properties")
1517
     * 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.
1518
     *
1519
     * If $this->alternativeData is an array it's used instead of the $this->data array in ->getData
1520
     *
1521
     * @param string $content Input value undergoing processing in this function. Possibly substituted by other values fetched from another source.
1522
     * @param array $conf TypoScript "stdWrap properties".
1523
     * @return string The processed input value
1524
     */
1525
    public function stdWrap($content = '', $conf = [])
1526
    {
1527
        $content = (string)$content;
1528
        // If there is any hook object, activate all of the process and override functions.
1529
        // The hook interface ContentObjectStdWrapHookInterface takes care that all 4 methods exist.
1530
        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...
1531
            $conf['stdWrapPreProcess'] = 1;
1532
            $conf['stdWrapOverride'] = 1;
1533
            $conf['stdWrapProcess'] = 1;
1534
            $conf['stdWrapPostProcess'] = 1;
1535
        }
1536
1537
        if (!is_array($conf) || !$conf) {
1538
            return $content;
1539
        }
1540
1541
        // Cache handling
1542
        if (is_array($conf['cache.'])) {
1543
            $conf['cache.']['key'] = $this->stdWrap($conf['cache.']['key'], $conf['cache.']['key.']);
1544
            $conf['cache.']['tags'] = $this->stdWrap($conf['cache.']['tags'], $conf['cache.']['tags.']);
1545
            $conf['cache.']['lifetime'] = $this->stdWrap($conf['cache.']['lifetime'], $conf['cache.']['lifetime.']);
1546
            $conf['cacheRead'] = 1;
1547
            $conf['cacheStore'] = 1;
1548
        }
1549
        // The configuration is sorted and filtered by intersection with the defined stdWrapOrder.
1550
        $sortedConf = array_keys(array_intersect_key($this->stdWrapOrder, $conf));
1551
        // Functions types that should not make use of nested stdWrap function calls to avoid conflicts with internal TypoScript used by these functions
1552
        $stdWrapDisabledFunctionTypes = 'cObject,functionName,stdWrap';
1553
        // Additional Array to check whether a function has already been executed
1554
        $isExecuted = [];
1555
        // Additional switch to make sure 'required', 'if' and 'fieldRequired'
1556
        // will still stop rendering immediately in case they return FALSE
1557
        $this->stdWrapRecursionLevel++;
1558
        $this->stopRendering[$this->stdWrapRecursionLevel] = false;
1559
        // execute each function in the predefined order
1560
        foreach ($sortedConf as $stdWrapName) {
1561
            // eliminate the second key of a pair 'key'|'key.' to make sure functions get called only once and check if rendering has been stopped
1562
            if (!$isExecuted[$stdWrapName] && !$this->stopRendering[$this->stdWrapRecursionLevel]) {
1563
                $functionName = rtrim($stdWrapName, '.');
1564
                $functionProperties = $functionName . '.';
1565
                $functionType = $this->stdWrapOrder[$functionName];
1566
                // If there is any code on the next level, check if it contains "official" stdWrap functions
1567
                // if yes, execute them first - will make each function stdWrap aware
1568
                // so additional stdWrap calls within the functions can be removed, since the result will be the same
1569
                if (!empty($conf[$functionProperties]) && !GeneralUtility::inList($stdWrapDisabledFunctionTypes, $functionType)) {
1570
                    if (array_intersect_key($this->stdWrapOrder, $conf[$functionProperties])) {
1571
                        $conf[$functionName] = $this->stdWrap($conf[$functionName], $conf[$functionProperties]);
1572
                    }
1573
                }
1574
                // Check if key is still containing something, since it might have been changed by next level stdWrap before
1575
                if ((isset($conf[$functionName]) || $conf[$functionProperties]) && ($functionType !== 'boolean' || $conf[$functionName])) {
1576
                    // Get just that part of $conf that is needed for the particular function
1577
                    $singleConf = [
1578
                        $functionName => $conf[$functionName],
1579
                        $functionProperties => $conf[$functionProperties]
1580
                    ];
1581
                    // Hand over the whole $conf array to the stdWrapHookObjects
1582
                    if ($functionType === 'hook') {
1583
                        $singleConf = $conf;
1584
                    }
1585
                    // Add both keys - with and without the dot - to the set of executed functions
1586
                    $isExecuted[$functionName] = true;
1587
                    $isExecuted[$functionProperties] = true;
1588
                    // Call the function with the prefix stdWrap_ to make sure nobody can execute functions just by adding their name to the TS Array
1589
                    $functionName = 'stdWrap_' . $functionName;
1590
                    $content = $this->{$functionName}($content, $singleConf);
1591
                } elseif ($functionType === 'boolean' && !$conf[$functionName]) {
1592
                    $isExecuted[$functionName] = true;
1593
                    $isExecuted[$functionProperties] = true;
1594
                }
1595
            }
1596
        }
1597
        unset($this->stopRendering[$this->stdWrapRecursionLevel]);
1598
        $this->stdWrapRecursionLevel--;
1599
1600
        return $content;
1601
    }
1602
1603
    /**
1604
     * Gets a configuration value by passing them through stdWrap first and taking a default value if stdWrap doesn't yield a result.
1605
     *
1606
     * @param string $key The config variable key (from TS array).
1607
     * @param array $config The TypoScript array.
1608
     * @param string $defaultValue Optional default value.
1609
     * @return string Value of the config variable
1610
     */
1611
    public function stdWrapValue($key, array $config, $defaultValue = '')
1612
    {
1613
        if (isset($config[$key])) {
1614
            if (!isset($config[$key . '.'])) {
1615
                return $config[$key];
1616
            }
1617
        } elseif (isset($config[$key . '.'])) {
1618
            $config[$key] = '';
1619
        } else {
1620
            return $defaultValue;
1621
        }
1622
        $stdWrapped = $this->stdWrap($config[$key], $config[$key . '.']);
1623
        return $stdWrapped ?: $defaultValue;
1624
    }
1625
1626
    /**
1627
     * stdWrap pre process hook
1628
     * can be used by extensions authors to modify the behaviour of stdWrap functions to their needs
1629
     * this hook will execute functions before any other stdWrap function can modify anything
1630
     *
1631
     * @param string $content Input value undergoing processing in these functions.
1632
     * @param array $conf All stdWrap properties, not just the ones for a particular function.
1633
     * @return string The processed input value
1634
     */
1635
    public function stdWrap_stdWrapPreProcess($content = '', $conf = [])
1636
    {
1637
        foreach ($this->stdWrapHookObjects as $hookObject) {
1638
            /** @var ContentObjectStdWrapHookInterface $hookObject */
1639
            $content = $hookObject->stdWrapPreProcess($content, $conf, $this);
1640
        }
1641
        return $content;
1642
    }
1643
1644
    /**
1645
     * Check if content was cached before (depending on the given cache key)
1646
     *
1647
     * @param string $content Input value undergoing processing in these functions.
1648
     * @param array $conf All stdWrap properties, not just the ones for a particular function.
1649
     * @return string The processed input value
1650
     */
1651
    public function stdWrap_cacheRead($content = '', $conf = [])
1652
    {
1653
        if (!isset($conf['cache.'])) {
1654
            return $content;
1655
        }
1656
        $result = $this->getFromCache($conf['cache.']);
1657
        return $result === false ? $content : $result;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $result === false ? $content : $result also could return the type true which is incompatible with the documented return type string.
Loading history...
1658
    }
1659
1660
    /**
1661
     * Add tags to page cache (comma-separated list)
1662
     *
1663
     * @param string $content Input value undergoing processing in these functions.
1664
     * @param array $conf All stdWrap properties, not just the ones for a particular function.
1665
     * @return string The processed input value
1666
     */
1667
    public function stdWrap_addPageCacheTags($content = '', $conf = [])
1668
    {
1669
        $tags = isset($conf['addPageCacheTags.'])
1670
            ? $this->stdWrap($conf['addPageCacheTags'], $conf['addPageCacheTags.'])
1671
            : $conf['addPageCacheTags'];
1672
        if (!empty($tags)) {
1673
            $cacheTags = GeneralUtility::trimExplode(',', $tags, true);
1674
            $this->getTypoScriptFrontendController()->addCacheTags($cacheTags);
1675
        }
1676
        return $content;
1677
    }
1678
1679
    /**
1680
     * setContentToCurrent
1681
     * actually it just does the contrary: Sets the value of 'current' based on current content
1682
     *
1683
     * @param string $content Input value undergoing processing in this function.
1684
     * @return string The processed input value
1685
     */
1686
    public function stdWrap_setContentToCurrent($content = '')
1687
    {
1688
        $this->data[$this->currentValKey] = $content;
1689
        return $content;
1690
    }
1691
1692
    /**
1693
     * setCurrent
1694
     * Sets the value of 'current' based on the outcome of stdWrap operations
1695
     *
1696
     * @param string $content Input value undergoing processing in this function.
1697
     * @param array $conf stdWrap properties for setCurrent.
1698
     * @return string The processed input value
1699
     */
1700
    public function stdWrap_setCurrent($content = '', $conf = [])
1701
    {
1702
        $this->data[$this->currentValKey] = $conf['setCurrent'];
1703
        return $content;
1704
    }
1705
1706
    /**
1707
     * lang
1708
     * Translates content based on the language currently used by the FE
1709
     *
1710
     * @param string $content Input value undergoing processing in this function.
1711
     * @param array $conf stdWrap properties for lang.
1712
     * @return string The processed input value
1713
     */
1714
    public function stdWrap_lang($content = '', $conf = [])
1715
    {
1716
        $tsfe = $this->getTypoScriptFrontendController();
1717
        if (isset($conf['lang.']) && $tsfe->config['config']['language'] && isset($conf['lang.'][$tsfe->config['config']['language']])) {
1718
            $content = $conf['lang.'][$tsfe->config['config']['language']];
1719
        }
1720
        return $content;
1721
    }
1722
1723
    /**
1724
     * data
1725
     * Gets content from different sources based on getText functions, makes use of alternativeData, when set
1726
     *
1727
     * @param string $content Input value undergoing processing in this function.
1728
     * @param array $conf stdWrap properties for data.
1729
     * @return string The processed input value
1730
     */
1731
    public function stdWrap_data($content = '', $conf = [])
1732
    {
1733
        $content = $this->getData($conf['data'], is_array($this->alternativeData) ? $this->alternativeData : $this->data);
1734
        // This must be unset directly after
1735
        $this->alternativeData = '';
1736
        return $content;
1737
    }
1738
1739
    /**
1740
     * field
1741
     * Gets content from a DB field
1742
     *
1743
     * @param string $content Input value undergoing processing in this function.
1744
     * @param array $conf stdWrap properties for field.
1745
     * @return string The processed input value
1746
     */
1747
    public function stdWrap_field($content = '', $conf = [])
1748
    {
1749
        return $this->getFieldVal($conf['field']);
1750
    }
1751
1752
    /**
1753
     * current
1754
     * Gets content that has been perviously set as 'current'
1755
     * Can be set via setContentToCurrent or setCurrent or will be set automatically i.e. inside the split function
1756
     *
1757
     * @param string $content Input value undergoing processing in this function.
1758
     * @param array $conf stdWrap properties for current.
1759
     * @return string The processed input value
1760
     */
1761
    public function stdWrap_current($content = '', $conf = [])
1762
    {
1763
        return $this->data[$this->currentValKey];
1764
    }
1765
1766
    /**
1767
     * cObject
1768
     * Will replace the content with the value of an official TypoScript cObject
1769
     * like TEXT, COA, HMENU
1770
     *
1771
     * @param string $content Input value undergoing processing in this function.
1772
     * @param array $conf stdWrap properties for cObject.
1773
     * @return string The processed input value
1774
     */
1775
    public function stdWrap_cObject($content = '', $conf = [])
1776
    {
1777
        return $this->cObjGetSingle($conf['cObject'], $conf['cObject.'], '/stdWrap/.cObject');
1778
    }
1779
1780
    /**
1781
     * numRows
1782
     * Counts the number of returned records of a DB operation
1783
     * makes use of select internally
1784
     *
1785
     * @param string $content Input value undergoing processing in this function.
1786
     * @param array $conf stdWrap properties for numRows.
1787
     * @return string The processed input value
1788
     */
1789
    public function stdWrap_numRows($content = '', $conf = [])
1790
    {
1791
        return $this->numRows($conf['numRows.']);
1792
    }
1793
1794
    /**
1795
     * filelist
1796
     * Will create a list of files based on some additional parameters
1797
     *
1798
     * @param string $content Input value undergoing processing in this function.
1799
     * @param array $conf stdWrap properties for filelist.
1800
     * @return string The processed input value
1801
     */
1802
    public function stdWrap_filelist($content = '', $conf = [])
1803
    {
1804
        return $this->filelist($conf['filelist']);
1805
    }
1806
1807
    /**
1808
     * preUserFunc
1809
     * Will execute a user public function before the content will be modified by any other stdWrap function
1810
     *
1811
     * @param string $content Input value undergoing processing in this function.
1812
     * @param array $conf stdWrap properties for preUserFunc.
1813
     * @return string The processed input value
1814
     */
1815
    public function stdWrap_preUserFunc($content = '', $conf = [])
1816
    {
1817
        return $this->callUserFunction($conf['preUserFunc'], $conf['preUserFunc.'], $content);
1818
    }
1819
1820
    /**
1821
     * stdWrap override hook
1822
     * can be used by extensions authors to modify the behaviour of stdWrap functions to their needs
1823
     * this hook will execute functions on existing content but still before the content gets modified or replaced
1824
     *
1825
     * @param string $content Input value undergoing processing in these functions.
1826
     * @param array $conf All stdWrap properties, not just the ones for a particular function.
1827
     * @return string The processed input value
1828
     */
1829
    public function stdWrap_stdWrapOverride($content = '', $conf = [])
1830
    {
1831
        foreach ($this->stdWrapHookObjects as $hookObject) {
1832
            /** @var ContentObjectStdWrapHookInterface $hookObject */
1833
            $content = $hookObject->stdWrapOverride($content, $conf, $this);
1834
        }
1835
        return $content;
1836
    }
1837
1838
    /**
1839
     * override
1840
     * Will override the current value of content with its own value'
1841
     *
1842
     * @param string $content Input value undergoing processing in this function.
1843
     * @param array $conf stdWrap properties for override.
1844
     * @return string The processed input value
1845
     */
1846
    public function stdWrap_override($content = '', $conf = [])
1847
    {
1848
        if (trim($conf['override'])) {
1849
            $content = $conf['override'];
1850
        }
1851
        return $content;
1852
    }
1853
1854
    /**
1855
     * preIfEmptyListNum
1856
     * Gets a value off a CSV list before the following ifEmpty check
1857
     * 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
1858
     *
1859
     * @param string $content Input value undergoing processing in this function.
1860
     * @param array $conf stdWrap properties for preIfEmptyListNum.
1861
     * @return string The processed input value
1862
     */
1863
    public function stdWrap_preIfEmptyListNum($content = '', $conf = [])
1864
    {
1865
        return $this->listNum($content, $conf['preIfEmptyListNum'], $conf['preIfEmptyListNum.']['splitChar']);
1866
    }
1867
1868
    /**
1869
     * ifNull
1870
     * Will set content to a replacement value in case the value of content is NULL
1871
     *
1872
     * @param string|null $content Input value undergoing processing in this function.
1873
     * @param array $conf stdWrap properties for ifNull.
1874
     * @return string The processed input value
1875
     */
1876
    public function stdWrap_ifNull($content = '', $conf = [])
1877
    {
1878
        return $content ?? $conf['ifNull'];
1879
    }
1880
1881
    /**
1882
     * ifEmpty
1883
     * Will set content to a replacement value in case the trimmed value of content returns FALSE
1884
     * 0 (zero) will be replaced as well
1885
     *
1886
     * @param string $content Input value undergoing processing in this function.
1887
     * @param array $conf stdWrap properties for ifEmpty.
1888
     * @return string The processed input value
1889
     */
1890
    public function stdWrap_ifEmpty($content = '', $conf = [])
1891
    {
1892
        if (!trim($content)) {
1893
            $content = $conf['ifEmpty'];
1894
        }
1895
        return $content;
1896
    }
1897
1898
    /**
1899
     * ifBlank
1900
     * Will set content to a replacement value in case the trimmed value of content has no length
1901
     * 0 (zero) will not be replaced
1902
     *
1903
     * @param string $content Input value undergoing processing in this function.
1904
     * @param array $conf stdWrap properties for ifBlank.
1905
     * @return string The processed input value
1906
     */
1907
    public function stdWrap_ifBlank($content = '', $conf = [])
1908
    {
1909
        if (trim($content) === '') {
1910
            $content = $conf['ifBlank'];
1911
        }
1912
        return $content;
1913
    }
1914
1915
    /**
1916
     * listNum
1917
     * Gets a value off a CSV list after ifEmpty check
1918
     * Might return an empty value in case the CSV does not contain a value at the position given by listNum
1919
     * Use preIfEmptyListNum to avoid that behaviour
1920
     *
1921
     * @param string $content Input value undergoing processing in this function.
1922
     * @param array $conf stdWrap properties for listNum.
1923
     * @return string The processed input value
1924
     */
1925
    public function stdWrap_listNum($content = '', $conf = [])
1926
    {
1927
        return $this->listNum($content, $conf['listNum'], $conf['listNum.']['splitChar']);
1928
    }
1929
1930
    /**
1931
     * trim
1932
     * Cuts off any whitespace at the beginning and the end of the content
1933
     *
1934
     * @param string $content Input value undergoing processing in this function.
1935
     * @return string The processed input value
1936
     */
1937
    public function stdWrap_trim($content = '')
1938
    {
1939
        return trim($content);
1940
    }
1941
1942
    /**
1943
     * strPad
1944
     * Will return a string padded left/right/on both sides, based on configuration given as stdWrap properties
1945
     *
1946
     * @param string $content Input value undergoing processing in this function.
1947
     * @param array $conf stdWrap properties for strPad.
1948
     * @return string The processed input value
1949
     */
1950
    public function stdWrap_strPad($content = '', $conf = [])
1951
    {
1952
        // Must specify a length in conf for this to make sense
1953
        $length = 0;
1954
        // Padding with space is PHP-default
1955
        $padWith = ' ';
1956
        // Padding on the right side is PHP-default
1957
        $padType = STR_PAD_RIGHT;
1958
        if (!empty($conf['strPad.']['length'])) {
1959
            $length = isset($conf['strPad.']['length.']) ? $this->stdWrap($conf['strPad.']['length'], $conf['strPad.']['length.']) : $conf['strPad.']['length'];
1960
            $length = (int)$length;
1961
        }
1962
        if (isset($conf['strPad.']['padWith']) && (string)$conf['strPad.']['padWith'] !== '') {
1963
            $padWith = isset($conf['strPad.']['padWith.']) ? $this->stdWrap($conf['strPad.']['padWith'], $conf['strPad.']['padWith.']) : $conf['strPad.']['padWith'];
1964
        }
1965
        if (!empty($conf['strPad.']['type'])) {
1966
            $type = isset($conf['strPad.']['type.']) ? $this->stdWrap($conf['strPad.']['type'], $conf['strPad.']['type.']) : $conf['strPad.']['type'];
1967
            if (strtolower($type) === 'left') {
1968
                $padType = STR_PAD_LEFT;
1969
            } elseif (strtolower($type) === 'both') {
1970
                $padType = STR_PAD_BOTH;
1971
            }
1972
        }
1973
        return str_pad($content, $length, $padWith, $padType);
1974
    }
1975
1976
    /**
1977
     * stdWrap
1978
     * A recursive call of the stdWrap function set
1979
     * This enables the user to execute stdWrap functions in another than the predefined order
1980
     * It modifies the content, not the property
1981
     * while the new feature of chained stdWrap functions modifies the property and not the content
1982
     *
1983
     * @param string $content Input value undergoing processing in this function.
1984
     * @param array $conf stdWrap properties for stdWrap.
1985
     * @return string The processed input value
1986
     */
1987
    public function stdWrap_stdWrap($content = '', $conf = [])
1988
    {
1989
        return $this->stdWrap($content, $conf['stdWrap.']);
1990
    }
1991
1992
    /**
1993
     * stdWrap process hook
1994
     * can be used by extensions authors to modify the behaviour of stdWrap functions to their needs
1995
     * this hook executes functions directly after the recursive stdWrap function call but still before the content gets modified
1996
     *
1997
     * @param string $content Input value undergoing processing in these functions.
1998
     * @param array $conf All stdWrap properties, not just the ones for a particular function.
1999
     * @return string The processed input value
2000
     */
2001
    public function stdWrap_stdWrapProcess($content = '', $conf = [])
2002
    {
2003
        foreach ($this->stdWrapHookObjects as $hookObject) {
2004
            /** @var ContentObjectStdWrapHookInterface $hookObject */
2005
            $content = $hookObject->stdWrapProcess($content, $conf, $this);
2006
        }
2007
        return $content;
2008
    }
2009
2010
    /**
2011
     * required
2012
     * Will immediately stop rendering and return an empty value
2013
     * when there is no content at this point
2014
     *
2015
     * @param string $content Input value undergoing processing in this function.
2016
     * @return string The processed input value
2017
     */
2018
    public function stdWrap_required($content = '')
2019
    {
2020
        if ((string)$content === '') {
2021
            $content = '';
2022
            $this->stopRendering[$this->stdWrapRecursionLevel] = true;
2023
        }
2024
        return $content;
2025
    }
2026
2027
    /**
2028
     * if
2029
     * Will immediately stop rendering and return an empty value
2030
     * when the result of the checks returns FALSE
2031
     *
2032
     * @param string $content Input value undergoing processing in this function.
2033
     * @param array $conf stdWrap properties for if.
2034
     * @return string The processed input value
2035
     */
2036
    public function stdWrap_if($content = '', $conf = [])
2037
    {
2038
        if (empty($conf['if.']) || $this->checkIf($conf['if.'])) {
2039
            return $content;
2040
        }
2041
        $this->stopRendering[$this->stdWrapRecursionLevel] = true;
2042
        return '';
2043
    }
2044
2045
    /**
2046
     * fieldRequired
2047
     * Will immediately stop rendering and return an empty value
2048
     * when there is no content in the field given by fieldRequired
2049
     *
2050
     * @param string $content Input value undergoing processing in this function.
2051
     * @param array $conf stdWrap properties for fieldRequired.
2052
     * @return string The processed input value
2053
     */
2054
    public function stdWrap_fieldRequired($content = '', $conf = [])
2055
    {
2056
        if (!trim($this->data[$conf['fieldRequired']])) {
2057
            $content = '';
2058
            $this->stopRendering[$this->stdWrapRecursionLevel] = true;
2059
        }
2060
        return $content;
2061
    }
2062
2063
    /**
2064
     * stdWrap csConv: Converts the input to UTF-8
2065
     *
2066
     * The character set of the input must be specified. Returns the input if
2067
     * matters go wrong, for example if an invalid character set is given.
2068
     *
2069
     * @param string $content The string to convert.
2070
     * @param array $conf stdWrap properties for csConv.
2071
     * @return string The processed input.
2072
     */
2073
    public function stdWrap_csConv($content = '', $conf = [])
2074
    {
2075
        if (!empty($conf['csConv'])) {
2076
            $output = mb_convert_encoding($content, 'utf-8', trim(strtolower($conf['csConv'])));
2077
            return $output !== false && $output !== '' ? $output : $content;
2078
        }
2079
        return $content;
2080
    }
2081
2082
    /**
2083
     * parseFunc
2084
     * Will parse the content based on functions given as stdWrap properties
2085
     * Heavily used together with RTE based content
2086
     *
2087
     * @param string $content Input value undergoing processing in this function.
2088
     * @param array $conf stdWrap properties for parseFunc.
2089
     * @return string The processed input value
2090
     */
2091
    public function stdWrap_parseFunc($content = '', $conf = [])
2092
    {
2093
        return $this->parseFunc($content, $conf['parseFunc.'], $conf['parseFunc']);
2094
    }
2095
2096
    /**
2097
     * HTMLparser
2098
     * Will parse HTML content based on functions given as stdWrap properties
2099
     * Heavily used together with RTE based content
2100
     *
2101
     * @param string $content Input value undergoing processing in this function.
2102
     * @param array $conf stdWrap properties for HTMLparser.
2103
     * @return string The processed input value
2104
     */
2105
    public function stdWrap_HTMLparser($content = '', $conf = [])
2106
    {
2107
        if (is_array($conf['HTMLparser.'])) {
2108
            $content = $this->HTMLparser_TSbridge($content, $conf['HTMLparser.']);
2109
        }
2110
        return $content;
2111
    }
2112
2113
    /**
2114
     * split
2115
     * Will split the content by a given token and treat the results separately
2116
     * Automatically fills 'current' with a single result
2117
     *
2118
     * @param string $content Input value undergoing processing in this function.
2119
     * @param array $conf stdWrap properties for split.
2120
     * @return string The processed input value
2121
     */
2122
    public function stdWrap_split($content = '', $conf = [])
2123
    {
2124
        return $this->splitObj($content, $conf['split.']);
2125
    }
2126
2127
    /**
2128
     * replacement
2129
     * Will execute replacements on the content (optionally with preg-regex)
2130
     *
2131
     * @param string $content Input value undergoing processing in this function.
2132
     * @param array $conf stdWrap properties for replacement.
2133
     * @return string The processed input value
2134
     */
2135
    public function stdWrap_replacement($content = '', $conf = [])
2136
    {
2137
        return $this->replacement($content, $conf['replacement.']);
2138
    }
2139
2140
    /**
2141
     * prioriCalc
2142
     * Will use the content as a mathematical term and calculate the result
2143
     * Can be set to 1 to just get a calculated value or 'intval' to get the integer of the result
2144
     *
2145
     * @param string $content Input value undergoing processing in this function.
2146
     * @param array $conf stdWrap properties for prioriCalc.
2147
     * @return string The processed input value
2148
     */
2149
    public function stdWrap_prioriCalc($content = '', $conf = [])
2150
    {
2151
        $content = MathUtility::calculateWithParentheses($content);
2152
        if ($conf['prioriCalc'] === 'intval') {
2153
            $content = (int)$content;
2154
        }
2155
        return $content;
2156
    }
2157
2158
    /**
2159
     * char
2160
     * Returns a one-character string containing the character specified by ascii code.
2161
     *
2162
     * Reliable results only for character codes in the integer range 0 - 127.
2163
     *
2164
     * @see http://php.net/manual/en/function.chr.php
2165
     * @param string $content Input value undergoing processing in this function.
2166
     * @param array $conf stdWrap properties for char.
2167
     * @return string The processed input value
2168
     */
2169
    public function stdWrap_char($content = '', $conf = [])
2170
    {
2171
        return chr((int)$conf['char']);
2172
    }
2173
2174
    /**
2175
     * intval
2176
     * Will return an integer value of the current content
2177
     *
2178
     * @param string $content Input value undergoing processing in this function.
2179
     * @return string The processed input value
2180
     */
2181
    public function stdWrap_intval($content = '')
2182
    {
2183
        return (int)$content;
2184
    }
2185
2186
    /**
2187
     * Will return a hashed value of the current content
2188
     *
2189
     * @param string $content Input value undergoing processing in this function.
2190
     * @param array $conf stdWrap properties for hash.
2191
     * @return string The processed input value
2192
     * @link http://php.net/manual/de/function.hash-algos.php for a list of supported hash algorithms
2193
     */
2194
    public function stdWrap_hash($content = '', array $conf = [])
2195
    {
2196
        $algorithm = isset($conf['hash.']) ? $this->stdWrap($conf['hash'], $conf['hash.']) : $conf['hash'];
2197
        if (function_exists('hash') && in_array($algorithm, hash_algos())) {
2198
            return hash($algorithm, $content);
2199
        }
2200
        // Non-existing hashing algorithm
2201
        return '';
2202
    }
2203
2204
    /**
2205
     * stdWrap_round will return a rounded number with ceil(), floor() or round(), defaults to round()
2206
     * Only the english number format is supported . (dot) as decimal point
2207
     *
2208
     * @param string $content Input value undergoing processing in this function.
2209
     * @param array $conf stdWrap properties for round.
2210
     * @return string The processed input value
2211
     */
2212
    public function stdWrap_round($content = '', $conf = [])
2213
    {
2214
        return $this->round($content, $conf['round.']);
2215
    }
2216
2217
    /**
2218
     * numberFormat
2219
     * Will return a formatted number based on configuration given as stdWrap properties
2220
     *
2221
     * @param string $content Input value undergoing processing in this function.
2222
     * @param array $conf stdWrap properties for numberFormat.
2223
     * @return string The processed input value
2224
     */
2225
    public function stdWrap_numberFormat($content = '', $conf = [])
2226
    {
2227
        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

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

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

2971
            $char = chr(/** @scrutinizer ignore-type */ $char);
Loading history...
2972
        }
2973
        $temp = explode($char, $content);
2974
        $last = '' . (count($temp) - 1);
2975
        // Take a random item if requested
2976
        if ($listNum === 'rand') {
2977
            $listNum = rand(0, count($temp) - 1);
2978
        }
2979
        $index = $this->calc(str_ireplace('last', $last, $listNum));
2980
        return $temp[$index];
2981
    }
2982
2983
    /**
2984
     * Compares values together based on the settings in the input TypoScript array and returns the comparison result.
2985
     * Implements the "if" function in TYPO3 TypoScript
2986
     *
2987
     * @param array $conf TypoScript properties defining what to compare
2988
     * @return bool
2989
     * @see stdWrap(), _parseFunc()
2990
     */
2991
    public function checkIf($conf)
2992
    {
2993
        if (!is_array($conf)) {
2994
            return true;
2995
        }
2996
        if (isset($conf['directReturn'])) {
2997
            return (bool)$conf['directReturn'];
2998
        }
2999
        $flag = true;
3000
        if (isset($conf['isNull.'])) {
3001
            $isNull = $this->stdWrap('', $conf['isNull.']);
3002
            if ($isNull !== null) {
3003
                $flag = false;
3004
            }
3005
        }
3006
        if (isset($conf['isTrue']) || isset($conf['isTrue.'])) {
3007
            $isTrue = isset($conf['isTrue.']) ? trim($this->stdWrap($conf['isTrue'], $conf['isTrue.'])) : trim($conf['isTrue']);
3008
            if (!$isTrue) {
3009
                $flag = false;
3010
            }
3011
        }
3012
        if (isset($conf['isFalse']) || isset($conf['isFalse.'])) {
3013
            $isFalse = isset($conf['isFalse.']) ? trim($this->stdWrap($conf['isFalse'], $conf['isFalse.'])) : trim($conf['isFalse']);
3014
            if ($isFalse) {
3015
                $flag = false;
3016
            }
3017
        }
3018
        if (isset($conf['isPositive']) || isset($conf['isPositive.'])) {
3019
            $number = isset($conf['isPositive.']) ? $this->calc($this->stdWrap($conf['isPositive'], $conf['isPositive.'])) : $this->calc($conf['isPositive']);
3020
            if ($number < 1) {
3021
                $flag = false;
3022
            }
3023
        }
3024
        if ($flag) {
3025
            $value = isset($conf['value.']) ? trim($this->stdWrap($conf['value'], $conf['value.'])) : trim($conf['value']);
3026
            if (isset($conf['isGreaterThan']) || isset($conf['isGreaterThan.'])) {
3027
                $number = isset($conf['isGreaterThan.']) ? trim($this->stdWrap($conf['isGreaterThan'], $conf['isGreaterThan.'])) : trim($conf['isGreaterThan']);
3028
                if ($number <= $value) {
3029
                    $flag = false;
3030
                }
3031
            }
3032
            if (isset($conf['isLessThan']) || isset($conf['isLessThan.'])) {
3033
                $number = isset($conf['isLessThan.']) ? trim($this->stdWrap($conf['isLessThan'], $conf['isLessThan.'])) : trim($conf['isLessThan']);
3034
                if ($number >= $value) {
3035
                    $flag = false;
3036
                }
3037
            }
3038
            if (isset($conf['equals']) || isset($conf['equals.'])) {
3039
                $number = isset($conf['equals.']) ? trim($this->stdWrap($conf['equals'], $conf['equals.'])) : trim($conf['equals']);
3040
                if ($number != $value) {
3041
                    $flag = false;
3042
                }
3043
            }
3044
            if (isset($conf['isInList']) || isset($conf['isInList.'])) {
3045
                $number = isset($conf['isInList.']) ? trim($this->stdWrap($conf['isInList'], $conf['isInList.'])) : trim($conf['isInList']);
3046
                if (!GeneralUtility::inList($value, $number)) {
3047
                    $flag = false;
3048
                }
3049
            }
3050
        }
3051
        if ($conf['negate']) {
3052
            $flag = !$flag;
3053
        }
3054
        return $flag;
3055
    }
3056
3057
    /**
3058
     * Reads a directory for files and returns the filepaths in a string list separated by comma.
3059
     * Implements the stdWrap property "filelist"
3060
     *
3061
     * @param string $data The command which contains information about what files/directory listing to return. See the "filelist" property of stdWrap for details.
3062
     * @return string Comma list of files.
3063
     * @access private
3064
     * @see stdWrap()
3065
     */
3066
    public function filelist($data)
3067
    {
3068
        $data = trim($data);
3069
        if ($data === '') {
3070
            return '';
3071
        }
3072
        list($possiblePath, $ext_list, $sorting, $reverse, $useFullPath) = GeneralUtility::trimExplode('|', $data);
3073
        // read directory:
3074
        // MUST exist!
3075
        $path = '';
3076
        // proceeds if no '//', '..' or '\' is in the $theFile
3077
        if (GeneralUtility::validPathStr($possiblePath)) {
3078
            // Removes all dots, slashes and spaces after a path.
3079
            $possiblePath = preg_replace('/[\\/\\. ]*$/', '', $possiblePath);
3080
            if (!GeneralUtility::isAbsPath($possiblePath) && @is_dir($possiblePath)) {
3081
                // Now check if it matches one of the FAL storages
3082
                $storageRepository = GeneralUtility::makeInstance(StorageRepository::class);
3083
                $storages = $storageRepository->findAll();
3084
                foreach ($storages as $storage) {
3085
                    if ($storage->getDriverType() === 'Local' && $storage->isPublic() && $storage->isOnline()) {
3086
                        $folder = $storage->getPublicUrl($storage->getRootLevelFolder(), true);
3087
                        if (GeneralUtility::isFirstPartOfStr($possiblePath . '/', $folder)) {
3088
                            $path = $possiblePath;
3089
                            break;
3090
                        }
3091
                    }
3092
                }
3093
            }
3094
        }
3095
        if (!$path) {
3096
            return '';
3097
        }
3098
        $items = [
3099
            'files' => [],
3100
            'sorting' => []
3101
        ];
3102
        $ext_list = strtolower(GeneralUtility::uniqueList($ext_list));
3103
        // Read dir:
3104
        $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

3104
        $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...
3105
        if (is_object($d)) {
3106
            $count = 0;
3107
            while ($entry = $d->read()) {
3108
                if ($entry !== '.' && $entry !== '..') {
3109
                    // Because of odd PHP-error where <br />-tag is sometimes placed after a filename!!
3110
                    $wholePath = $path . '/' . $entry;
3111
                    if (file_exists($wholePath) && filetype($wholePath) === 'file') {
3112
                        $info = GeneralUtility::split_fileref($wholePath);
3113
                        if (!$ext_list || GeneralUtility::inList($ext_list, $info['fileext'])) {
3114
                            $items['files'][] = $info['file'];
3115
                            switch ($sorting) {
3116
                                case 'name':
3117
                                    $items['sorting'][] = strtolower($info['file']);
3118
                                    break;
3119
                                case 'size':
3120
                                    $items['sorting'][] = filesize($wholePath);
3121
                                    break;
3122
                                case 'ext':
3123
                                    $items['sorting'][] = $info['fileext'];
3124
                                    break;
3125
                                case 'date':
3126
                                    $items['sorting'][] = filectime($wholePath);
3127
                                    break;
3128
                                case 'mdate':
3129
                                    $items['sorting'][] = filemtime($wholePath);
3130
                                    break;
3131
                                default:
3132
                                    $items['sorting'][] = $count;
3133
                            }
3134
                            $count++;
3135
                        }
3136
                    }
3137
                }
3138
            }
3139
            $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

3139
            $d->/** @scrutinizer ignore-call */ 
3140
                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...
3140
        }
3141
        // Sort if required
3142
        if (!empty($items['sorting'])) {
3143
            if (strtolower($reverse) !== 'r') {
3144
                asort($items['sorting']);
3145
            } else {
3146
                arsort($items['sorting']);
3147
            }
3148
        }
3149
        if (!empty($items['files'])) {
3150
            // Make list
3151
            reset($items['sorting']);
3152
            $list_arr = [];
3153
            foreach ($items['sorting'] as $key => $v) {
3154
                $list_arr[] = $useFullPath ? $path . '/' . $items['files'][$key] : $items['files'][$key];
3155
            }
3156
            return implode(',', $list_arr);
3157
        }
3158
        return '';
3159
    }
3160
3161
    /**
3162
     * Passes the input value, $theValue, to an instance of "\TYPO3\CMS\Core\Html\HtmlParser"
3163
     * together with the TypoScript options which are first converted from a TS style array
3164
     * to a set of arrays with options for the \TYPO3\CMS\Core\Html\HtmlParser class.
3165
     *
3166
     * @param string $theValue The value to parse by the class \TYPO3\CMS\Core\Html\HtmlParser
3167
     * @param array $conf TypoScript properties for the parser. See link.
3168
     * @return string Return value.
3169
     * @see stdWrap(), \TYPO3\CMS\Core\Html\HtmlParser::HTMLparserConfig(), \TYPO3\CMS\Core\Html\HtmlParser::HTMLcleaner()
3170
     */
3171
    public function HTMLparser_TSbridge($theValue, $conf)
3172
    {
3173
        $htmlParser = GeneralUtility::makeInstance(HtmlParser::class);
3174
        $htmlParserCfg = $htmlParser->HTMLparserConfig($conf);
3175
        return $htmlParser->HTMLcleaner($theValue, $htmlParserCfg[0], $htmlParserCfg[1], $htmlParserCfg[2], $htmlParserCfg[3]);
3176
    }
3177
3178
    /**
3179
     * Wrapping input value in a regular "wrap" but parses the wrapping value first for "insertData" codes.
3180
     *
3181
     * @param string $content Input string being wrapped
3182
     * @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.
3183
     * @return string Output string wrapped in the wrapping value.
3184
     * @see insertData(), stdWrap()
3185
     */
3186
    public function dataWrap($content, $wrap)
3187
    {
3188
        return $this->wrap($content, $this->insertData($wrap));
3189
    }
3190
3191
    /**
3192
     * Implements the "insertData" property of stdWrap meaning that if strings matching {...} is found in the input string they
3193
     * will be substituted with the return value from getData (datatype) which is passed the content of the curly braces.
3194
     * If the content inside the curly braces starts with a hash sign {#...} it is a field name that must be quoted by Doctrine
3195
     * DBAL and is skipped here for later processing.
3196
     *
3197
     * Example: If input string is "This is the page title: {page:title}" then the part, '{page:title}', will be substituted with
3198
     * the current pages title field value.
3199
     *
3200
     * @param string $str Input value
3201
     * @return string Processed input value
3202
     * @see getData(), stdWrap(), dataWrap()
3203
     */
3204
    public function insertData($str)
3205
    {
3206
        $inside = 0;
3207
        $newVal = '';
3208
        $pointer = 0;
3209
        $totalLen = strlen($str);
3210
        do {
3211
            if (!$inside) {
3212
                $len = strcspn(substr($str, $pointer), '{');
3213
                $newVal .= substr($str, $pointer, $len);
3214
                $inside = true;
3215
                if (substr($str, $pointer + $len + 1, 1) === '#') {
3216
                    $len2 = strcspn(substr($str, $pointer + $len), '}');
3217
                    $newVal .= substr($str, $pointer + $len, $len2);
3218
                    $len += $len2;
3219
                    $inside = false;
3220
                }
3221
            } else {
3222
                $len = strcspn(substr($str, $pointer), '}') + 1;
3223
                $newVal .= $this->getData(substr($str, $pointer + 1, $len - 2), $this->data);
3224
                $inside = false;
3225
            }
3226
            $pointer += $len;
3227
        } while ($pointer < $totalLen);
3228
        return $newVal;
3229
    }
3230
3231
    /**
3232
     * 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.
3233
     * Notice; this function (used by stdWrap) can be disabled by a "config.disablePrefixComment" setting in TypoScript.
3234
     *
3235
     * @param string $str Input value
3236
     * @param array $conf TypoScript Configuration (not used at this point.)
3237
     * @param string $content The content to wrap the comment around.
3238
     * @return string Processed input value
3239
     * @see stdWrap()
3240
     */
3241
    public function prefixComment($str, $conf, $content)
3242
    {
3243
        if (empty($str)) {
3244
            return $content;
3245
        }
3246
        $parts = explode('|', $str);
3247
        $indent = (int)$parts[0];
3248
        $comment = htmlspecialchars($this->insertData($parts[1]));
3249
        $output = LF
3250
            . str_pad('', $indent, TAB) . '<!-- ' . $comment . ' [begin] -->' . LF
3251
            . str_pad('', ($indent + 1), TAB) . $content . LF
3252
            . str_pad('', $indent, TAB) . '<!-- ' . $comment . ' [end] -->' . LF
3253
            . str_pad('', ($indent + 1), TAB);
3254
        return $output;
3255
    }
3256
3257
    /**
3258
     * Implements the stdWrap property "substring" which is basically a TypoScript implementation of the PHP function, substr()
3259
     *
3260
     * @param string $content The string to perform the operation on
3261
     * @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().
3262
     * @return string The processed input value.
3263
     * @access private
3264
     * @see stdWrap()
3265
     */
3266
    public function substring($content, $options)
3267
    {
3268
        $options = GeneralUtility::intExplode(',', $options . ',');
3269
        if ($options[1]) {
3270
            return mb_substr($content, $options[0], $options[1], 'utf-8');
3271
        }
3272
        return mb_substr($content, $options[0], null, 'utf-8');
3273
    }
3274
3275
    /**
3276
     * 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.
3277
     *
3278
     * @param string $content The string to perform the operation on
3279
     * @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.
3280
     * @return string The processed input value.
3281
     * @access private
3282
     * @see stdWrap()
3283
     */
3284
    public function crop($content, $options)
3285
    {
3286
        $options = explode('|', $options);
3287
        $chars = (int)$options[0];
3288
        $afterstring = trim($options[1]);
3289
        $crop2space = trim($options[2]);
3290
        if ($chars) {
3291
            if (mb_strlen($content, 'utf-8') > abs($chars)) {
3292
                $truncatePosition = false;
3293
                if ($chars < 0) {
3294
                    $content = mb_substr($content, $chars, null, 'utf-8');
3295
                    if ($crop2space) {
3296
                        $truncatePosition = strpos($content, ' ');
3297
                    }
3298
                    $content = $truncatePosition ? $afterstring . substr($content, $truncatePosition) : $afterstring . $content;
3299
                } else {
3300
                    $content = mb_substr($content, 0, $chars, 'utf-8');
3301
                    if ($crop2space) {
3302
                        $truncatePosition = strrpos($content, ' ');
3303
                    }
3304
                    $content = $truncatePosition ? substr($content, 0, $truncatePosition) . $afterstring : $content . $afterstring;
3305
                }
3306
            }
3307
        }
3308
        return $content;
3309
    }
3310
3311
    /**
3312
     * Implements the stdWrap property "cropHTML" which is a modified "substr" function allowing to limit a string length
3313
     * to a certain number of chars (from either start or end of string) and having a pre/postfix applied if the string
3314
     * really was cropped.
3315
     *
3316
     * Compared to stdWrap.crop it respects HTML tags and entities.
3317
     *
3318
     * @param string $content The string to perform the operation on
3319
     * @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.
3320
     * @return string The processed input value.
3321
     * @access private
3322
     * @see stdWrap()
3323
     */
3324
    public function cropHTML($content, $options)
3325
    {
3326
        $options = explode('|', $options);
3327
        $chars = (int)$options[0];
3328
        $absChars = abs($chars);
3329
        $replacementForEllipsis = trim($options[1]);
3330
        $crop2space = trim($options[2]) === '1';
3331
        // Split $content into an array(even items in the array are outside the tags, odd numbers are tag-blocks).
3332
        $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';
3333
        $tagsRegEx = '
3334
			(
3335
				(?:
3336
					<!--.*?-->					# a comment
3337
					|
3338
					<canvas[^>]*>.*?</canvas>   # a canvas tag
3339
					|
3340
					<script[^>]*>.*?</script>   # a script tag
3341
					|
3342
					<noscript[^>]*>.*?</noscript> # a noscript tag
3343
					|
3344
					<template[^>]*>.*?</template> # a template tag
3345
				)
3346
				|
3347
				</?(?:' . $tags . ')+			# opening tag (\'<tag\') or closing tag (\'</tag\')
3348
				(?:
3349
					(?:
3350
						(?:
3351
							\\s+\\w[\\w-]*		# EITHER spaces, followed by attribute names
3352
							(?:
3353
								\\s*=?\\s*		# equals
3354
								(?>
3355
									".*?"		# attribute values in double-quotes
3356
									|
3357
									\'.*?\'		# attribute values in single-quotes
3358
									|
3359
									[^\'">\\s]+	# plain attribute values
3360
								)
3361
							)?
3362
						)
3363
						|						# OR a single dash (for TYPO3 link tag)
3364
						(?:
3365
							\\s+-
3366
						)
3367
					)+\\s*
3368
					|							# OR only spaces
3369
					\\s*
3370
				)
3371
				/?>								# closing the tag with \'>\' or \'/>\'
3372
			)';
3373
        $splittedContent = preg_split('%' . $tagsRegEx . '%xs', $content, -1, PREG_SPLIT_DELIM_CAPTURE);
3374
        // Reverse array if we are cropping from right.
3375
        if ($chars < 0) {
3376
            $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

3376
            $splittedContent = array_reverse(/** @scrutinizer ignore-type */ $splittedContent);
Loading history...
3377
        }
3378
        // Crop the text (chars of tag-blocks are not counted).
3379
        $strLen = 0;
3380
        // This is the offset of the content item which was cropped.
3381
        $croppedOffset = null;
3382
        $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

3382
        $countSplittedContent = count(/** @scrutinizer ignore-type */ $splittedContent);
Loading history...
3383
        for ($offset = 0; $offset < $countSplittedContent; $offset++) {
3384
            if ($offset % 2 === 0) {
3385
                $tempContent = $splittedContent[$offset];
3386
                $thisStrLen = mb_strlen(html_entity_decode($tempContent, ENT_COMPAT, 'UTF-8'), 'utf-8');
3387
                if ($strLen + $thisStrLen > $absChars) {
3388
                    $croppedOffset = $offset;
3389
                    $cropPosition = $absChars - $strLen;
3390
                    // The snippet "&[^&\s;]{2,8};" in the RegEx below represents entities.
3391
                    $patternMatchEntityAsSingleChar = '(&[^&\\s;]{2,8};|.)';
3392
                    $cropRegEx = $chars < 0 ? '#' . $patternMatchEntityAsSingleChar . '{0,' . ($cropPosition + 1) . '}$#uis' : '#^' . $patternMatchEntityAsSingleChar . '{0,' . ($cropPosition + 1) . '}#uis';
3393
                    if (preg_match($cropRegEx, $tempContent, $croppedMatch)) {
3394
                        $tempContentPlusOneCharacter = $croppedMatch[0];
3395
                    } else {
3396
                        $tempContentPlusOneCharacter = false;
3397
                    }
3398
                    $cropRegEx = $chars < 0 ? '#' . $patternMatchEntityAsSingleChar . '{0,' . $cropPosition . '}$#uis' : '#^' . $patternMatchEntityAsSingleChar . '{0,' . $cropPosition . '}#uis';
3399
                    if (preg_match($cropRegEx, $tempContent, $croppedMatch)) {
3400
                        $tempContent = $croppedMatch[0];
3401
                        if ($crop2space && $tempContentPlusOneCharacter !== false) {
3402
                            $cropRegEx = $chars < 0 ? '#(?<=\\s)' . $patternMatchEntityAsSingleChar . '{0,' . $cropPosition . '}$#uis' : '#^' . $patternMatchEntityAsSingleChar . '{0,' . $cropPosition . '}(?=\\s)#uis';
3403
                            if (preg_match($cropRegEx, $tempContentPlusOneCharacter, $croppedMatch)) {
3404
                                $tempContent = $croppedMatch[0];
3405
                            }
3406
                        }
3407
                    }
3408
                    $splittedContent[$offset] = $tempContent;
3409
                    break;
3410
                }
3411
                $strLen += $thisStrLen;
3412
            }
3413
        }
3414
        // Close cropped tags.
3415
        $closingTags = [];
3416
        if ($croppedOffset !== null) {
3417
            $openingTagRegEx = '#^<(\\w+)(?:\\s|>)#';
3418
            $closingTagRegEx = '#^</(\\w+)(?:\\s|>)#';
3419
            for ($offset = $croppedOffset - 1; $offset >= 0; $offset = $offset - 2) {
3420
                if (substr($splittedContent[$offset], -2) === '/>') {
3421
                    // Ignore empty element tags (e.g. <br />).
3422
                    continue;
3423
                }
3424
                preg_match($chars < 0 ? $closingTagRegEx : $openingTagRegEx, $splittedContent[$offset], $matches);
3425
                $tagName = $matches[1] ?? null;
3426
                if ($tagName !== null) {
3427
                    // Seek for the closing (or opening) tag.
3428
                    $countSplittedContent = count($splittedContent);
3429
                    for ($seekingOffset = $offset + 2; $seekingOffset < $countSplittedContent; $seekingOffset = $seekingOffset + 2) {
3430
                        preg_match($chars < 0 ? $openingTagRegEx : $closingTagRegEx, $splittedContent[$seekingOffset], $matches);
3431
                        $seekingTagName = $matches[1] ?? null;
3432
                        if ($tagName === $seekingTagName) {
3433
                            // We found a matching tag.
3434
                            // Add closing tag only if it occurs after the cropped content item.
3435
                            if ($seekingOffset > $croppedOffset) {
3436
                                $closingTags[] = $splittedContent[$seekingOffset];
3437
                            }
3438
                            break;
3439
                        }
3440
                    }
3441
                }
3442
            }
3443
            // Drop the cropped items of the content array. The $closingTags will be added later on again.
3444
            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

3444
            array_splice(/** @scrutinizer ignore-type */ $splittedContent, $croppedOffset + 1);
Loading history...
3445
        }
3446
        $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

3446
        $splittedContent = array_merge(/** @scrutinizer ignore-type */ $splittedContent, [
Loading history...
3447
            $croppedOffset !== null ? $replacementForEllipsis : ''
3448
        ], $closingTags);
3449
        // Reverse array once again if we are cropping from the end.
3450
        if ($chars < 0) {
3451
            $splittedContent = array_reverse($splittedContent);
3452
        }
3453
        return implode('', $splittedContent);
3454
    }
3455
3456
    /**
3457
     * Implements the TypoScript function "addParams"
3458
     *
3459
     * @param string $content The string with the HTML tag.
3460
     * @param array $conf The TypoScript configuration properties
3461
     * @return string The modified string
3462
     * @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.
3463
     */
3464
    public function addParams($content, $conf)
3465
    {
3466
        // For XHTML compliance.
3467
        $lowerCaseAttributes = true;
3468
        if (!is_array($conf)) {
3469
            return $content;
3470
        }
3471
        $key = 1;
3472
        $parts = explode('<', $content);
3473
        if ((int)$conf['_offset']) {
3474
            $key = (int)$conf['_offset'] < 0 ? count($parts) + (int)$conf['_offset'] : (int)$conf['_offset'];
3475
        }
3476
        $subparts = explode('>', $parts[$key]);
3477
        if (trim($subparts[0])) {
3478
            // Get attributes and name
3479
            $attribs = GeneralUtility::get_tag_attributes('<' . $subparts[0] . '>');
3480
            list($tagName) = explode(' ', $subparts[0], 2);
3481
            // adds/overrides attributes
3482
            foreach ($conf as $pkey => $val) {
3483
                if (substr($pkey, -1) !== '.' && $pkey[0] !== '_') {
3484
                    $tmpVal = isset($conf[$pkey . '.']) ? $this->stdWrap($conf[$pkey], $conf[$pkey . '.']) : (string)$val;
3485
                    if ($lowerCaseAttributes) {
3486
                        $pkey = strtolower($pkey);
3487
                    }
3488
                    if ($tmpVal !== '') {
3489
                        $attribs[$pkey] = $tmpVal;
3490
                    }
3491
                }
3492
            }
3493
            // Re-assembles the tag and content
3494
            $subparts[0] = trim($tagName . ' ' . GeneralUtility::implodeAttributes($attribs));
3495
            $parts[$key] = implode('>', $subparts);
3496
            $content = implode('<', $parts);
3497
        }
3498
        return $content;
3499
    }
3500
3501
    /**
3502
     * Creates a list of links to files.
3503
     * Implements the stdWrap property "filelink"
3504
     *
3505
     * @param string $theValue The filename to link to, possibly prefixed with $conf[path]
3506
     * @param array $conf TypoScript parameters for the TypoScript function ->filelink
3507
     * @return string The link to the file possibly with icons, thumbnails, size in bytes shown etc.
3508
     * @access private
3509
     * @see stdWrap()
3510
     */
3511
    public function filelink($theValue, $conf)
3512
    {
3513
        $conf['path'] = isset($conf['path.']) ? $this->stdWrap($conf['path'], $conf['path.']) : $conf['path'];
3514
        $theFile = trim($conf['path']) . $theValue;
3515
        if (!@is_file($theFile)) {
3516
            return '';
3517
        }
3518
        $theFileEnc = str_replace('%2F', '/', rawurlencode($theFile));
3519
        $title = $conf['title'];
3520
        if (isset($conf['title.'])) {
3521
            $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

3521
            $title = $this->stdWrap($title, /** @scrutinizer ignore-type */ $conf['title.']);
Loading history...
3522
        }
3523
        $target = $conf['target'];
3524
        if (isset($conf['target.'])) {
3525
            $target = $this->stdWrap($target, $conf['target.']);
3526
        }
3527
        $tsfe = $this->getTypoScriptFrontendController();
3528
3529
        $typoLinkConf = [
3530
            'parameter' => $theFileEnc,
3531
            'fileTarget' => $target,
3532
            'title' => $title,
3533
            'ATagParams' => $this->getATagParams($conf)
3534
        ];
3535
3536
        if (isset($conf['typolinkConfiguration.'])) {
3537
            $additionalTypoLinkConfiguration = $conf['typolinkConfiguration.'];
3538
            // We only allow additional configuration. This is why the generated conf overwrites the additional conf.
3539
            ArrayUtility::mergeRecursiveWithOverrule($additionalTypoLinkConfiguration, $typoLinkConf);
3540
            $typoLinkConf = $additionalTypoLinkConfiguration;
3541
        }
3542
3543
        $theLinkWrap = $this->typoLink('|', $typoLinkConf);
3544
        $theSize = filesize($theFile);
3545
        $fI = GeneralUtility::split_fileref($theFile);
3546
        $icon = '';
3547
        if ($conf['icon']) {
3548
            $conf['icon.']['path'] = isset($conf['icon.']['path.'])
3549
                ? $this->stdWrap($conf['icon.']['path'], $conf['icon.']['path.'])
3550
                : $conf['icon.']['path'];
3551
            $iconPath = !empty($conf['icon.']['path'])
3552
                ? $conf['icon.']['path']
3553
                : GeneralUtility::getFileAbsFileName('EXT:frontend/Resources/Public/Icons/FileIcons/');
3554
            $conf['icon.']['ext'] = isset($conf['icon.']['ext.'])
3555
                ? $this->stdWrap($conf['icon.']['ext'], $conf['icon.']['ext.'])
3556
                : $conf['icon.']['ext'];
3557
            $iconExt = !empty($conf['icon.']['ext']) ? '.' . $conf['icon.']['ext'] : '.gif';
3558
            $icon = @is_file(($iconPath . $fI['fileext'] . $iconExt))
3559
                ? $iconPath . $fI['fileext'] . $iconExt
3560
                : $iconPath . 'default' . $iconExt;
3561
            $icon = PathUtility::stripPathSitePrefix($icon);
3562
            // Checking for images: If image, then return link to thumbnail.
3563
            $IEList = isset($conf['icon_image_ext_list.']) ? $this->stdWrap($conf['icon_image_ext_list'], $conf['icon_image_ext_list.']) : $conf['icon_image_ext_list'];
3564
            $image_ext_list = str_replace(' ', '', strtolower($IEList));
3565
            if ($fI['fileext'] && GeneralUtility::inList($image_ext_list, $fI['fileext'])) {
3566
                if ($conf['iconCObject']) {
3567
                    $icon = $this->cObjGetSingle($conf['iconCObject'], $conf['iconCObject.'], 'iconCObject');
3568
                } else {
3569
                    $notFoundThumb = GeneralUtility::getFileAbsFileName('EXT:core/Resources/Public/Images/NotFound.gif');
3570
                    $notFoundThumb = PathUtility::stripPathSitePrefix($notFoundThumb);
3571
                    $sizeParts = [64, 64];
3572
                    if ($GLOBALS['TYPO3_CONF_VARS']['GFX']['thumbnails']) {
3573
                        // using the File Abstraction Layer to generate a preview image
3574
                        try {
3575
                            /** @var File $fileObject */
3576
                            $fileObject = ResourceFactory::getInstance()->retrieveFileOrFolderObject($theFile);
3577
                            if ($fileObject->isMissing()) {
3578
                                $icon = $notFoundThumb;
3579
                            } else {
3580
                                $fileExtension = $fileObject->getExtension();
3581
                                if ($fileExtension === 'ttf' || GeneralUtility::inList($GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'], $fileExtension)) {
3582
                                    if ($conf['icon_thumbSize'] || $conf['icon_thumbSize.']) {
3583
                                        $thumbSize = isset($conf['icon_thumbSize.']) ? $this->stdWrap($conf['icon_thumbSize'], $conf['icon_thumbSize.']) : $conf['icon_thumbSize'];
3584
                                        $sizeParts = explode('x', $thumbSize);
3585
                                    }
3586
                                    $icon = $fileObject->process(ProcessedFile::CONTEXT_IMAGEPREVIEW, [
3587
                                        'width' => $sizeParts[0],
3588
                                        'height' => $sizeParts[1]
3589
                                    ])->getPublicUrl(true);
3590
                                }
3591
                            }
3592
                        } catch (ResourceDoesNotExistException $exception) {
3593
                            $icon = $notFoundThumb;
3594
                        }
3595
                    } else {
3596
                        $icon = $notFoundThumb;
3597
                    }
3598
                    $urlPrefix = '';
3599
                    if (parse_url($icon, PHP_URL_HOST) === null) {
3600
                        $urlPrefix = $tsfe->absRefPrefix;
3601
                    }
3602
                    $icon = '<img src="' . htmlspecialchars($urlPrefix . $icon) . '"' .
3603
                        ' width="' . (int)$sizeParts[0] . '" height="' . (int)$sizeParts[1] . '" ' .
3604
                        $this->getBorderAttr(' border="0"') . '' . $this->getAltParam($conf) . ' />';
3605
                }
3606
            } else {
3607
                $conf['icon.']['widthAttribute'] = isset($conf['icon.']['widthAttribute.'])
3608
                    ? $this->stdWrap($conf['icon.']['widthAttribute'], $conf['icon.']['widthAttribute.'])
3609
                    : $conf['icon.']['widthAttribute'];
3610
                $iconWidth = !empty($conf['icon.']['widthAttribute']) ? $conf['icon.']['widthAttribute'] : 18;
3611
                $conf['icon.']['heightAttribute'] = isset($conf['icon.']['heightAttribute.'])
3612
                    ? $this->stdWrap($conf['icon.']['heightAttribute'], $conf['icon.']['heightAttribute.'])
3613
                    : $conf['icon.']['heightAttribute'];
3614
                $iconHeight = !empty($conf['icon.']['heightAttribute']) ? (int)$conf['icon.']['heightAttribute'] : 16;
3615
                $icon = '<img src="' . htmlspecialchars($tsfe->absRefPrefix . $icon) . '" width="' . (int)$iconWidth . '" height="' . (int)$iconHeight . '"'
3616
                    . $this->getBorderAttr(' border="0"') . $this->getAltParam($conf) . ' />';
3617
            }
3618
            if ($conf['icon_link'] && !$conf['combinedLink']) {
3619
                $icon = $this->wrap($icon, $theLinkWrap);
3620
            }
3621
            $icon = isset($conf['icon.']) ? $this->stdWrap($icon, $conf['icon.']) : $icon;
3622
        }
3623
        $size = '';
3624
        if ($conf['size']) {
3625
            $size = isset($conf['size.']) ? $this->stdWrap($theSize, $conf['size.']) : $theSize;
3626
        }
3627
        // Wrapping file label
3628
        if ($conf['removePrependedNumbers']) {
3629
            $theValue = preg_replace('/_[0-9][0-9](\\.[[:alnum:]]*)$/', '\\1', $theValue);
3630
        }
3631
        if (isset($conf['labelStdWrap.'])) {
3632
            $theValue = $this->stdWrap($theValue, $conf['labelStdWrap.']);
3633
        }
3634
        // Wrapping file
3635
        $wrap = isset($conf['wrap.']) ? $this->stdWrap($conf['wrap'], $conf['wrap.']) : $conf['wrap'];
3636
        if ($conf['combinedLink']) {
3637
            $theValue = $icon . $theValue;
3638
            if ($conf['ATagBeforeWrap']) {
3639
                $theValue = $this->wrap($this->wrap($theValue, $wrap), $theLinkWrap);
3640
            } else {
3641
                $theValue = $this->wrap($this->wrap($theValue, $theLinkWrap), $wrap);
3642
            }
3643
            $file = isset($conf['file.']) ? $this->stdWrap($theValue, $conf['file.']) : $theValue;
3644
            // output
3645
            $output = $file . $size;
3646
        } else {
3647
            if ($conf['ATagBeforeWrap']) {
3648
                $theValue = $this->wrap($this->wrap($theValue, $wrap), $theLinkWrap);
3649
            } else {
3650
                $theValue = $this->wrap($this->wrap($theValue, $theLinkWrap), $wrap);
3651
            }
3652
            $file = isset($conf['file.']) ? $this->stdWrap($theValue, $conf['file.']) : $theValue;
3653
            // output
3654
            $output = $icon . $file . $size;
3655
        }
3656
        if (isset($conf['stdWrap.'])) {
3657
            $output = $this->stdWrap($output, $conf['stdWrap.']);
3658
        }
3659
        return $output;
3660
    }
3661
3662
    /**
3663
     * 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())
3664
     *
3665
     * @param string $val The string to evaluate. Example: "3+4*10/5" will generate "35". Only integer numbers can be used.
3666
     * @return int The result (might be a float if you did a division of the numbers).
3667
     * @see \TYPO3\CMS\Core\Utility\MathUtility::calculateWithPriorityToAdditionAndSubtraction()
3668
     */
3669
    public function calc($val)
3670
    {
3671
        $parts = GeneralUtility::splitCalc($val, '+-*/');
3672
        $value = 0;
3673
        foreach ($parts as $part) {
3674
            $theVal = $part[1];
3675
            $sign = $part[0];
3676
            if ((string)(int)$theVal === (string)$theVal) {
3677
                $theVal = (int)$theVal;
3678
            } else {
3679
                $theVal = 0;
3680
            }
3681
            if ($sign === '-') {
3682
                $value -= $theVal;
3683
            }
3684
            if ($sign === '+') {
3685
                $value += $theVal;
3686
            }
3687
            if ($sign === '/') {
3688
                if ((int)$theVal) {
3689
                    $value /= (int)$theVal;
3690
                }
3691
            }
3692
            if ($sign === '*') {
3693
                $value *= $theVal;
3694
            }
3695
        }
3696
        return $value;
3697
    }
3698
3699
    /**
3700
     * 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)
3701
     * Used to split and calculate min and max values for GMENUs.
3702
     *
3703
     * @param string $delim Delimited to explode by
3704
     * @param string $string The string with parts in (where each part is evaluated by ->calc())
3705
     * @return array And array with evaluated values.
3706
     * @see calc(), \TYPO3\CMS\Frontend\ContentObject\Menu\GraphicalMenuContentObject::makeGifs()
3707
     */
3708
    public function calcIntExplode($delim, $string)
3709
    {
3710
        $temp = explode($delim, $string);
3711
        foreach ($temp as $key => $val) {
3712
            $temp[$key] = (int)$this->calc($val);
3713
        }
3714
        return $temp;
3715
    }
3716
3717
    /**
3718
     * 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.
3719
     * 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.
3720
     * Implements the "optionSplit" processing of the TypoScript options for each splitted value to parse.
3721
     *
3722
     * @param string $value The string value to explode by $conf[token] and process each part
3723
     * @param array $conf TypoScript properties for "split
3724
     * @return string Compiled result
3725
     * @access private
3726
     * @see stdWrap(), \TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject::procesItemStates()
3727
     */
3728
    public function splitObj($value, $conf)
3729
    {
3730
        $conf['token'] = isset($conf['token.']) ? $this->stdWrap($conf['token'], $conf['token.']) : $conf['token'];
3731
        if ($conf['token'] === '') {
3732
            return $value;
3733
        }
3734
        $valArr = explode($conf['token'], $value);
3735
3736
        // return value directly by returnKey. No further processing
3737
        if (!empty($valArr) && (MathUtility::canBeInterpretedAsInteger($conf['returnKey']) || $conf['returnKey.'])) {
3738
            $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

3738
            $key = isset($conf['returnKey.']) ? (int)$this->stdWrap($conf['returnKey'], /** @scrutinizer ignore-type */ $conf['returnKey.']) : (int)$conf['returnKey'];
Loading history...
3739
            return $valArr[$key] ?? '';
3740
        }
3741
3742
        // return the amount of elements. No further processing
3743
        if (!empty($valArr) && ($conf['returnCount'] || $conf['returnCount.'])) {
3744
            $returnCount = isset($conf['returnCount.']) ? (bool)$this->stdWrap($conf['returnCount'], $conf['returnCount.']) : (bool)$conf['returnCount'];
3745
            return $returnCount ? count($valArr) : 0;
3746
        }
3747
3748
        // calculate splitCount
3749
        $splitCount = count($valArr);
3750
        $max = isset($conf['max.']) ? (int)$this->stdWrap($conf['max'], $conf['max.']) : (int)$conf['max'];
3751
        if ($max && $splitCount > $max) {
3752
            $splitCount = $max;
3753
        }
3754
        $min = isset($conf['min.']) ? (int)$this->stdWrap($conf['min'], $conf['min.']) : (int)$conf['min'];
3755
        if ($min && $splitCount < $min) {
3756
            $splitCount = $min;
3757
        }
3758
        $wrap = isset($conf['wrap.']) ? (string)$this->stdWrap($conf['wrap'], $conf['wrap.']) : (string)$conf['wrap'];
3759
        $cObjNumSplitConf = isset($conf['cObjNum.']) ? (string)$this->stdWrap($conf['cObjNum'], $conf['cObjNum.']) : (string)$conf['cObjNum'];
3760
        $splitArr = [];
3761
        if ($wrap !== '' || $cObjNumSplitConf !== '') {
3762
            $splitArr['wrap'] = $wrap;
3763
            $splitArr['cObjNum'] = $cObjNumSplitConf;
3764
            $splitArr = GeneralUtility::makeInstance(TypoScriptService::class)
3765
                ->explodeConfigurationForOptionSplit($splitArr, $splitCount);
3766
        }
3767
        $content = '';
3768
        for ($a = 0; $a < $splitCount; $a++) {
3769
            $this->getTypoScriptFrontendController()->register['SPLIT_COUNT'] = $a;
3770
            $value = '' . $valArr[$a];
3771
            $this->data[$this->currentValKey] = $value;
3772
            if ($splitArr[$a]['cObjNum']) {
3773
                $objName = (int)$splitArr[$a]['cObjNum'];
3774
                $value = isset($conf[$objName . '.'])
3775
                    ? $this->stdWrap($this->cObjGet($conf[$objName . '.'], $objName . '.'), $conf[$objName . '.'])
3776
                    : $this->cObjGet($conf[$objName . '.'], $objName . '.');
3777
            }
3778
            $wrap = isset($splitArr[$a]['wrap.']) ? $this->stdWrap($splitArr[$a]['wrap'], $splitArr[$a]['wrap.']) : $splitArr[$a]['wrap'];
3779
            if ($wrap) {
3780
                $value = $this->wrap($value, $wrap);
3781
            }
3782
            $content .= $value;
3783
        }
3784
        return $content;
3785
    }
3786
3787
    /**
3788
     * Processes ordered replacements on content data.
3789
     *
3790
     * @param string $content The content to be processed
3791
     * @param array $configuration The TypoScript configuration for stdWrap.replacement
3792
     * @return string The processed content data
3793
     */
3794
    protected function replacement($content, array $configuration)
3795
    {
3796
        // Sorts actions in configuration by numeric index
3797
        ksort($configuration, SORT_NUMERIC);
3798
        foreach ($configuration as $index => $action) {
3799
            // Checks whether we have an valid action and a numeric key ending with a dot ("10.")
3800
            if (is_array($action) && substr($index, -1) === '.' && MathUtility::canBeInterpretedAsInteger(substr($index, 0, -1))) {
3801
                $content = $this->replacementSingle($content, $action);
3802
            }
3803
        }
3804
        return $content;
3805
    }
3806
3807
    /**
3808
     * Processes a single search/replace on content data.
3809
     *
3810
     * @param string $content The content to be processed
3811
     * @param array $configuration The TypoScript of the search/replace action to be processed
3812
     * @return string The processed content data
3813
     */
3814
    protected function replacementSingle($content, array $configuration)
3815
    {
3816
        if ((isset($configuration['search']) || isset($configuration['search.'])) && (isset($configuration['replace']) || isset($configuration['replace.']))) {
3817
            // Gets the strings
3818
            $search = isset($configuration['search.']) ? $this->stdWrap($configuration['search'], $configuration['search.']) : $configuration['search'];
3819
            $replace = isset($configuration['replace.']) ? $this->stdWrap($configuration['replace'], $configuration['replace.']) : $configuration['replace'];
3820
            // Determines whether regular expression shall be used
3821
            if (isset($configuration['useRegExp']) || $configuration['useRegExp.']) {
3822
                $useRegularExpression = isset($configuration['useRegExp.']) ? $this->stdWrap($configuration['useRegExp'], $configuration['useRegExp.']) : $configuration['useRegExp'];
3823
            }
3824
            // Determines whether replace-pattern uses option-split
3825
            if (isset($configuration['useOptionSplitReplace']) || isset($configuration['useOptionSplitReplace.'])) {
3826
                $useOptionSplitReplace = isset($configuration['useOptionSplitReplace.']) ? $this->stdWrap($configuration['useOptionSplitReplace'], $configuration['useOptionSplitReplace.']) : $configuration['useOptionSplitReplace'];
3827
            }
3828
3829
            // Performs a replacement by preg_replace()
3830
            if (isset($useRegularExpression)) {
3831
                // Get separator-character which precedes the string and separates search-string from the modifiers
3832
                $separator = $search[0];
3833
                $startModifiers = strrpos($search, $separator);
3834
                if ($separator !== false && $startModifiers > 0) {
3835
                    $modifiers = substr($search, $startModifiers + 1);
3836
                    // remove "e" (eval-modifier), which would otherwise allow to run arbitrary PHP-code
3837
                    $modifiers = str_replace('e', '', $modifiers);
3838
                    $search = substr($search, 0, ($startModifiers + 1)) . $modifiers;
3839
                }
3840
                if (empty($useOptionSplitReplace)) {
3841
                    $content = preg_replace($search, $replace, $content);
3842
                } else {
3843
                    // init for replacement
3844
                    $splitCount = preg_match_all($search, $content, $matches);
3845
                    $typoScriptService = GeneralUtility::makeInstance(TypoScriptService::class);
3846
                    $replaceArray = $typoScriptService->explodeConfigurationForOptionSplit([$replace], $splitCount);
3847
                    $replaceCount = 0;
3848
3849
                    $replaceCallback = function ($match) use ($replaceArray, $search, &$replaceCount) {
3850
                        $replaceCount++;
3851
                        return preg_replace($search, $replaceArray[$replaceCount - 1][0], $match[0]);
3852
                    };
3853
                    $content = preg_replace_callback($search, $replaceCallback, $content);
3854
                }
3855
            } else {
3856
                if (empty($useOptionSplitReplace)) {
3857
                    $content = str_replace($search, $replace, $content);
3858
                } else {
3859
                    // turn search-string into a preg-pattern
3860
                    $searchPreg = '#' . preg_quote($search, '#') . '#';
3861
3862
                    // init for replacement
3863
                    $splitCount = preg_match_all($searchPreg, $content, $matches);
3864
                    $typoScriptService = GeneralUtility::makeInstance(TypoScriptService::class);
3865
                    $replaceArray = $typoScriptService->explodeConfigurationForOptionSplit([$replace], $splitCount);
3866
                    $replaceCount = 0;
3867
3868
                    $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...
3869
                        $replaceCount++;
3870
                        return $replaceArray[$replaceCount - 1][0];
3871
                    };
3872
                    $content = preg_replace_callback($searchPreg, $replaceCallback, $content);
3873
                }
3874
            }
3875
        }
3876
        return $content;
3877
    }
3878
3879
    /**
3880
     * Implements the "round" property of stdWrap
3881
     * This is a Wrapper function for PHP's rounding functions (round,ceil,floor), defaults to round()
3882
     *
3883
     * @param string $content Value to process
3884
     * @param array $conf TypoScript configuration for round
3885
     * @return string The formatted number
3886
     */
3887
    protected function round($content, array $conf = [])
3888
    {
3889
        $decimals = isset($conf['decimals.']) ? $this->stdWrap($conf['decimals'], $conf['decimals.']) : $conf['decimals'];
3890
        $type = isset($conf['roundType.']) ? $this->stdWrap($conf['roundType'], $conf['roundType.']) : $conf['roundType'];
3891
        $floatVal = (float)$content;
3892
        switch ($type) {
3893
            case 'ceil':
3894
                $content = ceil($floatVal);
3895
                break;
3896
            case 'floor':
3897
                $content = floor($floatVal);
3898
                break;
3899
            case 'round':
3900
3901
            default:
3902
                $content = round($floatVal, (int)$decimals);
3903
        }
3904
        return $content;
3905
    }
3906
3907
    /**
3908
     * Implements the stdWrap property "numberFormat"
3909
     * This is a Wrapper function for php's number_format()
3910
     *
3911
     * @param float $content Value to process
3912
     * @param array $conf TypoScript Configuration for numberFormat
3913
     * @return string The formatted number
3914
     */
3915
    public function numberFormat($content, $conf)
3916
    {
3917
        $decimals = isset($conf['decimals.']) ? (int)$this->stdWrap($conf['decimals'], $conf['decimals.']) : (int)$conf['decimals'];
3918
        $dec_point = isset($conf['dec_point.']) ? $this->stdWrap($conf['dec_point'], $conf['dec_point.']) : $conf['dec_point'];
3919
        $thousands_sep = isset($conf['thousands_sep.']) ? $this->stdWrap($conf['thousands_sep'], $conf['thousands_sep.']) : $conf['thousands_sep'];
3920
        return number_format((float)$content, $decimals, $dec_point, $thousands_sep);
3921
    }
3922
3923
    /**
3924
     * Implements the stdWrap property, "parseFunc".
3925
     * This is a function with a lot of interesting uses. In classic TypoScript this is used to process text
3926
     * from the bodytext field; This included highlighting of search words, changing http:// and mailto: prefixed strings into etc.
3927
     * It is still a very important function for processing of bodytext which is normally stored in the database
3928
     * in a format which is not fully ready to be outputted.
3929
     * This situation has not become better by having a RTE around...
3930
     *
3931
     * This function is actually just splitting the input content according to the configuration of "external blocks".
3932
     * This means that before the input string is actually "parsed" it will be splitted into the parts configured to BE parsed
3933
     * (while other parts/blocks should NOT be parsed).
3934
     * Therefore the actual processing of the parseFunc properties goes on in ->_parseFunc()
3935
     *
3936
     * @param string $theValue The value to process.
3937
     * @param array $conf TypoScript configuration for parseFunc
3938
     * @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!
3939
     * @return string The processed value
3940
     * @see _parseFunc()
3941
     */
3942
    public function parseFunc($theValue, $conf, $ref = '')
3943
    {
3944
        // Fetch / merge reference, if any
3945
        if ($ref) {
3946
            $temp_conf = [
3947
                'parseFunc' => $ref,
3948
                'parseFunc.' => $conf
3949
            ];
3950
            $temp_conf = $this->mergeTSRef($temp_conf, 'parseFunc');
3951
            $conf = $temp_conf['parseFunc.'];
3952
        }
3953
        // Process:
3954
        if ((string)$conf['externalBlocks'] === '') {
3955
            return $this->_parseFunc($theValue, $conf);
3956
        }
3957
        $tags = strtolower(implode(',', GeneralUtility::trimExplode(',', $conf['externalBlocks'])));
3958
        $htmlParser = GeneralUtility::makeInstance(HtmlParser::class);
3959
        $parts = $htmlParser->splitIntoBlock($tags, $theValue);
3960
        foreach ($parts as $k => $v) {
3961
            if ($k % 2) {
3962
                // font:
3963
                $tagName = strtolower($htmlParser->getFirstTagName($v));
3964
                $cfg = $conf['externalBlocks.'][$tagName . '.'];
3965
                if ($cfg['stripNLprev'] || $cfg['stripNL']) {
3966
                    $parts[$k - 1] = preg_replace('/' . CR . '?' . LF . '[ ]*$/', '', $parts[$k - 1]);
3967
                }
3968
                if ($cfg['stripNLnext'] || $cfg['stripNL']) {
3969
                    $parts[$k + 1] = preg_replace('/^[ ]*' . CR . '?' . LF . '/', '', $parts[$k + 1]);
3970
                }
3971
            }
3972
        }
3973
        foreach ($parts as $k => $v) {
3974
            if ($k % 2) {
3975
                $tag = $htmlParser->getFirstTag($v);
3976
                $tagName = strtolower($htmlParser->getFirstTagName($v));
3977
                $cfg = $conf['externalBlocks.'][$tagName . '.'];
3978
                if ($cfg['callRecursive']) {
3979
                    $parts[$k] = $this->parseFunc($htmlParser->removeFirstAndLastTag($v), $conf);
3980
                    if (!$cfg['callRecursive.']['dontWrapSelf']) {
3981
                        if ($cfg['callRecursive.']['alternativeWrap']) {
3982
                            $parts[$k] = $this->wrap($parts[$k], $cfg['callRecursive.']['alternativeWrap']);
3983
                        } else {
3984
                            if (is_array($cfg['callRecursive.']['tagStdWrap.'])) {
3985
                                $tag = $this->stdWrap($tag, $cfg['callRecursive.']['tagStdWrap.']);
3986
                            }
3987
                            $parts[$k] = $tag . $parts[$k] . '</' . $tagName . '>';
3988
                        }
3989
                    }
3990
                } elseif ($cfg['HTMLtableCells']) {
3991
                    $rowParts = $htmlParser->splitIntoBlock('tr', $parts[$k]);
3992
                    foreach ($rowParts as $kk => $vv) {
3993
                        if ($kk % 2) {
3994
                            $colParts = $htmlParser->splitIntoBlock('td,th', $vv);
3995
                            $cc = 0;
3996
                            foreach ($colParts as $kkk => $vvv) {
3997
                                if ($kkk % 2) {
3998
                                    $cc++;
3999
                                    $tag = $htmlParser->getFirstTag($vvv);
4000
                                    $tagName = strtolower($htmlParser->getFirstTagName($vvv));
4001
                                    $colParts[$kkk] = $htmlParser->removeFirstAndLastTag($vvv);
4002
                                    if ($cfg['HTMLtableCells.'][$cc . '.']['callRecursive'] || !isset($cfg['HTMLtableCells.'][$cc . '.']['callRecursive']) && $cfg['HTMLtableCells.']['default.']['callRecursive']) {
4003
                                        if ($cfg['HTMLtableCells.']['addChr10BetweenParagraphs']) {
4004
                                            $colParts[$kkk] = str_replace('</p><p>', '</p>' . LF . '<p>', $colParts[$kkk]);
4005
                                        }
4006
                                        $colParts[$kkk] = $this->parseFunc($colParts[$kkk], $conf);
4007
                                    }
4008
                                    $tagStdWrap = is_array($cfg['HTMLtableCells.'][$cc . '.']['tagStdWrap.'])
4009
                                        ? $cfg['HTMLtableCells.'][$cc . '.']['tagStdWrap.']
4010
                                        : $cfg['HTMLtableCells.']['default.']['tagStdWrap.'];
4011
                                    if (is_array($tagStdWrap)) {
4012
                                        $tag = $this->stdWrap($tag, $tagStdWrap);
4013
                                    }
4014
                                    $stdWrap = is_array($cfg['HTMLtableCells.'][$cc . '.']['stdWrap.'])
4015
                                        ? $cfg['HTMLtableCells.'][$cc . '.']['stdWrap.']
4016
                                        : $cfg['HTMLtableCells.']['default.']['stdWrap.'];
4017
                                    if (is_array($stdWrap)) {
4018
                                        $colParts[$kkk] = $this->stdWrap($colParts[$kkk], $stdWrap);
4019
                                    }
4020
                                    $colParts[$kkk] = $tag . $colParts[$kkk] . '</' . $tagName . '>';
4021
                                }
4022
                            }
4023
                            $rowParts[$kk] = implode('', $colParts);
4024
                        }
4025
                    }
4026
                    $parts[$k] = implode('', $rowParts);
4027
                }
4028
                if (is_array($cfg['stdWrap.'])) {
4029
                    $parts[$k] = $this->stdWrap($parts[$k], $cfg['stdWrap.']);
4030
                }
4031
            } else {
4032
                $parts[$k] = $this->_parseFunc($parts[$k], $conf);
4033
            }
4034
        }
4035
        return implode('', $parts);
4036
    }
4037
4038
    /**
4039
     * Helper function for parseFunc()
4040
     *
4041
     * @param string $theValue The value to process.
4042
     * @param array $conf TypoScript configuration for parseFunc
4043
     * @return string The processed value
4044
     * @access private
4045
     * @see parseFunc()
4046
     */
4047
    public function _parseFunc($theValue, $conf)
4048
    {
4049
        if (!empty($conf['if.']) && !$this->checkIf($conf['if.'])) {
4050
            return $theValue;
4051
        }
4052
        // Indicates that the data is from within a tag.
4053
        $inside = false;
4054
        // Pointer to the total string position
4055
        $pointer = 0;
4056
        // Loaded with the current typo-tag if any.
4057
        $currentTag = '';
4058
        $stripNL = 0;
4059
        $contentAccum = [];
4060
        $contentAccumP = 0;
4061
        $allowTags = strtolower(str_replace(' ', '', $conf['allowTags']));
4062
        $denyTags = strtolower(str_replace(' ', '', $conf['denyTags']));
4063
        $totalLen = strlen($theValue);
4064
        do {
4065
            if (!$inside) {
4066
                if (!is_array($currentTag)) {
4067
                    // These operations should only be performed on code outside the typotags...
4068
                    // data: this checks that we enter tags ONLY if the first char in the tag is alphanumeric OR '/'
4069
                    $len_p = 0;
4070
                    $c = 100;
4071
                    do {
4072
                        $len = strcspn(substr($theValue, $pointer + $len_p), '<');
4073
                        $len_p += $len + 1;
4074
                        $endChar = ord(strtolower(substr($theValue, $pointer + $len_p, 1)));
4075
                        $c--;
4076
                    } while ($c > 0 && $endChar && ($endChar < 97 || $endChar > 122) && $endChar != 47);
4077
                    $len = $len_p - 1;
4078
                } else {
4079
                    // If we're inside a currentTag, just take it to the end of that tag!
4080
                    $tempContent = strtolower(substr($theValue, $pointer));
4081
                    $len = strpos($tempContent, '</' . $currentTag[0]);
4082
                    if (is_string($len) && !$len) {
4083
                        $len = strlen($tempContent);
4084
                    }
4085
                }
4086
                // $data is the content until the next <tag-start or end is detected.
4087
                // In case of a currentTag set, this would mean all data between the start- and end-tags
4088
                $data = substr($theValue, $pointer, $len);
4089
                if ($data != '') {
4090
                    if ($stripNL) {
4091
                        // If the previous tag was set to strip NewLines in the beginning of the next data-chunk.
4092
                        $data = preg_replace('/^[ ]*' . CR . '?' . LF . '/', '', $data);
4093
                    }
4094
                    // These operations should only be performed on code outside the tags...
4095
                    if (!is_array($currentTag)) {
4096
                        // Constants
4097
                        $tsfe = $this->getTypoScriptFrontendController();
4098
                        $tmpConstants = $tsfe->tmpl->setup['constants.'];
4099
                        if ($conf['constants'] && is_array($tmpConstants)) {
4100
                            foreach ($tmpConstants as $key => $val) {
4101
                                if (is_string($val)) {
4102
                                    $data = str_replace('###' . $key . '###', $val, $data);
4103
                                }
4104
                            }
4105
                        }
4106
                        // Short
4107
                        if (is_array($conf['short.'])) {
4108
                            $shortWords = $conf['short.'];
4109
                            krsort($shortWords);
4110
                            foreach ($shortWords as $key => $val) {
4111
                                if (is_string($val)) {
4112
                                    $data = str_replace($key, $val, $data);
4113
                                }
4114
                            }
4115
                        }
4116
                        // stdWrap
4117
                        if (is_array($conf['plainTextStdWrap.'])) {
4118
                            $data = $this->stdWrap($data, $conf['plainTextStdWrap.']);
4119
                        }
4120
                        // userFunc
4121
                        if ($conf['userFunc']) {
4122
                            $data = $this->callUserFunction($conf['userFunc'], $conf['userFunc.'], $data);
4123
                        }
4124
                        // Makelinks: (Before search-words as we need the links to be generated when searchwords go on...!)
4125
                        if ($conf['makelinks']) {
4126
                            $data = $this->http_makelinks($data, $conf['makelinks.']['http.']);
4127
                            $data = $this->mailto_makelinks($data, $conf['makelinks.']['mailto.']);
4128
                        }
4129
                        // Search Words:
4130
                        if ($tsfe->no_cache && $conf['sword'] && is_array($tsfe->sWordList) && $tsfe->sWordRegEx) {
4131
                            $newstring = '';
4132
                            do {
4133
                                $pregSplitMode = 'i';
4134
                                if (isset($tsfe->config['config']['sword_noMixedCase']) && !empty($tsfe->config['config']['sword_noMixedCase'])) {
4135
                                    $pregSplitMode = '';
4136
                                }
4137
                                $pieces = preg_split('/' . $tsfe->sWordRegEx . '/' . $pregSplitMode, $data, 2);
4138
                                $newstring .= $pieces[0];
4139
                                $match_len = strlen($data) - (strlen($pieces[0]) + strlen($pieces[1]));
4140
                                $inTag = false;
4141
                                if (strstr($pieces[0], '<') || strstr($pieces[0], '>')) {
4142
                                    // Returns TRUE, if a '<' is closer to the string-end than '>'.
4143
                                    // This is the case if we're INSIDE a tag (that could have been
4144
                                    // made by makelinks...) and we must secure, that the inside of a tag is
4145
                                    // not marked up.
4146
                                    $inTag = strrpos($pieces[0], '<') > strrpos($pieces[0], '>');
4147
                                }
4148
                                // The searchword:
4149
                                $match = substr($data, strlen($pieces[0]), $match_len);
4150
                                if (trim($match) && strlen($match) > 1 && !$inTag) {
4151
                                    $match = $this->wrap($match, $conf['sword']);
4152
                                }
4153
                                // Concatenate the Search Word again.
4154
                                $newstring .= $match;
4155
                                $data = $pieces[1];
4156
                            } while ($pieces[1]);
4157
                            $data = $newstring;
4158
                        }
4159
                    }
4160
                    $contentAccum[$contentAccumP] .= $data;
4161
                }
4162
                $inside = true;
4163
            } else {
4164
                // tags
4165
                $len = strcspn(substr($theValue, $pointer), '>') + 1;
4166
                $data = substr($theValue, $pointer, $len);
4167
                if (StringUtility::endsWith($data, '/>') && strpos($data, '<link ') !== 0) {
4168
                    $tagContent = substr($data, 1, -2);
4169
                } else {
4170
                    $tagContent = substr($data, 1, -1);
4171
                }
4172
                $tag = explode(' ', trim($tagContent), 2);
4173
                $tag[0] = strtolower($tag[0]);
4174
                if ($tag[0][0] === '/') {
4175
                    $tag[0] = substr($tag[0], 1);
4176
                    $tag['out'] = 1;
4177
                }
4178
                if ($conf['tags.'][$tag[0]]) {
4179
                    $treated = false;
4180
                    $stripNL = false;
4181
                    // in-tag
4182
                    if (!$currentTag && !$tag['out']) {
4183
                        // $currentTag (array!) is the tag we are currently processing
4184
                        $currentTag = $tag;
4185
                        $contentAccumP++;
4186
                        $treated = true;
4187
                        // in-out-tag: img and other empty tags
4188
                        if (preg_match('/^(area|base|br|col|hr|img|input|meta|param)$/i', $tag[0])) {
4189
                            $tag['out'] = 1;
4190
                        }
4191
                    }
4192
                    // out-tag
4193
                    if ($currentTag[0] === $tag[0] && $tag['out']) {
4194
                        $theName = $conf['tags.'][$tag[0]];
4195
                        $theConf = $conf['tags.'][$tag[0] . '.'];
4196
                        // This flag indicates, that NL- (13-10-chars) should be stripped first and last.
4197
                        $stripNL = (bool)$theConf['stripNL'];
4198
                        // This flag indicates, that this TypoTag section should NOT be included in the nonTypoTag content.
4199
                        $breakOut = (bool)$theConf['breakoutTypoTagContent'];
4200
                        $this->parameters = [];
4201
                        if ($currentTag[1]) {
4202
                            $params = GeneralUtility::get_tag_attributes($currentTag[1]);
4203
                            if (is_array($params)) {
4204
                                foreach ($params as $option => $val) {
4205
                                    $this->parameters[strtolower($option)] = $val;
4206
                                }
4207
                            }
4208
                        }
4209
                        $this->parameters['allParams'] = trim($currentTag[1]);
4210
                        // Removes NL in the beginning and end of the tag-content AND at the end of the currentTagBuffer.
4211
                        // $stripNL depends on the configuration of the current tag
4212
                        if ($stripNL) {
4213
                            $contentAccum[$contentAccumP - 1] = preg_replace('/' . CR . '?' . LF . '[ ]*$/', '', $contentAccum[$contentAccumP - 1]);
4214
                            $contentAccum[$contentAccumP] = preg_replace('/^[ ]*' . CR . '?' . LF . '/', '', $contentAccum[$contentAccumP]);
4215
                            $contentAccum[$contentAccumP] = preg_replace('/' . CR . '?' . LF . '[ ]*$/', '', $contentAccum[$contentAccumP]);
4216
                        }
4217
                        $this->data[$this->currentValKey] = $contentAccum[$contentAccumP];
4218
                        $newInput = $this->cObjGetSingle($theName, $theConf, '/parseFunc/.tags.' . $tag[0]);
4219
                        // fetch the content object
4220
                        $contentAccum[$contentAccumP] = $newInput;
4221
                        $contentAccumP++;
4222
                        // If the TypoTag section
4223
                        if (!$breakOut) {
4224
                            $contentAccum[$contentAccumP - 2] .= $contentAccum[$contentAccumP - 1] . $contentAccum[$contentAccumP];
4225
                            unset($contentAccum[$contentAccumP]);
4226
                            unset($contentAccum[$contentAccumP - 1]);
4227
                            $contentAccumP -= 2;
4228
                        }
4229
                        unset($currentTag);
4230
                        $treated = true;
4231
                    }
4232
                    // other tags
4233
                    if (!$treated) {
4234
                        $contentAccum[$contentAccumP] .= $data;
4235
                    }
4236
                } else {
4237
                    // If a tag was not a typo tag, then it is just added to the content
4238
                    $stripNL = false;
4239
                    if (GeneralUtility::inList($allowTags, $tag[0]) || $denyTags !== '*' && !GeneralUtility::inList($denyTags, $tag[0])) {
4240
                        $contentAccum[$contentAccumP] .= $data;
4241
                    } else {
4242
                        $contentAccum[$contentAccumP] .= htmlspecialchars($data);
4243
                    }
4244
                }
4245
                $inside = false;
4246
            }
4247
            $pointer += $len;
4248
        } while ($pointer < $totalLen);
4249
        // Parsing nonTypoTag content (all even keys):
4250
        reset($contentAccum);
4251
        $contentAccumCount = count($contentAccum);
4252
        for ($a = 0; $a < $contentAccumCount; $a++) {
4253
            if ($a % 2 != 1) {
4254
                // stdWrap
4255
                if (is_array($conf['nonTypoTagStdWrap.'])) {
4256
                    $contentAccum[$a] = $this->stdWrap($contentAccum[$a], $conf['nonTypoTagStdWrap.']);
4257
                }
4258
                // userFunc
4259
                if ($conf['nonTypoTagUserFunc']) {
4260
                    $contentAccum[$a] = $this->callUserFunction($conf['nonTypoTagUserFunc'], $conf['nonTypoTagUserFunc.'], $contentAccum[$a]);
4261
                }
4262
            }
4263
        }
4264
        return implode('', $contentAccum);
4265
    }
4266
4267
    /**
4268
     * Lets you split the content by LF and process each line independently. Used to format content made with the RTE.
4269
     *
4270
     * @param string $theValue The input value
4271
     * @param array $conf TypoScript options
4272
     * @return string The processed input value being returned; Splitted lines imploded by LF again.
4273
     * @access private
4274
     */
4275
    public function encaps_lineSplit($theValue, $conf)
4276
    {
4277
        if ((string)$theValue === '') {
4278
            return '';
4279
        }
4280
        $lParts = explode(LF, $theValue);
4281
4282
        // When the last element is an empty linebreak we need to remove it, otherwise we will have a duplicate empty line.
4283
        $lastPartIndex = count($lParts) - 1;
4284
        if ($lParts[$lastPartIndex] === '' && trim($lParts[$lastPartIndex - 1], CR) === '') {
4285
            array_pop($lParts);
4286
        }
4287
4288
        $encapTags = GeneralUtility::trimExplode(',', strtolower($conf['encapsTagList']), true);
4289
        $nonWrappedTag = $conf['nonWrappedTag'];
4290
        $defaultAlign = isset($conf['defaultAlign.'])
4291
            ? trim($this->stdWrap($conf['defaultAlign'], $conf['defaultAlign.']))
4292
            : trim($conf['defaultAlign']);
4293
4294
        $str_content = '';
4295
        foreach ($lParts as $k => $l) {
4296
            $sameBeginEnd = 0;
4297
            $emptyTag = false;
4298
            $l = trim($l);
4299
            $attrib = [];
4300
            $nonWrapped = false;
4301
            $tagName = '';
4302
            if ($l[0] === '<' && substr($l, -1) === '>') {
4303
                $fwParts = explode('>', substr($l, 1), 2);
4304
                list($tagName) = explode(' ', $fwParts[0], 2);
4305
                if (!$fwParts[1]) {
4306
                    if (substr($tagName, -1) === '/') {
4307
                        $tagName = substr($tagName, 0, -1);
4308
                    }
4309
                    if (substr($fwParts[0], -1) === '/') {
4310
                        $sameBeginEnd = 1;
4311
                        $emptyTag = true;
4312
                        $attrib = GeneralUtility::get_tag_attributes('<' . substr($fwParts[0], 0, -1) . '>');
4313
                    }
4314
                } else {
4315
                    $backParts = GeneralUtility::revExplode('<', substr($fwParts[1], 0, -1), 2);
4316
                    $attrib = GeneralUtility::get_tag_attributes('<' . $fwParts[0] . '>');
4317
                    $str_content = $backParts[0];
4318
                    $sameBeginEnd = substr(strtolower($backParts[1]), 1, strlen($tagName)) === strtolower($tagName);
4319
                }
4320
            }
4321
            if ($sameBeginEnd && in_array(strtolower($tagName), $encapTags)) {
4322
                $uTagName = strtoupper($tagName);
4323
                $uTagName = strtoupper($conf['remapTag.'][$uTagName] ? $conf['remapTag.'][$uTagName] : $uTagName);
4324
            } else {
4325
                $uTagName = strtoupper($nonWrappedTag);
4326
                // The line will be wrapped: $uTagName should not be an empty tag
4327
                $emptyTag = false;
4328
                $str_content = $lParts[$k];
4329
                $nonWrapped = true;
4330
                $attrib = [];
4331
            }
4332
            // Wrapping all inner-content:
4333
            if (is_array($conf['innerStdWrap_all.'])) {
4334
                $str_content = $this->stdWrap($str_content, $conf['innerStdWrap_all.']);
4335
            }
4336
            if ($uTagName) {
4337
                // Setting common attributes
4338
                if (is_array($conf['addAttributes.'][$uTagName . '.'])) {
4339
                    foreach ($conf['addAttributes.'][$uTagName . '.'] as $kk => $vv) {
4340
                        if (!is_array($vv)) {
4341
                            if ((string)$conf['addAttributes.'][$uTagName . '.'][$kk . '.']['setOnly'] === 'blank') {
4342
                                if ((string)$attrib[$kk] === '') {
4343
                                    $attrib[$kk] = $vv;
4344
                                }
4345
                            } elseif ((string)$conf['addAttributes.'][$uTagName . '.'][$kk . '.']['setOnly'] === 'exists') {
4346
                                if (!isset($attrib[$kk])) {
4347
                                    $attrib[$kk] = $vv;
4348
                                }
4349
                            } else {
4350
                                $attrib[$kk] = $vv;
4351
                            }
4352
                        }
4353
                    }
4354
                }
4355
                // Wrapping all inner-content:
4356
                if (is_array($conf['encapsLinesStdWrap.'][$uTagName . '.'])) {
4357
                    $str_content = $this->stdWrap($str_content, $conf['encapsLinesStdWrap.'][$uTagName . '.']);
4358
                }
4359
                // Default align
4360
                if (!$attrib['align'] && $defaultAlign) {
4361
                    $attrib['align'] = $defaultAlign;
4362
                }
4363
                $params = GeneralUtility::implodeAttributes($attrib, true);
4364
                if (!($conf['removeWrapping'] && !($emptyTag && $conf['removeWrapping.']['keepSingleTag']))) {
4365
                    if ($emptyTag) {
4366
                        $str_content = '<' . strtolower($uTagName) . (trim($params) ? ' ' . trim($params) : '') . ' />';
4367
                    } else {
4368
                        $str_content = '<' . strtolower($uTagName) . (trim($params) ? ' ' . trim($params) : '') . '>' . $str_content . '</' . strtolower($uTagName) . '>';
4369
                    }
4370
                }
4371
            }
4372
            if ($nonWrapped && $conf['wrapNonWrappedLines']) {
4373
                $str_content = $this->wrap($str_content, $conf['wrapNonWrappedLines']);
4374
            }
4375
            $lParts[$k] = $str_content;
4376
        }
4377
        return implode(LF, $lParts);
4378
    }
4379
4380
    /**
4381
     * Finds URLS in text and makes it to a real link.
4382
     * Will find all strings prefixed with "http://" and "https://" in the $data string and make them into a link,
4383
     * linking to the URL we should have found.
4384
     *
4385
     * @param string $data The string in which to search for "http://
4386
     * @param array $conf Configuration for makeLinks, see link
4387
     * @return string The processed input string, being returned.
4388
     * @see _parseFunc()
4389
     */
4390
    public function http_makelinks($data, $conf)
4391
    {
4392
        $aTagParams = $this->getATagParams($conf);
4393
        $textstr = '';
4394
        foreach ([ 'http://', 'https://' ] as $scheme) {
4395
            $textpieces = explode($scheme, $data);
4396
            $pieces = count($textpieces);
4397
            $textstr = $textpieces[0];
4398
            for ($i = 1; $i < $pieces; $i++) {
4399
                $len = strcspn($textpieces[$i], chr(32) . TAB . CRLF);
4400
                if (trim(substr($textstr, -1)) === '' && $len) {
4401
                    $lastChar = substr($textpieces[$i], $len - 1, 1);
4402
                    if (!preg_match('/[A-Za-z0-9\\/#_-]/', $lastChar)) {
4403
                        $len--;
4404
                    }
4405
                    // Included '\/' 3/12
4406
                    $parts[0] = substr($textpieces[$i], 0, $len);
4407
                    $parts[1] = substr($textpieces[$i], $len);
4408
                    $keep = $conf['keep'];
4409
                    $linkParts = parse_url($scheme . $parts[0]);
4410
                    $linktxt = '';
4411
                    if (strstr($keep, 'scheme')) {
4412
                        $linktxt = $scheme;
4413
                    }
4414
                    $linktxt .= $linkParts['host'];
4415
                    if (strstr($keep, 'path')) {
4416
                        $linktxt .= $linkParts['path'];
4417
                        // Added $linkParts['query'] 3/12
4418
                        if (strstr($keep, 'query') && $linkParts['query']) {
4419
                            $linktxt .= '?' . $linkParts['query'];
4420
                        } elseif ($linkParts['path'] === '/') {
4421
                            $linktxt = substr($linktxt, 0, -1);
4422
                        }
4423
                    }
4424
                    if (isset($conf['extTarget'])) {
4425
                        if (isset($conf['extTarget.'])) {
4426
                            $target = $this->stdWrap($conf['extTarget'], $conf['extTarget.']);
4427
                        } else {
4428
                            $target = $conf['extTarget'];
4429
                        }
4430
                    } else {
4431
                        $target = $this->getTypoScriptFrontendController()->extTarget;
4432
                    }
4433
4434
                    // check for jump URLs or similar
4435
                    $linkUrl = $this->processUrl(UrlProcessorInterface::CONTEXT_COMMON, $scheme . $parts[0], $conf);
4436
4437
                    $res = '<a href="' . htmlspecialchars($linkUrl) . '"'
4438
                        . ($target !== '' ? ' target="' . htmlspecialchars($target) . '"' : '')
4439
                        . $aTagParams . $this->extLinkATagParams(('http://' . $parts[0]), 'url') . '>';
4440
4441
                    $wrap = isset($conf['wrap.']) ? $this->stdWrap($conf['wrap'], $conf['wrap.']) : $conf['wrap'];
4442
                    if ((string)$conf['ATagBeforeWrap'] !== '') {
4443
                        $res = $res . $this->wrap($linktxt, $wrap) . '</a>';
4444
                    } else {
4445
                        $res = $this->wrap($res . $linktxt . '</a>', $wrap);
4446
                    }
4447
                    $textstr .= $res . $parts[1];
4448
                } else {
4449
                    $textstr .= $scheme . $textpieces[$i];
4450
                }
4451
            }
4452
            $data = $textstr;
4453
        }
4454
        return $textstr;
4455
    }
4456
4457
    /**
4458
     * Will find all strings prefixed with "mailto:" in the $data string and make them into a link,
4459
     * linking to the email address they point to.
4460
     *
4461
     * @param string $data The string in which to search for "mailto:
4462
     * @param array $conf Configuration for makeLinks, see link
4463
     * @return string The processed input string, being returned.
4464
     * @see _parseFunc()
4465
     */
4466
    public function mailto_makelinks($data, $conf)
4467
    {
4468
        // http-split
4469
        $aTagParams = $this->getATagParams($conf);
4470
        $textpieces = explode('mailto:', $data);
4471
        $pieces = count($textpieces);
4472
        $textstr = $textpieces[0];
4473
        $tsfe = $this->getTypoScriptFrontendController();
4474
        for ($i = 1; $i < $pieces; $i++) {
4475
            $len = strcspn($textpieces[$i], chr(32) . TAB . CRLF);
4476
            if (trim(substr($textstr, -1)) === '' && $len) {
4477
                $lastChar = substr($textpieces[$i], $len - 1, 1);
4478
                if (!preg_match('/[A-Za-z0-9]/', $lastChar)) {
4479
                    $len--;
4480
                }
4481
                $parts[0] = substr($textpieces[$i], 0, $len);
4482
                $parts[1] = substr($textpieces[$i], $len);
4483
                $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...
4484
                list($mailToUrl, $linktxt) = $this->getMailTo($parts[0], $linktxt);
4485
                $mailToUrl = $tsfe->spamProtectEmailAddresses === 'ascii' ? $mailToUrl : htmlspecialchars($mailToUrl);
4486
                $res = '<a href="' . $mailToUrl . '"' . $aTagParams . '>';
4487
                $wrap = isset($conf['wrap.']) ? $this->stdWrap($conf['wrap'], $conf['wrap.']) : $conf['wrap'];
4488
                if ((string)$conf['ATagBeforeWrap'] !== '') {
4489
                    $res = $res . $this->wrap($linktxt, $wrap) . '</a>';
4490
                } else {
4491
                    $res = $this->wrap($res . $linktxt . '</a>', $wrap);
4492
                }
4493
                $textstr .= $res . $parts[1];
4494
            } else {
4495
                $textstr .= 'mailto:' . $textpieces[$i];
4496
            }
4497
        }
4498
        return $textstr;
4499
    }
4500
4501
    /**
4502
     * Creates and returns a TypoScript "imgResource".
4503
     * The value ($file) can either be a file reference (TypoScript resource) or the string "GIFBUILDER".
4504
     * In the first case a current image is returned, possibly scaled down or otherwise processed.
4505
     * In the latter case a GIFBUILDER image is returned; This means an image is made by TYPO3 from layers of elements as GIFBUILDER defines.
4506
     * In the function IMG_RESOURCE() this function is called like $this->getImgResource($conf['file'], $conf['file.']);
4507
     *
4508
     * Structure of the returned info array:
4509
     *  0 => width
4510
     *  1 => height
4511
     *  2 => file extension
4512
     *  3 => file name
4513
     *  origFile => original file name
4514
     *  origFile_mtime => original file mtime
4515
     *  -- only available if processed via FAL: --
4516
     *  originalFile => original file object
4517
     *  processedFile => processed file object
4518
     *  fileCacheHash => checksum of processed file
4519
     *
4520
     * @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.
4521
     * @param array $fileArray TypoScript properties for the imgResource type
4522
     * @return array|null Returns info-array
4523
     * @see IMG_RESOURCE(), cImage(), \TYPO3\CMS\Frontend\Imaging\GifBuilder
4524
     */
4525
    public function getImgResource($file, $fileArray)
4526
    {
4527
        if (empty($file) && empty($fileArray)) {
4528
            return null;
4529
        }
4530
        if (!is_array($fileArray)) {
4531
            $fileArray = (array)$fileArray;
4532
        }
4533
        $imageResource = null;
4534
        $tsfe = $this->getTypoScriptFrontendController();
4535
        if ($file === 'GIFBUILDER') {
4536
            /** @var GifBuilder $gifCreator */
4537
            $gifCreator = GeneralUtility::makeInstance(GifBuilder::class);
4538
            $gifCreator->init();
4539
            $theImage = '';
4540
            if ($GLOBALS['TYPO3_CONF_VARS']['GFX']['gdlib']) {
4541
                $gifCreator->start($fileArray, $this->data);
4542
                $theImage = $gifCreator->gifBuild();
4543
            }
4544
            $imageResource = $gifCreator->getImageDimensions($theImage);
4545
            $imageResource['origFile'] = $theImage;
4546
        } else {
4547
            if ($file instanceof File) {
4548
                $fileObject = $file;
4549
            } elseif ($file instanceof FileReference) {
4550
                $fileObject = $file->getOriginalFile();
4551
            } else {
4552
                try {
4553
                    if ($fileArray['import.']) {
4554
                        $importedFile = trim($this->stdWrap('', $fileArray['import.']));
4555
                        if (!empty($importedFile)) {
4556
                            $file = $importedFile;
4557
                        }
4558
                    }
4559
4560
                    if (MathUtility::canBeInterpretedAsInteger($file)) {
4561
                        $treatIdAsReference = isset($fileArray['treatIdAsReference.']) ? $this->stdWrap($fileArray['treatIdAsReference'], $fileArray['treatIdAsReference.']) : $fileArray['treatIdAsReference'];
4562
                        if (!empty($treatIdAsReference)) {
4563
                            $file = $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

4563
                            $file = $this->getResourceFactory()->getFileReferenceObject(/** @scrutinizer ignore-type */ $file);
Loading history...
4564
                            $fileObject = $file->getOriginalFile();
4565
                        } else {
4566
                            $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

4566
                            $fileObject = $this->getResourceFactory()->getFileObject(/** @scrutinizer ignore-type */ $file);
Loading history...
4567
                        }
4568
                    } elseif (preg_match('/^(0|[1-9][0-9]*):/', $file)) { // combined identifier
4569
                        $fileObject = $this->getResourceFactory()->retrieveFileOrFolderObject($file);
4570
                    } else {
4571
                        if (isset($importedFile) && !empty($importedFile) && !empty($fileArray['import'])) {
4572
                            $file = $fileArray['import'] . $file;
4573
                        }
4574
                        $fileObject = $this->getResourceFactory()->retrieveFileOrFolderObject($file);
4575
                    }
4576
                } catch (Exception $exception) {
4577
                    /** @var \TYPO3\CMS\Core\Log\Logger $logger */
4578
                    $logger = GeneralUtility::makeInstance(LogManager::class)->getLogger(__CLASS__);
4579
                    $logger->warning('The image "' . $file . '" could not be found and won\'t be included in frontend output', ['exception' => $exception]);
4580
                    return null;
4581
                }
4582
            }
4583
            if ($fileObject instanceof File) {
4584
                $processingConfiguration = [];
4585
                $processingConfiguration['width'] = isset($fileArray['width.']) ? $this->stdWrap($fileArray['width'], $fileArray['width.']) : $fileArray['width'];
4586
                $processingConfiguration['height'] = isset($fileArray['height.']) ? $this->stdWrap($fileArray['height'], $fileArray['height.']) : $fileArray['height'];
4587
                $processingConfiguration['fileExtension'] = isset($fileArray['ext.']) ? $this->stdWrap($fileArray['ext'], $fileArray['ext.']) : $fileArray['ext'];
4588
                $processingConfiguration['maxWidth'] = isset($fileArray['maxW.']) ? (int)$this->stdWrap($fileArray['maxW'], $fileArray['maxW.']) : (int)$fileArray['maxW'];
4589
                $processingConfiguration['maxHeight'] = isset($fileArray['maxH.']) ? (int)$this->stdWrap($fileArray['maxH'], $fileArray['maxH.']) : (int)$fileArray['maxH'];
4590
                $processingConfiguration['minWidth'] = isset($fileArray['minW.']) ? (int)$this->stdWrap($fileArray['minW'], $fileArray['minW.']) : (int)$fileArray['minW'];
4591
                $processingConfiguration['minHeight'] = isset($fileArray['minH.']) ? (int)$this->stdWrap($fileArray['minH'], $fileArray['minH.']) : (int)$fileArray['minH'];
4592
                $processingConfiguration['noScale'] = isset($fileArray['noScale.']) ? $this->stdWrap($fileArray['noScale'], $fileArray['noScale.']) : $fileArray['noScale'];
4593
                $processingConfiguration['additionalParameters'] = isset($fileArray['params.']) ? $this->stdWrap($fileArray['params'], $fileArray['params.']) : $fileArray['params'];
4594
                $processingConfiguration['frame'] = isset($fileArray['frame.']) ? (int)$this->stdWrap($fileArray['frame'], $fileArray['frame.']) : (int)$fileArray['frame'];
4595
                if ($file instanceof FileReference) {
4596
                    $processingConfiguration['crop'] = $this->getCropAreaFromFileReference($file, $fileArray);
4597
                } else {
4598
                    $processingConfiguration['crop'] = $this->getCropAreaFromFromTypoScriptSettings($fileObject, $fileArray);
4599
                }
4600
4601
                // Possibility to cancel/force profile extraction
4602
                // see $GLOBALS['TYPO3_CONF_VARS']['GFX']['processor_stripColorProfileCommand']
4603
                if (isset($fileArray['stripProfile'])) {
4604
                    $processingConfiguration['stripProfile'] = $fileArray['stripProfile'];
4605
                }
4606
                // Check if we can handle this type of file for editing
4607
                if (GeneralUtility::inList($GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'], $fileObject->getExtension())) {
4608
                    $maskArray = $fileArray['m.'];
4609
                    // Must render mask images and include in hash-calculating
4610
                    // - otherwise we cannot be sure the filename is unique for the setup!
4611
                    if (is_array($maskArray)) {
4612
                        $mask = $this->getImgResource($maskArray['mask'], $maskArray['mask.']);
4613
                        $bgImg = $this->getImgResource($maskArray['bgImg'], $maskArray['bgImg.']);
4614
                        $bottomImg = $this->getImgResource($maskArray['bottomImg'], $maskArray['bottomImg.']);
4615
                        $bottomImg_mask = $this->getImgResource($maskArray['bottomImg_mask'], $maskArray['bottomImg_mask.']);
4616
4617
                        $processingConfiguration['maskImages']['maskImage'] = $mask['processedFile'];
4618
                        $processingConfiguration['maskImages']['backgroundImage'] = $bgImg['processedFile'];
4619
                        $processingConfiguration['maskImages']['maskBottomImage'] = $bottomImg['processedFile'];
4620
                        $processingConfiguration['maskImages']['maskBottomImageMask'] = $bottomImg_mask['processedFile'];
4621
                    }
4622
                    $processedFileObject = $fileObject->process(ProcessedFile::CONTEXT_IMAGECROPSCALEMASK, $processingConfiguration);
4623
                    $hash = $processedFileObject->calculateChecksum();
4624
                    // store info in the TSFE template cache (kept for backwards compatibility)
4625
                    if ($processedFileObject->isProcessed() && !isset($tsfe->tmpl->fileCache[$hash])) {
4626
                        $tsfe->tmpl->fileCache[$hash] = [
4627
                            0 => $processedFileObject->getProperty('width'),
4628
                            1 => $processedFileObject->getProperty('height'),
4629
                            2 => $processedFileObject->getExtension(),
4630
                            3 => $processedFileObject->getPublicUrl(),
4631
                            'origFile' => $fileObject->getPublicUrl(),
4632
                            'origFile_mtime' => $fileObject->getModificationTime(),
4633
                            // This is needed by \TYPO3\CMS\Frontend\Imaging\GifBuilder,
4634
                            // in order for the setup-array to create a unique filename hash.
4635
                            'originalFile' => $fileObject,
4636
                            'processedFile' => $processedFileObject,
4637
                            'fileCacheHash' => $hash
4638
                        ];
4639
                    }
4640
                    $imageResource = $tsfe->tmpl->fileCache[$hash];
4641
                }
4642
            }
4643
        }
4644
        // If image was processed by GIFBUILDER:
4645
        // ($imageResource indicates that it was processed the regular way)
4646
        if (!isset($imageResource)) {
4647
            $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

4647
            $theImage = $tsfe->tmpl->getFileName(/** @scrutinizer ignore-type */ $file);
Loading history...
Bug introduced by
Are you sure the assignment to $theImage is correct as $tsfe->tmpl->getFileName($file) targeting TYPO3\CMS\Core\TypoScrip...eService::getFileName() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
4648
            if ($theImage) {
4649
                $gifCreator = GeneralUtility::makeInstance(GifBuilder::class);
4650
                /** @var $gifCreator GifBuilder */
4651
                $gifCreator->init();
4652
                $info = $gifCreator->imageMagickConvert($theImage, 'WEB');
0 ignored issues
show
Bug introduced by
$theImage of type void is incompatible with the type string expected by parameter $imagefile of TYPO3\CMS\Core\Imaging\G...s::imageMagickConvert(). ( Ignorable by Annotation )

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

4652
                $info = $gifCreator->imageMagickConvert(/** @scrutinizer ignore-type */ $theImage, 'WEB');
Loading history...
4653
                $info['origFile'] = $theImage;
4654
                // This is needed by \TYPO3\CMS\Frontend\Imaging\GifBuilder, ln 100ff in order for the setup-array to create a unique filename hash.
4655
                $info['origFile_mtime'] = @filemtime($theImage);
0 ignored issues
show
Bug introduced by
$theImage of type void is incompatible with the type string expected by parameter $filename of filemtime(). ( Ignorable by Annotation )

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

4655
                $info['origFile_mtime'] = @filemtime(/** @scrutinizer ignore-type */ $theImage);
Loading history...
4656
                $imageResource = $info;
4657
            }
4658
        }
4659
        // Hook 'getImgResource': Post-processing of image resources
4660
        if (isset($imageResource)) {
4661
            /** @var ContentObjectGetImageResourceHookInterface $hookObject */
4662
            foreach ($this->getGetImgResourceHookObjects() as $hookObject) {
4663
                $imageResource = $hookObject->getImgResourcePostProcess($file, (array)$fileArray, $imageResource, $this);
4664
            }
4665
        }
4666
        return $imageResource;
4667
    }
4668
4669
    /**
4670
     * Returns an ImageManipulation\Area object for the given cropVariant (or 'default')
4671
     * or null if the crop settings or crop area is empty.
4672
     *
4673
     * The cropArea from file reference is used, if not set in TypoScript.
4674
     *
4675
     * Example TypoScript settings:
4676
     * file.crop =
4677
     * OR
4678
     * file.crop = 50,50,100,100
4679
     * OR
4680
     * file.crop.data = file:current:crop
4681
     *
4682
     * @param FileReference $fileReference
4683
     * @param array $fileArray TypoScript properties for the imgResource type
4684
     * @return Area|null
4685
     */
4686
    protected function getCropAreaFromFileReference(FileReference $fileReference, array $fileArray)
4687
    {
4688
        /** @var Area $cropArea */
4689
        $cropArea = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $cropArea is dead and can be removed.
Loading history...
4690
        // Use cropping area from file reference if nothing is configured in TypoScript.
4691
        if (!isset($fileArray['crop']) && !isset($fileArray['crop.'])) {
4692
            // Set crop variant from TypoScript settings. If not set, use default.
4693
            $cropVariant = $fileArray['cropVariant'] ?? 'default';
4694
            $fileCropArea = $this->createCropAreaFromJsonString((string)$fileReference->getProperty('crop'), $cropVariant);
4695
            return $fileCropArea->isEmpty() ? null : $fileCropArea->makeAbsoluteBasedOnFile($fileReference);
4696
        }
4697
4698
        return $this->getCropAreaFromFromTypoScriptSettings($fileReference, $fileArray);
4699
    }
4700
4701
    /**
4702
     * Returns an ImageManipulation\Area object for the given cropVariant (or 'default')
4703
     * or null if the crop settings or crop area is empty.
4704
     *
4705
     * @param FileInterface $file
4706
     * @param array $fileArray
4707
     * @return Area|null
4708
     */
4709
    protected function getCropAreaFromFromTypoScriptSettings(FileInterface $file, array $fileArray)
4710
    {
4711
        /** @var Area $cropArea */
4712
        $cropArea = null;
4713
        // Resolve TypoScript configured cropping.
4714
        $cropSettings = isset($fileArray['crop.'])
4715
            ? $this->stdWrap($fileArray['crop'], $fileArray['crop.'])
4716
            : ($fileArray['crop'] ?? null);
4717
4718
        if (is_string($cropSettings)) {
4719
            // Set crop variant from TypoScript settings. If not set, use default.
4720
            $cropVariant = $fileArray['cropVariant'] ?? 'default';
4721
            // Get cropArea from CropVariantCollection, if cropSettings is a valid json.
4722
            // CropVariantCollection::create does json_decode.
4723
            $jsonCropArea = $this->createCropAreaFromJsonString($cropSettings, $cropVariant);
4724
            $cropArea = $jsonCropArea->isEmpty() ? null : $jsonCropArea->makeAbsoluteBasedOnFile($file);
4725
4726
            // Cropping is configured in TypoScript in the following way: file.crop = 50,50,100,100
4727
            if ($jsonCropArea->isEmpty() && preg_match('/^[0-9]+,[0-9]+,[0-9]+,[0-9]+$/', $cropSettings)) {
4728
                $cropSettings = explode(',', $cropSettings);
4729
                if (count($cropSettings) === 4) {
4730
                    $stringCropArea = GeneralUtility::makeInstance(
4731
                        Area::class,
4732
                        ...$cropSettings
4733
                    );
4734
                    $cropArea = $stringCropArea->isEmpty() ? null : $stringCropArea;
4735
                }
4736
            }
4737
        }
4738
4739
        return $cropArea;
4740
    }
4741
4742
    /**
4743
     * Takes a JSON string and creates CropVariantCollection and fetches the corresponding
4744
     * CropArea for that.
4745
     *
4746
     * @param string $cropSettings
4747
     * @param string $cropVariant
4748
     * @return Area
4749
     */
4750
    protected function createCropAreaFromJsonString(string $cropSettings, string $cropVariant): Area
4751
    {
4752
        return CropVariantCollection::create($cropSettings)->getCropArea($cropVariant);
4753
    }
4754
4755
    /***********************************************
4756
     *
4757
     * Data retrieval etc.
4758
     *
4759
     ***********************************************/
4760
    /**
4761
     * 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.
4762
     *
4763
     * @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)
4764
     * @return string
4765
     */
4766
    public function getFieldVal($field)
4767
    {
4768
        if (!strstr($field, '//')) {
4769
            return $this->data[trim($field)];
4770
        }
4771
        $sections = GeneralUtility::trimExplode('//', $field, true);
4772
        foreach ($sections as $k) {
4773
            if ((string)$this->data[$k] !== '') {
4774
                return $this->data[$k];
4775
            }
4776
        }
4777
4778
        return '';
4779
    }
4780
4781
    /**
4782
     * 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.
4783
     *
4784
     * @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)
4785
     * @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.
4786
     * @return string The value fetched
4787
     * @see getFieldVal()
4788
     */
4789
    public function getData($string, $fieldArray = null)
4790
    {
4791
        $tsfe = $this->getTypoScriptFrontendController();
4792
        if (!is_array($fieldArray)) {
4793
            $fieldArray = $tsfe->page;
4794
        }
4795
        $retVal = '';
4796
        $sections = explode('//', $string);
4797
        foreach ($sections as $secKey => $secVal) {
4798
            if ($retVal) {
4799
                break;
4800
            }
4801
            $parts = explode(':', $secVal, 2);
4802
            $type = strtolower(trim($parts[0]));
4803
            $typesWithOutParameters = ['level', 'date', 'current', 'pagelayout'];
4804
            $key = trim($parts[1]);
4805
            if (($key != '') || in_array($type, $typesWithOutParameters)) {
4806
                switch ($type) {
4807
                    case 'gp':
4808
                        // Merge GET and POST and get $key out of the merged array
4809
                        $getPostArray = GeneralUtility::_GET();
4810
                        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

4810
                        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

4810
                        ArrayUtility::mergeRecursiveWithOverrule($getPostArray, /** @scrutinizer ignore-type */ GeneralUtility::_POST());
Loading history...
4811
                        $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

4811
                        $retVal = $this->getGlobal($key, /** @scrutinizer ignore-type */ $getPostArray);
Loading history...
4812
                        break;
4813
                    case 'tsfe':
4814
                        $retVal = $this->getGlobal('TSFE|' . $key);
4815
                        break;
4816
                    case 'getenv':
4817
                        $retVal = getenv($key);
4818
                        break;
4819
                    case 'getindpenv':
4820
                        $retVal = $this->getEnvironmentVariable($key);
4821
                        break;
4822
                    case 'field':
4823
                        $retVal = $this->getGlobal($key, $fieldArray);
4824
                        break;
4825
                    case 'file':
4826
                        $retVal = $this->getFileDataKey($key);
4827
                        break;
4828
                    case 'parameters':
4829
                        $retVal = $this->parameters[$key];
4830
                        break;
4831
                    case 'register':
4832
                        $retVal = $tsfe->register[$key];
4833
                        break;
4834
                    case 'global':
4835
                        $retVal = $this->getGlobal($key);
4836
                        break;
4837
                    case 'level':
4838
                        $retVal = count($tsfe->tmpl->rootLine) - 1;
4839
                        break;
4840
                    case 'leveltitle':
4841
                        $keyParts = GeneralUtility::trimExplode(',', $key);
4842
                        $numericKey = $this->getKey($keyParts[0], $tsfe->tmpl->rootLine);
4843
                        $retVal = $this->rootLineValue($numericKey, 'title', strtolower($keyParts[1]) === 'slide');
4844
                        break;
4845
                    case 'levelmedia':
4846
                        $keyParts = GeneralUtility::trimExplode(',', $key);
4847
                        $numericKey = $this->getKey($keyParts[0], $tsfe->tmpl->rootLine);
4848
                        $retVal = $this->rootLineValue($numericKey, 'media', strtolower($keyParts[1]) === 'slide');
4849
                        break;
4850
                    case 'leveluid':
4851
                        $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

4851
                        $numericKey = $this->getKey(/** @scrutinizer ignore-type */ $key, $tsfe->tmpl->rootLine);
Loading history...
4852
                        $retVal = $this->rootLineValue($numericKey, 'uid');
4853
                        break;
4854
                    case 'levelfield':
4855
                        $keyParts = GeneralUtility::trimExplode(',', $key);
4856
                        $numericKey = $this->getKey($keyParts[0], $tsfe->tmpl->rootLine);
4857
                        $retVal = $this->rootLineValue($numericKey, $keyParts[1], strtolower($keyParts[2]) === 'slide');
4858
                        break;
4859
                    case 'fullrootline':
4860
                        $keyParts = GeneralUtility::trimExplode(',', $key);
4861
                        $fullKey = (int)$keyParts[0] - count($tsfe->tmpl->rootLine) + count($tsfe->rootLine);
4862
                        if ($fullKey >= 0) {
4863
                            $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

4863
                            $retVal = $this->rootLineValue($fullKey, $keyParts[1], /** @scrutinizer ignore-type */ stristr($keyParts[2], 'slide'), $tsfe->rootLine);
Loading history...
4864
                        }
4865
                        break;
4866
                    case 'date':
4867
                        if (!$key) {
4868
                            $key = 'd/m Y';
4869
                        }
4870
                        $retVal = date($key, $GLOBALS['EXEC_TIME']);
4871
                        break;
4872
                    case 'page':
4873
                        $retVal = $tsfe->page[$key];
4874
                        break;
4875
                    case 'pagelayout':
4876
                        // Check if the current page has a value in the DB field "backend_layout"
4877
                        // if empty, check the root line for "backend_layout_next_level"
4878
                        // same as
4879
                        //   field = backend_layout
4880
                        //   ifEmpty.data = levelfield:-2, backend_layout_next_level, slide
4881
                        //   ifEmpty.ifEmpty = default
4882
                        $retVal = $GLOBALS['TSFE']->page['backend_layout'];
4883
4884
                        // If it is set to "none" - don't use any
4885
                        if ($retVal === '-1') {
4886
                            $retVal = 'none';
4887
                        } elseif ($retVal === '' || $retVal === '0') {
4888
                            // If it not set check the root-line for a layout on next level and use this
4889
                            // Remove first element, which is the current page
4890
                            // See also \TYPO3\CMS\Backend\View\BackendLayoutView::getSelectedCombinedIdentifier()
4891
                            $rootLine = $tsfe->rootLine;
4892
                            array_shift($rootLine);
4893
                            foreach ($rootLine as $rootLinePage) {
4894
                                $retVal = (string) $rootLinePage['backend_layout_next_level'];
4895
                                // If layout for "next level" is set to "none" - don't use any and stop searching
4896
                                if ($retVal === '-1') {
4897
                                    $retVal = 'none';
4898
                                    break;
4899
                                }
4900
                                if ($retVal !== '' && $retVal !== '0') {
4901
                                    // Stop searching if a layout for "next level" is set
4902
                                    break;
4903
                                }
4904
                            }
4905
                        }
4906
                        if ($retVal === '0' || $retVal === '') {
4907
                            $retVal = 'default';
4908
                        }
4909
                        break;
4910
                    case 'current':
4911
                        $retVal = $this->data[$this->currentValKey];
4912
                        break;
4913
                    case 'db':
4914
                        $selectParts = GeneralUtility::trimExplode(':', $key);
4915
                        $db_rec = $tsfe->sys_page->getRawRecord($selectParts[0], $selectParts[1]);
4916
                        if (is_array($db_rec) && $selectParts[2]) {
4917
                            $retVal = $db_rec[$selectParts[2]];
4918
                        }
4919
                        break;
4920
                    case 'lll':
4921
                        $retVal = $tsfe->sL('LLL:' . $key);
4922
                        break;
4923
                    case 'path':
4924
                        $retVal = $tsfe->tmpl->getFileName($key);
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $retVal is correct as $tsfe->tmpl->getFileName($key) targeting TYPO3\CMS\Core\TypoScrip...eService::getFileName() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
4925
                        break;
4926
                    case 'cobj':
4927
                        switch ($key) {
4928
                            case 'parentRecordNumber':
4929
                                $retVal = $this->parentRecordNumber;
4930
                                break;
4931
                        }
4932
                        break;
4933
                    case 'debug':
4934
                        switch ($key) {
4935
                            case 'rootLine':
4936
                                $retVal = DebugUtility::viewArray($tsfe->tmpl->rootLine);
4937
                                break;
4938
                            case 'fullRootLine':
4939
                                $retVal = DebugUtility::viewArray($tsfe->rootLine);
4940
                                break;
4941
                            case 'data':
4942
                                $retVal = DebugUtility::viewArray($this->data);
4943
                                break;
4944
                            case 'register':
4945
                                $retVal = DebugUtility::viewArray($tsfe->register);
4946
                                break;
4947
                            case 'page':
4948
                                $retVal = DebugUtility::viewArray($tsfe->page);
4949
                                break;
4950
                        }
4951
                        break;
4952
                    case 'flexform':
4953
                        $keyParts = GeneralUtility::trimExplode(':', $key, true);
4954
                        if (count($keyParts) === 2 && isset($this->data[$keyParts[0]])) {
4955
                            $flexFormContent = $this->data[$keyParts[0]];
4956
                            if (!empty($flexFormContent)) {
4957
                                $flexFormService = GeneralUtility::makeInstance(FlexFormService::class);
4958
                                $flexFormKey = str_replace('.', '|', $keyParts[1]);
4959
                                $settings = $flexFormService->convertFlexFormContentToArray($flexFormContent);
4960
                                $retVal = $this->getGlobal($flexFormKey, $settings);
4961
                            }
4962
                        }
4963
                        break;
4964
                    case 'session':
4965
                        $keyParts = GeneralUtility::trimExplode('|', $key, true);
4966
                        $sessionKey = array_shift($keyParts);
4967
                        $retVal = $this->getTypoScriptFrontendController()->fe_user->getSessionData($sessionKey);
4968
                        foreach ($keyParts as $keyPart) {
4969
                            if (is_object($retVal)) {
4970
                                $retVal = $retVal->{$keyPart};
4971
                            } elseif (is_array($retVal)) {
4972
                                $retVal = $retVal[$keyPart];
4973
                            } else {
4974
                                $retVal = '';
4975
                                break;
4976
                            }
4977
                        }
4978
                        if (!is_scalar($retVal)) {
4979
                            $retVal = '';
4980
                        }
4981
                        break;
4982
                }
4983
            }
4984
4985
            foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['getData'] ?? [] as $className) {
4986
                $hookObject = GeneralUtility::makeInstance($className);
4987
                if (!$hookObject instanceof ContentObjectGetDataHookInterface) {
4988
                    throw new \UnexpectedValueException('$hookObject must implement interface ' . ContentObjectGetDataHookInterface::class, 1195044480);
4989
                }
4990
                $retVal = $hookObject->getDataExtension($string, $fieldArray, $secVal, $retVal, $this);
4991
            }
4992
        }
4993
        return $retVal;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $retVal also could return the type false|array which is incompatible with the documented return type string.
Loading history...
4994
    }
4995
4996
    /**
4997
     * Gets file information. This is a helper function for the getData() method above, which resolves e.g.
4998
     * page.10.data = file:current:title
4999
     * or
5000
     * page.10.data = file:17:title
5001
     *
5002
     * @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.)
5003
     * @return string|int The value as retrieved from the file object.
5004
     */
5005
    protected function getFileDataKey($key)
5006
    {
5007
        list($fileUidOrCurrentKeyword, $requestedFileInformationKey) = explode(':', $key, 3);
5008
        try {
5009
            if ($fileUidOrCurrentKeyword === 'current') {
5010
                $fileObject = $this->getCurrentFile();
5011
            } elseif (MathUtility::canBeInterpretedAsInteger($fileUidOrCurrentKeyword)) {
5012
                /** @var ResourceFactory $fileFactory */
5013
                $fileFactory = GeneralUtility::makeInstance(ResourceFactory::class);
5014
                $fileObject = $fileFactory->getFileObject($fileUidOrCurrentKeyword);
5015
            } else {
5016
                $fileObject = null;
5017
            }
5018
        } catch (Exception $exception) {
5019
            /** @var \TYPO3\CMS\Core\Log\Logger $logger */
5020
            $logger = GeneralUtility::makeInstance(LogManager::class)->getLogger(__CLASS__);
5021
            $logger->warning('The file "' . $fileUidOrCurrentKeyword . '" could not be found and won\'t be included in frontend output', ['exception' => $exception]);
5022
            $fileObject = null;
5023
        }
5024
5025
        if ($fileObject instanceof FileInterface) {
5026
            // All properties of the \TYPO3\CMS\Core\Resource\FileInterface are available here:
5027
            switch ($requestedFileInformationKey) {
5028
                case 'name':
5029
                    return $fileObject->getName();
5030
                case 'uid':
5031
                    if (method_exists($fileObject, 'getUid')) {
5032
                        return $fileObject->getUid();
5033
                    }
5034
                    return 0;
5035
                case 'originalUid':
5036
                    if ($fileObject instanceof FileReference) {
5037
                        return $fileObject->getOriginalFile()->getUid();
5038
                    }
5039
                    return null;
5040
                case 'size':
5041
                    return $fileObject->getSize();
5042
                case 'sha1':
5043
                    return $fileObject->getSha1();
5044
                case 'extension':
5045
                    return $fileObject->getExtension();
5046
                case 'mimetype':
5047
                    return $fileObject->getMimeType();
5048
                case 'contents':
5049
                    return $fileObject->getContents();
5050
                case 'publicUrl':
5051
                    return $fileObject->getPublicUrl();
5052
                default:
5053
                    // Generic alternative here
5054
                    return $fileObject->getProperty($requestedFileInformationKey);
5055
            }
5056
        } else {
5057
            // @todo fail silently as is common in tslib_content
5058
            return 'Error: no file object';
5059
        }
5060
    }
5061
5062
    /**
5063
     * Returns a value from the current rootline (site) from $GLOBALS['TSFE']->tmpl->rootLine;
5064
     *
5065
     * @param string $key Which level in the root line
5066
     * @param string $field The field in the rootline record to return (a field from the pages table)
5067
     * @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
5068
     * @param mixed $altRootLine If you supply an array for this it will be used as an alternative root line array
5069
     * @return string The value from the field of the rootline.
5070
     * @access private
5071
     * @see getData()
5072
     */
5073
    public function rootLineValue($key, $field, $slideBack = false, $altRootLine = '')
5074
    {
5075
        $rootLine = is_array($altRootLine) ? $altRootLine : $this->getTypoScriptFrontendController()->tmpl->rootLine;
5076
        if (!$slideBack) {
5077
            return $rootLine[$key][$field];
5078
        }
5079
        for ($a = $key; $a >= 0; $a--) {
5080
            $val = $rootLine[$a][$field];
5081
            if ($val) {
5082
                return $val;
5083
            }
5084
        }
5085
5086
        return '';
5087
    }
5088
5089
    /**
5090
     * Return global variable where the input string $var defines array keys separated by "|"
5091
     * Example: $var = "HTTP_SERVER_VARS | something" will return the value $GLOBALS['HTTP_SERVER_VARS']['something'] value
5092
     *
5093
     * @param string $keyString Global var key, eg. "HTTP_GET_VAR" or "HTTP_GET_VARS|id" to get the GET parameter "id" back.
5094
     * @param array $source Alternative array than $GLOBAL to get variables from.
5095
     * @return mixed Whatever value. If none, then blank string.
5096
     * @see getData()
5097
     */
5098
    public function getGlobal($keyString, $source = null)
5099
    {
5100
        $keys = explode('|', $keyString);
5101
        $numberOfLevels = count($keys);
5102
        $rootKey = trim($keys[0]);
5103
        $value = isset($source) ? $source[$rootKey] : $GLOBALS[$rootKey];
5104
        for ($i = 1; $i < $numberOfLevels && isset($value); $i++) {
5105
            $currentKey = trim($keys[$i]);
5106
            if (is_object($value)) {
5107
                $value = $value->{$currentKey};
5108
            } elseif (is_array($value)) {
5109
                $value = $value[$currentKey];
5110
            } else {
5111
                $value = '';
5112
                break;
5113
            }
5114
        }
5115
        if (!is_scalar($value)) {
5116
            $value = '';
5117
        }
5118
        return $value;
5119
    }
5120
5121
    /**
5122
     * 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).
5123
     * Example: entrylevel = -1 means that entryLevel ends up pointing at the outermost-level, -2 means the level before the outermost...
5124
     *
5125
     * @param int $key The integer to transform
5126
     * @param array $arr array in which the key should be found.
5127
     * @return int The processed integer key value.
5128
     * @access private
5129
     * @see getData()
5130
     */
5131
    public function getKey($key, $arr)
5132
    {
5133
        $key = (int)$key;
5134
        if (is_array($arr)) {
5135
            if ($key < 0) {
5136
                $key = count($arr) + $key;
5137
            }
5138
            if ($key < 0) {
5139
                $key = 0;
5140
            }
5141
        }
5142
        return $key;
5143
    }
5144
5145
    /***********************************************
5146
     *
5147
     * Link functions (typolink)
5148
     *
5149
     ***********************************************/
5150
5151
    /**
5152
     * called from the typoLink() function
5153
     *
5154
     * does the magic to split the full "typolink" string like "15,13 _blank myclass &more=1"
5155
     * into separate parts
5156
     *
5157
     * @param string $linkText The string (text) to link
5158
     * @param string $mixedLinkParameter destination data like "15,13 _blank myclass &more=1" used to create the link
5159
     * @param array $configuration TypoScript configuration
5160
     * @return array|string
5161
     * @see typoLink()
5162
     *
5163
     * @todo the functionality of the "file:" syntax + the hook should be marked as deprecated, an upgrade wizard should handle existing links
5164
     */
5165
    protected function resolveMixedLinkParameter($linkText, $mixedLinkParameter, &$configuration = [])
5166
    {
5167
        $linkParameter = null;
5168
5169
        // Link parameter value = first part
5170
        $linkParameterParts = GeneralUtility::makeInstance(TypoLinkCodecService::class)->decode($mixedLinkParameter);
5171
5172
        // Check for link-handler keyword
5173
        list($linkHandlerKeyword, $linkHandlerValue) = explode(':', $linkParameterParts['url'], 2);
5174
        if ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['typolinkLinkHandler'][$linkHandlerKeyword] && (string)$linkHandlerValue !== '') {
5175
            $linkHandlerObj = GeneralUtility::makeInstance($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['typolinkLinkHandler'][$linkHandlerKeyword]);
5176
            if (method_exists($linkHandlerObj, 'main')) {
5177
                return $linkHandlerObj->main($linkText, $configuration, $linkHandlerKeyword, $linkHandlerValue, $mixedLinkParameter, $this);
5178
            }
5179
        }
5180
5181
        // Resolve FAL-api "file:UID-of-sys_file-record" and "file:combined-identifier"
5182
        if ($linkHandlerKeyword === 'file' && strpos($linkParameterParts['url'], 'file://') !== 0) {
5183
            try {
5184
                $fileOrFolderObject = $this->getResourceFactory()->retrieveFileOrFolderObject($linkHandlerValue);
5185
                // Link to a folder or file
5186
                if ($fileOrFolderObject instanceof File || $fileOrFolderObject instanceof Folder) {
5187
                    $linkParameter = $fileOrFolderObject->getPublicUrl();
5188
                } else {
5189
                    $linkParameter = null;
5190
                }
5191
            } catch (\RuntimeException $e) {
5192
                // Element wasn't found
5193
                $linkParameter = null;
5194
            } catch (ResourceDoesNotExistException $e) {
5195
                // Resource was not found
5196
                return $linkText;
5197
            }
5198
        } elseif (in_array(strtolower(trim($linkHandlerKeyword)), ['javascript', 'data'], true)) {
5199
            // Disallow direct javascript: or data: links
5200
            return $linkText;
5201
        } else {
5202
            $linkParameter = $linkParameterParts['url'];
5203
        }
5204
5205
        // additional parameters that need to be set
5206
        if ($linkParameterParts['additionalParams'] !== '') {
5207
            $forceParams = $linkParameterParts['additionalParams'];
5208
            // params value
5209
            $configuration['additionalParams'] .= $forceParams[0] === '&' ? $forceParams : '&' . $forceParams;
5210
        }
5211
5212
        return [
5213
            'href'   => $linkParameter,
5214
            'target' => $linkParameterParts['target'],
5215
            'class'  => $linkParameterParts['class'],
5216
            'title'  => $linkParameterParts['title']
5217
        ];
5218
    }
5219
5220
    /**
5221
     * Implements the "typolink" property of stdWrap (and others)
5222
     * 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.
5223
     * 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.
5224
     * 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.
5225
     * For many more details on the parameters and how they are interpreted, please see the link to TSref below.
5226
     *
5227
     * the FAL API is handled with the namespace/prefix "file:..."
5228
     *
5229
     * @param string $linkText The string (text) to link
5230
     * @param array $conf TypoScript configuration (see link below)
5231
     * @return string A link-wrapped string.
5232
     * @see stdWrap(), \TYPO3\CMS\Frontend\Plugin\AbstractPlugin::pi_linkTP()
5233
     */
5234
    public function typoLink($linkText, $conf)
5235
    {
5236
        $linkText = (string)$linkText;
5237
        $tsfe = $this->getTypoScriptFrontendController();
5238
5239
        $linkParameter = trim(isset($conf['parameter.']) ? $this->stdWrap($conf['parameter'], $conf['parameter.']) : $conf['parameter']);
5240
        $this->lastTypoLinkUrl = '';
5241
        $this->lastTypoLinkTarget = '';
5242
5243
        $resolvedLinkParameters = $this->resolveMixedLinkParameter($linkText, $linkParameter, $conf);
5244
        // check if the link handler hook has resolved the link completely already
5245
        if (!is_array($resolvedLinkParameters)) {
5246
            return $resolvedLinkParameters;
5247
        }
5248
        $linkParameter = $resolvedLinkParameters['href'];
5249
        $target = $resolvedLinkParameters['target'];
5250
        $title = $resolvedLinkParameters['title'];
5251
5252
        if (!$linkParameter) {
5253
            return $linkText;
5254
        }
5255
5256
        // Detecting kind of link and resolve all necessary parameters
5257
        $linkService = GeneralUtility::makeInstance(LinkService::class);
5258
        try {
5259
            $linkDetails = $linkService->resolve($linkParameter);
5260
        } catch (Exception\InvalidPathException $exception) {
5261
            $logger = GeneralUtility::makeInstance(LogManager::class)->getLogger(__CLASS__);
5262
            $logger->warning('The link could not be generated', ['exception' => $exception]);
5263
5264
            return $linkText;
5265
        }
5266
5267
        $linkDetails['typoLinkParameter'] = $linkParameter;
5268
        if (isset($linkDetails['type']) && isset($GLOBALS['TYPO3_CONF_VARS']['FE']['typolinkBuilder'][$linkDetails['type']])) {
5269
            /** @var AbstractTypolinkBuilder $linkBuilder */
5270
            $linkBuilder = GeneralUtility::makeInstance(
5271
                $GLOBALS['TYPO3_CONF_VARS']['FE']['typolinkBuilder'][$linkDetails['type']],
5272
                $this
5273
            );
5274
            try {
5275
                list($this->lastTypoLinkUrl, $linkText, $target) = $linkBuilder->build($linkDetails, $linkText, $target, $conf);
5276
            } catch (UnableToLinkException $e) {
5277
                // Only return the link text directly
5278
                return $e->getLinkText();
5279
            }
5280
        } elseif (isset($linkDetails['url'])) {
5281
            $this->lastTypoLinkUrl = $linkDetails['url'];
5282
        } else {
5283
            return $linkText;
5284
        }
5285
5286
        $finalTagParts = [
5287
            'aTagParams' => $this->getATagParams($conf) . $this->extLinkATagParams($this->lastTypoLinkUrl, $linkDetails['type']),
5288
            'url'        => $this->lastTypoLinkUrl,
5289
            'TYPE'       => $linkDetails['type']
5290
        ];
5291
5292
        // Ensure "href" is not in the list of aTagParams to avoid double tags, usually happens within buggy parseFunc settings
5293
        if (!empty($finalTagParts['aTagParams'])) {
5294
            $aTagParams = GeneralUtility::get_tag_attributes($finalTagParts['aTagParams']);
5295
            if (isset($aTagParams['href'])) {
5296
                unset($aTagParams['href']);
5297
                $finalTagParts['aTagParams'] = GeneralUtility::implodeAttributes($aTagParams);
5298
            }
5299
        }
5300
5301
        // Building the final <a href=".."> tag
5302
        $tagAttributes = [];
5303
5304
        // Title attribute
5305
        if (empty($title)) {
5306
            $title = $conf['title'];
5307
            if ($conf['title.']) {
5308
                $title = $this->stdWrap($title, $conf['title.']);
5309
            }
5310
        }
5311
5312
        // Check, if the target is coded as a JS open window link:
5313
        $JSwindowParts = [];
5314
        $JSwindowParams = '';
5315
        if ($target && preg_match('/^([0-9]+)x([0-9]+)(:(.*)|.*)$/', $target, $JSwindowParts)) {
5316
            // Take all pre-configured and inserted parameters and compile parameter list, including width+height:
5317
            $JSwindow_tempParamsArr = GeneralUtility::trimExplode(',', strtolower($conf['JSwindow_params'] . ',' . $JSwindowParts[4]), true);
5318
            $JSwindow_paramsArr = [];
5319
            foreach ($JSwindow_tempParamsArr as $JSv) {
5320
                list($JSp, $JSv) = explode('=', $JSv, 2);
5321
                $JSwindow_paramsArr[$JSp] = $JSp . '=' . $JSv;
5322
            }
5323
            // Add width/height:
5324
            $JSwindow_paramsArr['width'] = 'width=' . $JSwindowParts[1];
5325
            $JSwindow_paramsArr['height'] = 'height=' . $JSwindowParts[2];
5326
            // Imploding into string:
5327
            $JSwindowParams = implode(',', $JSwindow_paramsArr);
5328
        }
5329
        if (!$JSwindowParams && $linkDetails['type'] === LinkService::TYPE_EMAIL && $tsfe->spamProtectEmailAddresses === 'ascii') {
5330
            $tagAttributes['href'] = $finalTagParts['url'];
5331
        } else {
5332
            $tagAttributes['href'] = htmlspecialchars($finalTagParts['url']);
5333
        }
5334
        if (!empty($title)) {
5335
            $tagAttributes['title'] = htmlspecialchars($title);
5336
        }
5337
5338
        // Target attribute
5339
        if (!empty($target)) {
5340
            $tagAttributes['target'] = htmlspecialchars($target);
5341
        } elseif ($JSwindowParams && !in_array($tsfe->xhtmlDoctype, ['xhtml_strict', 'xhtml_11'], true)) {
5342
            // Create TARGET-attribute only if the right doctype is used
5343
            $tagAttributes['target'] = 'FEopenLink';
5344
        }
5345
5346
        if ($JSwindowParams) {
5347
            $onClick = 'vHWin=window.open(' . GeneralUtility::quoteJSvalue($tsfe->baseUrlWrap($finalTagParts['url'])) . ',\'FEopenLink\',' . GeneralUtility::quoteJSvalue($JSwindowParams) . ');vHWin.focus();return false;';
5348
            $tagAttributes['onclick'] = htmlspecialchars($onClick);
5349
        }
5350
5351
        if (!empty($resolvedLinkParameters['class'])) {
5352
            $tagAttributes['class'] = htmlspecialchars($resolvedLinkParameters['class']);
5353
        }
5354
5355
        // Prevent trouble with double and missing spaces between attributes and merge params before implode
5356
        $finalTagAttributes = array_merge($tagAttributes, GeneralUtility::get_tag_attributes($finalTagParts['aTagParams']));
5357
        $finalAnchorTag = '<a ' . GeneralUtility::implodeAttributes($finalTagAttributes) . '>';
5358
5359
        if (!empty($finalTagParts['aTagParams'])) {
5360
            $tagAttributes = array_merge($tagAttributes, GeneralUtility::get_tag_attributes($finalTagParts['aTagParams']));
5361
        }
5362
        // kept for backwards-compatibility in hooks
5363
        $finalTagParts['targetParams'] = !empty($tagAttributes['target']) ? ' target="' . $tagAttributes['target'] . '"' : '';
5364
        $this->lastTypoLinkTarget = $target;
5365
5366
        // Call user function:
5367
        if ($conf['userFunc']) {
5368
            $finalTagParts['TAG'] = $finalAnchorTag;
5369
            $finalAnchorTag = $this->callUserFunction($conf['userFunc'], $conf['userFunc.'], $finalTagParts);
5370
        }
5371
5372
        // Hook: Call post processing function for link rendering:
5373
        $_params = [
5374
            'conf' => &$conf,
5375
            'linktxt' => &$linkText,
5376
            'finalTag' => &$finalAnchorTag,
5377
            'finalTagParts' => &$finalTagParts,
5378
            'linkDetails' => &$linkDetails,
5379
            'tagAttributes' => &$tagAttributes
5380
        ];
5381
        foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['typoLink_PostProc'] ?? [] as $_funcRef) {
5382
            GeneralUtility::callUserFunction($_funcRef, $_params, $this);
5383
        }
5384
5385
        // If flag "returnLastTypoLinkUrl" set, then just return the latest URL made:
5386
        if ($conf['returnLast']) {
5387
            switch ($conf['returnLast']) {
5388
                case 'url':
5389
                    return $this->lastTypoLinkUrl;
5390
                    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...
5391
                case 'target':
5392
                    return $this->lastTypoLinkTarget;
5393
                    break;
5394
            }
5395
        }
5396
5397
        $wrap = isset($conf['wrap.']) ? $this->stdWrap($conf['wrap'], $conf['wrap.']) : $conf['wrap'];
5398
5399
        if ($conf['ATagBeforeWrap']) {
5400
            return $finalAnchorTag . $this->wrap($linkText, $wrap) . '</a>';
5401
        }
5402
        return $this->wrap($finalAnchorTag . $linkText . '</a>', $wrap);
5403
    }
5404
5405
    /**
5406
     * Based on the input "TypoLink" TypoScript configuration this will return the generated URL
5407
     *
5408
     * @param array $conf TypoScript properties for "typolink
5409
     * @return string The URL of the link-tag that typolink() would by itself return
5410
     * @see typoLink()
5411
     */
5412
    public function typoLink_URL($conf)
5413
    {
5414
        $this->typoLink('|', $conf);
5415
        return $this->lastTypoLinkUrl;
5416
    }
5417
5418
    /**
5419
     * Returns a linked string made from typoLink parameters.
5420
     *
5421
     * 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.
5422
     * Optionally you can supply $urlParameters which is an array with key/value pairs that are rawurlencoded and appended to the resulting url.
5423
     *
5424
     * @param string $label Text string being wrapped by the link.
5425
     * @param string $params Link parameter; eg. "123" for page id, "[email protected]" for email address, "http://...." for URL, "fileadmin/blabla.txt" for file.
5426
     * @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.
5427
     * @param string $target Specific target set, if any. (Default is using the current)
5428
     * @return string The wrapped $label-text string
5429
     * @see getTypoLink_URL()
5430
     */
5431
    public function getTypoLink($label, $params, $urlParameters = [], $target = '')
5432
    {
5433
        $conf = [];
5434
        $conf['parameter'] = $params;
5435
        if ($target) {
5436
            $conf['target'] = $target;
5437
            $conf['extTarget'] = $target;
5438
            $conf['fileTarget'] = $target;
5439
        }
5440
        if (is_array($urlParameters)) {
5441
            if (!empty($urlParameters)) {
5442
                $conf['additionalParams'] .= GeneralUtility::implodeArrayForUrl('', $urlParameters);
5443
            }
5444
        } else {
5445
            $conf['additionalParams'] .= $urlParameters;
5446
        }
5447
        $out = $this->typoLink($label, $conf);
5448
        return $out;
5449
    }
5450
5451
    /**
5452
     * Returns the canonical URL to the current "location", which include the current page ID and type
5453
     * and optionally the query string
5454
     *
5455
     * @param bool $addQueryString Whether additional GET arguments in the query string should be included or not
5456
     * @return string
5457
     */
5458
    public function getUrlToCurrentLocation($addQueryString = true)
5459
    {
5460
        $conf = [];
5461
        $conf['parameter'] = $this->getTypoScriptFrontendController()->id . ',' . $this->getTypoScriptFrontendController()->type;
5462
        if ($addQueryString) {
5463
            $conf['addQueryString'] = '1';
5464
            $linkVars = implode(',', array_keys(GeneralUtility::explodeUrl2Array($this->getTypoScriptFrontendController()->linkVars)));
5465
            $conf['addQueryString.'] = [
5466
                'method' => 'GET',
5467
                'exclude' => 'id,type,cHash' . ($linkVars ? ',' . $linkVars : '')
5468
            ];
5469
            $conf['useCacheHash'] = GeneralUtility::_GET('cHash') ? '1' : '0';
5470
        }
5471
5472
        return $this->typoLink_URL($conf);
5473
    }
5474
5475
    /**
5476
     * Returns the URL of a "typolink" create from the input parameter string, url-parameters and target
5477
     *
5478
     * @param string $params Link parameter; eg. "123" for page id, "[email protected]" for email address, "http://...." for URL, "fileadmin/blabla.txt" for file.
5479
     * @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.
5480
     * @param string $target Specific target set, if any. (Default is using the current)
5481
     * @return string The URL
5482
     * @see getTypoLink()
5483
     */
5484
    public function getTypoLink_URL($params, $urlParameters = [], $target = '')
5485
    {
5486
        $this->getTypoLink('', $params, $urlParameters, $target);
5487
        return $this->lastTypoLinkUrl;
5488
    }
5489
5490
    /**
5491
     * Generates a typolink and returns the two link tags - start and stop - in an array
5492
     *
5493
     * @param array $conf "typolink" TypoScript properties
5494
     * @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
5495
     * @see typolink()
5496
     */
5497
    public function typolinkWrap($conf)
5498
    {
5499
        $k = md5(microtime());
5500
        return explode($k, $this->typoLink($k, $conf));
5501
    }
5502
5503
    /**
5504
     * Returns the current page URL
5505
     *
5506
     * @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.
5507
     * @param int $id An alternative ID to the current id ($GLOBALS['TSFE']->id)
5508
     * @return string The URL
5509
     * @see getTypoLink_URL()
5510
     */
5511
    public function currentPageUrl($urlParameters = [], $id = 0)
5512
    {
5513
        $tsfe = $this->getTypoScriptFrontendController();
5514
        return $this->getTypoLink_URL($id ?: $tsfe->id, $urlParameters, $tsfe->sPre);
5515
    }
5516
5517
    /**
5518
     * Loops over all configured URL modifier hooks (if available) and returns the generated URL or NULL if no URL was generated.
5519
     *
5520
     * @param string $context The context in which the method is called (e.g. typoLink).
5521
     * @param string $url The URL that should be processed.
5522
     * @param array $typolinkConfiguration The current link configuration array.
5523
     * @return string|null Returns NULL if URL was not processed or the processed URL as a string.
5524
     * @throws \RuntimeException if a hook was registered but did not fulfill the correct parameters.
5525
     */
5526
    protected function processUrl($context, $url, $typolinkConfiguration = [])
5527
    {
5528
        $urlProcessors = $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['urlProcessing']['urlProcessors'] ?? [];
5529
        if (empty($urlProcessors)) {
5530
            return $url;
5531
        }
5532
5533
        foreach ($urlProcessors as $identifier => $configuration) {
5534
            if (empty($configuration) || !is_array($configuration)) {
5535
                throw new \RuntimeException('Missing configuration for URI processor "' . $identifier . '".', 1442050529);
5536
            }
5537
            if (!is_string($configuration['processor']) || empty($configuration['processor']) || !class_exists($configuration['processor']) || !is_subclass_of($configuration['processor'], UrlProcessorInterface::class)) {
5538
                throw new \RuntimeException('The URI processor "' . $identifier . '" defines an invalid provider. Ensure the class exists and implements the "' . UrlProcessorInterface::class . '".', 1442050579);
5539
            }
5540
        }
5541
5542
        $orderedProcessors = GeneralUtility::makeInstance(DependencyOrderingService::class)->orderByDependencies($urlProcessors);
5543
        $keepProcessing = true;
5544
5545
        foreach ($orderedProcessors as $configuration) {
5546
            /** @var UrlProcessorInterface $urlProcessor */
5547
            $urlProcessor = GeneralUtility::makeInstance($configuration['processor']);
5548
            $url = $urlProcessor->process($context, $url, $typolinkConfiguration, $this, $keepProcessing);
5549
            if (!$keepProcessing) {
5550
                break;
5551
            }
5552
        }
5553
5554
        return $url;
5555
    }
5556
5557
    /**
5558
     * Creates a href attibute for given $mailAddress.
5559
     * The function uses spamProtectEmailAddresses for encoding the mailto statement.
5560
     * If spamProtectEmailAddresses is disabled, it'll just return a string like "mailto:[email protected]".
5561
     *
5562
     * @param string $mailAddress Email address
5563
     * @param string $linktxt Link text, default will be the email address.
5564
     * @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.
5565
     */
5566
    public function getMailTo($mailAddress, $linktxt)
5567
    {
5568
        $mailAddress = (string)$mailAddress;
5569
        if ((string)$linktxt === '') {
5570
            $linktxt = htmlspecialchars($mailAddress);
5571
        }
5572
5573
        $originalMailToUrl = 'mailto:' . $mailAddress;
5574
        $mailToUrl = $this->processUrl(UrlProcessorInterface::CONTEXT_MAIL, $originalMailToUrl);
5575
5576
        // no processing happened, therefore, the default processing kicks in
5577
        if ($mailToUrl === $originalMailToUrl) {
5578
            $tsfe = $this->getTypoScriptFrontendController();
5579
            if ($tsfe->spamProtectEmailAddresses) {
5580
                $mailToUrl = $this->encryptEmail($mailToUrl, $tsfe->spamProtectEmailAddresses);
5581
                if ($tsfe->spamProtectEmailAddresses !== 'ascii') {
5582
                    $mailToUrl = 'javascript:linkTo_UnCryptMailto(' . GeneralUtility::quoteJSvalue($mailToUrl) . ');';
5583
                }
5584
                $atLabel = trim($tsfe->config['config']['spamProtectEmailAddresses_atSubst']) ?: '(at)';
5585
                $spamProtectedMailAddress = str_replace('@', $atLabel, htmlspecialchars($mailAddress));
5586
                if ($tsfe->config['config']['spamProtectEmailAddresses_lastDotSubst']) {
5587
                    $lastDotLabel = trim($tsfe->config['config']['spamProtectEmailAddresses_lastDotSubst']);
5588
                    $lastDotLabel = $lastDotLabel ? $lastDotLabel : '(dot)';
5589
                    $spamProtectedMailAddress = preg_replace('/\\.([^\\.]+)$/', $lastDotLabel . '$1', $spamProtectedMailAddress);
5590
                }
5591
                $linktxt = str_ireplace($mailAddress, $spamProtectedMailAddress, $linktxt);
5592
            }
5593
        }
5594
5595
        return [$mailToUrl, $linktxt];
0 ignored issues
show
Bug Best Practice introduced by
The expression return array($mailToUrl, $linktxt) returns the type array<integer,null|string|mixed> which is incompatible with the documented return type string.
Loading history...
5596
    }
5597
5598
    /**
5599
     * Encryption of email addresses for <A>-tags See the spam protection setup in TS 'config.'
5600
     *
5601
     * @param string $string Input string to en/decode: "mailto:[email protected]
5602
     * @param mixed  $type - either "ascii" or a number between -10 and 10, taken from config.spamProtectEmailAddresses
5603
     * @return string encoded version of $string
5604
     */
5605
    protected function encryptEmail($string, $type)
5606
    {
5607
        $out = '';
5608
        // obfuscates using the decimal HTML entity references for each character
5609
        if ($type === 'ascii') {
5610
            $stringLength = strlen($string);
5611
            for ($a = 0; $a < $stringLength; $a++) {
5612
                $out .= '&#' . ord(substr($string, $a, 1)) . ';';
5613
            }
5614
        } else {
5615
            // like str_rot13() but with a variable offset and a wider character range
5616
            $len = strlen($string);
5617
            $offset = (int)$type;
5618
            for ($i = 0; $i < $len; $i++) {
5619
                $charValue = ord($string[$i]);
5620
                // 0-9 . , - + / :
5621
                if ($charValue >= 43 && $charValue <= 58) {
5622
                    $out .= $this->encryptCharcode($charValue, 43, 58, $offset);
5623
                } elseif ($charValue >= 64 && $charValue <= 90) {
5624
                    // A-Z @
5625
                    $out .= $this->encryptCharcode($charValue, 64, 90, $offset);
5626
                } elseif ($charValue >= 97 && $charValue <= 122) {
5627
                    // a-z
5628
                    $out .= $this->encryptCharcode($charValue, 97, 122, $offset);
5629
                } else {
5630
                    $out .= $string[$i];
5631
                }
5632
            }
5633
        }
5634
        return $out;
5635
    }
5636
5637
    /**
5638
     * Decryption of email addresses for <A>-tags See the spam protection setup in TS 'config.'
5639
     *
5640
     * @param string $string Input string to en/decode: "mailto:[email protected]
5641
     * @param mixed  $type - either "ascii" or a number between -10 and 10 taken from config.spamProtectEmailAddresses
5642
     * @return string decoded version of $string
5643
     */
5644
    protected function decryptEmail($string, $type)
5645
    {
5646
        $out = '';
5647
        // obfuscates using the decimal HTML entity references for each character
5648
        if ($type === 'ascii') {
5649
            $stringLength = strlen($string);
5650
            for ($a = 0; $a < $stringLength; $a++) {
5651
                $out .= '&#' . ord(substr($string, $a, 1)) . ';';
5652
            }
5653
        } else {
5654
            // like str_rot13() but with a variable offset and a wider character range
5655
            $len = strlen($string);
5656
            $offset = (int)$type * -1;
5657
            for ($i = 0; $i < $len; $i++) {
5658
                $charValue = ord($string[$i]);
5659
                // 0-9 . , - + / :
5660
                if ($charValue >= 43 && $charValue <= 58) {
5661
                    $out .= $this->encryptCharcode($charValue, 43, 58, $offset);
5662
                } elseif ($charValue >= 64 && $charValue <= 90) {
5663
                    // A-Z @
5664
                    $out .= $this->encryptCharcode($charValue, 64, 90, $offset);
5665
                } elseif ($charValue >= 97 && $charValue <= 122) {
5666
                    // a-z
5667
                    $out .= $this->encryptCharcode($charValue, 97, 122, $offset);
5668
                } else {
5669
                    $out .= $string[$i];
5670
                }
5671
            }
5672
        }
5673
        return $out;
5674
    }
5675
5676
    /**
5677
     * Encryption (or decryption) of a single character.
5678
     * Within the given range the character is shifted with the supplied offset.
5679
     *
5680
     * @param int $n Ordinal of input character
5681
     * @param int $start Start of range
5682
     * @param int $end End of range
5683
     * @param int $offset Offset
5684
     * @return string encoded/decoded version of character
5685
     */
5686
    protected function encryptCharcode($n, $start, $end, $offset)
5687
    {
5688
        $n = $n + $offset;
5689
        if ($offset > 0 && $n > $end) {
5690
            $n = $start + ($n - $end - 1);
5691
        } elseif ($offset < 0 && $n < $start) {
5692
            $n = $end - ($start - $n - 1);
5693
        }
5694
        return chr($n);
5695
    }
5696
5697
    /**
5698
     * Gets the query arguments and assembles them for URLs.
5699
     * Arguments may be removed or set, depending on configuration.
5700
     *
5701
     * @param array $conf Configuration
5702
     * @param array $overruleQueryArguments Multidimensional key/value pairs that overrule incoming query arguments
5703
     * @param bool $forceOverruleArguments If set, key/value pairs not in the query but the overrule array will be set
5704
     * @return string The URL query part (starting with a &)
5705
     */
5706
    public function getQueryArguments($conf, $overruleQueryArguments = [], $forceOverruleArguments = false)
5707
    {
5708
        switch ((string)$conf['method']) {
5709
            case 'GET':
5710
                $currentQueryArray = GeneralUtility::_GET();
5711
                break;
5712
            case 'POST':
5713
                $currentQueryArray = GeneralUtility::_POST();
5714
                break;
5715
            case 'GET,POST':
5716
                $currentQueryArray = GeneralUtility::_GET();
5717
                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

5717
                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

5717
                ArrayUtility::mergeRecursiveWithOverrule(/** @scrutinizer ignore-type */ $currentQueryArray, GeneralUtility::_POST());
Loading history...
5718
                break;
5719
            case 'POST,GET':
5720
                $currentQueryArray = GeneralUtility::_POST();
5721
                ArrayUtility::mergeRecursiveWithOverrule($currentQueryArray, GeneralUtility::_GET());
5722
                break;
5723
            default:
5724
                $currentQueryArray = GeneralUtility::explodeUrl2Array($this->getEnvironmentVariable('QUERY_STRING'), true);
5725
        }
5726
        if ($conf['exclude']) {
5727
            $exclude = str_replace(',', '&', $conf['exclude']);
5728
            $exclude = GeneralUtility::explodeUrl2Array($exclude, true);
5729
            // never repeat id
5730
            $exclude['id'] = 0;
5731
            $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

5731
            $newQueryArray = ArrayUtility::arrayDiffAssocRecursive(/** @scrutinizer ignore-type */ $currentQueryArray, $exclude);
Loading history...
5732
        } else {
5733
            $newQueryArray = $currentQueryArray;
5734
        }
5735
        if ($forceOverruleArguments) {
5736
            ArrayUtility::mergeRecursiveWithOverrule($newQueryArray, $overruleQueryArguments);
5737
        } else {
5738
            ArrayUtility::mergeRecursiveWithOverrule($newQueryArray, $overruleQueryArguments, false);
5739
        }
5740
        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

5740
        return GeneralUtility::implodeArrayForUrl('', /** @scrutinizer ignore-type */ $newQueryArray, '', false, true);
Loading history...
5741
    }
5742
5743
    /***********************************************
5744
     *
5745
     * Miscellaneous functions, stand alone
5746
     *
5747
     ***********************************************/
5748
    /**
5749
     * Wrapping a string.
5750
     * Implements the TypoScript "wrap" property.
5751
     * Example: $content = "HELLO WORLD" and $wrap = "<strong> | </strong>", result: "<strong>HELLO WORLD</strong>"
5752
     *
5753
     * @param string $content The content to wrap
5754
     * @param string $wrap The wrap value, eg. "<strong> | </strong>
5755
     * @param string $char The char used to split the wrapping value, default is "|
5756
     * @return string Wrapped input string
5757
     * @see noTrimWrap()
5758
     */
5759
    public function wrap($content, $wrap, $char = '|')
5760
    {
5761
        if ($wrap) {
5762
            $wrapArr = explode($char, $wrap);
5763
            $content = trim($wrapArr[0]) . $content . trim($wrapArr[1]);
5764
        }
5765
        return $content;
5766
    }
5767
5768
    /**
5769
     * Wrapping a string, preserving whitespace in wrap value.
5770
     * Notice that the wrap value uses part 1/2 to wrap (and not 0/1 which wrap() does)
5771
     *
5772
     * @param string $content The content to wrap, eg. "HELLO WORLD
5773
     * @param string $wrap The wrap value, eg. " | <strong> | </strong>
5774
     * @param string $char The char used to split the wrapping value, default is "|"
5775
     * @return string Wrapped input string, eg. " <strong> HELLO WORD </strong>
5776
     * @see wrap()
5777
     */
5778
    public function noTrimWrap($content, $wrap, $char = '|')
5779
    {
5780
        if ($wrap) {
5781
            // expects to be wrapped with (at least) 3 characters (before, middle, after)
5782
            // anything else is not taken into account
5783
            $wrapArr = explode($char, $wrap, 4);
5784
            $content = $wrapArr[1] . $content . $wrapArr[2];
5785
        }
5786
        return $content;
5787
    }
5788
5789
    /**
5790
     * Calling a user function/class-method
5791
     * Notice: For classes the instantiated object will have the internal variable, $cObj, set to be a *reference* to $this (the parent/calling object).
5792
     *
5793
     * @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.
5794
     * @param array $conf The TypoScript configuration to pass the function
5795
     * @param string $content The content string to pass the function
5796
     * @return string The return content from the function call. Should probably be a string.
5797
     * @see USER(), stdWrap(), typoLink(), _parseFunc()
5798
     */
5799
    public function callUserFunction($funcName, $conf, $content)
5800
    {
5801
        // Split parts
5802
        $parts = explode('->', $funcName);
5803
        if (count($parts) === 2) {
5804
            // Check whether PHP class is available
5805
            if (class_exists($parts[0])) {
5806
                $classObj = GeneralUtility::makeInstance($parts[0]);
5807
                if (is_object($classObj) && method_exists($classObj, $parts[1])) {
5808
                    $classObj->cObj = $this;
5809
                    $content = call_user_func_array([
5810
                        $classObj,
5811
                        $parts[1]
5812
                    ], [
5813
                        $content,
5814
                        $conf
5815
                    ]);
5816
                } else {
5817
                    $this->getTimeTracker()->setTSlogMessage('Method "' . $parts[1] . '" did not exist in class "' . $parts[0] . '"', 3);
5818
                }
5819
            } else {
5820
                $this->getTimeTracker()->setTSlogMessage('Class "' . $parts[0] . '" did not exist', 3);
5821
            }
5822
        } elseif (function_exists($funcName)) {
5823
            $content = call_user_func($funcName, $content, $conf);
5824
        } else {
5825
            $this->getTimeTracker()->setTSlogMessage('Function "' . $funcName . '" did not exist', 3);
5826
        }
5827
        return $content;
5828
    }
5829
5830
    /**
5831
     * Cleans up a string of keywords. Keywords at splitted by "," (comma)  ";" (semi colon) and linebreak
5832
     *
5833
     * @param string $content String of keywords
5834
     * @return string Cleaned up string, keywords will be separated by a comma only.
5835
     */
5836
    public function keywords($content)
5837
    {
5838
        $listArr = preg_split('/[,;' . LF . ']/', $content);
5839
        foreach ($listArr as $k => $v) {
5840
            $listArr[$k] = trim($v);
5841
        }
5842
        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

5842
        return implode(',', /** @scrutinizer ignore-type */ $listArr);
Loading history...
5843
    }
5844
5845
    /**
5846
     * Changing character case of a string, converting typically used western charset characters as well.
5847
     *
5848
     * @param string $theValue The string to change case for.
5849
     * @param string $case The direction; either "upper" or "lower
5850
     * @return string
5851
     * @see HTMLcaseshift()
5852
     */
5853
    public function caseshift($theValue, $case)
5854
    {
5855
        switch (strtolower($case)) {
5856
            case 'upper':
5857
                $theValue = mb_strtoupper($theValue, 'utf-8');
5858
                break;
5859
            case 'lower':
5860
                $theValue = mb_strtolower($theValue, 'utf-8');
5861
                break;
5862
            case 'capitalize':
5863
                $theValue = mb_convert_case($theValue, MB_CASE_TITLE, 'utf-8');
5864
                break;
5865
            case 'ucfirst':
5866
                $firstChar = mb_substr($theValue, 0, 1, 'utf-8');
5867
                $firstChar = mb_strtoupper($firstChar, 'utf-8');
5868
                $remainder = mb_substr($theValue, 1, null, 'utf-8');
5869
                $theValue = $firstChar . $remainder;
5870
                break;
5871
            case 'lcfirst':
5872
                $firstChar = mb_substr($theValue, 0, 1, 'utf-8');
5873
                $firstChar = mb_strtolower($firstChar, 'utf-8');
5874
                $remainder = mb_substr($theValue, 1, null, 'utf-8');
5875
                $theValue = $firstChar . $remainder;
5876
                break;
5877
            case 'uppercamelcase':
5878
                $theValue = GeneralUtility::underscoredToUpperCamelCase($theValue);
5879
                break;
5880
            case 'lowercamelcase':
5881
                $theValue = GeneralUtility::underscoredToLowerCamelCase($theValue);
5882
                break;
5883
        }
5884
        return $theValue;
5885
    }
5886
5887
    /**
5888
     * Shifts the case of characters outside of HTML tags in the input string
5889
     *
5890
     * @param string $theValue The string to change case for.
5891
     * @param string $case The direction; either "upper" or "lower
5892
     * @return string
5893
     * @see caseshift()
5894
     */
5895
    public function HTMLcaseshift($theValue, $case)
5896
    {
5897
        $inside = 0;
5898
        $newVal = '';
5899
        $pointer = 0;
5900
        $totalLen = strlen($theValue);
5901
        do {
5902
            if (!$inside) {
5903
                $len = strcspn(substr($theValue, $pointer), '<');
5904
                $newVal .= $this->caseshift(substr($theValue, $pointer, $len), $case);
5905
                $inside = 1;
5906
            } else {
5907
                $len = strcspn(substr($theValue, $pointer), '>') + 1;
5908
                $newVal .= substr($theValue, $pointer, $len);
5909
                $inside = 0;
5910
            }
5911
            $pointer += $len;
5912
        } while ($pointer < $totalLen);
5913
        return $newVal;
5914
    }
5915
5916
    /**
5917
     * Returns the 'age' of the tstamp $seconds
5918
     *
5919
     * @param int $seconds Seconds to return age for. Example: "70" => "1 min", "3601" => "1 hrs
5920
     * @param string $labels The labels of the individual units. Defaults to : ' min| hrs| days| yrs'
5921
     * @return string The formatted string
5922
     */
5923
    public function calcAge($seconds, $labels)
5924
    {
5925
        if (MathUtility::canBeInterpretedAsInteger($labels)) {
5926
            $labels = ' min| hrs| days| yrs| min| hour| day| year';
5927
        } else {
5928
            $labels = str_replace('"', '', $labels);
5929
        }
5930
        $labelArr = explode('|', $labels);
5931
        if (count($labelArr) === 4) {
5932
            $labelArr = array_merge($labelArr, $labelArr);
5933
        }
5934
        $absSeconds = abs($seconds);
5935
        $sign = $seconds > 0 ? 1 : -1;
5936
        if ($absSeconds < 3600) {
5937
            $val = round($absSeconds / 60);
5938
            $seconds = $sign * $val . ($val == 1 ? $labelArr[4] : $labelArr[0]);
5939
        } elseif ($absSeconds < 24 * 3600) {
5940
            $val = round($absSeconds / 3600);
5941
            $seconds = $sign * $val . ($val == 1 ? $labelArr[5] : $labelArr[1]);
5942
        } elseif ($absSeconds < 365 * 24 * 3600) {
5943
            $val = round($absSeconds / (24 * 3600));
5944
            $seconds = $sign * $val . ($val == 1 ? $labelArr[6] : $labelArr[2]);
5945
        } else {
5946
            $val = round($absSeconds / (365 * 24 * 3600));
5947
            $seconds = $sign * $val . ($val == 1 ? $labelArr[7] : $labelArr[3]);
5948
        }
5949
        return $seconds;
5950
    }
5951
5952
    /**
5953
     * Sends a notification email
5954
     *
5955
     * @param string $message The message content. If blank, no email is sent.
5956
     * @param string $recipients Comma list of recipient email addresses
5957
     * @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.
5958
     * @param string $senderAddress "From" email address
5959
     * @param string $senderName Optional "From" name
5960
     * @param string $replyTo Optional "Reply-To" header email address.
5961
     * @return bool Returns TRUE if sent
5962
     */
5963
    public function sendNotifyEmail($message, $recipients, $cc, $senderAddress, $senderName = '', $replyTo = '')
5964
    {
5965
        /** @var $mail MailMessage */
5966
        $mail = GeneralUtility::makeInstance(MailMessage::class);
5967
        $senderName = trim($senderName);
5968
        $senderAddress = trim($senderAddress);
5969
        if ($senderName !== '' && $senderAddress !== '') {
5970
            $mail->setFrom([$senderAddress => $senderName]);
5971
        } elseif ($senderAddress !== '') {
5972
            $mail->setFrom([$senderAddress]);
5973
        }
5974
        $parsedReplyTo = MailUtility::parseAddresses($replyTo);
5975
        if (!empty($parsedReplyTo)) {
5976
            $mail->setReplyTo($parsedReplyTo);
5977
        }
5978
        $message = trim($message);
5979
        if ($message !== '') {
5980
            // First line is subject
5981
            $messageParts = explode(LF, $message, 2);
5982
            $subject = trim($messageParts[0]);
5983
            $plainMessage = trim($messageParts[1]);
5984
            $parsedRecipients = MailUtility::parseAddresses($recipients);
5985
            if (!empty($parsedRecipients)) {
5986
                $mail->setTo($parsedRecipients)
5987
                    ->setSubject($subject)
5988
                    ->setBody($plainMessage);
5989
                $mail->send();
5990
            }
5991
            $parsedCc = MailUtility::parseAddresses($cc);
5992
            if (!empty($parsedCc)) {
5993
                $from = $mail->getFrom();
5994
                /** @var $mail MailMessage */
5995
                $mail = GeneralUtility::makeInstance(MailMessage::class);
5996
                if (!empty($parsedReplyTo)) {
5997
                    $mail->setReplyTo($parsedReplyTo);
5998
                }
5999
                $mail->setFrom($from)
6000
                    ->setTo($parsedCc)
6001
                    ->setSubject($subject)
6002
                    ->setBody($plainMessage);
6003
                $mail->send();
6004
            }
6005
            return true;
6006
        }
6007
        return false;
6008
    }
6009
6010
    /**
6011
     * Resolves a TypoScript reference value to the full set of properties BUT overridden with any local properties set.
6012
     * So the reference is resolved but overlaid with local TypoScript properties of the reference value.
6013
     *
6014
     * @param array $confArr The TypoScript array
6015
     * @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.
6016
     * @return array The modified TypoScript array
6017
     */
6018
    public function mergeTSRef($confArr, $prop)
6019
    {
6020
        if ($confArr[$prop][0] === '<') {
6021
            $key = trim(substr($confArr[$prop], 1));
6022
            $cF = GeneralUtility::makeInstance(TypoScriptParser::class);
6023
            // $name and $conf is loaded with the referenced values.
6024
            $old_conf = $confArr[$prop . '.'];
6025
            list(, $conf) = $cF->getVal($key, $this->getTypoScriptFrontendController()->tmpl->setup);
6026
            if (is_array($old_conf) && !empty($old_conf)) {
6027
                $conf = is_array($conf) ? array_replace_recursive($conf, $old_conf) : $old_conf;
6028
            }
6029
            $confArr[$prop . '.'] = $conf;
6030
        }
6031
        return $confArr;
6032
    }
6033
6034
    /***********************************************
6035
     *
6036
     * Database functions, making of queries
6037
     *
6038
     ***********************************************/
6039
6040
    /**
6041
     * Returns a part of a WHERE clause which will filter out records with start/end times or hidden/fe_groups fields
6042
     * set to values that should de-select them according to the current time, preview settings or user login.
6043
     * Definitely a frontend function.
6044
     * THIS IS A VERY IMPORTANT FUNCTION: Basically you must add the output from this function for EVERY select query you create
6045
     * for selecting records of tables in your own applications - thus they will always be filtered according to the "enablefields"
6046
     * configured in TCA
6047
     * Simply calls \TYPO3\CMS\Frontend\Page\PageRepository::enableFields() BUT will send the show_hidden flag along!
6048
     * This means this function will work in conjunction with the preview facilities of the frontend engine/Admin Panel.
6049
     *
6050
     * @param string $table The table for which to get the where clause
6051
     * @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.
6052
     * @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.
6053
     * @return string The part of the where clause on the form " AND [fieldname]=0 AND ...". Eg. " AND hidden=0 AND starttime < 123345567
6054
     */
6055
    public function enableFields($table, $show_hidden = false, array $ignore_array = [])
6056
    {
6057
        $tsfe = $this->getTypoScriptFrontendController();
6058
        $show_hidden = $show_hidden ?: ($table === 'pages' ? $tsfe->showHiddenPage : $tsfe->showHiddenRecords);
6059
        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

6059
        return $tsfe->sys_page->enableFields($table, /** @scrutinizer ignore-type */ (bool)$show_hidden, $ignore_array);
Loading history...
6060
    }
6061
6062
    /**
6063
     * Generates a list of Page-uid's from $id. List does not include $id itself
6064
     * (unless the id specified is negative in which case it does!)
6065
     * The only pages WHICH PREVENTS DECENDING in a branch are
6066
     * - deleted pages,
6067
     * - pages in a recycler (doktype = 255) or of the Backend User Section (doktpe = 6) type
6068
     * - pages that has the extendToSubpages set, WHERE start/endtime, hidden
6069
     * and fe_users would hide the records.
6070
     * Apart from that, pages with enable-fields excluding them, will also be
6071
     * removed. HOWEVER $dontCheckEnableFields set will allow
6072
     * enableFields-excluded pages to be included anyway - including
6073
     * extendToSubpages sections!
6074
     * Mount Pages are also descended but notice that these ID numbers are not
6075
     * useful for links unless the correct MPvar is set.
6076
     *
6077
     * @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!
6078
     * @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...)
6079
     * @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'
6080
     * @param bool $dontCheckEnableFields See function description
6081
     * @param string $addSelectFields Additional fields to select. Syntax: ",[fieldname],[fieldname],...
6082
     * @param string $moreWhereClauses Additional where clauses. Syntax: " AND [fieldname]=[value] AND ...
6083
     * @param array $prevId_array array of IDs from previous recursions. In order to prevent infinite loops with mount pages.
6084
     * @param int $recursionLevel Internal: Zero for the first recursion, incremented for each recursive call.
6085
     * @return string Returns the list of ids as a comma separated string
6086
     * @see TypoScriptFrontendController::checkEnableFields(), TypoScriptFrontendController::checkPagerecordForIncludeSection()
6087
     */
6088
    public function getTreeList($id, $depth, $begin = 0, $dontCheckEnableFields = false, $addSelectFields = '', $moreWhereClauses = '', array $prevId_array = [], $recursionLevel = 0)
6089
    {
6090
        $id = (int)$id;
6091
        if (!$id) {
6092
            return '';
6093
        }
6094
6095
        // Init vars:
6096
        $allFields = 'uid,hidden,starttime,endtime,fe_group,extendToSubpages,doktype,php_tree_stop,mount_pid,mount_pid_ol,t3ver_state' . $addSelectFields;
6097
        $depth = (int)$depth;
6098
        $begin = (int)$begin;
6099
        $theList = [];
6100
        $addId = 0;
6101
        $requestHash = '';
6102
6103
        // First level, check id (second level, this is done BEFORE the recursive call)
6104
        $tsfe = $this->getTypoScriptFrontendController();
6105
        if (!$recursionLevel) {
6106
            // Check tree list cache
6107
            // First, create the hash for this request - not sure yet whether we need all these parameters though
6108
            $parameters = [
6109
                $id,
6110
                $depth,
6111
                $begin,
6112
                $dontCheckEnableFields,
6113
                $addSelectFields,
6114
                $moreWhereClauses,
6115
                $prevId_array,
6116
                $tsfe->gr_list
6117
            ];
6118
            $requestHash = md5(serialize($parameters));
6119
            $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
6120
                ->getQueryBuilderForTable('cache_treelist');
6121
            $cacheEntry = $queryBuilder->select('treelist')
6122
                ->from('cache_treelist')
6123
                ->where(
6124
                    $queryBuilder->expr()->eq(
6125
                        'md5hash',
6126
                        $queryBuilder->createNamedParameter($requestHash, \PDO::PARAM_STR)
6127
                    ),
6128
                    $queryBuilder->expr()->orX(
6129
                        $queryBuilder->expr()->gt(
6130
                            'expires',
6131
                            $queryBuilder->createNamedParameter($GLOBALS['EXEC_TIME'], \PDO::PARAM_INT)
6132
                        ),
6133
                        $queryBuilder->expr()->eq('expires', $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT))
6134
                    )
6135
                )
6136
                ->setMaxResults(1)
6137
                ->execute()
6138
                ->fetch();
6139
6140
            if (is_array($cacheEntry)) {
6141
                // Cache hit
6142
                return $cacheEntry['treelist'];
6143
            }
6144
            // If Id less than zero it means we should add the real id to list:
6145
            if ($id < 0) {
6146
                $addId = $id = abs($id);
6147
            }
6148
            // Check start page:
6149
            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

6149
            if ($tsfe->sys_page->getRawRecord('pages', /** @scrutinizer ignore-type */ $id, 'uid')) {
Loading history...
6150
                // Find mount point if any:
6151
                $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

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

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